@mgcrea/react-native-tailwind 0.9.1 → 0.10.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/README.md +356 -30
- package/dist/babel/config-loader.test.ts +152 -0
- package/dist/babel/index.cjs +547 -47
- package/dist/babel/plugin.d.ts +21 -0
- package/dist/babel/plugin.test.ts +331 -0
- package/dist/babel/plugin.ts +258 -28
- package/dist/babel/utils/colorSchemeModifierProcessing.d.ts +34 -0
- package/dist/babel/utils/colorSchemeModifierProcessing.ts +89 -0
- package/dist/babel/utils/dynamicProcessing.d.ts +33 -2
- package/dist/babel/utils/dynamicProcessing.ts +352 -33
- package/dist/babel/utils/styleInjection.d.ts +13 -0
- package/dist/babel/utils/styleInjection.ts +101 -0
- package/dist/babel/utils/styleTransforms.test.ts +56 -0
- package/dist/babel/utils/twProcessing.d.ts +2 -0
- package/dist/babel/utils/twProcessing.ts +22 -1
- package/dist/parser/index.d.ts +2 -2
- package/dist/parser/index.js +1 -1
- package/dist/parser/modifiers.d.ts +48 -2
- package/dist/parser/modifiers.js +1 -1
- package/dist/parser/modifiers.test.js +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.cjs.map +3 -3
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +3 -3
- package/dist/types/config.d.ts +7 -0
- package/dist/types/config.js +0 -0
- package/package.json +3 -2
- package/src/babel/config-loader.test.ts +152 -0
- package/src/babel/plugin.test.ts +331 -0
- package/src/babel/plugin.ts +258 -28
- package/src/babel/utils/colorSchemeModifierProcessing.ts +89 -0
- package/src/babel/utils/dynamicProcessing.ts +352 -33
- package/src/babel/utils/styleInjection.ts +101 -0
- package/src/babel/utils/styleTransforms.test.ts +56 -0
- package/src/babel/utils/twProcessing.ts +22 -1
- package/src/parser/index.ts +12 -1
- package/src/parser/modifiers.test.ts +151 -1
- package/src/parser/modifiers.ts +139 -4
- package/src/types/config.ts +7 -0
package/dist/babel/plugin.ts
CHANGED
|
@@ -7,7 +7,10 @@ import type { NodePath, PluginObj, PluginPass } from "@babel/core";
|
|
|
7
7
|
import * as BabelTypes from "@babel/types";
|
|
8
8
|
import type { ParsedModifier, StateModifierType } from "../parser/index.js";
|
|
9
9
|
import {
|
|
10
|
+
expandSchemeModifier,
|
|
11
|
+
isColorSchemeModifier,
|
|
10
12
|
isPlatformModifier,
|
|
13
|
+
isSchemeModifier,
|
|
11
14
|
isStateModifier,
|
|
12
15
|
parseClassName,
|
|
13
16
|
parsePlaceholderClasses,
|
|
@@ -18,17 +21,25 @@ import { generateStyleKey } from "../utils/styleKey.js";
|
|
|
18
21
|
import { extractCustomColors } from "./config-loader.js";
|
|
19
22
|
|
|
20
23
|
// Import utility functions
|
|
24
|
+
import type { SchemeModifierConfig } from "../types/config.js";
|
|
21
25
|
import {
|
|
22
26
|
DEFAULT_CLASS_ATTRIBUTES,
|
|
23
27
|
buildAttributeMatchers,
|
|
24
28
|
getTargetStyleProp,
|
|
25
29
|
isAttributeSupported,
|
|
26
30
|
} from "./utils/attributeMatchers.js";
|
|
31
|
+
import { processColorSchemeModifiers } from "./utils/colorSchemeModifierProcessing.js";
|
|
27
32
|
import { getComponentModifierSupport, getStatePropertyForModifier } from "./utils/componentSupport.js";
|
|
28
33
|
import { processDynamicExpression } from "./utils/dynamicProcessing.js";
|
|
29
34
|
import { createStyleFunction, processStaticClassNameWithModifiers } from "./utils/modifierProcessing.js";
|
|
30
35
|
import { processPlatformModifiers } from "./utils/platformModifierProcessing.js";
|
|
31
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
addColorSchemeImport,
|
|
38
|
+
addPlatformImport,
|
|
39
|
+
addStyleSheetImport,
|
|
40
|
+
injectColorSchemeHook,
|
|
41
|
+
injectStylesAtTop,
|
|
42
|
+
} from "./utils/styleInjection.js";
|
|
32
43
|
import {
|
|
33
44
|
addOrMergePlaceholderTextColorProp,
|
|
34
45
|
findStyleAttribute,
|
|
@@ -61,6 +72,22 @@ export type PluginOptions = {
|
|
|
61
72
|
* @default '_twStyles'
|
|
62
73
|
*/
|
|
63
74
|
stylesIdentifier?: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Configuration for the scheme: modifier that expands to both dark: and light: modifiers
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* {
|
|
81
|
+
* darkSuffix: '-dark', // scheme:bg-primary -> dark:bg-primary-dark
|
|
82
|
+
* lightSuffix: '-light' // scheme:bg-primary -> light:bg-primary-light
|
|
83
|
+
* }
|
|
84
|
+
*
|
|
85
|
+
* @default { darkSuffix: '-dark', lightSuffix: '-light' }
|
|
86
|
+
*/
|
|
87
|
+
schemeModifier?: {
|
|
88
|
+
darkSuffix?: string;
|
|
89
|
+
lightSuffix?: string;
|
|
90
|
+
};
|
|
64
91
|
};
|
|
65
92
|
|
|
66
93
|
type PluginState = PluginPass & {
|
|
@@ -69,7 +96,11 @@ type PluginState = PluginPass & {
|
|
|
69
96
|
hasStyleSheetImport: boolean;
|
|
70
97
|
hasPlatformImport: boolean;
|
|
71
98
|
needsPlatformImport: boolean;
|
|
99
|
+
hasColorSchemeImport: boolean;
|
|
100
|
+
needsColorSchemeImport: boolean;
|
|
101
|
+
colorSchemeVariableName: string;
|
|
72
102
|
customColors: Record<string, string>;
|
|
103
|
+
schemeModifierConfig: SchemeModifierConfig;
|
|
73
104
|
supportedAttributes: Set<string>;
|
|
74
105
|
attributePatterns: RegExp[];
|
|
75
106
|
stylesIdentifier: string;
|
|
@@ -78,11 +109,91 @@ type PluginState = PluginPass & {
|
|
|
78
109
|
hasTwImport: boolean;
|
|
79
110
|
// Track react-native import path for conditional StyleSheet/Platform injection
|
|
80
111
|
reactNativeImportPath?: NodePath<BabelTypes.ImportDeclaration>;
|
|
112
|
+
// Track function components that need colorScheme hook injection
|
|
113
|
+
functionComponentsNeedingColorScheme: Set<NodePath<BabelTypes.Function>>;
|
|
81
114
|
};
|
|
82
115
|
|
|
83
116
|
// Default identifier for the generated StyleSheet constant
|
|
84
117
|
const DEFAULT_STYLES_IDENTIFIER = "_twStyles";
|
|
85
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Check if a function path represents a valid component scope for hook injection
|
|
121
|
+
* Valid scopes:
|
|
122
|
+
* - Top-level FunctionDeclaration
|
|
123
|
+
* - FunctionExpression/ArrowFunctionExpression in top-level VariableDeclarator (with PascalCase name)
|
|
124
|
+
* - NOT class methods, NOT nested functions, NOT inline callbacks
|
|
125
|
+
*
|
|
126
|
+
* @param functionPath - Path to the function to check
|
|
127
|
+
* @param t - Babel types
|
|
128
|
+
* @returns true if function is a valid component scope
|
|
129
|
+
*/
|
|
130
|
+
function isComponentScope(functionPath: NodePath<BabelTypes.Function>, t: typeof BabelTypes): boolean {
|
|
131
|
+
const node = functionPath.node;
|
|
132
|
+
const parent = functionPath.parent;
|
|
133
|
+
const parentPath = functionPath.parentPath;
|
|
134
|
+
|
|
135
|
+
// Reject class methods (class components not supported for hooks)
|
|
136
|
+
if (t.isClassMethod(parent)) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Reject if inside a class body
|
|
141
|
+
if (functionPath.findParent((p) => t.isClassBody(p.node))) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Accept top-level FunctionDeclaration
|
|
146
|
+
if (t.isFunctionDeclaration(node)) {
|
|
147
|
+
// Check if it's at program level or in export
|
|
148
|
+
if (t.isProgram(parent) || t.isExportNamedDeclaration(parent) || t.isExportDefaultDeclaration(parent)) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Accept FunctionExpression/ArrowFunctionExpression in VariableDeclarator
|
|
154
|
+
if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
|
|
155
|
+
if (t.isVariableDeclarator(parent)) {
|
|
156
|
+
// Check if it's at program level (via VariableDeclaration)
|
|
157
|
+
const varDeclarationPath = parentPath?.parentPath;
|
|
158
|
+
if (
|
|
159
|
+
varDeclarationPath &&
|
|
160
|
+
t.isVariableDeclaration(varDeclarationPath.node) &&
|
|
161
|
+
(t.isProgram(varDeclarationPath.parent) || t.isExportNamedDeclaration(varDeclarationPath.parent))
|
|
162
|
+
) {
|
|
163
|
+
// Check for PascalCase naming (component convention)
|
|
164
|
+
if (t.isIdentifier(parent.id)) {
|
|
165
|
+
const name = parent.id.name;
|
|
166
|
+
return /^[A-Z]/.test(name); // Starts with uppercase
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Find the nearest valid component scope for hook injection
|
|
177
|
+
* Climbs the AST from the current path to find a component-level function
|
|
178
|
+
*
|
|
179
|
+
* @param path - Starting path (e.g., JSXAttribute)
|
|
180
|
+
* @param t - Babel types
|
|
181
|
+
* @returns NodePath to component function, or null if not found
|
|
182
|
+
*/
|
|
183
|
+
function findComponentScope(path: NodePath, t: typeof BabelTypes): NodePath<BabelTypes.Function> | null {
|
|
184
|
+
let current = path.getFunctionParent();
|
|
185
|
+
|
|
186
|
+
while (current) {
|
|
187
|
+
if (t.isFunction(current.node) && isComponentScope(current, t)) {
|
|
188
|
+
return current;
|
|
189
|
+
}
|
|
190
|
+
// Climb to next parent function
|
|
191
|
+
current = current.getFunctionParent();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
86
197
|
export default function reactNativeTailwindBabelPlugin(
|
|
87
198
|
{ types: t }: { types: typeof BabelTypes },
|
|
88
199
|
options?: PluginOptions,
|
|
@@ -92,6 +203,12 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
92
203
|
const { exactMatches, patterns } = buildAttributeMatchers(attributes);
|
|
93
204
|
const stylesIdentifier = options?.stylesIdentifier ?? DEFAULT_STYLES_IDENTIFIER;
|
|
94
205
|
|
|
206
|
+
// Scheme modifier configuration from plugin options
|
|
207
|
+
const schemeModifierConfig = {
|
|
208
|
+
darkSuffix: options?.schemeModifier?.darkSuffix ?? "-dark",
|
|
209
|
+
lightSuffix: options?.schemeModifier?.lightSuffix ?? "-light",
|
|
210
|
+
};
|
|
211
|
+
|
|
95
212
|
return {
|
|
96
213
|
name: "react-native-tailwind",
|
|
97
214
|
|
|
@@ -104,14 +221,21 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
104
221
|
state.hasStyleSheetImport = false;
|
|
105
222
|
state.hasPlatformImport = false;
|
|
106
223
|
state.needsPlatformImport = false;
|
|
224
|
+
state.hasColorSchemeImport = false;
|
|
225
|
+
state.needsColorSchemeImport = false;
|
|
226
|
+
state.colorSchemeVariableName = "_twColorScheme";
|
|
107
227
|
state.supportedAttributes = exactMatches;
|
|
108
228
|
state.attributePatterns = patterns;
|
|
109
229
|
state.stylesIdentifier = stylesIdentifier;
|
|
110
230
|
state.twImportNames = new Set();
|
|
111
231
|
state.hasTwImport = false;
|
|
232
|
+
state.functionComponentsNeedingColorScheme = new Set();
|
|
112
233
|
|
|
113
234
|
// Load custom colors from tailwind.config.*
|
|
114
235
|
state.customColors = extractCustomColors(state.file.opts.filename ?? "");
|
|
236
|
+
|
|
237
|
+
// Use scheme modifier config from plugin options
|
|
238
|
+
state.schemeModifierConfig = schemeModifierConfig;
|
|
115
239
|
},
|
|
116
240
|
|
|
117
241
|
exit(path, state) {
|
|
@@ -135,6 +259,18 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
135
259
|
addPlatformImport(path, t);
|
|
136
260
|
}
|
|
137
261
|
|
|
262
|
+
// Add useColorScheme import if color scheme modifiers were used and not already present
|
|
263
|
+
if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
|
|
264
|
+
addColorSchemeImport(path, t);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Inject useColorScheme hook in function components that need it
|
|
268
|
+
if (state.needsColorSchemeImport) {
|
|
269
|
+
for (const functionPath of state.functionComponentsNeedingColorScheme) {
|
|
270
|
+
injectColorSchemeHook(functionPath, state.colorSchemeVariableName, t);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
138
274
|
// Generate and inject StyleSheet.create at the beginning of the file (after imports)
|
|
139
275
|
// This ensures _twStyles is defined before any code that references it
|
|
140
276
|
injectStylesAtTop(path, state.styleRegistry, state.stylesIdentifier, t);
|
|
@@ -163,6 +299,13 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
163
299
|
return false;
|
|
164
300
|
});
|
|
165
301
|
|
|
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
|
+
|
|
166
309
|
// Only track if imports exist - don't mutate yet
|
|
167
310
|
// Actual import injection happens in Program.exit only if needed
|
|
168
311
|
if (hasStyleSheet) {
|
|
@@ -173,6 +316,10 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
173
316
|
state.hasPlatformImport = true;
|
|
174
317
|
}
|
|
175
318
|
|
|
319
|
+
if (hasUseColorScheme) {
|
|
320
|
+
state.hasColorSchemeImport = true;
|
|
321
|
+
}
|
|
322
|
+
|
|
176
323
|
// Store reference to the react-native import for later modification if needed
|
|
177
324
|
state.reactNativeImportPath = path;
|
|
178
325
|
}
|
|
@@ -323,12 +470,31 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
323
470
|
|
|
324
471
|
state.hasClassNames = true;
|
|
325
472
|
|
|
326
|
-
// Check if className contains modifiers (active:, hover:, focus:, placeholder:, ios:, android:, web:)
|
|
327
|
-
const { baseClasses, modifierClasses } = splitModifierClasses(className);
|
|
473
|
+
// Check if className contains modifiers (active:, hover:, focus:, placeholder:, ios:, android:, web:, dark:, light:, scheme:)
|
|
474
|
+
const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(className);
|
|
475
|
+
|
|
476
|
+
// Expand scheme: modifiers into dark: and light: modifiers
|
|
477
|
+
const modifierClasses: ParsedModifier[] = [];
|
|
478
|
+
for (const modifier of rawModifierClasses) {
|
|
479
|
+
if (isSchemeModifier(modifier.modifier)) {
|
|
480
|
+
// Expand scheme: into dark: and light:
|
|
481
|
+
const expanded = expandSchemeModifier(
|
|
482
|
+
modifier,
|
|
483
|
+
state.customColors,
|
|
484
|
+
state.schemeModifierConfig.darkSuffix,
|
|
485
|
+
state.schemeModifierConfig.lightSuffix,
|
|
486
|
+
);
|
|
487
|
+
modifierClasses.push(...expanded);
|
|
488
|
+
} else {
|
|
489
|
+
// Keep other modifiers as-is
|
|
490
|
+
modifierClasses.push(modifier);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
328
493
|
|
|
329
494
|
// Separate modifiers by type
|
|
330
495
|
const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
|
|
331
496
|
const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
|
|
497
|
+
const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
|
|
332
498
|
const stateModifiers = modifierClasses.filter(
|
|
333
499
|
(m) => isStateModifier(m.modifier) && m.modifier !== "placeholder",
|
|
334
500
|
);
|
|
@@ -359,18 +525,37 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
359
525
|
|
|
360
526
|
// Handle combination of modifiers
|
|
361
527
|
const hasPlatformModifiers = platformModifiers.length > 0;
|
|
528
|
+
const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
|
|
362
529
|
const hasStateModifiers = stateModifiers.length > 0;
|
|
363
530
|
const hasBaseClasses = baseClasses.length > 0;
|
|
364
531
|
|
|
365
|
-
// If we have
|
|
366
|
-
|
|
367
|
-
if (
|
|
532
|
+
// If we have color scheme modifiers, we need to track the parent function component
|
|
533
|
+
let componentScope: NodePath<BabelTypes.Function> | null = null;
|
|
534
|
+
if (hasColorSchemeModifiers) {
|
|
535
|
+
componentScope = findComponentScope(path, t);
|
|
536
|
+
if (componentScope) {
|
|
537
|
+
state.functionComponentsNeedingColorScheme.add(componentScope);
|
|
538
|
+
} else {
|
|
539
|
+
// Warn if color scheme modifiers used in invalid context (class component, nested function)
|
|
540
|
+
if (process.env.NODE_ENV !== "production") {
|
|
541
|
+
console.warn(
|
|
542
|
+
`[react-native-tailwind] dark:/light: modifiers require a function component scope. ` +
|
|
543
|
+
`Found in non-component context at ${state.file.opts.filename ?? "unknown"}. ` +
|
|
544
|
+
`These modifiers are not supported in class components or nested callbacks.`,
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// If we have multiple modifier types, combine them in an array expression
|
|
551
|
+
// For state modifiers, wrap in arrow function; for color scheme, they're just conditionals
|
|
552
|
+
if (hasStateModifiers && (hasPlatformModifiers || hasColorSchemeModifiers)) {
|
|
368
553
|
// Get the JSX opening element for component support checking
|
|
369
554
|
const jsxOpeningElement = path.parent;
|
|
370
555
|
const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
|
|
371
556
|
|
|
372
557
|
if (componentSupport) {
|
|
373
|
-
// Build style array: [baseStyle, Platform.select(...), stateConditionals]
|
|
558
|
+
// Build style array: [baseStyle, Platform.select(...), colorSchemeConditionals, stateConditionals]
|
|
374
559
|
const styleArrayElements: BabelTypes.Expression[] = [];
|
|
375
560
|
|
|
376
561
|
// Add base classes
|
|
@@ -385,14 +570,28 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
385
570
|
}
|
|
386
571
|
|
|
387
572
|
// Add platform modifiers as Platform.select()
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
573
|
+
if (hasPlatformModifiers) {
|
|
574
|
+
const platformSelectExpression = processPlatformModifiers(
|
|
575
|
+
platformModifiers,
|
|
576
|
+
state,
|
|
577
|
+
parseClassName,
|
|
578
|
+
generateStyleKey,
|
|
579
|
+
t,
|
|
580
|
+
);
|
|
581
|
+
styleArrayElements.push(platformSelectExpression);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Add color scheme modifiers as conditionals (only if component scope exists)
|
|
585
|
+
if (hasColorSchemeModifiers && componentScope) {
|
|
586
|
+
const colorSchemeConditionals = processColorSchemeModifiers(
|
|
587
|
+
colorSchemeModifiers,
|
|
588
|
+
state,
|
|
589
|
+
parseClassName,
|
|
590
|
+
generateStyleKey,
|
|
591
|
+
t,
|
|
592
|
+
);
|
|
593
|
+
styleArrayElements.push(...colorSchemeConditionals);
|
|
594
|
+
}
|
|
396
595
|
|
|
397
596
|
// Add state modifiers as conditionals
|
|
398
597
|
// Group by modifier type
|
|
@@ -446,9 +645,9 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
446
645
|
}
|
|
447
646
|
}
|
|
448
647
|
|
|
449
|
-
// Handle platform
|
|
450
|
-
if (hasPlatformModifiers && !hasStateModifiers) {
|
|
451
|
-
// Build style array/expression: [baseStyle, Platform.select(...)]
|
|
648
|
+
// Handle platform and/or color scheme modifiers (no state modifiers)
|
|
649
|
+
if ((hasPlatformModifiers || hasColorSchemeModifiers) && !hasStateModifiers) {
|
|
650
|
+
// Build style array/expression: [baseStyle, Platform.select(...), colorSchemeConditionals]
|
|
452
651
|
const styleExpressions: BabelTypes.Expression[] = [];
|
|
453
652
|
|
|
454
653
|
// Add base classes
|
|
@@ -463,14 +662,28 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
463
662
|
}
|
|
464
663
|
|
|
465
664
|
// Add platform modifiers as Platform.select()
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
665
|
+
if (hasPlatformModifiers) {
|
|
666
|
+
const platformSelectExpression = processPlatformModifiers(
|
|
667
|
+
platformModifiers,
|
|
668
|
+
state,
|
|
669
|
+
parseClassName,
|
|
670
|
+
generateStyleKey,
|
|
671
|
+
t,
|
|
672
|
+
);
|
|
673
|
+
styleExpressions.push(platformSelectExpression);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Add color scheme modifiers as conditionals (only if we have a valid component scope)
|
|
677
|
+
if (hasColorSchemeModifiers && componentScope) {
|
|
678
|
+
const colorSchemeConditionals = processColorSchemeModifiers(
|
|
679
|
+
colorSchemeModifiers,
|
|
680
|
+
state,
|
|
681
|
+
parseClassName,
|
|
682
|
+
generateStyleKey,
|
|
683
|
+
t,
|
|
684
|
+
);
|
|
685
|
+
styleExpressions.push(...colorSchemeConditionals);
|
|
686
|
+
}
|
|
474
687
|
|
|
475
688
|
// Generate style attribute
|
|
476
689
|
const styleExpression =
|
|
@@ -629,8 +842,25 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
629
842
|
}
|
|
630
843
|
|
|
631
844
|
try {
|
|
632
|
-
//
|
|
633
|
-
const
|
|
845
|
+
// Find component scope for color scheme modifiers
|
|
846
|
+
const componentScope = findComponentScope(path, t);
|
|
847
|
+
|
|
848
|
+
// Process dynamic expression with modifier support
|
|
849
|
+
const result = processDynamicExpression(
|
|
850
|
+
expression,
|
|
851
|
+
state,
|
|
852
|
+
parseClassName,
|
|
853
|
+
generateStyleKey,
|
|
854
|
+
splitModifierClasses,
|
|
855
|
+
processPlatformModifiers,
|
|
856
|
+
processColorSchemeModifiers,
|
|
857
|
+
componentScope,
|
|
858
|
+
isPlatformModifier as (modifier: unknown) => boolean,
|
|
859
|
+
isColorSchemeModifier as (modifier: unknown) => boolean,
|
|
860
|
+
isSchemeModifier as (modifier: unknown) => boolean,
|
|
861
|
+
expandSchemeModifier,
|
|
862
|
+
t,
|
|
863
|
+
);
|
|
634
864
|
|
|
635
865
|
if (result) {
|
|
636
866
|
state.hasClassNames = true;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for processing color scheme modifiers (dark:, light:)
|
|
3
|
+
*/
|
|
4
|
+
import type * as BabelTypes from "@babel/types";
|
|
5
|
+
import type { ParsedModifier } from "../../parser/index.js";
|
|
6
|
+
import type { StyleObject } from "../../types/core.js";
|
|
7
|
+
/**
|
|
8
|
+
* Plugin state interface (subset needed for color scheme modifier processing)
|
|
9
|
+
*/
|
|
10
|
+
export interface ColorSchemeModifierProcessingState {
|
|
11
|
+
styleRegistry: Map<string, StyleObject>;
|
|
12
|
+
customColors: Record<string, string>;
|
|
13
|
+
stylesIdentifier: string;
|
|
14
|
+
needsColorSchemeImport: boolean;
|
|
15
|
+
colorSchemeVariableName: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Process color scheme modifiers and generate conditional style expressions
|
|
19
|
+
*
|
|
20
|
+
* @param colorSchemeModifiers - Array of parsed color scheme modifiers
|
|
21
|
+
* @param state - Plugin state
|
|
22
|
+
* @param parseClassName - Function to parse class names into style objects
|
|
23
|
+
* @param generateStyleKey - Function to generate unique style keys
|
|
24
|
+
* @param t - Babel types
|
|
25
|
+
* @returns Array of AST nodes for conditional expressions
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* Input: [{ modifier: "dark", baseClass: "bg-gray-900" }, { modifier: "light", baseClass: "bg-white" }]
|
|
29
|
+
* Output: [
|
|
30
|
+
* _twColorScheme === 'dark' && styles._dark_bg_gray_900,
|
|
31
|
+
* _twColorScheme === 'light' && styles._light_bg_white
|
|
32
|
+
* ]
|
|
33
|
+
*/
|
|
34
|
+
export declare function processColorSchemeModifiers(colorSchemeModifiers: ParsedModifier[], state: ColorSchemeModifierProcessingState, parseClassName: (className: string, customColors: Record<string, string>) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes): BabelTypes.Expression[];
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for processing color scheme modifiers (dark:, light:)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type * as BabelTypes from "@babel/types";
|
|
6
|
+
import type { ColorSchemeModifierType, ParsedModifier } from "../../parser/index.js";
|
|
7
|
+
import type { StyleObject } from "../../types/core.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Plugin state interface (subset needed for color scheme modifier processing)
|
|
11
|
+
*/
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
13
|
+
export interface ColorSchemeModifierProcessingState {
|
|
14
|
+
styleRegistry: Map<string, StyleObject>;
|
|
15
|
+
customColors: Record<string, string>;
|
|
16
|
+
stylesIdentifier: string;
|
|
17
|
+
needsColorSchemeImport: boolean;
|
|
18
|
+
colorSchemeVariableName: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Process color scheme modifiers and generate conditional style expressions
|
|
23
|
+
*
|
|
24
|
+
* @param colorSchemeModifiers - Array of parsed color scheme modifiers
|
|
25
|
+
* @param state - Plugin state
|
|
26
|
+
* @param parseClassName - Function to parse class names into style objects
|
|
27
|
+
* @param generateStyleKey - Function to generate unique style keys
|
|
28
|
+
* @param t - Babel types
|
|
29
|
+
* @returns Array of AST nodes for conditional expressions
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* Input: [{ modifier: "dark", baseClass: "bg-gray-900" }, { modifier: "light", baseClass: "bg-white" }]
|
|
33
|
+
* Output: [
|
|
34
|
+
* _twColorScheme === 'dark' && styles._dark_bg_gray_900,
|
|
35
|
+
* _twColorScheme === 'light' && styles._light_bg_white
|
|
36
|
+
* ]
|
|
37
|
+
*/
|
|
38
|
+
export function processColorSchemeModifiers(
|
|
39
|
+
colorSchemeModifiers: ParsedModifier[],
|
|
40
|
+
state: ColorSchemeModifierProcessingState,
|
|
41
|
+
parseClassName: (className: string, customColors: Record<string, string>) => StyleObject,
|
|
42
|
+
generateStyleKey: (className: string) => string,
|
|
43
|
+
t: typeof BabelTypes,
|
|
44
|
+
): BabelTypes.Expression[] {
|
|
45
|
+
// Mark that we need useColorScheme import and hook injection
|
|
46
|
+
state.needsColorSchemeImport = true;
|
|
47
|
+
|
|
48
|
+
// Group modifiers by color scheme (dark, light)
|
|
49
|
+
const modifiersByScheme = new Map<ColorSchemeModifierType, ParsedModifier[]>();
|
|
50
|
+
|
|
51
|
+
for (const mod of colorSchemeModifiers) {
|
|
52
|
+
const scheme = mod.modifier as ColorSchemeModifierType;
|
|
53
|
+
if (!modifiersByScheme.has(scheme)) {
|
|
54
|
+
modifiersByScheme.set(scheme, []);
|
|
55
|
+
}
|
|
56
|
+
const schemeGroup = modifiersByScheme.get(scheme);
|
|
57
|
+
if (schemeGroup) {
|
|
58
|
+
schemeGroup.push(mod);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Build conditional expressions for each color scheme
|
|
63
|
+
const conditionalExpressions: BabelTypes.Expression[] = [];
|
|
64
|
+
|
|
65
|
+
for (const [scheme, modifiers] of modifiersByScheme) {
|
|
66
|
+
// Parse all classes for this color scheme together
|
|
67
|
+
const classNames = modifiers.map((m) => m.baseClass).join(" ");
|
|
68
|
+
const styleObject = parseClassName(classNames, state.customColors);
|
|
69
|
+
const styleKey = generateStyleKey(`${scheme}_${classNames}`);
|
|
70
|
+
|
|
71
|
+
// Register style in the registry
|
|
72
|
+
state.styleRegistry.set(styleKey, styleObject);
|
|
73
|
+
|
|
74
|
+
// Create conditional: _twColorScheme === 'dark' && styles._dark_bg_gray_900
|
|
75
|
+
const colorSchemeCheck = t.binaryExpression(
|
|
76
|
+
"===",
|
|
77
|
+
t.identifier(state.colorSchemeVariableName),
|
|
78
|
+
t.stringLiteral(scheme),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const styleReference = t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(styleKey));
|
|
82
|
+
|
|
83
|
+
const conditionalExpression = t.logicalExpression("&&", colorSchemeCheck, styleReference);
|
|
84
|
+
|
|
85
|
+
conditionalExpressions.push(conditionalExpression);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return conditionalExpressions;
|
|
89
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utility functions for processing dynamic className expressions
|
|
3
3
|
*/
|
|
4
|
+
import type { NodePath } from "@babel/core";
|
|
4
5
|
import type * as BabelTypes from "@babel/types";
|
|
6
|
+
import type { ParsedModifier } from "../../parser/index.js";
|
|
7
|
+
import type { SchemeModifierConfig } from "../../types/config.js";
|
|
5
8
|
import type { StyleObject } from "../../types/core.js";
|
|
6
9
|
/**
|
|
7
10
|
* Plugin state interface (subset needed for dynamic processing)
|
|
@@ -9,8 +12,36 @@ import type { StyleObject } from "../../types/core.js";
|
|
|
9
12
|
export interface DynamicProcessingState {
|
|
10
13
|
styleRegistry: Map<string, StyleObject>;
|
|
11
14
|
customColors: Record<string, string>;
|
|
15
|
+
schemeModifierConfig: SchemeModifierConfig;
|
|
12
16
|
stylesIdentifier: string;
|
|
17
|
+
needsPlatformImport: boolean;
|
|
18
|
+
needsColorSchemeImport: boolean;
|
|
19
|
+
colorSchemeVariableName: string;
|
|
20
|
+
functionComponentsNeedingColorScheme: Set<NodePath<BabelTypes.Function>>;
|
|
13
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Type for the splitModifierClasses function
|
|
24
|
+
*/
|
|
25
|
+
export type SplitModifierClassesFn = (className: string) => {
|
|
26
|
+
baseClasses: string[];
|
|
27
|
+
modifierClasses: ParsedModifier[];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Type for the processPlatformModifiers function
|
|
31
|
+
*/
|
|
32
|
+
export type ProcessPlatformModifiersFn = (modifiers: ParsedModifier[], state: DynamicProcessingState, parseClassName: (className: string, customColors: Record<string, string>) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes) => BabelTypes.Expression;
|
|
33
|
+
/**
|
|
34
|
+
* Type for the processColorSchemeModifiers function
|
|
35
|
+
*/
|
|
36
|
+
export type ProcessColorSchemeModifiersFn = (modifiers: ParsedModifier[], state: DynamicProcessingState, parseClassName: (className: string, customColors: Record<string, string>) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes) => BabelTypes.Expression[];
|
|
37
|
+
/**
|
|
38
|
+
* Type for modifier type guard functions
|
|
39
|
+
*/
|
|
40
|
+
export type ModifierTypeGuardFn = (modifier: unknown) => boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Type for the expandSchemeModifier function
|
|
43
|
+
*/
|
|
44
|
+
export type ExpandSchemeModifierFn = (modifier: ParsedModifier, customColors: Record<string, string>, darkSuffix: string, lightSuffix: string) => ParsedModifier[];
|
|
14
45
|
/**
|
|
15
46
|
* Result of processing a dynamic expression
|
|
16
47
|
*/
|
|
@@ -22,8 +53,8 @@ export type DynamicExpressionResult = {
|
|
|
22
53
|
* Process a dynamic className expression
|
|
23
54
|
* Extracts static strings and transforms the expression to use pre-compiled styles
|
|
24
55
|
*/
|
|
25
|
-
export declare function processDynamicExpression(expression: BabelTypes.Expression, state: DynamicProcessingState, parseClassName: (className: string, customColors: Record<string, string>) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes): {
|
|
26
|
-
expression: BabelTypes.
|
|
56
|
+
export declare function processDynamicExpression(expression: BabelTypes.Expression, state: DynamicProcessingState, parseClassName: (className: string, customColors: Record<string, string>) => StyleObject, generateStyleKey: (className: string) => string, splitModifierClasses: SplitModifierClassesFn, processPlatformModifiers: ProcessPlatformModifiersFn, processColorSchemeModifiers: ProcessColorSchemeModifiersFn, componentScope: NodePath<BabelTypes.Function> | null, isPlatformModifier: ModifierTypeGuardFn, isColorSchemeModifier: ModifierTypeGuardFn, isSchemeModifier: ModifierTypeGuardFn, expandSchemeModifier: ExpandSchemeModifierFn, t: typeof BabelTypes): {
|
|
57
|
+
expression: BabelTypes.Expression;
|
|
27
58
|
staticParts: string[] | undefined;
|
|
28
59
|
} | {
|
|
29
60
|
expression: BabelTypes.ConditionalExpression;
|