Skip to content

Commit c663159

Browse files
authored
feat(style-context): implement recipe style context (#3317)
* chore: implement * chore: update test snap * chore: add use-client for react * fix: codegen test * refactor: update * feat: add forward props to factory * chore: support jsx options * chore: add example
1 parent c064485 commit c663159

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1500
-104
lines changed

.changeset/tame-ligers-collect.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,58 @@
1919
'@pandacss/studio': major
2020
'@pandacss/token-dictionary': major
2121
'@pandacss/types': major
22-
'docusaurus-ts': major
2322
---
2423

2524
Stable release of PandaCSS
25+
26+
### Style Context
27+
28+
Add `createStyleContext` function to framework artifacts for React, Preact, Solid, and Vue frameworks
29+
30+
```tsx
31+
import { sva } from 'styled-system/css'
32+
import { createStyleContext } from 'styled-system/jsx'
33+
34+
const card = sva({
35+
slots: ['root', 'label'],
36+
base: {
37+
root: {
38+
color: 'red',
39+
bg: 'red.300',
40+
},
41+
label: {
42+
fontWeight: 'medium',
43+
},
44+
},
45+
variants: {
46+
size: {
47+
sm: {
48+
root: {
49+
padding: '10px',
50+
},
51+
},
52+
md: {
53+
root: {
54+
padding: '20px',
55+
},
56+
},
57+
},
58+
},
59+
defaultVariants: {
60+
size: 'sm',
61+
},
62+
})
63+
64+
const { withProvider, withContext } = createStyleContext(card)
65+
66+
const CardRoot = withProvider('div', 'root')
67+
const CardLabel = withContext('label', 'label')
68+
```
69+
70+
Then, use like this:
71+
72+
```tsx
73+
<CardRoot size="sm">
74+
<CardLabel>Hello</CardLabel>
75+
</CardRoot>
76+
```

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"typescript.tsdk": "node_modules/typescript/lib",
3-
"eslint.workingDirectories": ["./packages/**", "./sandbox/**", "./sandbox/next-js"]
3+
"eslint.workingDirectories": ["./packages/**", "./sandbox/**", "./sandbox/next-js"],
4+
"biome.enabled": false
45
}

packages/config/src/config-deps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const artifactConfigDeps: Record<ArtifactId, ConfigPath[]> = {
6161
'jsx-helpers': jsx,
6262
'jsx-patterns': jsx.concat('patterns'),
6363
'jsx-patterns-index': jsx.concat('patterns'),
64+
'jsx-create-style-context': jsx,
6465
'css-index': ['syntax'],
6566
'package.json': ['forceConsistentTypeExtension', 'outExtension'],
6667
'types-styles': ['shorthands'],

packages/generator/__tests__/generate-recipe.test.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,14 @@ describe('generate recipes', () => {
120120
[key in keyof TextStyleVariant]: Array<TextStyleVariant[key]>
121121
}
122122
123+
124+
123125
export type TextStyleVariantProps = {
124126
[key in keyof TextStyleVariant]?: ConditionalValue<TextStyleVariant[key]> | undefined
125127
}
126128
127129
export interface TextStyleRecipe {
130+
128131
__type: TextStyleVariantProps
129132
(props?: TextStyleVariantProps): string
130133
raw: (props?: TextStyleVariantProps) => TextStyleVariantProps
@@ -179,11 +182,14 @@ describe('generate recipes', () => {
179182
[key in keyof TooltipStyleVariant]: Array<TooltipStyleVariant[key]>
180183
}
181184
185+
186+
182187
export type TooltipStyleVariantProps = {
183188
[key in keyof TooltipStyleVariant]?: ConditionalValue<TooltipStyleVariant[key]> | undefined
184189
}
185190
186191
export interface TooltipStyleRecipe {
192+
187193
__type: TooltipStyleVariantProps
188194
(props?: TooltipStyleVariantProps): string
189195
raw: (props?: TooltipStyleVariantProps) => TooltipStyleVariantProps
@@ -233,11 +239,14 @@ describe('generate recipes', () => {
233239
[key in keyof CardStyleVariant]: Array<CardStyleVariant[key]>
234240
}
235241
242+
243+
236244
export type CardStyleVariantProps = {
237245
[key in keyof CardStyleVariant]?: ConditionalValue<CardStyleVariant[key]> | undefined
238246
}
239247
240248
export interface CardStyleRecipe {
249+
241250
__type: CardStyleVariantProps
242251
(props?: CardStyleVariantProps): string
243252
raw: (props?: CardStyleVariantProps) => CardStyleVariantProps
@@ -298,11 +307,14 @@ describe('generate recipes', () => {
298307
[key in keyof ButtonStyleVariant]: Array<ButtonStyleVariant[key]>
299308
}
300309
310+
311+
301312
export type ButtonStyleVariantProps = {
302313
[key in keyof ButtonStyleVariant]?: ConditionalValue<ButtonStyleVariant[key]> | undefined
303314
}
304315
305316
export interface ButtonStyleRecipe {
317+
306318
__type: ButtonStyleVariantProps
307319
(props?: ButtonStyleVariantProps): string
308320
raw: (props?: ButtonStyleVariantProps) => ButtonStyleVariantProps
@@ -367,13 +379,16 @@ describe('generate recipes', () => {
367379
[key in keyof CheckboxVariant]: Array<CheckboxVariant[key]>
368380
}
369381
382+
type CheckboxSlot = "root" | "control" | "label"
383+
370384
export type CheckboxVariantProps = {
371385
[key in keyof CheckboxVariant]?: ConditionalValue<CheckboxVariant[key]> | undefined
372386
}
373387
374388
export interface CheckboxRecipe {
389+
__slot: CheckboxSlot
375390
__type: CheckboxVariantProps
376-
(props?: CheckboxVariantProps): Pretty<Record<"root" | "control" | "label", string>>
391+
(props?: CheckboxVariantProps): Pretty<Record<CheckboxSlot, string>>
377392
raw: (props?: CheckboxVariantProps) => CheckboxVariantProps
378393
variantMap: CheckboxVariantMap
379394
variantKeys: Array<keyof CheckboxVariant>
@@ -420,6 +435,7 @@ describe('generate recipes', () => {
420435
__recipe__: false,
421436
__name__: 'checkbox',
422437
raw: (props) => props,
438+
classNameMap: {},
423439
variantKeys: checkboxVariantKeys,
424440
variantMap: {
425441
"size": [
@@ -448,13 +464,16 @@ describe('generate recipes', () => {
448464
[key in keyof BadgeVariant]: Array<BadgeVariant[key]>
449465
}
450466
467+
type BadgeSlot = "title" | "body"
468+
451469
export type BadgeVariantProps = {
452470
[key in keyof BadgeVariant]?: BadgeVariant[key] | undefined
453471
}
454472
455473
export interface BadgeRecipe {
474+
__slot: BadgeSlot
456475
__type: BadgeVariantProps
457-
(props?: BadgeVariantProps): Pretty<Record<"title" | "body", string>>
476+
(props?: BadgeVariantProps): Pretty<Record<BadgeSlot, string>>
458477
raw: (props?: BadgeVariantProps) => BadgeVariantProps
459478
variantMap: BadgeVariantMap
460479
variantKeys: Array<keyof BadgeVariant>
@@ -506,6 +525,7 @@ describe('generate recipes', () => {
506525
__recipe__: false,
507526
__name__: 'badge',
508527
raw: (props) => props,
528+
classNameMap: {},
509529
variantKeys: badgeVariantKeys,
510530
variantMap: {
511531
"size": [

packages/generator/__tests__/setup-artifacts.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@ describe('setup-artifacts', () => {
301301
"jsx/cq.mjs",
302302
"jsx/cq.d.ts",
303303
],
304+
[
305+
"jsx/create-style-context.mjs",
306+
"jsx/create-style-context.d.ts",
307+
],
304308
[
305309
"jsx/index.mjs",
306310
"jsx/index.d.ts",

packages/generator/src/artifacts/js/recipe.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ export function generateRecipes(ctx: Context, filters?: ArtifactFilters) {
156156
__recipe__: false,
157157
__name__: '${baseName}',
158158
raw: (props) => props,
159+
classNameMap: {},
159160
variantKeys: ${baseName}VariantKeys,
160161
variantMap: ${stringify(variantKeyMap)},
161162
splitVariantProps(props) {
@@ -219,16 +220,19 @@ export function generateRecipes(ctx: Context, filters?: ArtifactFilters) {
219220
[key in keyof ${upperName}Variant]: Array<${upperName}Variant[key]>
220221
}
221222
223+
${Recipes.isSlotRecipeConfig(config) ? `type ${upperName}Slot = ${unionType(config.slots)}` : ''}
224+
222225
export type ${upperName}VariantProps = {
223226
[key in keyof ${upperName}Variant]?: ${
224227
compoundVariants?.length ? `${upperName}Variant[key]` : `ConditionalValue<${upperName}Variant[key]>`
225228
} | undefined
226229
}
227230
228231
export interface ${upperName}Recipe {
232+
${Recipes.isSlotRecipeConfig(config) ? `__slot: ${upperName}Slot` : ''}
229233
__type: ${upperName}VariantProps
230234
(props?: ${upperName}VariantProps): ${
231-
Recipes.isSlotRecipeConfig(config) ? `Pretty<Record<${unionType(config.slots)}, string>>` : 'string'
235+
Recipes.isSlotRecipeConfig(config) ? `Pretty<Record<${upperName}Slot, string>>` : 'string'
232236
}
233237
raw: (props?: ${upperName}VariantProps) => ${upperName}VariantProps
234238
variantMap: ${upperName}VariantMap

packages/generator/src/artifacts/js/sva.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ export function generateSvaFn(ctx: Context) {
88
${ctx.file.import('cva', './cva')}
99
${ctx.file.import('cx', './cx')}
1010
11-
const slotClass = (className, slot) => className + '__' + slot
12-
1311
export function sva(config) {
1412
const slots = Object.entries(getSlotRecipes(config)).map(([slot, slotCva]) => [slot, cva(slotCva)])
1513
const defaultVariants = config.defaultVariants ?? {}
1614
15+
const classNameMap = slots.reduce((acc, [slot, cvaFn]) => {
16+
if (config.className) acc[slot] = cvaFn.config.className
17+
return acc
18+
}, {})
19+
1720
function svaFn(props) {
18-
const result = slots.map(([slot, cvaFn]) => [slot, cx(cvaFn(props), config.className && slotClass(config.className, slot))])
21+
const result = slots.map(([slot, cvaFn]) => [slot, cx(cvaFn(props), classNameMap[slot])])
1922
return Object.fromEntries(result)
2023
}
2124
@@ -30,7 +33,7 @@ export function generateSvaFn(ctx: Context) {
3033
function splitVariantProps(props) {
3134
return splitProps(props, variantKeys);
3235
}
33-
const getVariantProps = (variants) => ({ ...(defaultVariants || {}), ...compact(variants) })
36+
const getVariantProps = (variants) => ({ ...defaultVariants, ...compact(variants) })
3437
3538
const variantMap = Object.fromEntries(
3639
Object.entries(variants).map(([key, value]) => [key, Object.keys(value)])
@@ -39,8 +42,10 @@ export function generateSvaFn(ctx: Context) {
3942
return Object.assign(memo(svaFn), {
4043
__cva__: false,
4144
raw,
45+
config,
4246
variantMap,
4347
variantKeys,
48+
classNameMap,
4449
splitVariantProps,
4550
getVariantProps,
4651
})

packages/generator/src/artifacts/jsx.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,39 @@
11
import type { Context } from '@pandacss/core'
22
import type { ArtifactFilters, JsxFramework } from '@pandacss/types'
3-
import { generatePreactJsxFactory, generatePreactJsxPattern, generatePreactJsxTypes } from './preact-jsx'
3+
import {
4+
generatePreactJsxFactory,
5+
generatePreactJsxPattern,
6+
generatePreactJsxTypes,
7+
generatePreactCreateStyleContext,
8+
} from './preact-jsx'
49
import { generatePreactJsxStringLiteralFactory } from './preact-jsx/jsx.string-literal'
510
import { generatePreactJsxStringLiteralTypes } from './preact-jsx/types.string-literal'
611
import { generateQwikJsxFactory, generateQwikJsxPattern, generateQwikJsxTypes } from './qwik-jsx'
712
import { generateQwikJsxStringLiteralFactory } from './qwik-jsx/jsx.string-literal'
813
import { generateQwikJsxStringLiteralTypes } from './qwik-jsx/types.string-literal'
9-
import { generateReactJsxFactory, generateReactJsxPattern, generateReactJsxTypes } from './react-jsx'
14+
import {
15+
generateReactJsxFactory,
16+
generateReactJsxPattern,
17+
generateReactJsxTypes,
18+
generateReactCreateStyleContext,
19+
} from './react-jsx'
1020
import { generateReactJsxStringLiteralFactory } from './react-jsx/jsx.string-literal'
1121
import { generateReactJsxStringLiteralTypes } from './react-jsx/types.string-literal'
12-
import { generateSolidJsxFactory, generateSolidJsxPattern, generateSolidJsxTypes } from './solid-jsx'
22+
import {
23+
generateSolidJsxFactory,
24+
generateSolidJsxPattern,
25+
generateSolidJsxTypes,
26+
generateSolidCreateStyleContext,
27+
} from './solid-jsx'
1328
import { generateSolidJsxStringLiteralFactory } from './solid-jsx/jsx.string-literal'
1429
import { generateSolidJsxStringLiteralTypes } from './solid-jsx/types.string-literal'
15-
import { generateVueJsxFactory } from './vue-jsx/jsx'
30+
import {
31+
generateVueJsxFactory,
32+
generateVueJsxPattern,
33+
generateVueJsxTypes,
34+
generateVueCreateStyleContext,
35+
} from './vue-jsx'
1636
import { generateVueJsxStringLiteralFactory } from './vue-jsx/jsx.string-literal'
17-
import { generateVueJsxPattern } from './vue-jsx/pattern'
18-
import { generateVueJsxTypes } from './vue-jsx/types'
1937
import { generateVueJsxStringLiteralTypes } from './vue-jsx/types.string-literal'
2038

2139
/* -----------------------------------------------------------------------------
@@ -93,3 +111,21 @@ export function generateJsxPatterns(ctx: Context, filters?: ArtifactFilters) {
93111
if (!isKnownFramework(ctx.jsx.framework)) return
94112
return patternMap[ctx.jsx.framework!](ctx, filters)
95113
}
114+
115+
/* -----------------------------------------------------------------------------
116+
* Create Style Context JSX
117+
* -----------------------------------------------------------------------------*/
118+
119+
const createStyleContextMap = {
120+
react: generateReactCreateStyleContext,
121+
preact: generatePreactCreateStyleContext,
122+
solid: generateSolidCreateStyleContext,
123+
vue: generateVueCreateStyleContext,
124+
}
125+
126+
export function generateJsxCreateStyleContext(ctx: Context) {
127+
if (ctx.isTemplateLiteralSyntax || !ctx.jsx.framework) return
128+
if (!isKnownFramework(ctx.jsx.framework)) return
129+
const generator = (createStyleContextMap as any)[ctx.jsx.framework]
130+
return generator?.(ctx)
131+
}

0 commit comments

Comments
 (0)