From 93dc9f5b8bfbf168349d6c6a17a404972e3da3d9 Mon Sep 17 00:00:00 2001 From: Alexandre Stahmer <47224540+astahmer@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:33:47 +0200 Subject: [PATCH] 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 --- .changeset/hot-rivers-rescue.md | 18 ++++++++ packages/studio/astro.config.mjs | 3 +- packages/studio/src/components/colors.tsx | 8 ++-- .../studio/src/components/font-family.tsx | 10 +++++ .../studio/src/components/font-tokens.tsx | 10 +++++ packages/studio/src/components/overview.tsx | 2 +- .../studio/src/components/semantic-color.tsx | 18 +------- packages/studio/src/components/sizes.tsx | 13 +++++- .../studio/src/components/text-styles.tsx | 15 ++++++- packages/studio/src/lib/use-color-docs.ts | 42 +++++++++++-------- packages/studio/src/pages/sizes.astro | 5 +-- packages/studio/src/pages/spacing.astro | 8 ++-- packages/studio/styled-system/styles.css | 7 +++- packages/token-dictionary/src/dictionary.ts | 41 ++++++++++++++++-- packages/token-dictionary/src/index.ts | 3 +- 15 files changed, 145 insertions(+), 58 deletions(-) create mode 100644 .changeset/hot-rivers-rescue.md diff --git a/.changeset/hot-rivers-rescue.md b/.changeset/hot-rivers-rescue.md new file mode 100644 index 000000000..ab6d003da --- /dev/null +++ b/.changeset/hot-rivers-rescue.md @@ -0,0 +1,18 @@ +--- +'@pandacss/token-dictionary': patch +'@pandacss/studio': patch +--- + +Public changes: Some quality of life fixes for the Studio: + +- Handle displaying values using the `[xxx]` escape-hatch syntax for `textStyles` in the studio +- Display an empty state when there's no token in a specific token page in the studio + +--- + +(mostly) Internal changes: + +- Add `deepResolveReference` in TokenDictionary, helpful to get the raw value from a semantic token by recursively + traversing the token references. +- Added some exports in the `@pandacss/token-dictionary` package, mostly useful when building tooling around Panda + (Prettier/ESLint/VSCode plugin etc) diff --git a/packages/studio/astro.config.mjs b/packages/studio/astro.config.mjs index 1ac6ce7b0..61354b545 100644 --- a/packages/studio/astro.config.mjs +++ b/packages/studio/astro.config.mjs @@ -1,9 +1,8 @@ -import react from '@astrojs/react' import studio from '@pandacss/astro-plugin-studio' import { defineConfig } from 'astro/config' // https://astro.build/config export default defineConfig({ devToolbar: { enabled: true }, - integrations: [react(), studio()], + integrations: [studio()], }) diff --git a/packages/studio/src/components/colors.tsx b/packages/studio/src/components/colors.tsx index be06c8513..0ae9377b6 100644 --- a/packages/studio/src/components/colors.tsx +++ b/packages/studio/src/components/colors.tsx @@ -27,12 +27,12 @@ export default function Colors() { diff --git a/packages/studio/src/components/font-family.tsx b/packages/studio/src/components/font-family.tsx index 0a117c2b7..f6a84f756 100644 --- a/packages/studio/src/components/font-family.tsx +++ b/packages/studio/src/components/font-family.tsx @@ -1,6 +1,8 @@ import * as React from 'react' import { Flex, HStack, Square, Stack, panda } from '../../styled-system/jsx' import * as context from '../lib/panda-context' +import { EmptyState } from './empty-state' +import { TypographyIcon } from './icons' const fonts = context.getTokens('fonts') @@ -9,6 +11,14 @@ const symbols = Array.from({ length: 10 }, (_, i) => String.fromCharCode(48 + i) const specials = ['@', '#', '$', '%', '&', '!', '?', '+', '-'] export const FontFamily = () => { + if (fonts.length === 0) { + return ( + }> + The panda config does not contain any font family + + ) + } + return ( {fonts.map((font) => ( diff --git a/packages/studio/src/components/font-tokens.tsx b/packages/studio/src/components/font-tokens.tsx index 89fec527b..a38c24202 100644 --- a/packages/studio/src/components/font-tokens.tsx +++ b/packages/studio/src/components/font-tokens.tsx @@ -5,6 +5,8 @@ import { TokenContent } from '../components/token-content' import { TokenGroup } from '../components/token-group' import { Input, Textarea } from './input' import { StickyTop } from './sticky-top' +import { EmptyState } from './empty-state' +import { TypographyIcon, XMarkIcon } from './icons' interface FontTokensProps { text?: string @@ -23,6 +25,14 @@ export default function FontTokens(props: FontTokensProps) { setText(event.target.value) } + if (fontTokens.length === 0) { + return ( + }> + The panda config does not contain any `{token}` tokens + + ) + } + return ( diff --git a/packages/studio/src/components/overview.tsx b/packages/studio/src/components/overview.tsx index c522e60b5..aea43a6cc 100644 --- a/packages/studio/src/components/overview.tsx +++ b/packages/studio/src/components/overview.tsx @@ -44,7 +44,7 @@ export default function Overview() {
- + Panda Studio

Live documentation for your design tokens (colors, fonts, etc.)

diff --git a/packages/studio/src/components/semantic-color.tsx b/packages/studio/src/components/semantic-color.tsx index 73863cd8d..b997981c6 100644 --- a/packages/studio/src/components/semantic-color.tsx +++ b/packages/studio/src/components/semantic-color.tsx @@ -3,29 +3,13 @@ import { Flex, panda } from '../../styled-system/jsx' import { ColorWrapper } from './color-wrapper' import * as context from '../lib/panda-context' -const getSemanticColorValue = (variable: string): string => { - const _name = variable?.match(/var\(\s*--(.*?)\s*\)/) - if (!_name) return variable - - const name = _name[1].replaceAll('-', '.') - const token = context.tokens.getByName(name) - - if (!token) { - const defaultToken = context.tokens.getByName(`${name}.default`) - return getSemanticColorValue(defaultToken?.value) - } - - if (token.value.startsWith('var(--')) return getSemanticColorValue(token.value) - return token.value -} - // remove initial underscore const cleanCondition = (condition: string) => condition.replace(/^_/, '') export function SemanticColorDisplay(props: { value: string; condition: string; token?: string }) { const { value, condition } = props - const tokenValue = getSemanticColorValue(value) + const tokenValue = context.tokens.deepResolveReference(value) return ( diff --git a/packages/studio/src/components/sizes.tsx b/packages/studio/src/components/sizes.tsx index e76e86c38..be4794fbf 100644 --- a/packages/studio/src/components/sizes.tsx +++ b/packages/studio/src/components/sizes.tsx @@ -5,16 +5,19 @@ import { Grid, panda } from '../../styled-system/jsx' import { getSortedSizes } from '../lib/sizes-sort' import { TokenGroup } from './token-group' import type { Token } from '@pandacss/token-dictionary' +import { EmptyState } from './empty-state' +import { SizesIcon } from './icons' export interface SizesProps { sizes: Token[] + name: string } const contentRegex = /^(min|max|fit)-content$/ const unitRegex = /(ch|%)$/ export default function Sizes(props: SizesProps) { - const { sizes } = props + const { sizes, name } = props const sortedSizes = getSortedSizes(sizes).filter( (token) => @@ -27,6 +30,14 @@ export default function Sizes(props: SizesProps) { !unitRegex.test(token.value), ) + if (sortedSizes.length === 0) { + return ( + }> + The panda config does not contain any `{name}`` tokens + + ) + } + return ( diff --git a/packages/studio/src/components/text-styles.tsx b/packages/studio/src/components/text-styles.tsx index ce5d08662..519db218c 100644 --- a/packages/studio/src/components/text-styles.tsx +++ b/packages/studio/src/components/text-styles.tsx @@ -5,6 +5,7 @@ import { EmptyState } from './empty-state' import { TextStylesIcon } from './icons' import { TokenContent } from './token-content' import { TokenGroup } from './token-group' +import type { Dict } from '../../styled-system/types' export default function TextStyles() { const textStyles = Object.entries(context.textStyles) @@ -18,7 +19,7 @@ export default function TextStyles() { {name} - + Panda textStyles are time saving @@ -32,3 +33,15 @@ export default function TextStyles() { ) } + +const removeEscapeHatchSyntax = (styles: Dict) => { + return Object.fromEntries( + Object.entries(styles).map(([key, value]) => { + if (typeof value === 'string' && value[0] === '[' && value[value.length - 1] === ']') { + return [key, value.slice(1, -1)] + } + + return [key, value] + }), + ) +} diff --git a/packages/studio/src/lib/use-color-docs.ts b/packages/studio/src/lib/use-color-docs.ts index 66df632ef..aa777ca42 100644 --- a/packages/studio/src/lib/use-color-docs.ts +++ b/packages/studio/src/lib/use-color-docs.ts @@ -1,4 +1,4 @@ -import type { Token } from '@pandacss/token-dictionary' +import type { Token, TokenExtensions } from '@pandacss/token-dictionary' import { useState } from 'react' import * as context from './panda-context' @@ -10,33 +10,36 @@ interface Color { path: string[] } -type ColorToken = Token & Color +type ColorToken = Token & Color & TokenExtensions const UNCATEGORIZED_ID = 'uncategorized' as const -const groupByColorPalette = (colors: Token[], filterMethod?: (token: ColorToken) => boolean) => { +const groupByColorPalette = (colors: ColorToken[], filterMethod?: (token: ColorToken) => boolean) => { const values = colors.filter((color) => !color.isConditional && !color.extensions.isVirtual) - return values.reduce>((acc, color) => { - if (!filterMethod?.(color)) return acc + return values.reduce( + (acc, color) => { + if (!filterMethod?.(color)) return acc - const colorPalette = color.extensions.colorPalette || UNCATEGORIZED_ID + const colorPalette = color.extensions.colorPalette || UNCATEGORIZED_ID - if (!(colorPalette in acc)) { - acc[colorPalette] = [] - } + if (!(colorPalette in acc)) { + acc[colorPalette] = [] + } - const exists = (acc[colorPalette] as any[]).find((tok) => tok.name === color.name) - if (!exists) acc[colorPalette].push(color) + const exists = (acc[colorPalette] as any[]).find((tok) => tok.name === color.name) + if (!exists) acc[colorPalette].push(color) - return acc - }, {}) + return acc + }, + {} as Record, + ) } -const getSemanticTokens = (allTokens: ColorToken[], filterMethod?: (token: ColorToken) => boolean) => { +const getSemanticTokens = (allTokens: Token[], filterMethod?: (token: ColorToken) => boolean) => { const semanticTokens = allTokens.filter( (token) => token.type === 'color' && token.isConditional && !token.extensions?.isVirtual, - ) + ) as ColorToken[] return semanticTokens .reduce((acc, nxt) => { if (!filterMethod) { @@ -50,7 +53,7 @@ const getSemanticTokens = (allTokens: ColorToken[], filterMethod?: (token: Color } return acc }, [] as ColorToken[]) - .reduce>( + .reduce>( (acc, nxt) => ({ ...acc, [nxt.extensions?.prop]: { @@ -85,14 +88,17 @@ export const useColorDocs = () => { .some((prop) => prop.includes(filterQuery)) } - const colorsInCategories = groupByColorPalette(colors, filterMethod) + const colorsInCategories = groupByColorPalette(colors as ColorToken[], filterMethod) const uncategorizedColors = colorsInCategories[UNCATEGORIZED_ID] const categorizedColors = Object.entries(colorsInCategories).filter( ([category]) => category !== UNCATEGORIZED_ID, ) - const semanticTokens = Object.entries>(getSemanticTokens(allTokens, filterMethod)) + const semanticTokens = Object.entries>(getSemanticTokens(allTokens, filterMethod)) as [ + string, + Record, + ][] const hasResults = !!categorizedColors.length || !!uncategorizedColors?.length || !!Object.values(semanticTokens).length diff --git a/packages/studio/src/pages/sizes.astro b/packages/studio/src/pages/sizes.astro index b6221f740..0ca1e6fe8 100644 --- a/packages/studio/src/pages/sizes.astro +++ b/packages/studio/src/pages/sizes.astro @@ -1,5 +1,5 @@ --- -import Sizes from '../components/sizes' +import Sizes from '../components/sizes' import Layout from '../layouts/Layout.astro' import Sidebar from '../layouts/Sidebar.astro' import * as context from '../lib/panda-context' @@ -9,7 +9,6 @@ const tokens = context.getTokens('sizes') - + - diff --git a/packages/studio/src/pages/spacing.astro b/packages/studio/src/pages/spacing.astro index 9ed5bbe5c..18a7bf5b0 100644 --- a/packages/studio/src/pages/spacing.astro +++ b/packages/studio/src/pages/spacing.astro @@ -1,5 +1,5 @@ --- -import Sizes from '../components/sizes' +import Sizes from '../components/sizes' import Layout from '../layouts/Layout.astro' import Sidebar from '../layouts/Sidebar.astro' import * as context from '../lib/panda-context' @@ -8,7 +8,7 @@ const tokens = context.getTokens('spacing') --- - - - + + + diff --git a/packages/studio/styled-system/styles.css b/packages/studio/styled-system/styles.css index e0ec8edd8..73f387733 100644 --- a/packages/studio/styled-system/styles.css +++ b/packages/studio/styled-system/styles.css @@ -707,7 +707,6 @@ } @layer utilities{ - .d_flex { display: flex; } @@ -957,6 +956,10 @@ margin-block: var(--spacing-10); } + .h_300px { + height: 300px; +} + .tracking_tighter { letter-spacing: var(--letter-spacings-tighter); } @@ -1422,4 +1425,4 @@ padding-inline: var(--spacing-8); } } -} +} \ No newline at end of file diff --git a/packages/token-dictionary/src/dictionary.ts b/packages/token-dictionary/src/dictionary.ts index 0bd790bda..bad697d4f 100644 --- a/packages/token-dictionary/src/dictionary.ts +++ b/packages/token-dictionary/src/dictionary.ts @@ -398,12 +398,37 @@ export class TokenDictionary { } /** - * Resolve token references to their actual raw value + * Get the value of a token reference */ resolveReference(value: string) { return expandReferences(value, (key) => this.getByName(key)?.value) } + /** + * Resolve token references to their actual raw value (recursively resolves references) + */ + deepResolveReference(originalValue: string) { + const stack = [originalValue] + while (stack.length) { + const next = stack.pop()! + + if (next.startsWith('{')) { + stack.push(this.resolveReference(next)) + continue + } + + if (next.startsWith('var(')) { + const ref = this.view.nameByVar.get(next) + if (ref) { + stack.push(this.resolveReference(`{${ref}}`)) + continue + } + } + + return next + } + } + build() { this.applyMiddlewares('pre') this.transformTokens('pre') @@ -451,13 +476,14 @@ export class TokenDictionaryView { const colorPalettes = new Map>() const valuesByCategory = new Map>() const flatValues = new Map() + const nameByVar = new Map() const vars = new Map>() this.dictionary.allTokens.forEach((token) => { this.processCondition(token, conditionMap) this.processColorPalette(token, colorPalettes, this.dictionary.byName) this.processCategory(token, categoryMap) - this.processValue(token, valuesByCategory, flatValues) + this.processValue(token, valuesByCategory, flatValues, nameByVar) this.processVars(token, vars) }) @@ -468,6 +494,7 @@ export class TokenDictionaryView { colorPalettes, vars, values: flatValues, + nameByVar: nameByVar, json, get: memo((path: string, fallback?: string | number) => { return (flatValues.get(path) ?? fallback) as string @@ -538,14 +565,20 @@ export class TokenDictionaryView { group.get(category)!.set(prop, token) } - private processValue(token: Token, byCategory: Map>, flat: Map) { + private processValue( + token: Token, + byCategory: Map>, + flatValues: Map, + nameByVar: Map, + ) { const { category, prop, varRef, isNegative } = token.extensions if (!category) return if (!byCategory.has(category)) byCategory.set(category, new Map()) const value = isNegative ? (token.extensions.condition !== 'base' ? token.originalValue : token.value) : varRef byCategory.get(category)!.set(prop, value) - flat.set(token.name, value) + flatValues.set(token.name, value) + nameByVar.set(value, token.name) } private processVars(token: Token, group: Map>) { diff --git a/packages/token-dictionary/src/index.ts b/packages/token-dictionary/src/index.ts index cdda327fb..02e717ee7 100644 --- a/packages/token-dictionary/src/index.ts +++ b/packages/token-dictionary/src/index.ts @@ -1,2 +1,3 @@ export { TokenDictionary } from './dictionary' -export { Token } from './token' +export { Token, type TokenExtensions } from './token' +export * from './utils'