Skip to content

Commit cb77b92

Browse files
chore: configure Tailwind v3/v4 dual support across packages
- React Native package: Tailwind v3 only (using twrnc preset) - React package: Support both v3 (preset) and v4 (theme.css) - Update eslint and jest configs for platform-specific requirements - Configure VSCode settings for appropriate Tailwind versions
1 parent 55c6251 commit cb77b92

12 files changed

Lines changed: 271 additions & 25 deletions

File tree

.depcheckrc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ignores:
1414
- 'eslint-interactive'
1515
- 'rimraf'
1616
- 'simple-git-hooks'
17+
- 'tailwindcss'
1718
- 'tsx'
1819
# Ignore plugins for tools
1920
- '@typescript-eslint/*'

.yarnrc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ plugins:
1616
- path: .yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs
1717
spec: 'https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js'
1818

19-
nmHoistingLimits: none
19+
nmHoistingLimits: workspaces
2020

2121
# NPM Supply Chain Attack Protection
2222
# Minimum age gate: only allow packages older than 3 days (4320 minutes)

apps/storybook-react-native/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@
3636
"@types/react": "^18.2.0",
3737
"@types/react-dom": "^18.2.0",
3838
"@types/react-native-get-random-values": "^1",
39+
"eslint-plugin-tailwindcss": "^3.18.2",
3940
"react-dom": "^18.2.0",
4041
"react-native-svg-transformer": "^1.5.0",
42+
"tailwindcss": "^3.4.0",
4143
"typescript": "~5.2.2"
4244
},
4345
"dependenciesMeta": {

eslint.config.mjs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,19 @@ import base, { createConfig } from '@metamask/eslint-config';
22
import jest from '@metamask/eslint-config-jest';
33
import nodejs from '@metamask/eslint-config-nodejs';
44
import typescript from '@metamask/eslint-config-typescript';
5-
import tailwind from 'eslint-plugin-better-tailwindcss';
5+
import betterTailwind from 'eslint-plugin-better-tailwindcss';
6+
import { createRequire } from 'node:module';
7+
import path from 'node:path';
68

79
const NODE_LTS_VERSION = 22;
10+
const nativeRequire = createRequire(
11+
new URL('./apps/storybook-react-native/package.json', import.meta.url),
12+
);
13+
const nativeTailwind = nativeRequire('eslint-plugin-tailwindcss');
14+
const NATIVE_TAILWIND_CONFIG_PATH = path.resolve(
15+
import.meta.dirname,
16+
'apps/storybook-react-native/tailwind-intellisense.config.js',
17+
);
818

919
const config = createConfig([
1020
...base,
@@ -221,7 +231,7 @@ const config = createConfig([
221231
},
222232
},
223233
plugins: {
224-
'better-tailwindcss': tailwind,
234+
'better-tailwindcss': betterTailwind,
225235
},
226236
rules: {
227237
'better-tailwindcss/sort-classes': 'error',
@@ -233,17 +243,24 @@ const config = createConfig([
233243
'packages/design-system-react-native/src/**',
234244
'apps/storybook-react-native/stories/**',
235245
],
236-
settings: {
237-
'better-tailwindcss': {
238-
tailwindConfig:
239-
'apps/storybook-react-native/tailwind-intellisense.config.js',
240-
},
241-
},
242246
plugins: {
243-
'better-tailwindcss': tailwind,
247+
tailwindcss: nativeTailwind,
244248
},
245249
rules: {
246-
'better-tailwindcss/sort-classes': 'error',
250+
'tailwindcss/classnames-order': 'error',
251+
'tailwindcss/enforces-negative-arbitrary-values': 'error',
252+
'tailwindcss/enforces-shorthand': 'error',
253+
'tailwindcss/no-arbitrary-value': 'off', // There are legitimate reasons to use arbitrary values but we should specifically error on static colors
254+
'tailwindcss/no-custom-classname': 'off', // Native includes dynamic token-driven class segments (e.g. text-${...})
255+
'tailwindcss/no-contradicting-classname': 'error',
256+
'tailwindcss/no-unnecessary-arbitrary-value': 'error',
257+
},
258+
settings: {
259+
tailwindcss: {
260+
callees: ['twClassName', 'tw'],
261+
config: NATIVE_TAILWIND_CONFIG_PATH,
262+
tags: ['tw'], // Enable template literal support for tw`classnames`
263+
},
247264
},
248265
},
249266
]);

jest.config.packages.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,5 +200,5 @@ module.exports = {
200200
// watchPathIgnorePatterns: [],
201201

202202
// Whether to use watchman for file crawling
203-
// watchman: true,
203+
watchman: false,
204204
};

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"simple-plist": "1.3.1",
7070
"shell-quote": "1.7.3",
7171
"cross-spawn": "7.0.5",
72+
"twrnc/tailwindcss": "^3.4.0",
7273
"node-fetch": "2.6.7",
7374
"json5": "2.2.2",
7475
"merge": "2.1.1",
@@ -122,6 +123,7 @@
122123
"rimraf": "^5.0.5",
123124
"semver": "^7.7.1",
124125
"simple-git-hooks": "^2.8.0",
126+
"tailwindcss": "^4.0.0",
125127
"tsx": "^4.20.6",
126128
"twrnc": "^4.5.1",
127129
"typescript": "~5.2.2",

packages/design-system-react-native/jest.setup.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
global.__fbBatchedBridgeConfig = {
2+
remoteModuleConfig: [],
3+
};
4+
5+
jest.mock(
6+
'react-native/Libraries/Utilities/NativePlatformConstantsIOS',
7+
() => ({
8+
__esModule: true,
9+
default: {
10+
getConstants: () => ({
11+
forceTouchAvailable: false,
12+
interfaceIdiom: 'handset',
13+
isTesting: true,
14+
osVersion: '0.0.0',
15+
reactNativeVersion: { major: 0, minor: 0, patch: 0, prerelease: null },
16+
systemName: 'iOS',
17+
}),
18+
},
19+
}),
20+
);
21+
122
jest.mock('react-native-svg', () => {
223
const React = require('react');
324
const { View } = require('react-native');

packages/design-system-react-native/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"react-native-svg": "^15.10.1",
8585
"react-native-svg-transformer": "^1.5.0",
8686
"react-test-renderer": "^18.3.1",
87+
"tailwindcss": "^3.4.0",
8788
"ts-jest": "^29.2.5",
8889
"tsx": "^4.20.6",
8990
"typescript": "~5.2.2"

packages/design-system-react/global.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,17 @@ declare module '*.svg' {
1010
const src: string;
1111
export default src;
1212
}
13+
14+
declare module '@testing-library/dom' {
15+
export type Queries = any;
16+
export type BoundFunction<T = any> = (...args: any[]) => any;
17+
export const queries: Queries;
18+
export const screen: any;
19+
export const fireEvent: any;
20+
export function waitFor<T = any>(callback: () => T, options?: any): Promise<T>;
21+
export function waitForElementToBeRemoved<T = any>(callback: () => T, options?: any): Promise<void>;
22+
export function act(fn: () => any): Promise<any> | void;
23+
export function within(...args: any[]): any;
24+
export function configure(options: any): void;
25+
export function getConfig(): any;
26+
}

packages/design-system-twrnc-preset/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"metro-react-native-babel-preset": "^0.77.0",
7777
"react": "^18.2.0",
7878
"react-test-renderer": "^18.3.1",
79+
"tailwindcss": "^3.4.0",
7980
"ts-jest": "^29.2.5",
8081
"typescript": "~5.2.2"
8182
},

0 commit comments

Comments
 (0)