@metacells/mcellui-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +50 -0
- package/src/components/ErrorBoundary.tsx +188 -0
- package/src/components/index.ts +6 -0
- package/src/config/ConfigProvider.tsx +117 -0
- package/src/config/defineConfig.ts +75 -0
- package/src/config/index.ts +42 -0
- package/src/config/types.ts +185 -0
- package/src/constants.ts +203 -0
- package/src/index.ts +144 -0
- package/src/primitives/Portal.tsx +185 -0
- package/src/primitives/Pressable.tsx +114 -0
- package/src/primitives/Slot.tsx +177 -0
- package/src/primitives/index.ts +19 -0
- package/src/theme/ThemeProvider.tsx +475 -0
- package/src/theme/animations.ts +379 -0
- package/src/theme/colors.ts +266 -0
- package/src/theme/components.ts +267 -0
- package/src/theme/index.ts +130 -0
- package/src/theme/presets.ts +341 -0
- package/src/theme/radius.ts +175 -0
- package/src/theme/shadows.ts +166 -0
- package/src/theme/spacing.ts +41 -0
- package/src/theme/typography.ts +389 -0
- package/src/tokens/colors.ts +67 -0
- package/src/tokens/index.ts +29 -0
- package/src/tokens/radius.ts +18 -0
- package/src/tokens/shadows.ts +38 -0
- package/src/tokens/spacing.ts +45 -0
- package/src/tokens/typography.ts +70 -0
- package/src/utils/accessibility.ts +112 -0
- package/src/utils/cn.ts +58 -0
- package/src/utils/haptics.ts +125 -0
- package/src/utils/index.ts +28 -0
- package/src/utils/platform.ts +66 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Presets
|
|
3
|
+
*
|
|
4
|
+
* Pre-designed color schemes for instant brand alignment.
|
|
5
|
+
* Each preset defines primary colors and derives a full semantic palette.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```tsx
|
|
9
|
+
* <ThemeProvider theme="rose">
|
|
10
|
+
* <ThemeProvider theme="violet">
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { ThemeColors } from './colors';
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Color Palettes (Tailwind-inspired)
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
const palettes = {
|
|
21
|
+
zinc: {
|
|
22
|
+
50: '#fafafa',
|
|
23
|
+
100: '#f4f4f5',
|
|
24
|
+
200: '#e4e4e7',
|
|
25
|
+
300: '#d4d4d8',
|
|
26
|
+
400: '#a1a1aa',
|
|
27
|
+
500: '#71717a',
|
|
28
|
+
600: '#52525b',
|
|
29
|
+
700: '#3f3f46',
|
|
30
|
+
800: '#27272a',
|
|
31
|
+
900: '#18181b',
|
|
32
|
+
950: '#09090b',
|
|
33
|
+
},
|
|
34
|
+
slate: {
|
|
35
|
+
50: '#f8fafc',
|
|
36
|
+
100: '#f1f5f9',
|
|
37
|
+
200: '#e2e8f0',
|
|
38
|
+
300: '#cbd5e1',
|
|
39
|
+
400: '#94a3b8',
|
|
40
|
+
500: '#64748b',
|
|
41
|
+
600: '#475569',
|
|
42
|
+
700: '#334155',
|
|
43
|
+
800: '#1e293b',
|
|
44
|
+
900: '#0f172a',
|
|
45
|
+
950: '#020617',
|
|
46
|
+
},
|
|
47
|
+
stone: {
|
|
48
|
+
50: '#fafaf9',
|
|
49
|
+
100: '#f5f5f4',
|
|
50
|
+
200: '#e7e5e4',
|
|
51
|
+
300: '#d6d3d1',
|
|
52
|
+
400: '#a8a29e',
|
|
53
|
+
500: '#78716c',
|
|
54
|
+
600: '#57534e',
|
|
55
|
+
700: '#44403c',
|
|
56
|
+
800: '#292524',
|
|
57
|
+
900: '#1c1917',
|
|
58
|
+
950: '#0c0a09',
|
|
59
|
+
},
|
|
60
|
+
blue: {
|
|
61
|
+
50: '#eff6ff',
|
|
62
|
+
100: '#dbeafe',
|
|
63
|
+
200: '#bfdbfe',
|
|
64
|
+
300: '#93c5fd',
|
|
65
|
+
400: '#60a5fa',
|
|
66
|
+
500: '#3b82f6',
|
|
67
|
+
600: '#2563eb',
|
|
68
|
+
700: '#1d4ed8',
|
|
69
|
+
800: '#1e40af',
|
|
70
|
+
900: '#1e3a8a',
|
|
71
|
+
950: '#172554',
|
|
72
|
+
},
|
|
73
|
+
green: {
|
|
74
|
+
50: '#f0fdf4',
|
|
75
|
+
100: '#dcfce7',
|
|
76
|
+
200: '#bbf7d0',
|
|
77
|
+
300: '#86efac',
|
|
78
|
+
400: '#4ade80',
|
|
79
|
+
500: '#22c55e',
|
|
80
|
+
600: '#16a34a',
|
|
81
|
+
700: '#15803d',
|
|
82
|
+
800: '#166534',
|
|
83
|
+
900: '#14532d',
|
|
84
|
+
950: '#052e16',
|
|
85
|
+
},
|
|
86
|
+
rose: {
|
|
87
|
+
50: '#fff1f2',
|
|
88
|
+
100: '#ffe4e6',
|
|
89
|
+
200: '#fecdd3',
|
|
90
|
+
300: '#fda4af',
|
|
91
|
+
400: '#fb7185',
|
|
92
|
+
500: '#f43f5e',
|
|
93
|
+
600: '#e11d48',
|
|
94
|
+
700: '#be123c',
|
|
95
|
+
800: '#9f1239',
|
|
96
|
+
900: '#881337',
|
|
97
|
+
950: '#4c0519',
|
|
98
|
+
},
|
|
99
|
+
orange: {
|
|
100
|
+
50: '#fff7ed',
|
|
101
|
+
100: '#ffedd5',
|
|
102
|
+
200: '#fed7aa',
|
|
103
|
+
300: '#fdba74',
|
|
104
|
+
400: '#fb923c',
|
|
105
|
+
500: '#f97316',
|
|
106
|
+
600: '#ea580c',
|
|
107
|
+
700: '#c2410c',
|
|
108
|
+
800: '#9a3412',
|
|
109
|
+
900: '#7c2d12',
|
|
110
|
+
950: '#431407',
|
|
111
|
+
},
|
|
112
|
+
violet: {
|
|
113
|
+
50: '#f5f3ff',
|
|
114
|
+
100: '#ede9fe',
|
|
115
|
+
200: '#ddd6fe',
|
|
116
|
+
300: '#c4b5fd',
|
|
117
|
+
400: '#a78bfa',
|
|
118
|
+
500: '#8b5cf6',
|
|
119
|
+
600: '#7c3aed',
|
|
120
|
+
700: '#6d28d9',
|
|
121
|
+
800: '#5b21b6',
|
|
122
|
+
900: '#4c1d95',
|
|
123
|
+
950: '#2e1065',
|
|
124
|
+
},
|
|
125
|
+
} as const;
|
|
126
|
+
|
|
127
|
+
// Neutral palette (used for backgrounds, text, borders)
|
|
128
|
+
const neutral = {
|
|
129
|
+
0: '#ffffff',
|
|
130
|
+
50: '#fafafa',
|
|
131
|
+
100: '#f5f5f5',
|
|
132
|
+
200: '#e5e5e5',
|
|
133
|
+
300: '#d4d4d4',
|
|
134
|
+
400: '#a3a3a3',
|
|
135
|
+
500: '#737373',
|
|
136
|
+
600: '#525252',
|
|
137
|
+
700: '#404040',
|
|
138
|
+
800: '#262626',
|
|
139
|
+
900: '#171717',
|
|
140
|
+
950: '#0a0a0a',
|
|
141
|
+
} as const;
|
|
142
|
+
|
|
143
|
+
// =============================================================================
|
|
144
|
+
// Theme Preset Types
|
|
145
|
+
// =============================================================================
|
|
146
|
+
|
|
147
|
+
export type ThemePreset = 'zinc' | 'slate' | 'stone' | 'blue' | 'green' | 'rose' | 'orange' | 'violet';
|
|
148
|
+
|
|
149
|
+
export interface PresetColors {
|
|
150
|
+
light: ThemeColors;
|
|
151
|
+
dark: ThemeColors;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// =============================================================================
|
|
155
|
+
// Preset Generation
|
|
156
|
+
// =============================================================================
|
|
157
|
+
|
|
158
|
+
type ColorPalette = {
|
|
159
|
+
50: string;
|
|
160
|
+
100: string;
|
|
161
|
+
200: string;
|
|
162
|
+
300: string;
|
|
163
|
+
400: string;
|
|
164
|
+
500: string;
|
|
165
|
+
600: string;
|
|
166
|
+
700: string;
|
|
167
|
+
800: string;
|
|
168
|
+
900: string;
|
|
169
|
+
950: string;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Creates a complete color scheme from a primary color palette.
|
|
174
|
+
*/
|
|
175
|
+
function createPresetColors(
|
|
176
|
+
primaryPalette: ColorPalette,
|
|
177
|
+
neutralPalette: ColorPalette = palettes.zinc
|
|
178
|
+
): PresetColors {
|
|
179
|
+
return {
|
|
180
|
+
light: {
|
|
181
|
+
// Backgrounds
|
|
182
|
+
background: neutral[0],
|
|
183
|
+
backgroundSubtle: neutralPalette[50],
|
|
184
|
+
backgroundMuted: neutralPalette[100],
|
|
185
|
+
backgroundElevated: neutral[0],
|
|
186
|
+
|
|
187
|
+
// Foreground/Text
|
|
188
|
+
foreground: neutralPalette[900],
|
|
189
|
+
foregroundMuted: neutralPalette[500],
|
|
190
|
+
foregroundSubtle: neutralPalette[400],
|
|
191
|
+
|
|
192
|
+
// Borders
|
|
193
|
+
border: neutralPalette[200],
|
|
194
|
+
borderMuted: neutralPalette[100],
|
|
195
|
+
borderFocused: primaryPalette[500],
|
|
196
|
+
|
|
197
|
+
// Primary
|
|
198
|
+
primary: primaryPalette[500],
|
|
199
|
+
primaryForeground: neutral[0],
|
|
200
|
+
primaryMuted: primaryPalette[100],
|
|
201
|
+
|
|
202
|
+
// Secondary
|
|
203
|
+
secondary: neutralPalette[100],
|
|
204
|
+
secondaryForeground: neutralPalette[900],
|
|
205
|
+
|
|
206
|
+
// Destructive (always red)
|
|
207
|
+
destructive: '#ef4444',
|
|
208
|
+
destructiveForeground: neutral[0],
|
|
209
|
+
|
|
210
|
+
// Success (always green)
|
|
211
|
+
success: '#22c55e',
|
|
212
|
+
successForeground: neutral[0],
|
|
213
|
+
successMuted: '#dcfce7',
|
|
214
|
+
|
|
215
|
+
// Warning (always amber)
|
|
216
|
+
warning: '#f59e0b',
|
|
217
|
+
warningForeground: neutral[900],
|
|
218
|
+
warningMuted: '#fef3c7',
|
|
219
|
+
|
|
220
|
+
// Error (always red)
|
|
221
|
+
error: '#ef4444',
|
|
222
|
+
errorForeground: neutral[0],
|
|
223
|
+
errorMuted: '#fee2e2',
|
|
224
|
+
|
|
225
|
+
// Card
|
|
226
|
+
card: neutral[0],
|
|
227
|
+
cardForeground: neutralPalette[900],
|
|
228
|
+
|
|
229
|
+
// Input
|
|
230
|
+
input: neutral[0],
|
|
231
|
+
inputBorder: neutralPalette[200],
|
|
232
|
+
inputPlaceholder: neutralPalette[400],
|
|
233
|
+
|
|
234
|
+
// Overlay
|
|
235
|
+
overlay: 'rgba(0, 0, 0, 0.5)',
|
|
236
|
+
scrim: 'rgba(0, 0, 0, 0.3)',
|
|
237
|
+
},
|
|
238
|
+
dark: {
|
|
239
|
+
// Backgrounds
|
|
240
|
+
background: neutralPalette[950],
|
|
241
|
+
backgroundSubtle: neutralPalette[900],
|
|
242
|
+
backgroundMuted: neutralPalette[800],
|
|
243
|
+
backgroundElevated: neutralPalette[900],
|
|
244
|
+
|
|
245
|
+
// Foreground/Text
|
|
246
|
+
foreground: neutralPalette[50],
|
|
247
|
+
foregroundMuted: neutralPalette[400],
|
|
248
|
+
foregroundSubtle: neutralPalette[500],
|
|
249
|
+
|
|
250
|
+
// Borders
|
|
251
|
+
border: neutralPalette[800],
|
|
252
|
+
borderMuted: neutralPalette[900],
|
|
253
|
+
borderFocused: primaryPalette[400],
|
|
254
|
+
|
|
255
|
+
// Primary
|
|
256
|
+
primary: primaryPalette[400],
|
|
257
|
+
primaryForeground: neutralPalette[900],
|
|
258
|
+
primaryMuted: primaryPalette[900],
|
|
259
|
+
|
|
260
|
+
// Secondary
|
|
261
|
+
secondary: neutralPalette[800],
|
|
262
|
+
secondaryForeground: neutralPalette[50],
|
|
263
|
+
|
|
264
|
+
// Destructive
|
|
265
|
+
destructive: '#ef4444',
|
|
266
|
+
destructiveForeground: neutral[0],
|
|
267
|
+
|
|
268
|
+
// Success
|
|
269
|
+
success: '#4ade80',
|
|
270
|
+
successForeground: neutral[900],
|
|
271
|
+
successMuted: '#14532d',
|
|
272
|
+
|
|
273
|
+
// Warning
|
|
274
|
+
warning: '#fbbf24',
|
|
275
|
+
warningForeground: neutral[900],
|
|
276
|
+
warningMuted: '#78350f',
|
|
277
|
+
|
|
278
|
+
// Error
|
|
279
|
+
error: '#f87171',
|
|
280
|
+
errorForeground: neutral[0],
|
|
281
|
+
errorMuted: '#7f1d1d',
|
|
282
|
+
|
|
283
|
+
// Card
|
|
284
|
+
card: neutralPalette[900],
|
|
285
|
+
cardForeground: neutralPalette[50],
|
|
286
|
+
|
|
287
|
+
// Input
|
|
288
|
+
input: neutralPalette[800],
|
|
289
|
+
inputBorder: neutralPalette[700],
|
|
290
|
+
inputPlaceholder: neutralPalette[500],
|
|
291
|
+
|
|
292
|
+
// Overlay
|
|
293
|
+
overlay: 'rgba(0, 0, 0, 0.7)',
|
|
294
|
+
scrim: 'rgba(0, 0, 0, 0.5)',
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// =============================================================================
|
|
300
|
+
// Theme Presets
|
|
301
|
+
// =============================================================================
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* All available theme presets.
|
|
305
|
+
*/
|
|
306
|
+
export const themePresets: Record<ThemePreset, PresetColors> = {
|
|
307
|
+
// Neutral presets (use their own palette as neutral base)
|
|
308
|
+
zinc: createPresetColors(palettes.zinc, palettes.zinc),
|
|
309
|
+
slate: createPresetColors(palettes.slate, palettes.slate),
|
|
310
|
+
stone: createPresetColors(palettes.stone, palettes.stone),
|
|
311
|
+
|
|
312
|
+
// Color presets (use zinc as neutral base)
|
|
313
|
+
blue: createPresetColors(palettes.blue, palettes.zinc),
|
|
314
|
+
green: createPresetColors(palettes.green, palettes.zinc),
|
|
315
|
+
rose: createPresetColors(palettes.rose, palettes.zinc),
|
|
316
|
+
orange: createPresetColors(palettes.orange, palettes.zinc),
|
|
317
|
+
violet: createPresetColors(palettes.violet, palettes.zinc),
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Default theme preset.
|
|
322
|
+
*/
|
|
323
|
+
export const defaultThemePreset: ThemePreset = 'zinc';
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Gets colors for a theme preset.
|
|
327
|
+
*/
|
|
328
|
+
export function getPresetColors(preset: ThemePreset): PresetColors {
|
|
329
|
+
return themePresets[preset];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Gets light or dark colors for a theme preset.
|
|
334
|
+
*/
|
|
335
|
+
export function getPresetColorsForMode(
|
|
336
|
+
preset: ThemePreset,
|
|
337
|
+
isDark: boolean
|
|
338
|
+
): ThemeColors {
|
|
339
|
+
const presetColors = themePresets[preset];
|
|
340
|
+
return isDark ? presetColors.dark : presetColors.light;
|
|
341
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Border Radius Tokens
|
|
3
|
+
*
|
|
4
|
+
* Consistent border radius scale for all components.
|
|
5
|
+
* Use the `radius` prop on ThemeProvider to change the base radius
|
|
6
|
+
* and transform your entire app from sharp to rounded.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```tsx
|
|
10
|
+
* // Sharp, brutalist design
|
|
11
|
+
* <ThemeProvider radius="none">
|
|
12
|
+
*
|
|
13
|
+
* // Soft, friendly design
|
|
14
|
+
* <ThemeProvider radius="lg">
|
|
15
|
+
*
|
|
16
|
+
* // In components
|
|
17
|
+
* const { radius } = useTheme();
|
|
18
|
+
* <View style={{ borderRadius: radius.md }} />
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Radius Preset System
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Available radius presets.
|
|
28
|
+
* Each preset defines a base value that scales all radii.
|
|
29
|
+
*/
|
|
30
|
+
export type RadiusPreset = 'none' | 'sm' | 'md' | 'lg' | 'full';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Base values for each radius preset.
|
|
34
|
+
*/
|
|
35
|
+
export const radiusPresetBase: Record<RadiusPreset, number> = {
|
|
36
|
+
none: 0,
|
|
37
|
+
sm: 4,
|
|
38
|
+
md: 8,
|
|
39
|
+
lg: 12,
|
|
40
|
+
full: 9999,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Radius token structure.
|
|
45
|
+
*/
|
|
46
|
+
export interface RadiusTokens {
|
|
47
|
+
none: number;
|
|
48
|
+
xs: number;
|
|
49
|
+
sm: number;
|
|
50
|
+
md: number;
|
|
51
|
+
lg: number;
|
|
52
|
+
xl: number;
|
|
53
|
+
'2xl': number;
|
|
54
|
+
full: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type RadiusKey = keyof RadiusTokens;
|
|
58
|
+
export type RadiusValue = number;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates radius tokens from a preset or base value.
|
|
62
|
+
*
|
|
63
|
+
* @param preset - Radius preset name or custom base value
|
|
64
|
+
* @returns Scaled radius tokens
|
|
65
|
+
*/
|
|
66
|
+
export function createRadius(preset: RadiusPreset | number = 'md'): RadiusTokens {
|
|
67
|
+
const base = typeof preset === 'number' ? preset : radiusPresetBase[preset];
|
|
68
|
+
|
|
69
|
+
// Special case: 'none' preset = all zeros except full
|
|
70
|
+
if (preset === 'none' || base === 0) {
|
|
71
|
+
return {
|
|
72
|
+
none: 0,
|
|
73
|
+
xs: 0,
|
|
74
|
+
sm: 0,
|
|
75
|
+
md: 0,
|
|
76
|
+
lg: 0,
|
|
77
|
+
xl: 0,
|
|
78
|
+
'2xl': 0,
|
|
79
|
+
full: 0,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Special case: 'full' preset = all pill-shaped
|
|
84
|
+
if (preset === 'full' || base >= 9999) {
|
|
85
|
+
return {
|
|
86
|
+
none: 0,
|
|
87
|
+
xs: 9999,
|
|
88
|
+
sm: 9999,
|
|
89
|
+
md: 9999,
|
|
90
|
+
lg: 9999,
|
|
91
|
+
xl: 9999,
|
|
92
|
+
'2xl': 9999,
|
|
93
|
+
full: 9999,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Scale from base value
|
|
98
|
+
return {
|
|
99
|
+
none: 0,
|
|
100
|
+
xs: Math.round(base * 0.25), // 2 when base=8
|
|
101
|
+
sm: Math.round(base * 0.5), // 4 when base=8
|
|
102
|
+
md: base, // 8 when base=8
|
|
103
|
+
lg: Math.round(base * 1.5), // 12 when base=8
|
|
104
|
+
xl: Math.round(base * 2), // 16 when base=8
|
|
105
|
+
'2xl': Math.round(base * 3), // 24 when base=8
|
|
106
|
+
full: 9999,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Default radius tokens (md preset).
|
|
112
|
+
*/
|
|
113
|
+
export const radius = createRadius('md');
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Default radius preset.
|
|
117
|
+
*/
|
|
118
|
+
export const defaultRadiusPreset: RadiusPreset = 'md';
|
|
119
|
+
|
|
120
|
+
// =============================================================================
|
|
121
|
+
// Component Radius
|
|
122
|
+
// =============================================================================
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Component-specific radius tokens structure.
|
|
126
|
+
*/
|
|
127
|
+
export interface ComponentRadiusTokens {
|
|
128
|
+
button: number;
|
|
129
|
+
buttonSm: number;
|
|
130
|
+
buttonLg: number;
|
|
131
|
+
input: number;
|
|
132
|
+
card: number;
|
|
133
|
+
badge: number;
|
|
134
|
+
avatar: number;
|
|
135
|
+
checkbox: number;
|
|
136
|
+
switch: number;
|
|
137
|
+
modal: number;
|
|
138
|
+
tooltip: number;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export type ComponentRadiusKey = keyof ComponentRadiusTokens;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Pill radius constant - always fully rounded regardless of preset.
|
|
145
|
+
* Used for components that should always be pill-shaped (avatar, switch, badge).
|
|
146
|
+
*/
|
|
147
|
+
const PILL_RADIUS = 9999;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Creates component-specific radius values from radius tokens.
|
|
151
|
+
*
|
|
152
|
+
* @param radiusTokens - Base radius tokens
|
|
153
|
+
* @returns Component-specific radius values
|
|
154
|
+
*/
|
|
155
|
+
export function createComponentRadius(radiusTokens: RadiusTokens): ComponentRadiusTokens {
|
|
156
|
+
return {
|
|
157
|
+
button: radiusTokens.md,
|
|
158
|
+
buttonSm: radiusTokens.sm,
|
|
159
|
+
buttonLg: radiusTokens.lg,
|
|
160
|
+
input: radiusTokens.md,
|
|
161
|
+
card: radiusTokens.lg,
|
|
162
|
+
// Always pill-shaped regardless of preset
|
|
163
|
+
badge: PILL_RADIUS,
|
|
164
|
+
avatar: PILL_RADIUS,
|
|
165
|
+
checkbox: radiusTokens.sm,
|
|
166
|
+
switch: PILL_RADIUS,
|
|
167
|
+
modal: radiusTokens.xl,
|
|
168
|
+
tooltip: radiusTokens.md,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Default component radius values.
|
|
174
|
+
*/
|
|
175
|
+
export const componentRadius = createComponentRadius(radius);
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shadow Presets
|
|
3
|
+
*
|
|
4
|
+
* Consistent shadow tokens for depth and elevation.
|
|
5
|
+
* Platform-aware: iOS uses shadow properties, Android uses elevation.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by iOS 17+ depth language and Linear App shadows.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Platform, ViewStyle } from 'react-native';
|
|
11
|
+
|
|
12
|
+
export interface ShadowStyle {
|
|
13
|
+
shadowColor: string;
|
|
14
|
+
shadowOffset: { width: number; height: number };
|
|
15
|
+
shadowOpacity: number;
|
|
16
|
+
shadowRadius: number;
|
|
17
|
+
elevation: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Shadow presets for light mode
|
|
21
|
+
const lightShadows = {
|
|
22
|
+
/** Subtle shadow for cards and surfaces */
|
|
23
|
+
sm: {
|
|
24
|
+
shadowColor: '#000000',
|
|
25
|
+
shadowOffset: { width: 0, height: 1 },
|
|
26
|
+
shadowOpacity: 0.05,
|
|
27
|
+
shadowRadius: 2,
|
|
28
|
+
elevation: 1,
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
/** Default shadow for buttons and interactive elements */
|
|
32
|
+
md: {
|
|
33
|
+
shadowColor: '#000000',
|
|
34
|
+
shadowOffset: { width: 0, height: 2 },
|
|
35
|
+
shadowOpacity: 0.08,
|
|
36
|
+
shadowRadius: 4,
|
|
37
|
+
elevation: 3,
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/** Elevated shadow for modals and popovers */
|
|
41
|
+
lg: {
|
|
42
|
+
shadowColor: '#000000',
|
|
43
|
+
shadowOffset: { width: 0, height: 4 },
|
|
44
|
+
shadowOpacity: 0.12,
|
|
45
|
+
shadowRadius: 8,
|
|
46
|
+
elevation: 6,
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/** Prominent shadow for floating elements */
|
|
50
|
+
xl: {
|
|
51
|
+
shadowColor: '#000000',
|
|
52
|
+
shadowOffset: { width: 0, height: 8 },
|
|
53
|
+
shadowOpacity: 0.15,
|
|
54
|
+
shadowRadius: 16,
|
|
55
|
+
elevation: 12,
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
/** Maximum shadow for overlays */
|
|
59
|
+
'2xl': {
|
|
60
|
+
shadowColor: '#000000',
|
|
61
|
+
shadowOffset: { width: 0, height: 12 },
|
|
62
|
+
shadowOpacity: 0.2,
|
|
63
|
+
shadowRadius: 24,
|
|
64
|
+
elevation: 16,
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/** No shadow */
|
|
68
|
+
none: {
|
|
69
|
+
shadowColor: 'transparent',
|
|
70
|
+
shadowOffset: { width: 0, height: 0 },
|
|
71
|
+
shadowOpacity: 0,
|
|
72
|
+
shadowRadius: 0,
|
|
73
|
+
elevation: 0,
|
|
74
|
+
},
|
|
75
|
+
} as const;
|
|
76
|
+
|
|
77
|
+
// Shadow presets for dark mode (more subtle)
|
|
78
|
+
const darkShadows = {
|
|
79
|
+
sm: {
|
|
80
|
+
shadowColor: '#000000',
|
|
81
|
+
shadowOffset: { width: 0, height: 1 },
|
|
82
|
+
shadowOpacity: 0.2,
|
|
83
|
+
shadowRadius: 2,
|
|
84
|
+
elevation: 1,
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
md: {
|
|
88
|
+
shadowColor: '#000000',
|
|
89
|
+
shadowOffset: { width: 0, height: 2 },
|
|
90
|
+
shadowOpacity: 0.3,
|
|
91
|
+
shadowRadius: 4,
|
|
92
|
+
elevation: 3,
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
lg: {
|
|
96
|
+
shadowColor: '#000000',
|
|
97
|
+
shadowOffset: { width: 0, height: 4 },
|
|
98
|
+
shadowOpacity: 0.4,
|
|
99
|
+
shadowRadius: 8,
|
|
100
|
+
elevation: 6,
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
xl: {
|
|
104
|
+
shadowColor: '#000000',
|
|
105
|
+
shadowOffset: { width: 0, height: 8 },
|
|
106
|
+
shadowOpacity: 0.5,
|
|
107
|
+
shadowRadius: 16,
|
|
108
|
+
elevation: 12,
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
'2xl': {
|
|
112
|
+
shadowColor: '#000000',
|
|
113
|
+
shadowOffset: { width: 0, height: 12 },
|
|
114
|
+
shadowOpacity: 0.6,
|
|
115
|
+
shadowRadius: 24,
|
|
116
|
+
elevation: 16,
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
none: {
|
|
120
|
+
shadowColor: 'transparent',
|
|
121
|
+
shadowOffset: { width: 0, height: 0 },
|
|
122
|
+
shadowOpacity: 0,
|
|
123
|
+
shadowRadius: 0,
|
|
124
|
+
elevation: 0,
|
|
125
|
+
},
|
|
126
|
+
} as const;
|
|
127
|
+
|
|
128
|
+
export type ShadowSize = keyof typeof lightShadows;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get shadow styles for a given size and color scheme
|
|
132
|
+
*/
|
|
133
|
+
export function getShadow(size: ShadowSize, isDark: boolean = false): ShadowStyle {
|
|
134
|
+
const shadows = isDark ? darkShadows : lightShadows;
|
|
135
|
+
return shadows[size];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get platform-optimized shadow style
|
|
140
|
+
* Returns only the relevant properties for the current platform
|
|
141
|
+
*/
|
|
142
|
+
export function getPlatformShadow(size: ShadowSize, isDark: boolean = false): ViewStyle {
|
|
143
|
+
const shadow = getShadow(size, isDark);
|
|
144
|
+
|
|
145
|
+
if (Platform.OS === 'ios') {
|
|
146
|
+
return {
|
|
147
|
+
shadowColor: shadow.shadowColor,
|
|
148
|
+
shadowOffset: shadow.shadowOffset,
|
|
149
|
+
shadowOpacity: shadow.shadowOpacity,
|
|
150
|
+
shadowRadius: shadow.shadowRadius,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (Platform.OS === 'android') {
|
|
155
|
+
return {
|
|
156
|
+
elevation: shadow.elevation,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return shadow;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const shadows = {
|
|
164
|
+
light: lightShadows,
|
|
165
|
+
dark: darkShadows,
|
|
166
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spacing Tokens
|
|
3
|
+
*
|
|
4
|
+
* Consistent spacing scale inspired by Tailwind CSS.
|
|
5
|
+
* All values in logical pixels.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const { spacing } = useTheme();
|
|
10
|
+
* <View style={{ padding: spacing[4], gap: spacing[2] }} />
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const spacing = {
|
|
15
|
+
0: 0,
|
|
16
|
+
0.5: 2,
|
|
17
|
+
1: 4,
|
|
18
|
+
1.5: 6,
|
|
19
|
+
2: 8,
|
|
20
|
+
2.5: 10,
|
|
21
|
+
3: 12,
|
|
22
|
+
3.5: 14,
|
|
23
|
+
4: 16,
|
|
24
|
+
5: 20,
|
|
25
|
+
6: 24,
|
|
26
|
+
7: 28,
|
|
27
|
+
8: 32,
|
|
28
|
+
9: 36,
|
|
29
|
+
10: 40,
|
|
30
|
+
11: 44,
|
|
31
|
+
12: 48,
|
|
32
|
+
14: 56,
|
|
33
|
+
16: 64,
|
|
34
|
+
20: 80,
|
|
35
|
+
24: 96,
|
|
36
|
+
28: 112,
|
|
37
|
+
32: 128,
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
export type SpacingKey = keyof typeof spacing;
|
|
41
|
+
export type SpacingValue = (typeof spacing)[SpacingKey];
|