@glyphui/ui 0.0.0-stage → 0.1.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 (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +137 -2
  3. package/dist/data-table.d.ts +110 -0
  4. package/dist/data-table.js +225 -0
  5. package/dist/field-DXZD0l8w.js +107 -0
  6. package/dist/field-context-B8cYheXz.js +321 -0
  7. package/dist/form.d.ts +79 -0
  8. package/dist/form.js +84 -0
  9. package/dist/index-BWK4czIG.d.ts +233 -0
  10. package/dist/index.d.ts +2155 -0
  11. package/dist/index.js +1529 -0
  12. package/dist/registry-BGSJoiLq.d.ts +9 -0
  13. package/dist/states-BxPjCUxz.js +287 -0
  14. package/dist/tv-DuMh7UH2.d.ts +6 -0
  15. package/package.json +113 -3
  16. package/src/components/avatar/avatar.styles.ts +22 -0
  17. package/src/components/avatar/avatar.tsx +62 -0
  18. package/src/components/avatar/index.ts +2 -0
  19. package/src/components/badge/badge.styles.ts +69 -0
  20. package/src/components/badge/badge.tsx +30 -0
  21. package/src/components/badge/index.ts +2 -0
  22. package/src/components/button/button.styles.ts +43 -0
  23. package/src/components/button/button.tsx +77 -0
  24. package/src/components/button/index.ts +2 -0
  25. package/src/components/card/card.styles.ts +32 -0
  26. package/src/components/card/card.tsx +91 -0
  27. package/src/components/card/index.ts +11 -0
  28. package/src/components/checkbox/checkbox.tsx +39 -0
  29. package/src/components/checkbox/index.ts +1 -0
  30. package/src/components/container/container.tsx +35 -0
  31. package/src/components/container/index.ts +1 -0
  32. package/src/components/eyebrow/eyebrow.styles.ts +14 -0
  33. package/src/components/eyebrow/eyebrow.tsx +19 -0
  34. package/src/components/eyebrow/index.ts +2 -0
  35. package/src/components/field/field-context.ts +40 -0
  36. package/src/components/field/field.styles.ts +23 -0
  37. package/src/components/field/field.tsx +109 -0
  38. package/src/components/field/index.ts +3 -0
  39. package/src/components/heading/heading.styles.ts +16 -0
  40. package/src/components/heading/heading.tsx +26 -0
  41. package/src/components/heading/index.ts +2 -0
  42. package/src/components/input/index.ts +2 -0
  43. package/src/components/input/input.styles.ts +36 -0
  44. package/src/components/input/input.tsx +33 -0
  45. package/src/components/kbd/index.ts +1 -0
  46. package/src/components/kbd/kbd.tsx +21 -0
  47. package/src/components/label/index.ts +1 -0
  48. package/src/components/label/label.tsx +21 -0
  49. package/src/components/section/index.ts +1 -0
  50. package/src/components/section/section.tsx +39 -0
  51. package/src/components/segmented-control/index.ts +6 -0
  52. package/src/components/segmented-control/segmented-control.tsx +79 -0
  53. package/src/components/select/index.ts +1 -0
  54. package/src/components/select/select.tsx +133 -0
  55. package/src/components/separator/index.ts +1 -0
  56. package/src/components/separator/separator.tsx +29 -0
  57. package/src/components/skeleton/index.ts +1 -0
  58. package/src/components/skeleton/skeleton.tsx +20 -0
  59. package/src/components/spinner/index.ts +2 -0
  60. package/src/components/spinner/spinner.styles.ts +19 -0
  61. package/src/components/spinner/spinner.tsx +42 -0
  62. package/src/components/stack/index.ts +1 -0
  63. package/src/components/stack/stack.tsx +62 -0
  64. package/src/components/switch/index.ts +1 -0
  65. package/src/components/switch/switch.tsx +48 -0
  66. package/src/components/table/index.ts +10 -0
  67. package/src/components/table/table.tsx +90 -0
  68. package/src/components/tabs/index.ts +1 -0
  69. package/src/components/tabs/tabs.tsx +92 -0
  70. package/src/components/text/index.ts +2 -0
  71. package/src/components/text/text.styles.ts +32 -0
  72. package/src/components/text/text.tsx +17 -0
  73. package/src/components/textarea/index.ts +1 -0
  74. package/src/components/textarea/textarea.tsx +27 -0
  75. package/src/components/tooltip/index.ts +1 -0
  76. package/src/components/tooltip/tooltip.tsx +42 -0
  77. package/src/hooks/index.ts +4 -0
  78. package/src/hooks/use-component-config.ts +42 -0
  79. package/src/hooks/use-controllable-state.ts +27 -0
  80. package/src/hooks/use-media-query.ts +14 -0
  81. package/src/hooks/use-scrolled.ts +14 -0
  82. package/src/index.ts +52 -0
  83. package/src/internal/icons.tsx +99 -0
  84. package/src/patterns/app-shell/app-shell-context.ts +27 -0
  85. package/src/patterns/app-shell/app-shell.styles.ts +127 -0
  86. package/src/patterns/app-shell/app-shell.tsx +81 -0
  87. package/src/patterns/app-shell/index.ts +16 -0
  88. package/src/patterns/app-shell/navbar.tsx +119 -0
  89. package/src/patterns/app-shell/page-header.tsx +30 -0
  90. package/src/patterns/app-shell/sidebar.tsx +163 -0
  91. package/src/patterns/async/async.tsx +83 -0
  92. package/src/patterns/async/index.ts +10 -0
  93. package/src/patterns/async/states.tsx +128 -0
  94. package/src/patterns/data-table/data-table.tsx +367 -0
  95. package/src/patterns/data-table/index.ts +2 -0
  96. package/src/patterns/form/form.tsx +150 -0
  97. package/src/patterns/form/index.ts +13 -0
  98. package/src/provider/color-mode.tsx +133 -0
  99. package/src/provider/glyph-context.ts +42 -0
  100. package/src/provider/glyph-provider.tsx +41 -0
  101. package/src/provider/index.ts +18 -0
  102. package/src/provider/theme-scope.tsx +36 -0
  103. package/src/provider/theme-style.tsx +19 -0
  104. package/src/register.ts +69 -0
  105. package/src/themes.ts +56 -0
  106. package/src/utils/cn.ts +12 -0
  107. package/src/utils/compose-refs.ts +11 -0
  108. package/src/utils/registry.ts +9 -0
  109. package/src/utils/styles.ts +13 -0
  110. package/src/utils/tv.ts +7 -0
  111. package/src/utils/tw-merge-config.ts +14 -0
  112. package/styles.css +178 -0
package/dist/index.js ADDED
@@ -0,0 +1,1529 @@
1
+ "use client";
2
+ import { A as useGlyph, C as popover, D as twMergeConfig, E as tv, O as useComponentConfig, S as focusRing, T as transition, _ as Button, b as Spinner, f as MenuIcon, g as XIcon, j as useLinkComponent, k as GlyphContext, m as PanelLeftIcon, n as useFieldControl, o as CheckIcon, s as ChevronDownIcon, t as FieldContext, v as IconButton, w as themedBorder, x as spinnerStyles, y as buttonStyles } from "./field-context-B8cYheXz.js";
3
+ import { _ as inputStyles, a as Table, c as TableCell, d as TableRow, f as tableStyles, g as fieldControl, h as Input, i as stateStyles, l as TableHead, m as skeletonStyles, n as ErrorState, o as TableBody, p as Skeleton, r as LoadingState, s as TableCaption, t as EmptyState, u as TableHeader, v as Checkbox, y as checkboxStyles } from "./states-BxPjCUxz.js";
4
+ import { i as labelStyles, n as fieldStyles, r as Label, t as Field } from "./field-DXZD0l8w.js";
5
+ import { defineTheme, defineTheme as defineTheme$1, presets as presets$1, scopeSelector, themeToCss, themeToCss as themeToCss$1, tokenVar } from "@glyphui/tokens";
6
+ import { Avatar as Avatar$1, Dialog, Select as Select$1, Separator as Separator$1, Slot, Switch as Switch$1, Tabs as Tabs$1, ToggleGroup, Tooltip as Tooltip$1 } from "radix-ui";
7
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
8
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
+ import { clsx } from "clsx";
10
+ import { extendTailwindMerge } from "tailwind-merge";
11
+ //#region src/components/avatar/avatar.styles.ts
12
+ const avatarStyles = tv({
13
+ slots: {
14
+ root: "relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-pill bg-muted align-middle",
15
+ image: "size-full object-cover",
16
+ fallback: "flex size-full items-center justify-center font-medium text-muted-foreground",
17
+ group: "flex items-center -space-x-2 [&>[data-slot=avatar]]:ring-2 [&>[data-slot=avatar]]:ring-background"
18
+ },
19
+ variants: { size: {
20
+ xs: {
21
+ root: "size-5",
22
+ fallback: "text-[0.5625rem]"
23
+ },
24
+ sm: {
25
+ root: "size-6",
26
+ fallback: "text-[0.625rem]"
27
+ },
28
+ md: {
29
+ root: "size-8",
30
+ fallback: "text-caption"
31
+ },
32
+ lg: {
33
+ root: "size-11",
34
+ fallback: "text-small"
35
+ }
36
+ } },
37
+ defaultVariants: { size: "md" }
38
+ });
39
+ //#endregion
40
+ //#region src/components/avatar/avatar.tsx
41
+ function initials(name) {
42
+ const parts = name.trim().split(/\s+/);
43
+ return ((parts[0]?.[0] ?? "") + (parts.length > 1 ? parts.at(-1)?.[0] ?? "" : "")).toUpperCase();
44
+ }
45
+ /** A stable hue per name, so fallback avatars are distinguishable but not random. */
46
+ function hue(name) {
47
+ let h = 0;
48
+ for (const char of name) h = (h * 31 + char.charCodeAt(0)) % 360;
49
+ return h;
50
+ }
51
+ function Avatar(inProps) {
52
+ const [props, slots] = useComponentConfig("Avatar", inProps);
53
+ const { name, src, size, className, style, ...rest } = props;
54
+ const s = avatarStyles({ size });
55
+ const tint = {
56
+ "--avatar-hue": hue(name),
57
+ ...style
58
+ };
59
+ return /* @__PURE__ */ jsxs(Avatar$1.Root, {
60
+ "data-slot": "avatar",
61
+ className: s.root({ class: [slots.root, className] }),
62
+ style: tint,
63
+ ...rest,
64
+ children: [src && /* @__PURE__ */ jsx(Avatar$1.Image, {
65
+ src,
66
+ alt: name,
67
+ className: s.image({ class: slots.image })
68
+ }), /* @__PURE__ */ jsx(Avatar$1.Fallback, {
69
+ className: s.fallback({ class: ["bg-[oklch(68%_0.11_var(--avatar-hue)/0.22)] text-foreground", slots.fallback] }),
70
+ "aria-label": src ? void 0 : name,
71
+ children: initials(name)
72
+ })]
73
+ });
74
+ }
75
+ function AvatarGroup({ className, ...props }) {
76
+ const [, slots] = useComponentConfig("Avatar", {});
77
+ return /* @__PURE__ */ jsx("div", {
78
+ "data-slot": "avatar-group",
79
+ className: avatarStyles().group({ class: [slots.group, className] }),
80
+ ...props
81
+ });
82
+ }
83
+ //#endregion
84
+ //#region src/components/badge/badge.styles.ts
85
+ const tones = {
86
+ neutral: {
87
+ soft: "bg-muted text-muted-foreground",
88
+ solid: "bg-secondary text-secondary-foreground",
89
+ outline: "border-border-strong text-foreground"
90
+ },
91
+ primary: {
92
+ soft: "bg-primary/14 text-[color-mix(in_oklab,var(--color-primary)_62%,var(--color-foreground))]",
93
+ solid: "bg-primary text-primary-foreground",
94
+ outline: "border-primary/50 text-[color-mix(in_oklab,var(--color-primary)_62%,var(--color-foreground))]"
95
+ },
96
+ success: {
97
+ soft: "bg-success/14 text-[color-mix(in_oklab,var(--color-success)_62%,var(--color-foreground))]",
98
+ solid: "bg-success text-success-foreground",
99
+ outline: "border-success/50 text-[color-mix(in_oklab,var(--color-success)_62%,var(--color-foreground))]"
100
+ },
101
+ warning: {
102
+ soft: "bg-warning/16 text-[color-mix(in_oklab,var(--color-warning)_62%,var(--color-foreground))]",
103
+ solid: "bg-warning text-warning-foreground",
104
+ outline: "border-warning/50 text-[color-mix(in_oklab,var(--color-warning)_62%,var(--color-foreground))]"
105
+ },
106
+ danger: {
107
+ soft: "bg-danger/14 text-[color-mix(in_oklab,var(--color-danger)_62%,var(--color-foreground))]",
108
+ solid: "bg-danger text-danger-foreground",
109
+ outline: "border-danger/50 text-[color-mix(in_oklab,var(--color-danger)_62%,var(--color-foreground))]"
110
+ },
111
+ info: {
112
+ soft: "bg-info/14 text-[color-mix(in_oklab,var(--color-info)_62%,var(--color-foreground))]",
113
+ solid: "bg-info text-info-foreground",
114
+ outline: "border-info/50 text-[color-mix(in_oklab,var(--color-info)_62%,var(--color-foreground))]"
115
+ }
116
+ };
117
+ const badgeStyles = tv({
118
+ slots: {
119
+ base: [
120
+ "inline-flex h-5.5 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-pill border-transparent px-2",
121
+ "text-caption tabular-nums",
122
+ themedBorder
123
+ ],
124
+ dot: "size-1.5 shrink-0 rounded-pill bg-current"
125
+ },
126
+ variants: {
127
+ tone: {
128
+ neutral: {},
129
+ primary: {},
130
+ success: {},
131
+ warning: {},
132
+ danger: {},
133
+ info: {}
134
+ },
135
+ variant: {
136
+ soft: {},
137
+ solid: {},
138
+ outline: { base: "bg-transparent" }
139
+ }
140
+ },
141
+ compoundVariants: Object.keys(tones).flatMap((tone) => Object.keys(tones[tone]).map((variant) => ({
142
+ tone,
143
+ variant,
144
+ class: { base: tones[tone][variant] }
145
+ }))),
146
+ defaultVariants: {
147
+ tone: "neutral",
148
+ variant: "soft"
149
+ }
150
+ });
151
+ //#endregion
152
+ //#region src/components/badge/badge.tsx
153
+ function Badge(inProps) {
154
+ const [props, slots] = useComponentConfig("Badge", inProps);
155
+ const { tone, variant, dot, asChild, className, children, ...rest } = props;
156
+ const s = badgeStyles({
157
+ tone,
158
+ variant
159
+ });
160
+ const Comp = asChild ? Slot.Root : "span";
161
+ return /* @__PURE__ */ jsxs(Comp, {
162
+ "data-slot": "badge",
163
+ "data-tone": tone ?? "neutral",
164
+ className: s.base({ class: [slots.base, className] }),
165
+ ...rest,
166
+ children: [dot && /* @__PURE__ */ jsx("span", {
167
+ "data-slot": "badge-dot",
168
+ className: s.dot({ class: slots.dot }),
169
+ "aria-hidden": "true"
170
+ }), /* @__PURE__ */ jsx(Slot.Slottable, { children })]
171
+ });
172
+ }
173
+ //#endregion
174
+ //#region src/components/card/card.styles.ts
175
+ const cardStyles = tv({
176
+ slots: {
177
+ root: [
178
+ "flex flex-col rounded-lg text-foreground",
179
+ themedBorder,
180
+ transition
181
+ ],
182
+ header: "flex flex-col gap-1",
183
+ title: "font-display text-h4 text-foreground",
184
+ description: "text-small text-muted-foreground",
185
+ body: "min-w-0",
186
+ footer: "mt-auto flex items-center gap-2 pt-1"
187
+ },
188
+ variants: {
189
+ variant: {
190
+ outline: { root: "border-border bg-surface" },
191
+ elevated: { root: "border-border bg-surface shadow-md" },
192
+ ghost: { root: "border-transparent bg-subtle" },
193
+ interactive: { root: "cursor-pointer border-border bg-surface shadow-sm hover:-translate-y-0.5 hover:border-border-strong hover:shadow-lg" }
194
+ },
195
+ padding: {
196
+ none: { root: "gap-0 p-0" },
197
+ sm: { root: "gap-3 p-4" },
198
+ md: { root: "gap-4 p-6" },
199
+ lg: { root: "gap-5 p-8" }
200
+ }
201
+ },
202
+ defaultVariants: {
203
+ variant: "elevated",
204
+ padding: "md"
205
+ }
206
+ });
207
+ //#endregion
208
+ //#region src/components/card/card.tsx
209
+ const CardContext = createContext(null);
210
+ function useCardParts() {
211
+ return useContext(CardContext) ?? {
212
+ s: cardStyles(),
213
+ slots: {}
214
+ };
215
+ }
216
+ /** A surface that groups related content. Compose with CardHeader, CardTitle, CardBody, CardFooter. */
217
+ function Card(inProps) {
218
+ const [props, slots] = useComponentConfig("Card", inProps);
219
+ const { variant, padding, asChild, className, ...rest } = props;
220
+ const s = cardStyles({
221
+ variant,
222
+ padding
223
+ });
224
+ const Comp = asChild ? Slot.Root : "div";
225
+ return /* @__PURE__ */ jsx(CardContext.Provider, {
226
+ value: {
227
+ s,
228
+ slots
229
+ },
230
+ children: /* @__PURE__ */ jsx(Comp, {
231
+ "data-slot": "card",
232
+ "data-variant": variant,
233
+ className: s.root({ class: [slots.root, className] }),
234
+ ...rest
235
+ })
236
+ });
237
+ }
238
+ function CardHeader({ className, ...props }) {
239
+ const { s, slots } = useCardParts();
240
+ return /* @__PURE__ */ jsx("div", {
241
+ "data-slot": "card-header",
242
+ className: s.header({ class: [slots.header, className] }),
243
+ ...props
244
+ });
245
+ }
246
+ function CardTitle({ asChild, className, ...props }) {
247
+ const { s, slots } = useCardParts();
248
+ const Comp = asChild ? Slot.Root : "h3";
249
+ return /* @__PURE__ */ jsx(Comp, {
250
+ "data-slot": "card-title",
251
+ className: s.title({ class: [slots.title, className] }),
252
+ ...props
253
+ });
254
+ }
255
+ function CardDescription({ className, ...props }) {
256
+ const { s, slots } = useCardParts();
257
+ return /* @__PURE__ */ jsx("p", {
258
+ "data-slot": "card-description",
259
+ className: s.description({ class: [slots.description, className] }),
260
+ ...props
261
+ });
262
+ }
263
+ function CardBody({ className, ...props }) {
264
+ const { s, slots } = useCardParts();
265
+ return /* @__PURE__ */ jsx("div", {
266
+ "data-slot": "card-body",
267
+ className: s.body({ class: [slots.body, className] }),
268
+ ...props
269
+ });
270
+ }
271
+ function CardFooter({ className, ...props }) {
272
+ const { s, slots } = useCardParts();
273
+ return /* @__PURE__ */ jsx("div", {
274
+ "data-slot": "card-footer",
275
+ className: s.footer({ class: [slots.footer, className] }),
276
+ ...props
277
+ });
278
+ }
279
+ //#endregion
280
+ //#region src/components/container/container.tsx
281
+ const containerStyles = tv({
282
+ base: "mx-auto w-full px-4 sm:px-6",
283
+ variants: { size: {
284
+ sm: "max-w-2xl",
285
+ md: "max-w-3xl",
286
+ lg: "max-w-5xl",
287
+ xl: "max-w-7xl",
288
+ full: "max-w-none"
289
+ } },
290
+ defaultVariants: { size: "xl" }
291
+ });
292
+ /** Centers content at a readable max width with consistent side gutters. */
293
+ function Container({ size, asChild, className, ...props }) {
294
+ const Comp = asChild ? Slot.Root : "div";
295
+ return /* @__PURE__ */ jsx(Comp, {
296
+ "data-slot": "container",
297
+ className: containerStyles({
298
+ size,
299
+ class: className
300
+ }),
301
+ ...props
302
+ });
303
+ }
304
+ //#endregion
305
+ //#region src/components/eyebrow/eyebrow.styles.ts
306
+ const eyebrowStyles = tv({
307
+ slots: { base: "font-mono text-caption uppercase tracking-[0.12em]" },
308
+ variants: { tone: {
309
+ muted: { base: "text-muted-foreground" },
310
+ primary: { base: "text-primary" }
311
+ } },
312
+ defaultVariants: { tone: "muted" }
313
+ });
314
+ //#endregion
315
+ //#region src/components/eyebrow/eyebrow.tsx
316
+ /** Small uppercase label that sits above a heading. */
317
+ function Eyebrow(inProps) {
318
+ const [props, slots] = useComponentConfig("Eyebrow", inProps);
319
+ const { tone, asChild, className, ...rest } = props;
320
+ const s = eyebrowStyles({ tone });
321
+ const Comp = asChild ? Slot.Root : "p";
322
+ return /* @__PURE__ */ jsx(Comp, {
323
+ "data-slot": "eyebrow",
324
+ className: s.base({ class: [slots.base, className] }),
325
+ ...rest
326
+ });
327
+ }
328
+ //#endregion
329
+ //#region src/components/heading/heading.styles.ts
330
+ const headingStyles = tv({
331
+ slots: { base: "font-display text-balance text-foreground" },
332
+ variants: { size: {
333
+ display: { base: "text-display" },
334
+ h1: { base: "text-h1" },
335
+ h2: { base: "text-h2" },
336
+ h3: { base: "text-h3" },
337
+ h4: { base: "text-h4" }
338
+ } }
339
+ });
340
+ //#endregion
341
+ //#region src/components/heading/heading.tsx
342
+ const sizeForLevel = {
343
+ 1: "h1",
344
+ 2: "h2",
345
+ 3: "h3",
346
+ 4: "h4",
347
+ 5: "h4",
348
+ 6: "h4"
349
+ };
350
+ /** A heading whose semantic level and visual size are set independently. */
351
+ function Heading(inProps) {
352
+ const [props, slots] = useComponentConfig("Heading", inProps);
353
+ const { level = 2, size, asChild, className, ...rest } = props;
354
+ const s = headingStyles({ size: size ?? sizeForLevel[level] });
355
+ const Comp = asChild ? Slot.Root : `h${level}`;
356
+ return /* @__PURE__ */ jsx(Comp, {
357
+ "data-slot": "heading",
358
+ className: s.base({ class: [slots.base, className] }),
359
+ ...rest
360
+ });
361
+ }
362
+ //#endregion
363
+ //#region src/components/kbd/kbd.tsx
364
+ const kbdStyles = tv({ slots: { base: "inline-flex h-5 min-w-5 items-center justify-center rounded-xs border border-border bg-muted px-1 font-mono text-[0.6875rem] font-medium text-muted-foreground shadow-xs" } });
365
+ /** A keyboard key, e.g. `<Kbd>⌘K</Kbd>`. */
366
+ function Kbd(inProps) {
367
+ const [{ className, ...rest }, slots] = useComponentConfig("Kbd", inProps);
368
+ return /* @__PURE__ */ jsx("kbd", {
369
+ "data-slot": "kbd",
370
+ className: kbdStyles().base({ class: [slots.base, className] }),
371
+ ...rest
372
+ });
373
+ }
374
+ //#endregion
375
+ //#region src/components/section/section.tsx
376
+ const sectionStyles = tv({
377
+ base: "w-full",
378
+ variants: {
379
+ spacing: {
380
+ none: "py-0",
381
+ sm: "py-10 sm:py-12",
382
+ md: "py-16 sm:py-20",
383
+ lg: "py-20 sm:py-28"
384
+ },
385
+ tone: {
386
+ default: "",
387
+ subtle: "bg-subtle"
388
+ },
389
+ divided: { true: "border-t-(length:--glyph-border-width) border-border" }
390
+ },
391
+ defaultVariants: {
392
+ spacing: "md",
393
+ tone: "default"
394
+ }
395
+ });
396
+ /** A full-width page band with consistent vertical rhythm. Put a Container inside. */
397
+ function Section({ spacing, tone, divided, asChild, className, ...props }) {
398
+ const Comp = asChild ? Slot.Root : "section";
399
+ return /* @__PURE__ */ jsx(Comp, {
400
+ "data-slot": "section",
401
+ className: sectionStyles({
402
+ spacing,
403
+ tone,
404
+ divided,
405
+ class: className
406
+ }),
407
+ ...props
408
+ });
409
+ }
410
+ //#endregion
411
+ //#region src/components/segmented-control/segmented-control.tsx
412
+ const segmentedControlStyles = tv({
413
+ slots: {
414
+ root: ["inline-flex items-center gap-0.5 rounded-md border-border bg-muted p-0.5", themedBorder],
415
+ item: [
416
+ "inline-flex h-full cursor-pointer select-none items-center justify-center gap-1.5 whitespace-nowrap rounded-[calc(var(--radius-md)-2px)] px-3 font-medium text-muted-foreground",
417
+ transition,
418
+ focusRing,
419
+ "hover:text-foreground data-[state=on]:bg-raised data-[state=on]:text-foreground data-[state=on]:shadow-xs",
420
+ "disabled:pointer-events-none disabled:opacity-50 [&>svg]:size-4"
421
+ ]
422
+ },
423
+ variants: {
424
+ size: {
425
+ sm: {
426
+ root: "h-(--glyph-control-sm)",
427
+ item: "px-2.5 text-caption"
428
+ },
429
+ md: {
430
+ root: "h-(--glyph-control-md)",
431
+ item: "text-small"
432
+ }
433
+ },
434
+ fullWidth: { true: {
435
+ root: "flex w-full",
436
+ item: "flex-1"
437
+ } }
438
+ },
439
+ defaultVariants: { size: "md" }
440
+ });
441
+ /**
442
+ * Pick exactly one of a few options. `T` is inferred from `options`, so `onValueChange`
443
+ * receives your union type, not `string`.
444
+ */
445
+ function SegmentedControl(inProps) {
446
+ const [props, slots] = useComponentConfig("SegmentedControl", inProps);
447
+ const { options, value, defaultValue, onValueChange, size, fullWidth, className, ...rest } = props;
448
+ const s = segmentedControlStyles({
449
+ size,
450
+ fullWidth
451
+ });
452
+ return /* @__PURE__ */ jsx(ToggleGroup.Root, {
453
+ type: "single",
454
+ "data-slot": "segmented-control",
455
+ ...value !== void 0 && { value },
456
+ ...defaultValue !== void 0 && { defaultValue },
457
+ onValueChange: (next) => next && onValueChange?.(next),
458
+ className: s.root({ class: [slots.root, className] }),
459
+ ...rest,
460
+ children: options.map((option) => /* @__PURE__ */ jsx(ToggleGroup.Item, {
461
+ value: option.value,
462
+ disabled: option.disabled,
463
+ className: s.item({ class: slots.item }),
464
+ children: option.label
465
+ }, option.value))
466
+ });
467
+ }
468
+ //#endregion
469
+ //#region src/components/select/select.tsx
470
+ const selectStyles = tv({
471
+ slots: {
472
+ trigger: [
473
+ ...fieldControl,
474
+ "flex cursor-pointer items-center justify-between gap-2 px-3 text-left outline-none",
475
+ "focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/20",
476
+ "data-placeholder:text-faint-foreground disabled:cursor-not-allowed disabled:opacity-50",
477
+ "[&>span]:truncate"
478
+ ],
479
+ icon: "size-4 shrink-0 text-faint-foreground",
480
+ content: [popover, "max-h-(--radix-select-content-available-height) min-w-(--radix-select-trigger-width)"],
481
+ viewport: "p-1",
482
+ item: ["relative flex h-8 cursor-pointer select-none items-center gap-2 rounded-sm pr-8 pl-2 text-small outline-none", "data-highlighted:bg-muted data-disabled:pointer-events-none data-disabled:opacity-50"],
483
+ indicator: "absolute right-2 flex items-center text-primary [&>svg]:size-4"
484
+ },
485
+ variants: { size: {
486
+ sm: { trigger: "h-(--glyph-control-sm) text-small" },
487
+ md: { trigger: "h-(--glyph-control-md) text-small" },
488
+ lg: { trigger: "h-(--glyph-control-lg) text-body" }
489
+ } },
490
+ defaultVariants: { size: "md" }
491
+ });
492
+ /**
493
+ * A dropdown select. `value` is typed by the `T` you use:
494
+ * `<Select<Plan> value={plan} onValueChange={setPlan}>` only accepts `Plan` values.
495
+ */
496
+ function Select(inProps) {
497
+ const [props, slots] = useComponentConfig("Select", useFieldControl(inProps));
498
+ const { size, placeholder, id, className, contentClassName, children, onValueChange, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, ...rootProps } = props;
499
+ const s = selectStyles({ size });
500
+ return /* @__PURE__ */ jsxs(Select$1.Root, {
501
+ ...rootProps,
502
+ ...onValueChange && { onValueChange: (v) => onValueChange(v) },
503
+ children: [/* @__PURE__ */ jsxs(Select$1.Trigger, {
504
+ id,
505
+ "data-slot": "select-trigger",
506
+ "aria-label": ariaLabel,
507
+ "aria-describedby": ariaDescribedBy,
508
+ "aria-invalid": ariaInvalid,
509
+ className: s.trigger({ class: [slots.trigger, className] }),
510
+ children: [/* @__PURE__ */ jsx(Select$1.Value, { placeholder }), /* @__PURE__ */ jsx(Select$1.Icon, {
511
+ asChild: true,
512
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: s.icon({ class: slots.icon }) })
513
+ })]
514
+ }), /* @__PURE__ */ jsx(Select$1.Portal, { children: /* @__PURE__ */ jsx(Select$1.Content, {
515
+ position: "popper",
516
+ sideOffset: 4,
517
+ "data-slot": "select-content",
518
+ className: s.content({ class: [slots.content, contentClassName] }),
519
+ children: /* @__PURE__ */ jsx(Select$1.Viewport, {
520
+ className: s.viewport({ class: slots.viewport }),
521
+ children
522
+ })
523
+ }) })]
524
+ });
525
+ }
526
+ function SelectItem({ className, children, ...props }) {
527
+ const [, slots] = useComponentConfig("Select", {});
528
+ const s = selectStyles();
529
+ return /* @__PURE__ */ jsxs(Select$1.Item, {
530
+ "data-slot": "select-item",
531
+ className: s.item({ class: [slots.item, className] }),
532
+ ...props,
533
+ children: [/* @__PURE__ */ jsx(Select$1.ItemText, { children }), /* @__PURE__ */ jsx(Select$1.ItemIndicator, {
534
+ className: s.indicator({ class: slots.indicator }),
535
+ children: /* @__PURE__ */ jsx(CheckIcon, {})
536
+ })]
537
+ });
538
+ }
539
+ //#endregion
540
+ //#region src/components/separator/separator.tsx
541
+ const separatorStyles = tv({
542
+ slots: { base: "shrink-0 bg-border" },
543
+ variants: { orientation: {
544
+ horizontal: { base: "h-px w-full" },
545
+ vertical: { base: "h-full w-px self-stretch" }
546
+ } },
547
+ defaultVariants: { orientation: "horizontal" }
548
+ });
549
+ function Separator(inProps) {
550
+ const [props, slots] = useComponentConfig("Separator", inProps);
551
+ const { orientation = "horizontal", decorative = true, className, ...rest } = props;
552
+ return /* @__PURE__ */ jsx(Separator$1.Root, {
553
+ "data-slot": "separator",
554
+ orientation,
555
+ decorative,
556
+ className: separatorStyles({ orientation }).base({ class: [slots.base, className] }),
557
+ ...rest
558
+ });
559
+ }
560
+ //#endregion
561
+ //#region src/components/stack/stack.tsx
562
+ const stackStyles = tv({
563
+ base: "flex",
564
+ variants: {
565
+ direction: {
566
+ column: "flex-col",
567
+ row: "flex-row"
568
+ },
569
+ gap: {
570
+ 0: "gap-0",
571
+ 1: "gap-1",
572
+ 2: "gap-2",
573
+ 3: "gap-3",
574
+ 4: "gap-4",
575
+ 5: "gap-5",
576
+ 6: "gap-6",
577
+ 8: "gap-8",
578
+ 10: "gap-10",
579
+ 12: "gap-12"
580
+ },
581
+ align: {
582
+ start: "items-start",
583
+ center: "items-center",
584
+ end: "items-end",
585
+ stretch: "items-stretch",
586
+ baseline: "items-baseline"
587
+ },
588
+ justify: {
589
+ start: "justify-start",
590
+ center: "justify-center",
591
+ end: "justify-end",
592
+ between: "justify-between"
593
+ },
594
+ wrap: { true: "flex-wrap" }
595
+ },
596
+ defaultVariants: {
597
+ direction: "column",
598
+ gap: 4
599
+ }
600
+ });
601
+ /** Flex layout with spacing from the scale. `direction="row"` for horizontal. */
602
+ function Stack({ direction, gap, align, justify, wrap, asChild, className, ...props }) {
603
+ const Comp = asChild ? Slot.Root : "div";
604
+ return /* @__PURE__ */ jsx(Comp, {
605
+ "data-slot": "stack",
606
+ className: stackStyles({
607
+ direction,
608
+ gap,
609
+ align,
610
+ justify,
611
+ wrap,
612
+ class: className
613
+ }),
614
+ ...props
615
+ });
616
+ }
617
+ //#endregion
618
+ //#region src/components/switch/switch.tsx
619
+ const switchStyles = tv({
620
+ slots: {
621
+ root: [
622
+ "peer inline-flex shrink-0 cursor-pointer items-center rounded-pill border-transparent bg-input p-px",
623
+ themedBorder,
624
+ transition,
625
+ focusRing,
626
+ "data-[state=checked]:bg-primary disabled:cursor-not-allowed disabled:opacity-50"
627
+ ],
628
+ thumb: "pointer-events-none block rounded-pill bg-white shadow-sm transition-transform duration-(--glyph-duration-base) ease-emphasized data-[state=unchecked]:translate-x-0"
629
+ },
630
+ variants: { size: {
631
+ sm: {
632
+ root: "h-4 w-7",
633
+ thumb: "size-3 data-[state=checked]:translate-x-3"
634
+ },
635
+ md: {
636
+ root: "h-5 w-9",
637
+ thumb: "size-4 data-[state=checked]:translate-x-4"
638
+ }
639
+ } },
640
+ defaultVariants: { size: "md" }
641
+ });
642
+ function Switch(inProps) {
643
+ const [{ size, className, ...rest }, slots] = useComponentConfig("Switch", useFieldControl(inProps));
644
+ const s = switchStyles({ size });
645
+ return /* @__PURE__ */ jsx(Switch$1.Root, {
646
+ "data-slot": "switch",
647
+ className: s.root({ class: [slots.root, className] }),
648
+ ...rest,
649
+ children: /* @__PURE__ */ jsx(Switch$1.Thumb, { className: s.thumb({ class: slots.thumb }) })
650
+ });
651
+ }
652
+ //#endregion
653
+ //#region src/components/tabs/tabs.tsx
654
+ const tabsStyles = tv({
655
+ slots: {
656
+ root: "flex flex-col gap-4",
657
+ list: "flex items-center",
658
+ trigger: [
659
+ "relative inline-flex cursor-pointer select-none items-center justify-center gap-2 whitespace-nowrap text-small font-medium text-muted-foreground",
660
+ transition,
661
+ focusRing,
662
+ "hover:text-foreground data-[state=active]:text-foreground disabled:pointer-events-none disabled:opacity-50",
663
+ "[&>svg]:size-4"
664
+ ],
665
+ content: "outline-none"
666
+ },
667
+ variants: { variant: {
668
+ underline: {
669
+ list: "gap-5 border-b border-border",
670
+ trigger: "h-9 after:absolute after:inset-x-0 after:-bottom-px after:h-0.5 after:rounded-pill after:bg-transparent data-[state=active]:after:bg-foreground"
671
+ },
672
+ pill: {
673
+ list: ["inline-flex w-fit gap-0.5 rounded-md border-border bg-muted p-0.5", themedBorder],
674
+ trigger: "h-7 rounded-[calc(var(--radius-md)-2px)] px-3 data-[state=active]:bg-raised data-[state=active]:shadow-xs"
675
+ }
676
+ } },
677
+ defaultVariants: { variant: "underline" }
678
+ });
679
+ const TabsContext = createContext({});
680
+ function Tabs(inProps) {
681
+ const [{ variant, className, ...rest }, slots] = useComponentConfig("Tabs", inProps);
682
+ return /* @__PURE__ */ jsx(TabsContext.Provider, {
683
+ value: { variant },
684
+ children: /* @__PURE__ */ jsx(Tabs$1.Root, {
685
+ "data-slot": "tabs",
686
+ className: tabsStyles({ variant }).root({ class: [slots.root, className] }),
687
+ ...rest
688
+ })
689
+ });
690
+ }
691
+ function useTabsParts() {
692
+ const { variant } = useContext(TabsContext);
693
+ const [, slots] = useComponentConfig("Tabs", {});
694
+ return {
695
+ s: tabsStyles({ variant }),
696
+ slots
697
+ };
698
+ }
699
+ function TabsList({ className, ...props }) {
700
+ const { s, slots } = useTabsParts();
701
+ return /* @__PURE__ */ jsx(Tabs$1.List, {
702
+ "data-slot": "tabs-list",
703
+ className: s.list({ class: [slots.list, className] }),
704
+ ...props
705
+ });
706
+ }
707
+ function TabsTrigger({ className, ...props }) {
708
+ const { s, slots } = useTabsParts();
709
+ return /* @__PURE__ */ jsx(Tabs$1.Trigger, {
710
+ "data-slot": "tabs-trigger",
711
+ className: s.trigger({ class: [slots.trigger, className] }),
712
+ ...props
713
+ });
714
+ }
715
+ function TabsContent({ className, ...props }) {
716
+ const { s, slots } = useTabsParts();
717
+ return /* @__PURE__ */ jsx(Tabs$1.Content, {
718
+ "data-slot": "tabs-content",
719
+ className: s.content({ class: [slots.content, className] }),
720
+ ...props
721
+ });
722
+ }
723
+ //#endregion
724
+ //#region src/components/text/text.styles.ts
725
+ const textStyles = tv({
726
+ slots: { base: "" },
727
+ variants: {
728
+ size: {
729
+ lead: { base: "text-lead" },
730
+ body: { base: "text-body" },
731
+ small: { base: "text-small" },
732
+ caption: { base: "text-caption" }
733
+ },
734
+ tone: {
735
+ default: { base: "text-foreground" },
736
+ muted: { base: "text-muted-foreground" },
737
+ faint: { base: "text-faint-foreground" },
738
+ primary: { base: "text-primary" },
739
+ success: { base: "text-success" },
740
+ warning: { base: "text-warning" },
741
+ danger: { base: "text-danger" }
742
+ },
743
+ weight: {
744
+ regular: { base: "font-normal" },
745
+ medium: { base: "font-medium" },
746
+ semibold: { base: "font-semibold" }
747
+ },
748
+ mono: { true: { base: "font-mono" } },
749
+ truncate: { true: { base: "truncate" } }
750
+ },
751
+ defaultVariants: {
752
+ size: "body",
753
+ tone: "default"
754
+ }
755
+ });
756
+ //#endregion
757
+ //#region src/components/text/text.tsx
758
+ function Text(inProps) {
759
+ const [props, slots] = useComponentConfig("Text", inProps);
760
+ const { size, tone, weight, mono, truncate, asChild, className, ...rest } = props;
761
+ const s = textStyles({
762
+ size,
763
+ tone,
764
+ weight,
765
+ mono,
766
+ truncate
767
+ });
768
+ const Comp = asChild ? Slot.Root : "p";
769
+ return /* @__PURE__ */ jsx(Comp, {
770
+ "data-slot": "text",
771
+ className: s.base({ class: [slots.base, className] }),
772
+ ...rest
773
+ });
774
+ }
775
+ //#endregion
776
+ //#region src/components/textarea/textarea.tsx
777
+ const textareaStyles = tv({ slots: { base: [
778
+ ...fieldControl,
779
+ "field-sizing-content min-h-20 resize-y rounded-[min(var(--radius-md),var(--radius-lg))] px-3 py-2 text-small",
780
+ "outline-none placeholder:text-faint-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/20",
781
+ "disabled:cursor-not-allowed disabled:opacity-50"
782
+ ] } });
783
+ function Textarea(inProps) {
784
+ const [{ className, ...rest }, slots] = useComponentConfig("Textarea", useFieldControl(inProps));
785
+ return /* @__PURE__ */ jsx("textarea", {
786
+ "data-slot": "textarea",
787
+ className: textareaStyles().base({ class: [slots.base, className] }),
788
+ ...rest
789
+ });
790
+ }
791
+ //#endregion
792
+ //#region src/components/tooltip/tooltip.tsx
793
+ const tooltipStyles = tv({ slots: { content: "z-50 max-w-xs animate-pop-in rounded-sm bg-secondary px-2 py-1 text-caption text-secondary-foreground shadow-md" } });
794
+ /** A short hint on hover or focus. The child must be focusable (a button or link). */
795
+ function Tooltip(inProps) {
796
+ const [props, slots] = useComponentConfig("Tooltip", inProps);
797
+ const { content, children, disabled, delayDuration, className, sideOffset = 6, ...rest } = props;
798
+ if (disabled) return children;
799
+ return /* @__PURE__ */ jsxs(Tooltip$1.Root, {
800
+ ...delayDuration !== void 0 && { delayDuration },
801
+ children: [/* @__PURE__ */ jsx(Tooltip$1.Trigger, {
802
+ asChild: true,
803
+ children
804
+ }), /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsx(Tooltip$1.Content, {
805
+ "data-slot": "tooltip",
806
+ sideOffset,
807
+ className: tooltipStyles().content({ class: [slots.content, className] }),
808
+ ...rest,
809
+ children: content
810
+ }) })]
811
+ });
812
+ }
813
+ //#endregion
814
+ //#region src/hooks/use-controllable-state.ts
815
+ /** State that can be controlled by a parent (`value` + `onChange`) or left uncontrolled. */
816
+ function useControllableState({ value, defaultValue, onChange }) {
817
+ const [internal, setInternal] = useState(defaultValue);
818
+ const isControlled = value !== void 0;
819
+ const current = isControlled ? value : internal;
820
+ const onChangeRef = useRef(onChange);
821
+ onChangeRef.current = onChange;
822
+ return [current, useCallback((next) => {
823
+ const resolved = typeof next === "function" ? next(current) : next;
824
+ if (!isControlled) setInternal(resolved);
825
+ onChangeRef.current?.(resolved);
826
+ }, [current, isControlled])];
827
+ }
828
+ //#endregion
829
+ //#region src/hooks/use-media-query.ts
830
+ /** Subscribes to a CSS media query. Returns `fallback` during SSR. */
831
+ function useMediaQuery(query, fallback = false) {
832
+ return useSyncExternalStore((onChange) => {
833
+ const list = window.matchMedia(query);
834
+ list.addEventListener("change", onChange);
835
+ return () => list.removeEventListener("change", onChange);
836
+ }, () => window.matchMedia(query).matches, () => fallback);
837
+ }
838
+ //#endregion
839
+ //#region src/hooks/use-scrolled.ts
840
+ /** True once the window has scrolled past `threshold` pixels. */
841
+ function useScrolled(threshold = 8, enabled = true) {
842
+ const [scrolled, setScrolled] = useState(false);
843
+ useEffect(() => {
844
+ if (!enabled) return;
845
+ const update = () => setScrolled(window.scrollY > threshold);
846
+ update();
847
+ window.addEventListener("scroll", update, { passive: true });
848
+ return () => window.removeEventListener("scroll", update);
849
+ }, [threshold, enabled]);
850
+ return scrolled;
851
+ }
852
+ //#endregion
853
+ //#region src/patterns/app-shell/app-shell.styles.ts
854
+ const appShellStyles = tv({
855
+ slots: {
856
+ root: "flex w-full bg-background",
857
+ sidebar: "hidden shrink-0 md:flex",
858
+ main: "flex min-w-0 flex-1 flex-col",
859
+ mobileBar: ["flex h-14 shrink-0 items-center gap-2 border-b-border px-3 md:hidden", "border-b-(length:--glyph-border-width)"],
860
+ overlay: "fixed inset-0 z-50 animate-fade-in bg-overlay",
861
+ drawer: "fixed inset-y-0 left-0 z-50 flex w-72 max-w-[85vw] animate-slide-in-left shadow-lg outline-none [&>[data-slot=sidebar]]:w-full"
862
+ },
863
+ variants: { layout: {
864
+ page: {
865
+ root: "min-h-dvh",
866
+ sidebar: "sticky top-0 h-dvh",
867
+ mobileBar: "sticky top-0 z-30 bg-background/80 backdrop-blur-xl"
868
+ },
869
+ embedded: {
870
+ root: "h-full min-h-0 overflow-hidden",
871
+ sidebar: "h-full",
872
+ main: "overflow-y-auto"
873
+ }
874
+ } },
875
+ defaultVariants: { layout: "page" }
876
+ });
877
+ const sidebarStyles = tv({ slots: {
878
+ root: [
879
+ "group/sidebar flex h-full w-60 flex-col overflow-hidden border-r-border bg-subtle",
880
+ "border-r-(length:--glyph-border-width)",
881
+ "transition-[width] duration-(--glyph-duration-base) ease-standard data-[collapsed=true]:w-15"
882
+ ],
883
+ header: "flex h-14 shrink-0 items-center gap-2 px-3",
884
+ content: "flex flex-1 flex-col gap-5 overflow-y-auto overflow-x-hidden px-3 py-3",
885
+ section: "flex flex-col gap-0.5",
886
+ sectionLabel: "mb-1 truncate px-2 font-mono text-caption uppercase tracking-[0.1em] text-faint-foreground group-data-[collapsed=true]/sidebar:sr-only",
887
+ item: [
888
+ "relative flex h-8 w-full shrink-0 cursor-pointer items-center gap-2.5 rounded-sm px-2 text-small font-medium text-muted-foreground",
889
+ transition,
890
+ focusRing,
891
+ "hover:bg-muted hover:text-foreground",
892
+ "aria-[current=page]:bg-muted aria-[current=page]:text-foreground",
893
+ "before:absolute before:inset-y-1.5 before:-left-3 before:w-0.5 before:rounded-r-pill before:bg-transparent aria-[current=page]:before:bg-primary",
894
+ "group-data-[collapsed=true]/sidebar:justify-center group-data-[collapsed=true]/sidebar:px-0"
895
+ ],
896
+ itemIcon: "flex size-4 shrink-0 items-center justify-center [&>svg]:size-4",
897
+ itemLabel: "min-w-0 flex-1 truncate text-left group-data-[collapsed=true]/sidebar:hidden",
898
+ itemBadge: "ml-auto font-mono text-caption text-faint-foreground tabular-nums group-data-[collapsed=true]/sidebar:hidden",
899
+ footer: ["mt-auto flex shrink-0 items-center gap-2 border-t-border px-3 py-3 group-data-[collapsed=true]/sidebar:justify-center", "border-t-(length:--glyph-border-width)"]
900
+ } });
901
+ const navbarStyles = tv({
902
+ slots: {
903
+ root: [
904
+ "top-0 z-40 w-full border-b-transparent",
905
+ "border-b-(length:--glyph-border-width)",
906
+ "transition-[background-color,border-color,backdrop-filter] duration-(--glyph-duration-base) ease-standard"
907
+ ],
908
+ inner: "flex h-16 items-center gap-6",
909
+ brand: "flex shrink-0 items-center gap-2 font-display text-h4 text-foreground",
910
+ links: "hidden items-center gap-1 md:flex",
911
+ link: [
912
+ "rounded-sm px-3 py-1.5 text-small font-medium text-muted-foreground",
913
+ transition,
914
+ focusRing,
915
+ "hover:text-foreground aria-[current=page]:text-foreground"
916
+ ],
917
+ actions: "ml-auto hidden items-center gap-2 md:flex",
918
+ menuButton: "ml-auto md:hidden",
919
+ overlay: "fixed inset-0 z-50 animate-fade-in bg-overlay",
920
+ panel: ["fixed inset-x-0 top-0 z-50 max-h-dvh overflow-y-auto border-b-border bg-background px-4 pb-6 shadow-lg outline-none animate-slide-in-top", "border-b-(length:--glyph-border-width)"],
921
+ panelHeader: "flex h-16 items-center justify-between",
922
+ panelLinks: "flex flex-col gap-1",
923
+ panelLink: [
924
+ "rounded-md px-3 py-2.5 text-body font-medium text-muted-foreground",
925
+ focusRing,
926
+ "hover:bg-muted hover:text-foreground aria-[current=page]:bg-muted aria-[current=page]:text-foreground"
927
+ ],
928
+ panelActions: ["mt-4 flex flex-col gap-2 border-t-border pt-4", "border-t-(length:--glyph-border-width)"]
929
+ },
930
+ variants: {
931
+ sticky: {
932
+ true: { root: "sticky" },
933
+ false: { root: "relative" }
934
+ },
935
+ blur: {
936
+ true: { root: "data-[scrolled=true]:border-b-border data-[scrolled=true]:bg-background/72 data-[scrolled=true]:backdrop-blur-xl data-[scrolled=true]:backdrop-saturate-150" },
937
+ false: { root: "border-b-border bg-background" }
938
+ }
939
+ },
940
+ defaultVariants: {
941
+ sticky: true,
942
+ blur: true
943
+ }
944
+ });
945
+ const pageHeaderStyles = tv({ slots: {
946
+ root: ["flex flex-col gap-4 border-b-border pb-5 sm:flex-row sm:items-end sm:justify-between", "border-b-(length:--glyph-border-width)"],
947
+ text: "grid min-w-0 gap-1",
948
+ eyebrow: "font-mono text-caption uppercase tracking-[0.12em] text-faint-foreground",
949
+ title: "font-display text-h3 text-foreground",
950
+ description: "max-w-2xl text-small text-muted-foreground",
951
+ actions: "flex shrink-0 flex-wrap items-center gap-2"
952
+ } });
953
+ //#endregion
954
+ //#region src/patterns/app-shell/app-shell-context.ts
955
+ const AppShellContext = createContext(null);
956
+ /** Sidebar state. Works outside an AppShell too (always expanded). */
957
+ function useAppShell() {
958
+ return useContext(AppShellContext) ?? {
959
+ collapsed: false,
960
+ setCollapsed: () => {},
961
+ mobileOpen: false,
962
+ setMobileOpen: () => {},
963
+ inDrawer: false
964
+ };
965
+ }
966
+ //#endregion
967
+ //#region src/patterns/app-shell/sidebar.tsx
968
+ const s = sidebarStyles();
969
+ function useSlots() {
970
+ return useComponentConfig("Sidebar", {})[1];
971
+ }
972
+ /** Navigation sidebar. Place inside `AppShell`'s `sidebar` prop. */
973
+ function Sidebar({ className, ...props }) {
974
+ const { collapsed } = useAppShell();
975
+ const slots = useSlots();
976
+ return /* @__PURE__ */ jsx("aside", {
977
+ "data-slot": "sidebar",
978
+ "data-collapsed": collapsed,
979
+ className: s.root({ class: [slots.root, className] }),
980
+ ...props
981
+ });
982
+ }
983
+ function SidebarHeader({ className, ...props }) {
984
+ const slots = useSlots();
985
+ return /* @__PURE__ */ jsx("div", {
986
+ "data-slot": "sidebar-header",
987
+ className: s.header({ class: [slots.header, className] }),
988
+ ...props
989
+ });
990
+ }
991
+ function SidebarContent({ className, ...props }) {
992
+ const slots = useSlots();
993
+ return /* @__PURE__ */ jsx("nav", {
994
+ "data-slot": "sidebar-content",
995
+ "aria-label": "Main",
996
+ className: s.content({ class: [slots.content, className] }),
997
+ ...props
998
+ });
999
+ }
1000
+ function SidebarSection({ label, className, children, ...props }) {
1001
+ const slots = useSlots();
1002
+ return /* @__PURE__ */ jsxs("div", {
1003
+ "data-slot": "sidebar-section",
1004
+ className: s.section({ class: [slots.section, className] }),
1005
+ ...props,
1006
+ children: [label && /* @__PURE__ */ jsx("p", {
1007
+ className: s.sectionLabel({ class: slots.sectionLabel }),
1008
+ children: label
1009
+ }), children]
1010
+ });
1011
+ }
1012
+ function SidebarItem({ href, icon, badge, active, onClick, className, children }) {
1013
+ const { collapsed, inDrawer, setMobileOpen } = useAppShell();
1014
+ const Link = useLinkComponent();
1015
+ const slots = useSlots();
1016
+ const handleClick = (event) => {
1017
+ onClick?.(event);
1018
+ if (inDrawer) setMobileOpen(false);
1019
+ };
1020
+ const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [
1021
+ icon && /* @__PURE__ */ jsx("span", {
1022
+ className: s.itemIcon({ class: slots.itemIcon }),
1023
+ "aria-hidden": "true",
1024
+ children: icon
1025
+ }),
1026
+ /* @__PURE__ */ jsx("span", {
1027
+ className: s.itemLabel({ class: slots.itemLabel }),
1028
+ children
1029
+ }),
1030
+ badge !== void 0 && /* @__PURE__ */ jsx("span", {
1031
+ className: s.itemBadge({ class: slots.itemBadge }),
1032
+ children: badge
1033
+ })
1034
+ ] });
1035
+ const shared = {
1036
+ "data-slot": "sidebar-item",
1037
+ "aria-current": active ? "page" : void 0,
1038
+ className: s.item({ class: [slots.item, className] })
1039
+ };
1040
+ const element = href ? /* @__PURE__ */ jsx(Link, {
1041
+ href,
1042
+ onClick: handleClick,
1043
+ ...shared,
1044
+ children: content
1045
+ }) : /* @__PURE__ */ jsx("button", {
1046
+ type: "button",
1047
+ onClick: handleClick,
1048
+ ...shared,
1049
+ children: content
1050
+ });
1051
+ return /* @__PURE__ */ jsx(Tooltip, {
1052
+ content: children,
1053
+ side: "right",
1054
+ disabled: !collapsed,
1055
+ children: element
1056
+ });
1057
+ }
1058
+ function SidebarFooter({ className, ...props }) {
1059
+ const slots = useSlots();
1060
+ return /* @__PURE__ */ jsx("div", {
1061
+ "data-slot": "sidebar-footer",
1062
+ className: s.footer({ class: [slots.footer, className] }),
1063
+ ...props
1064
+ });
1065
+ }
1066
+ /** Collapses the sidebar on desktop; opens the navigation drawer on mobile. */
1067
+ function SidebarTrigger(props) {
1068
+ const { collapsed, setCollapsed, setMobileOpen, inDrawer } = useAppShell();
1069
+ const desktop = useMediaQuery("(min-width: 48rem)");
1070
+ if (inDrawer) return null;
1071
+ return /* @__PURE__ */ jsx(IconButton, {
1072
+ icon: /* @__PURE__ */ jsx(PanelLeftIcon, {}),
1073
+ "aria-label": desktop ? collapsed ? "Expand sidebar" : "Collapse sidebar" : "Open navigation",
1074
+ size: "sm",
1075
+ onClick: () => desktop ? setCollapsed(!collapsed) : setMobileOpen(true),
1076
+ ...props
1077
+ });
1078
+ }
1079
+ //#endregion
1080
+ //#region src/patterns/app-shell/app-shell.tsx
1081
+ /**
1082
+ * App layout: sidebar plus main content. Handles desktop collapse to an icon rail,
1083
+ * the mobile drawer (focus-trapped, closes on navigation), and the mobile top bar.
1084
+ *
1085
+ * `layout="page"` fills the viewport; `layout="embedded"` fills its parent.
1086
+ */
1087
+ function AppShell(inProps) {
1088
+ const [props, slots] = useComponentConfig("AppShell", inProps);
1089
+ const { sidebar, children, collapsed: collapsedProp, defaultCollapsed = false, onCollapsedChange, mobileBrand, layout, className, ...rest } = props;
1090
+ const [collapsed, setCollapsed] = useControllableState({
1091
+ value: collapsedProp,
1092
+ defaultValue: defaultCollapsed,
1093
+ onChange: onCollapsedChange
1094
+ });
1095
+ const [mobileOpen, setMobileOpen] = useState(false);
1096
+ const s = appShellStyles({ layout });
1097
+ const desktop = useMemo(() => ({
1098
+ collapsed,
1099
+ setCollapsed,
1100
+ mobileOpen,
1101
+ setMobileOpen,
1102
+ inDrawer: false
1103
+ }), [
1104
+ collapsed,
1105
+ setCollapsed,
1106
+ mobileOpen
1107
+ ]);
1108
+ const drawer = useMemo(() => ({
1109
+ ...desktop,
1110
+ collapsed: false,
1111
+ inDrawer: true
1112
+ }), [desktop]);
1113
+ return /* @__PURE__ */ jsx(AppShellContext.Provider, {
1114
+ value: desktop,
1115
+ children: /* @__PURE__ */ jsxs("div", {
1116
+ "data-slot": "app-shell",
1117
+ className: s.root({ class: [slots.root, className] }),
1118
+ ...rest,
1119
+ children: [
1120
+ /* @__PURE__ */ jsx("div", {
1121
+ className: s.sidebar({ class: slots.sidebar }),
1122
+ children: sidebar
1123
+ }),
1124
+ /* @__PURE__ */ jsx(Dialog.Root, {
1125
+ open: mobileOpen,
1126
+ onOpenChange: setMobileOpen,
1127
+ children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [/* @__PURE__ */ jsx(Dialog.Overlay, { className: s.overlay({ class: slots.overlay }) }), /* @__PURE__ */ jsxs(Dialog.Content, {
1128
+ className: s.drawer({ class: slots.drawer }),
1129
+ "aria-describedby": void 0,
1130
+ children: [/* @__PURE__ */ jsx(Dialog.Title, {
1131
+ className: "sr-only",
1132
+ children: "Navigation"
1133
+ }), /* @__PURE__ */ jsx(AppShellContext.Provider, {
1134
+ value: drawer,
1135
+ children: sidebar
1136
+ })]
1137
+ })] })
1138
+ }),
1139
+ /* @__PURE__ */ jsxs("div", {
1140
+ "data-slot": "app-shell-main",
1141
+ className: s.main({ class: slots.main }),
1142
+ children: [/* @__PURE__ */ jsxs("div", {
1143
+ className: s.mobileBar({ class: slots.mobileBar }),
1144
+ children: [/* @__PURE__ */ jsx(SidebarTrigger, {}), mobileBrand]
1145
+ }), children]
1146
+ })
1147
+ ]
1148
+ })
1149
+ });
1150
+ }
1151
+ //#endregion
1152
+ //#region src/patterns/app-shell/navbar.tsx
1153
+ /**
1154
+ * Site header from data: brand, links, actions. Responsive out of the box: links collapse
1155
+ * into a focus-trapped menu panel on small screens.
1156
+ */
1157
+ function Navbar(inProps) {
1158
+ const [props, slots] = useComponentConfig("Navbar", inProps);
1159
+ const { brand, links = [], actions, sticky = true, blurOnScroll = true, containerSize, className, ...rest } = props;
1160
+ const [open, setOpen] = useState(false);
1161
+ const scrolled = useScrolled(8, blurOnScroll);
1162
+ const Link = useLinkComponent();
1163
+ const s = navbarStyles({
1164
+ sticky,
1165
+ blur: blurOnScroll
1166
+ });
1167
+ return /* @__PURE__ */ jsx("header", {
1168
+ "data-slot": "navbar",
1169
+ "data-scrolled": scrolled,
1170
+ className: s.root({ class: [slots.root, className] }),
1171
+ ...rest,
1172
+ children: /* @__PURE__ */ jsxs(Container, {
1173
+ size: containerSize,
1174
+ className: s.inner({ class: slots.inner }),
1175
+ children: [
1176
+ /* @__PURE__ */ jsx("div", {
1177
+ className: s.brand({ class: slots.brand }),
1178
+ children: brand
1179
+ }),
1180
+ links.length > 0 && /* @__PURE__ */ jsx("nav", {
1181
+ "aria-label": "Main",
1182
+ className: s.links({ class: slots.links }),
1183
+ children: links.map((link) => /* @__PURE__ */ jsx(Link, {
1184
+ href: link.href,
1185
+ "aria-current": link.active ? "page" : void 0,
1186
+ className: s.link({ class: slots.link }),
1187
+ children: link.label
1188
+ }, link.href))
1189
+ }),
1190
+ actions && /* @__PURE__ */ jsx("div", {
1191
+ className: s.actions({ class: slots.actions }),
1192
+ children: actions
1193
+ }),
1194
+ /* @__PURE__ */ jsxs(Dialog.Root, {
1195
+ open,
1196
+ onOpenChange: setOpen,
1197
+ children: [/* @__PURE__ */ jsx(Dialog.Trigger, {
1198
+ asChild: true,
1199
+ children: /* @__PURE__ */ jsx(IconButton, {
1200
+ icon: /* @__PURE__ */ jsx(MenuIcon, {}),
1201
+ "aria-label": "Open menu",
1202
+ className: s.menuButton({ class: slots.menuButton })
1203
+ })
1204
+ }), /* @__PURE__ */ jsxs(Dialog.Portal, { children: [/* @__PURE__ */ jsx(Dialog.Overlay, { className: s.overlay({ class: slots.overlay }) }), /* @__PURE__ */ jsxs(Dialog.Content, {
1205
+ className: s.panel({ class: slots.panel }),
1206
+ "aria-describedby": void 0,
1207
+ children: [
1208
+ /* @__PURE__ */ jsxs("div", {
1209
+ className: s.panelHeader({ class: slots.panelHeader }),
1210
+ children: [/* @__PURE__ */ jsx(Dialog.Title, {
1211
+ asChild: true,
1212
+ children: /* @__PURE__ */ jsx("div", {
1213
+ className: s.brand({ class: slots.brand }),
1214
+ children: brand
1215
+ })
1216
+ }), /* @__PURE__ */ jsx(Dialog.Close, {
1217
+ asChild: true,
1218
+ children: /* @__PURE__ */ jsx(IconButton, {
1219
+ icon: /* @__PURE__ */ jsx(XIcon, {}),
1220
+ "aria-label": "Close menu"
1221
+ })
1222
+ })]
1223
+ }),
1224
+ /* @__PURE__ */ jsx("nav", {
1225
+ "aria-label": "Main",
1226
+ className: s.panelLinks({ class: slots.panelLinks }),
1227
+ children: links.map((link) => /* @__PURE__ */ jsx(Link, {
1228
+ href: link.href,
1229
+ "aria-current": link.active ? "page" : void 0,
1230
+ className: s.panelLink({ class: slots.panelLink }),
1231
+ onClick: () => setOpen(false),
1232
+ children: link.label
1233
+ }, link.href))
1234
+ }),
1235
+ actions && /* @__PURE__ */ jsx("div", {
1236
+ className: s.panelActions({ class: slots.panelActions }),
1237
+ children: actions
1238
+ })
1239
+ ]
1240
+ })] })]
1241
+ })
1242
+ ]
1243
+ })
1244
+ });
1245
+ }
1246
+ //#endregion
1247
+ //#region src/patterns/app-shell/page-header.tsx
1248
+ /** Title block at the top of a page: optional eyebrow, title, description, actions. */
1249
+ function PageHeader(inProps) {
1250
+ const [props, slots] = useComponentConfig("PageHeader", inProps);
1251
+ const { title, description, eyebrow, actions, className, ...rest } = props;
1252
+ const s = pageHeaderStyles();
1253
+ return /* @__PURE__ */ jsxs("div", {
1254
+ "data-slot": "page-header",
1255
+ className: s.root({ class: [slots.root, className] }),
1256
+ ...rest,
1257
+ children: [/* @__PURE__ */ jsxs("div", {
1258
+ className: s.text({ class: slots.text }),
1259
+ children: [
1260
+ eyebrow && /* @__PURE__ */ jsx("p", {
1261
+ className: s.eyebrow({ class: slots.eyebrow }),
1262
+ children: eyebrow
1263
+ }),
1264
+ /* @__PURE__ */ jsx("h1", {
1265
+ className: s.title({ class: slots.title }),
1266
+ children: title
1267
+ }),
1268
+ description && /* @__PURE__ */ jsx("p", {
1269
+ className: s.description({ class: slots.description }),
1270
+ children: description
1271
+ })
1272
+ ]
1273
+ }), actions && /* @__PURE__ */ jsx("div", {
1274
+ className: s.actions({ class: slots.actions }),
1275
+ children: actions
1276
+ })]
1277
+ });
1278
+ }
1279
+ //#endregion
1280
+ //#region src/patterns/async/async.tsx
1281
+ const defaultIsEmpty = (data) => Array.isArray(data) && data.length === 0;
1282
+ /**
1283
+ * Renders the right UI for every state of an async result: loading, error (with retry),
1284
+ * empty, or your content. One convention for every data-driven screen.
1285
+ *
1286
+ * ```tsx
1287
+ * <Async result={useQuery(...)} empty={<EmptyState title="No invoices yet" />}>
1288
+ * {(invoices) => <InvoiceList invoices={invoices} />}
1289
+ * </Async>
1290
+ * ```
1291
+ */
1292
+ function Async({ result, children, isEmpty = defaultIsEmpty, loading, empty, error }) {
1293
+ if (result.status === "pending") return loading ?? /* @__PURE__ */ jsx(LoadingState, {});
1294
+ if (result.status === "error") {
1295
+ if (typeof error === "function") return error(result.error, result.refetch);
1296
+ return error ?? /* @__PURE__ */ jsx(ErrorState, {
1297
+ error: result.error,
1298
+ onRetry: result.refetch
1299
+ });
1300
+ }
1301
+ const data = result.data;
1302
+ if (data === void 0 || data === null || isEmpty(data)) return empty ?? /* @__PURE__ */ jsx(EmptyState, { title: "Nothing here yet" });
1303
+ return children(data);
1304
+ }
1305
+ /**
1306
+ * Runs an async function and tracks its state as an `AsyncResult`. Re-runs when `deps`
1307
+ * change; stale responses are ignored. For caching and deduping, use TanStack Query.
1308
+ */
1309
+ function useAsync(fn, deps) {
1310
+ const [state, setState] = useState({ status: "pending" });
1311
+ const [run, setRun] = useState(0);
1312
+ useEffect(() => {
1313
+ let active = true;
1314
+ setState((prev) => ({
1315
+ ...prev,
1316
+ status: "pending"
1317
+ }));
1318
+ fn().then((data) => active && setState({
1319
+ status: "success",
1320
+ data
1321
+ }), (error) => active && setState({
1322
+ status: "error",
1323
+ error
1324
+ }));
1325
+ return () => {
1326
+ active = false;
1327
+ };
1328
+ }, [...deps, run]);
1329
+ const refetch = useCallback(() => setRun((n) => n + 1), []);
1330
+ return {
1331
+ ...state,
1332
+ refetch
1333
+ };
1334
+ }
1335
+ //#endregion
1336
+ //#region src/provider/color-mode.tsx
1337
+ const ColorModeContext = createContext(null);
1338
+ const DARK_QUERY = "(prefers-color-scheme: dark)";
1339
+ const isBrowser = typeof window !== "undefined";
1340
+ function readStoredMode(storageKey) {
1341
+ if (!isBrowser || !storageKey) return null;
1342
+ try {
1343
+ const value = window.localStorage.getItem(storageKey);
1344
+ return value === "light" || value === "dark" || value === "system" ? value : null;
1345
+ } catch {
1346
+ return null;
1347
+ }
1348
+ }
1349
+ function systemMode() {
1350
+ return isBrowser && window.matchMedia(DARK_QUERY).matches ? "dark" : "light";
1351
+ }
1352
+ /** Disables transitions for one frame so a mode switch doesn't animate every color. */
1353
+ function suppressTransitions() {
1354
+ const style = document.createElement("style");
1355
+ style.textContent = "*,*::before,*::after{transition:none!important}";
1356
+ document.head.appendChild(style);
1357
+ return () => {
1358
+ window.getComputedStyle(document.body);
1359
+ requestAnimationFrame(() => style.remove());
1360
+ };
1361
+ }
1362
+ function ColorModeProvider({ children, defaultMode = "system", storageKey = "glyph-mode" }) {
1363
+ const [mode, setModeState] = useState(() => readStoredMode(storageKey) ?? defaultMode);
1364
+ const [system, setSystem] = useState(systemMode);
1365
+ useEffect(() => {
1366
+ const query = window.matchMedia(DARK_QUERY);
1367
+ const onChange = () => setSystem(query.matches ? "dark" : "light");
1368
+ query.addEventListener("change", onChange);
1369
+ return () => query.removeEventListener("change", onChange);
1370
+ }, []);
1371
+ const resolvedMode = mode === "system" ? system : mode;
1372
+ useEffect(() => {
1373
+ const root = document.documentElement;
1374
+ if (root.dataset.mode === resolvedMode) return;
1375
+ const restore = suppressTransitions();
1376
+ root.dataset.mode = resolvedMode;
1377
+ root.style.colorScheme = resolvedMode;
1378
+ restore();
1379
+ }, [resolvedMode]);
1380
+ const setMode = useCallback((next) => {
1381
+ setModeState(next);
1382
+ if (!storageKey) return;
1383
+ try {
1384
+ window.localStorage.setItem(storageKey, next);
1385
+ } catch {}
1386
+ }, [storageKey]);
1387
+ const value = useMemo(() => ({
1388
+ mode,
1389
+ resolvedMode,
1390
+ setMode
1391
+ }), [
1392
+ mode,
1393
+ resolvedMode,
1394
+ setMode
1395
+ ]);
1396
+ return /* @__PURE__ */ jsx(ColorModeContext.Provider, {
1397
+ value,
1398
+ children
1399
+ });
1400
+ }
1401
+ /** Read and change the color mode. */
1402
+ function useColorMode() {
1403
+ const value = useContext(ColorModeContext);
1404
+ if (!value) throw new Error("useColorMode must be used inside <GlyphProvider>.");
1405
+ return value;
1406
+ }
1407
+ /**
1408
+ * Inline script that sets `data-mode` on `<html>` before first paint, so a dark-mode
1409
+ * visitor never sees a light flash. Put it in `<head>`.
1410
+ */
1411
+ function getColorModeScript({ defaultMode = "system", storageKey = "glyph-mode" } = {}) {
1412
+ return `(function(){try{var d=${JSON.stringify(defaultMode)},m=null;${storageKey ? `m=localStorage.getItem(${JSON.stringify(storageKey)});` : ""}if(m!=='light'&&m!=='dark'&&m!=='system')m=d;if(m==='system')m=matchMedia('${DARK_QUERY}').matches?'dark':'light';var r=document.documentElement;r.dataset.mode=m;r.style.colorScheme=m}catch(e){}})()`;
1413
+ }
1414
+ /** `<script>` element version of `getColorModeScript` for SSR frameworks. */
1415
+ function ColorModeScript(props) {
1416
+ return /* @__PURE__ */ jsx("script", {
1417
+ dangerouslySetInnerHTML: { __html: getColorModeScript(props) },
1418
+ suppressHydrationWarning: true
1419
+ });
1420
+ }
1421
+ //#endregion
1422
+ //#region src/themes.ts
1423
+ /**
1424
+ * Built-in themes: the token presets from @glyphui/tokens plus per-component recipes.
1425
+ * Extend any of them in your theme file: `defineTheme({ name: 'acme', extends: presets.glyph, ... })`.
1426
+ */
1427
+ /** Precise, dark-first, one electric-blue accent, layered soft shadows, Geist. */
1428
+ const glyph = presets$1.glyph;
1429
+ /** Zero radius, heavy borders, hard offset shadows, loud color. */
1430
+ const brutalist = defineTheme$1({
1431
+ name: "brutalist",
1432
+ extends: presets$1.brutalist,
1433
+ components: {
1434
+ Button: { className: { base: "font-bold uppercase tracking-wide shadow-sm hover:-translate-x-px hover:-translate-y-px hover:shadow-md active:translate-x-[3px] active:translate-y-[3px] active:shadow-none" } },
1435
+ Badge: { className: { base: "font-bold uppercase" } },
1436
+ Card: { defaultVariants: { variant: "elevated" } },
1437
+ Heading: { className: { base: "uppercase" } },
1438
+ Input: { className: { root: "shadow-sm focus-within:ring-0 focus-within:shadow-md" } }
1439
+ }
1440
+ });
1441
+ /** Serif display type, warm paper neutrals, deep green ink, hairlines instead of shadows. */
1442
+ const editorial = defineTheme$1({
1443
+ name: "editorial",
1444
+ extends: presets$1.editorial,
1445
+ components: {
1446
+ Card: { defaultVariants: { variant: "outline" } },
1447
+ Eyebrow: { className: { base: "font-sans tracking-[0.18em]" } },
1448
+ Button: { className: { base: "tracking-wide" } }
1449
+ }
1450
+ });
1451
+ /** Pill shapes, lavender and mint, chunky rounded type, springy motion. */
1452
+ const playful = defineTheme$1({
1453
+ name: "playful",
1454
+ extends: presets$1.playful,
1455
+ components: {
1456
+ Button: { className: { base: "font-bold hover:-translate-y-0.5 active:translate-y-0 active:scale-[0.97]" } },
1457
+ Card: { defaultVariants: { variant: "elevated" } },
1458
+ Badge: { className: { base: "font-semibold" } }
1459
+ }
1460
+ });
1461
+ const presets = {
1462
+ glyph,
1463
+ brutalist,
1464
+ editorial,
1465
+ playful
1466
+ };
1467
+ //#endregion
1468
+ //#region src/provider/theme-style.tsx
1469
+ /** Renders a theme's CSS variables. Re-renders replace the previous rules in place. */
1470
+ function ThemeStyle({ theme, selector }) {
1471
+ const css = useMemo(() => themeToCss$1(theme, selector ? { selector } : {}), [theme, selector]);
1472
+ return /* @__PURE__ */ jsx("style", {
1473
+ "data-glyph-theme-style": theme.name,
1474
+ dangerouslySetInnerHTML: { __html: css }
1475
+ });
1476
+ }
1477
+ //#endregion
1478
+ //#region src/provider/glyph-provider.tsx
1479
+ /** Provides the theme, color mode and app settings. Render once at the root. */
1480
+ function GlyphProvider({ children, theme = glyph, defaultMode, storageKey, linkComponent = "a" }) {
1481
+ const value = useMemo(() => ({
1482
+ theme,
1483
+ linkComponent
1484
+ }), [theme, linkComponent]);
1485
+ return /* @__PURE__ */ jsx(ColorModeProvider, {
1486
+ ...defaultMode !== void 0 && { defaultMode },
1487
+ ...storageKey !== void 0 && { storageKey },
1488
+ children: /* @__PURE__ */ jsxs(GlyphContext.Provider, {
1489
+ value,
1490
+ children: [/* @__PURE__ */ jsx(ThemeStyle, { theme }), /* @__PURE__ */ jsx(Tooltip$1.Provider, {
1491
+ delayDuration: 300,
1492
+ children
1493
+ })]
1494
+ })
1495
+ });
1496
+ }
1497
+ //#endregion
1498
+ //#region src/utils/cn.ts
1499
+ const twMerge = extendTailwindMerge(twMergeConfig);
1500
+ /** Joins class names and resolves Tailwind conflicts (last one wins). */
1501
+ function cn(...inputs) {
1502
+ return twMerge(clsx(inputs));
1503
+ }
1504
+ //#endregion
1505
+ //#region src/provider/theme-scope.tsx
1506
+ /** Renders a subtree with a different theme, e.g. to compare themes side by side. */
1507
+ function ThemeScope({ theme, mode, asChild, children, ...props }) {
1508
+ const parent = useContext(GlyphContext);
1509
+ const value = useMemo(() => ({
1510
+ theme,
1511
+ linkComponent: parent?.linkComponent ?? "a"
1512
+ }), [theme, parent?.linkComponent]);
1513
+ const Comp = asChild ? Slot.Root : "div";
1514
+ return /* @__PURE__ */ jsxs(GlyphContext.Provider, {
1515
+ value,
1516
+ children: [/* @__PURE__ */ jsx(ThemeStyle, {
1517
+ theme,
1518
+ selector: scopeSelector(theme)
1519
+ }), /* @__PURE__ */ jsx(Comp, {
1520
+ "data-glyph-theme": theme.name,
1521
+ "data-mode": mode,
1522
+ ...props,
1523
+ className: cn("bg-background font-sans text-foreground", props.className),
1524
+ children
1525
+ })]
1526
+ });
1527
+ }
1528
+ //#endregion
1529
+ export { AppShell, Async, Avatar, AvatarGroup, Badge, Button, Card, CardBody, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, ColorModeProvider, ColorModeScript, Container, EmptyState, ErrorState, Eyebrow, Field, FieldContext, GlyphProvider, Heading, IconButton, Input, Kbd, Label, LoadingState, Navbar, PageHeader, Section, SegmentedControl, Select, SelectItem, Separator, Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarItem, SidebarSection, SidebarTrigger, Skeleton, Spinner, Stack, Switch, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Text, Textarea, ThemeScope, Tooltip, appShellStyles, avatarStyles, badgeStyles, brutalist, buttonStyles, cardStyles, checkboxStyles, cn, containerStyles, defineTheme, editorial, eyebrowStyles, fieldControl, fieldStyles, getColorModeScript, glyph, headingStyles, inputStyles, kbdStyles, labelStyles, navbarStyles, pageHeaderStyles, playful, presets, sectionStyles, segmentedControlStyles, selectStyles, separatorStyles, sidebarStyles, skeletonStyles, spinnerStyles, stackStyles, stateStyles, switchStyles, tableStyles, tabsStyles, textStyles, textareaStyles, themeToCss, tokenVar, tooltipStyles, tv, useAppShell, useAsync, useColorMode, useComponentConfig, useControllableState, useFieldControl, useGlyph, useLinkComponent, useMediaQuery, useScrolled };