11import deepmerge from "@fastify/deepmerge" ;
2+ import { createVar , fallbackVar } from "@vanilla-extract/css" ;
23import { addFunctionSerializer } from "@vanilla-extract/css/functionSerializer" ;
34import { setFileScope } from "@vanilla-extract/css/fileScope" ;
4- import type { ComplexCSSRule } from "@mincho-js/transform-to-vanilla" ;
5+ import type {
6+ ComplexCSSRule ,
7+ CSSRule ,
8+ PureCSSVarKey
9+ } from "@mincho-js/transform-to-vanilla" ;
510
611import { css , cssVariants } from "../css" ;
7- import { className } from "../utils" ;
12+ import { className , getVarName } from "../utils" ;
813import { createRuntimeFn } from "./createRuntimeFn" ;
914import type {
1015 PatternOptions ,
@@ -15,7 +20,10 @@ import type {
1520 VariantSelection ,
1621 VariantObjectSelection ,
1722 ComplexPropDefinitions ,
23+ PropDefinition ,
24+ PropDefinitionOutput ,
1825 PropTarget ,
26+ PropVars ,
1927 ConditionalVariants ,
2028 Serializable
2129} from "./types" ;
@@ -34,25 +42,48 @@ export function rules<
3442> (
3543 options : PatternOptions < Variants , ToggleVariants , Props > ,
3644 debugId ?: string
37- ) : RuntimeFn < ConditionalVariants < Variants , ToggleVariants > > {
45+ ) : RuntimeFn <
46+ ConditionalVariants < Variants , ToggleVariants > ,
47+ Exclude < Props , undefined >
48+ > {
3849 const {
3950 toggles = { } ,
4051 variants = { } ,
4152 defaultVariants = { } ,
4253 compoundVariants = [ ] ,
54+ props = { } ,
4355 base,
4456 ...baseStyles
4557 } = options ;
4658
59+ type PureProps = Exclude < Props , undefined > ;
60+ const propVars = { } as PropVars < PureProps > ;
61+ const propStyles : CSSRule = { } ;
62+ if ( Array . isArray ( props ) ) {
63+ for ( const prop of props ) {
64+ if ( typeof prop === "string" ) {
65+ const propVar = createVar ( `${ debugId } _${ prop } ` ) ;
66+ propVars [ prop as keyof PropDefinitionOutput < PureProps > ] =
67+ getVarName ( propVar ) ;
68+ // @ts -expect-error Expression produces a union type that is too complex to represent.ts(2590)
69+ propStyles [ prop ] = propVar ;
70+ } else {
71+ processPropObject ( prop , propVars , propStyles , debugId ) ;
72+ }
73+ }
74+ } else {
75+ processPropObject ( props , propVars , propStyles , debugId ) ;
76+ }
77+
4778 let defaultClassName : string ;
4879 if ( ! base || typeof base === "string" ) {
49- const baseClassName = css ( baseStyles , debugId ) ;
80+ const baseClassName = css ( [ baseStyles , propStyles ] , debugId ) ;
5081 defaultClassName = base ? `${ baseClassName } ${ base } ` : baseClassName ;
5182 } else {
5283 defaultClassName = css (
5384 Array . isArray ( base )
54- ? [ baseStyles , ...base ]
55- : mergeObject ( baseStyles , base ) ,
85+ ? [ baseStyles , ...base , propStyles ]
86+ : [ mergeObject ( baseStyles , base ) , propStyles ] ,
5687 debugId
5788 ) ;
5889 }
@@ -92,18 +123,20 @@ export function rules<
92123 ] ) ;
93124 }
94125
95- const config : PatternResult < CombinedVariants > = {
126+ const config : PatternResult < CombinedVariants , PureProps > = {
96127 defaultClassName,
97128 variantClassNames,
98129 defaultVariants : transformVariantSelection ( defaultVariants ) ,
99- compoundVariants : compounds
130+ compoundVariants : compounds ,
131+ propVars
100132 } ;
101133
102134 return addFunctionSerializer <
103- RuntimeFn < ConditionalVariants < Variants , ToggleVariants > >
135+ RuntimeFn < ConditionalVariants < Variants , ToggleVariants > , PureProps >
104136 > (
105137 createRuntimeFn ( config ) as RuntimeFn <
106- ConditionalVariants < Variants , ToggleVariants >
138+ ConditionalVariants < Variants , ToggleVariants > ,
139+ PureProps
107140 > ,
108141 {
109142 importPath : "@mincho-js/css/rules/createRuntimeFn" ,
@@ -114,6 +147,25 @@ export function rules<
114147}
115148export const recipe = rules ;
116149
150+ function processPropObject < Target extends PropTarget > (
151+ props : PropDefinition < Target > ,
152+ propVars : Record < string , PureCSSVarKey > ,
153+ propStyles : CSSRule ,
154+ debugId ?: string
155+ ) {
156+ Object . entries ( props ) . forEach ( ( [ propName , propValue ] ) => {
157+ const propVar = createVar ( `${ debugId } _${ propName } ` ) ;
158+ propVars [ propName ] = getVarName ( propVar ) ;
159+
160+ const isBaseValue = propValue ?. base !== undefined ;
161+ propValue ?. targets . forEach ( ( target ) => {
162+ propStyles [ target ] = isBaseValue
163+ ? fallbackVar ( propVar , `${ propValue . base } ` )
164+ : propVar ;
165+ } ) ;
166+ } ) ;
167+ }
168+
117169// == Tests ====================================================================
118170// Ignore errors when compiling to CommonJS.
119171// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -131,8 +183,9 @@ if (import.meta.vitest) {
131183 const result = rules ( { base : { color : "red" } } , debugId ) ;
132184
133185 assert . isFunction ( result ) ;
134- assert . hasAllKeys ( result , [ "variants" , "classNames" ] ) ;
186+ assert . hasAllKeys ( result , [ "props" , " variants", "classNames" ] ) ;
135187 assert . hasAllKeys ( result . classNames , [ "base" , "variants" ] ) ;
188+ assert . isFunction ( result . props ) ;
136189
137190 expect ( result ( ) ) . toMatch ( className ( debugId ) ) ;
138191 expect ( result . classNames . base ) . toMatch ( className ( debugId ) ) ;
@@ -144,8 +197,9 @@ if (import.meta.vitest) {
144197 const result = rules ( { color : "red" } , debugId ) ;
145198
146199 assert . isFunction ( result ) ;
147- assert . hasAllKeys ( result , [ "variants" , "classNames" ] ) ;
200+ assert . hasAllKeys ( result , [ "props" , " variants", "classNames" ] ) ;
148201 assert . hasAllKeys ( result . classNames , [ "base" , "variants" ] ) ;
202+ assert . isFunction ( result . props ) ;
149203
150204 expect ( result ( ) ) . toMatch ( className ( debugId ) ) ;
151205 expect ( result . classNames . base ) . toMatch ( className ( debugId ) ) ;
@@ -177,8 +231,9 @@ if (import.meta.vitest) {
177231
178232 // Base check
179233 assert . isFunction ( result ) ;
180- assert . hasAllKeys ( result , [ "variants" , "classNames" ] ) ;
234+ assert . hasAllKeys ( result , [ "props" , " variants", "classNames" ] ) ;
181235 assert . hasAllKeys ( result . classNames , [ "base" , "variants" ] ) ;
236+ assert . isFunction ( result . props ) ;
182237
183238 expect ( result ( ) ) . toMatch ( className ( debugId ) ) ;
184239 expect ( result . classNames . base ) . toMatch ( className ( debugId ) ) ;
@@ -273,8 +328,9 @@ if (import.meta.vitest) {
273328
274329 // Base check
275330 assert . isFunction ( result ) ;
276- assert . hasAllKeys ( result , [ "variants" , "classNames" ] ) ;
331+ assert . hasAllKeys ( result , [ "props" , " variants", "classNames" ] ) ;
277332 assert . hasAllKeys ( result . classNames , [ "base" , "variants" ] ) ;
333+ assert . isFunction ( result . props ) ;
278334
279335 expect ( result ( ) ) . toMatch ( className ( debugId ) ) ;
280336 expect ( result . classNames . base ) . toMatch ( className ( debugId ) ) ;
@@ -337,8 +393,9 @@ if (import.meta.vitest) {
337393
338394 // Base check
339395 assert . isFunction ( result ) ;
340- assert . hasAllKeys ( result , [ "variants" , "classNames" ] ) ;
396+ assert . hasAllKeys ( result , [ "props" , " variants", "classNames" ] ) ;
341397 assert . hasAllKeys ( result . classNames , [ "base" , "variants" ] ) ;
398+ assert . isFunction ( result . props ) ;
342399
343400 expect ( result ( ) ) . toMatch ( className ( debugId , `${ debugId } _disabled_true` ) ) ;
344401 expect ( result . classNames . base ) . toMatch ( className ( debugId ) ) ;
@@ -438,8 +495,9 @@ if (import.meta.vitest) {
438495
439496 // Base check
440497 assert . isFunction ( result ) ;
441- assert . hasAllKeys ( result , [ "variants" , "classNames" ] ) ;
498+ assert . hasAllKeys ( result , [ "props" , " variants", "classNames" ] ) ;
442499 assert . hasAllKeys ( result . classNames , [ "base" , "variants" ] ) ;
500+ assert . isFunction ( result . props ) ;
443501
444502 expect ( result ( ) ) . toMatch ( className ( debugId ) ) ;
445503 expect ( result . classNames . base ) . toMatch ( className ( debugId ) ) ;
@@ -543,5 +601,118 @@ if (import.meta.vitest) {
543601 className ( debugId , `${ debugId } _outlined_true` )
544602 ) ;
545603 } ) ;
604+
605+ it ( "Props" , ( ) => {
606+ const result1 = rules (
607+ {
608+ props : [ "color" , "background" ]
609+ } ,
610+ debugId
611+ ) ;
612+
613+ assert . isFunction ( result1 ) ;
614+ assert . hasAllKeys ( result1 , [ "props" , "variants" , "classNames" ] ) ;
615+ assert . hasAllKeys ( result1 . classNames , [ "base" , "variants" ] ) ;
616+ assert . isFunction ( result1 . props ) ;
617+
618+ Object . entries (
619+ result1 . props ( {
620+ color : "red"
621+ } )
622+ ) . forEach ( ( [ varName , propValue ] ) => {
623+ // Partial
624+ expect ( propValue ) . toBe ( "red" ) ;
625+ expect ( varName ) . toMatch ( className ( `--${ debugId } _color` ) ) ;
626+ } ) ;
627+ Object . entries (
628+ result1 . props ( {
629+ color : "red" ,
630+ background : "blue"
631+ } )
632+ ) . forEach ( ( [ varName , propValue ] ) => {
633+ // Fully
634+ expect ( propValue ) . toBeOneOf ( [ "red" , "blue" ] ) ;
635+
636+ if ( propValue === "red" ) {
637+ expect ( varName ) . toMatch ( className ( `--${ debugId } _color` ) ) ;
638+ }
639+ if ( propValue === "blue" ) {
640+ expect ( varName ) . toMatch ( className ( `--${ debugId } _background` ) ) ;
641+ }
642+ } ) ;
643+ Object . entries (
644+ result1 . props ( {
645+ // @ts -expect-error Not valid property
646+ "something-else" : "red"
647+ } )
648+ ) . forEach ( ( [ varName , propValue ] ) => {
649+ expect ( varName ) . toBeUndefined ( ) ;
650+ expect ( propValue ) . toBeUndefined ( ) ;
651+ } ) ;
652+
653+ const result2 = rules (
654+ {
655+ props : {
656+ rounded : { targets : [ "borderRadius" ] } ,
657+ size : { base : 0 , targets : [ "padding" , "margin" ] }
658+ }
659+ } ,
660+ debugId
661+ ) ;
662+ Object . entries (
663+ result2 . props ( {
664+ rounded : "999px" ,
665+ size : "2rem"
666+ } )
667+ ) . forEach ( ( [ varName , propValue ] ) => {
668+ // Fully
669+ expect ( propValue ) . toBeOneOf ( [ "999px" , "2rem" ] ) ;
670+
671+ if ( propValue === "999px" ) {
672+ expect ( varName ) . toMatch ( className ( `--${ debugId } _rounded` ) ) ;
673+ }
674+ if ( propValue === "2rem" ) {
675+ expect ( varName ) . toMatch ( className ( `--${ debugId } _size` ) ) ;
676+ }
677+ } ) ;
678+
679+ const result3 = rules (
680+ {
681+ props : [
682+ "color" ,
683+ "background" ,
684+ {
685+ rounded : { targets : [ "borderRadius" ] } ,
686+ size : { base : 0 , targets : [ "padding" , "margin" ] }
687+ }
688+ ]
689+ } ,
690+ debugId
691+ ) ;
692+ Object . entries (
693+ result3 . props ( {
694+ color : "red" ,
695+ background : "blue" ,
696+ rounded : "999px" ,
697+ size : "2rem"
698+ } )
699+ ) . forEach ( ( [ varName , propValue ] ) => {
700+ // Fully
701+ expect ( propValue ) . toBeOneOf ( [ "red" , "blue" , "999px" , "2rem" ] ) ;
702+
703+ if ( propValue === "red" ) {
704+ expect ( varName ) . toMatch ( className ( `--${ debugId } _color` ) ) ;
705+ }
706+ if ( propValue === "blue" ) {
707+ expect ( varName ) . toMatch ( className ( `--${ debugId } _background` ) ) ;
708+ }
709+ if ( propValue === "999px" ) {
710+ expect ( varName ) . toMatch ( className ( `--${ debugId } _rounded` ) ) ;
711+ }
712+ if ( propValue === "2rem" ) {
713+ expect ( varName ) . toMatch ( className ( `--${ debugId } _size` ) ) ;
714+ }
715+ } ) ;
716+ } ) ;
546717 } ) ;
547718}
0 commit comments