feat : Composant Bouton
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { type PropsWithChildren, useMemo } from 'react';
|
||||
|
||||
import colors from './colors';
|
||||
import { colors, glow, gradientDirection, gradients, palette } from './colors';
|
||||
import spacing, { borderRadius } from './spacing';
|
||||
import typography from './typography';
|
||||
import { ThemeContext } from './useTheme';
|
||||
@@ -9,6 +9,10 @@ export function ThemeProvider({ children }: PropsWithChildren) {
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
colors,
|
||||
gradients,
|
||||
glow,
|
||||
gradientDirection,
|
||||
palette,
|
||||
typography,
|
||||
spacing,
|
||||
borderRadius,
|
||||
|
||||
@@ -1,17 +1,173 @@
|
||||
export const colors = {
|
||||
deepBackground: '#0A0A12',
|
||||
surface: '#14141F',
|
||||
surfaceElevated: '#1E1E2D',
|
||||
border: '#2A2A3C',
|
||||
primary: '#FF2E88',
|
||||
secondary: '#7B3DFF',
|
||||
tertiary: '#28E0F0',
|
||||
success: '#3DDC97',
|
||||
textPrimary: '#F4F4FA',
|
||||
textSecondary: '#9A9AB4',
|
||||
signatureGradient: ['#7B3DFF', '#FF2E88'] as const,
|
||||
import type { ColorValue, ViewStyle } from 'react-native';
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Palette brute — 1:1 avec les tokens :root de la maquette
|
||||
* ------------------------------------------------------------------ */
|
||||
export const palette = {
|
||||
/** Fond profond de l'app */
|
||||
ink900: '#0A0A12',
|
||||
/** Fond du canvas hors écran (planche de style / desktop) */
|
||||
ink950: '#06060C',
|
||||
/** Surface / carte */
|
||||
ink800: '#14141F',
|
||||
/** Surface élevée (champ, bouton secondaire, sheet) */
|
||||
ink700: '#1E1E2D',
|
||||
/** Bordure subtile */
|
||||
ink600: '#2A2A3C',
|
||||
|
||||
magenta: '#FF2E88',
|
||||
/** Magenta pressé — extrémité du dégradé assombri */
|
||||
magentaPressed: '#E5216F',
|
||||
/** Teinte magenta claire (texte sur fond magenta translucide) */
|
||||
magentaTint: '#FFB4D4',
|
||||
|
||||
violet: '#7B3DFF',
|
||||
violetPressed: '#6A2FE6',
|
||||
violetTint: '#C9B6FF',
|
||||
|
||||
cyan: '#28E0F0',
|
||||
green: '#3DDC97',
|
||||
/** Ambre — seule teinte chaude du système (placeholders, alertes) */
|
||||
amber: '#FFC478',
|
||||
|
||||
white: '#F4F4FA',
|
||||
grey100: '#D6D6E6',
|
||||
grey200: '#C9C9DC',
|
||||
grey400: '#9A9AB4',
|
||||
} as const;
|
||||
|
||||
export const signatureGradient = colors.signatureGradient;
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Tokens sémantiques
|
||||
* ------------------------------------------------------------------ */
|
||||
export const colors = {
|
||||
/* --- Fonds & surfaces ------------------------------------------- */
|
||||
deepBackground: palette.ink900,
|
||||
/** Fond hors-écran / derrière les sheets */
|
||||
canvas: palette.ink950,
|
||||
surface: palette.ink800,
|
||||
surfaceElevated: palette.ink700,
|
||||
border: palette.ink600,
|
||||
/** Séparateur intra-carte, plus léger que `border` */
|
||||
divider: 'rgba(255,255,255,0.12)',
|
||||
/** Bloc squelette de chargement */
|
||||
skeleton: 'rgba(244,244,250,0.07)',
|
||||
/** Piste de barre de progression / slider */
|
||||
track: 'rgba(244,244,250,0.28)',
|
||||
|
||||
/* --- Accents ---------------------------------------------------- */
|
||||
primary: palette.magenta,
|
||||
primaryPressed: palette.magentaPressed,
|
||||
/** Fond de badge / chip actif teinté magenta */
|
||||
primarySoft: 'rgba(255,46,136,0.14)',
|
||||
/** Bordure de bouton destructif */
|
||||
primaryBorder: 'rgba(255,46,136,0.55)',
|
||||
onPrimary: '#FFFFFF',
|
||||
|
||||
secondary: palette.violet,
|
||||
secondaryPressed: palette.violetPressed,
|
||||
secondarySoft: 'rgba(123,61,255,0.14)',
|
||||
|
||||
tertiary: palette.cyan,
|
||||
/** Fond de badge teinté cyan (line-up, « en cours ») */
|
||||
tertiarySoft: 'rgba(40,224,240,0.12)',
|
||||
|
||||
/* --- États ------------------------------------------------------ */
|
||||
success: palette.green,
|
||||
successSoft: 'rgba(61,220,151,0.14)',
|
||||
successBorder: 'rgba(61,220,151,0.5)',
|
||||
/** Pas de rouge dans le système : le destructif emprunte le magenta */
|
||||
danger: palette.magenta,
|
||||
dangerSoft: 'rgba(255,46,136,0.14)',
|
||||
warning: palette.amber,
|
||||
warningSoft: 'rgba(255,196,120,0.14)',
|
||||
|
||||
/* --- Texte ------------------------------------------------------ */
|
||||
textPrimary: palette.white,
|
||||
/** Titres et libellés secondaires (le plus utilisé après textPrimary) */
|
||||
textStrong: palette.grey100,
|
||||
/** Corps de texte long — jamais du blanc pur sur fond sombre */
|
||||
textBody: palette.grey200,
|
||||
textSecondary: palette.grey400,
|
||||
textOnAccent: '#FFFFFF',
|
||||
textOnPrimarySoft: palette.magentaTint,
|
||||
textOnSecondarySoft: palette.violetTint,
|
||||
/** Texte désactivé (bouton off) — appliquer aussi opacity 0.45 */
|
||||
textDisabled: palette.grey400,
|
||||
|
||||
/* --- Voiles sur média ------------------------------------------- */
|
||||
/** Header / tab bar en blur */
|
||||
scrimHeader: 'rgba(10,10,18,0.94)',
|
||||
/** Bas d'image de couverture (B4, C3) */
|
||||
scrimHeavy: 'rgba(10,10,18,0.86)',
|
||||
scrimMedium: 'rgba(10,10,18,0.6)',
|
||||
scrimLight: 'rgba(10,10,18,0.15)',
|
||||
/** Bouton flottant sur photo */
|
||||
glassFill: 'rgba(255,255,255,0.14)',
|
||||
|
||||
/* --- Carte (B7) ------------------------------------------------- */
|
||||
mapBackground: '#0C1424',
|
||||
mapWater: '#101A2E',
|
||||
mapLand: '#0A0E1A',
|
||||
} as const;
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Dégradés — tuples pour expo-linear-gradient.
|
||||
* La maquette utilise 135deg (haut-gauche → bas-droite) : d'où
|
||||
* start {0,0} / end {1,1}.
|
||||
* ------------------------------------------------------------------ */
|
||||
export const gradientDirection = {
|
||||
start: { x: 0.2, y: 0 },
|
||||
end: { x: 0.8, y: 1 },
|
||||
} as const;
|
||||
|
||||
export const gradients = {
|
||||
/** Dégradé signature — bouton primaire, FAB, logo */
|
||||
signature: [palette.violet, palette.magenta],
|
||||
/** Même dégradé à l'état pressé */
|
||||
signaturePressed: [palette.violetPressed, palette.magentaPressed],
|
||||
/** Voile vertical sur image de couverture (start {0,0} end {0,1}) */
|
||||
coverScrim: [
|
||||
colors.scrimMedium,
|
||||
colors.scrimLight,
|
||||
'rgba(10,10,18,0.98)',
|
||||
],
|
||||
} as const satisfies Record<string, readonly ColorValue[]>;
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Glow — les box-shadow CSS n'existent pas en RN.
|
||||
* iOS lit shadow*, Android ne lit que elevation (et ignore la couleur
|
||||
* avant API 28) : le glow rose y sera un simple relief.
|
||||
* ------------------------------------------------------------------ */
|
||||
export const glow = {
|
||||
/** --gl · sous un bouton primaire / une carte magenta */
|
||||
primary: {
|
||||
shadowColor: palette.magenta,
|
||||
shadowOffset: { width: 0, height: 12 },
|
||||
shadowOpacity: 0.55,
|
||||
shadowRadius: 25,
|
||||
elevation: 12,
|
||||
},
|
||||
/** --glv · variante violette */
|
||||
violet: {
|
||||
shadowColor: palette.violet,
|
||||
shadowOffset: { width: 0, height: 12 },
|
||||
shadowOpacity: 0.6,
|
||||
shadowRadius: 25,
|
||||
elevation: 12,
|
||||
},
|
||||
/** Ombre neutre de carte élevée */
|
||||
card: {
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 8 },
|
||||
shadowOpacity: 0.5,
|
||||
shadowRadius: 18,
|
||||
elevation: 6,
|
||||
},
|
||||
} as const satisfies Record<string, ViewStyle>;
|
||||
|
||||
export type ColorToken = keyof typeof colors;
|
||||
|
||||
/** @deprecated utiliser `gradients.signature` */
|
||||
export const signatureGradient = gradients.signature;
|
||||
|
||||
export default colors;
|
||||
|
||||
@@ -1,67 +1,226 @@
|
||||
import { Inter_400Regular, Inter_500Medium } from '@expo-google-fonts/inter';
|
||||
import { SpaceGrotesk_600SemiBold, SpaceGrotesk_700Bold } from '@expo-google-fonts/space-grotesk';
|
||||
import {
|
||||
Inter_400Regular,
|
||||
Inter_500Medium,
|
||||
Inter_600SemiBold,
|
||||
Inter_700Bold,
|
||||
} from '@expo-google-fonts/inter';
|
||||
import {
|
||||
JetBrainsMono_400Regular,
|
||||
JetBrainsMono_500Medium,
|
||||
JetBrainsMono_700Bold,
|
||||
} from '@expo-google-fonts/jetbrains-mono';
|
||||
import {
|
||||
SpaceGrotesk_600SemiBold,
|
||||
SpaceGrotesk_700Bold,
|
||||
} from '@expo-google-fonts/space-grotesk';
|
||||
import { useFonts } from 'expo-font';
|
||||
import type { TextStyle } from 'react-native';
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Familles
|
||||
* Maquette Bahla : Space Grotesk (display) / Inter (UI) / JetBrains Mono
|
||||
* (labels + chiffres). Le mono n'est PAS la mono système : c'est un
|
||||
* choix de marque, il doit être chargé.
|
||||
* ------------------------------------------------------------------ */
|
||||
export const fontFamily = {
|
||||
display: 'SpaceGrotesk_600SemiBold',
|
||||
displayBold: 'SpaceGrotesk_700Bold',
|
||||
body: 'Inter_400Regular',
|
||||
bodyMedium: 'Inter_500Medium',
|
||||
mono: 'monospace',
|
||||
bodySemi: 'Inter_600SemiBold',
|
||||
bodyBold: 'Inter_700Bold',
|
||||
mono: 'JetBrainsMono_400Regular',
|
||||
monoMedium: 'JetBrainsMono_500Medium',
|
||||
monoBold: 'JetBrainsMono_700Bold',
|
||||
} as const;
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Tokens
|
||||
* letterSpacing est en px sur RN (pas en em) : chaque valeur ci-dessous
|
||||
* est la conversion em × fontSize de la maquette.
|
||||
* ------------------------------------------------------------------ */
|
||||
export const typography = {
|
||||
display: {
|
||||
fontFamily: fontFamily.display,
|
||||
fontSize: 40,
|
||||
/* --- Display · Space Grotesk ------------------------------------ */
|
||||
hero: {
|
||||
fontFamily: fontFamily.displayBold,
|
||||
fontSize: 48,
|
||||
lineHeight: 48,
|
||||
letterSpacing: -0.8,
|
||||
letterSpacing: -1.44,
|
||||
},
|
||||
display: {
|
||||
fontFamily: fontFamily.displayBold,
|
||||
fontSize: 40,
|
||||
lineHeight: 42,
|
||||
letterSpacing: -1.2,
|
||||
},
|
||||
/** Grand titre d'écran (« Mes events », dashboard) */
|
||||
screenTitleLg: {
|
||||
fontFamily: fontFamily.displayBold,
|
||||
fontSize: 28,
|
||||
lineHeight: 32,
|
||||
letterSpacing: -0.56,
|
||||
},
|
||||
screenTitle: {
|
||||
fontFamily: fontFamily.displayBold,
|
||||
fontSize: 26,
|
||||
lineHeight: 32,
|
||||
letterSpacing: -0.6,
|
||||
lineHeight: 30,
|
||||
letterSpacing: -0.52,
|
||||
},
|
||||
/** Titre de bloc / nom d'artiste en tête de line-up */
|
||||
sectionTitle: {
|
||||
fontFamily: fontFamily.displayBold,
|
||||
fontSize: 20,
|
||||
lineHeight: 24,
|
||||
letterSpacing: -0.2,
|
||||
},
|
||||
cardTitle: {
|
||||
fontFamily: fontFamily.display,
|
||||
fontSize: 18,
|
||||
lineHeight: 24,
|
||||
letterSpacing: -0.2,
|
||||
lineHeight: 22,
|
||||
letterSpacing: 0,
|
||||
},
|
||||
sectionLabel: {
|
||||
fontFamily: fontFamily.mono,
|
||||
fontSize: 11,
|
||||
lineHeight: 16,
|
||||
letterSpacing: 0.8,
|
||||
textTransform: 'uppercase' as const,
|
||||
/** Titre de carte compacte (grille, slot de line-up) */
|
||||
cardTitleSm: {
|
||||
fontFamily: fontFamily.display,
|
||||
fontSize: 16,
|
||||
lineHeight: 20,
|
||||
letterSpacing: -0.16,
|
||||
},
|
||||
|
||||
/* --- Corps & UI · Inter ---------------------------------------- */
|
||||
body: {
|
||||
fontFamily: fontFamily.body,
|
||||
fontSize: 15,
|
||||
lineHeight: 24,
|
||||
letterSpacing: 0,
|
||||
},
|
||||
bodySm: {
|
||||
fontFamily: fontFamily.body,
|
||||
fontSize: 14,
|
||||
lineHeight: 22,
|
||||
letterSpacing: 0,
|
||||
},
|
||||
/** Libellé de bouton — 52 px de haut, rayon 14 px */
|
||||
button: {
|
||||
fontFamily: fontFamily.bodySemi,
|
||||
fontSize: 15,
|
||||
lineHeight: 20,
|
||||
letterSpacing: 0,
|
||||
},
|
||||
/** Nom d'organisateur, en-tête de ligne, valeur de champ */
|
||||
label: {
|
||||
fontFamily: fontFamily.bodySemi,
|
||||
fontSize: 13,
|
||||
lineHeight: 18,
|
||||
letterSpacing: 0,
|
||||
},
|
||||
metadata: {
|
||||
fontFamily: fontFamily.bodyMedium,
|
||||
fontSize: 13,
|
||||
lineHeight: 18,
|
||||
letterSpacing: 0,
|
||||
},
|
||||
/** Texte de chip / filtre */
|
||||
chip: {
|
||||
fontFamily: fontFamily.bodySemi,
|
||||
fontSize: 12,
|
||||
lineHeight: 16,
|
||||
letterSpacing: 0,
|
||||
},
|
||||
metadataSm: {
|
||||
fontFamily: fontFamily.bodyMedium,
|
||||
fontSize: 12,
|
||||
lineHeight: 16,
|
||||
letterSpacing: 0,
|
||||
},
|
||||
caption: {
|
||||
fontFamily: fontFamily.body,
|
||||
fontSize: 11,
|
||||
lineHeight: 15,
|
||||
letterSpacing: 0,
|
||||
},
|
||||
/** Libellé de la tab bar (4 onglets) */
|
||||
tabLabel: {
|
||||
fontFamily: fontFamily.bodyMedium,
|
||||
fontSize: 10,
|
||||
lineHeight: 13,
|
||||
letterSpacing: 0,
|
||||
},
|
||||
|
||||
/* --- Labels & chiffres · JetBrains Mono ------------------------- */
|
||||
/** Overline de rangée / d'écran sur la planche de style */
|
||||
overlineLg: {
|
||||
fontFamily: fontFamily.monoMedium,
|
||||
fontSize: 13,
|
||||
lineHeight: 17,
|
||||
letterSpacing: 1.82,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
/** Libellé de section dans l'app */
|
||||
sectionLabel: {
|
||||
fontFamily: fontFamily.monoMedium,
|
||||
fontSize: 11,
|
||||
lineHeight: 14,
|
||||
letterSpacing: 1.76,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
/** Overline resserré (cartouches, en-têtes de colonne) */
|
||||
overlineSm: {
|
||||
fontFamily: fontFamily.monoMedium,
|
||||
fontSize: 10,
|
||||
lineHeight: 13,
|
||||
letterSpacing: 1.8,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
/** Badge / pastille (« COMPLET », « CE SOIR ») */
|
||||
badge: {
|
||||
fontFamily: fontFamily.monoMedium,
|
||||
fontSize: 9,
|
||||
lineHeight: 12,
|
||||
letterSpacing: 1.62,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
/** Chiffres, horaires, prix, distances — 22:30 · 25 CHF · 18 km */
|
||||
digits: {
|
||||
fontFamily: fontFamily.mono,
|
||||
fontSize: 11,
|
||||
lineHeight: 16,
|
||||
letterSpacing: 0.4,
|
||||
lineHeight: 14,
|
||||
letterSpacing: 0,
|
||||
},
|
||||
} as const;
|
||||
digitsSm: {
|
||||
fontFamily: fontFamily.mono,
|
||||
fontSize: 10,
|
||||
lineHeight: 13,
|
||||
letterSpacing: 0,
|
||||
},
|
||||
/** Grand chiffre de statistique (dashboard organisateur) */
|
||||
statValue: {
|
||||
fontFamily: fontFamily.displayBold,
|
||||
fontSize: 24,
|
||||
lineHeight: 26,
|
||||
letterSpacing: -0.48,
|
||||
},
|
||||
} as const satisfies Record<string, TextStyle>;
|
||||
|
||||
export const loadFonts = () =>
|
||||
export type TypographyToken = keyof typeof typography;
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Chargement — hook, donc préfixe `use` (règle react-hooks).
|
||||
* ------------------------------------------------------------------ */
|
||||
export const useAppFonts = () =>
|
||||
useFonts({
|
||||
SpaceGrotesk_600SemiBold,
|
||||
SpaceGrotesk_700Bold,
|
||||
Inter_400Regular,
|
||||
Inter_500Medium,
|
||||
Inter_600SemiBold,
|
||||
Inter_700Bold,
|
||||
JetBrainsMono_400Regular,
|
||||
JetBrainsMono_500Medium,
|
||||
JetBrainsMono_700Bold,
|
||||
});
|
||||
|
||||
/** @deprecated conservé pour compat — utiliser `useAppFonts` */
|
||||
export const loadFonts = useAppFonts;
|
||||
|
||||
export default typography;
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
import colors from './colors';
|
||||
import { colors, glow, gradientDirection, gradients, palette } from './colors';
|
||||
import spacing, { borderRadius } from './spacing';
|
||||
import typography from './typography';
|
||||
|
||||
export type ThemeContextValue = {
|
||||
colors: typeof colors;
|
||||
gradients: typeof gradients;
|
||||
glow: typeof glow;
|
||||
gradientDirection: typeof gradientDirection;
|
||||
palette: typeof palette;
|
||||
typography: typeof typography;
|
||||
spacing: typeof spacing;
|
||||
borderRadius: typeof borderRadius;
|
||||
|
||||
Reference in New Issue
Block a user