@mgcrea/react-native-tailwind 0.10.0 → 0.11.1
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/README.md +159 -13
- package/dist/babel/config-loader.d.ts +12 -3
- package/dist/babel/config-loader.test.ts +14 -12
- package/dist/babel/config-loader.ts +41 -9
- package/dist/babel/index.cjs +91 -54
- package/dist/babel/plugin.d.ts +39 -1
- package/dist/babel/plugin.test.ts +275 -1
- package/dist/babel/plugin.ts +84 -25
- package/dist/babel/utils/colorSchemeModifierProcessing.d.ts +3 -3
- package/dist/babel/utils/colorSchemeModifierProcessing.ts +4 -4
- package/dist/babel/utils/dynamicProcessing.d.ts +5 -5
- package/dist/babel/utils/dynamicProcessing.ts +11 -11
- package/dist/babel/utils/modifierProcessing.d.ts +3 -3
- package/dist/babel/utils/modifierProcessing.ts +5 -5
- package/dist/babel/utils/platformModifierProcessing.d.ts +3 -3
- package/dist/babel/utils/platformModifierProcessing.ts +4 -4
- package/dist/babel/utils/styleInjection.d.ts +5 -3
- package/dist/babel/utils/styleInjection.ts +38 -23
- package/dist/babel/utils/twProcessing.d.ts +3 -3
- package/dist/babel/utils/twProcessing.ts +6 -6
- package/dist/parser/index.d.ts +11 -4
- package/dist/parser/index.js +1 -1
- package/dist/parser/typography.d.ts +3 -1
- package/dist/parser/typography.js +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.cjs.map +3 -3
- package/dist/runtime.d.ts +8 -1
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +3 -3
- package/dist/runtime.test.js +1 -1
- package/package.json +1 -1
- package/src/babel/config-loader.test.ts +14 -12
- package/src/babel/config-loader.ts +41 -9
- package/src/babel/plugin.test.ts +275 -1
- package/src/babel/plugin.ts +84 -25
- package/src/babel/utils/colorSchemeModifierProcessing.ts +4 -4
- package/src/babel/utils/dynamicProcessing.ts +11 -11
- package/src/babel/utils/modifierProcessing.ts +5 -5
- package/src/babel/utils/platformModifierProcessing.ts +4 -4
- package/src/babel/utils/styleInjection.ts +38 -23
- package/src/babel/utils/twProcessing.ts +6 -6
- package/src/parser/index.ts +16 -8
- package/src/parser/typography.ts +14 -2
- package/src/runtime.test.ts +7 -7
- package/src/runtime.ts +37 -14
package/src/babel/plugin.ts
CHANGED
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
} from "../parser/index.js";
|
|
19
19
|
import type { StyleObject } from "../types/core.js";
|
|
20
20
|
import { generateStyleKey } from "../utils/styleKey.js";
|
|
21
|
-
import {
|
|
21
|
+
import type { CustomTheme } from "./config-loader.js";
|
|
22
|
+
import { extractCustomTheme } from "./config-loader.js";
|
|
22
23
|
|
|
23
24
|
// Import utility functions
|
|
24
25
|
import type { SchemeModifierConfig } from "../types/config.js";
|
|
@@ -88,6 +89,42 @@ export type PluginOptions = {
|
|
|
88
89
|
darkSuffix?: string;
|
|
89
90
|
lightSuffix?: string;
|
|
90
91
|
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Configuration for color scheme hook import (dark:/light: modifiers)
|
|
95
|
+
*
|
|
96
|
+
* Allows using custom color scheme hooks from theme providers instead of
|
|
97
|
+
* React Native's built-in useColorScheme.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* // Use custom hook from theme provider
|
|
101
|
+
* {
|
|
102
|
+
* importFrom: '@/hooks/useColorScheme',
|
|
103
|
+
* importName: 'useColorScheme'
|
|
104
|
+
* }
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* // Use React Navigation theme
|
|
108
|
+
* {
|
|
109
|
+
* importFrom: '@react-navigation/native',
|
|
110
|
+
* importName: 'useTheme' // You'd wrap this to return ColorSchemeName
|
|
111
|
+
* }
|
|
112
|
+
*
|
|
113
|
+
* @default { importFrom: 'react-native', importName: 'useColorScheme' }
|
|
114
|
+
*/
|
|
115
|
+
colorScheme?: {
|
|
116
|
+
/**
|
|
117
|
+
* Module to import the color scheme hook from
|
|
118
|
+
* @default 'react-native'
|
|
119
|
+
*/
|
|
120
|
+
importFrom?: string;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Name of the hook to import
|
|
124
|
+
* @default 'useColorScheme'
|
|
125
|
+
*/
|
|
126
|
+
importName?: string;
|
|
127
|
+
};
|
|
91
128
|
};
|
|
92
129
|
|
|
93
130
|
type PluginState = PluginPass & {
|
|
@@ -99,7 +136,10 @@ type PluginState = PluginPass & {
|
|
|
99
136
|
hasColorSchemeImport: boolean;
|
|
100
137
|
needsColorSchemeImport: boolean;
|
|
101
138
|
colorSchemeVariableName: string;
|
|
102
|
-
|
|
139
|
+
colorSchemeImportSource: string; // Where to import the hook from (e.g., 'react-native')
|
|
140
|
+
colorSchemeHookName: string; // Name of the hook to import (e.g., 'useColorScheme')
|
|
141
|
+
colorSchemeLocalIdentifier?: string; // Local identifier if hook is already imported with an alias
|
|
142
|
+
customTheme: CustomTheme;
|
|
103
143
|
schemeModifierConfig: SchemeModifierConfig;
|
|
104
144
|
supportedAttributes: Set<string>;
|
|
105
145
|
attributePatterns: RegExp[];
|
|
@@ -209,6 +249,10 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
209
249
|
lightSuffix: options?.schemeModifier?.lightSuffix ?? "-light",
|
|
210
250
|
};
|
|
211
251
|
|
|
252
|
+
// Color scheme hook configuration from plugin options
|
|
253
|
+
const colorSchemeImportSource = options?.colorScheme?.importFrom ?? "react-native";
|
|
254
|
+
const colorSchemeHookName = options?.colorScheme?.importName ?? "useColorScheme";
|
|
255
|
+
|
|
212
256
|
return {
|
|
213
257
|
name: "react-native-tailwind",
|
|
214
258
|
|
|
@@ -224,6 +268,8 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
224
268
|
state.hasColorSchemeImport = false;
|
|
225
269
|
state.needsColorSchemeImport = false;
|
|
226
270
|
state.colorSchemeVariableName = "_twColorScheme";
|
|
271
|
+
state.colorSchemeImportSource = colorSchemeImportSource;
|
|
272
|
+
state.colorSchemeHookName = colorSchemeHookName;
|
|
227
273
|
state.supportedAttributes = exactMatches;
|
|
228
274
|
state.attributePatterns = patterns;
|
|
229
275
|
state.stylesIdentifier = stylesIdentifier;
|
|
@@ -231,8 +277,8 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
231
277
|
state.hasTwImport = false;
|
|
232
278
|
state.functionComponentsNeedingColorScheme = new Set();
|
|
233
279
|
|
|
234
|
-
// Load custom
|
|
235
|
-
state.
|
|
280
|
+
// Load custom theme from tailwind.config.*
|
|
281
|
+
state.customTheme = extractCustomTheme(state.file.opts.filename ?? "");
|
|
236
282
|
|
|
237
283
|
// Use scheme modifier config from plugin options
|
|
238
284
|
state.schemeModifierConfig = schemeModifierConfig;
|
|
@@ -259,15 +305,21 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
259
305
|
addPlatformImport(path, t);
|
|
260
306
|
}
|
|
261
307
|
|
|
262
|
-
// Add
|
|
308
|
+
// Add color scheme hook import if color scheme modifiers were used and not already present
|
|
263
309
|
if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
|
|
264
|
-
addColorSchemeImport(path, t);
|
|
310
|
+
addColorSchemeImport(path, state.colorSchemeImportSource, state.colorSchemeHookName, t);
|
|
265
311
|
}
|
|
266
312
|
|
|
267
|
-
// Inject
|
|
313
|
+
// Inject color scheme hook in function components that need it
|
|
268
314
|
if (state.needsColorSchemeImport) {
|
|
269
315
|
for (const functionPath of state.functionComponentsNeedingColorScheme) {
|
|
270
|
-
injectColorSchemeHook(
|
|
316
|
+
injectColorSchemeHook(
|
|
317
|
+
functionPath,
|
|
318
|
+
state.colorSchemeVariableName,
|
|
319
|
+
state.colorSchemeHookName,
|
|
320
|
+
state.colorSchemeLocalIdentifier,
|
|
321
|
+
t,
|
|
322
|
+
);
|
|
271
323
|
}
|
|
272
324
|
}
|
|
273
325
|
|
|
@@ -299,13 +351,6 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
299
351
|
return false;
|
|
300
352
|
});
|
|
301
353
|
|
|
302
|
-
const hasUseColorScheme = specifiers.some((spec) => {
|
|
303
|
-
if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
|
|
304
|
-
return spec.imported.name === "useColorScheme";
|
|
305
|
-
}
|
|
306
|
-
return false;
|
|
307
|
-
});
|
|
308
|
-
|
|
309
354
|
// Only track if imports exist - don't mutate yet
|
|
310
355
|
// Actual import injection happens in Program.exit only if needed
|
|
311
356
|
if (hasStyleSheet) {
|
|
@@ -316,14 +361,28 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
316
361
|
state.hasPlatformImport = true;
|
|
317
362
|
}
|
|
318
363
|
|
|
319
|
-
if (hasUseColorScheme) {
|
|
320
|
-
state.hasColorSchemeImport = true;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
364
|
// Store reference to the react-native import for later modification if needed
|
|
324
365
|
state.reactNativeImportPath = path;
|
|
325
366
|
}
|
|
326
367
|
|
|
368
|
+
// Track color scheme hook import from the configured source
|
|
369
|
+
// (default: react-native, but can be custom like @/hooks/useColorScheme)
|
|
370
|
+
if (node.source.value === state.colorSchemeImportSource) {
|
|
371
|
+
const specifiers = node.specifiers;
|
|
372
|
+
|
|
373
|
+
for (const spec of specifiers) {
|
|
374
|
+
if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
|
|
375
|
+
if (spec.imported.name === state.colorSchemeHookName) {
|
|
376
|
+
state.hasColorSchemeImport = true;
|
|
377
|
+
// Track the local identifier (handles aliased imports)
|
|
378
|
+
// e.g., import { useTheme as navTheme } → local name is 'navTheme'
|
|
379
|
+
state.colorSchemeLocalIdentifier = spec.local.name;
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
327
386
|
// Track tw/twStyle imports from main package (for compile-time transformation)
|
|
328
387
|
if (node.source.value === "@mgcrea/react-native-tailwind") {
|
|
329
388
|
const specifiers = node.specifiers;
|
|
@@ -480,7 +539,7 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
480
539
|
// Expand scheme: into dark: and light:
|
|
481
540
|
const expanded = expandSchemeModifier(
|
|
482
541
|
modifier,
|
|
483
|
-
state.
|
|
542
|
+
state.customTheme.colors ?? {},
|
|
484
543
|
state.schemeModifierConfig.darkSuffix,
|
|
485
544
|
state.schemeModifierConfig.lightSuffix,
|
|
486
545
|
);
|
|
@@ -507,7 +566,7 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
507
566
|
|
|
508
567
|
if (componentSupport?.supportedModifiers.includes("placeholder")) {
|
|
509
568
|
const placeholderClasses = placeholderModifiers.map((m) => m.baseClass).join(" ");
|
|
510
|
-
const placeholderColor = parsePlaceholderClasses(placeholderClasses, state.
|
|
569
|
+
const placeholderColor = parsePlaceholderClasses(placeholderClasses, state.customTheme.colors);
|
|
511
570
|
|
|
512
571
|
if (placeholderColor) {
|
|
513
572
|
// Add or merge placeholderTextColor prop
|
|
@@ -561,7 +620,7 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
561
620
|
// Add base classes
|
|
562
621
|
if (hasBaseClasses) {
|
|
563
622
|
const baseClassName = baseClasses.join(" ");
|
|
564
|
-
const baseStyleObject = parseClassName(baseClassName, state.
|
|
623
|
+
const baseStyleObject = parseClassName(baseClassName, state.customTheme);
|
|
565
624
|
const baseStyleKey = generateStyleKey(baseClassName);
|
|
566
625
|
state.styleRegistry.set(baseStyleKey, baseStyleObject);
|
|
567
626
|
styleArrayElements.push(
|
|
@@ -611,7 +670,7 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
611
670
|
}
|
|
612
671
|
|
|
613
672
|
const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
|
|
614
|
-
const modifierStyleObject = parseClassName(modifierClassNames, state.
|
|
673
|
+
const modifierStyleObject = parseClassName(modifierClassNames, state.customTheme);
|
|
615
674
|
const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
|
|
616
675
|
state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
|
|
617
676
|
|
|
@@ -653,7 +712,7 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
653
712
|
// Add base classes
|
|
654
713
|
if (hasBaseClasses) {
|
|
655
714
|
const baseClassName = baseClasses.join(" ");
|
|
656
|
-
const baseStyleObject = parseClassName(baseClassName, state.
|
|
715
|
+
const baseStyleObject = parseClassName(baseClassName, state.customTheme);
|
|
657
716
|
const baseStyleKey = generateStyleKey(baseClassName);
|
|
658
717
|
state.styleRegistry.set(baseStyleKey, baseStyleObject);
|
|
659
718
|
styleExpressions.push(
|
|
@@ -815,7 +874,7 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
815
874
|
return;
|
|
816
875
|
}
|
|
817
876
|
|
|
818
|
-
const styleObject = parseClassName(classNameForStyle, state.
|
|
877
|
+
const styleObject = parseClassName(classNameForStyle, state.customTheme);
|
|
819
878
|
const styleKey = generateStyleKey(classNameForStyle);
|
|
820
879
|
state.styleRegistry.set(styleKey, styleObject);
|
|
821
880
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type * as BabelTypes from "@babel/types";
|
|
6
|
-
import type { ColorSchemeModifierType, ParsedModifier } from "../../parser/index.js";
|
|
6
|
+
import type { ColorSchemeModifierType, CustomTheme, ParsedModifier } from "../../parser/index.js";
|
|
7
7
|
import type { StyleObject } from "../../types/core.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -12,7 +12,7 @@ import type { StyleObject } from "../../types/core.js";
|
|
|
12
12
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
13
13
|
export interface ColorSchemeModifierProcessingState {
|
|
14
14
|
styleRegistry: Map<string, StyleObject>;
|
|
15
|
-
|
|
15
|
+
customTheme: CustomTheme;
|
|
16
16
|
stylesIdentifier: string;
|
|
17
17
|
needsColorSchemeImport: boolean;
|
|
18
18
|
colorSchemeVariableName: string;
|
|
@@ -38,7 +38,7 @@ export interface ColorSchemeModifierProcessingState {
|
|
|
38
38
|
export function processColorSchemeModifiers(
|
|
39
39
|
colorSchemeModifiers: ParsedModifier[],
|
|
40
40
|
state: ColorSchemeModifierProcessingState,
|
|
41
|
-
parseClassName: (className: string,
|
|
41
|
+
parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
|
|
42
42
|
generateStyleKey: (className: string) => string,
|
|
43
43
|
t: typeof BabelTypes,
|
|
44
44
|
): BabelTypes.Expression[] {
|
|
@@ -65,7 +65,7 @@ export function processColorSchemeModifiers(
|
|
|
65
65
|
for (const [scheme, modifiers] of modifiersByScheme) {
|
|
66
66
|
// Parse all classes for this color scheme together
|
|
67
67
|
const classNames = modifiers.map((m) => m.baseClass).join(" ");
|
|
68
|
-
const styleObject = parseClassName(classNames, state.
|
|
68
|
+
const styleObject = parseClassName(classNames, state.customTheme);
|
|
69
69
|
const styleKey = generateStyleKey(`${scheme}_${classNames}`);
|
|
70
70
|
|
|
71
71
|
// Register style in the registry
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { NodePath } from "@babel/core";
|
|
6
6
|
import type * as BabelTypes from "@babel/types";
|
|
7
|
-
import type { ParsedModifier } from "../../parser/index.js";
|
|
7
|
+
import type { CustomTheme, ParsedModifier } from "../../parser/index.js";
|
|
8
8
|
import type { SchemeModifierConfig } from "../../types/config.js";
|
|
9
9
|
import type { StyleObject } from "../../types/core.js";
|
|
10
10
|
|
|
@@ -14,7 +14,7 @@ import type { StyleObject } from "../../types/core.js";
|
|
|
14
14
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
15
15
|
export interface DynamicProcessingState {
|
|
16
16
|
styleRegistry: Map<string, StyleObject>;
|
|
17
|
-
|
|
17
|
+
customTheme: CustomTheme;
|
|
18
18
|
schemeModifierConfig: SchemeModifierConfig;
|
|
19
19
|
stylesIdentifier: string;
|
|
20
20
|
needsPlatformImport: boolean;
|
|
@@ -37,7 +37,7 @@ export type SplitModifierClassesFn = (className: string) => {
|
|
|
37
37
|
export type ProcessPlatformModifiersFn = (
|
|
38
38
|
modifiers: ParsedModifier[],
|
|
39
39
|
state: DynamicProcessingState,
|
|
40
|
-
parseClassName: (className: string,
|
|
40
|
+
parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
|
|
41
41
|
generateStyleKey: (className: string) => string,
|
|
42
42
|
t: typeof BabelTypes,
|
|
43
43
|
) => BabelTypes.Expression;
|
|
@@ -48,7 +48,7 @@ export type ProcessPlatformModifiersFn = (
|
|
|
48
48
|
export type ProcessColorSchemeModifiersFn = (
|
|
49
49
|
modifiers: ParsedModifier[],
|
|
50
50
|
state: DynamicProcessingState,
|
|
51
|
-
parseClassName: (className: string,
|
|
51
|
+
parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
|
|
52
52
|
generateStyleKey: (className: string) => string,
|
|
53
53
|
t: typeof BabelTypes,
|
|
54
54
|
) => BabelTypes.Expression[];
|
|
@@ -85,7 +85,7 @@ export type DynamicExpressionResult = {
|
|
|
85
85
|
export function processDynamicExpression(
|
|
86
86
|
expression: BabelTypes.Expression,
|
|
87
87
|
state: DynamicProcessingState,
|
|
88
|
-
parseClassName: (className: string,
|
|
88
|
+
parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
|
|
89
89
|
generateStyleKey: (className: string) => string,
|
|
90
90
|
splitModifierClasses: SplitModifierClassesFn,
|
|
91
91
|
processPlatformModifiers: ProcessPlatformModifiersFn,
|
|
@@ -164,7 +164,7 @@ export function processDynamicExpression(
|
|
|
164
164
|
function processTemplateLiteral(
|
|
165
165
|
node: BabelTypes.TemplateLiteral,
|
|
166
166
|
state: DynamicProcessingState,
|
|
167
|
-
parseClassName: (className: string,
|
|
167
|
+
parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
|
|
168
168
|
generateStyleKey: (className: string) => string,
|
|
169
169
|
splitModifierClasses: SplitModifierClassesFn,
|
|
170
170
|
processPlatformModifiers: ProcessPlatformModifiersFn,
|
|
@@ -262,7 +262,7 @@ function processTemplateLiteral(
|
|
|
262
262
|
function processConditionalExpression(
|
|
263
263
|
node: BabelTypes.ConditionalExpression,
|
|
264
264
|
state: DynamicProcessingState,
|
|
265
|
-
parseClassName: (className: string,
|
|
265
|
+
parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
|
|
266
266
|
generateStyleKey: (className: string) => string,
|
|
267
267
|
splitModifierClasses: SplitModifierClassesFn,
|
|
268
268
|
processPlatformModifiers: ProcessPlatformModifiersFn,
|
|
@@ -325,7 +325,7 @@ function processConditionalExpression(
|
|
|
325
325
|
function processLogicalExpression(
|
|
326
326
|
node: BabelTypes.LogicalExpression,
|
|
327
327
|
state: DynamicProcessingState,
|
|
328
|
-
parseClassName: (className: string,
|
|
328
|
+
parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
|
|
329
329
|
generateStyleKey: (className: string) => string,
|
|
330
330
|
splitModifierClasses: SplitModifierClassesFn,
|
|
331
331
|
processPlatformModifiers: ProcessPlatformModifiersFn,
|
|
@@ -376,7 +376,7 @@ function processLogicalExpression(
|
|
|
376
376
|
function processStringOrExpressionHelper(
|
|
377
377
|
node: BabelTypes.StringLiteral | BabelTypes.Expression,
|
|
378
378
|
state: DynamicProcessingState,
|
|
379
|
-
parseClassName: (className: string,
|
|
379
|
+
parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
|
|
380
380
|
generateStyleKey: (className: string) => string,
|
|
381
381
|
splitModifierClasses: SplitModifierClassesFn,
|
|
382
382
|
processPlatformModifiers: ProcessPlatformModifiersFn,
|
|
@@ -405,7 +405,7 @@ function processStringOrExpressionHelper(
|
|
|
405
405
|
// Expand scheme: into dark: and light:
|
|
406
406
|
const expanded = expandSchemeModifier(
|
|
407
407
|
modifier,
|
|
408
|
-
state.
|
|
408
|
+
state.customTheme.colors ?? {},
|
|
409
409
|
state.schemeModifierConfig.darkSuffix ?? "-dark",
|
|
410
410
|
state.schemeModifierConfig.lightSuffix ?? "-light",
|
|
411
411
|
);
|
|
@@ -425,7 +425,7 @@ function processStringOrExpressionHelper(
|
|
|
425
425
|
// Process base classes
|
|
426
426
|
if (baseClasses.length > 0) {
|
|
427
427
|
const baseClassName = baseClasses.join(" ");
|
|
428
|
-
const styleObject = parseClassName(baseClassName, state.
|
|
428
|
+
const styleObject = parseClassName(baseClassName, state.customTheme);
|
|
429
429
|
const styleKey = generateStyleKey(baseClassName);
|
|
430
430
|
state.styleRegistry.set(styleKey, styleObject);
|
|
431
431
|
styleElements.push(t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey)));
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type * as BabelTypes from "@babel/types";
|
|
6
|
-
import type { ModifierType, ParsedModifier } from "../../parser/index.js";
|
|
6
|
+
import type { CustomTheme, ModifierType, ParsedModifier } from "../../parser/index.js";
|
|
7
7
|
import type { StyleObject } from "../../types/core.js";
|
|
8
8
|
import { getStatePropertyForModifier } from "./componentSupport.js";
|
|
9
9
|
|
|
@@ -13,7 +13,7 @@ import { getStatePropertyForModifier } from "./componentSupport.js";
|
|
|
13
13
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
14
14
|
export interface ModifierProcessingState {
|
|
15
15
|
styleRegistry: Map<string, StyleObject>;
|
|
16
|
-
|
|
16
|
+
customTheme: CustomTheme;
|
|
17
17
|
stylesIdentifier: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -24,7 +24,7 @@ export interface ModifierProcessingState {
|
|
|
24
24
|
export function processStaticClassNameWithModifiers(
|
|
25
25
|
className: string,
|
|
26
26
|
state: ModifierProcessingState,
|
|
27
|
-
parseClassName: (className: string,
|
|
27
|
+
parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
|
|
28
28
|
generateStyleKey: (className: string) => string,
|
|
29
29
|
splitModifierClasses: (className: string) => { baseClasses: string[]; modifierClasses: ParsedModifier[] },
|
|
30
30
|
t: typeof BabelTypes,
|
|
@@ -35,7 +35,7 @@ export function processStaticClassNameWithModifiers(
|
|
|
35
35
|
let baseStyleExpression: BabelTypes.Node | null = null;
|
|
36
36
|
if (baseClasses.length > 0) {
|
|
37
37
|
const baseClassName = baseClasses.join(" ");
|
|
38
|
-
const baseStyleObject = parseClassName(baseClassName, state.
|
|
38
|
+
const baseStyleObject = parseClassName(baseClassName, state.customTheme);
|
|
39
39
|
const baseStyleKey = generateStyleKey(baseClassName);
|
|
40
40
|
state.styleRegistry.set(baseStyleKey, baseStyleObject);
|
|
41
41
|
baseStyleExpression = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey));
|
|
@@ -66,7 +66,7 @@ export function processStaticClassNameWithModifiers(
|
|
|
66
66
|
for (const [modifierType, modifiers] of modifiersByType) {
|
|
67
67
|
// Parse all modifier classes together
|
|
68
68
|
const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
|
|
69
|
-
const modifierStyleObject = parseClassName(modifierClassNames, state.
|
|
69
|
+
const modifierStyleObject = parseClassName(modifierClassNames, state.customTheme);
|
|
70
70
|
const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
|
|
71
71
|
state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
|
|
72
72
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type * as BabelTypes from "@babel/types";
|
|
6
|
-
import type { ParsedModifier, PlatformModifierType } from "../../parser/index.js";
|
|
6
|
+
import type { CustomTheme, ParsedModifier, PlatformModifierType } from "../../parser/index.js";
|
|
7
7
|
import type { StyleObject } from "../../types/core.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -12,7 +12,7 @@ import type { StyleObject } from "../../types/core.js";
|
|
|
12
12
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
13
13
|
export interface PlatformModifierProcessingState {
|
|
14
14
|
styleRegistry: Map<string, StyleObject>;
|
|
15
|
-
|
|
15
|
+
customTheme: CustomTheme;
|
|
16
16
|
stylesIdentifier: string;
|
|
17
17
|
needsPlatformImport: boolean;
|
|
18
18
|
}
|
|
@@ -34,7 +34,7 @@ export interface PlatformModifierProcessingState {
|
|
|
34
34
|
export function processPlatformModifiers(
|
|
35
35
|
platformModifiers: ParsedModifier[],
|
|
36
36
|
state: PlatformModifierProcessingState,
|
|
37
|
-
parseClassName: (className: string,
|
|
37
|
+
parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
|
|
38
38
|
generateStyleKey: (className: string) => string,
|
|
39
39
|
t: typeof BabelTypes,
|
|
40
40
|
): BabelTypes.Expression {
|
|
@@ -61,7 +61,7 @@ export function processPlatformModifiers(
|
|
|
61
61
|
for (const [platform, modifiers] of modifiersByPlatform) {
|
|
62
62
|
// Parse all classes for this platform together
|
|
63
63
|
const classNames = modifiers.map((m) => m.baseClass).join(" ");
|
|
64
|
-
const styleObject = parseClassName(classNames, state.
|
|
64
|
+
const styleObject = parseClassName(classNames, state.customTheme);
|
|
65
65
|
const styleKey = generateStyleKey(`${platform}_${classNames}`);
|
|
66
66
|
|
|
67
67
|
// Register style in the registry
|
|
@@ -67,54 +67,64 @@ export function addPlatformImport(path: NodePath<BabelTypes.Program>, t: typeof
|
|
|
67
67
|
/**
|
|
68
68
|
* Add useColorScheme import to the file or merge with existing react-native import
|
|
69
69
|
*/
|
|
70
|
-
export function addColorSchemeImport(
|
|
71
|
-
|
|
70
|
+
export function addColorSchemeImport(
|
|
71
|
+
path: NodePath<BabelTypes.Program>,
|
|
72
|
+
importSource: string,
|
|
73
|
+
hookName: string,
|
|
74
|
+
t: typeof BabelTypes,
|
|
75
|
+
): void {
|
|
76
|
+
// Check if there's already an import from the specified source
|
|
72
77
|
const body = path.node.body;
|
|
73
|
-
let
|
|
78
|
+
let existingValueImport: BabelTypes.ImportDeclaration | null = null;
|
|
74
79
|
|
|
75
80
|
for (const statement of body) {
|
|
76
|
-
if (t.isImportDeclaration(statement) && statement.source.value ===
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
if (t.isImportDeclaration(statement) && statement.source.value === importSource) {
|
|
82
|
+
// Only consider value imports (not type-only imports which get erased)
|
|
83
|
+
if (statement.importKind !== "type") {
|
|
84
|
+
existingValueImport = statement;
|
|
85
|
+
break; // Found a value import, we can stop
|
|
86
|
+
}
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
// If we found a value import (not type-only), merge with it
|
|
91
|
+
if (existingValueImport) {
|
|
92
|
+
// Check if the hook is already imported
|
|
93
|
+
const hasHook = existingValueImport.specifiers.some(
|
|
85
94
|
(spec) =>
|
|
86
|
-
t.isImportSpecifier(spec) &&
|
|
87
|
-
spec.imported.type === "Identifier" &&
|
|
88
|
-
spec.imported.name === "useColorScheme",
|
|
95
|
+
t.isImportSpecifier(spec) && spec.imported.type === "Identifier" && spec.imported.name === hookName,
|
|
89
96
|
);
|
|
90
97
|
|
|
91
|
-
if (!
|
|
92
|
-
// Add
|
|
93
|
-
|
|
94
|
-
t.importSpecifier(t.identifier("useColorScheme"), t.identifier("useColorScheme")),
|
|
95
|
-
);
|
|
98
|
+
if (!hasHook) {
|
|
99
|
+
// Add hook to existing value import
|
|
100
|
+
existingValueImport.specifiers.push(t.importSpecifier(t.identifier(hookName), t.identifier(hookName)));
|
|
96
101
|
}
|
|
97
102
|
} else {
|
|
98
|
-
//
|
|
103
|
+
// No value import exists - create a new one
|
|
104
|
+
// (Don't merge with type-only imports as they get erased by Babel/TypeScript)
|
|
99
105
|
const importDeclaration = t.importDeclaration(
|
|
100
|
-
[t.importSpecifier(t.identifier(
|
|
101
|
-
t.stringLiteral(
|
|
106
|
+
[t.importSpecifier(t.identifier(hookName), t.identifier(hookName))],
|
|
107
|
+
t.stringLiteral(importSource),
|
|
102
108
|
);
|
|
103
109
|
path.unshiftContainer("body", importDeclaration);
|
|
104
110
|
}
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
/**
|
|
108
|
-
* Inject
|
|
114
|
+
* Inject color scheme hook call at the top of a function component
|
|
109
115
|
*
|
|
110
116
|
* @param functionPath - Path to the function component
|
|
111
117
|
* @param colorSchemeVariableName - Name for the color scheme variable
|
|
118
|
+
* @param hookName - Name of the hook to call (e.g., 'useColorScheme')
|
|
119
|
+
* @param localIdentifier - Local identifier if hook is already imported with an alias
|
|
112
120
|
* @param t - Babel types
|
|
113
121
|
* @returns true if hook was injected, false if already exists
|
|
114
122
|
*/
|
|
115
123
|
export function injectColorSchemeHook(
|
|
116
124
|
functionPath: NodePath<BabelTypes.Function>,
|
|
117
125
|
colorSchemeVariableName: string,
|
|
126
|
+
hookName: string,
|
|
127
|
+
localIdentifier: string | undefined,
|
|
118
128
|
t: typeof BabelTypes,
|
|
119
129
|
): boolean {
|
|
120
130
|
let body = functionPath.node.body;
|
|
@@ -151,11 +161,16 @@ export function injectColorSchemeHook(
|
|
|
151
161
|
return false; // Already injected
|
|
152
162
|
}
|
|
153
163
|
|
|
154
|
-
//
|
|
164
|
+
// Use the local identifier if hook was already imported with an alias,
|
|
165
|
+
// otherwise use the configured hook name
|
|
166
|
+
// e.g., import { useTheme as navTheme } → call navTheme()
|
|
167
|
+
const identifierToCall = localIdentifier ?? hookName;
|
|
168
|
+
|
|
169
|
+
// Create: const _twColorScheme = useColorScheme(); (or aliased name if already imported)
|
|
155
170
|
const hookCall = t.variableDeclaration("const", [
|
|
156
171
|
t.variableDeclarator(
|
|
157
172
|
t.identifier(colorSchemeVariableName),
|
|
158
|
-
t.callExpression(t.identifier(
|
|
173
|
+
t.callExpression(t.identifier(identifierToCall), []),
|
|
159
174
|
),
|
|
160
175
|
]);
|
|
161
176
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { NodePath } from "@babel/core";
|
|
6
6
|
import type * as BabelTypes from "@babel/types";
|
|
7
|
-
import type { ModifierType, ParsedModifier } from "../../parser/index.js";
|
|
7
|
+
import type { CustomTheme, ModifierType, ParsedModifier } from "../../parser/index.js";
|
|
8
8
|
import { expandSchemeModifier, isSchemeModifier } from "../../parser/index.js";
|
|
9
9
|
import type { SchemeModifierConfig } from "../../types/config.js";
|
|
10
10
|
import type { StyleObject } from "../../types/core.js";
|
|
@@ -15,7 +15,7 @@ import type { StyleObject } from "../../types/core.js";
|
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
16
16
|
export interface TwProcessingState {
|
|
17
17
|
styleRegistry: Map<string, StyleObject>;
|
|
18
|
-
|
|
18
|
+
customTheme: CustomTheme;
|
|
19
19
|
schemeModifierConfig: SchemeModifierConfig;
|
|
20
20
|
stylesIdentifier: string;
|
|
21
21
|
}
|
|
@@ -28,7 +28,7 @@ export function processTwCall(
|
|
|
28
28
|
className: string,
|
|
29
29
|
path: NodePath,
|
|
30
30
|
state: TwProcessingState,
|
|
31
|
-
parseClassName: (className: string,
|
|
31
|
+
parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject,
|
|
32
32
|
generateStyleKey: (className: string) => string,
|
|
33
33
|
splitModifierClasses: (className: string) => { baseClasses: string[]; modifierClasses: ParsedModifier[] },
|
|
34
34
|
t: typeof BabelTypes,
|
|
@@ -42,7 +42,7 @@ export function processTwCall(
|
|
|
42
42
|
// Expand scheme: into dark: and light:
|
|
43
43
|
const expanded = expandSchemeModifier(
|
|
44
44
|
modifier,
|
|
45
|
-
state.
|
|
45
|
+
state.customTheme.colors ?? {},
|
|
46
46
|
state.schemeModifierConfig.darkSuffix ?? "-dark",
|
|
47
47
|
state.schemeModifierConfig.lightSuffix ?? "-light",
|
|
48
48
|
);
|
|
@@ -59,7 +59,7 @@ export function processTwCall(
|
|
|
59
59
|
// Parse and add base styles
|
|
60
60
|
if (baseClasses.length > 0) {
|
|
61
61
|
const baseClassName = baseClasses.join(" ");
|
|
62
|
-
const baseStyleObject = parseClassName(baseClassName, state.
|
|
62
|
+
const baseStyleObject = parseClassName(baseClassName, state.customTheme);
|
|
63
63
|
const baseStyleKey = generateStyleKey(baseClassName);
|
|
64
64
|
state.styleRegistry.set(baseStyleKey, baseStyleObject);
|
|
65
65
|
|
|
@@ -89,7 +89,7 @@ export function processTwCall(
|
|
|
89
89
|
// Add modifier styles
|
|
90
90
|
for (const [modifierType, modifiers] of modifiersByType) {
|
|
91
91
|
const modifierClassNames = modifiers.map((m) => m.baseClass).join(" ");
|
|
92
|
-
const modifierStyleObject = parseClassName(modifierClassNames, state.
|
|
92
|
+
const modifierStyleObject = parseClassName(modifierClassNames, state.customTheme);
|
|
93
93
|
const modifierStyleKey = generateStyleKey(`${modifierType}_${modifierClassNames}`);
|
|
94
94
|
state.styleRegistry.set(modifierStyleKey, modifierStyleObject);
|
|
95
95
|
|
package/src/parser/index.ts
CHANGED
|
@@ -14,18 +14,26 @@ import { parseSpacing } from "./spacing";
|
|
|
14
14
|
import { parseTransform } from "./transforms";
|
|
15
15
|
import { parseTypography } from "./typography";
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Custom theme configuration (subset of tailwind.config theme extensions)
|
|
19
|
+
*/
|
|
20
|
+
export type CustomTheme = {
|
|
21
|
+
colors?: Record<string, string>;
|
|
22
|
+
fontFamily?: Record<string, string>;
|
|
23
|
+
};
|
|
24
|
+
|
|
17
25
|
/**
|
|
18
26
|
* Parse a className string and return a React Native style object
|
|
19
27
|
* @param className - Space-separated class names
|
|
20
|
-
* @param
|
|
28
|
+
* @param customTheme - Optional custom theme from tailwind.config
|
|
21
29
|
* @returns React Native style object
|
|
22
30
|
*/
|
|
23
|
-
export function parseClassName(className: string,
|
|
31
|
+
export function parseClassName(className: string, customTheme?: CustomTheme): StyleObject {
|
|
24
32
|
const classes = className.split(/\s+/).filter(Boolean);
|
|
25
33
|
const style: StyleObject = {};
|
|
26
34
|
|
|
27
35
|
for (const cls of classes) {
|
|
28
|
-
const parsedStyle = parseClass(cls,
|
|
36
|
+
const parsedStyle = parseClass(cls, customTheme);
|
|
29
37
|
Object.assign(style, parsedStyle);
|
|
30
38
|
}
|
|
31
39
|
|
|
@@ -35,19 +43,19 @@ export function parseClassName(className: string, customColors?: Record<string,
|
|
|
35
43
|
/**
|
|
36
44
|
* Parse a single class name
|
|
37
45
|
* @param cls - Single class name
|
|
38
|
-
* @param
|
|
46
|
+
* @param customTheme - Optional custom theme from tailwind.config
|
|
39
47
|
* @returns React Native style object
|
|
40
48
|
*/
|
|
41
|
-
export function parseClass(cls: string,
|
|
49
|
+
export function parseClass(cls: string, customTheme?: CustomTheme): StyleObject {
|
|
42
50
|
// Try each parser in order
|
|
43
51
|
// Note: parseBorder must come before parseColor to avoid border-[3px] being parsed as a color
|
|
44
|
-
// parseColor
|
|
52
|
+
// parseColor and parseTypography get custom theme, others don't need it
|
|
45
53
|
const parsers: Array<(cls: string) => StyleObject | null> = [
|
|
46
54
|
parseSpacing,
|
|
47
55
|
parseBorder,
|
|
48
|
-
(cls: string) => parseColor(cls,
|
|
56
|
+
(cls: string) => parseColor(cls, customTheme?.colors),
|
|
49
57
|
parseLayout,
|
|
50
|
-
parseTypography,
|
|
58
|
+
(cls: string) => parseTypography(cls, customTheme?.fontFamily),
|
|
51
59
|
parseSizing,
|
|
52
60
|
parseShadow,
|
|
53
61
|
parseAspectRatio,
|