@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.
@@ -0,0 +1,379 @@
1
+ /**
2
+ * Animation Presets
3
+ *
4
+ * Consistent animation configurations for micro-interactions.
5
+ * Uses Reanimated's spring physics for natural motion.
6
+ *
7
+ * Inspired by iOS 17+ animations and Linear App micro-interactions.
8
+ */
9
+
10
+ import { Easing, EasingFunction } from 'react-native-reanimated';
11
+
12
+ // Type definitions for Reanimated-compatible configs
13
+ export interface SpringConfig {
14
+ damping?: number;
15
+ mass?: number;
16
+ stiffness?: number;
17
+ overshootClamping?: boolean;
18
+ restDisplacementThreshold?: number;
19
+ restSpeedThreshold?: number;
20
+ velocity?: number;
21
+ }
22
+
23
+ export interface TimingConfig {
24
+ duration?: number;
25
+ easing?: EasingFunction;
26
+ }
27
+
28
+ // Common easing functions using Reanimated's Easing module (worklet-compatible)
29
+ const easeOutQuad = Easing.out(Easing.quad);
30
+ const easeOutCubic = Easing.out(Easing.cubic);
31
+ const easeInQuad = Easing.in(Easing.quad);
32
+
33
+ /**
34
+ * Spring configurations for different animation contexts
35
+ */
36
+ export const springs: SpringTokens = {
37
+ /** Quick, snappy response - for buttons, toggles */
38
+ snappy: {
39
+ damping: 20,
40
+ stiffness: 300,
41
+ mass: 0.5,
42
+ },
43
+
44
+ /** Balanced spring - for general UI */
45
+ default: {
46
+ damping: 15,
47
+ stiffness: 200,
48
+ mass: 0.8,
49
+ },
50
+
51
+ /** Bouncy spring - for playful interactions */
52
+ bouncy: {
53
+ damping: 12,
54
+ stiffness: 180,
55
+ mass: 0.8,
56
+ },
57
+
58
+ /** Gentle spring - for larger movements */
59
+ gentle: {
60
+ damping: 18,
61
+ stiffness: 120,
62
+ mass: 1,
63
+ },
64
+
65
+ /** Stiff spring - minimal overshoot */
66
+ stiff: {
67
+ damping: 25,
68
+ stiffness: 400,
69
+ mass: 0.5,
70
+ },
71
+
72
+ /** Soft spring - for modal animations */
73
+ soft: {
74
+ damping: 20,
75
+ stiffness: 100,
76
+ mass: 1,
77
+ },
78
+ };
79
+
80
+ /**
81
+ * Timing configurations for linear/eased animations
82
+ */
83
+ export const timing: TimingTokens = {
84
+ /** Fast transition - 150ms */
85
+ fast: {
86
+ duration: 150,
87
+ easing: easeOutQuad,
88
+ },
89
+
90
+ /** Default transition - 200ms */
91
+ default: {
92
+ duration: 200,
93
+ easing: easeOutQuad,
94
+ },
95
+
96
+ /** Slow transition - 300ms */
97
+ slow: {
98
+ duration: 300,
99
+ easing: easeOutQuad,
100
+ },
101
+
102
+ /** Enter animation - 250ms ease out */
103
+ enter: {
104
+ duration: 250,
105
+ easing: easeOutCubic,
106
+ },
107
+
108
+ /** Exit animation - 200ms ease in */
109
+ exit: {
110
+ duration: 200,
111
+ easing: easeInQuad,
112
+ },
113
+ };
114
+
115
+ /**
116
+ * Scale values for press animations
117
+ */
118
+ export const pressScale: PressScaleTokens = {
119
+ /** Subtle press - for text buttons */
120
+ subtle: 0.98,
121
+ /** Default press - for buttons */
122
+ default: 0.96,
123
+ /** Prominent press - for cards */
124
+ prominent: 0.94,
125
+ };
126
+
127
+ /**
128
+ * Press scale token structure (flexible for presets)
129
+ */
130
+ export interface PressScaleTokens {
131
+ subtle: number;
132
+ default: number;
133
+ prominent: number;
134
+ }
135
+
136
+ /**
137
+ * Common animation durations in ms
138
+ */
139
+ export const durations: DurationTokens = {
140
+ instant: 0,
141
+ fast: 150,
142
+ default: 200,
143
+ slow: 300,
144
+ slower: 500,
145
+ };
146
+
147
+ /**
148
+ * Duration token structure (flexible for presets)
149
+ */
150
+ export interface DurationTokens {
151
+ instant: number;
152
+ fast: number;
153
+ default: number;
154
+ slow: number;
155
+ slower: number;
156
+ }
157
+
158
+ /**
159
+ * Spring tokens structure (flexible for presets)
160
+ */
161
+ export interface SpringTokens {
162
+ snappy: SpringConfig;
163
+ default: SpringConfig;
164
+ bouncy: SpringConfig;
165
+ gentle: SpringConfig;
166
+ stiff: SpringConfig;
167
+ soft: SpringConfig;
168
+ }
169
+
170
+ /**
171
+ * Timing tokens structure (flexible for presets)
172
+ */
173
+ export interface TimingTokens {
174
+ fast: TimingConfig;
175
+ default: TimingConfig;
176
+ slow: TimingConfig;
177
+ enter: TimingConfig;
178
+ exit: TimingConfig;
179
+ }
180
+
181
+ export type SpringPreset = keyof SpringTokens;
182
+ export type TimingPreset = keyof TimingTokens;
183
+ export type PressScalePreset = keyof PressScaleTokens;
184
+
185
+ // ─────────────────────────────────────────────────────────────────────────────
186
+ // Animation Presets (subtle vs playful)
187
+ // ─────────────────────────────────────────────────────────────────────────────
188
+
189
+ /**
190
+ * Animation preset type.
191
+ * Controls the overall feel of animations throughout the app.
192
+ *
193
+ * - `subtle`: Professional, smooth animations with minimal overshoot
194
+ * - `playful`: Bouncy, energetic animations with more personality
195
+ */
196
+ export type AnimationPreset = 'subtle' | 'playful';
197
+
198
+ /**
199
+ * Animation tokens structure for presets
200
+ */
201
+ export interface AnimationTokens {
202
+ springs: SpringTokens;
203
+ timing: TimingTokens;
204
+ pressScale: PressScaleTokens;
205
+ durations: DurationTokens;
206
+ }
207
+
208
+ /**
209
+ * Subtle animation preset.
210
+ * Professional, smooth animations suited for business/productivity apps.
211
+ * Higher damping = less bounce, faster settling.
212
+ */
213
+ export const subtleAnimations: AnimationTokens = {
214
+ springs: {
215
+ snappy: {
216
+ damping: 28,
217
+ stiffness: 350,
218
+ mass: 0.4,
219
+ },
220
+ default: {
221
+ damping: 22,
222
+ stiffness: 250,
223
+ mass: 0.6,
224
+ },
225
+ bouncy: {
226
+ damping: 18,
227
+ stiffness: 220,
228
+ mass: 0.6,
229
+ },
230
+ gentle: {
231
+ damping: 24,
232
+ stiffness: 150,
233
+ mass: 0.8,
234
+ },
235
+ stiff: {
236
+ damping: 30,
237
+ stiffness: 450,
238
+ mass: 0.4,
239
+ },
240
+ soft: {
241
+ damping: 26,
242
+ stiffness: 130,
243
+ mass: 0.8,
244
+ },
245
+ },
246
+ timing: {
247
+ fast: {
248
+ duration: 120,
249
+ easing: easeOutQuad,
250
+ },
251
+ default: {
252
+ duration: 180,
253
+ easing: easeOutQuad,
254
+ },
255
+ slow: {
256
+ duration: 280,
257
+ easing: easeOutQuad,
258
+ },
259
+ enter: {
260
+ duration: 220,
261
+ easing: easeOutCubic,
262
+ },
263
+ exit: {
264
+ duration: 180,
265
+ easing: easeInQuad,
266
+ },
267
+ },
268
+ pressScale: {
269
+ subtle: 0.99,
270
+ default: 0.97,
271
+ prominent: 0.95,
272
+ },
273
+ durations: {
274
+ instant: 0,
275
+ fast: 120,
276
+ default: 180,
277
+ slow: 280,
278
+ slower: 450,
279
+ },
280
+ };
281
+
282
+ /**
283
+ * Playful animation preset.
284
+ * Bouncy, energetic animations suited for consumer/social apps.
285
+ * Lower damping = more bounce, more overshoot.
286
+ */
287
+ export const playfulAnimations: AnimationTokens = {
288
+ springs: {
289
+ snappy: {
290
+ damping: 14,
291
+ stiffness: 280,
292
+ mass: 0.5,
293
+ },
294
+ default: {
295
+ damping: 12,
296
+ stiffness: 180,
297
+ mass: 0.8,
298
+ },
299
+ bouncy: {
300
+ damping: 8,
301
+ stiffness: 150,
302
+ mass: 0.9,
303
+ },
304
+ gentle: {
305
+ damping: 14,
306
+ stiffness: 100,
307
+ mass: 1.1,
308
+ },
309
+ stiff: {
310
+ damping: 18,
311
+ stiffness: 350,
312
+ mass: 0.5,
313
+ },
314
+ soft: {
315
+ damping: 16,
316
+ stiffness: 80,
317
+ mass: 1.1,
318
+ },
319
+ },
320
+ timing: {
321
+ fast: {
322
+ duration: 180,
323
+ easing: easeOutQuad,
324
+ },
325
+ default: {
326
+ duration: 240,
327
+ easing: easeOutQuad,
328
+ },
329
+ slow: {
330
+ duration: 350,
331
+ easing: easeOutQuad,
332
+ },
333
+ enter: {
334
+ duration: 300,
335
+ easing: easeOutCubic,
336
+ },
337
+ exit: {
338
+ duration: 220,
339
+ easing: easeInQuad,
340
+ },
341
+ },
342
+ pressScale: {
343
+ subtle: 0.97,
344
+ default: 0.94,
345
+ prominent: 0.90,
346
+ },
347
+ durations: {
348
+ instant: 0,
349
+ fast: 180,
350
+ default: 240,
351
+ slow: 350,
352
+ slower: 550,
353
+ },
354
+ };
355
+
356
+ /**
357
+ * Get animation tokens for a specific preset.
358
+ */
359
+ export function getAnimationPreset(preset: AnimationPreset): AnimationTokens {
360
+ switch (preset) {
361
+ case 'subtle':
362
+ return subtleAnimations;
363
+ case 'playful':
364
+ return playfulAnimations;
365
+ default:
366
+ // Default behavior matches the original springs/timing
367
+ return {
368
+ springs,
369
+ timing,
370
+ pressScale,
371
+ durations,
372
+ };
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Default animation preset.
378
+ */
379
+ export const defaultAnimationPreset: AnimationPreset = 'subtle';
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Theme Colors
3
+ *
4
+ * Semantic color tokens that adapt to light/dark mode.
5
+ * Inspired by iOS 17+ Dynamic Colors, Arc Browser, and Linear App.
6
+ */
7
+
8
+ // Base color palette (raw values)
9
+ export const palette = {
10
+ // Neutral scale
11
+ neutral: {
12
+ 0: '#ffffff',
13
+ 50: '#fafafa',
14
+ 100: '#f5f5f5',
15
+ 200: '#e5e5e5',
16
+ 300: '#d4d4d4',
17
+ 400: '#a3a3a3',
18
+ 500: '#737373',
19
+ 600: '#525252',
20
+ 700: '#404040',
21
+ 800: '#262626',
22
+ 900: '#171717',
23
+ 950: '#0a0a0a',
24
+ },
25
+
26
+ // Primary blue
27
+ primary: {
28
+ 50: '#eff6ff',
29
+ 100: '#dbeafe',
30
+ 200: '#bfdbfe',
31
+ 300: '#93c5fd',
32
+ 400: '#60a5fa',
33
+ 500: '#3b82f6',
34
+ 600: '#2563eb',
35
+ 700: '#1d4ed8',
36
+ 800: '#1e40af',
37
+ 900: '#1e3a8a',
38
+ },
39
+
40
+ // Success green
41
+ success: {
42
+ 50: '#f0fdf4',
43
+ 100: '#dcfce7',
44
+ 200: '#bbf7d0',
45
+ 300: '#86efac',
46
+ 400: '#4ade80',
47
+ 500: '#22c55e',
48
+ 600: '#16a34a',
49
+ 700: '#15803d',
50
+ 800: '#166534',
51
+ 900: '#14532d',
52
+ },
53
+
54
+ // Warning amber
55
+ warning: {
56
+ 50: '#fffbeb',
57
+ 100: '#fef3c7',
58
+ 200: '#fde68a',
59
+ 300: '#fcd34d',
60
+ 400: '#fbbf24',
61
+ 500: '#f59e0b',
62
+ 600: '#d97706',
63
+ 700: '#b45309',
64
+ 800: '#92400e',
65
+ 900: '#78350f',
66
+ },
67
+
68
+ // Error red
69
+ error: {
70
+ 50: '#fef2f2',
71
+ 100: '#fee2e2',
72
+ 200: '#fecaca',
73
+ 300: '#fca5a5',
74
+ 400: '#f87171',
75
+ 500: '#ef4444',
76
+ 600: '#dc2626',
77
+ 700: '#b91c1c',
78
+ 800: '#991b1b',
79
+ 900: '#7f1d1d',
80
+ },
81
+ } as const;
82
+
83
+ // Semantic colors for light mode
84
+ export const lightColors = {
85
+ // Backgrounds
86
+ background: palette.neutral[0],
87
+ backgroundSubtle: palette.neutral[50],
88
+ backgroundMuted: palette.neutral[100],
89
+ backgroundElevated: palette.neutral[0],
90
+
91
+ // Foreground/Text
92
+ foreground: palette.neutral[900],
93
+ foregroundMuted: palette.neutral[500],
94
+ foregroundSubtle: palette.neutral[400],
95
+
96
+ // Borders
97
+ border: palette.neutral[200],
98
+ borderMuted: palette.neutral[100],
99
+ borderFocused: palette.primary[500],
100
+
101
+ // Primary
102
+ primary: palette.primary[500],
103
+ primaryForeground: palette.neutral[0],
104
+ primaryMuted: palette.primary[100],
105
+
106
+ // Secondary
107
+ secondary: palette.neutral[100],
108
+ secondaryForeground: palette.neutral[900],
109
+
110
+ // Destructive
111
+ destructive: palette.error[500],
112
+ destructiveForeground: palette.neutral[0],
113
+
114
+ // Success
115
+ success: palette.success[500],
116
+ successForeground: palette.neutral[0],
117
+ successMuted: palette.success[100],
118
+
119
+ // Warning
120
+ warning: palette.warning[500],
121
+ warningForeground: palette.neutral[900],
122
+ warningMuted: palette.warning[100],
123
+
124
+ // Error
125
+ error: palette.error[500],
126
+ errorForeground: palette.neutral[0],
127
+ errorMuted: palette.error[100],
128
+
129
+ // Card
130
+ card: palette.neutral[0],
131
+ cardForeground: palette.neutral[900],
132
+
133
+ // Input
134
+ input: palette.neutral[0],
135
+ inputBorder: palette.neutral[200],
136
+ inputPlaceholder: palette.neutral[400],
137
+
138
+ // Overlay
139
+ overlay: 'rgba(0, 0, 0, 0.5)',
140
+ scrim: 'rgba(0, 0, 0, 0.3)',
141
+ } as const;
142
+
143
+ // Semantic colors for dark mode
144
+ export const darkColors = {
145
+ // Backgrounds
146
+ background: palette.neutral[950],
147
+ backgroundSubtle: palette.neutral[900],
148
+ backgroundMuted: palette.neutral[800],
149
+ backgroundElevated: palette.neutral[900],
150
+
151
+ // Foreground/Text
152
+ foreground: palette.neutral[50],
153
+ foregroundMuted: palette.neutral[400],
154
+ foregroundSubtle: palette.neutral[500],
155
+
156
+ // Borders
157
+ border: palette.neutral[800],
158
+ borderMuted: palette.neutral[900],
159
+ borderFocused: palette.primary[400],
160
+
161
+ // Primary
162
+ primary: palette.primary[400],
163
+ primaryForeground: palette.neutral[900],
164
+ primaryMuted: palette.primary[900],
165
+
166
+ // Secondary
167
+ secondary: palette.neutral[800],
168
+ secondaryForeground: palette.neutral[50],
169
+
170
+ // Destructive
171
+ destructive: palette.error[500],
172
+ destructiveForeground: palette.neutral[0],
173
+
174
+ // Success
175
+ success: palette.success[400],
176
+ successForeground: palette.neutral[900],
177
+ successMuted: palette.success[900],
178
+
179
+ // Warning
180
+ warning: palette.warning[400],
181
+ warningForeground: palette.neutral[900],
182
+ warningMuted: palette.warning[900],
183
+
184
+ // Error
185
+ error: palette.error[400],
186
+ errorForeground: palette.neutral[0],
187
+ errorMuted: palette.error[900],
188
+
189
+ // Card
190
+ card: palette.neutral[900],
191
+ cardForeground: palette.neutral[50],
192
+
193
+ // Input
194
+ input: palette.neutral[800],
195
+ inputBorder: palette.neutral[700],
196
+ inputPlaceholder: palette.neutral[500],
197
+
198
+ // Overlay
199
+ overlay: 'rgba(0, 0, 0, 0.7)',
200
+ scrim: 'rgba(0, 0, 0, 0.5)',
201
+ } as const;
202
+
203
+ /**
204
+ * Theme colors interface.
205
+ * Uses string type to allow color customization and presets.
206
+ */
207
+ export interface ThemeColors {
208
+ // Backgrounds
209
+ background: string;
210
+ backgroundSubtle: string;
211
+ backgroundMuted: string;
212
+ backgroundElevated: string;
213
+
214
+ // Foreground/Text
215
+ foreground: string;
216
+ foregroundMuted: string;
217
+ foregroundSubtle: string;
218
+
219
+ // Borders
220
+ border: string;
221
+ borderMuted: string;
222
+ borderFocused: string;
223
+
224
+ // Primary
225
+ primary: string;
226
+ primaryForeground: string;
227
+ primaryMuted: string;
228
+
229
+ // Secondary
230
+ secondary: string;
231
+ secondaryForeground: string;
232
+
233
+ // Destructive
234
+ destructive: string;
235
+ destructiveForeground: string;
236
+
237
+ // Success
238
+ success: string;
239
+ successForeground: string;
240
+ successMuted: string;
241
+
242
+ // Warning
243
+ warning: string;
244
+ warningForeground: string;
245
+ warningMuted: string;
246
+
247
+ // Error
248
+ error: string;
249
+ errorForeground: string;
250
+ errorMuted: string;
251
+
252
+ // Card
253
+ card: string;
254
+ cardForeground: string;
255
+
256
+ // Input
257
+ input: string;
258
+ inputBorder: string;
259
+ inputPlaceholder: string;
260
+
261
+ // Overlay
262
+ overlay: string;
263
+ scrim: string;
264
+ }
265
+
266
+ export type ColorKey = keyof ThemeColors;