@pandacss/studio 1.5.1 → 1.6.1
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 +1 -1
- package/package.json +17 -15
- package/src/components/colors.tsx +6 -2
- package/src/components/font-family.tsx +8 -4
- package/src/components/radii.tsx +34 -22
- package/src/components/theme-selector.tsx +79 -0
- package/src/components/token-pages.tsx +102 -0
- package/src/layouts/Sidebar.astro +4 -0
- package/src/lib/panda-context.ts +148 -9
- package/src/lib/theme-store.ts +40 -0
- package/src/lib/url.ts +7 -1
- package/src/lib/use-color-docs.ts +56 -37
- package/src/lib/use-theme-tokens.ts +15 -0
- package/src/lib/virtual-panda.d.ts +1 -0
- package/src/pages/colors.astro +2 -2
- package/src/pages/font-sizes.astro +2 -5
- package/src/pages/font-weights.astro +2 -5
- package/src/pages/fonts.astro +4 -4
- package/src/pages/letter-spacings.astro +2 -11
- package/src/pages/line-heights.astro +2 -11
- package/src/pages/radii.astro +2 -2
- package/src/pages/sizes.astro +2 -5
- package/src/pages/spacing.astro +2 -5
- package/styled-system/styles.css +62 -1
package/dist/studio.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.
|
|
1
|
+
// ../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.3_@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.
|
|
3
|
+
"version": "1.6.1",
|
|
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.
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"@pandacss/
|
|
54
|
-
"@pandacss/
|
|
55
|
-
"@pandacss/
|
|
56
|
-
"@pandacss/
|
|
46
|
+
"@astrojs/react": "4.4.2",
|
|
47
|
+
"@nanostores/react": "^1.0.0",
|
|
48
|
+
"astro": "5.16.3",
|
|
49
|
+
"nanostores": "^1.1.0",
|
|
50
|
+
"react": "19.2.0",
|
|
51
|
+
"react-dom": "19.2.0",
|
|
52
|
+
"vite": "7.2.6",
|
|
53
|
+
"@pandacss/astro-plugin-studio": "1.6.1",
|
|
54
|
+
"@pandacss/config": "1.6.1",
|
|
55
|
+
"@pandacss/logger": "1.6.1",
|
|
56
|
+
"@pandacss/shared": "1.6.1",
|
|
57
|
+
"@pandacss/token-dictionary": "1.6.1",
|
|
58
|
+
"@pandacss/types": "1.6.1"
|
|
57
59
|
},
|
|
58
60
|
"devDependencies": {
|
|
59
|
-
"@
|
|
60
|
-
"@types/react
|
|
61
|
-
"@
|
|
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
|
-
|
|
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
|
-
|
|
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 />}>
|
package/src/components/radii.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
|
|
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
|
-
|
|
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
|
|
package/src/lib/panda-context.ts
CHANGED
|
@@ -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
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/pages/colors.astro
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
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
|
-
<
|
|
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
|
|
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
|
-
<
|
|
9
|
+
<FontSizesPage client:load />
|
|
13
10
|
</Sidebar>
|
|
14
11
|
</Layout>
|
|
15
12
|
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
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
|
-
<
|
|
9
|
+
<FontWeightsPage client:load />
|
|
13
10
|
</Sidebar>
|
|
14
11
|
</Layout>
|
package/src/pages/fonts.astro
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Layout from '../layouts/Layout.astro'
|
|
3
3
|
import Sidebar from '../layouts/Sidebar.astro'
|
|
4
|
-
import {
|
|
4
|
+
import { FontsPage } from '../components/token-pages'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<Layout>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
<Sidebar title="Fonts">
|
|
9
|
+
<FontsPage client:load />
|
|
10
|
+
</Sidebar>
|
|
11
11
|
</Layout>
|
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
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
|
-
<
|
|
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
|
|
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
|
-
<
|
|
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>
|
package/src/pages/radii.astro
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
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
|
-
<
|
|
9
|
+
<RadiiPage client:load />
|
|
10
10
|
</Sidebar>
|
|
11
11
|
</Layout>
|
package/src/pages/sizes.astro
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
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
|
-
<
|
|
9
|
+
<SizesPage client:load />
|
|
13
10
|
</Sidebar>
|
|
14
11
|
</Layout>
|
package/src/pages/spacing.astro
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
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
|
-
<
|
|
9
|
+
<SpacingPage client:load />
|
|
13
10
|
</Sidebar>
|
|
14
11
|
</Layout>
|
package/styled-system/styles.css
CHANGED
|
@@ -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
|
-
.
|
|
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
|
}
|