fix: improvements to platform prefixes

This commit is contained in:
Mark Lawlor
2022-04-27 17:43:30 +10:00
parent f053020fe9
commit 3496060bc0
12 changed files with 423 additions and 321 deletions

View File

@@ -18,11 +18,12 @@ Install the library
`npm install tailwindcss-react-native tailwindcss` or `yarn add tailwindcss-react-native tailwindcss`
Create a `tailwind.config.js` and set `content`
Create a `tailwind.config.js` and set `content` and added the `tailwindcss-react-native/plugin`
```js
// tailwind.config.js
module.exports = {
plugins: [require("tailwindcss-react-native/plugin")],
content: [
"./screens/**/*.{js,ts,jsx,tsx}",
"./pages/**/*.{js,ts,jsx,tsx}",
@@ -200,11 +201,11 @@ function MyAppsProviders ({ children }) {
You don't need to provide these props if you are using Babel or spreading the CLI output.
| Prop | Values | Default | Description |
| -------- | ----------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------- |
| platform | `native`, `web`, `ios`, `android`, `windows`, `macos` | Platform.OS | Specifies how the className is transformed. `ios`, `android`, `windows`, `macos` are aliases for `native` |
| style | Compiled style object | | |
| media | Compiled media object | | |
| Prop | Values | Default | Description |
| -------- | ------------------------------------------------------------------- | ----------- | ----------------------------------------- |
| platform | `web`, `native`, `ios`, `android`, `windows`, `macos`, `web-inline` | Platform.OS | Specifies how the styles are transformed. |
| style | Compiled style object | | |
| media | Compiled media object | | |
## Component API
@@ -265,17 +266,22 @@ Options can be provided via the babel config
```js
// babel.config.js
module.exports = {
plugins: [["tailwindcss-react-native/babel", { platform: "native" }]],
plugins: [
[
"tailwindcss-react-native/babel",
{ tailwindConfig: "./tailwind.native.config.js" },
],
],
};
```
| Option | Values | Default | Description |
| -------------- | ----------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| platform | `native`, `web`, `ios`, `android`, `windows`, `macos` | `native` | Specifies how the className is transformed. `ios`, `android`, `windows`, `macos` are aliases for `native` |
| hmr | `boolean` | Development: `true` <br />Production: `false` | Allow fast-refresh of styles |
| tailwindConfig | Path relative to `cwd` | `tailwind.config.js` | Provide a custom `tailwind.config.js`. Useful for setting different settings per platform. |
| allowModules | `*`, `string[]` | `*` | Only transform components from these imported modules. `*` will transform all modules |
| blockModules | `string[]` | `[]` | Do not transform components from these imported modules. |
| Option | Values | Default | Description |
| -------------- | ----------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------ |
| platform | `native`, `web`, `ios`, `android`, `windows`, `macos` | `native` | Specifies how the className is transformed. |
| hmr | `boolean` | Development: `true` <br />Production: `false` | Allow fast-refresh of styles |
| tailwindConfig | Path relative to `cwd` | `tailwind.config.js` | Provide a custom `tailwind.config.js`. Useful for setting different settings per platform. |
| allowModules | `*`, `string[]` | `*` | Only transform components from these imported modules. `*` will transform all modules |
| blockModules | `string[]` | `[]` | Do not transform components from these imported modules. |
### CLI Options

View File

@@ -0,0 +1,80 @@
import { tailwindRunner } from "../tailwindcss/runner";
tailwindRunner("Platform Prefixes", [
[
"ios:w-px",
{
styles: {
"ios_w-px_0": { width: 1 },
},
media: {
"ios_w-px": [["ios", 0]],
},
},
],
[
"android:w-px",
{
styles: {
"android_w-px_0": { width: 1 },
},
media: {
"android_w-px": [["android", 0]],
},
},
],
[
"windows:w-px",
{
styles: {
"windows_w-px_0": { width: 1 },
},
media: {
"windows_w-px": [["windows", 0]],
},
},
],
[
"macos:w-px",
{
styles: {
"macos_w-px_0": { width: 1 },
},
media: {
"macos_w-px": [["macos", 0]],
},
},
],
[
"web:w-px",
{
styles: {
"web_w-px_0": { width: 1 },
},
media: {
"web_w-px": [["web-inline", 0]],
},
},
],
[
"native:w-px",
{
styles: {
"native_w-px_0": { width: 1 },
"native_w-px_1": { width: 1 },
"native_w-px_2": { width: 1 },
"native_w-px_3": { width: 1 },
"native_w-px_4": { width: 1 },
},
media: {
"native_w-px": [
["native", 0],
["android", 1],
["ios", 2],
["windows", 3],
["macos", 4],
],
},
},
],
]);

View File

@@ -1,9 +1,9 @@
import { getNativeTailwindConfig } from "../../src/babel/tailwind/native-config";
import { extractStyles } from "../../src/babel/native-style-extraction";
import { normaliseSelector } from "../../src/shared/selector";
import { MediaRecord, StyleRecord } from "../../src/types/common";
const nativeConfig = getNativeTailwindConfig();
import plugin from "../../src/plugin";
import { nativePlugin } from "../../src/plugin/native";
export type Test = [string, Expected];
@@ -22,23 +22,16 @@ export function tailwindRunner(name: string, testCases: Test[]) {
});
}
export function assertStyles(
css: string,
{ styles: expectedStyles, media: expectedMedia }: Expected
) {
const { styles, media } = extractStyles({
export function assertStyles(css: string, { styles, media = {} }: Expected) {
const output = extractStyles({
theme: {},
...nativeConfig,
plugins: [plugin, nativePlugin()],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
content: [{ raw: "", extension: "html" } as any],
safelist: [css],
});
expect(styles).toEqual(expectedStyles);
if (expectedMedia) {
expect(media).toEqual(expectedMedia);
}
expect(output).toEqual({ styles, media });
}
/**

1
plugin.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require("./dist/plugin").default;

View File

@@ -4,7 +4,7 @@ import { existsSync } from "node:fs";
import resolveTailwindConfig from "tailwindcss/resolveConfig";
import { TailwindConfig } from "tailwindcss/tailwind-config";
import { getNativeTailwindConfig } from "./native-config";
import { nativePlugin } from "../../plugin/native";
export interface GetTailwindConfigOptions {
rem?: number;
@@ -33,16 +33,9 @@ export function getTailwindConfig(
userConfig = {};
}
const nativeConfig = getNativeTailwindConfig(options);
const mergedConfig = {
...nativeConfig,
...userConfig,
theme: {
...nativeConfig.theme,
...userConfig.theme,
},
plugins: [...(nativeConfig.plugins ?? []), ...(userConfig.plugins ?? [])],
plugins: [nativePlugin(options), ...(userConfig.plugins ?? [])],
};
return resolveTailwindConfig(mergedConfig);

View File

@@ -1,248 +0,0 @@
import { TailwindConfig } from "tailwindcss/tailwind-config";
import { nativePlugin } from "./native-plugin";
export interface GetNativeTailwindConfigOptions {
rem?: number;
}
export function getNativeTailwindConfig({
rem = 16,
}: GetNativeTailwindConfigOptions = {}) {
const config: Partial<TailwindConfig> = {
plugins: [nativePlugin],
corePlugins: {
accentColor: false,
accessibility: false,
animation: false,
appearance: false,
aspectRatio: false,
backdropBlur: false,
backdropBrightness: false,
backdropContrast: false,
backdropFilter: false,
backdropGrayscale: false,
backdropHueRotate: false,
backdropInvert: false,
backdropOpacity: false,
backdropSaturate: false,
backdropSepia: false,
backgroundAttachment: false,
backgroundBlendMode: false,
backgroundClip: false,
backgroundImage: false,
backgroundOrigin: false,
backgroundPosition: false,
backgroundRepeat: false,
backgroundSize: false,
blur: false,
borderCollapse: false,
boxDecorationBreak: false,
boxShadow: false,
boxSizing: false,
breakAfter: false,
breakBefore: false,
breakInside: false,
brightness: false,
caretColor: false,
clear: false,
columns: false,
content: false,
contrast: false,
cursor: false,
divideColor: false,
divideOpacity: false,
divideStyle: false,
divideWidth: false,
dropShadow: false,
fill: false,
filter: false,
float: false,
fontSmoothing: false,
gap: false,
gradientColorStops: false,
grayscale: false,
gridAutoColumns: false,
gridAutoFlow: false,
gridAutoRows: false,
gridColumn: false,
gridColumnEnd: false,
gridColumnStart: false,
gridRow: false,
gridRowEnd: false,
gridRowStart: false,
gridTemplateColumns: false,
gridTemplateRows: false,
hueRotate: false,
invert: false,
isolation: false,
justifyItems: false,
justifySelf: false,
listStylePosition: false,
listStyleType: false,
mixBlendMode: false,
objectFit: false,
objectPosition: false,
order: false,
overscrollBehavior: false,
placeItems: false,
placeSelf: false,
placeholderColor: false,
placeholderOpacity: false,
preflight: false,
resize: false,
ringColor: false,
ringOffsetColor: false,
ringOffsetWidth: false,
ringOpacity: false,
ringWidth: false,
rotate: false,
saturate: false,
scale: false,
scrollBehavior: false,
scrollMargin: false,
scrollPadding: false,
scrollSnapAlign: false,
scrollSnapStop: false,
scrollSnapType: false,
sepia: false,
skew: false,
space: false,
stroke: false,
strokeWidth: false,
tableLayout: false,
textIndent: false,
textOverflow: false,
touchAction: false,
transform: false,
transformOrigin: false,
transitionDelay: false,
transitionDuration: false,
transitionProperty: false,
transitionTimingFunction: false,
translate: false,
userSelect: false,
verticalAlign: false,
visibility: false,
whitespace: false,
willChange: false,
wordBreak: false,
},
theme: {
aspectRatio: {
auto: "0",
square: "1",
video: "1.777777778",
},
letterSpacing: {
tighter: "-0.5px",
tight: "-0.25px",
normal: "0px",
wide: "0.25px",
wider: "0.5px",
widest: "1px",
},
spacing: {
px: "1px",
0: "0px",
0.5: `${rem * 0.125}px`,
1: `${rem * 0.25}px`,
1.5: `${rem * 0.375}px`,
2: `${rem * 0.5}px`,
2.5: `${rem * 0.625}px`,
3: `${rem * 0.75}px`,
3.5: `${rem * 0.875}px`,
4: `${rem * 1}px`,
5: `${rem * 1.25}px`,
6: `${rem * 1.5}px`,
7: `${rem * 1.75}px`,
8: `${rem * 2}px`,
9: `${rem * 2.25}px`,
10: `${rem * 2.5}px`,
11: `${rem * 2.75}px`,
12: `${rem * 3}px`,
14: `${rem * 3.5}px`,
16: `${rem * 4}px`,
20: `${rem * 5}px`,
24: `${rem * 6}px`,
28: `${rem * 7}px`,
32: `${rem * 8}px`,
36: `${rem * 9}px`,
40: `${rem * 10}px`,
44: `${rem * 11}px`,
48: `${rem * 12}px`,
52: `${rem * 13}px`,
56: `${rem * 14}px`,
60: `${rem * 15}px`,
64: `${rem * 16}px`,
72: `${rem * 18}px`,
80: `${rem * 20}px`,
96: `${rem * 24}px`,
},
borderRadius: {
none: "0px",
sm: `${rem * 0.125}px`,
DEFAULT: `${rem * 0.25}px`,
md: `${rem * 0.375}px`,
lg: `${rem * 0.5}px`,
xl: `${rem * 0.75}px`,
"2xl": `${rem * 1}px`,
"3xl": `${rem * 1.5}px`,
full: "9999px",
},
fontSize: {
xs: [`${rem * 0.75}px`, { lineHeight: `${rem * 1}px` }],
sm: [`${rem * 0.875}px`, { lineHeight: `${rem * 1.25}px` }],
base: [`${rem * 1}px`, { lineHeight: `${rem * 1.5}px` }],
lg: [`${rem * 1.125}px`, { lineHeight: `${rem * 1.75}px` }],
xl: [`${rem * 1.25}px`, { lineHeight: `${rem * 1.75}px` }],
"2xl": [`${rem * 1.5}px`, { lineHeight: `${rem * 2}px` }],
"3xl": [`${rem * 1.875}px`, { lineHeight: `${rem * 2.25}px` }],
"4xl": [`${rem * 2.25}px`, { lineHeight: `${rem * 2.5}px` }],
"5xl": [`${rem * 3}px`, { lineHeight: "1" }],
"6xl": [`${rem * 3.75}px`, { lineHeight: "1" }],
"7xl": [`${rem * 4.5}px`, { lineHeight: "1" }],
"8xl": [`${rem * 6}px`, { lineHeight: "1" }],
"9xl": [`${rem * 8}px`, { lineHeight: "1" }],
},
lineHeight: {
none: "1",
tight: "1.25",
snug: "1.375",
normal: "1.5",
relaxed: "1.625",
loose: "2",
3: `${rem * 0.75}px`,
4: `${rem * 1}px`,
5: `${rem * 1.25}px`,
6: `${rem * 1.5}px`,
7: `${rem * 1.75}px`,
8: `${rem * 2}px`,
9: `${rem * 2.25}px`,
10: `${rem * 2.5}px`,
},
maxWidth: ({ theme, breakpoints }) => ({
none: "none",
0: `${rem * 0}px`,
xs: `${rem * 20}px`,
sm: `${rem * 24}px`,
md: `${rem * 28}px`,
lg: `${rem * 32}px`,
xl: `${rem * 36}px`,
"2xl": `${rem * 42}px`,
"3xl": `${rem * 48}px`,
"4xl": `${rem * 56}px`,
"5xl": `${rem * 64}px`,
"6xl": `${rem * 72}px`,
"7xl": `${rem * 80}px`,
full: "100%",
min: "min-content",
max: "max-content",
fit: "fit-content",
prose: "65ch",
...breakpoints(theme("screens")),
}),
},
};
return config;
}

View File

@@ -1,32 +0,0 @@
import plugin from "tailwindcss/plugin";
export const nativePlugin = plugin(function ({
addVariant,
matchUtilities,
theme,
}) {
addVariant("native", "@media native");
addVariant("ios", "");
addVariant("android", "");
matchUtilities(
{
aspect: (value: string) => {
let aspectRatio = value;
if (value.includes("/")) {
const [left, right] = value.split("/").map((n) => {
return Number.parseInt(n, 10);
});
aspectRatio = `${left / right}`;
}
return {
aspectRatio,
};
},
},
{ values: theme("aspectRatio") }
);
});

View File

@@ -6,9 +6,10 @@ import micromatch from "micromatch";
import { NodePath } from "@babel/core";
import { ImportDeclaration } from "@babel/types";
import { VisitorState } from "../visitor";
import { platforms } from "../../shared/platforms";
const allowedIndexFiles: string[] = [];
for (const platform of ["android", "ios", "native", "web", "windows"]) {
for (const platform of platforms) {
for (const extension of ["js", "jsx", "ts", "tsx"]) {
allowedIndexFiles.push(`index.${platform}.${extension}`);
}

View File

@@ -1,6 +1,7 @@
import { createContext } from "react";
import { Appearance, ColorSchemeName, Platform } from "react-native";
import { Appearance, ColorSchemeName } from "react-native";
import { MediaRecord, StyleRecord } from "./types/common";
import { Platform } from "./shared/platforms";
declare global {
// eslint-disable-next-line no-var
@@ -27,6 +28,6 @@ export const TailwindSetColorSchemeContext = createContext<
>(() => {
return;
});
export const TailwindPlatformContext = createContext<
typeof Platform.OS | "native" | undefined
>(undefined);
export const TailwindPlatformContext = createContext<Platform | undefined>(
undefined
);

15
src/plugin/index.ts Normal file
View File

@@ -0,0 +1,15 @@
import plugin from "tailwindcss/plugin";
import { platforms, nativePlatforms } from "../shared/platforms";
export default plugin(function ({ addVariant }) {
for (const platform of platforms) {
addVariant(platform, `@media ${platform}`);
}
addVariant(
"native",
nativePlatforms.map((platform) => `@media ${platform}`)
);
addVariant("web", "@media web-inline");
});

271
src/plugin/native.ts Normal file
View File

@@ -0,0 +1,271 @@
import plugin from "tailwindcss/plugin";
import { TailwindConfig } from "tailwindcss/tailwind-config";
export interface NativePluginOptions {
rem?: number;
}
export const nativePlugin = plugin.withOptions<NativePluginOptions | undefined>(
function () {
return ({ matchUtilities, theme }) => {
matchUtilities(
{
aspect: (value: string) => {
let aspectRatio = value;
if (value.includes("/")) {
const [left, right] = value.split("/").map((n) => {
return Number.parseInt(n, 10);
});
aspectRatio = `${left / right}`;
}
return {
aspectRatio,
};
},
},
{ values: theme("aspectRatio") }
);
};
},
function ({ rem = 16 } = {}) {
const config: Partial<TailwindConfig> = {
corePlugins: {
accentColor: false,
accessibility: false,
animation: false,
appearance: false,
aspectRatio: false,
backdropBlur: false,
backdropBrightness: false,
backdropContrast: false,
backdropFilter: false,
backdropGrayscale: false,
backdropHueRotate: false,
backdropInvert: false,
backdropOpacity: false,
backdropSaturate: false,
backdropSepia: false,
backgroundAttachment: false,
backgroundBlendMode: false,
backgroundClip: false,
backgroundImage: false,
backgroundOrigin: false,
backgroundPosition: false,
backgroundRepeat: false,
backgroundSize: false,
blur: false,
borderCollapse: false,
boxDecorationBreak: false,
boxShadow: false,
boxSizing: false,
breakAfter: false,
breakBefore: false,
breakInside: false,
brightness: false,
caretColor: false,
clear: false,
columns: false,
content: false,
contrast: false,
cursor: false,
divideColor: false,
divideOpacity: false,
divideStyle: false,
divideWidth: false,
dropShadow: false,
fill: false,
filter: false,
float: false,
fontSmoothing: false,
gap: false,
gradientColorStops: false,
grayscale: false,
gridAutoColumns: false,
gridAutoFlow: false,
gridAutoRows: false,
gridColumn: false,
gridColumnEnd: false,
gridColumnStart: false,
gridRow: false,
gridRowEnd: false,
gridRowStart: false,
gridTemplateColumns: false,
gridTemplateRows: false,
hueRotate: false,
invert: false,
isolation: false,
justifyItems: false,
justifySelf: false,
listStylePosition: false,
listStyleType: false,
mixBlendMode: false,
objectFit: false,
objectPosition: false,
order: false,
overscrollBehavior: false,
placeItems: false,
placeSelf: false,
placeholderColor: false,
placeholderOpacity: false,
preflight: false,
resize: false,
ringColor: false,
ringOffsetColor: false,
ringOffsetWidth: false,
ringOpacity: false,
ringWidth: false,
rotate: false,
saturate: false,
scale: false,
scrollBehavior: false,
scrollMargin: false,
scrollPadding: false,
scrollSnapAlign: false,
scrollSnapStop: false,
scrollSnapType: false,
sepia: false,
skew: false,
space: false,
stroke: false,
strokeWidth: false,
tableLayout: false,
textIndent: false,
textOverflow: false,
touchAction: false,
transform: false,
transformOrigin: false,
transitionDelay: false,
transitionDuration: false,
transitionProperty: false,
transitionTimingFunction: false,
translate: false,
userSelect: false,
verticalAlign: false,
visibility: false,
whitespace: false,
willChange: false,
wordBreak: false,
},
theme: {
aspectRatio: {
auto: "0",
square: "1",
video: "1.777777778",
},
letterSpacing: {
tighter: "-0.5px",
tight: "-0.25px",
normal: "0px",
wide: "0.25px",
wider: "0.5px",
widest: "1px",
},
spacing: {
px: "1px",
0: "0px",
0.5: `${rem * 0.125}px`,
1: `${rem * 0.25}px`,
1.5: `${rem * 0.375}px`,
2: `${rem * 0.5}px`,
2.5: `${rem * 0.625}px`,
3: `${rem * 0.75}px`,
3.5: `${rem * 0.875}px`,
4: `${rem * 1}px`,
5: `${rem * 1.25}px`,
6: `${rem * 1.5}px`,
7: `${rem * 1.75}px`,
8: `${rem * 2}px`,
9: `${rem * 2.25}px`,
10: `${rem * 2.5}px`,
11: `${rem * 2.75}px`,
12: `${rem * 3}px`,
14: `${rem * 3.5}px`,
16: `${rem * 4}px`,
20: `${rem * 5}px`,
24: `${rem * 6}px`,
28: `${rem * 7}px`,
32: `${rem * 8}px`,
36: `${rem * 9}px`,
40: `${rem * 10}px`,
44: `${rem * 11}px`,
48: `${rem * 12}px`,
52: `${rem * 13}px`,
56: `${rem * 14}px`,
60: `${rem * 15}px`,
64: `${rem * 16}px`,
72: `${rem * 18}px`,
80: `${rem * 20}px`,
96: `${rem * 24}px`,
},
borderRadius: {
none: "0px",
sm: `${rem * 0.125}px`,
DEFAULT: `${rem * 0.25}px`,
md: `${rem * 0.375}px`,
lg: `${rem * 0.5}px`,
xl: `${rem * 0.75}px`,
"2xl": `${rem * 1}px`,
"3xl": `${rem * 1.5}px`,
full: "9999px",
},
fontSize: {
xs: [`${rem * 0.75}px`, { lineHeight: `${rem * 1}px` }],
sm: [`${rem * 0.875}px`, { lineHeight: `${rem * 1.25}px` }],
base: [`${rem * 1}px`, { lineHeight: `${rem * 1.5}px` }],
lg: [`${rem * 1.125}px`, { lineHeight: `${rem * 1.75}px` }],
xl: [`${rem * 1.25}px`, { lineHeight: `${rem * 1.75}px` }],
"2xl": [`${rem * 1.5}px`, { lineHeight: `${rem * 2}px` }],
"3xl": [`${rem * 1.875}px`, { lineHeight: `${rem * 2.25}px` }],
"4xl": [`${rem * 2.25}px`, { lineHeight: `${rem * 2.5}px` }],
"5xl": [`${rem * 3}px`, { lineHeight: "1" }],
"6xl": [`${rem * 3.75}px`, { lineHeight: "1" }],
"7xl": [`${rem * 4.5}px`, { lineHeight: "1" }],
"8xl": [`${rem * 6}px`, { lineHeight: "1" }],
"9xl": [`${rem * 8}px`, { lineHeight: "1" }],
},
lineHeight: {
none: "1",
tight: "1.25",
snug: "1.375",
normal: "1.5",
relaxed: "1.625",
loose: "2",
3: `${rem * 0.75}px`,
4: `${rem * 1}px`,
5: `${rem * 1.25}px`,
6: `${rem * 1.5}px`,
7: `${rem * 1.75}px`,
8: `${rem * 2}px`,
9: `${rem * 2.25}px`,
10: `${rem * 2.5}px`,
},
maxWidth: ({ theme, breakpoints }) => ({
none: "none",
0: `${rem * 0}px`,
xs: `${rem * 20}px`,
sm: `${rem * 24}px`,
md: `${rem * 28}px`,
lg: `${rem * 32}px`,
xl: `${rem * 36}px`,
"2xl": `${rem * 42}px`,
"3xl": `${rem * 48}px`,
"4xl": `${rem * 56}px`,
"5xl": `${rem * 64}px`,
"6xl": `${rem * 72}px`,
"7xl": `${rem * 80}px`,
full: "100%",
min: "min-content",
max: "max-content",
fit: "fit-content",
prose: "65ch",
...breakpoints(theme("screens")),
}),
},
};
return config;
}
);

21
src/shared/platforms.ts Normal file
View File

@@ -0,0 +1,21 @@
import { Platform } from "react-native";
export type Platform = typeof Platform.OS | "native" | "web-inline";
export const platforms: Platform[] = [
"android",
"ios",
"web",
"native",
"windows",
"macos",
"web-inline",
];
export const nativePlatforms: Platform[] = [
"native",
"android",
"ios",
"windows",
"macos",
];