@stonedogcode/style 0.9.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.
Files changed (75) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +18 -0
  3. package/README.md +699 -0
  4. package/package.json +95 -0
  5. package/src/components/DictationControls.tsx +141 -0
  6. package/src/components/DictationPrompt.tsx +78 -0
  7. package/src/components/StyledBox.tsx +174 -0
  8. package/src/components/StyledButton.tsx +144 -0
  9. package/src/components/StyledCollapsible.tsx +127 -0
  10. package/src/components/StyledDefinitionList.tsx +134 -0
  11. package/src/components/StyledFieldset.tsx +157 -0
  12. package/src/components/StyledFlex.tsx +13 -0
  13. package/src/components/StyledFooter.tsx +399 -0
  14. package/src/components/StyledFormLabel.tsx +141 -0
  15. package/src/components/StyledGrid.tsx +109 -0
  16. package/src/components/StyledGridItem.tsx +19 -0
  17. package/src/components/StyledHStack.tsx +145 -0
  18. package/src/components/StyledHeading.tsx +79 -0
  19. package/src/components/StyledHrRule.tsx +33 -0
  20. package/src/components/StyledIcon.tsx +172 -0
  21. package/src/components/StyledIconButton.tsx +135 -0
  22. package/src/components/StyledInputBool.tsx +81 -0
  23. package/src/components/StyledInputRadio.tsx +141 -0
  24. package/src/components/StyledInputSelect.tsx +115 -0
  25. package/src/components/StyledInputSlider.tsx +83 -0
  26. package/src/components/StyledInputText.tsx +146 -0
  27. package/src/components/StyledInputTextArea.tsx +119 -0
  28. package/src/components/StyledInputToggle.tsx +224 -0
  29. package/src/components/StyledList.tsx +188 -0
  30. package/src/components/StyledScrollbar.tsx +53 -0
  31. package/src/components/StyledSearch.tsx +78 -0
  32. package/src/components/StyledSeparator.tsx +38 -0
  33. package/src/components/StyledSidebar.tsx +555 -0
  34. package/src/components/StyledSimpleGrid.tsx +99 -0
  35. package/src/components/StyledSparkLine.tsx +119 -0
  36. package/src/components/StyledSpinner.tsx +91 -0
  37. package/src/components/StyledStack.tsx +62 -0
  38. package/src/components/StyledText.tsx +99 -0
  39. package/src/components/StyledTooltip.tsx +398 -0
  40. package/src/components/StyledVStack.tsx +143 -0
  41. package/src/components/TitleLogo.tsx +223 -0
  42. package/src/components/create-icon.tsx +66 -0
  43. package/src/components/create-intent-button.tsx +134 -0
  44. package/src/components/dictation.ts +71 -0
  45. package/src/components/intent-buttons.ts +154 -0
  46. package/src/config/can-hover.ts +75 -0
  47. package/src/config/density.ts +138 -0
  48. package/src/config/font-size.ts +113 -0
  49. package/src/config/intent-icons.tsx +116 -0
  50. package/src/config/logger.ts +60 -0
  51. package/src/config/style-config.tsx +263 -0
  52. package/src/config/types.ts +137 -0
  53. package/src/index.ts +259 -0
  54. package/src/preset/index.ts +243 -0
  55. package/src/preset/recipes/arrows.ts +29 -0
  56. package/src/preset/recipes/box.ts +122 -0
  57. package/src/preset/recipes/button.ts +161 -0
  58. package/src/preset/recipes/dl-list.ts +109 -0
  59. package/src/preset/recipes/drawer.ts +125 -0
  60. package/src/preset/recipes/form.ts +95 -0
  61. package/src/preset/recipes/icon-button.ts +161 -0
  62. package/src/preset/recipes/icon.ts +34 -0
  63. package/src/preset/recipes/input-bool.ts +184 -0
  64. package/src/preset/recipes/input-dropdown.ts +93 -0
  65. package/src/preset/recipes/input-radio.ts +158 -0
  66. package/src/preset/recipes/input-surface.ts +152 -0
  67. package/src/preset/recipes/input-text.ts +17 -0
  68. package/src/preset/recipes/list.ts +196 -0
  69. package/src/preset/recipes/menu.ts +28 -0
  70. package/src/preset/recipes/separator.ts +89 -0
  71. package/src/preset/recipes/stack.ts +89 -0
  72. package/src/preset/recipes/striped.ts +34 -0
  73. package/src/preset/recipes/text.ts +41 -0
  74. package/src/preset/recipes/tooltip.ts +77 -0
  75. package/src/preset/semantic-variables.ts +283 -0
@@ -0,0 +1,89 @@
1
+ import { defineRecipe } from '@pandacss/dev'
2
+
3
+ export const stackRecipe = defineRecipe({
4
+ className: 'stack',
5
+ base: {
6
+ display: 'flex',
7
+ alignItems: 'center',
8
+ },
9
+ variants: {
10
+ direction: {
11
+ row: {
12
+ flexDirection: 'row',
13
+ },
14
+ column: {
15
+ flexDirection: 'column',
16
+ },
17
+ },
18
+ justify: {
19
+ start: {
20
+ justifyContent: 'flex-start',
21
+ },
22
+ center: {
23
+ justifyContent: 'center',
24
+ },
25
+ end: {
26
+ justifyContent: 'flex-end',
27
+ },
28
+ between: {
29
+ justifyContent: 'space-between',
30
+ },
31
+ },
32
+ gap: {
33
+ '0': { gap: '0' },
34
+ '1': { gap: '1' },
35
+ '2': { gap: '2' },
36
+ '3': { gap: '3' },
37
+ '4': { gap: '4' },
38
+ '5': { gap: '5' },
39
+ '6': { gap: '6' },
40
+ },
41
+ variant: {
42
+ solid: {
43
+ bg: "textPrimary",
44
+ color: "textPrimary",
45
+ borderColor: "borderBgPrimary",
46
+ },
47
+ outline: {
48
+ borderColor: "borderBgSecondary",
49
+ color: "textPrimary",
50
+ _hover: {
51
+ bg: "textPrimary",
52
+ },
53
+ },
54
+ aurora: {
55
+ // Quoted token names are CSS strings, so this gradient was invalid
56
+ // and never painted (NEH-301). `{colors.X}` substitutes.
57
+ backgroundImage: `linear-gradient(to right, {colors.boxBgAccent}, {colors.boxBgSecondary})`,
58
+ color: "textPrimary",
59
+ borderColor: "transparent",
60
+ },
61
+ glass: {
62
+ backdropFilter: "blur(10px)",
63
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
64
+ border: "1px solid rgba(255, 255, 255, 0.2)",
65
+ color: "textPrimary",
66
+ },
67
+ matte: {
68
+ // `secondary` was undefined vocabulary — never painted (NEH-301).
69
+ bg: "boxBgSecondary",
70
+ color: "textSecondary",
71
+ borderColor: "borderBgPrimary",
72
+ },
73
+ ghost: {
74
+ bg: "boxBgSecondary",
75
+ border: "none",
76
+ },
77
+ none: {
78
+ border: "none",
79
+ backgroundColor: "inherit",
80
+ },
81
+ unstyled: {
82
+ border: "none",
83
+ p: "0",
84
+ m: "0",
85
+ backgroundColor: "inherit",
86
+ },
87
+ }
88
+ },
89
+ })
@@ -0,0 +1,34 @@
1
+ import { defineRecipe } from "@pandacss/dev";
2
+
3
+ export const stripedRecipe = defineRecipe({
4
+ className: "striped",
5
+ description: "A recipe for applying striped styles to a list of items",
6
+ base: {
7
+ "& > *:nth-child(odd)": {
8
+ background: "boxBgSecondary",
9
+ height: "100%",
10
+ width: "100%",
11
+ },
12
+ "& > *:nth-child(even)": {
13
+ background: "boxBgSecondary",
14
+ color: "textAccent",
15
+ },
16
+ },
17
+ variants: {
18
+ // Optional: Add a variant if you sometimes want to stripe odd rows instead
19
+ stripe: {
20
+ odd: {
21
+ "& > *:nth-child(even)": {
22
+ background: "transparent",
23
+ },
24
+ "& > *:nth-child(odd)": {
25
+ background: "boxBgSecondary",
26
+ },
27
+ },
28
+ even: {}, // This is the default behavior from 'base'
29
+ },
30
+ },
31
+ defaultVariants: {
32
+ stripe: "even",
33
+ },
34
+ });
@@ -0,0 +1,41 @@
1
+ import { defineRecipe } from "@pandacss/dev";
2
+
3
+ export const textRecipe = defineRecipe({
4
+ className: "text",
5
+ description: "The styles for the Text component",
6
+ base: {
7
+ color: "textPrimary",
8
+ // The theme's body face (NEH-289). StyledHeading renders through this
9
+ // recipe too and asks for the heading face at its call site instead.
10
+ fontFamily: "body",
11
+ },
12
+ variants: {
13
+ variant: {
14
+ unstyled: {
15
+ color: "inherit",
16
+ },
17
+ pop: {
18
+ color: "textPop",
19
+ },
20
+ warning: {
21
+ bg: "textPop",
22
+ py: "6",
23
+ px: {
24
+ base: "3",
25
+ md: "6",
26
+ },
27
+ color: "textWarning",
28
+ fontWeight: "bold",
29
+ },
30
+ error: {
31
+ bg: "textPop",
32
+ py: "2",
33
+ px: {
34
+ base: "1",
35
+ md: "3",
36
+ },
37
+ color: "textError",
38
+ },
39
+ },
40
+ },
41
+ });
@@ -0,0 +1,77 @@
1
+ import { defineRecipe } from "@pandacss/dev";
2
+
3
+ export const tooltipRecipe = defineRecipe({
4
+ className: "tooltip",
5
+ base: {
6
+ position: "absolute",
7
+ zIndex: 9999,
8
+ pointerEvents: "none",
9
+ color: "buttonTextSecondary",
10
+ padding: "2px",
11
+ borderRadius: "md",
12
+ fontSize: "var(--font-sizes-lg, 1rem)",
13
+ boxShadow: "lg",
14
+ whiteSpace: "pre-line",
15
+ maxWidth: {
16
+ base: "340px",
17
+ md: "550px",
18
+ },
19
+ left: "50%",
20
+ top: "100%",
21
+ transformOrigin: "top center",
22
+ bg: "boxBgPrimary"
23
+ },
24
+ variants: {
25
+ variant: {
26
+ solid: {
27
+ bg: "boxBgPrimary",
28
+ color: "buttonTextSecondary",
29
+ borderColor: "borderBgPrimary",
30
+ },
31
+ outline: {
32
+ bg: "boxBgPrimary",
33
+ color: "textPrimary",
34
+ borderColor: "borderBgPrimary",
35
+ border: "1px solid",
36
+ },
37
+ aurora: {
38
+ bg: "boxBgPrimary",
39
+ color: "textSecondary",
40
+ borderColor: "borderBgSecondary",
41
+ border: "1px solid",
42
+ },
43
+ glass: {
44
+ bg: "boxBgPrimary",
45
+ color: "buttonTextPrimary",
46
+ border: "1px solid",
47
+ borderColor: "borderBgPrimary",
48
+ },
49
+ matte: {
50
+ bg: "boxBgPrimary",
51
+ color: "textSecondary",
52
+ borderColor: "borderBgSecondary",
53
+ border: "1px solid",
54
+ },
55
+ ghost: {
56
+ bg: "boxBgPrimary",
57
+ color: "buttonTextSecondary",
58
+ },
59
+ link: {
60
+ bg: "boxBgPrimary",
61
+ color: "textPrimary",
62
+ border: "1px solid",
63
+ borderColor: "borderBgPrimary",
64
+ },
65
+ none: {
66
+ bg: "boxBgPrimary",
67
+ color: "textMain",
68
+ border: "1px solid",
69
+ borderColor: "borderBgPrimary",
70
+ },
71
+ unstyled: {
72
+ bg: "boxBgPrimary",
73
+ color: "textMain",
74
+ },
75
+ },
76
+ },
77
+ });
@@ -0,0 +1,283 @@
1
+ /**
2
+ * The token contract.
3
+ *
4
+ * Every colour in this design system is a Panda token whose *value* is a bare
5
+ * CSS custom property — `boxBgPrimary` resolves to `var(--hopper-box-primary-bg)`.
6
+ * Nothing here defines a colour. The host application is what sets those custom
7
+ * properties at runtime (from a theme picker, a database, a `<style>` block, a
8
+ * stylesheet — the system does not care), and that indirection is the whole
9
+ * reason a single component library can wear two products' branding.
10
+ *
11
+ * Two consequences worth internalising before changing anything below:
12
+ *
13
+ * 1. **A token with no matching custom property renders as nothing.** There is
14
+ * no fallback colour by design — a silent black-on-black box is a louder bug
15
+ * than a silent wrong-shade box, and it surfaces during development instead
16
+ * of in production. Consumers MUST define every property this file names.
17
+ * `requiredCssCustomProperties()` exists so a consumer can assert that.
18
+ *
19
+ * 2. **The names are public API.** A host app's theme data keys off them. Adding
20
+ * a token is backwards-compatible; renaming or removing one silently breaks
21
+ * whatever was painting it, because CSS has no import errors.
22
+ */
23
+
24
+ /** A Panda token name mapped to the CSS custom-property suffix it reads. */
25
+ type TokenMap = Record<string, string>;
26
+
27
+ /**
28
+ * `<panda token name>` → `<custom property suffix>`.
29
+ *
30
+ * The suffix is everything after the prefix, so `"box-primary-bg"` becomes
31
+ * `var(--hopper-box-primary-bg)` at the default prefix. The two naming schemes
32
+ * differ on purpose and are not derivable from each other: token names are
33
+ * camelCase and read component-first (`boxBgPrimary`), custom properties are
34
+ * kebab-case and read scope-first (`box-primary-bg`). Keeping the mapping
35
+ * explicit is what lets either side be renamed without touching the other.
36
+ */
37
+ const COLOR_TOKENS: TokenMap = {
38
+ // Text that carries meaning on its own — errors, warnings, emphasis.
39
+ textPop: "text-pop-text",
40
+ textError: "text-error-text",
41
+ textWarning: "text-warning-text",
42
+
43
+ // Text on each surface. Pair these with the matching `boxBg*` — see
44
+ // TEXT_BACKGROUND_PAIRS for which goes with which.
45
+ textMain: "box-main-text",
46
+ textPrimary: "box-primary-text",
47
+ textSecondary: "box-secondary-text",
48
+ textAccent: "box-accent-text",
49
+
50
+ // Arrows / carets.
51
+ arrowBgPrimary: "arrow-primary-bg",
52
+ arrowBgSecondary: "arrow-secondary-bg",
53
+ arrowBgAccent: "arrow-accent-bg",
54
+ arrowBorderPrimary: "arrow-primary-border",
55
+ arrowBorderSecondary: "arrow-secondary-border",
56
+ arrowBorderAccent: "arrow-accent-border",
57
+
58
+ // Surfaces.
59
+ boxBgMain: "box-main-bg",
60
+ boxBgPrimary: "box-primary-bg",
61
+ boxBgSecondary: "box-secondary-bg",
62
+ boxBgAccent: "box-accent-bg",
63
+ boxInfo: "box-info-bg",
64
+
65
+ // Borders.
66
+ borderBgPrimary: "box-primary-border",
67
+ borderBgSecondary: "box-secondary-border",
68
+ borderBgAccent: "box-accent-border",
69
+
70
+ // Shadows.
71
+ boxshadowBgPrimary: "shadow-primary-bg",
72
+ boxshadowBgSecondary: "shadow-secondary-bg",
73
+ boxshadowBgAccent: "shadow-accent-bg",
74
+
75
+ // Buttons.
76
+ buttonBgPrimary: "button-primary-bg",
77
+ buttonBgSecondary: "button-secondary-bg",
78
+ buttonBgAccent: "button-accent-bg",
79
+ buttonBgPrimaryHover: "button-primary-hover-bg",
80
+ buttonBgSecondaryHover: "button-secondary-hover-bg",
81
+ buttonBgAccentHover: "button-accent-hover-bg",
82
+ buttonTextPrimary: "button-primary-text",
83
+ buttonTextSecondary: "button-secondary-text",
84
+ buttonTextAccent: "button-accent-text",
85
+ buttonTextPrimaryHover: "button-primary-hover-text",
86
+ buttonTextSecondaryHover: "button-secondary-hover-text",
87
+ buttonTextAccentHover: "button-accent-hover-text",
88
+ buttonBgPlain: "button-plain-bg",
89
+ buttonTextPlain: "button-plain-text",
90
+
91
+ // Icons.
92
+ iconBgPrimary: "icon-primary-bg",
93
+ iconBgSecondary: "icon-secondary-bg",
94
+ iconBgAccent: "icon-accent-bg",
95
+ iconBgPrimaryHover: "icon-primary-hover-bg",
96
+ iconBgSecondaryHover: "icon-secondary-hover-bg",
97
+ iconBgAccentHover: "icon-accent-hover-bg",
98
+ };
99
+
100
+ /**
101
+ * Host-provided *layout* properties, as `token name → [suffix, fallback]`.
102
+ *
103
+ * These differ from the colours in one way that matters: they carry a fallback,
104
+ * so a host that never sets them still renders something sensible rather than
105
+ * nothing. They are routed through the token layer anyway, for the same reason
106
+ * the colours are — a recipe writing `var(--hopper-…)` directly would ignore a
107
+ * consumer's chosen prefix, silently, and no amount of correct configuration on
108
+ * their side would fix it.
109
+ */
110
+ const SIZE_TOKENS: Record<string, [suffix: string, fallback: string]> = {
111
+ /** Height budget for a dashboard widget; also caps dropdown menus. */
112
+ widgetBaseHeight: ["widget-base-height", "240px"],
113
+ };
114
+
115
+ /**
116
+ * The host's typeface, as `token name → [suffix, fallback]` (NEH-289).
117
+ *
118
+ * `stonedog-theme` resolves a theme's fonts into `--<prefix>-font-family-*` and
119
+ * `--<prefix>-font-weight-*`. Until these tokens existed nothing in this package
120
+ * read them, so a themed typeface was emitted and inert — it stopped at the
121
+ * theme package's edge.
122
+ *
123
+ * They follow the SIZE_TOKENS pattern (a fallback) rather than the COLOR_TOKENS
124
+ * one (no fallback), and the asymmetry is deliberate. An undefined colour paints
125
+ * an invisible element, which is a louder bug than a wrong shade and is exactly
126
+ * what you want to trip over in development. Type is the opposite: an undefined
127
+ * font falls back to the browser's own face and the page stays readable. So
128
+ * these are NOT in `requiredCssCustomProperties()` — putting them there would
129
+ * break every existing host (none of them define these) for no safety gain, and
130
+ * would move the `required === colours` identity that the contract test pins on
131
+ * both sides.
132
+ *
133
+ * Consequence worth stating plainly: a host that says nothing about type keeps
134
+ * its own, and this stays purely additive.
135
+ *
136
+ * This package still owns SHAPE. The size scale, line height and density are
137
+ * unchanged and stay here; family and weight are BRAND, and this is only the
138
+ * wire that lets the theme deliver them.
139
+ */
140
+ const FONT_FAMILY_TOKENS: Record<string, [suffix: string, fallback: string]> = {
141
+ /** Body copy, and every form control that would otherwise use the UA font. */
142
+ body: ["font-family-body", "inherit"],
143
+ /** Headings, so a theme can pair a display face with its body face. */
144
+ heading: ["font-family-heading", "inherit"],
145
+ /**
146
+ * Monospace. `inherit` would be wrong here — the whole point of asking for
147
+ * mono is not wanting the inherited proportional face — so it falls back to
148
+ * the system mono stack instead.
149
+ */
150
+ mono: ["font-family-mono", "ui-monospace, SFMono-Regular, Menlo, monospace"],
151
+ };
152
+
153
+ /**
154
+ * Weight steps, matching what the recipes already name.
155
+ *
156
+ * The union is closed on purpose (`stonedog-theme` owns it): a recipe needing a
157
+ * step outside these four is a change there first. The names deliberately match
158
+ * Panda's built-in `fontWeights` tokens, so every existing `fontWeight: "bold"`
159
+ * in a recipe starts reading the theme without a single call site moving —
160
+ * `fontWeight` resolves through this token category already.
161
+ */
162
+ const FONT_WEIGHT_TOKENS: Record<string, [suffix: string, fallback: string]> = {
163
+ normal: ["font-weight-normal", "400"],
164
+ medium: ["font-weight-medium", "500"],
165
+ semibold: ["font-weight-semibold", "600"],
166
+ bold: ["font-weight-bold", "700"],
167
+ };
168
+
169
+ /** Shared shape for the token maps that carry a fallback. */
170
+ function createFallbackTokens(
171
+ map: Record<string, [suffix: string, fallback: string]>,
172
+ prefix: string,
173
+ ): Record<string, { value: string }> {
174
+ return Object.fromEntries(
175
+ Object.entries(map).map(([token, [suffix, fallback]]) => [
176
+ token,
177
+ { value: `var(--${prefix}-${suffix}, ${fallback})` },
178
+ ]),
179
+ );
180
+ }
181
+
182
+ /** Panda `fonts` token definitions, bound to a custom-property prefix. */
183
+ export function createSemanticFonts(
184
+ prefix: string = DEFAULT_CSS_VAR_PREFIX,
185
+ ): Record<string, { value: string }> {
186
+ return createFallbackTokens(FONT_FAMILY_TOKENS, prefix);
187
+ }
188
+
189
+ /** Panda `fontWeights` token definitions, bound to a custom-property prefix. */
190
+ export function createSemanticFontWeights(
191
+ prefix: string = DEFAULT_CSS_VAR_PREFIX,
192
+ ): Record<string, { value: string }> {
193
+ return createFallbackTokens(FONT_WEIGHT_TOKENS, prefix);
194
+ }
195
+
196
+ /** Every Panda font-family token this preset defines. */
197
+ export function fontTokenNames(): string[] {
198
+ return Object.keys(FONT_FAMILY_TOKENS);
199
+ }
200
+
201
+ /** Every Panda font-weight token this preset defines. */
202
+ export function fontWeightTokenNames(): string[] {
203
+ return Object.keys(FONT_WEIGHT_TOKENS);
204
+ }
205
+
206
+ /** Panda size-token definitions, bound to a custom-property prefix. */
207
+ export function createSemanticSizes(
208
+ prefix: string = DEFAULT_CSS_VAR_PREFIX,
209
+ ): Record<string, { value: string }> {
210
+ return Object.fromEntries(
211
+ Object.entries(SIZE_TOKENS).map(([token, [suffix, fallback]]) => [
212
+ token,
213
+ { value: `var(--${prefix}-${suffix}, ${fallback})` },
214
+ ]),
215
+ );
216
+ }
217
+
218
+ /**
219
+ * The default custom-property prefix.
220
+ *
221
+ * `hopper` rather than something neutral because HopperGuard's theme engine
222
+ * already emits `--hopper-*` and a rename there would be a coordinated change
223
+ * across a running product's stored theme data. A second consumer that wants
224
+ * its own namespace passes `cssVarPrefix` — see `stonedogStylePreset`.
225
+ */
226
+ export const DEFAULT_CSS_VAR_PREFIX = "hopper";
227
+
228
+ /**
229
+ * Text/background pairings, for contrast checking.
230
+ *
231
+ * A theme is only usable if each text token has enough contrast against the
232
+ * surface it actually lands on, and that relationship is not inferable from the
233
+ * names — `textMain` sits on `boxBgMain`, but `buttonTextPlain` sits on
234
+ * `buttonBgPlain`, not on any `box*`. A contrast checker that guesses will pass
235
+ * themes that are unreadable in practice.
236
+ */
237
+ export const TEXT_BACKGROUND_PAIRS: Readonly<Record<string, string>> = {
238
+ textMain: "boxBgMain",
239
+ textPrimary: "boxBgPrimary",
240
+ textSecondary: "boxBgSecondary",
241
+ textAccent: "boxBgAccent",
242
+ buttonTextPrimary: "buttonBgPrimary",
243
+ buttonTextSecondary: "buttonBgSecondary",
244
+ buttonTextAccent: "buttonBgAccent",
245
+ buttonTextPrimaryHover: "buttonBgPrimaryHover",
246
+ buttonTextSecondaryHover: "buttonBgSecondaryHover",
247
+ buttonTextAccentHover: "buttonBgAccentHover",
248
+ buttonTextPlain: "buttonBgPlain",
249
+ };
250
+
251
+ /** The surface token a given text token is meant to be read against. */
252
+ export function getBackgroundForText(textToken: string): string | undefined {
253
+ return TEXT_BACKGROUND_PAIRS[textToken];
254
+ }
255
+
256
+ /** Every Panda colour token this preset defines. */
257
+ export function colorTokenNames(): string[] {
258
+ return Object.keys(COLOR_TOKENS);
259
+ }
260
+
261
+ /**
262
+ * Every CSS custom property a consumer must define for the system to render.
263
+ *
264
+ * Intended for a startup assertion or a theme-validation test: a theme missing
265
+ * one of these paints nothing, with no error anywhere.
266
+ */
267
+ export function requiredCssCustomProperties(
268
+ prefix: string = DEFAULT_CSS_VAR_PREFIX,
269
+ ): string[] {
270
+ return Object.values(COLOR_TOKENS).map((suffix) => `--${prefix}-${suffix}`);
271
+ }
272
+
273
+ /** Panda colour-token definitions, bound to a custom-property prefix. */
274
+ export function createSemanticColors(
275
+ prefix: string = DEFAULT_CSS_VAR_PREFIX,
276
+ ): Record<string, { value: string }> {
277
+ return Object.fromEntries(
278
+ Object.entries(COLOR_TOKENS).map(([token, suffix]) => [
279
+ token,
280
+ { value: `var(--${prefix}-${suffix})` },
281
+ ]),
282
+ );
283
+ }