zelune 1.5.5 → 1.5.7

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,997 @@
1
+ import React from 'react';
2
+
3
+ export interface TailwindThemeConfig {
4
+ colors?: Record<string, unknown>;
5
+ fontSize?: Record<string, unknown>;
6
+ borderRadius?: Record<string, unknown>;
7
+ spacing?: Record<string, unknown>;
8
+ opacity?: Record<string, unknown>;
9
+ fontFamily?: Record<string, unknown>;
10
+ }
11
+
12
+ declare global {
13
+ interface Window {
14
+ __ZELUNE_TAILWIND_THEME__?: TailwindThemeConfig;
15
+ }
16
+ }
17
+
18
+ // Built-in fallback palette combining standard Tailwind v3 colors + M3 / NativeWind semantic tokens
19
+ const DEFAULT_COLORS: Record<string, unknown> = {
20
+ transparent: 'transparent',
21
+ current: 'currentColor',
22
+ black: '#000000',
23
+ white: '#FFFFFF',
24
+ surface: {
25
+ DEFAULT: '#FAFAFA',
26
+ dim: '#F2F2F2',
27
+ bright: '#FFFFFF',
28
+ 'container-lowest': '#F9FAFB',
29
+ 'container-low': '#F3F4F6',
30
+ container: '#FFFFFF',
31
+ 'container-high': '#F3F4F6',
32
+ 'container-highest': '#E5E7EB',
33
+ },
34
+ 'on-surface': {
35
+ DEFAULT: '#1A1A2E',
36
+ variant: '#6B7280',
37
+ },
38
+ primary: {
39
+ DEFAULT: '#0F3460',
40
+ container: '#E8F0FF',
41
+ fixed: '#E8F0FF',
42
+ 'fixed-dim': '#C5D5F0',
43
+ 'on-primary': '#FFFFFF',
44
+ 'on-container': '#0F3460',
45
+ },
46
+ secondary: {
47
+ DEFAULT: '#4B5563',
48
+ container: '#F3F4F6',
49
+ 'on-secondary': '#FFFFFF',
50
+ 'on-container': '#1A1A2E',
51
+ },
52
+ tertiary: {
53
+ DEFAULT: '#0D9488',
54
+ container: '#CCFBF1',
55
+ 'on-tertiary': '#FFFFFF',
56
+ 'on-container': '#134E4A',
57
+ },
58
+ outline: {
59
+ DEFAULT: '#E5E7EB',
60
+ variant: '#F3F4F6',
61
+ },
62
+ inverse: {
63
+ surface: '#1A1A2E',
64
+ 'on-surface': '#FAFAFA',
65
+ primary: '#C5D5F0',
66
+ },
67
+ error: {
68
+ DEFAULT: '#DC2626',
69
+ container: '#FEE2E2',
70
+ },
71
+ slate: {
72
+ 50: '#F8FAFC',
73
+ 100: '#F1F5F9',
74
+ 200: '#E2E8F0',
75
+ 300: '#CBD5E1',
76
+ 400: '#94A3B8',
77
+ 500: '#64748B',
78
+ 600: '#475569',
79
+ 700: '#334155',
80
+ 800: '#1E293B',
81
+ 900: '#0F172A',
82
+ },
83
+ gray: {
84
+ 50: '#F9FAFB',
85
+ 100: '#F3F4F6',
86
+ 200: '#E5E7EB',
87
+ 300: '#D1D5DB',
88
+ 400: '#9CA3AF',
89
+ 500: '#6B7280',
90
+ 600: '#4B5563',
91
+ 700: '#374151',
92
+ 800: '#1F2937',
93
+ 900: '#111827',
94
+ },
95
+ zinc: {
96
+ 50: '#FAFAFA',
97
+ 100: '#F4F4F5',
98
+ 200: '#E4E4E7',
99
+ 300: '#D4D4D8',
100
+ 400: '#A1A1AA',
101
+ 500: '#71717A',
102
+ 600: '#52525B',
103
+ 700: '#3F3F46',
104
+ 800: '#27272A',
105
+ 900: '#18181B',
106
+ },
107
+ red: {
108
+ 50: '#FEF2F2',
109
+ 100: '#FEE2E2',
110
+ 200: '#FECACA',
111
+ 400: '#F87171',
112
+ 500: '#EF4444',
113
+ 600: '#DC2626',
114
+ 700: '#B91C1C',
115
+ },
116
+ amber: {
117
+ 50: '#FFFBEB',
118
+ 100: '#FEF3C7',
119
+ 200: '#FDE68A',
120
+ 400: '#FBBF24',
121
+ 500: '#F59E0B',
122
+ 600: '#D97706',
123
+ 700: '#B45309',
124
+ },
125
+ yellow: {
126
+ 50: '#FEFCE8',
127
+ 100: '#FEF9C3',
128
+ 400: '#FACC15',
129
+ 500: '#EAB308',
130
+ 600: '#CA8A04',
131
+ },
132
+ green: {
133
+ 50: '#F0FDF4',
134
+ 100: '#DCFCE7',
135
+ 400: '#4ADE80',
136
+ 500: '#22C55E',
137
+ 600: '#16A34A',
138
+ 700: '#15803D',
139
+ },
140
+ emerald: {
141
+ 50: '#ECFDF5',
142
+ 100: '#D1FAE5',
143
+ 400: '#34D399',
144
+ 500: '#10B981',
145
+ 600: '#059669',
146
+ 700: '#047857',
147
+ },
148
+ teal: {
149
+ 50: '#F0FDFA',
150
+ 100: '#CCFBF1',
151
+ 400: '#2DD4BF',
152
+ 500: '#14B8A6',
153
+ 600: '#0D9488',
154
+ 700: '#0F766E',
155
+ },
156
+ sky: {
157
+ 50: '#F0F9FF',
158
+ 100: '#E0F2FE',
159
+ 400: '#38BDF8',
160
+ 500: '#0EA5E9',
161
+ 600: '#0284C7',
162
+ },
163
+ blue: {
164
+ 50: '#EFF6FF',
165
+ 100: '#DBEAFE',
166
+ 200: '#BFDBFE',
167
+ 400: '#60A5FA',
168
+ 500: '#3B82F6',
169
+ 600: '#2563EB',
170
+ 700: '#1D4ED8',
171
+ },
172
+ indigo: {
173
+ 50: '#EEF2FF',
174
+ 100: '#E0E7FF',
175
+ 400: '#818CF8',
176
+ 500: '#6366F1',
177
+ 600: '#4F46E5',
178
+ 700: '#4338CA',
179
+ },
180
+ purple: {
181
+ 50: '#FAF5FF',
182
+ 100: '#F3E8FF',
183
+ 500: '#A855F7',
184
+ 600: '#9333EA',
185
+ },
186
+ pink: {
187
+ 50: '#FDF2F8',
188
+ 100: '#FCE7F3',
189
+ 500: '#EC4899',
190
+ 600: '#DB2777',
191
+ },
192
+ };
193
+
194
+ const DEFAULT_FONT_SIZES: Record<string, [number, { lineHeight?: number; letterSpacing?: number; fontWeight?: string }]> = {
195
+ xs: [12, { lineHeight: 16 }],
196
+ sm: [14, { lineHeight: 20 }],
197
+ base: [16, { lineHeight: 24 }],
198
+ lg: [18, { lineHeight: 28 }],
199
+ xl: [20, { lineHeight: 28 }],
200
+ '2xl': [24, { lineHeight: 32 }],
201
+ '3xl': [30, { lineHeight: 36 }],
202
+ '4xl': [36, { lineHeight: 40 }],
203
+ '5xl': [48, { lineHeight: 52 }],
204
+ 'display-lg': [57, { lineHeight: 64, letterSpacing: -0.25 }],
205
+ 'display-md': [45, { lineHeight: 52 }],
206
+ 'display-sm': [36, { lineHeight: 44 }],
207
+ 'headline-lg': [32, { lineHeight: 40 }],
208
+ 'headline-md': [28, { lineHeight: 36 }],
209
+ 'headline-sm': [24, { lineHeight: 32 }],
210
+ 'title-lg': [22, { lineHeight: 28 }],
211
+ 'title-md': [16, { lineHeight: 24, letterSpacing: 0.15, fontWeight: '500' }],
212
+ 'title-sm': [14, { lineHeight: 20, letterSpacing: 0.1, fontWeight: '500' }],
213
+ 'body-lg': [16, { lineHeight: 24, letterSpacing: 0.5 }],
214
+ 'body-md': [14, { lineHeight: 20, letterSpacing: 0.25 }],
215
+ 'body-sm': [12, { lineHeight: 16, letterSpacing: 0.4 }],
216
+ 'label-lg': [14, { lineHeight: 20, letterSpacing: 0.1, fontWeight: '500' }],
217
+ 'label-md': [12, { lineHeight: 16, letterSpacing: 0.5, fontWeight: '500' }],
218
+ 'label-sm': [11, { lineHeight: 16, letterSpacing: 0.5, fontWeight: '500' }],
219
+ };
220
+
221
+ const DEFAULT_RADII: Record<string, number> = {
222
+ none: 0,
223
+ sm: 4,
224
+ DEFAULT: 6,
225
+ md: 8,
226
+ lg: 12,
227
+ xl: 16,
228
+ '2xl': 24,
229
+ '3xl': 28,
230
+ full: 9999,
231
+ };
232
+
233
+ function parsePxNumber(val: unknown): number | undefined {
234
+ if (typeof val === 'number' && Number.isFinite(val)) return val;
235
+ if (typeof val === 'string') {
236
+ const trimmed = val.trim();
237
+ if (trimmed.endsWith('rem')) {
238
+ const n = Number.parseFloat(trimmed);
239
+ return Number.isFinite(n) ? n * 16 : undefined;
240
+ }
241
+ const n = Number.parseFloat(trimmed.replace(/px$/i, ''));
242
+ return Number.isFinite(n) ? n : undefined;
243
+ }
244
+ return undefined;
245
+ }
246
+
247
+ function applyColorAlpha(colorStr: string, alphaRatio: number): string {
248
+ if (!Number.isFinite(alphaRatio) || alphaRatio >= 1) return colorStr;
249
+ const clamped = Math.max(0, Math.min(1, alphaRatio));
250
+ const hexMatch = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(colorStr.trim());
251
+ if (hexMatch) {
252
+ let hex = hexMatch[1];
253
+ if (hex.length === 3) {
254
+ hex = hex
255
+ .split('')
256
+ .map((c) => c + c)
257
+ .join('');
258
+ }
259
+ const r = Number.parseInt(hex.slice(0, 2), 16);
260
+ const g = Number.parseInt(hex.slice(2, 4), 16);
261
+ const b = Number.parseInt(hex.slice(4, 6), 16);
262
+ return `rgba(${r}, ${g}, ${b}, ${Number(clamped.toFixed(3))})`;
263
+ }
264
+ const rgbMatch = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i.exec(colorStr.trim());
265
+ if (rgbMatch) {
266
+ return `rgba(${rgbMatch[1]}, ${rgbMatch[2]}, ${rgbMatch[3]}, ${Number(clamped.toFixed(3))})`;
267
+ }
268
+ return colorStr;
269
+ }
270
+
271
+ function lookupColorToken(rawToken: string, themeColors: Record<string, unknown>): string | undefined {
272
+ // Arbitrary value e.g. [#0F3460] or [rgba(0,0,0,0.5)]
273
+ if (rawToken.startsWith('[') && rawToken.endsWith(']')) {
274
+ return rawToken.slice(1, -1);
275
+ }
276
+
277
+ const [colorPath, alphaPart] = rawToken.split('/');
278
+ let resolvedHex: string | undefined;
279
+
280
+ // 1. Direct match in themeColors or DEFAULT_COLORS
281
+ const sources = [themeColors, DEFAULT_COLORS];
282
+ for (const src of sources) {
283
+ if (!src || typeof src !== 'object') continue;
284
+ const direct = src[colorPath];
285
+ if (typeof direct === 'string') {
286
+ resolvedHex = direct;
287
+ break;
288
+ }
289
+ if (direct && typeof direct === 'object' && 'DEFAULT' in direct) {
290
+ const defVal = (direct as Record<string, unknown>).DEFAULT;
291
+ if (typeof defVal === 'string') {
292
+ resolvedHex = defVal;
293
+ break;
294
+ }
295
+ }
296
+
297
+ // Try splitting hyphenated prefixes e.g. 'surface-container-high' -> src['surface']['container-high']
298
+ // or 'on-surface-variant' -> src['on-surface']['variant']
299
+ const parts = colorPath.split('-');
300
+ for (let splitIdx = parts.length - 1; splitIdx >= 1; splitIdx--) {
301
+ const groupKey = parts.slice(0, splitIdx).join('-');
302
+ const shadeKey = parts.slice(splitIdx).join('-');
303
+ const groupObj = src[groupKey];
304
+ if (groupObj && typeof groupObj === 'object') {
305
+ const shadeVal = (groupObj as Record<string, unknown>)[shadeKey];
306
+ if (typeof shadeVal === 'string') {
307
+ resolvedHex = shadeVal;
308
+ break;
309
+ }
310
+ }
311
+ }
312
+ if (resolvedHex) break;
313
+ }
314
+
315
+ if (!resolvedHex) return undefined;
316
+
317
+ if (alphaPart !== undefined) {
318
+ const customOpacity =
319
+ typeof window !== 'undefined' ? window.__ZELUNE_TAILWIND_THEME__?.opacity?.[alphaPart] : undefined;
320
+ let alphaRatio =
321
+ typeof customOpacity === 'string' || typeof customOpacity === 'number'
322
+ ? Number.parseFloat(String(customOpacity))
323
+ : Number.NaN;
324
+ if (!Number.isFinite(alphaRatio)) {
325
+ const numeric = Number.parseFloat(alphaPart);
326
+ if (Number.isFinite(numeric)) {
327
+ alphaRatio = numeric <= 1 ? numeric : numeric / 100;
328
+ }
329
+ }
330
+ if (Number.isFinite(alphaRatio)) {
331
+ return applyColorAlpha(resolvedHex, alphaRatio);
332
+ }
333
+ }
334
+
335
+ return resolvedHex;
336
+ }
337
+
338
+ function resolveSpacingToken(rawVal: string, themeSpacing: Record<string, unknown>): number | string | undefined {
339
+ if (rawVal === 'auto') return 'auto';
340
+ if (rawVal === 'px') return 1;
341
+ if (rawVal === 'full') return '100%';
342
+ if (rawVal.startsWith('[') && rawVal.endsWith(']')) {
343
+ const inner = rawVal.slice(1, -1);
344
+ if (inner.endsWith('%')) return inner;
345
+ const px = parsePxNumber(inner);
346
+ return px !== undefined ? px : inner;
347
+ }
348
+ if (rawVal.includes('/')) {
349
+ const [num, den] = rawVal.split('/').map(Number);
350
+ if (Number.isFinite(num) && Number.isFinite(den) && den !== 0) {
351
+ return `${Number(((num / den) * 100).toFixed(4))}%`;
352
+ }
353
+ }
354
+ if (themeSpacing && rawVal in themeSpacing) {
355
+ const customPx = parsePxNumber(themeSpacing[rawVal]);
356
+ if (customPx !== undefined) return customPx;
357
+ }
358
+ const num = Number.parseFloat(rawVal);
359
+ if (Number.isFinite(num)) {
360
+ return num * 4;
361
+ }
362
+ return undefined;
363
+ }
364
+
365
+ const classStyleCache = new Map<string, Record<string, unknown>>();
366
+
367
+ export function resolveTailwindClassNameToStyle(className?: string): Record<string, unknown> | null {
368
+ if (!className || typeof className !== 'string') return null;
369
+ const normalized = className.replace(/\s+/g, ' ').trim();
370
+ if (!normalized) return null;
371
+
372
+ const cached = classStyleCache.get(normalized);
373
+ if (cached) return cached;
374
+
375
+ const projectTheme: TailwindThemeConfig =
376
+ (typeof window !== 'undefined' && window.__ZELUNE_TAILWIND_THEME__) || {};
377
+ const themeColors = (projectTheme.colors ?? {}) as Record<string, unknown>;
378
+ const themeFontSizes = (projectTheme.fontSize ?? {}) as Record<string, unknown>;
379
+ const themeFontFamilies = (projectTheme.fontFamily ?? {}) as Record<string, unknown>;
380
+ const themeRadii = (projectTheme.borderRadius ?? {}) as Record<string, unknown>;
381
+ const themeSpacing = (projectTheme.spacing ?? {}) as Record<string, unknown>;
382
+
383
+ const style: Record<string, unknown> = {};
384
+ const tokens = normalized.split(' ');
385
+
386
+ for (const rawToken of tokens) {
387
+ if (!rawToken) continue;
388
+ // Strip responsive/state prefixes like sm:, md:, active:, focus:, dark:
389
+ const token = rawToken.includes(':') ? rawToken.slice(rawToken.lastIndexOf(':') + 1) : rawToken;
390
+ if (!token) continue;
391
+
392
+ // 1. Flexbox & Layout
393
+ if (token === 'flex-1') {
394
+ style.flex = 1;
395
+ continue;
396
+ }
397
+ if (token === 'flex-auto') {
398
+ style.flex = 1;
399
+ continue;
400
+ }
401
+ if (token === 'flex-none') {
402
+ style.flex = 0;
403
+ continue;
404
+ }
405
+ if (token === 'flex-grow' || token === 'grow') {
406
+ style.flexGrow = 1;
407
+ continue;
408
+ }
409
+ if (token === 'flex-shrink' || token === 'shrink') {
410
+ style.flexShrink = 1;
411
+ continue;
412
+ }
413
+ if (token === 'flex-shrink-0' || token === 'shrink-0') {
414
+ style.flexShrink = 0;
415
+ continue;
416
+ }
417
+ if (token === 'flex-row') {
418
+ style.flexDirection = 'row';
419
+ continue;
420
+ }
421
+ if (token === 'flex-row-reverse') {
422
+ style.flexDirection = 'row-reverse';
423
+ continue;
424
+ }
425
+ if (token === 'flex-col') {
426
+ style.flexDirection = 'column';
427
+ continue;
428
+ }
429
+ if (token === 'flex-col-reverse') {
430
+ style.flexDirection = 'column-reverse';
431
+ continue;
432
+ }
433
+ if (token === 'flex-wrap') {
434
+ style.flexWrap = 'wrap';
435
+ continue;
436
+ }
437
+ if (token === 'flex-nowrap') {
438
+ style.flexWrap = 'nowrap';
439
+ continue;
440
+ }
441
+ if (token === 'hidden') {
442
+ style.display = 'none';
443
+ continue;
444
+ }
445
+
446
+ // Align Items & Self & Content
447
+ if (token === 'items-start') {
448
+ style.alignItems = 'flex-start';
449
+ continue;
450
+ }
451
+ if (token === 'items-center') {
452
+ style.alignItems = 'center';
453
+ continue;
454
+ }
455
+ if (token === 'items-end') {
456
+ style.alignItems = 'flex-end';
457
+ continue;
458
+ }
459
+ if (token === 'items-stretch') {
460
+ style.alignItems = 'stretch';
461
+ continue;
462
+ }
463
+ if (token === 'items-baseline') {
464
+ style.alignItems = 'baseline';
465
+ continue;
466
+ }
467
+ if (token === 'self-start') {
468
+ style.alignSelf = 'flex-start';
469
+ continue;
470
+ }
471
+ if (token === 'self-center') {
472
+ style.alignSelf = 'center';
473
+ continue;
474
+ }
475
+ if (token === 'self-end') {
476
+ style.alignSelf = 'flex-end';
477
+ continue;
478
+ }
479
+ if (token === 'self-stretch') {
480
+ style.alignSelf = 'stretch';
481
+ continue;
482
+ }
483
+ if (token === 'self-auto') {
484
+ style.alignSelf = 'auto';
485
+ continue;
486
+ }
487
+
488
+ // Justify Content
489
+ if (token === 'justify-start') {
490
+ style.justifyContent = 'flex-start';
491
+ continue;
492
+ }
493
+ if (token === 'justify-center') {
494
+ style.justifyContent = 'center';
495
+ continue;
496
+ }
497
+ if (token === 'justify-end') {
498
+ style.justifyContent = 'flex-end';
499
+ continue;
500
+ }
501
+ if (token === 'justify-between') {
502
+ style.justifyContent = 'space-between';
503
+ continue;
504
+ }
505
+ if (token === 'justify-around') {
506
+ style.justifyContent = 'space-around';
507
+ continue;
508
+ }
509
+ if (token === 'justify-evenly') {
510
+ style.justifyContent = 'space-evenly';
511
+ continue;
512
+ }
513
+
514
+ // Position & Overflow
515
+ if (token === 'relative') {
516
+ style.position = 'relative';
517
+ continue;
518
+ }
519
+ if (token === 'absolute') {
520
+ style.position = 'absolute';
521
+ continue;
522
+ }
523
+ if (token === 'inset-0') {
524
+ style.top = 0;
525
+ style.right = 0;
526
+ style.bottom = 0;
527
+ style.left = 0;
528
+ continue;
529
+ }
530
+ if (token === 'overflow-hidden') {
531
+ style.overflow = 'hidden';
532
+ continue;
533
+ }
534
+ if (token === 'overflow-visible') {
535
+ style.overflow = 'visible';
536
+ continue;
537
+ }
538
+ if (token === 'overflow-scroll') {
539
+ style.overflow = 'scroll';
540
+ continue;
541
+ }
542
+
543
+ const posMatch = /^(-?)(top|right|bottom|left)-(.+)$/.exec(token);
544
+ if (posMatch) {
545
+ const sign = posMatch[1] === '-' ? -1 : 1;
546
+ const prop = posMatch[2];
547
+ const val = resolveSpacingToken(posMatch[3], themeSpacing);
548
+ if (typeof val === 'number') style[prop] = val * sign;
549
+ else if (val !== undefined) style[prop] = val;
550
+ continue;
551
+ }
552
+
553
+ if (token.startsWith('z-')) {
554
+ const zVal = Number.parseInt(token.slice(2), 10);
555
+ if (Number.isFinite(zVal)) style.zIndex = zVal;
556
+ continue;
557
+ }
558
+
559
+ // 2. Gap
560
+ if (token.startsWith('gap-x-')) {
561
+ const v = resolveSpacingToken(token.slice(6), themeSpacing);
562
+ if (typeof v === 'number') style.columnGap = v;
563
+ continue;
564
+ }
565
+ if (token.startsWith('gap-y-')) {
566
+ const v = resolveSpacingToken(token.slice(6), themeSpacing);
567
+ if (typeof v === 'number') style.rowGap = v;
568
+ continue;
569
+ }
570
+ if (token.startsWith('gap-')) {
571
+ const v = resolveSpacingToken(token.slice(4), themeSpacing);
572
+ if (typeof v === 'number') style.gap = v;
573
+ continue;
574
+ }
575
+
576
+ // 3. Padding & Margin
577
+ const spacingMatch = /^(-?)(p|px|py|pt|pr|pb|pl|m|mx|my|mt|mr|mb|ml)-(.+)$/.exec(token);
578
+ if (spacingMatch) {
579
+ const sign = spacingMatch[1] === '-' ? -1 : 1;
580
+ const dir = spacingMatch[2];
581
+ const rawVal = resolveSpacingToken(spacingMatch[3], themeSpacing);
582
+ if (rawVal !== undefined) {
583
+ const val = typeof rawVal === 'number' ? rawVal * sign : rawVal;
584
+ const propMap: Record<string, string> = {
585
+ p: 'padding',
586
+ px: 'paddingHorizontal',
587
+ py: 'paddingVertical',
588
+ pt: 'paddingTop',
589
+ pr: 'paddingRight',
590
+ pb: 'paddingBottom',
591
+ pl: 'paddingLeft',
592
+ m: 'margin',
593
+ mx: 'marginHorizontal',
594
+ my: 'marginVertical',
595
+ mt: 'marginTop',
596
+ mr: 'marginRight',
597
+ mb: 'marginBottom',
598
+ ml: 'marginLeft',
599
+ };
600
+ style[propMap[dir]] = val;
601
+ continue;
602
+ }
603
+ }
604
+
605
+ // 4. Width & Height
606
+ if (token === 'w-screen') {
607
+ style.width = 393;
608
+ continue;
609
+ }
610
+ if (token === 'h-screen') {
611
+ style.height = 852;
612
+ continue;
613
+ }
614
+ if (token.startsWith('w-')) {
615
+ const v = resolveSpacingToken(token.slice(2), themeSpacing);
616
+ if (v !== undefined) {
617
+ style.width = v;
618
+ continue;
619
+ }
620
+ }
621
+ if (token.startsWith('h-')) {
622
+ const v = resolveSpacingToken(token.slice(2), themeSpacing);
623
+ if (v !== undefined) {
624
+ style.height = v;
625
+ continue;
626
+ }
627
+ }
628
+ if (token.startsWith('size-')) {
629
+ const v = resolveSpacingToken(token.slice(5), themeSpacing);
630
+ if (v !== undefined) {
631
+ style.width = v;
632
+ style.height = v;
633
+ continue;
634
+ }
635
+ }
636
+ if (token.startsWith('min-w-')) {
637
+ const v = resolveSpacingToken(token.slice(6), themeSpacing);
638
+ if (v !== undefined) {
639
+ style.minWidth = v;
640
+ continue;
641
+ }
642
+ }
643
+ if (token.startsWith('min-h-')) {
644
+ const v = resolveSpacingToken(token.slice(6), themeSpacing);
645
+ if (v !== undefined) {
646
+ style.minHeight = v;
647
+ continue;
648
+ }
649
+ }
650
+ if (token.startsWith('max-w-')) {
651
+ const v = resolveSpacingToken(token.slice(6), themeSpacing);
652
+ if (v !== undefined) {
653
+ style.maxWidth = v;
654
+ continue;
655
+ }
656
+ }
657
+ if (token.startsWith('max-h-')) {
658
+ const v = resolveSpacingToken(token.slice(6), themeSpacing);
659
+ if (v !== undefined) {
660
+ style.maxHeight = v;
661
+ continue;
662
+ }
663
+ }
664
+
665
+ // 5. Border Radius
666
+ if (token === 'rounded' || token.startsWith('rounded-')) {
667
+ const suffix = token === 'rounded' ? 'DEFAULT' : token.slice(8);
668
+ const customR = parsePxNumber(themeRadii[suffix]);
669
+ const radiusVal = customR ?? DEFAULT_RADII[suffix];
670
+ if (radiusVal !== undefined) {
671
+ style.borderRadius = radiusVal;
672
+ continue;
673
+ }
674
+ const cornerMatch = /^(t|b|l|r|tl|tr|bl|br)-(.+)$/.exec(suffix);
675
+ if (cornerMatch) {
676
+ const cDir = cornerMatch[1];
677
+ const cKey = cornerMatch[2];
678
+ const r = parsePxNumber(themeRadii[cKey]) ?? DEFAULT_RADII[cKey];
679
+ if (r !== undefined) {
680
+ if (cDir === 't' || cDir === 'tl') style.borderTopLeftRadius = r;
681
+ if (cDir === 't' || cDir === 'tr') style.borderTopRightRadius = r;
682
+ if (cDir === 'b' || cDir === 'bl') style.borderBottomLeftRadius = r;
683
+ if (cDir === 'b' || cDir === 'br') style.borderBottomRightRadius = r;
684
+ if (cDir === 'l') {
685
+ style.borderTopLeftRadius = r;
686
+ style.borderBottomLeftRadius = r;
687
+ }
688
+ if (cDir === 'r') {
689
+ style.borderTopRightRadius = r;
690
+ style.borderBottomRightRadius = r;
691
+ }
692
+ continue;
693
+ }
694
+ }
695
+ }
696
+
697
+ // 6. Borders
698
+ if (token === 'border') {
699
+ style.borderWidth = 1;
700
+ continue;
701
+ }
702
+ const borderWidthMatch = /^border-(0|2|4|8)$/.exec(token);
703
+ if (borderWidthMatch) {
704
+ style.borderWidth = Number.parseInt(borderWidthMatch[1], 10);
705
+ continue;
706
+ }
707
+ const borderSideMatch = /^border-(t|r|b|l)(?:-(0|2|4|8))?$/.exec(token);
708
+ if (borderSideMatch) {
709
+ const sideMap: Record<string, string> = {
710
+ t: 'borderTopWidth',
711
+ r: 'borderRightWidth',
712
+ b: 'borderBottomWidth',
713
+ l: 'borderLeftWidth',
714
+ };
715
+ style[sideMap[borderSideMatch[1]]] = borderSideMatch[2]
716
+ ? Number.parseInt(borderSideMatch[2], 10)
717
+ : 1;
718
+ continue;
719
+ }
720
+ if (token.startsWith('border-')) {
721
+ const bColor = lookupColorToken(token.slice(7), themeColors);
722
+ if (bColor) {
723
+ style.borderColor = bColor;
724
+ continue;
725
+ }
726
+ }
727
+
728
+ // 7. Background Color (bg-*)
729
+ if (token.startsWith('bg-')) {
730
+ const bgColor = lookupColorToken(token.slice(3), themeColors);
731
+ if (bgColor) {
732
+ style.backgroundColor = bgColor;
733
+ continue;
734
+ }
735
+ }
736
+
737
+ // 8. Typography & Text Color (text-*, font-*, tracking-*, leading-*, etc.)
738
+ if (token === 'italic') {
739
+ style.fontStyle = 'italic';
740
+ continue;
741
+ }
742
+ if (token === 'not-italic') {
743
+ style.fontStyle = 'normal';
744
+ continue;
745
+ }
746
+ if (token === 'uppercase') {
747
+ style.textTransform = 'uppercase';
748
+ continue;
749
+ }
750
+ if (token === 'lowercase') {
751
+ style.textTransform = 'lowercase';
752
+ continue;
753
+ }
754
+ if (token === 'capitalize') {
755
+ style.textTransform = 'capitalize';
756
+ continue;
757
+ }
758
+ if (token === 'underline') {
759
+ style.textDecorationLine = 'underline';
760
+ continue;
761
+ }
762
+ if (token === 'line-through') {
763
+ style.textDecorationLine = 'line-through';
764
+ continue;
765
+ }
766
+ if (token === 'text-left') {
767
+ style.textAlign = 'left';
768
+ continue;
769
+ }
770
+ if (token === 'text-center') {
771
+ style.textAlign = 'center';
772
+ continue;
773
+ }
774
+ if (token === 'text-right') {
775
+ style.textAlign = 'right';
776
+ continue;
777
+ }
778
+
779
+ // Font family / weight shorthands (both standard Tailwind and custom M3 font tokens)
780
+ if (token === 'font-headline' || token === 'font-bold') {
781
+ style.fontWeight = '700';
782
+ continue;
783
+ }
784
+ if (
785
+ token === 'font-headline-medium' ||
786
+ token === 'font-body-semibold' ||
787
+ token === 'font-semibold'
788
+ ) {
789
+ style.fontWeight = '600';
790
+ continue;
791
+ }
792
+ if (token === 'font-body-medium' || token === 'font-medium') {
793
+ style.fontWeight = '500';
794
+ continue;
795
+ }
796
+ if (token === 'font-body' || token === 'font-normal') {
797
+ style.fontWeight = '400';
798
+ continue;
799
+ }
800
+ if (token === 'font-light') {
801
+ style.fontWeight = '300';
802
+ continue;
803
+ }
804
+ if (token === 'font-extrabold' || token === 'font-black') {
805
+ style.fontWeight = '800';
806
+ continue;
807
+ }
808
+ if (token === 'font-mono') {
809
+ style.fontFamily = 'monospace';
810
+ continue;
811
+ }
812
+ if (token.startsWith('font-')) {
813
+ const fontKey = token.slice(5);
814
+ const customFontEntry = themeFontFamilies[fontKey];
815
+ if (customFontEntry !== undefined) {
816
+ const primaryFont = Array.isArray(customFontEntry)
817
+ ? String(customFontEntry[0] ?? '')
818
+ : String(customFontEntry ?? '');
819
+ const lowerFont = primaryFont.toLowerCase();
820
+ if (lowerFont.includes('800') || lowerFont.includes('extrabold') || lowerFont.includes('black')) {
821
+ style.fontWeight = '800';
822
+ } else if (lowerFont.includes('700') || lowerFont.includes('bold')) {
823
+ style.fontWeight = lowerFont.includes('semibold') ? '600' : '700';
824
+ } else if (lowerFont.includes('600') || lowerFont.includes('semibold')) {
825
+ style.fontWeight = '600';
826
+ } else if (lowerFont.includes('500') || lowerFont.includes('medium')) {
827
+ style.fontWeight = '500';
828
+ } else if (lowerFont.includes('300') || lowerFont.includes('light')) {
829
+ style.fontWeight = '300';
830
+ } else {
831
+ style.fontWeight = '400';
832
+ }
833
+ continue;
834
+ }
835
+ }
836
+
837
+ // Letter spacing (tracking-*)
838
+ if (token === 'tracking-tighter') {
839
+ style.letterSpacing = -0.8;
840
+ continue;
841
+ }
842
+ if (token === 'tracking-tight') {
843
+ style.letterSpacing = -0.4;
844
+ continue;
845
+ }
846
+ if (token === 'tracking-normal') {
847
+ style.letterSpacing = 0;
848
+ continue;
849
+ }
850
+ if (token === 'tracking-wide') {
851
+ style.letterSpacing = 0.4;
852
+ continue;
853
+ }
854
+ if (token === 'tracking-wider') {
855
+ style.letterSpacing = 0.8;
856
+ continue;
857
+ }
858
+ if (token === 'tracking-widest') {
859
+ style.letterSpacing = 1.4;
860
+ continue;
861
+ }
862
+
863
+ if (token.startsWith('text-')) {
864
+ const textSuffix = token.slice(5);
865
+
866
+ // Check custom theme fontSize first
867
+ const customFs = themeFontSizes[textSuffix];
868
+ if (customFs !== undefined) {
869
+ if (Array.isArray(customFs)) {
870
+ const sz = parsePxNumber(customFs[0]);
871
+ if (sz !== undefined) style.fontSize = sz;
872
+ const meta = customFs[1];
873
+ if (meta && typeof meta === 'object') {
874
+ const lh = parsePxNumber((meta as Record<string, unknown>).lineHeight);
875
+ const ls = parsePxNumber((meta as Record<string, unknown>).letterSpacing);
876
+ const fw = (meta as Record<string, unknown>).fontWeight;
877
+ if (lh !== undefined) style.lineHeight = lh;
878
+ if (ls !== undefined) style.letterSpacing = ls;
879
+ if (typeof fw === 'string' || typeof fw === 'number') style.fontWeight = String(fw);
880
+ }
881
+ continue;
882
+ }
883
+ const singleSz = parsePxNumber(customFs);
884
+ if (singleSz !== undefined) {
885
+ style.fontSize = singleSz;
886
+ continue;
887
+ }
888
+ }
889
+
890
+ // Check built-in fontSize scale (including M3 type scale: headline-md, title-lg, body-sm, label-md, etc.)
891
+ const builtinFs = DEFAULT_FONT_SIZES[textSuffix];
892
+ if (builtinFs) {
893
+ style.fontSize = builtinFs[0];
894
+ if (builtinFs[1].lineHeight !== undefined) style.lineHeight = builtinFs[1].lineHeight;
895
+ if (builtinFs[1].letterSpacing !== undefined) style.letterSpacing = builtinFs[1].letterSpacing;
896
+ if (builtinFs[1].fontWeight !== undefined && !style.fontWeight) {
897
+ style.fontWeight = builtinFs[1].fontWeight;
898
+ }
899
+ continue;
900
+ }
901
+
902
+ // Arbitrary font size e.g. text-[15px]
903
+ if (textSuffix.startsWith('[') && textSuffix.endsWith(']')) {
904
+ const inner = textSuffix.slice(1, -1);
905
+ const px = parsePxNumber(inner);
906
+ if (px !== undefined && !inner.startsWith('#') && !inner.startsWith('rgb')) {
907
+ style.fontSize = px;
908
+ continue;
909
+ }
910
+ }
911
+
912
+ // Otherwise resolve as text color
913
+ const tColor = lookupColorToken(textSuffix, themeColors);
914
+ if (tColor) {
915
+ style.color = tColor;
916
+ continue;
917
+ }
918
+ }
919
+
920
+ // 9. Opacity & Shadows
921
+ if (token.startsWith('opacity-')) {
922
+ const opVal = Number.parseFloat(token.slice(8));
923
+ if (Number.isFinite(opVal)) {
924
+ style.opacity = opVal <= 1 ? opVal : opVal / 100;
925
+ continue;
926
+ }
927
+ }
928
+ if (token === 'shadow-sm' || token === 'shadow') {
929
+ style.shadowColor = '#0F172A';
930
+ style.shadowOffset = { width: 0, height: 2 };
931
+ style.shadowOpacity = 0.06;
932
+ style.shadowRadius = 6;
933
+ continue;
934
+ }
935
+ if (token === 'shadow-md' || token === 'shadow-lg' || token === 'shadow-xl') {
936
+ style.shadowColor = '#0F172A';
937
+ style.shadowOffset = { width: 0, height: 6 };
938
+ style.shadowOpacity = 0.1;
939
+ style.shadowRadius = 14;
940
+ continue;
941
+ }
942
+ }
943
+
944
+ classStyleCache.set(normalized, style);
945
+ return style;
946
+ }
947
+
948
+ export function withNativeWind<P extends Record<string, unknown>>(
949
+ BaseComponent: React.ComponentType<P>,
950
+ displayName?: string
951
+ ): React.ComponentType<P> {
952
+ const Wrapped = React.forwardRef<unknown, P>((props, ref) => {
953
+ const rawClassName = props?.className as string | undefined;
954
+ const rawContentClassName = props?.contentContainerClassName as string | undefined;
955
+
956
+ if (!rawClassName && !rawContentClassName) {
957
+ return React.createElement(BaseComponent, { ...props, ref } as unknown as P);
958
+ }
959
+
960
+ const twStyle = resolveTailwindClassNameToStyle(rawClassName);
961
+ const twContentStyle = resolveTailwindClassNameToStyle(rawContentClassName);
962
+
963
+ const nextProps: Record<string, unknown> = { ...props, ref };
964
+ delete nextProps.className;
965
+ delete nextProps.contentContainerClassName;
966
+
967
+ if (twStyle) {
968
+ const origStyle = props.style;
969
+ if (typeof origStyle === 'function') {
970
+ nextProps.style = (state: unknown) => [twStyle, (origStyle as (s: unknown) => unknown)(state)];
971
+ } else if (origStyle !== undefined) {
972
+ nextProps.style = [twStyle, origStyle];
973
+ } else {
974
+ nextProps.style = twStyle;
975
+ }
976
+ }
977
+
978
+ if (twContentStyle) {
979
+ const origContentStyle = props.contentContainerStyle;
980
+ nextProps.contentContainerStyle =
981
+ origContentStyle !== undefined ? [twContentStyle, origContentStyle] : twContentStyle;
982
+ }
983
+
984
+ return React.createElement(BaseComponent, nextProps as unknown as P);
985
+ });
986
+
987
+ Wrapped.displayName = displayName ?? BaseComponent.displayName ?? BaseComponent.name ?? 'RNComponent';
988
+ // Copy static properties (e.g., Image.getSize, Image.prefetch, Animated.*)
989
+ for (const key of Object.keys(BaseComponent)) {
990
+ if (!(key in Wrapped)) {
991
+ (Wrapped as unknown as Record<string, unknown>)[key] = (
992
+ BaseComponent as unknown as Record<string, unknown>
993
+ )[key];
994
+ }
995
+ }
996
+ return Wrapped as unknown as React.ComponentType<P>;
997
+ }