@mgcrea/react-native-tailwind 0.9.0 → 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 +575 -60
- package/dist/babel/plugin.d.ts +23 -1
- package/dist/babel/plugin.test.ts +417 -0
- package/dist/babel/plugin.ts +265 -32
- 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 +14 -1
- package/dist/babel/utils/styleInjection.ts +125 -7
- 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/aspectRatio.js +1 -1
- package/dist/parser/aspectRatio.test.js +1 -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/parser/spacing.d.ts +1 -1
- package/dist/parser/spacing.js +1 -1
- package/dist/parser/spacing.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/runtime.test.js +1 -1
- package/dist/types/config.d.ts +7 -0
- package/dist/types/config.js +0 -0
- package/package.json +4 -4
- package/src/babel/config-loader.test.ts +152 -0
- package/src/babel/plugin.test.ts +417 -0
- package/src/babel/plugin.ts +265 -32
- package/src/babel/utils/colorSchemeModifierProcessing.ts +89 -0
- package/src/babel/utils/dynamicProcessing.ts +352 -33
- package/src/babel/utils/styleInjection.ts +125 -7
- package/src/babel/utils/styleTransforms.test.ts +56 -0
- package/src/babel/utils/twProcessing.ts +22 -1
- package/src/parser/aspectRatio.test.ts +25 -2
- package/src/parser/aspectRatio.ts +3 -3
- package/src/parser/index.ts +12 -1
- package/src/parser/modifiers.test.ts +151 -1
- package/src/parser/modifiers.ts +139 -4
- package/src/parser/spacing.test.ts +63 -0
- package/src/parser/spacing.ts +10 -6
- package/src/runtime.test.ts +27 -0
- package/src/runtime.ts +2 -1
- package/src/types/config.ts +7 -0
- package/dist/babel/index.test.ts +0 -481
- package/dist/config/palettes.d.ts +0 -302
- package/dist/config/palettes.js +0 -1
- package/dist/parser/__snapshots__/aspectRatio.test.js.snap +0 -9
- package/dist/parser/__snapshots__/borders.test.js.snap +0 -23
- package/dist/parser/__snapshots__/colors.test.js.snap +0 -251
- package/dist/parser/__snapshots__/shadows.test.js.snap +0 -76
- package/dist/parser/__snapshots__/sizing.test.js.snap +0 -61
- package/dist/parser/__snapshots__/spacing.test.js.snap +0 -40
- package/dist/parser/__snapshots__/transforms.test.js.snap +0 -58
- package/dist/parser/__snapshots__/typography.test.js.snap +0 -30
- package/dist/parser/aspectRatio.test.d.ts +0 -1
- package/dist/parser/borders.test.d.ts +0 -1
- package/dist/parser/colors.test.d.ts +0 -1
- package/dist/parser/layout.test.d.ts +0 -1
- package/dist/parser/modifiers.test.d.ts +0 -1
- package/dist/parser/shadows.test.d.ts +0 -1
- package/dist/parser/sizing.test.d.ts +0 -1
- package/dist/parser/spacing.test.d.ts +0 -1
- package/dist/parser/typography.test.d.ts +0 -1
- package/dist/types.d.ts +0 -42
- package/dist/types.js +0 -1
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,18 +96,104 @@ 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;
|
|
76
107
|
// Track tw/twStyle imports from main package
|
|
77
108
|
twImportNames: Set<string>; // e.g., ['tw', 'twStyle'] or ['tw as customTw']
|
|
78
109
|
hasTwImport: boolean;
|
|
110
|
+
// Track react-native import path for conditional StyleSheet/Platform injection
|
|
111
|
+
reactNativeImportPath?: NodePath<BabelTypes.ImportDeclaration>;
|
|
112
|
+
// Track function components that need colorScheme hook injection
|
|
113
|
+
functionComponentsNeedingColorScheme: Set<NodePath<BabelTypes.Function>>;
|
|
79
114
|
};
|
|
80
115
|
|
|
81
116
|
// Default identifier for the generated StyleSheet constant
|
|
82
117
|
const DEFAULT_STYLES_IDENTIFIER = "_twStyles";
|
|
83
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
|
+
|
|
84
197
|
export default function reactNativeTailwindBabelPlugin(
|
|
85
198
|
{ types: t }: { types: typeof BabelTypes },
|
|
86
199
|
options?: PluginOptions,
|
|
@@ -90,6 +203,12 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
90
203
|
const { exactMatches, patterns } = buildAttributeMatchers(attributes);
|
|
91
204
|
const stylesIdentifier = options?.stylesIdentifier ?? DEFAULT_STYLES_IDENTIFIER;
|
|
92
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
|
+
|
|
93
212
|
return {
|
|
94
213
|
name: "react-native-tailwind",
|
|
95
214
|
|
|
@@ -102,14 +221,21 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
102
221
|
state.hasStyleSheetImport = false;
|
|
103
222
|
state.hasPlatformImport = false;
|
|
104
223
|
state.needsPlatformImport = false;
|
|
224
|
+
state.hasColorSchemeImport = false;
|
|
225
|
+
state.needsColorSchemeImport = false;
|
|
226
|
+
state.colorSchemeVariableName = "_twColorScheme";
|
|
105
227
|
state.supportedAttributes = exactMatches;
|
|
106
228
|
state.attributePatterns = patterns;
|
|
107
229
|
state.stylesIdentifier = stylesIdentifier;
|
|
108
230
|
state.twImportNames = new Set();
|
|
109
231
|
state.hasTwImport = false;
|
|
232
|
+
state.functionComponentsNeedingColorScheme = new Set();
|
|
110
233
|
|
|
111
234
|
// Load custom colors from tailwind.config.*
|
|
112
235
|
state.customColors = extractCustomColors(state.file.opts.filename ?? "");
|
|
236
|
+
|
|
237
|
+
// Use scheme modifier config from plugin options
|
|
238
|
+
state.schemeModifierConfig = schemeModifierConfig;
|
|
113
239
|
},
|
|
114
240
|
|
|
115
241
|
exit(path, state) {
|
|
@@ -133,6 +259,18 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
133
259
|
addPlatformImport(path, t);
|
|
134
260
|
}
|
|
135
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
|
+
|
|
136
274
|
// Generate and inject StyleSheet.create at the beginning of the file (after imports)
|
|
137
275
|
// This ensures _twStyles is defined before any code that references it
|
|
138
276
|
injectStylesAtTop(path, state.styleRegistry, state.stylesIdentifier, t);
|
|
@@ -161,17 +299,29 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
161
299
|
return false;
|
|
162
300
|
});
|
|
163
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
|
+
|
|
309
|
+
// Only track if imports exist - don't mutate yet
|
|
310
|
+
// Actual import injection happens in Program.exit only if needed
|
|
164
311
|
if (hasStyleSheet) {
|
|
165
312
|
state.hasStyleSheetImport = true;
|
|
166
|
-
} else {
|
|
167
|
-
// Add StyleSheet to existing import
|
|
168
|
-
node.specifiers.push(t.importSpecifier(t.identifier("StyleSheet"), t.identifier("StyleSheet")));
|
|
169
|
-
state.hasStyleSheetImport = true;
|
|
170
313
|
}
|
|
171
314
|
|
|
172
315
|
if (hasPlatform) {
|
|
173
316
|
state.hasPlatformImport = true;
|
|
174
317
|
}
|
|
318
|
+
|
|
319
|
+
if (hasUseColorScheme) {
|
|
320
|
+
state.hasColorSchemeImport = true;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Store reference to the react-native import for later modification if needed
|
|
324
|
+
state.reactNativeImportPath = path;
|
|
175
325
|
}
|
|
176
326
|
|
|
177
327
|
// Track tw/twStyle imports from main package (for compile-time transformation)
|
|
@@ -320,12 +470,31 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
320
470
|
|
|
321
471
|
state.hasClassNames = true;
|
|
322
472
|
|
|
323
|
-
// Check if className contains modifiers (active:, hover:, focus:, placeholder:, ios:, android:, web:)
|
|
324
|
-
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
|
+
}
|
|
325
493
|
|
|
326
494
|
// Separate modifiers by type
|
|
327
495
|
const placeholderModifiers = modifierClasses.filter((m) => m.modifier === "placeholder");
|
|
328
496
|
const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
|
|
497
|
+
const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
|
|
329
498
|
const stateModifiers = modifierClasses.filter(
|
|
330
499
|
(m) => isStateModifier(m.modifier) && m.modifier !== "placeholder",
|
|
331
500
|
);
|
|
@@ -356,18 +525,37 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
356
525
|
|
|
357
526
|
// Handle combination of modifiers
|
|
358
527
|
const hasPlatformModifiers = platformModifiers.length > 0;
|
|
528
|
+
const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
|
|
359
529
|
const hasStateModifiers = stateModifiers.length > 0;
|
|
360
530
|
const hasBaseClasses = baseClasses.length > 0;
|
|
361
531
|
|
|
362
|
-
// If we have
|
|
363
|
-
|
|
364
|
-
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)) {
|
|
365
553
|
// Get the JSX opening element for component support checking
|
|
366
554
|
const jsxOpeningElement = path.parent;
|
|
367
555
|
const componentSupport = getComponentModifierSupport(jsxOpeningElement, t);
|
|
368
556
|
|
|
369
557
|
if (componentSupport) {
|
|
370
|
-
// Build style array: [baseStyle, Platform.select(...), stateConditionals]
|
|
558
|
+
// Build style array: [baseStyle, Platform.select(...), colorSchemeConditionals, stateConditionals]
|
|
371
559
|
const styleArrayElements: BabelTypes.Expression[] = [];
|
|
372
560
|
|
|
373
561
|
// Add base classes
|
|
@@ -382,14 +570,28 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
382
570
|
}
|
|
383
571
|
|
|
384
572
|
// Add platform modifiers as Platform.select()
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
+
}
|
|
393
595
|
|
|
394
596
|
// Add state modifiers as conditionals
|
|
395
597
|
// Group by modifier type
|
|
@@ -443,9 +645,9 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
443
645
|
}
|
|
444
646
|
}
|
|
445
647
|
|
|
446
|
-
// Handle platform
|
|
447
|
-
if (hasPlatformModifiers && !hasStateModifiers) {
|
|
448
|
-
// 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]
|
|
449
651
|
const styleExpressions: BabelTypes.Expression[] = [];
|
|
450
652
|
|
|
451
653
|
// Add base classes
|
|
@@ -460,14 +662,28 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
460
662
|
}
|
|
461
663
|
|
|
462
664
|
// Add platform modifiers as Platform.select()
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
+
}
|
|
471
687
|
|
|
472
688
|
// Generate style attribute
|
|
473
689
|
const styleExpression =
|
|
@@ -626,8 +842,25 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
626
842
|
}
|
|
627
843
|
|
|
628
844
|
try {
|
|
629
|
-
//
|
|
630
|
-
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
|
+
);
|
|
631
864
|
|
|
632
865
|
if (result) {
|
|
633
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;
|