@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
|
@@ -7,7 +7,9 @@ import babelPlugin, { type PluginOptions } from "./plugin.js";
|
|
|
7
7
|
* Helper to transform code with the Babel plugin
|
|
8
8
|
*/
|
|
9
9
|
function transform(code: string, options?: PluginOptions, includeJsx = false) {
|
|
10
|
-
const presets = includeJsx
|
|
10
|
+
const presets = includeJsx
|
|
11
|
+
? ["@babel/preset-react", ["@babel/preset-typescript", { isTSX: true, allExtensions: true }]]
|
|
12
|
+
: [];
|
|
11
13
|
|
|
12
14
|
const result = transformSync(code, {
|
|
13
15
|
presets,
|
|
@@ -1022,6 +1024,278 @@ describe("Babel plugin - color scheme modifier transformation", () => {
|
|
|
1022
1024
|
});
|
|
1023
1025
|
});
|
|
1024
1026
|
|
|
1027
|
+
describe("Babel plugin - custom color scheme hook import", () => {
|
|
1028
|
+
it("should use custom import source for color scheme hook", () => {
|
|
1029
|
+
const input = `
|
|
1030
|
+
import React from 'react';
|
|
1031
|
+
import { View } from 'react-native';
|
|
1032
|
+
|
|
1033
|
+
export function Component() {
|
|
1034
|
+
return <View className="dark:bg-gray-900" />;
|
|
1035
|
+
}
|
|
1036
|
+
`;
|
|
1037
|
+
|
|
1038
|
+
const output = transform(
|
|
1039
|
+
input,
|
|
1040
|
+
{
|
|
1041
|
+
colorScheme: {
|
|
1042
|
+
importFrom: "@/hooks/useColorScheme",
|
|
1043
|
+
importName: "useColorScheme",
|
|
1044
|
+
},
|
|
1045
|
+
},
|
|
1046
|
+
true,
|
|
1047
|
+
);
|
|
1048
|
+
|
|
1049
|
+
// Should import from custom source
|
|
1050
|
+
expect(output).toContain('from "@/hooks/useColorScheme"');
|
|
1051
|
+
expect(output).not.toContain('useColorScheme } from "react-native"');
|
|
1052
|
+
|
|
1053
|
+
// Should inject hook call
|
|
1054
|
+
expect(output).toContain("_twColorScheme = useColorScheme()");
|
|
1055
|
+
|
|
1056
|
+
// Should have conditional styling
|
|
1057
|
+
expect(output).toMatch(/_twColorScheme\s*===\s*['"]dark['"]/);
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
it("should use custom hook name", () => {
|
|
1061
|
+
const input = `
|
|
1062
|
+
import React from 'react';
|
|
1063
|
+
import { View } from 'react-native';
|
|
1064
|
+
|
|
1065
|
+
export function Component() {
|
|
1066
|
+
return <View className="dark:bg-gray-900" />;
|
|
1067
|
+
}
|
|
1068
|
+
`;
|
|
1069
|
+
|
|
1070
|
+
const output = transform(
|
|
1071
|
+
input,
|
|
1072
|
+
{
|
|
1073
|
+
colorScheme: {
|
|
1074
|
+
importFrom: "@react-navigation/native",
|
|
1075
|
+
importName: "useTheme",
|
|
1076
|
+
},
|
|
1077
|
+
},
|
|
1078
|
+
true,
|
|
1079
|
+
);
|
|
1080
|
+
|
|
1081
|
+
// Should import useTheme from React Navigation
|
|
1082
|
+
expect(output).toContain('from "@react-navigation/native"');
|
|
1083
|
+
expect(output).toContain("useTheme");
|
|
1084
|
+
|
|
1085
|
+
// Should call useTheme hook
|
|
1086
|
+
expect(output).toContain("_twColorScheme = useTheme()");
|
|
1087
|
+
|
|
1088
|
+
// Should have conditional styling
|
|
1089
|
+
expect(output).toMatch(/_twColorScheme\s*===\s*['"]dark['"]/);
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
it("should merge custom hook with existing import from same source", () => {
|
|
1093
|
+
const input = `
|
|
1094
|
+
import React from 'react';
|
|
1095
|
+
import { View, Text } from 'react-native';
|
|
1096
|
+
import { useNavigation } from '@react-navigation/native';
|
|
1097
|
+
|
|
1098
|
+
export function Component() {
|
|
1099
|
+
const navigation = useNavigation();
|
|
1100
|
+
return (
|
|
1101
|
+
<View className="dark:bg-gray-900">
|
|
1102
|
+
<Text onPress={() => navigation.navigate('Home')}>Go Home</Text>
|
|
1103
|
+
</View>
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
`;
|
|
1107
|
+
|
|
1108
|
+
const output = transform(
|
|
1109
|
+
input,
|
|
1110
|
+
{
|
|
1111
|
+
colorScheme: {
|
|
1112
|
+
importFrom: "@react-navigation/native",
|
|
1113
|
+
importName: "useTheme",
|
|
1114
|
+
},
|
|
1115
|
+
},
|
|
1116
|
+
true,
|
|
1117
|
+
);
|
|
1118
|
+
|
|
1119
|
+
// Should merge with existing import (both useNavigation and useTheme in same import)
|
|
1120
|
+
expect(output).toMatch(
|
|
1121
|
+
/import\s+\{\s*useNavigation[^}]*useTheme[^}]*\}\s+from\s+['"]@react-navigation\/native['"]/,
|
|
1122
|
+
);
|
|
1123
|
+
expect(output).toContain("useNavigation()");
|
|
1124
|
+
expect(output).toContain("useTheme()");
|
|
1125
|
+
|
|
1126
|
+
// Should only have one import from that source
|
|
1127
|
+
const importCount = (output.match(/@react-navigation\/native/g) ?? []).length;
|
|
1128
|
+
expect(importCount).toBe(1);
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
it("should not duplicate custom hook if already imported", () => {
|
|
1132
|
+
const input = `
|
|
1133
|
+
import React from 'react';
|
|
1134
|
+
import { View } from 'react-native';
|
|
1135
|
+
import { useColorScheme } from '@/hooks/useColorScheme';
|
|
1136
|
+
|
|
1137
|
+
export function Component() {
|
|
1138
|
+
return <View className="dark:bg-gray-900" />;
|
|
1139
|
+
}
|
|
1140
|
+
`;
|
|
1141
|
+
|
|
1142
|
+
const output = transform(
|
|
1143
|
+
input,
|
|
1144
|
+
{
|
|
1145
|
+
colorScheme: {
|
|
1146
|
+
importFrom: "@/hooks/useColorScheme",
|
|
1147
|
+
importName: "useColorScheme",
|
|
1148
|
+
},
|
|
1149
|
+
},
|
|
1150
|
+
true,
|
|
1151
|
+
);
|
|
1152
|
+
|
|
1153
|
+
// Should not add duplicate import
|
|
1154
|
+
const importMatches = output.match(/import.*useColorScheme.*from ['"]@\/hooks\/useColorScheme['"]/g);
|
|
1155
|
+
expect(importMatches).toHaveLength(1);
|
|
1156
|
+
|
|
1157
|
+
// Should still inject hook call
|
|
1158
|
+
expect(output).toContain("_twColorScheme = useColorScheme()");
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
it("should use react-native by default when no custom config provided", () => {
|
|
1162
|
+
const input = `
|
|
1163
|
+
import React from 'react';
|
|
1164
|
+
import { View } from 'react-native';
|
|
1165
|
+
|
|
1166
|
+
export function Component() {
|
|
1167
|
+
return <View className="dark:bg-gray-900" />;
|
|
1168
|
+
}
|
|
1169
|
+
`;
|
|
1170
|
+
|
|
1171
|
+
const output = transform(input, undefined, true);
|
|
1172
|
+
|
|
1173
|
+
// Should use default react-native import (can be single or double quotes)
|
|
1174
|
+
expect(output).toMatch(/useColorScheme\s*}\s*from\s+['"]react-native['"]/);
|
|
1175
|
+
expect(output).not.toContain("@/hooks");
|
|
1176
|
+
expect(output).not.toContain("@react-navigation");
|
|
1177
|
+
|
|
1178
|
+
// Should inject hook call with default name
|
|
1179
|
+
expect(output).toContain("_twColorScheme = useColorScheme()");
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
it("should create separate import when only type-only import exists", () => {
|
|
1183
|
+
const input = `
|
|
1184
|
+
import React from 'react';
|
|
1185
|
+
import { View } from 'react-native';
|
|
1186
|
+
import type { NavigationProp } from '@react-navigation/native';
|
|
1187
|
+
|
|
1188
|
+
export function Component() {
|
|
1189
|
+
return <View className="dark:bg-gray-900" />;
|
|
1190
|
+
}
|
|
1191
|
+
`;
|
|
1192
|
+
|
|
1193
|
+
const output = transform(
|
|
1194
|
+
input,
|
|
1195
|
+
{
|
|
1196
|
+
colorScheme: {
|
|
1197
|
+
importFrom: "@react-navigation/native",
|
|
1198
|
+
importName: "useTheme",
|
|
1199
|
+
},
|
|
1200
|
+
},
|
|
1201
|
+
true,
|
|
1202
|
+
);
|
|
1203
|
+
|
|
1204
|
+
// TypeScript preset strips type-only imports, but the important thing is:
|
|
1205
|
+
// 1. useTheme hook is imported (not skipped thinking it was already imported)
|
|
1206
|
+
// 2. Hook is correctly called in the component
|
|
1207
|
+
expect(output).toMatch(/import\s+\{\s*useTheme\s*\}\s+from\s+['"]@react-navigation\/native['"]/);
|
|
1208
|
+
expect(output).toContain("_twColorScheme = useTheme()");
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
it("should use aliased identifier when hook is already imported with alias", () => {
|
|
1212
|
+
const input = `
|
|
1213
|
+
import React from 'react';
|
|
1214
|
+
import { View, Text } from 'react-native';
|
|
1215
|
+
import { useTheme as navTheme } from '@react-navigation/native';
|
|
1216
|
+
|
|
1217
|
+
export function Component() {
|
|
1218
|
+
const theme = navTheme();
|
|
1219
|
+
return (
|
|
1220
|
+
<View className="dark:bg-gray-900">
|
|
1221
|
+
<Text>{theme.dark ? 'Dark' : 'Light'}</Text>
|
|
1222
|
+
</View>
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
`;
|
|
1226
|
+
|
|
1227
|
+
const output = transform(
|
|
1228
|
+
input,
|
|
1229
|
+
{
|
|
1230
|
+
colorScheme: {
|
|
1231
|
+
importFrom: "@react-navigation/native",
|
|
1232
|
+
importName: "useTheme",
|
|
1233
|
+
},
|
|
1234
|
+
},
|
|
1235
|
+
true,
|
|
1236
|
+
);
|
|
1237
|
+
|
|
1238
|
+
// Should not add duplicate import
|
|
1239
|
+
const importMatches = output.match(
|
|
1240
|
+
/import\s+\{[^}]*useTheme[^}]*\}\s+from\s+['"]@react-navigation\/native['"]/g,
|
|
1241
|
+
);
|
|
1242
|
+
expect(importMatches).toHaveLength(1);
|
|
1243
|
+
|
|
1244
|
+
// Should still have the aliased import
|
|
1245
|
+
expect(output).toMatch(/useTheme\s+as\s+navTheme/);
|
|
1246
|
+
|
|
1247
|
+
// Should call the aliased name (navTheme), not the export name (useTheme)
|
|
1248
|
+
// Both the user's code and our injected hook should use navTheme
|
|
1249
|
+
expect(output).toContain("_twColorScheme = navTheme()");
|
|
1250
|
+
expect(output).not.toContain("_twColorScheme = useTheme()");
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
it("should handle both type-only and aliased imports together", () => {
|
|
1254
|
+
const input = `
|
|
1255
|
+
import React from 'react';
|
|
1256
|
+
import { View, Text } from 'react-native';
|
|
1257
|
+
import type { Theme } from '@react-navigation/native';
|
|
1258
|
+
import { useTheme as getNavTheme } from '@react-navigation/native';
|
|
1259
|
+
|
|
1260
|
+
export function Component() {
|
|
1261
|
+
const theme = getNavTheme();
|
|
1262
|
+
return (
|
|
1263
|
+
<View className="dark:bg-gray-900">
|
|
1264
|
+
<Text>{theme.dark ? 'Dark Mode' : 'Light Mode'}</Text>
|
|
1265
|
+
</View>
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
`;
|
|
1269
|
+
|
|
1270
|
+
const output = transform(
|
|
1271
|
+
input,
|
|
1272
|
+
{
|
|
1273
|
+
colorScheme: {
|
|
1274
|
+
importFrom: "@react-navigation/native",
|
|
1275
|
+
importName: "useTheme",
|
|
1276
|
+
},
|
|
1277
|
+
},
|
|
1278
|
+
true,
|
|
1279
|
+
);
|
|
1280
|
+
|
|
1281
|
+
// TypeScript preset strips type-only imports
|
|
1282
|
+
// The important thing is: should not add duplicate import, and should use aliased name
|
|
1283
|
+
expect(output).toMatch(
|
|
1284
|
+
/import\s+\{[^}]*useTheme\s+as\s+getNavTheme[^}]*\}\s+from\s+['"]@react-navigation\/native['"]/,
|
|
1285
|
+
);
|
|
1286
|
+
|
|
1287
|
+
// Should not add duplicate import - useTheme should only appear in the aliased import
|
|
1288
|
+
const useThemeImports = output.match(
|
|
1289
|
+
/import\s+\{[^}]*useTheme[^}]*\}\s+from\s+['"]@react-navigation\/native['"]/g,
|
|
1290
|
+
);
|
|
1291
|
+
expect(useThemeImports).toHaveLength(1);
|
|
1292
|
+
|
|
1293
|
+
// Should call the aliased name for both user code and our injected hook
|
|
1294
|
+
expect(output).toContain("_twColorScheme = getNavTheme()");
|
|
1295
|
+
expect(output).not.toContain("_twColorScheme = useTheme()");
|
|
1296
|
+
});
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1025
1299
|
describe("Babel plugin - import injection", () => {
|
|
1026
1300
|
it("should not add StyleSheet import to files without className usage", () => {
|
|
1027
1301
|
const input = `
|
package/dist/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
|
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* Utility functions for processing color scheme modifiers (dark:, light:)
|
|
3
3
|
*/
|
|
4
4
|
import type * as BabelTypes from "@babel/types";
|
|
5
|
-
import type { ParsedModifier } from "../../parser/index.js";
|
|
5
|
+
import type { CustomTheme, ParsedModifier } from "../../parser/index.js";
|
|
6
6
|
import type { StyleObject } from "../../types/core.js";
|
|
7
7
|
/**
|
|
8
8
|
* Plugin state interface (subset needed for color scheme modifier processing)
|
|
9
9
|
*/
|
|
10
10
|
export interface ColorSchemeModifierProcessingState {
|
|
11
11
|
styleRegistry: Map<string, StyleObject>;
|
|
12
|
-
|
|
12
|
+
customTheme: CustomTheme;
|
|
13
13
|
stylesIdentifier: string;
|
|
14
14
|
needsColorSchemeImport: boolean;
|
|
15
15
|
colorSchemeVariableName: string;
|
|
@@ -31,4 +31,4 @@ export interface ColorSchemeModifierProcessingState {
|
|
|
31
31
|
* _twColorScheme === 'light' && styles._light_bg_white
|
|
32
32
|
* ]
|
|
33
33
|
*/
|
|
34
|
-
export declare function processColorSchemeModifiers(colorSchemeModifiers: ParsedModifier[], state: ColorSchemeModifierProcessingState, parseClassName: (className: string,
|
|
34
|
+
export declare function processColorSchemeModifiers(colorSchemeModifiers: ParsedModifier[], state: ColorSchemeModifierProcessingState, parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes): BabelTypes.Expression[];
|
|
@@ -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
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { NodePath } from "@babel/core";
|
|
5
5
|
import type * as BabelTypes from "@babel/types";
|
|
6
|
-
import type { ParsedModifier } from "../../parser/index.js";
|
|
6
|
+
import type { CustomTheme, ParsedModifier } from "../../parser/index.js";
|
|
7
7
|
import type { SchemeModifierConfig } from "../../types/config.js";
|
|
8
8
|
import type { StyleObject } from "../../types/core.js";
|
|
9
9
|
/**
|
|
@@ -11,7 +11,7 @@ import type { StyleObject } from "../../types/core.js";
|
|
|
11
11
|
*/
|
|
12
12
|
export interface DynamicProcessingState {
|
|
13
13
|
styleRegistry: Map<string, StyleObject>;
|
|
14
|
-
|
|
14
|
+
customTheme: CustomTheme;
|
|
15
15
|
schemeModifierConfig: SchemeModifierConfig;
|
|
16
16
|
stylesIdentifier: string;
|
|
17
17
|
needsPlatformImport: boolean;
|
|
@@ -29,11 +29,11 @@ export type SplitModifierClassesFn = (className: string) => {
|
|
|
29
29
|
/**
|
|
30
30
|
* Type for the processPlatformModifiers function
|
|
31
31
|
*/
|
|
32
|
-
export type ProcessPlatformModifiersFn = (modifiers: ParsedModifier[], state: DynamicProcessingState, parseClassName: (className: string,
|
|
32
|
+
export type ProcessPlatformModifiersFn = (modifiers: ParsedModifier[], state: DynamicProcessingState, parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes) => BabelTypes.Expression;
|
|
33
33
|
/**
|
|
34
34
|
* Type for the processColorSchemeModifiers function
|
|
35
35
|
*/
|
|
36
|
-
export type ProcessColorSchemeModifiersFn = (modifiers: ParsedModifier[], state: DynamicProcessingState, parseClassName: (className: string,
|
|
36
|
+
export type ProcessColorSchemeModifiersFn = (modifiers: ParsedModifier[], state: DynamicProcessingState, parseClassName: (className: string, customTheme?: CustomTheme) => StyleObject, generateStyleKey: (className: string) => string, t: typeof BabelTypes) => BabelTypes.Expression[];
|
|
37
37
|
/**
|
|
38
38
|
* Type for modifier type guard functions
|
|
39
39
|
*/
|
|
@@ -53,7 +53,7 @@ export type DynamicExpressionResult = {
|
|
|
53
53
|
* Process a dynamic className expression
|
|
54
54
|
* Extracts static strings and transforms the expression to use pre-compiled styles
|
|
55
55
|
*/
|
|
56
|
-
export declare function processDynamicExpression(expression: BabelTypes.Expression, state: DynamicProcessingState, parseClassName: (className: string,
|
|
56
|
+
export declare function processDynamicExpression(expression: BabelTypes.Expression, state: DynamicProcessingState, parseClassName: (className: string, customTheme?: CustomTheme) => 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
57
|
expression: BabelTypes.Expression;
|
|
58
58
|
staticParts: string[] | undefined;
|
|
59
59
|
} | {
|