@mgcrea/react-native-tailwind 0.11.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.
@@ -89,6 +89,42 @@ export type PluginOptions = {
89
89
  darkSuffix?: string;
90
90
  lightSuffix?: string;
91
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
+ };
92
128
  };
93
129
 
94
130
  type PluginState = PluginPass & {
@@ -100,6 +136,9 @@ type PluginState = PluginPass & {
100
136
  hasColorSchemeImport: boolean;
101
137
  needsColorSchemeImport: boolean;
102
138
  colorSchemeVariableName: string;
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
103
142
  customTheme: CustomTheme;
104
143
  schemeModifierConfig: SchemeModifierConfig;
105
144
  supportedAttributes: Set<string>;
@@ -210,6 +249,10 @@ export default function reactNativeTailwindBabelPlugin(
210
249
  lightSuffix: options?.schemeModifier?.lightSuffix ?? "-light",
211
250
  };
212
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
+
213
256
  return {
214
257
  name: "react-native-tailwind",
215
258
 
@@ -225,6 +268,8 @@ export default function reactNativeTailwindBabelPlugin(
225
268
  state.hasColorSchemeImport = false;
226
269
  state.needsColorSchemeImport = false;
227
270
  state.colorSchemeVariableName = "_twColorScheme";
271
+ state.colorSchemeImportSource = colorSchemeImportSource;
272
+ state.colorSchemeHookName = colorSchemeHookName;
228
273
  state.supportedAttributes = exactMatches;
229
274
  state.attributePatterns = patterns;
230
275
  state.stylesIdentifier = stylesIdentifier;
@@ -260,15 +305,21 @@ export default function reactNativeTailwindBabelPlugin(
260
305
  addPlatformImport(path, t);
261
306
  }
262
307
 
263
- // Add useColorScheme import if color scheme modifiers were used and not already present
308
+ // Add color scheme hook import if color scheme modifiers were used and not already present
264
309
  if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
265
- addColorSchemeImport(path, t);
310
+ addColorSchemeImport(path, state.colorSchemeImportSource, state.colorSchemeHookName, t);
266
311
  }
267
312
 
268
- // Inject useColorScheme hook in function components that need it
313
+ // Inject color scheme hook in function components that need it
269
314
  if (state.needsColorSchemeImport) {
270
315
  for (const functionPath of state.functionComponentsNeedingColorScheme) {
271
- injectColorSchemeHook(functionPath, state.colorSchemeVariableName, t);
316
+ injectColorSchemeHook(
317
+ functionPath,
318
+ state.colorSchemeVariableName,
319
+ state.colorSchemeHookName,
320
+ state.colorSchemeLocalIdentifier,
321
+ t,
322
+ );
272
323
  }
273
324
  }
274
325
 
@@ -300,13 +351,6 @@ export default function reactNativeTailwindBabelPlugin(
300
351
  return false;
301
352
  });
302
353
 
303
- const hasUseColorScheme = specifiers.some((spec) => {
304
- if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
305
- return spec.imported.name === "useColorScheme";
306
- }
307
- return false;
308
- });
309
-
310
354
  // Only track if imports exist - don't mutate yet
311
355
  // Actual import injection happens in Program.exit only if needed
312
356
  if (hasStyleSheet) {
@@ -317,14 +361,28 @@ export default function reactNativeTailwindBabelPlugin(
317
361
  state.hasPlatformImport = true;
318
362
  }
319
363
 
320
- if (hasUseColorScheme) {
321
- state.hasColorSchemeImport = true;
322
- }
323
-
324
364
  // Store reference to the react-native import for later modification if needed
325
365
  state.reactNativeImportPath = path;
326
366
  }
327
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
+
328
386
  // Track tw/twStyle imports from main package (for compile-time transformation)
329
387
  if (node.source.value === "@mgcrea/react-native-tailwind") {
330
388
  const specifiers = node.specifiers;
@@ -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(path: NodePath<BabelTypes.Program>, t: typeof BabelTypes): void {
71
- // Check if there's already a react-native import
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 reactNativeImport: BabelTypes.ImportDeclaration | null = null;
78
+ let existingValueImport: BabelTypes.ImportDeclaration | null = null;
74
79
 
75
80
  for (const statement of body) {
76
- if (t.isImportDeclaration(statement) && statement.source.value === "react-native") {
77
- reactNativeImport = statement;
78
- break;
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
- if (reactNativeImport) {
83
- // Check if useColorScheme is already imported
84
- const hasUseColorScheme = reactNativeImport.specifiers.some(
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 (!hasUseColorScheme) {
92
- // Add useColorScheme to existing react-native import
93
- reactNativeImport.specifiers.push(
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
- // Create new react-native import with useColorScheme
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("useColorScheme"), t.identifier("useColorScheme"))],
101
- t.stringLiteral("react-native"),
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 useColorScheme hook call at the top of a function component
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
- // Create: const _twColorScheme = useColorScheme();
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("useColorScheme"), []),
173
+ t.callExpression(t.identifier(identifierToCall), []),
159
174
  ),
160
175
  ]);
161
176