@pandacss/studio 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/studio.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // ../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.1_@swc+helpers@0.5.17__jiti@2.6.1_postcss@8.5.6_tsx@4.20.6_typescript@5.9.3_yaml@2.8.1/node_modules/tsup/assets/esm_shims.js
1
+ // ../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.2_@swc+helpers@0.5.17__jiti@2.6.1_postcss@8.5.6_tsx@4.20.6_typescript@5.9.3_yaml@2.8.1/node_modules/tsup/assets/esm_shims.js
2
2
  import path from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  var getFilename = () => fileURLToPath(import.meta.url);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pandacss/studio",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "The automated token documentation for Panda CSS",
5
5
  "main": "dist/studio.js",
6
6
  "module": "dist/studio.mjs",
@@ -43,22 +43,24 @@
43
43
  "author": "Segun Adebayo <joseshegs@gmail.com>",
44
44
  "license": "MIT",
45
45
  "dependencies": {
46
- "@astrojs/react": "4.3.0",
47
- "astro": "5.15.6",
48
- "react": "19.1.1",
49
- "react-dom": "19.1.1",
50
- "vite": "7.2.2",
51
- "@pandacss/config": "1.5.1",
52
- "@pandacss/logger": "1.5.1",
53
- "@pandacss/shared": "1.5.1",
54
- "@pandacss/token-dictionary": "1.5.1",
55
- "@pandacss/types": "1.5.1",
56
- "@pandacss/astro-plugin-studio": "1.5.1"
46
+ "@astrojs/react": "4.4.2",
47
+ "@nanostores/react": "^1.0.0",
48
+ "astro": "5.16.2",
49
+ "nanostores": "^1.1.0",
50
+ "react": "19.2.0",
51
+ "react-dom": "19.2.0",
52
+ "vite": "7.2.4",
53
+ "@pandacss/astro-plugin-studio": "1.6.0",
54
+ "@pandacss/config": "1.6.0",
55
+ "@pandacss/logger": "1.6.0",
56
+ "@pandacss/shared": "1.6.0",
57
+ "@pandacss/token-dictionary": "1.6.0",
58
+ "@pandacss/types": "1.6.0"
57
59
  },
58
60
  "devDependencies": {
59
- "@types/react": "19.2.2",
60
- "@types/react-dom": "19.2.2",
61
- "@testing-library/react": "16.3.0"
61
+ "@testing-library/react": "16.3.0",
62
+ "@types/react": "19.2.7",
63
+ "@types/react-dom": "19.2.3"
62
64
  },
63
65
  "scripts": {
64
66
  "panda": "node ../cli/bin.js",
@@ -61,9 +61,13 @@ export function SemanticToken(props: SemanticTokenProps) {
61
61
  )
62
62
  }
63
63
 
64
- export default function Colors() {
64
+ interface ColorsProps {
65
+ theme?: string
66
+ }
67
+
68
+ export function Colors({ theme }: ColorsProps) {
65
69
  const { filterQuery, setFilterQuery, semanticTokens, hasResults, uncategorizedColors, categorizedColors } =
66
- useColorDocs()
70
+ useColorDocs(theme)
67
71
 
68
72
  return (
69
73
  <TokenGroup>
@@ -1,16 +1,20 @@
1
1
  import * as React from 'react'
2
2
  import { Flex, HStack, Square, Stack, panda } from '../../styled-system/jsx'
3
- import * as context from '../lib/panda-context'
4
3
  import { EmptyState } from './empty-state'
5
4
  import { TypographyIcon } from './icons'
6
-
7
- const fonts = context.getTokens('fonts')
5
+ import type { useThemeTokens } from '../lib/use-theme-tokens'
8
6
 
9
7
  const letters = Array.from({ length: 26 }, (_, i) => String.fromCharCode(65 + i))
10
8
  const symbols = Array.from({ length: 10 }, (_, i) => String.fromCharCode(48 + i))
11
9
  const specials = ['@', '#', '$', '%', '&', '!', '?', '+', '-']
12
10
 
13
- export const FontFamily = () => {
11
+ type Token = ReturnType<typeof useThemeTokens>[number]
12
+
13
+ interface FontFamilyProps {
14
+ fonts: Token[]
15
+ }
16
+
17
+ export function FontFamily({ fonts }: FontFamilyProps) {
14
18
  if (fonts.length === 0) {
15
19
  return (
16
20
  <EmptyState title="No Tokens" icon={<TypographyIcon />}>
@@ -1,35 +1,47 @@
1
1
  import * as React from 'react'
2
2
  import { toPx } from '@pandacss/shared'
3
3
  import { Grid, panda, Stack } from '../../styled-system/jsx'
4
- import * as context from '../lib/panda-context'
5
4
  import { getSortedSizes } from '../lib/sizes-sort'
6
5
  import { TokenGroup } from './token-group'
6
+ import { EmptyState } from './empty-state'
7
+ import { SizesIcon } from './icons'
8
+ import type { useThemeTokens } from '../lib/use-theme-tokens'
7
9
 
8
- const radii = context.getTokens('radii')
10
+ type Token = ReturnType<typeof useThemeTokens>[number]
11
+
12
+ interface RadiiProps {
13
+ radii: Token[]
14
+ }
15
+
16
+ export function Radii({ radii }: RadiiProps) {
17
+ if (radii.length === 0) {
18
+ return (
19
+ <EmptyState title="No Tokens" icon={<SizesIcon />}>
20
+ The panda config does not contain any `radii` tokens
21
+ </EmptyState>
22
+ )
23
+ }
9
24
 
10
- export default function Radii() {
11
25
  return (
12
26
  <TokenGroup>
13
- {radii && (
14
- <Grid display="grid" minChildWidth="10rem" gap="10">
15
- {getSortedSizes([...radii.values()])
16
- .sort((a, b) => parseFloat(toPx(a.value)!) - parseFloat(toPx(b.value)!))
17
- .map((size, index) => (
18
- <Stack direction="column" key={index}>
19
- <panda.div
20
- width="80px"
21
- height="80px"
22
- background="rgba(255, 192, 203, 0.5)"
23
- style={{ borderRadius: size.value }}
24
- />
25
- <Stack gap="1">
26
- <b>{size.extensions.prop}</b>
27
- <panda.span opacity="0.7">{size.value}</panda.span>
28
- </Stack>
27
+ <Grid display="grid" minChildWidth="10rem" gap="10">
28
+ {getSortedSizes(radii)
29
+ .sort((a, b) => parseFloat(toPx(a.value)!) - parseFloat(toPx(b.value)!))
30
+ .map((size, index) => (
31
+ <Stack direction="column" key={index}>
32
+ <panda.div
33
+ width="80px"
34
+ height="80px"
35
+ background="rgba(255, 192, 203, 0.5)"
36
+ style={{ borderRadius: size.value }}
37
+ />
38
+ <Stack gap="1">
39
+ <b>{size.extensions.prop}</b>
40
+ <panda.span opacity="0.7">{size.value}</panda.span>
29
41
  </Stack>
30
- ))}
31
- </Grid>
32
- )}
42
+ </Stack>
43
+ ))}
44
+ </Grid>
33
45
  </TokenGroup>
34
46
  )
35
47
  }
@@ -0,0 +1,79 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import { useStore } from '@nanostores/react'
3
+ import { panda, Stack } from '../../styled-system/jsx'
4
+ import { availableThemes } from '../lib/panda-context'
5
+ import { currentThemeStore } from '../lib/theme-store'
6
+
7
+ const titleCase = (str: string) => str.replace(/[-_]/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase())
8
+
9
+ export function ThemeSelector() {
10
+ const currentTheme = useStore(currentThemeStore)
11
+ const [isHydrated, setIsHydrated] = useState(false)
12
+
13
+ useEffect(() => {
14
+ setIsHydrated(true)
15
+ }, [])
16
+
17
+ // Only show theme selector when themes are defined in the configuration
18
+ if (availableThemes.length === 0) {
19
+ return null
20
+ }
21
+
22
+ const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
23
+ const value = e.target.value
24
+ currentThemeStore.set(value || undefined)
25
+ }
26
+
27
+ // Show a simple fallback until hydrated to prevent hydration mismatch
28
+ if (!isHydrated) {
29
+ return (
30
+ <Stack gap="2">
31
+ <panda.label fontWeight="bold" fontSize="small" opacity="0.7">
32
+ THEME
33
+ </panda.label>
34
+ <panda.select
35
+ px="3"
36
+ py="2"
37
+ borderRadius="md"
38
+ borderWidth="1"
39
+ borderColor="gray.200"
40
+ bg="white"
41
+ _dark={{ bg: 'gray.800', borderColor: 'gray.600' }}
42
+ fontSize="sm"
43
+ disabled
44
+ >
45
+ <option>Loading...</option>
46
+ </panda.select>
47
+ </Stack>
48
+ )
49
+ }
50
+
51
+ return (
52
+ <Stack gap="2">
53
+ <panda.label fontWeight="bold" fontSize="small" opacity="0.7">
54
+ THEME
55
+ </panda.label>
56
+ <panda.select
57
+ id="theme-selector"
58
+ px="3"
59
+ py="2"
60
+ borderRadius="md"
61
+ borderWidth="1"
62
+ borderColor="gray.200"
63
+ bg="white"
64
+ _dark={{ bg: 'gray.800', borderColor: 'gray.600' }}
65
+ fontSize="sm"
66
+ cursor="pointer"
67
+ value={currentTheme || ''}
68
+ onChange={handleChange}
69
+ >
70
+ <option value="">Default</option>
71
+ {availableThemes.map((theme) => (
72
+ <option key={theme} value={theme}>
73
+ {titleCase(theme)}
74
+ </option>
75
+ ))}
76
+ </panda.select>
77
+ </Stack>
78
+ )
79
+ }
@@ -0,0 +1,102 @@
1
+ import { useStore } from '@nanostores/react'
2
+ import type { TokenDataTypes } from '@pandacss/types'
3
+ import React, { useEffect, useState } from 'react'
4
+ import { css } from '../../styled-system/css'
5
+ import { Center } from '../../styled-system/jsx'
6
+ import { availableThemes } from '../lib/panda-context'
7
+ import { currentThemeStore } from '../lib/theme-store'
8
+ import { useThemeTokens } from '../lib/use-theme-tokens'
9
+ import { Colors } from './colors'
10
+ import { FontFamily } from './font-family'
11
+ import FontTokens from './font-tokens'
12
+ import { Radii } from './radii'
13
+ import Sizes from './sizes'
14
+
15
+ type TokenCategory = keyof TokenDataTypes
16
+
17
+ const hasThemes = availableThemes.length > 0
18
+
19
+ const loadingStyles = css({
20
+ width: '40px',
21
+ height: '40px',
22
+ border: '2px solid #FFF',
23
+ borderColor: 'yellow.400',
24
+ borderBottomColor: 'transparent',
25
+ borderRadius: '50%',
26
+ display: 'inline-block',
27
+ boxSizing: 'border-box',
28
+ animation: 'spin 0.6s linear infinite',
29
+ })
30
+
31
+ function Loader() {
32
+ return (
33
+ <Center height="400px">
34
+ <span className={loadingStyles} />
35
+ </Center>
36
+ )
37
+ }
38
+
39
+ function ThemeLoading({ children }: { children: React.ReactNode }) {
40
+ const [isHydrated, setIsHydrated] = useState(false)
41
+ useStore(currentThemeStore)
42
+
43
+ useEffect(() => {
44
+ setIsHydrated(true)
45
+ }, [])
46
+
47
+ if (!isHydrated) {
48
+ return <Loader />
49
+ }
50
+
51
+ return <>{children}</>
52
+ }
53
+
54
+ function createTokenPage<T extends TokenCategory>(
55
+ tokenType: T,
56
+ render: (tokens: ReturnType<typeof useThemeTokens>) => React.ReactNode,
57
+ ) {
58
+ return function TokenPage() {
59
+ const tokens = useThemeTokens(tokenType)
60
+ return hasThemes ? <ThemeLoading>{render(tokens)}</ThemeLoading> : <>{render(tokens)}</>
61
+ }
62
+ }
63
+
64
+ export const SizesPage = createTokenPage('sizes', (tokens) => <Sizes sizes={tokens} name="sizes" />)
65
+
66
+ export const SpacingPage = createTokenPage('spacing', (tokens) => <Sizes sizes={tokens} name="spacing" />)
67
+
68
+ export const FontSizesPage = createTokenPage('fontSizes', (tokens) => (
69
+ <FontTokens fontTokens={tokens} token="fontSize" />
70
+ ))
71
+
72
+ export const FontWeightsPage = createTokenPage('fontWeights', (tokens) => (
73
+ <FontTokens fontTokens={tokens} token="fontWeight" />
74
+ ))
75
+
76
+ export const LetterSpacingsPage = createTokenPage('letterSpacings', (tokens) => (
77
+ <FontTokens fontTokens={tokens} token="letterSpacing" text="The quick brown fox jumps over the lazy dog." />
78
+ ))
79
+
80
+ export const LineHeightsPage = createTokenPage('lineHeights', (tokens) => (
81
+ <FontTokens
82
+ fontTokens={tokens}
83
+ token="lineHeight"
84
+ largeText
85
+ text="Panda design system lineHeight specifies the vertical distance between two lines of text. You can preview this visually here."
86
+ />
87
+ ))
88
+
89
+ export const RadiiPage = createTokenPage('radii', (tokens) => <Radii radii={tokens} />)
90
+
91
+ export const FontsPage = createTokenPage('fonts', (tokens) => <FontFamily fonts={tokens} />)
92
+
93
+ export function ColorsPage() {
94
+ const theme = hasThemes ? useStore(currentThemeStore) : undefined
95
+ return hasThemes ? (
96
+ <ThemeLoading>
97
+ <Colors theme={theme} />
98
+ </ThemeLoading>
99
+ ) : (
100
+ <Colors />
101
+ )
102
+ }
@@ -3,6 +3,7 @@ import { Container, panda, Stack } from '../../styled-system/jsx'
3
3
  import '../../styled-system/styles.css'
4
4
  import SideNav from '../components/side-nav.astro'
5
5
  import ThemeToggle from '../components/theme-toggle.astro'
6
+ import { ThemeSelector } from '../components/theme-selector'
6
7
  import { Logo } from '../icons/logo'
7
8
 
8
9
  interface Props {
@@ -20,6 +21,9 @@ const { title } = Astro.props
20
21
  <panda.div mt="4">
21
22
  <ThemeToggle />
22
23
  </panda.div>
24
+ <panda.div mt="4">
25
+ <ThemeSelector client:load />
26
+ </panda.div>
23
27
  <SideNav />
24
28
  </Stack>
25
29
 
@@ -1,28 +1,167 @@
1
1
  import { flatten } from '@pandacss/shared'
2
2
  import { Token, TokenDictionary } from '@pandacss/token-dictionary'
3
3
  import type { TokenDataTypes } from '@pandacss/types'
4
- import { config } from 'virtual:panda'
4
+ import { config, themes as configThemes } from 'virtual:panda'
5
+
6
+ // Get current theme from URL params or use default
7
+ export const getCurrentTheme = (url?: string) => {
8
+ // Client-side: read from window.location
9
+ if (typeof window !== 'undefined') {
10
+ const params = new URLSearchParams(window.location.search)
11
+ return params.get('theme') || undefined
12
+ }
13
+
14
+ // Server-side: read from provided URL
15
+ if (url) {
16
+ const urlObj = new URL(url)
17
+ const params = new URLSearchParams(urlObj.search)
18
+ return params.get('theme') || undefined
19
+ }
20
+
21
+ return undefined
22
+ }
23
+
24
+ export const availableThemes = Object.keys(configThemes || {})
25
+
26
+ // Deep merge objects recursively
27
+ const deepMerge = (target: any, source: any): any => {
28
+ if (!source) return target
29
+ if (!target) return source
30
+
31
+ const result = { ...target }
32
+
33
+ for (const key in source) {
34
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
35
+ result[key] = deepMerge(target[key], source[key])
36
+ } else {
37
+ result[key] = source[key]
38
+ }
39
+ }
40
+
41
+ return result
42
+ }
43
+
44
+ // Merge base theme with selected theme
45
+ export const getActiveTheme = (themeName?: string) => {
46
+ const baseTheme = config.theme ?? {}
47
+
48
+ if (!themeName || !configThemes?.[themeName]) {
49
+ return baseTheme
50
+ }
51
+
52
+ const selectedTheme = configThemes[themeName]
53
+
54
+ // Deep merge theme configuration
55
+ return {
56
+ ...baseTheme,
57
+ tokens: deepMerge(baseTheme.tokens, selectedTheme.tokens),
58
+ semanticTokens: deepMerge(baseTheme.semanticTokens, selectedTheme.semanticTokens),
59
+ textStyles: deepMerge(baseTheme.textStyles, selectedTheme.textStyles),
60
+ layerStyles: deepMerge(baseTheme.layerStyles, selectedTheme.layerStyles),
61
+ }
62
+ }
5
63
 
6
64
  export const theme = config.theme ?? {}
7
65
 
8
66
  export const tokens = new TokenDictionary(theme).init()
9
67
 
10
- export const getTokens = (category: keyof TokenDataTypes): Token[] => {
11
- const map = tokens.view.categoryMap.get(category) ?? new Map()
68
+ export const getTokens = (category: keyof TokenDataTypes, themeName?: string): Token[] => {
69
+ const activeTheme = getActiveTheme(themeName)
70
+ const themeTokens = new TokenDictionary(activeTheme).init()
71
+ const map = themeTokens.view.categoryMap.get(category) ?? new Map()
12
72
  return Array.from(map.values())
13
73
  }
14
74
 
75
+ // Get token paths defined in a theme for a category
76
+ const getThemeDefinedPaths = (themeName: string, category: keyof TokenDataTypes): Set<string> => {
77
+ const selectedTheme = configThemes?.[themeName]
78
+ if (!selectedTheme) return new Set()
79
+
80
+ const paths = new Set<string>()
81
+
82
+ // Collect paths from tokens (e.g., colors.red.500)
83
+ const collectPaths = (obj: any, prefix: string[] = []) => {
84
+ if (!obj || typeof obj !== 'object') return
85
+ for (const key in obj) {
86
+ const path = [...prefix, key]
87
+ if (obj[key]?.value !== undefined) {
88
+ paths.add(path.join('.'))
89
+ } else {
90
+ collectPaths(obj[key], path)
91
+ }
92
+ }
93
+ }
94
+
95
+ collectPaths(selectedTheme.tokens?.[category])
96
+ collectPaths(selectedTheme.semanticTokens?.[category])
97
+
98
+ return paths
99
+ }
100
+
101
+ export const getThemeRelevantTokens = (category: keyof TokenDataTypes, themeName?: string): Token[] => {
102
+ if (!themeName || !configThemes?.[themeName]) {
103
+ // No theme selected - return all base tokens
104
+ return getTokens(category)
105
+ }
106
+
107
+ const activeTheme = getActiveTheme(themeName)
108
+ const themeTokens = new TokenDictionary(activeTheme).init()
109
+
110
+ // Get all tokens for the category from merged theme
111
+ const allTokens = Array.from(themeTokens.view.categoryMap.get(category)?.values() || [])
112
+
113
+ // Get paths explicitly defined in this theme
114
+ const definedPaths = getThemeDefinedPaths(themeName, category)
115
+
116
+ // If theme doesn't define any tokens for this category, return empty
117
+ if (definedPaths.size === 0) {
118
+ return []
119
+ }
120
+
121
+ // Filter to tokens whose path matches theme-defined paths
122
+ // Also include semantic tokens (isConditional) that are defined in the theme
123
+ const themeTokensList = allTokens.filter((token) => {
124
+ const tokenPath = token.path.join('.')
125
+ return definedPaths.has(tokenPath) || token.isConditional
126
+ })
127
+
128
+ // Collect referenced tokens (for semantic tokens that reference base tokens)
129
+ const referencedNames = new Set<string>()
130
+ themeTokensList.forEach((token) => {
131
+ if (token.extensions.references) {
132
+ Object.keys(token.extensions.references).forEach((name) => referencedNames.add(name))
133
+ }
134
+ if (token.extensions.conditions) {
135
+ Object.values(token.extensions.conditions).forEach((val) => {
136
+ if (typeof val === 'string') {
137
+ themeTokens.getReferences(val).forEach((ref) => referencedNames.add(ref.name))
138
+ }
139
+ })
140
+ }
141
+ })
142
+
143
+ // Include referenced base tokens
144
+ const referencedTokens = allTokens.filter(
145
+ (token) => referencedNames.has(token.name) && !themeTokensList.includes(token),
146
+ )
147
+
148
+ return [...themeTokensList, ...referencedTokens]
149
+ }
150
+
15
151
  type Colors = Array<{
16
152
  label: string
17
153
  value: string
18
154
  }>
19
155
 
20
- export const colors: Colors = getTokens('colors')
21
- .filter((color) => !color.isConditional && !color.extensions.isVirtual)
22
- .map((color) => ({
23
- label: color.extensions.prop,
24
- value: color.value,
25
- }))
156
+ export const getColors = (themeName?: string): Colors =>
157
+ getTokens('colors', themeName)
158
+ .filter((color) => !color.isConditional && !color.extensions.isVirtual)
159
+ .map((color) => ({
160
+ label: color.extensions.prop,
161
+ value: color.value,
162
+ }))
163
+
164
+ export const colors: Colors = getColors()
26
165
 
27
166
  export const textStyles = flatten(theme?.textStyles ?? {})
28
167
 
@@ -0,0 +1,40 @@
1
+ import { atom } from 'nanostores'
2
+
3
+ const THEME_STORAGE_KEY = 'panda-studio-theme'
4
+
5
+ // Store for the current theme
6
+ export const currentThemeStore = atom<string | undefined>(undefined)
7
+
8
+ // Get theme from localStorage only (client-side only)
9
+ const getInitialTheme = (): string | undefined => {
10
+ if (typeof window === 'undefined') return undefined
11
+
12
+ try {
13
+ const storedTheme = localStorage.getItem(THEME_STORAGE_KEY)
14
+ return storedTheme || undefined
15
+ } catch {
16
+ // Handle localStorage access errors
17
+ return undefined
18
+ }
19
+ }
20
+
21
+ // Initialize theme from localStorage
22
+ if (typeof window !== 'undefined') {
23
+ const theme = getInitialTheme()
24
+ currentThemeStore.set(theme)
25
+ }
26
+
27
+ // Update localStorage when theme changes (client-side only)
28
+ currentThemeStore.listen((value) => {
29
+ if (typeof window !== 'undefined') {
30
+ try {
31
+ if (value) {
32
+ localStorage.setItem(THEME_STORAGE_KEY, value)
33
+ } else {
34
+ localStorage.removeItem(THEME_STORAGE_KEY)
35
+ }
36
+ } catch {
37
+ // Handle localStorage access errors
38
+ }
39
+ }
40
+ })
package/src/lib/url.ts CHANGED
@@ -1,3 +1,9 @@
1
1
  export const getUrl = (path?: string) => {
2
- return [import.meta.env.BASE_URL, path].filter(Boolean).join('')
2
+ const base = import.meta.env.BASE_URL || '/'
3
+ if (!path) return base
4
+
5
+ // Remove leading slash from path if base already ends with slash
6
+ const cleanPath = base.endsWith('/') && path.startsWith('/') ? path.slice(1) : path
7
+
8
+ return base + cleanPath
3
9
  }
@@ -1,5 +1,6 @@
1
+ import { TokenDictionary } from '@pandacss/token-dictionary'
1
2
  import type { Token, TokenExtensions } from '@pandacss/token-dictionary'
2
- import { useState } from 'react'
3
+ import { useState, useMemo, useDeferredValue } from 'react'
3
4
  import * as context from './panda-context'
4
5
 
5
6
  interface Color {
@@ -67,47 +68,65 @@ const getSemanticTokens = (allTokens: Token[], filterMethod?: (token: ColorToken
67
68
  )
68
69
  }
69
70
 
70
- const allTokens = context.tokens.allTokens
71
- const colors = context.getTokens('colors')
72
-
73
- export const useColorDocs = () => {
71
+ export const useColorDocs = (theme?: string) => {
74
72
  const [filterQuery, setFilterQuery] = useState('')
73
+ const deferredQuery = useDeferredValue(filterQuery)
74
+
75
+ // Memoize token data based on theme to ensure reactivity
76
+ const { colors, allTokens } = useMemo(() => {
77
+ // Get tokens based on provided theme (filtered to show only relevant tokens when theme is active)
78
+ const colors = context.getThemeRelevantTokens('colors', theme)
79
+
80
+ // Get all tokens for the active theme
81
+ const activeTheme = context.getActiveTheme(theme)
82
+ const themeTokens = new TokenDictionary(activeTheme).init()
83
+ const allTokens = themeTokens.allTokens
84
+
85
+ return { colors, allTokens }
86
+ }, [theme])
87
+
88
+ // Memoize processed data based on theme and filter query (deferred for performance)
89
+ const processedData = useMemo(() => {
90
+ const filterMethod = (token: ColorToken) => {
91
+ return [
92
+ ...token.path,
93
+ token.originalValue,
94
+ token.description,
95
+ token.value,
96
+ token.name,
97
+ token.extensions?.var,
98
+ token.extensions?.prop,
99
+ ...Object.values(token.extensions?.conditions || {}),
100
+ ]
101
+ .filter(Boolean)
102
+ .some((prop) => prop.includes(deferredQuery))
103
+ }
104
+
105
+ const colorsInCategories = groupByColorPalette(colors as ColorToken[], filterMethod)
106
+ const uncategorizedColors = colorsInCategories[UNCATEGORIZED_ID]
107
+
108
+ const categorizedColors = Object.entries<any[]>(colorsInCategories).filter(
109
+ ([category]) => category !== UNCATEGORIZED_ID,
110
+ )
75
111
 
76
- const filterMethod = (token: ColorToken) => {
77
- return [
78
- ...token.path,
79
- token.originalValue,
80
- token.description,
81
- token.value,
82
- token.name,
83
- token.extensions?.var,
84
- token.extensions?.prop,
85
- ...Object.values(token.extensions?.conditions || {}),
86
- ]
87
- .filter(Boolean)
88
- .some((prop) => prop.includes(filterQuery))
89
- }
90
-
91
- const colorsInCategories = groupByColorPalette(colors as ColorToken[], filterMethod)
92
- const uncategorizedColors = colorsInCategories[UNCATEGORIZED_ID]
93
-
94
- const categorizedColors = Object.entries<any[]>(colorsInCategories).filter(
95
- ([category]) => category !== UNCATEGORIZED_ID,
96
- )
97
-
98
- const semanticTokens = Object.entries<Record<string, any>>(getSemanticTokens(allTokens, filterMethod)) as [
99
- string,
100
- Record<string, ColorToken>,
101
- ][]
102
- const hasResults =
103
- !!categorizedColors.length || !!uncategorizedColors?.length || !!Object.values(semanticTokens).length
112
+ const semanticTokens = Object.entries<Record<string, any>>(getSemanticTokens(allTokens, filterMethod)) as [
113
+ string,
114
+ Record<string, ColorToken>,
115
+ ][]
116
+ const hasResults =
117
+ !!categorizedColors.length || !!uncategorizedColors?.length || !!Object.values(semanticTokens).length
118
+
119
+ return {
120
+ uncategorizedColors,
121
+ categorizedColors,
122
+ semanticTokens,
123
+ hasResults,
124
+ }
125
+ }, [colors, allTokens, deferredQuery])
104
126
 
105
127
  return {
106
128
  filterQuery,
107
129
  setFilterQuery,
108
- uncategorizedColors,
109
- categorizedColors,
110
- semanticTokens,
111
- hasResults,
130
+ ...processedData,
112
131
  }
113
132
  }
@@ -0,0 +1,15 @@
1
+ import { useMemo } from 'react'
2
+ import { useStore } from '@nanostores/react'
3
+ import type { TokenDataTypes } from '@pandacss/types'
4
+ import * as context from './panda-context'
5
+ import { currentThemeStore } from './theme-store'
6
+
7
+ export const useThemeTokens = (category: keyof TokenDataTypes) => {
8
+ const theme = useStore(currentThemeStore)
9
+
10
+ const tokens = useMemo(() => {
11
+ return context.getThemeRelevantTokens(category, theme)
12
+ }, [category, theme])
13
+
14
+ return tokens
15
+ }
@@ -2,4 +2,5 @@ declare module 'virtual:*' {
2
2
  export const config: import('@pandacss/types').UserConfig
3
3
  export const textStyles: Record<string, string>
4
4
  export const layerStyles: Record<string, string>
5
+ export const themes: Record<string, any>
5
6
  }
@@ -1,11 +1,11 @@
1
1
  ---
2
- import Colors from '../components/colors'
2
+ import { ColorsPage } from '../components/token-pages'
3
3
  import Layout from '../layouts/Layout.astro'
4
4
  import Sidebar from '../layouts/Sidebar.astro'
5
5
  ---
6
6
 
7
7
  <Layout>
8
8
  <Sidebar title="Colors">
9
- <Colors client:load />
9
+ <ColorsPage client:load />
10
10
  </Sidebar>
11
11
  </Layout>
@@ -1,15 +1,12 @@
1
1
  ---
2
- import FontTokens from '../components/font-tokens'
3
2
  import Layout from '../layouts/Layout.astro'
4
3
  import Sidebar from '../layouts/Sidebar.astro'
5
- import * as context from '../lib/panda-context'
6
-
7
- const fontSizes = context.getTokens('fontSizes')
4
+ import { FontSizesPage } from '../components/token-pages'
8
5
  ---
9
6
 
10
7
  <Layout>
11
8
  <Sidebar title="Font Sizes">
12
- <FontTokens client:load fontTokens={fontSizes} token="fontSize" />
9
+ <FontSizesPage client:load />
13
10
  </Sidebar>
14
11
  </Layout>
15
12
 
@@ -1,14 +1,11 @@
1
1
  ---
2
- import FontTokens from '../components/font-tokens'
2
+ import { FontWeightsPage } from '../components/token-pages'
3
3
  import Layout from '../layouts/Layout.astro'
4
4
  import Sidebar from '../layouts/Sidebar.astro'
5
- import * as context from '../lib/panda-context'
6
-
7
- const fontWeights = context.getTokens('fontWeights')
8
5
  ---
9
6
 
10
7
  <Layout>
11
8
  <Sidebar title="Font Weights">
12
- <FontTokens client:load fontTokens={fontWeights} token="fontWeight" />
9
+ <FontWeightsPage client:load />
13
10
  </Sidebar>
14
11
  </Layout>
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  import Layout from '../layouts/Layout.astro'
3
3
  import Sidebar from '../layouts/Sidebar.astro'
4
- import { FontFamily } from '../components/font-family'
4
+ import { FontsPage } from '../components/token-pages'
5
5
  ---
6
6
 
7
7
  <Layout>
8
- <Sidebar title="Fonts">
9
- <FontFamily />
10
- </Sidebar>
8
+ <Sidebar title="Fonts">
9
+ <FontsPage client:load />
10
+ </Sidebar>
11
11
  </Layout>
@@ -1,21 +1,12 @@
1
1
  ---
2
- import FontTokens from '../components/font-tokens'
2
+ import { LetterSpacingsPage } from '../components/token-pages'
3
3
  import Layout from '../layouts/Layout.astro'
4
4
  import Sidebar from '../layouts/Sidebar.astro'
5
-
6
- import * as context from '../lib/panda-context'
7
-
8
- const tokens = context.getTokens('letterSpacings')
9
5
  ---
10
6
 
11
7
  <Layout>
12
8
  <Sidebar title="Letter Spacings">
13
- <FontTokens
14
- client:load
15
- fontTokens={tokens}
16
- token="letterSpacing"
17
- text="The quick brown fox jumps over the lazy dog."
18
- />
9
+ <LetterSpacingsPage client:load />
19
10
  </Sidebar>
20
11
  </Layout>
21
12
 
@@ -1,20 +1,11 @@
1
1
  ---
2
- import FontTokens from '../components/font-tokens'
2
+ import { LineHeightsPage } from '../components/token-pages'
3
3
  import Layout from '../layouts/Layout.astro'
4
4
  import Sidebar from '../layouts/Sidebar.astro'
5
- import * as context from '../lib/panda-context'
6
-
7
- const tokens = context.getTokens('lineHeights')
8
5
  ---
9
6
 
10
7
  <Layout>
11
8
  <Sidebar title="Line Heights">
12
- <FontTokens
13
- client:load
14
- fontTokens={tokens}
15
- token="lineHeight"
16
- largeText
17
- text={`Panda design system lineHeight specifies the vertical distance between two lines of text. You can peveiew this visually here.`}
18
- />
9
+ <LineHeightsPage client:load />
19
10
  </Sidebar>
20
11
  </Layout>
@@ -1,11 +1,11 @@
1
1
  ---
2
- import Radii from '../components/radii'
2
+ import { RadiiPage } from '../components/token-pages'
3
3
  import Layout from '../layouts/Layout.astro'
4
4
  import Sidebar from '../layouts/Sidebar.astro'
5
5
  ---
6
6
 
7
7
  <Layout>
8
8
  <Sidebar title="Border Radius">
9
- <Radii client:load />
9
+ <RadiiPage client:load />
10
10
  </Sidebar>
11
11
  </Layout>
@@ -1,14 +1,11 @@
1
1
  ---
2
- import Sizes from '../components/sizes'
2
+ import { SizesPage } from '../components/token-pages'
3
3
  import Layout from '../layouts/Layout.astro'
4
4
  import Sidebar from '../layouts/Sidebar.astro'
5
- import * as context from '../lib/panda-context'
6
-
7
- const tokens = context.getTokens('sizes')
8
5
  ---
9
6
 
10
7
  <Layout>
11
8
  <Sidebar title="Sizes">
12
- <Sizes sizes={tokens} name="sizes" client:load />
9
+ <SizesPage client:load />
13
10
  </Sidebar>
14
11
  </Layout>
@@ -1,14 +1,11 @@
1
1
  ---
2
- import Sizes from '../components/sizes'
2
+ import { SpacingPage } from '../components/token-pages'
3
3
  import Layout from '../layouts/Layout.astro'
4
4
  import Sidebar from '../layouts/Sidebar.astro'
5
- import * as context from '../lib/panda-context'
6
-
7
- const tokens = context.getTokens('spacing')
8
5
  ---
9
6
 
10
7
  <Layout>
11
8
  <Sidebar title="Spacing">
12
- <Sizes sizes={tokens} name="spacing" client:load />
9
+ <SpacingPage client:load />
13
10
  </Sidebar>
14
11
  </Layout>
@@ -744,6 +744,18 @@
744
744
  background: var(--colors-neutral-800);
745
745
  }
746
746
 
747
+ .bg_white {
748
+ background: var(--colors-white);
749
+ }
750
+
751
+ .bd_2px_solid_\#FFF {
752
+ border: 2px solid #FFF;
753
+ }
754
+
755
+ .anim_spin_0\.6s_linear_infinite {
756
+ animation: spin 0.6s linear infinite;
757
+ }
758
+
747
759
  .gap_8px {
748
760
  gap: 8px;
749
761
  }
@@ -912,6 +924,14 @@
912
924
  grid-column: span 5 / span 5;
913
925
  }
914
926
 
927
+ .bd-w_1 {
928
+ border-width: 1px;
929
+ }
930
+
931
+ .bd-c_gray\.200 {
932
+ border-color: var(--colors-gray-200);
933
+ }
934
+
915
935
  .px_2 {
916
936
  padding-inline: var(--spacing-2);
917
937
  }
@@ -920,6 +940,14 @@
920
940
  gap: var(--spacing-12);
921
941
  }
922
942
 
943
+ .bd-c_yellow\.400 {
944
+ border-color: var(--colors-yellow-400);
945
+ }
946
+
947
+ .bdr_50\% {
948
+ border-radius: 50%;
949
+ }
950
+
923
951
  .ring_0 {
924
952
  outline: 0;
925
953
  }
@@ -1187,10 +1215,19 @@
1187
1215
  margin: -1px;
1188
1216
  overflow: hidden;
1189
1217
  clip: rect(0, 0, 0, 0);
1218
+ white-space: nowrap;
1190
1219
  border-width: 0;
1191
1220
  }
1192
1221
 
1193
- .sr_true,.white-space_nowrap {
1222
+ .d_inline-block {
1223
+ display: inline-block;
1224
+ }
1225
+
1226
+ .bx-s_border-box {
1227
+ box-sizing: border-box;
1228
+ }
1229
+
1230
+ .white-space_nowrap {
1194
1231
  white-space: nowrap;
1195
1232
  }
1196
1233
 
@@ -1314,6 +1351,22 @@
1314
1351
  margin-bottom: var(--spacing-4);
1315
1352
  }
1316
1353
 
1354
+ .w_40px {
1355
+ width: 40px;
1356
+ }
1357
+
1358
+ .h_40px {
1359
+ height: 40px;
1360
+ }
1361
+
1362
+ .bd-b-c_transparent {
1363
+ border-bottom-color: var(--colors-transparent);
1364
+ }
1365
+
1366
+ .h_400px {
1367
+ height: 400px;
1368
+ }
1369
+
1317
1370
  .pt_28 {
1318
1371
  padding-top: var(--spacing-28);
1319
1372
  }
@@ -1362,10 +1415,18 @@
1362
1415
  padding-bottom: var(--spacing-10);
1363
1416
  }
1364
1417
 
1418
+ .dark .dark\:bg_gray\.800 {
1419
+ background: var(--colors-gray-800);
1420
+ }
1421
+
1365
1422
  .before\:bdr_sm::before {
1366
1423
  border-radius: var(--radii-sm);
1367
1424
  }
1368
1425
 
1426
+ .dark .dark\:bd-c_gray\.600 {
1427
+ border-color: var(--colors-gray-600);
1428
+ }
1429
+
1369
1430
  .before\:content_\'\'::before {
1370
1431
  content: '';
1371
1432
  }