diff --git a/package-lock.json b/package-lock.json index d175a23..e1d92f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14632,6 +14632,22 @@ "node": ">=0.10.0" } }, + "node_modules/class-variance-authority": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.2.4.tgz", + "integrity": "sha512-JJKn9ESARiNEBBRdTSPB9/SwaPb+wi19DMX/r8BVSyp1dxHa3JyUxa2GQjEVag5rBi+O3pL64UZ0XhnQsMz+3w==", + "funding": { + "url": "https://joebell.co.uk" + }, + "peerDependencies": { + "typescript": ">= 4.5.5 < 5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/clean-css": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", @@ -34808,6 +34824,7 @@ "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/types": "7.19.4", + "class-variance-authority": "^0.2.4", "css-tree": "^2.2.1", "find-cache-dir": "^3.3.2", "micromatch": "^4.0.5", @@ -45978,6 +45995,11 @@ } } }, + "class-variance-authority": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.2.4.tgz", + "integrity": "sha512-JJKn9ESARiNEBBRdTSPB9/SwaPb+wi19DMX/r8BVSyp1dxHa3JyUxa2GQjEVag5rBi+O3pL64UZ0XhnQsMz+3w==" + }, "clean-css": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", @@ -54271,6 +54293,7 @@ "@types/react-native": "0.70.6", "@types/use-sync-external-store": "0.0.3", "babel-plugin-tester": "10.1.0", + "class-variance-authority": "*", "css-tree": "^2.2.1", "find-cache-dir": "^3.3.2", "jest": "29.2.1", @@ -60555,7 +60578,7 @@ "clsx": "1.2.1", "dedent": "0.7.0", "docusaurus-plugin-sass": "0.2.2", - "front-matter": "*", + "front-matter": "^4.0.2", "lodash.debounce": "4.0.8", "nativewind": "^3.0.0-65eddcf.0", "prism-react-renderer": "1.3.3", diff --git a/packages/nativewind/package.json b/packages/nativewind/package.json index 588ec54..9794fe2 100644 --- a/packages/nativewind/package.json +++ b/packages/nativewind/package.json @@ -50,8 +50,9 @@ "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/types": "7.19.4", - "find-cache-dir": "^3.3.2", + "class-variance-authority": "^0.2.4", "css-tree": "^2.2.1", + "find-cache-dir": "^3.3.2", "micromatch": "^4.0.5", "react-is": "^18.1.0", "use-sync-external-store": "^1.1.0" diff --git a/packages/nativewind/src/styled/cva.ts b/packages/nativewind/src/styled/cva.ts new file mode 100644 index 0000000..b8f1598 --- /dev/null +++ b/packages/nativewind/src/styled/cva.ts @@ -0,0 +1,19 @@ +import type { + StringToBoolean, + ClassValue, + ClassProp, +} from "class-variance-authority/dist/types"; + +type ConfigSchema = Record>; +type ConfigVariants = { + [Variant in keyof T]?: StringToBoolean | null; +}; +export type CVAConfig = T extends ConfigSchema + ? { + variants?: T; + defaultVariants?: ConfigVariants; + compoundVariants?: (T extends ConfigSchema + ? ConfigVariants & ClassProp + : ClassProp)[]; + } + : never; diff --git a/packages/nativewind/src/styled/index.ts b/packages/nativewind/src/styled/index.ts index b7ae9e9..d617ec3 100644 --- a/packages/nativewind/src/styled/index.ts +++ b/packages/nativewind/src/styled/index.ts @@ -1,5 +1,6 @@ import type { ComponentType } from "react"; import type { Style } from "../transform-css/types"; +import { CVAConfig } from "./cva"; export type PropsWithClassName = T & { className?: string; @@ -8,11 +9,11 @@ export type PropsWithClassName = T & { baseTw?: string; }; -export interface StyledOptions { +export type StyledOptions = CVAConfig & { props?: Partial>; classProps?: (keyof T & string)[]; baseClassName?: string; -} +}; /** * Default diff --git a/packages/nativewind/src/styled/native/index.tsx b/packages/nativewind/src/styled/native/index.tsx index a118667..faf680d 100644 --- a/packages/nativewind/src/styled/native/index.tsx +++ b/packages/nativewind/src/styled/native/index.tsx @@ -25,6 +25,7 @@ import { getStyleSet, subscribeToStyleSheet, } from "../../style-sheet/native/runtime"; +import { cva } from "class-variance-authority"; const stateInheritanceContent = createContext({}); @@ -33,30 +34,42 @@ export function styled( styledBaseClassNameOrOptions?: | string | StyledOptions, string>, - maybeOptions: StyledOptions, string> = {} + maybeOptions: StyledOptions = {} ) { - const { props: propsToTransform, classProps } = - typeof styledBaseClassNameOrOptions === "object" - ? styledBaseClassNameOrOptions - : maybeOptions; + const { + classProps, + baseClassName = "", + props: propsToTransform, + ...cvaOptions + } = typeof styledBaseClassNameOrOptions === "object" + ? styledBaseClassNameOrOptions + : maybeOptions; - const baseClassName = + const defaultClassName = typeof styledBaseClassNameOrOptions === "string" ? styledBaseClassNameOrOptions - : maybeOptions?.baseClassName; + : baseClassName; - const Styled = forwardRef((props, ref) => { - return ( - - ); - }); + const classGenerator = cva(defaultClassName, cvaOptions); + + const Styled = forwardRef( + ({ className, tw, ...props }, ref) => { + const generatedClassName = classGenerator({ + class: tw ?? className, + ...props, + }); + + return ( + + ); + } + ); if (typeof component !== "string") { Styled.displayName = `NativeWind.${ component.displayName || component.name || "NoName" @@ -69,8 +82,7 @@ export function styled( export const StyledComponent = forwardRef(function NativeWindStyledComponent( { component: Component, - baseClassName, - tw: twClassName, + tw, className: propClassName, propsToTransform, classProps, @@ -89,15 +101,11 @@ export const StyledComponent = forwardRef(function NativeWindStyledComponent( */ const [componentState, componentStateDispatch] = useComponentState(); - const classNameWithDefaults = [baseClassName, twClassName ?? propClassName] - .filter(Boolean) - .join(" "); - /** * Resolve the props/classProps/spreadProps options */ const { styledProps, className } = withStyledProps({ - className: classNameWithDefaults, + className: tw ?? propClassName, propsToTransform, classProps, componentState, diff --git a/packages/nativewind/src/styled/web/index.tsx b/packages/nativewind/src/styled/web/index.tsx index f491909..26a7bc7 100644 --- a/packages/nativewind/src/styled/web/index.tsx +++ b/packages/nativewind/src/styled/web/index.tsx @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ComponentType, ForwardedRef, forwardRef, useMemo } from "react"; import { StyleProp } from "react-native"; +import { cva } from "class-variance-authority"; import { Style } from "../../transform-css/types"; import type { PropsWithClassName, StyledOptions } from "../index"; @@ -10,17 +11,19 @@ export function styled( ref: ForwardedRef; }>, styledBaseClassNameOrOptions?: string | StyledOptions, - maybeOptions: StyledOptions = {} + maybeOptions: StyledOptions = {} ) { - const { classProps } = + const { classProps, baseClassName, props, ...cvaOptions } = typeof styledBaseClassNameOrOptions === "object" ? styledBaseClassNameOrOptions : maybeOptions; - const baseClassName = + const defaultClassName = typeof styledBaseClassNameOrOptions === "string" ? styledBaseClassNameOrOptions - : maybeOptions?.baseClassName; + : baseClassName; + + const classGenerator = cva(`${classProps} ${defaultClassName} `, cvaOptions); return forwardRef( ( @@ -32,17 +35,19 @@ export function styled( }: PropsWithClassName<{ style: Style }>, ref ) => { - let actualClassName = tw ?? className; - - if (classProps) actualClassName = `${classProps} ${actualClassName}`; - if (baseClassName) - actualClassName = `${baseClassName} ${actualClassName}`; + const generatedClassName = classGenerator({ + class: tw ?? className, + ...props, + }); const style = useMemo(() => { - return actualClassName - ? [{ $$css: true, tailwind: actualClassName } as Style, inlineStyle] + return generatedClassName + ? [ + { $$css: true, tailwind: generatedClassName } as Style, + inlineStyle, + ] : inlineStyle; - }, [inlineStyle, actualClassName]); + }, [inlineStyle, generatedClassName]); return ; }