Skip to content

Commit 93dc9f5

Browse files
authored
fix(studio): display escape hatch values in textStyles (#2452)
* fix: logo height * chore: add missing export * chore: typings * feat(token-dictionary): deepResolveReference * fix: studio hmr * fix: handle escape hatch syntax in studio textStyles page * fix: add EmptyState to tokens page * chore: add changeset
1 parent f89db71 commit 93dc9f5

15 files changed

+145
-58
lines changed

.changeset/hot-rivers-rescue.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@pandacss/token-dictionary': patch
3+
'@pandacss/studio': patch
4+
---
5+
6+
Public changes: Some quality of life fixes for the Studio:
7+
8+
- Handle displaying values using the `[xxx]` escape-hatch syntax for `textStyles` in the studio
9+
- Display an empty state when there's no token in a specific token page in the studio
10+
11+
---
12+
13+
(mostly) Internal changes:
14+
15+
- Add `deepResolveReference` in TokenDictionary, helpful to get the raw value from a semantic token by recursively
16+
traversing the token references.
17+
- Added some exports in the `@pandacss/token-dictionary` package, mostly useful when building tooling around Panda
18+
(Prettier/ESLint/VSCode plugin etc)

packages/studio/astro.config.mjs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import react from '@astrojs/react'
21
import studio from '@pandacss/astro-plugin-studio'
32
import { defineConfig } from 'astro/config'
43

54
// https://astro.build/config
65
export default defineConfig({
76
devToolbar: { enabled: true },
8-
integrations: [react(), studio()],
7+
integrations: [studio()],
98
})

packages/studio/src/components/colors.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ export default function Colors() {
2727
<SemanticColorDisplay
2828
value={colors.base.value}
2929
condition="base"
30-
token={getColorFromReference(colors.extensions.conditions.base)}
30+
token={getColorFromReference(colors.extensions.conditions!.base)}
3131
/>
3232
<SemanticColorDisplay
33-
value={colors[colors.extensions.condition].value}
34-
condition={colors.extensions.condition}
35-
token={getColorFromReference(colors.extensions.conditions[colors.extensions.condition])}
33+
value={colors[colors.extensions.condition!].value}
34+
condition={colors.extensions.condition!}
35+
token={getColorFromReference(colors.extensions.conditions![colors.extensions.condition!])}
3636
/>
3737
</HStack>
3838

packages/studio/src/components/font-family.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as React from 'react'
22
import { Flex, HStack, Square, Stack, panda } from '../../styled-system/jsx'
33
import * as context from '../lib/panda-context'
4+
import { EmptyState } from './empty-state'
5+
import { TypographyIcon } from './icons'
46

57
const fonts = context.getTokens('fonts')
68

@@ -9,6 +11,14 @@ const symbols = Array.from({ length: 10 }, (_, i) => String.fromCharCode(48 + i)
911
const specials = ['@', '#', '$', '%', '&', '!', '?', '+', '-']
1012

1113
export const FontFamily = () => {
14+
if (fonts.length === 0) {
15+
return (
16+
<EmptyState title="No Tokens" icon={<TypographyIcon />}>
17+
The panda config does not contain any font family
18+
</EmptyState>
19+
)
20+
}
21+
1222
return (
1323
<Stack gap="10">
1424
{fonts.map((font) => (

packages/studio/src/components/font-tokens.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { TokenContent } from '../components/token-content'
55
import { TokenGroup } from '../components/token-group'
66
import { Input, Textarea } from './input'
77
import { StickyTop } from './sticky-top'
8+
import { EmptyState } from './empty-state'
9+
import { TypographyIcon, XMarkIcon } from './icons'
810

911
interface FontTokensProps {
1012
text?: string
@@ -23,6 +25,14 @@ export default function FontTokens(props: FontTokensProps) {
2325
setText(event.target.value)
2426
}
2527

28+
if (fontTokens.length === 0) {
29+
return (
30+
<EmptyState title="No Tokens" icon={<TypographyIcon />}>
31+
The panda config does not contain any `{token}` tokens
32+
</EmptyState>
33+
)
34+
}
35+
2636
return (
2737
<TokenGroup>
2838
<StickyTop>

packages/studio/src/components/overview.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default function Overview() {
4444
<Logo />
4545

4646
<div className={vstack({ my: '10', textAlign: 'center' })}>
47-
<Yums className={css({ fontSize: '24rem' })} />
47+
<Yums className={css({ fontSize: '24rem', h: '300px' })} />
4848
<span className={css({ fontSize: '7xl', letterSpacing: 'tighter', fontWeight: 'medium' })}>Panda Studio</span>
4949
<p className={css({ fontSize: '2xl' })}>Live documentation for your design tokens (colors, fonts, etc.)</p>
5050
</div>

packages/studio/src/components/semantic-color.tsx

+1-17
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,13 @@ import { Flex, panda } from '../../styled-system/jsx'
33
import { ColorWrapper } from './color-wrapper'
44
import * as context from '../lib/panda-context'
55

6-
const getSemanticColorValue = (variable: string): string => {
7-
const _name = variable?.match(/var\(\s*--(.*?)\s*\)/)
8-
if (!_name) return variable
9-
10-
const name = _name[1].replaceAll('-', '.')
11-
const token = context.tokens.getByName(name)
12-
13-
if (!token) {
14-
const defaultToken = context.tokens.getByName(`${name}.default`)
15-
return getSemanticColorValue(defaultToken?.value)
16-
}
17-
18-
if (token.value.startsWith('var(--')) return getSemanticColorValue(token.value)
19-
return token.value
20-
}
21-
226
// remove initial underscore
237
const cleanCondition = (condition: string) => condition.replace(/^_/, '')
248

259
export function SemanticColorDisplay(props: { value: string; condition: string; token?: string }) {
2610
const { value, condition } = props
2711

28-
const tokenValue = getSemanticColorValue(value)
12+
const tokenValue = context.tokens.deepResolveReference(value)
2913

3014
return (
3115
<Flex direction="column" w="full">

packages/studio/src/components/sizes.tsx

+12-1
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@ import { Grid, panda } from '../../styled-system/jsx'
55
import { getSortedSizes } from '../lib/sizes-sort'
66
import { TokenGroup } from './token-group'
77
import type { Token } from '@pandacss/token-dictionary'
8+
import { EmptyState } from './empty-state'
9+
import { SizesIcon } from './icons'
810

911
export interface SizesProps {
1012
sizes: Token[]
13+
name: string
1114
}
1215

1316
const contentRegex = /^(min|max|fit)-content$/
1417
const unitRegex = /(ch|%)$/
1518

1619
export default function Sizes(props: SizesProps) {
17-
const { sizes } = props
20+
const { sizes, name } = props
1821

1922
const sortedSizes = getSortedSizes(sizes).filter(
2023
(token) =>
@@ -27,6 +30,14 @@ export default function Sizes(props: SizesProps) {
2730
!unitRegex.test(token.value),
2831
)
2932

33+
if (sortedSizes.length === 0) {
34+
return (
35+
<EmptyState title="No Tokens" icon={<SizesIcon />}>
36+
The panda config does not contain any `{name}`` tokens
37+
</EmptyState>
38+
)
39+
}
40+
3041
return (
3142
<TokenGroup>
3243
<Grid display="grid" columnGap="10" rowGap="2.5" columns={5}>

packages/studio/src/components/text-styles.tsx

+14-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { EmptyState } from './empty-state'
55
import { TextStylesIcon } from './icons'
66
import { TokenContent } from './token-content'
77
import { TokenGroup } from './token-group'
8+
import type { Dict } from '../../styled-system/types'
89

910
export default function TextStyles() {
1011
const textStyles = Object.entries(context.textStyles)
@@ -18,7 +19,7 @@ export default function TextStyles() {
1819
<panda.div borderColor="card">
1920
<panda.span fontWeight="medium">{name}</panda.span>
2021
</panda.div>
21-
<panda.div flex="auto" my="3" style={styles} truncate>
22+
<panda.div flex="auto" my="3" style={removeEscapeHatchSyntax(styles)} truncate>
2223
Panda textStyles are time saving
2324
</panda.div>
2425
</panda.div>
@@ -32,3 +33,15 @@ export default function TextStyles() {
3233
</TokenGroup>
3334
)
3435
}
36+
37+
const removeEscapeHatchSyntax = (styles: Dict) => {
38+
return Object.fromEntries(
39+
Object.entries(styles).map(([key, value]) => {
40+
if (typeof value === 'string' && value[0] === '[' && value[value.length - 1] === ']') {
41+
return [key, value.slice(1, -1)]
42+
}
43+
44+
return [key, value]
45+
}),
46+
)
47+
}

packages/studio/src/lib/use-color-docs.ts

+24-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Token } from '@pandacss/token-dictionary'
1+
import type { Token, TokenExtensions } from '@pandacss/token-dictionary'
22
import { useState } from 'react'
33
import * as context from './panda-context'
44

@@ -10,33 +10,36 @@ interface Color {
1010
path: string[]
1111
}
1212

13-
type ColorToken = Token & Color
13+
type ColorToken = Token & Color & TokenExtensions
1414

1515
const UNCATEGORIZED_ID = 'uncategorized' as const
1616

17-
const groupByColorPalette = (colors: Token[], filterMethod?: (token: ColorToken) => boolean) => {
17+
const groupByColorPalette = (colors: ColorToken[], filterMethod?: (token: ColorToken) => boolean) => {
1818
const values = colors.filter((color) => !color.isConditional && !color.extensions.isVirtual)
1919

20-
return values.reduce<Record<string, any>>((acc, color) => {
21-
if (!filterMethod?.(color)) return acc
20+
return values.reduce(
21+
(acc, color) => {
22+
if (!filterMethod?.(color)) return acc
2223

23-
const colorPalette = color.extensions.colorPalette || UNCATEGORIZED_ID
24+
const colorPalette = color.extensions.colorPalette || UNCATEGORIZED_ID
2425

25-
if (!(colorPalette in acc)) {
26-
acc[colorPalette] = []
27-
}
26+
if (!(colorPalette in acc)) {
27+
acc[colorPalette] = []
28+
}
2829

29-
const exists = (acc[colorPalette] as any[]).find((tok) => tok.name === color.name)
30-
if (!exists) acc[colorPalette].push(color)
30+
const exists = (acc[colorPalette] as any[]).find((tok) => tok.name === color.name)
31+
if (!exists) acc[colorPalette].push(color)
3132

32-
return acc
33-
}, {})
33+
return acc
34+
},
35+
{} as Record<string, ColorToken[]>,
36+
)
3437
}
3538

36-
const getSemanticTokens = (allTokens: ColorToken[], filterMethod?: (token: ColorToken) => boolean) => {
39+
const getSemanticTokens = (allTokens: Token[], filterMethod?: (token: ColorToken) => boolean) => {
3740
const semanticTokens = allTokens.filter(
3841
(token) => token.type === 'color' && token.isConditional && !token.extensions?.isVirtual,
39-
)
42+
) as ColorToken[]
4043
return semanticTokens
4144
.reduce((acc, nxt) => {
4245
if (!filterMethod) {
@@ -50,7 +53,7 @@ const getSemanticTokens = (allTokens: ColorToken[], filterMethod?: (token: Color
5053
}
5154
return acc
5255
}, [] as ColorToken[])
53-
.reduce<Record<string, any>>(
56+
.reduce<Record<string, ColorToken>>(
5457
(acc, nxt) => ({
5558
...acc,
5659
[nxt.extensions?.prop]: {
@@ -85,14 +88,17 @@ export const useColorDocs = () => {
8588
.some((prop) => prop.includes(filterQuery))
8689
}
8790

88-
const colorsInCategories = groupByColorPalette(colors, filterMethod)
91+
const colorsInCategories = groupByColorPalette(colors as ColorToken[], filterMethod)
8992
const uncategorizedColors = colorsInCategories[UNCATEGORIZED_ID]
9093

9194
const categorizedColors = Object.entries<any[]>(colorsInCategories).filter(
9295
([category]) => category !== UNCATEGORIZED_ID,
9396
)
9497

95-
const semanticTokens = Object.entries<Record<string, any>>(getSemanticTokens(allTokens, filterMethod))
98+
const semanticTokens = Object.entries<Record<string, any>>(getSemanticTokens(allTokens, filterMethod)) as [
99+
string,
100+
Record<string, ColorToken>,
101+
][]
96102
const hasResults =
97103
!!categorizedColors.length || !!uncategorizedColors?.length || !!Object.values(semanticTokens).length
98104

packages/studio/src/pages/sizes.astro

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
import Sizes from '../components/sizes'
2+
import Sizes from '../components/sizes'
33
import Layout from '../layouts/Layout.astro'
44
import Sidebar from '../layouts/Sidebar.astro'
55
import * as context from '../lib/panda-context'
@@ -9,7 +9,6 @@ const tokens = context.getTokens('sizes')
99

1010
<Layout>
1111
<Sidebar title="Sizes">
12-
<Sizes sizes={tokens} client:load />
12+
<Sizes sizes={tokens} name="sizes" client:load />
1313
</Sidebar>
1414
</Layout>
15-
+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
import Sizes from '../components/sizes'
2+
import Sizes from '../components/sizes'
33
import Layout from '../layouts/Layout.astro'
44
import Sidebar from '../layouts/Sidebar.astro'
55
import * as context from '../lib/panda-context'
@@ -8,7 +8,7 @@ const tokens = context.getTokens('spacing')
88
---
99

1010
<Layout>
11-
<Sidebar title="Spacing">
12-
<Sizes sizes={tokens} client:load />
13-
</Sidebar>
11+
<Sidebar title="Spacing">
12+
<Sizes sizes={tokens} name="spacing" client:load />
13+
</Sidebar>
1414
</Layout>

packages/studio/styled-system/styles.css

+5-2
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,6 @@
707707
}
708708

709709
@layer utilities{
710-
711710
.d_flex {
712711
display: flex;
713712
}
@@ -957,6 +956,10 @@
957956
margin-block: var(--spacing-10);
958957
}
959958

959+
.h_300px {
960+
height: 300px;
961+
}
962+
960963
.tracking_tighter {
961964
letter-spacing: var(--letter-spacings-tighter);
962965
}
@@ -1422,4 +1425,4 @@
14221425
padding-inline: var(--spacing-8);
14231426
}
14241427
}
1425-
}
1428+
}

0 commit comments

Comments
 (0)