@mgcrea/react-native-tailwind 0.11.0 → 0.12.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 +129 -0
- package/dist/babel/index.cjs +229 -46
- package/dist/babel/plugin.d.ts +37 -0
- package/dist/babel/plugin.test.ts +773 -1
- package/dist/babel/plugin.ts +127 -30
- package/dist/babel/utils/styleInjection.d.ts +5 -3
- package/dist/babel/utils/styleInjection.ts +38 -23
- package/dist/babel/utils/twProcessing.d.ts +8 -1
- package/dist/babel/utils/twProcessing.ts +212 -4
- 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 +2 -2
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +2 -2
- package/dist/runtime.test.js +1 -1
- package/dist/types/runtime.d.ts +8 -1
- package/package.json +1 -1
- package/src/babel/plugin.test.ts +773 -1
- package/src/babel/plugin.ts +127 -30
- package/src/babel/utils/styleInjection.ts +38 -23
- package/src/babel/utils/twProcessing.ts +212 -4
- package/src/parser/spacing.test.ts +62 -0
- package/src/parser/spacing.ts +7 -7
- package/src/runtime.test.ts +4 -1
- package/src/types/runtime.ts +8 -1
package/README.md
CHANGED
|
@@ -1887,6 +1887,135 @@ const styles = StyleSheet.create({
|
|
|
1887
1887
|
- Choose a name that won't conflict with existing variables in your files
|
|
1888
1888
|
- The same identifier is used across all files in your project
|
|
1889
1889
|
|
|
1890
|
+
### Custom Color Scheme Hook
|
|
1891
|
+
|
|
1892
|
+
By default, the plugin uses React Native's built-in `useColorScheme()` hook for `dark:` and `light:` modifiers. You can configure it to use a custom color scheme hook from theme providers like React Navigation, Expo, or your own implementation.
|
|
1893
|
+
|
|
1894
|
+
**Configuration:**
|
|
1895
|
+
|
|
1896
|
+
```javascript
|
|
1897
|
+
// babel.config.js
|
|
1898
|
+
module.exports = {
|
|
1899
|
+
plugins: [
|
|
1900
|
+
[
|
|
1901
|
+
"@mgcrea/react-native-tailwind/babel",
|
|
1902
|
+
{
|
|
1903
|
+
colorScheme: {
|
|
1904
|
+
importFrom: "@/hooks/useColorScheme", // Module to import from
|
|
1905
|
+
importName: "useColorScheme", // Hook name to import
|
|
1906
|
+
},
|
|
1907
|
+
},
|
|
1908
|
+
],
|
|
1909
|
+
],
|
|
1910
|
+
};
|
|
1911
|
+
```
|
|
1912
|
+
|
|
1913
|
+
**Use Cases:**
|
|
1914
|
+
|
|
1915
|
+
#### 1. Custom Theme Provider
|
|
1916
|
+
|
|
1917
|
+
Override system color scheme with user preferences from a store:
|
|
1918
|
+
|
|
1919
|
+
```typescript
|
|
1920
|
+
// src/hooks/useColorScheme.ts
|
|
1921
|
+
import { useColorScheme as useSystemColorScheme } from "react-native";
|
|
1922
|
+
import { profileStore } from "@/stores/profileStore";
|
|
1923
|
+
import { type ColorSchemeName } from "react-native";
|
|
1924
|
+
|
|
1925
|
+
export const useColorScheme = (): ColorSchemeName => {
|
|
1926
|
+
const systemColorScheme = useSystemColorScheme();
|
|
1927
|
+
const userTheme = profileStore.theme; // 'dark' | 'light' | 'auto'
|
|
1928
|
+
|
|
1929
|
+
// Return user preference, or fall back to system if set to 'auto'
|
|
1930
|
+
return userTheme === 'auto' ? systemColorScheme : userTheme;
|
|
1931
|
+
};
|
|
1932
|
+
```
|
|
1933
|
+
|
|
1934
|
+
```javascript
|
|
1935
|
+
// babel.config.js
|
|
1936
|
+
{
|
|
1937
|
+
colorScheme: {
|
|
1938
|
+
importFrom: "@/hooks/useColorScheme",
|
|
1939
|
+
importName: "useColorScheme"
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
```
|
|
1943
|
+
|
|
1944
|
+
#### 2. React Navigation Theme
|
|
1945
|
+
|
|
1946
|
+
Integrate with React Navigation's theme system:
|
|
1947
|
+
|
|
1948
|
+
```typescript
|
|
1949
|
+
// Wrap React Navigation's useTheme to return ColorSchemeName
|
|
1950
|
+
import { useTheme as useNavTheme } from "@react-navigation/native";
|
|
1951
|
+
import { type ColorSchemeName } from "react-native";
|
|
1952
|
+
|
|
1953
|
+
export const useColorScheme = (): ColorSchemeName => {
|
|
1954
|
+
const { dark } = useNavTheme();
|
|
1955
|
+
return dark ? "dark" : "light";
|
|
1956
|
+
};
|
|
1957
|
+
```
|
|
1958
|
+
|
|
1959
|
+
#### 3. Expo Router Theme
|
|
1960
|
+
|
|
1961
|
+
Use Expo Router's theme hook:
|
|
1962
|
+
|
|
1963
|
+
```javascript
|
|
1964
|
+
// babel.config.js
|
|
1965
|
+
{
|
|
1966
|
+
colorScheme: {
|
|
1967
|
+
importFrom: "expo-router",
|
|
1968
|
+
importName: "useColorScheme"
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
```
|
|
1972
|
+
|
|
1973
|
+
#### 4. Testing
|
|
1974
|
+
|
|
1975
|
+
Mock color scheme for tests:
|
|
1976
|
+
|
|
1977
|
+
```typescript
|
|
1978
|
+
// test/mocks/useColorScheme.ts
|
|
1979
|
+
export const useColorScheme = () => "light"; // Or "dark" for dark mode tests
|
|
1980
|
+
```
|
|
1981
|
+
|
|
1982
|
+
```javascript
|
|
1983
|
+
// babel.config.js (test environment)
|
|
1984
|
+
{
|
|
1985
|
+
colorScheme: {
|
|
1986
|
+
importFrom: "@/test/mocks/useColorScheme",
|
|
1987
|
+
importName: "useColorScheme"
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
```
|
|
1991
|
+
|
|
1992
|
+
#### How it works
|
|
1993
|
+
|
|
1994
|
+
When you use `dark:` or `light:` modifiers:
|
|
1995
|
+
|
|
1996
|
+
```tsx
|
|
1997
|
+
<View className="bg-white dark:bg-gray-900" />
|
|
1998
|
+
```
|
|
1999
|
+
|
|
2000
|
+
The plugin will:
|
|
2001
|
+
|
|
2002
|
+
1. Import your custom hook: `import { useColorScheme } from "@/hooks/useColorScheme"`
|
|
2003
|
+
2. Inject it in components: `const _twColorScheme = useColorScheme();`
|
|
2004
|
+
3. Generate conditionals: `_twColorScheme === "dark" && styles._dark_bg_gray_900`
|
|
2005
|
+
|
|
2006
|
+
#### Default behavior (no configuration)
|
|
2007
|
+
|
|
2008
|
+
Without custom configuration, the plugin uses React Native's built-in hook:
|
|
2009
|
+
|
|
2010
|
+
- Import: `import { useColorScheme } from "react-native"`
|
|
2011
|
+
- This works out of the box for basic system color scheme detection
|
|
2012
|
+
|
|
2013
|
+
#### Requirements
|
|
2014
|
+
|
|
2015
|
+
- Your custom hook must return `ColorSchemeName` (type from React Native: `"light" | "dark" | null | undefined`)
|
|
2016
|
+
- The hook must be compatible with React's rules of hooks (can only be called in function components)
|
|
2017
|
+
- Import merging works automatically if you already import from the same source
|
|
2018
|
+
|
|
1890
2019
|
### Arbitrary Values
|
|
1891
2020
|
|
|
1892
2021
|
Use arbitrary values for custom sizes, spacing, and borders not in the preset scales:
|
package/dist/babel/index.cjs
CHANGED
|
@@ -1194,14 +1194,14 @@ var SPACING_SCALE = {
|
|
|
1194
1194
|
96: 384
|
|
1195
1195
|
};
|
|
1196
1196
|
function parseArbitrarySpacing(value) {
|
|
1197
|
-
const pxMatch = value.match(/^\[(
|
|
1197
|
+
const pxMatch = value.match(/^\[(-?\d+(?:\.\d+)?)(?:px)?\]$/);
|
|
1198
1198
|
if (pxMatch) {
|
|
1199
|
-
return
|
|
1199
|
+
return parseFloat(pxMatch[1]);
|
|
1200
1200
|
}
|
|
1201
1201
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
1202
1202
|
if (process.env.NODE_ENV !== "production") {
|
|
1203
1203
|
console.warn(
|
|
1204
|
-
`[react-native-tailwind] Unsupported arbitrary spacing value: ${value}. Only px values are supported (e.g., [16px]
|
|
1204
|
+
`[react-native-tailwind] Unsupported arbitrary spacing value: ${value}. Only px values are supported (e.g., [16px], [16], [4.5px], [4.5]).`
|
|
1205
1205
|
);
|
|
1206
1206
|
}
|
|
1207
1207
|
return null;
|
|
@@ -2518,33 +2518,33 @@ function addPlatformImport(path2, t) {
|
|
|
2518
2518
|
path2.unshiftContainer("body", importDeclaration);
|
|
2519
2519
|
}
|
|
2520
2520
|
}
|
|
2521
|
-
function addColorSchemeImport(path2, t) {
|
|
2521
|
+
function addColorSchemeImport(path2, importSource, hookName, t) {
|
|
2522
2522
|
const body = path2.node.body;
|
|
2523
|
-
let
|
|
2523
|
+
let existingValueImport = null;
|
|
2524
2524
|
for (const statement of body) {
|
|
2525
|
-
if (t.isImportDeclaration(statement) && statement.source.value ===
|
|
2526
|
-
|
|
2527
|
-
|
|
2525
|
+
if (t.isImportDeclaration(statement) && statement.source.value === importSource) {
|
|
2526
|
+
if (statement.importKind !== "type") {
|
|
2527
|
+
existingValueImport = statement;
|
|
2528
|
+
break;
|
|
2529
|
+
}
|
|
2528
2530
|
}
|
|
2529
2531
|
}
|
|
2530
|
-
if (
|
|
2531
|
-
const
|
|
2532
|
-
(spec) => t.isImportSpecifier(spec) && spec.imported.type === "Identifier" && spec.imported.name ===
|
|
2532
|
+
if (existingValueImport) {
|
|
2533
|
+
const hasHook = existingValueImport.specifiers.some(
|
|
2534
|
+
(spec) => t.isImportSpecifier(spec) && spec.imported.type === "Identifier" && spec.imported.name === hookName
|
|
2533
2535
|
);
|
|
2534
|
-
if (!
|
|
2535
|
-
|
|
2536
|
-
t.importSpecifier(t.identifier("useColorScheme"), t.identifier("useColorScheme"))
|
|
2537
|
-
);
|
|
2536
|
+
if (!hasHook) {
|
|
2537
|
+
existingValueImport.specifiers.push(t.importSpecifier(t.identifier(hookName), t.identifier(hookName)));
|
|
2538
2538
|
}
|
|
2539
2539
|
} else {
|
|
2540
2540
|
const importDeclaration = t.importDeclaration(
|
|
2541
|
-
[t.importSpecifier(t.identifier(
|
|
2542
|
-
t.stringLiteral(
|
|
2541
|
+
[t.importSpecifier(t.identifier(hookName), t.identifier(hookName))],
|
|
2542
|
+
t.stringLiteral(importSource)
|
|
2543
2543
|
);
|
|
2544
2544
|
path2.unshiftContainer("body", importDeclaration);
|
|
2545
2545
|
}
|
|
2546
2546
|
}
|
|
2547
|
-
function injectColorSchemeHook(functionPath, colorSchemeVariableName, t) {
|
|
2547
|
+
function injectColorSchemeHook(functionPath, colorSchemeVariableName, hookName, localIdentifier, t) {
|
|
2548
2548
|
let body = functionPath.node.body;
|
|
2549
2549
|
if (!t.isBlockStatement(body)) {
|
|
2550
2550
|
if (t.isArrowFunctionExpression(functionPath.node) && t.isExpression(body)) {
|
|
@@ -2566,10 +2566,11 @@ function injectColorSchemeHook(functionPath, colorSchemeVariableName, t) {
|
|
|
2566
2566
|
if (hasHook) {
|
|
2567
2567
|
return false;
|
|
2568
2568
|
}
|
|
2569
|
+
const identifierToCall = localIdentifier ?? hookName;
|
|
2569
2570
|
const hookCall = t.variableDeclaration("const", [
|
|
2570
2571
|
t.variableDeclarator(
|
|
2571
2572
|
t.identifier(colorSchemeVariableName),
|
|
2572
|
-
t.callExpression(t.identifier(
|
|
2573
|
+
t.callExpression(t.identifier(identifierToCall), [])
|
|
2573
2574
|
)
|
|
2574
2575
|
]);
|
|
2575
2576
|
body.body.unshift(hookCall);
|
|
@@ -2724,7 +2725,7 @@ function addOrMergePlaceholderTextColorProp(jsxOpeningElement, color, t) {
|
|
|
2724
2725
|
}
|
|
2725
2726
|
|
|
2726
2727
|
// src/babel/utils/twProcessing.ts
|
|
2727
|
-
function processTwCall(className, path2, state, parseClassName2, generateStyleKey2, splitModifierClasses2, t) {
|
|
2728
|
+
function processTwCall(className, path2, state, parseClassName2, generateStyleKey2, splitModifierClasses2, findComponentScope2, t) {
|
|
2728
2729
|
const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses2(className);
|
|
2729
2730
|
const modifierClasses = [];
|
|
2730
2731
|
for (const modifier of rawModifierClasses) {
|
|
@@ -2755,8 +2756,145 @@ function processTwCall(className, path2, state, parseClassName2, generateStyleKe
|
|
|
2755
2756
|
} else {
|
|
2756
2757
|
objectProperties.push(t.objectProperty(t.identifier("style"), t.objectExpression([])));
|
|
2757
2758
|
}
|
|
2759
|
+
const colorSchemeModifiers = modifierClasses.filter((m) => isColorSchemeModifier(m.modifier));
|
|
2760
|
+
const platformModifiers = modifierClasses.filter((m) => isPlatformModifier(m.modifier));
|
|
2761
|
+
const otherModifiers = modifierClasses.filter(
|
|
2762
|
+
(m) => !isColorSchemeModifier(m.modifier) && !isPlatformModifier(m.modifier)
|
|
2763
|
+
);
|
|
2764
|
+
const hasColorSchemeModifiers = colorSchemeModifiers.length > 0;
|
|
2765
|
+
let componentScope = null;
|
|
2766
|
+
if (hasColorSchemeModifiers) {
|
|
2767
|
+
componentScope = findComponentScope2(path2, t);
|
|
2768
|
+
if (!componentScope) {
|
|
2769
|
+
if (process.env.NODE_ENV !== "production") {
|
|
2770
|
+
console.warn(
|
|
2771
|
+
`[react-native-tailwind] Color scheme modifiers (dark:, light:) in tw/twStyle calls must be used inside a React component. Modifiers will be ignored.`
|
|
2772
|
+
);
|
|
2773
|
+
}
|
|
2774
|
+
} else {
|
|
2775
|
+
state.functionComponentsNeedingColorScheme.add(componentScope);
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
if (hasColorSchemeModifiers && componentScope) {
|
|
2779
|
+
const colorSchemeConditionals = processColorSchemeModifiers(
|
|
2780
|
+
colorSchemeModifiers,
|
|
2781
|
+
state,
|
|
2782
|
+
parseClassName2,
|
|
2783
|
+
generateStyleKey2,
|
|
2784
|
+
t
|
|
2785
|
+
);
|
|
2786
|
+
const styleArrayElements = [];
|
|
2787
|
+
if (baseClasses.length > 0) {
|
|
2788
|
+
const baseClassName = baseClasses.join(" ");
|
|
2789
|
+
const baseStyleObject = parseClassName2(baseClassName, state.customTheme);
|
|
2790
|
+
const baseStyleKey = generateStyleKey2(baseClassName);
|
|
2791
|
+
state.styleRegistry.set(baseStyleKey, baseStyleObject);
|
|
2792
|
+
styleArrayElements.push(
|
|
2793
|
+
t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
|
|
2794
|
+
);
|
|
2795
|
+
}
|
|
2796
|
+
styleArrayElements.push(...colorSchemeConditionals);
|
|
2797
|
+
objectProperties[0] = t.objectProperty(t.identifier("style"), t.arrayExpression(styleArrayElements));
|
|
2798
|
+
const darkModifiers = colorSchemeModifiers.filter((m) => m.modifier === "dark");
|
|
2799
|
+
const lightModifiers = colorSchemeModifiers.filter((m) => m.modifier === "light");
|
|
2800
|
+
if (darkModifiers.length > 0) {
|
|
2801
|
+
const darkClassNames = darkModifiers.map((m) => m.baseClass).join(" ");
|
|
2802
|
+
const darkStyleObject = parseClassName2(darkClassNames, state.customTheme);
|
|
2803
|
+
const darkStyleKey = generateStyleKey2(`dark_${darkClassNames}`);
|
|
2804
|
+
state.styleRegistry.set(darkStyleKey, darkStyleObject);
|
|
2805
|
+
objectProperties.push(
|
|
2806
|
+
t.objectProperty(
|
|
2807
|
+
t.identifier("darkStyle"),
|
|
2808
|
+
t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(darkStyleKey))
|
|
2809
|
+
)
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
if (lightModifiers.length > 0) {
|
|
2813
|
+
const lightClassNames = lightModifiers.map((m) => m.baseClass).join(" ");
|
|
2814
|
+
const lightStyleObject = parseClassName2(lightClassNames, state.customTheme);
|
|
2815
|
+
const lightStyleKey = generateStyleKey2(`light_${lightClassNames}`);
|
|
2816
|
+
state.styleRegistry.set(lightStyleKey, lightStyleObject);
|
|
2817
|
+
objectProperties.push(
|
|
2818
|
+
t.objectProperty(
|
|
2819
|
+
t.identifier("lightStyle"),
|
|
2820
|
+
t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(lightStyleKey))
|
|
2821
|
+
)
|
|
2822
|
+
);
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
const hasPlatformModifiers = platformModifiers.length > 0;
|
|
2826
|
+
if (hasPlatformModifiers) {
|
|
2827
|
+
state.needsPlatformImport = true;
|
|
2828
|
+
const platformSelectExpression = processPlatformModifiers(
|
|
2829
|
+
platformModifiers,
|
|
2830
|
+
state,
|
|
2831
|
+
parseClassName2,
|
|
2832
|
+
generateStyleKey2,
|
|
2833
|
+
t
|
|
2834
|
+
);
|
|
2835
|
+
if (hasColorSchemeModifiers && componentScope) {
|
|
2836
|
+
const styleProperty = objectProperties.find(
|
|
2837
|
+
(prop) => t.isIdentifier(prop.key) && prop.key.name === "style"
|
|
2838
|
+
);
|
|
2839
|
+
if (styleProperty && t.isArrayExpression(styleProperty.value)) {
|
|
2840
|
+
styleProperty.value.elements.push(platformSelectExpression);
|
|
2841
|
+
}
|
|
2842
|
+
} else {
|
|
2843
|
+
const styleArrayElements = [];
|
|
2844
|
+
if (baseClasses.length > 0) {
|
|
2845
|
+
const baseClassName = baseClasses.join(" ");
|
|
2846
|
+
const baseStyleObject = parseClassName2(baseClassName, state.customTheme);
|
|
2847
|
+
const baseStyleKey = generateStyleKey2(baseClassName);
|
|
2848
|
+
state.styleRegistry.set(baseStyleKey, baseStyleObject);
|
|
2849
|
+
styleArrayElements.push(
|
|
2850
|
+
t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(baseStyleKey))
|
|
2851
|
+
);
|
|
2852
|
+
}
|
|
2853
|
+
styleArrayElements.push(platformSelectExpression);
|
|
2854
|
+
objectProperties[0] = t.objectProperty(t.identifier("style"), t.arrayExpression(styleArrayElements));
|
|
2855
|
+
}
|
|
2856
|
+
const iosModifiers = platformModifiers.filter((m) => m.modifier === "ios");
|
|
2857
|
+
const androidModifiers = platformModifiers.filter((m) => m.modifier === "android");
|
|
2858
|
+
const webModifiers = platformModifiers.filter((m) => m.modifier === "web");
|
|
2859
|
+
if (iosModifiers.length > 0) {
|
|
2860
|
+
const iosClassNames = iosModifiers.map((m) => m.baseClass).join(" ");
|
|
2861
|
+
const iosStyleObject = parseClassName2(iosClassNames, state.customTheme);
|
|
2862
|
+
const iosStyleKey = generateStyleKey2(`ios_${iosClassNames}`);
|
|
2863
|
+
state.styleRegistry.set(iosStyleKey, iosStyleObject);
|
|
2864
|
+
objectProperties.push(
|
|
2865
|
+
t.objectProperty(
|
|
2866
|
+
t.identifier("iosStyle"),
|
|
2867
|
+
t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(iosStyleKey))
|
|
2868
|
+
)
|
|
2869
|
+
);
|
|
2870
|
+
}
|
|
2871
|
+
if (androidModifiers.length > 0) {
|
|
2872
|
+
const androidClassNames = androidModifiers.map((m) => m.baseClass).join(" ");
|
|
2873
|
+
const androidStyleObject = parseClassName2(androidClassNames, state.customTheme);
|
|
2874
|
+
const androidStyleKey = generateStyleKey2(`android_${androidClassNames}`);
|
|
2875
|
+
state.styleRegistry.set(androidStyleKey, androidStyleObject);
|
|
2876
|
+
objectProperties.push(
|
|
2877
|
+
t.objectProperty(
|
|
2878
|
+
t.identifier("androidStyle"),
|
|
2879
|
+
t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(androidStyleKey))
|
|
2880
|
+
)
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
if (webModifiers.length > 0) {
|
|
2884
|
+
const webClassNames = webModifiers.map((m) => m.baseClass).join(" ");
|
|
2885
|
+
const webStyleObject = parseClassName2(webClassNames, state.customTheme);
|
|
2886
|
+
const webStyleKey = generateStyleKey2(`web_${webClassNames}`);
|
|
2887
|
+
state.styleRegistry.set(webStyleKey, webStyleObject);
|
|
2888
|
+
objectProperties.push(
|
|
2889
|
+
t.objectProperty(
|
|
2890
|
+
t.identifier("webStyle"),
|
|
2891
|
+
t.memberExpression(t.identifier(state.stylesIdentifier), t.identifier(webStyleKey))
|
|
2892
|
+
)
|
|
2893
|
+
);
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2758
2896
|
const modifiersByType = /* @__PURE__ */ new Map();
|
|
2759
|
-
for (const mod of
|
|
2897
|
+
for (const mod of otherModifiers) {
|
|
2760
2898
|
if (!modifiersByType.has(mod.modifier)) {
|
|
2761
2899
|
modifiersByType.set(mod.modifier, []);
|
|
2762
2900
|
}
|
|
@@ -2852,6 +2990,8 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
|
|
|
2852
2990
|
darkSuffix: options?.schemeModifier?.darkSuffix ?? "-dark",
|
|
2853
2991
|
lightSuffix: options?.schemeModifier?.lightSuffix ?? "-light"
|
|
2854
2992
|
};
|
|
2993
|
+
const colorSchemeImportSource = options?.colorScheme?.importFrom ?? "react-native";
|
|
2994
|
+
const colorSchemeHookName = options?.colorScheme?.importName ?? "useColorScheme";
|
|
2855
2995
|
return {
|
|
2856
2996
|
name: "react-native-tailwind",
|
|
2857
2997
|
visitor: {
|
|
@@ -2865,12 +3005,18 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
|
|
|
2865
3005
|
state.hasColorSchemeImport = false;
|
|
2866
3006
|
state.needsColorSchemeImport = false;
|
|
2867
3007
|
state.colorSchemeVariableName = "_twColorScheme";
|
|
3008
|
+
state.colorSchemeImportSource = colorSchemeImportSource;
|
|
3009
|
+
state.colorSchemeHookName = colorSchemeHookName;
|
|
2868
3010
|
state.supportedAttributes = exactMatches;
|
|
2869
3011
|
state.attributePatterns = patterns;
|
|
2870
3012
|
state.stylesIdentifier = stylesIdentifier;
|
|
2871
3013
|
state.twImportNames = /* @__PURE__ */ new Set();
|
|
2872
3014
|
state.hasTwImport = false;
|
|
2873
3015
|
state.functionComponentsNeedingColorScheme = /* @__PURE__ */ new Set();
|
|
3016
|
+
state.hasColorSchemeImport = false;
|
|
3017
|
+
state.colorSchemeLocalIdentifier = void 0;
|
|
3018
|
+
state.needsPlatformImport = false;
|
|
3019
|
+
state.hasPlatformImport = false;
|
|
2874
3020
|
state.customTheme = extractCustomTheme(state.file.opts.filename ?? "");
|
|
2875
3021
|
state.schemeModifierConfig = schemeModifierConfig;
|
|
2876
3022
|
},
|
|
@@ -2888,11 +3034,17 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
|
|
|
2888
3034
|
addPlatformImport(path2, t);
|
|
2889
3035
|
}
|
|
2890
3036
|
if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
|
|
2891
|
-
addColorSchemeImport(path2, t);
|
|
3037
|
+
addColorSchemeImport(path2, state.colorSchemeImportSource, state.colorSchemeHookName, t);
|
|
2892
3038
|
}
|
|
2893
3039
|
if (state.needsColorSchemeImport) {
|
|
2894
3040
|
for (const functionPath of state.functionComponentsNeedingColorScheme) {
|
|
2895
|
-
injectColorSchemeHook(
|
|
3041
|
+
injectColorSchemeHook(
|
|
3042
|
+
functionPath,
|
|
3043
|
+
state.colorSchemeVariableName,
|
|
3044
|
+
state.colorSchemeHookName,
|
|
3045
|
+
state.colorSchemeLocalIdentifier,
|
|
3046
|
+
t
|
|
3047
|
+
);
|
|
2896
3048
|
}
|
|
2897
3049
|
}
|
|
2898
3050
|
injectStylesAtTop(path2, state.styleRegistry, state.stylesIdentifier, t);
|
|
@@ -2915,23 +3067,26 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
|
|
|
2915
3067
|
}
|
|
2916
3068
|
return false;
|
|
2917
3069
|
});
|
|
2918
|
-
const hasUseColorScheme = specifiers.some((spec) => {
|
|
2919
|
-
if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
|
|
2920
|
-
return spec.imported.name === "useColorScheme";
|
|
2921
|
-
}
|
|
2922
|
-
return false;
|
|
2923
|
-
});
|
|
2924
3070
|
if (hasStyleSheet) {
|
|
2925
3071
|
state.hasStyleSheetImport = true;
|
|
2926
3072
|
}
|
|
2927
3073
|
if (hasPlatform) {
|
|
2928
3074
|
state.hasPlatformImport = true;
|
|
2929
3075
|
}
|
|
2930
|
-
if (hasUseColorScheme) {
|
|
2931
|
-
state.hasColorSchemeImport = true;
|
|
2932
|
-
}
|
|
2933
3076
|
state.reactNativeImportPath = path2;
|
|
2934
3077
|
}
|
|
3078
|
+
if (node.source.value === state.colorSchemeImportSource && node.importKind !== "type") {
|
|
3079
|
+
const specifiers = node.specifiers;
|
|
3080
|
+
for (const spec of specifiers) {
|
|
3081
|
+
if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
|
|
3082
|
+
if (spec.imported.name === state.colorSchemeHookName) {
|
|
3083
|
+
state.hasColorSchemeImport = true;
|
|
3084
|
+
state.colorSchemeLocalIdentifier = spec.local.name;
|
|
3085
|
+
break;
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
2935
3090
|
if (node.source.value === "@mgcrea/react-native-tailwind") {
|
|
2936
3091
|
const specifiers = node.specifiers;
|
|
2937
3092
|
specifiers.forEach((spec) => {
|
|
@@ -2976,7 +3131,16 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
|
|
|
2976
3131
|
return;
|
|
2977
3132
|
}
|
|
2978
3133
|
state.hasClassNames = true;
|
|
2979
|
-
processTwCall(
|
|
3134
|
+
processTwCall(
|
|
3135
|
+
className,
|
|
3136
|
+
path2,
|
|
3137
|
+
state,
|
|
3138
|
+
parseClassName,
|
|
3139
|
+
generateStyleKey,
|
|
3140
|
+
splitModifierClasses,
|
|
3141
|
+
findComponentScope,
|
|
3142
|
+
t
|
|
3143
|
+
);
|
|
2980
3144
|
},
|
|
2981
3145
|
// Handle twStyle('...') call expressions
|
|
2982
3146
|
CallExpression(path2, state) {
|
|
@@ -3011,7 +3175,16 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
|
|
|
3011
3175
|
return;
|
|
3012
3176
|
}
|
|
3013
3177
|
state.hasClassNames = true;
|
|
3014
|
-
processTwCall(
|
|
3178
|
+
processTwCall(
|
|
3179
|
+
className,
|
|
3180
|
+
path2,
|
|
3181
|
+
state,
|
|
3182
|
+
parseClassName,
|
|
3183
|
+
generateStyleKey,
|
|
3184
|
+
splitModifierClasses,
|
|
3185
|
+
findComponentScope,
|
|
3186
|
+
t
|
|
3187
|
+
);
|
|
3015
3188
|
},
|
|
3016
3189
|
JSXAttribute(path2, state) {
|
|
3017
3190
|
const node = path2.node;
|
|
@@ -3024,14 +3197,14 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
|
|
|
3024
3197
|
}
|
|
3025
3198
|
const value = node.value;
|
|
3026
3199
|
const targetStyleProp = getTargetStyleProp(attributeName);
|
|
3027
|
-
|
|
3028
|
-
const
|
|
3029
|
-
if (!
|
|
3200
|
+
const processStaticClassName = (className) => {
|
|
3201
|
+
const trimmedClassName = className.trim();
|
|
3202
|
+
if (!trimmedClassName) {
|
|
3030
3203
|
path2.remove();
|
|
3031
|
-
return;
|
|
3204
|
+
return true;
|
|
3032
3205
|
}
|
|
3033
3206
|
state.hasClassNames = true;
|
|
3034
|
-
const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(
|
|
3207
|
+
const { baseClasses, modifierClasses: rawModifierClasses } = splitModifierClasses(trimmedClassName);
|
|
3035
3208
|
const modifierClasses = [];
|
|
3036
3209
|
for (const modifier of rawModifierClasses) {
|
|
3037
3210
|
if (isSchemeModifier(modifier.modifier)) {
|
|
@@ -3155,7 +3328,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
|
|
|
3155
3328
|
} else {
|
|
3156
3329
|
replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
|
|
3157
3330
|
}
|
|
3158
|
-
return;
|
|
3331
|
+
return true;
|
|
3159
3332
|
} else {
|
|
3160
3333
|
}
|
|
3161
3334
|
}
|
|
@@ -3206,7 +3379,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
|
|
|
3206
3379
|
path2.node.name = t.jsxIdentifier(targetStyleProp);
|
|
3207
3380
|
path2.node.value = t.jsxExpressionContainer(styleExpression);
|
|
3208
3381
|
}
|
|
3209
|
-
return;
|
|
3382
|
+
return true;
|
|
3210
3383
|
}
|
|
3211
3384
|
if (hasStateModifiers) {
|
|
3212
3385
|
const jsxOpeningElement = path2.parent;
|
|
@@ -3244,11 +3417,11 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
|
|
|
3244
3417
|
} else {
|
|
3245
3418
|
replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
|
|
3246
3419
|
}
|
|
3247
|
-
return;
|
|
3420
|
+
return true;
|
|
3248
3421
|
}
|
|
3249
3422
|
} else {
|
|
3250
3423
|
const styleExpression = processStaticClassNameWithModifiers(
|
|
3251
|
-
|
|
3424
|
+
trimmedClassName,
|
|
3252
3425
|
state,
|
|
3253
3426
|
parseClassName,
|
|
3254
3427
|
generateStyleKey,
|
|
@@ -3263,7 +3436,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
|
|
|
3263
3436
|
} else {
|
|
3264
3437
|
replaceWithStyleFunctionAttribute(path2, styleFunctionExpression, targetStyleProp, t);
|
|
3265
3438
|
}
|
|
3266
|
-
return;
|
|
3439
|
+
return true;
|
|
3267
3440
|
}
|
|
3268
3441
|
} else {
|
|
3269
3442
|
if (process.env.NODE_ENV !== "production") {
|
|
@@ -3277,7 +3450,7 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
|
|
|
3277
3450
|
const classNameForStyle = baseClasses.join(" ");
|
|
3278
3451
|
if (!classNameForStyle) {
|
|
3279
3452
|
path2.remove();
|
|
3280
|
-
return;
|
|
3453
|
+
return true;
|
|
3281
3454
|
}
|
|
3282
3455
|
const styleObject = parseClassName(classNameForStyle, state.customTheme);
|
|
3283
3456
|
const styleKey = generateStyleKey(classNameForStyle);
|
|
@@ -3288,13 +3461,23 @@ function reactNativeTailwindBabelPlugin({ types: t }, options) {
|
|
|
3288
3461
|
} else {
|
|
3289
3462
|
replaceWithStyleAttribute(path2, styleKey, targetStyleProp, state.stylesIdentifier, t);
|
|
3290
3463
|
}
|
|
3291
|
-
return;
|
|
3464
|
+
return true;
|
|
3465
|
+
};
|
|
3466
|
+
if (t.isStringLiteral(value)) {
|
|
3467
|
+
if (processStaticClassName(value.value)) {
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3292
3470
|
}
|
|
3293
3471
|
if (t.isJSXExpressionContainer(value)) {
|
|
3294
3472
|
const expression = value.expression;
|
|
3295
3473
|
if (t.isJSXEmptyExpression(expression)) {
|
|
3296
3474
|
return;
|
|
3297
3475
|
}
|
|
3476
|
+
if (t.isStringLiteral(expression)) {
|
|
3477
|
+
if (processStaticClassName(expression.value)) {
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3298
3481
|
try {
|
|
3299
3482
|
const componentScope = findComponentScope(path2, t);
|
|
3300
3483
|
const result = processDynamicExpression(
|
package/dist/babel/plugin.d.ts
CHANGED
|
@@ -41,6 +41,40 @@ export type PluginOptions = {
|
|
|
41
41
|
darkSuffix?: string;
|
|
42
42
|
lightSuffix?: string;
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Configuration for color scheme hook import (dark:/light: modifiers)
|
|
46
|
+
*
|
|
47
|
+
* Allows using custom color scheme hooks from theme providers instead of
|
|
48
|
+
* React Native's built-in useColorScheme.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* // Use custom hook from theme provider
|
|
52
|
+
* {
|
|
53
|
+
* importFrom: '@/hooks/useColorScheme',
|
|
54
|
+
* importName: 'useColorScheme'
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // Use React Navigation theme
|
|
59
|
+
* {
|
|
60
|
+
* importFrom: '@react-navigation/native',
|
|
61
|
+
* importName: 'useTheme' // You'd wrap this to return ColorSchemeName
|
|
62
|
+
* }
|
|
63
|
+
*
|
|
64
|
+
* @default { importFrom: 'react-native', importName: 'useColorScheme' }
|
|
65
|
+
*/
|
|
66
|
+
colorScheme?: {
|
|
67
|
+
/**
|
|
68
|
+
* Module to import the color scheme hook from
|
|
69
|
+
* @default 'react-native'
|
|
70
|
+
*/
|
|
71
|
+
importFrom?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Name of the hook to import
|
|
74
|
+
* @default 'useColorScheme'
|
|
75
|
+
*/
|
|
76
|
+
importName?: string;
|
|
77
|
+
};
|
|
44
78
|
};
|
|
45
79
|
type PluginState = PluginPass & {
|
|
46
80
|
styleRegistry: Map<string, StyleObject>;
|
|
@@ -51,6 +85,9 @@ type PluginState = PluginPass & {
|
|
|
51
85
|
hasColorSchemeImport: boolean;
|
|
52
86
|
needsColorSchemeImport: boolean;
|
|
53
87
|
colorSchemeVariableName: string;
|
|
88
|
+
colorSchemeImportSource: string;
|
|
89
|
+
colorSchemeHookName: string;
|
|
90
|
+
colorSchemeLocalIdentifier?: string;
|
|
54
91
|
customTheme: CustomTheme;
|
|
55
92
|
schemeModifierConfig: SchemeModifierConfig;
|
|
56
93
|
supportedAttributes: Set<string>;
|