// cSpell:ignore borderless import { AtRule } from "postcss"; import { Config } from "tailwindcss"; import flattenColorPalette from "tailwindcss/lib/util/flattenColorPalette"; import toColorValue from "tailwindcss/lib/util/toColorValue"; import plugin from "tailwindcss/plugin"; import { PluginUtils } from "tailwindcss/types/config"; import { hairlineWidth, platformSelect } from "../theme"; import { color } from "./color"; import { allowedColors } from "./common"; import { darkModeAtRule } from "./dark-mode"; import { safeArea } from "./safe-area"; import { shadows } from "./shadows"; import { nativeSwitch } from "./switch"; import { translateX, translateY } from "./translate"; import { verify } from "./verify"; const kebabCase = (str: string) => { return str.replace( /[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? "-" : "") + $.toLowerCase(), ); }; const nativePlugins = plugin(function ({ addBase, addUtilities, addVariant, config, matchUtilities, matchVariant, theme, ...other }) { const nativePlatforms = ["android", "ios", "windows", "macos"]; // RN requires the unit, so reset the defaults // This is an undocumented feature of Tailwind that allows you to modify the @default layer (other as any).addDefaults("transform", { "--tw-rotate": "0deg", "--tw-skew-x": "0deg", "--tw-skew-y": "0deg", }); /** * `display-mode` is a valid media query, but the ${platform} values are not. * * We need to either add a new Media Condition or hijack an existing one, * display-mode seems good enough? * * https://developer.mozilla.org/en-US/docs/Web/CSS/@media/display-mode */ for (const platform of nativePlatforms) { addVariant(platform, `@media (display-mode: ${platform})`); } addVariant( "native", nativePlatforms.map((platform) => `@media (display-mode: ${platform})`), ); /** * The native module requires the `.dark` selector to pickup darkMode variables * when using darkMode: 'class' * * If the user never uses the word 'dark' the selector will never be processed * This is an edge case, but one we often encounter in testing (where .dark) * will only contain CSS variables and never referenced directly */ config("safelist").push("dark"); /** * Change the visible/invisible classes * */ addUtilities({ ".visible": { opacity: 1 }, ".invisible": { opacity: 0 }, } as any); /** * move-[]: */ matchVariant( "{}", (value = "", { modifier, container }: any) => { value = kebabCase(value.replace(/&/, "\\&").replaceAll(".", "\\.")); if (modifier) { modifier = modifier.replace(/&/, "\\&").replaceAll(".", "\\."); } if (modifier && !value) { value = modifier; modifier = undefined; } container.walkRules((rule: any) => { rule.append( new AtRule({ name: "rn-move", params: `${modifier ?? "\\*"} ${value}`, }), ); }); return `&`; }, { values: { DEFAULT: undefined } }, ); /** * Native Prop remapping */ addVariant("selection", (({ container }: any) => { container.walkRules((rule: any) => { rule.append( new AtRule({ name: "rn-move", params: "color selectionColor" }), ); }); return "&"; }) as any); addVariant("placeholder", (({ container }: any) => { container.walkRules((rule: any) => { rule.append( new AtRule({ name: "rn-move", params: "color placeholderTextColor" }), ); }); return "&"; }) as any); // https://github.com/tailwindlabs/tailwindcss/blob/eb2fe9494b638c3b67194f3ddf3c040f064e060d/src/corePlugins.js#L624-L629 addUtilities({ ".pointer-events-none": { "pointer-events": "none" }, ".pointer-events-auto": { "pointer-events": "auto" }, ".pointer-events-box-none": { "pointer-events": "box-none" }, ".pointer-events-box-only": { "pointer-events": "box-only" }, }); // https://github.com/tailwindlabs/tailwindcss/blob/eb2fe9494b638c3b67194f3ddf3c040f064e060d/src/corePlugins.js#L729C3-L750C5 matchUtilities( { "line-clamp": (value) => ({ "&": { "@rn-move -rn-number-of-lines number-of-lines": "true", overflow: "hidden", "-rn-number-of-lines": value, }, }), }, { values: theme("lineClamp") }, ); matchUtilities( { elevation: (value) => ({ "-rn-elevation": value, }), }, { values: theme("elevation") }, ); addUtilities({ ".line-clamp-none": { "&": { "@rn-move -rn-number-of-lines number-of-lines": "true", overflow: "visible", "-rn-number-of-lines": "0", }, }, }); /** * https://reactnative.dev/docs/pressable#rippleconfig */ matchUtilities( { ripple: (value) => { return { "&": { "@rn-move color android_ripple\\.color": "true", color: toColorValue(value), }, }; }, }, { values: flattenColorPalette(theme("rippleColor")), type: ["color", "any"], }, ); matchUtilities( { ripple: (value) => { return { "&": { "@rn-move -rn-borderless android_ripple\\.borderless": "true", "-rn-borderless": value, }, }; }, }, { values: { borderless: "true", bordered: "false", }, type: "any", }, ); matchUtilities( { caret: (value) => { return { "&": { "@rn-move caret-color cursor-color": "true", "caret-color": toColorValue(value), }, }; }, }, { values: flattenColorPalette(theme("caretColor")), type: ["color", "any"], }, ); /** * SVG Prop remapping */ matchUtilities( { fill: (value) => { return { "&": { "@rn-move fill": "true", fill: `${toColorValue(value)}`, }, }; }, }, { values: flattenColorPalette(theme("fill")), type: ["color", "any"] }, ); matchUtilities( { stroke: (value) => ({ "&": { "@rn-move stroke": "true", stroke: toColorValue(value), }, }), }, { values: flattenColorPalette(theme("stroke")), type: ["color", "any"] }, ); matchUtilities( { stroke: (value) => ({ "&": { "@rn-move stroke-width": "true", strokeWidth: toColorValue(value), }, }), }, { values: theme("strokeWidth"), type: ["length", "number", "percentage"] }, ); }); const preset: Config = { content: [], theme: { extend: { fontFamily: { sans: platformSelect({ android: "san-serif", ios: "'system font'" }), serif: platformSelect({ android: "serif", ios: "Georgia" }), mono: platformSelect({ android: "mono", ios: "'Courier New'" }), }, elevation: { sm: "1", DEFAULT: "3", md: "6", lg: "8", xl: "13", "2xl": "24", none: "0", }, boxShadow: { sm: " 0px 1px 1px rgba(0, 0, 0, 0.35)", DEFAULT: "0px 1px 4px rgba(0, 0, 0, 0.35)", md: "0px 3px 10px rgba(0, 0, 0, 0.35)", lg: "0px 4px 10px rgba(0, 0, 0, 0.35)", xl: "0px 6px 19px rgba(0, 0, 0, 0.35)", "2xl": "0px 12px 38px rgba(0, 0, 0, 0.35) ", none: "0 0 #0000", }, translateX: ({ theme }: PluginUtils) => ({ ...theme("spacing"), "1/2": "50rnw", "1/3": "33.333333rnw", "2/3": "66.666667rnw", "1/4": "25rnw", "2/4": "50rnw", "3/4": "75rnw", full: "100rnw", }), translateY: ({ theme }: PluginUtils) => ({ ...theme("spacing"), "1/2": "50rnh", "1/3": "33.333333rnh", "2/3": "66.666667rnh", "1/4": "25rnh", "2/4": "50rnh", "3/4": "75rnh", full: "100rnh", }), borderWidth: { hairline: hairlineWidth(), }, letterSpacing: { tighter: "-0.5px", tight: "-0.25px", normal: "0px", wide: "0.25px", wider: "0.5px", widest: "1px", }, transitionProperty: { DEFAULT: "color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, perspective, rotate, rotateX, rotateY, rotateZ, scale, scaleX, scaleY, translateX, translateY, skewX, skewY, filter, backdrop-filter", all: "background-color, border-bottom-color, border-bottom-left-radius, border-bottom-right-radius, border-bottom-width, border-color, border-left-color, border-left-width, border-radius, border-right-color, border-right-width, border-top-color, border-top-width, border-width, color, fill, font-size, font-weight, gap, letter-spacing, line-height, margin, margin-bottom, margin-left, margin-right, margin-top, object-position, opacity, order, padding, padding-bottom, padding-left, padding-right, padding-top, rotate, scale, stroke, text-decoration, text-decoration-color, perspective, rotate, rotateX, rotateY, rotateZ, scale, scaleX, scaleY, translateX, translateY, skewX, skewY, transform-origin, translate, vertical-align, visibility, word-spacing, z-index", transform: "perspective, rotate, rotateX, rotateY, rotateZ, scale, scaleX, scaleY, translateX, translateY, skewX, skewY", }, trackColor: allowedColors, thumbColor: allowedColors, rippleColor: allowedColors, }, }, plugins: [ safeArea, color, darkModeAtRule, shadows, translateX, translateY, verify, nativePlugins, nativeSwitch, ], corePlugins: { preflight: false, backgroundOpacity: false, borderOpacity: false, boxShadow: false, caretColor: false, divideOpacity: false, fill: false, placeholderColor: false, placeholderOpacity: false, lineClamp: false, ringOpacity: false, stroke: false, strokeWidth: false, textOpacity: false, translate: false, pointerEvents: false, visibility: false, }, }; export default preset;