@repraze/kernelkit 0.1.0-alpha.1 → 0.1.0-alpha.3

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 (117) hide show
  1. package/dist/components/alert.d.ts.map +1 -1
  2. package/dist/components/alert.js +4 -6
  3. package/dist/components/alert.js.map +1 -1
  4. package/dist/components/avatar.js +1 -1
  5. package/dist/components/avatar.js.map +1 -1
  6. package/dist/components/badge.js +1 -1
  7. package/dist/components/badge.js.map +1 -1
  8. package/dist/components/breadcrumb.d.ts +1 -0
  9. package/dist/components/breadcrumb.d.ts.map +1 -1
  10. package/dist/components/breadcrumb.js +7 -6
  11. package/dist/components/breadcrumb.js.map +1 -1
  12. package/dist/components/calendar.js +1 -1
  13. package/dist/components/calendar.js.map +1 -1
  14. package/dist/components/callout.d.ts +8 -4
  15. package/dist/components/callout.d.ts.map +1 -1
  16. package/dist/components/callout.js +22 -12
  17. package/dist/components/callout.js.map +1 -1
  18. package/dist/components/chart.js +2 -2
  19. package/dist/components/chart.js.map +1 -1
  20. package/dist/components/code-block.js +1 -1
  21. package/dist/components/code-block.js.map +1 -1
  22. package/dist/components/command.d.ts +1 -1
  23. package/dist/components/command.d.ts.map +1 -1
  24. package/dist/components/command.js +3 -3
  25. package/dist/components/command.js.map +1 -1
  26. package/dist/components/data-table/elements/data-table-info-row.js +1 -1
  27. package/dist/components/data-table/elements/data-table-info-row.js.map +1 -1
  28. package/dist/components/dropdown-menu.d.ts +1 -1
  29. package/dist/components/dropdown-menu.d.ts.map +1 -1
  30. package/dist/components/dropdown-menu.js +2 -2
  31. package/dist/components/dropdown-menu.js.map +1 -1
  32. package/dist/components/kbd.d.ts.map +1 -1
  33. package/dist/components/kbd.js +2 -4
  34. package/dist/components/kbd.js.map +1 -1
  35. package/dist/components/message.js +2 -2
  36. package/dist/components/message.js.map +1 -1
  37. package/dist/components/meter.js +1 -1
  38. package/dist/components/meter.js.map +1 -1
  39. package/dist/components/navigation-menu.d.ts.map +1 -1
  40. package/dist/components/navigation-menu.js +3 -1
  41. package/dist/components/navigation-menu.js.map +1 -1
  42. package/dist/components/progress.js +1 -1
  43. package/dist/components/progress.js.map +1 -1
  44. package/dist/components/questionnaire.js +1 -1
  45. package/dist/components/questionnaire.js.map +1 -1
  46. package/dist/components/search-trigger.d.ts +1 -1
  47. package/dist/components/search-trigger.d.ts.map +1 -1
  48. package/dist/components/search-trigger.js +2 -2
  49. package/dist/components/search-trigger.js.map +1 -1
  50. package/dist/components/section.js +1 -1
  51. package/dist/components/section.js.map +1 -1
  52. package/dist/components/sidebar.d.ts.map +1 -1
  53. package/dist/components/sidebar.js +4 -3
  54. package/dist/components/sidebar.js.map +1 -1
  55. package/dist/components/site-footer.js +1 -1
  56. package/dist/components/site-footer.js.map +1 -1
  57. package/dist/components/site-header.d.ts +3 -3
  58. package/dist/components/site-header.d.ts.map +1 -1
  59. package/dist/components/site-header.js +7 -9
  60. package/dist/components/site-header.js.map +1 -1
  61. package/dist/components/stat.js +2 -2
  62. package/dist/components/stat.js.map +1 -1
  63. package/dist/components/tabs.d.ts +4 -2
  64. package/dist/components/tabs.d.ts.map +1 -1
  65. package/dist/components/tabs.js +13 -7
  66. package/dist/components/tabs.js.map +1 -1
  67. package/dist/components/theme-menu.d.ts +49 -0
  68. package/dist/components/theme-menu.d.ts.map +1 -0
  69. package/dist/components/theme-menu.js +75 -0
  70. package/dist/components/theme-menu.js.map +1 -0
  71. package/dist/components/toc.js +1 -1
  72. package/dist/components/toc.js.map +1 -1
  73. package/dist/components/tooltip.d.ts.map +1 -1
  74. package/dist/components/tooltip.js +5 -2
  75. package/dist/components/tooltip.js.map +1 -1
  76. package/dist/components/trace.js +1 -1
  77. package/dist/components/trace.js.map +1 -1
  78. package/dist/lib/recipes.d.ts +17 -1
  79. package/dist/lib/recipes.d.ts.map +1 -1
  80. package/dist/lib/recipes.js +19 -1
  81. package/dist/lib/recipes.js.map +1 -1
  82. package/dist/provider/theme-provider.d.ts +4 -0
  83. package/dist/provider/theme-provider.d.ts.map +1 -1
  84. package/dist/provider/theme-provider.js +14 -1
  85. package/dist/provider/theme-provider.js.map +1 -1
  86. package/package.json +1 -1
  87. package/src/components/alert.tsx +4 -6
  88. package/src/components/avatar.tsx +1 -1
  89. package/src/components/badge.tsx +1 -1
  90. package/src/components/breadcrumb.tsx +8 -7
  91. package/src/components/calendar.tsx +1 -1
  92. package/src/components/callout.tsx +27 -12
  93. package/src/components/chart.tsx +2 -2
  94. package/src/components/code-block.tsx +1 -1
  95. package/src/components/command.tsx +3 -4
  96. package/src/components/data-table/elements/data-table-info-row.tsx +1 -1
  97. package/src/components/dropdown-menu.tsx +2 -2
  98. package/src/components/kbd.tsx +2 -4
  99. package/src/components/message.tsx +2 -2
  100. package/src/components/meter.tsx +1 -1
  101. package/src/components/navigation-menu.tsx +4 -0
  102. package/src/components/progress.tsx +1 -1
  103. package/src/components/questionnaire.tsx +1 -1
  104. package/src/components/search-trigger.tsx +2 -3
  105. package/src/components/section.tsx +1 -1
  106. package/src/components/sidebar.tsx +4 -3
  107. package/src/components/site-footer.tsx +1 -1
  108. package/src/components/site-header.tsx +8 -9
  109. package/src/components/stat.tsx +2 -2
  110. package/src/components/tabs.tsx +17 -9
  111. package/src/components/theme-menu.tsx +243 -0
  112. package/src/components/toc.tsx +1 -1
  113. package/src/components/tooltip.tsx +15 -3
  114. package/src/components/trace.tsx +1 -1
  115. package/src/lib/recipes.ts +23 -2
  116. package/src/provider/theme-provider.tsx +18 -1
  117. package/src/styles/kernelkit.css +4 -0
@@ -0,0 +1,243 @@
1
+ "use client";
2
+
3
+ import {Button} from "@repraze/kernelkit/components/button";
4
+ import {Label} from "@repraze/kernelkit/components/label";
5
+ import {
6
+ Popover,
7
+ PopoverContent,
8
+ PopoverHeader,
9
+ PopoverTitle,
10
+ PopoverTrigger,
11
+ } from "@repraze/kernelkit/components/popover";
12
+ import {Switch} from "@repraze/kernelkit/components/switch";
13
+ import {ToggleGroup, ToggleGroupItem} from "@repraze/kernelkit/components/toggle-group";
14
+ import {Tooltip, TooltipContent, TooltipTrigger} from "@repraze/kernelkit/components/tooltip";
15
+ import {type Accent, ACCENTS, type Mode, RADII, type Radius, useTheme} from "@repraze/kernelkit/provider";
16
+ import {cn} from "cn";
17
+ import {CheckIcon, type LucideIcon, MonitorIcon, MoonIcon, RotateCcwIcon, SunIcon} from "lucide-react";
18
+ import * as React from "react";
19
+
20
+ /** Mode choices in display order, with the icon the trigger shows for each. */
21
+ const THEME_MODES: readonly {value: Mode; label: string; icon: LucideIcon}[] = [
22
+ {value: "light", label: "Light", icon: SunIcon},
23
+ {value: "dark", label: "Dark", icon: MoonIcon},
24
+ {value: "system", label: "System", icon: MonitorIcon},
25
+ ];
26
+
27
+ const modeOption = (mode: Mode) => THEME_MODES.find((m) => m.value === mode) ?? THEME_MODES[2]!;
28
+
29
+ /** The icon for the chosen mode (not the resolved one): sun, moon, or monitor for "system". */
30
+ function ThemeModeIcon({mode, ...props}: {mode?: Mode} & React.ComponentProps<LucideIcon>) {
31
+ const theme = useTheme();
32
+ const Icon = modeOption(mode ?? theme.mode).icon;
33
+ return <Icon data-slot="theme-mode-icon" {...props} />;
34
+ }
35
+
36
+ /** Light / dark / system as a segmented control. */
37
+ function ThemeModePicker({className}: {className?: string}) {
38
+ const theme = useTheme();
39
+ return (
40
+ <ToggleGroup
41
+ data-slot="theme-mode-picker"
42
+ aria-label="Mode"
43
+ variant="outline"
44
+ size="sm"
45
+ value={[theme.mode]}
46
+ onValueChange={(v) => v[0] && theme.setMode(v[0] as Mode)}
47
+ className={cn("w-full", className)}
48
+ >
49
+ {THEME_MODES.map(({value, label, icon: Icon}) => (
50
+ <ToggleGroupItem key={value} value={value} className="flex-1">
51
+ <Icon /> {label}
52
+ </ToggleGroupItem>
53
+ ))}
54
+ </ToggleGroup>
55
+ );
56
+ }
57
+
58
+ /** Swatch per accent: the vivid fill tier, with that accent's own on-fill color for the check mark. */
59
+ const swatch = (accent: Accent) => ({
60
+ background: `var(--tc-accent-${accent}-accent-fill)`,
61
+ color: `var(--tc-accent-${accent}-on-accent-fill)`,
62
+ });
63
+
64
+ /** The eight TermiColor accents as swatches; sets the theme's accent. */
65
+ function ThemeAccentPicker({className}: {className?: string}) {
66
+ const theme = useTheme();
67
+ return (
68
+ <div
69
+ data-slot="theme-accent-picker"
70
+ role="group"
71
+ aria-label="Accent"
72
+ className={cn("grid grid-cols-8 gap-1.5", className)}
73
+ >
74
+ {ACCENTS.map((accent) => {
75
+ const on = theme.accent === accent;
76
+ return (
77
+ <Tooltip key={accent}>
78
+ <TooltipTrigger
79
+ render={
80
+ <button
81
+ type="button"
82
+ aria-label={accent}
83
+ aria-pressed={on}
84
+ onClick={() => theme.setAccent(accent)}
85
+ style={swatch(accent)}
86
+ className={cn(
87
+ "flex aspect-square items-center justify-center rounded-md outline-none",
88
+ "ring-offset-2 ring-offset-transparent transition-shadow duration-100 motion-reduce:transition-none",
89
+ "hover:ring-2 hover:ring-input focus-visible:ring-2 focus-visible:ring-ring",
90
+ on && "ring-2 ring-foreground hover:ring-foreground",
91
+ )}
92
+ />
93
+ }
94
+ >
95
+ {on ? <CheckIcon className="size-3.5" strokeWidth={3} /> : null}
96
+ </TooltipTrigger>
97
+ <TooltipContent className="capitalize">{accent}</TooltipContent>
98
+ </Tooltip>
99
+ );
100
+ })}
101
+ </div>
102
+ );
103
+ }
104
+
105
+ /** The radius scale as a segmented control; sets the theme's radius. */
106
+ function ThemeRadiusPicker({className}: {className?: string}) {
107
+ const theme = useTheme();
108
+ return (
109
+ <ToggleGroup
110
+ data-slot="theme-radius-picker"
111
+ aria-label="Radius"
112
+ variant="outline"
113
+ size="sm"
114
+ value={[theme.radius]}
115
+ onValueChange={(v) => v[0] && theme.setRadius(v[0] as Radius)}
116
+ className={cn("w-full", className)}
117
+ >
118
+ {RADII.map((radius) => (
119
+ <ToggleGroupItem key={radius} value={radius} className="flex-1 font-alt">
120
+ {radius}
121
+ </ToggleGroupItem>
122
+ ))}
123
+ </ToggleGroup>
124
+ );
125
+ }
126
+
127
+ /** High contrast on/off, with its label. */
128
+ function ThemeContrastSwitch({className}: {className?: string}) {
129
+ const theme = useTheme();
130
+ const id = React.useId();
131
+ return (
132
+ <div data-slot="theme-contrast-switch" className={cn("flex items-center justify-between gap-2", className)}>
133
+ <Label htmlFor={id}>High contrast</Label>
134
+ <Switch
135
+ id={id}
136
+ checked={theme.contrast === "high"}
137
+ onCheckedChange={(on) => theme.setContrast(on ? "high" : "normal")}
138
+ />
139
+ </div>
140
+ );
141
+ }
142
+
143
+ /** A labelled block inside the menu. */
144
+ function ThemeMenuSection({
145
+ label,
146
+ children,
147
+ className,
148
+ }: {
149
+ label: string;
150
+ children: React.ReactNode;
151
+ className?: string;
152
+ }) {
153
+ return (
154
+ <div data-slot="theme-menu-section" className={cn("flex flex-col gap-2", className)}>
155
+ <span className="text-xs font-medium text-muted-foreground">{label}</span>
156
+ {children}
157
+ </div>
158
+ );
159
+ }
160
+
161
+ interface ThemeMenuProps extends Omit<React.ComponentProps<typeof Button>, "children" | "title"> {
162
+ /** Popover title. */
163
+ title?: React.ReactNode;
164
+ /** Extra controls, after the built-in ones and before Reset. */
165
+ children?: React.ReactNode;
166
+ /** Popover alignment against the trigger. */
167
+ align?: "start" | "center" | "end";
168
+ contentClassName?: string;
169
+ }
170
+
171
+ /**
172
+ * The suite's theme control (GUIDELINES §7b): one ghost icon button in `SiteHeaderActions` showing the
173
+ * chosen mode (sun, moon, monitor), opening a popover with mode, accent, high contrast, radius and a
174
+ * Reset to the provider's defaults. Reads and writes `useTheme()`, so it needs a `ThemeProvider`.
175
+ * Trigger props (`aria-label`, `className`, `data-*`) pass through to the button.
176
+ */
177
+ function ThemeMenu({title = "Theme", children, align = "end", contentClassName, className, ...props}: ThemeMenuProps) {
178
+ const theme = useTheme();
179
+ const {defaults} = theme;
180
+ const isDefault =
181
+ theme.mode === defaults.mode &&
182
+ theme.accent === defaults.accent &&
183
+ theme.contrast === defaults.contrast &&
184
+ theme.radius === defaults.radius;
185
+ return (
186
+ <Popover>
187
+ <PopoverTrigger
188
+ render={
189
+ <Button
190
+ variant="ghost"
191
+ size="icon"
192
+ aria-label={`Theme: ${modeOption(theme.mode).label}`}
193
+ data-slot="theme-menu-trigger"
194
+ data-mode={theme.mode}
195
+ className={className}
196
+ {...props}
197
+ />
198
+ }
199
+ >
200
+ <ThemeModeIcon />
201
+ </PopoverTrigger>
202
+ <PopoverContent data-slot="theme-menu-content" align={align} className={cn("w-72 gap-4", contentClassName)}>
203
+ <PopoverHeader>
204
+ <PopoverTitle>{title}</PopoverTitle>
205
+ </PopoverHeader>
206
+ <ThemeMenuSection label="Mode">
207
+ <ThemeModePicker />
208
+ </ThemeMenuSection>
209
+ <ThemeMenuSection label="Accent">
210
+ <ThemeAccentPicker />
211
+ </ThemeMenuSection>
212
+ <ThemeContrastSwitch />
213
+ <ThemeMenuSection label="Radius">
214
+ <ThemeRadiusPicker />
215
+ </ThemeMenuSection>
216
+ {children}
217
+ <div className="-mx-3 -mb-3 border-t border-border p-3">
218
+ <Button
219
+ variant="outline"
220
+ size="sm"
221
+ data-slot="theme-menu-reset"
222
+ className="w-full"
223
+ disabled={isDefault}
224
+ onClick={theme.resetTheme}
225
+ >
226
+ <RotateCcwIcon data-icon="inline-start" /> Reset to defaults
227
+ </Button>
228
+ </div>
229
+ </PopoverContent>
230
+ </Popover>
231
+ );
232
+ }
233
+
234
+ export {
235
+ ThemeAccentPicker,
236
+ ThemeContrastSwitch,
237
+ ThemeMenu,
238
+ ThemeMenuSection,
239
+ ThemeModeIcon,
240
+ ThemeModePicker,
241
+ ThemeRadiusPicker,
242
+ };
243
+ export type {ThemeMenuProps};
@@ -35,7 +35,7 @@ function TocTitle({className, render, ...props}: useRender.ComponentProps<"p">)
35
35
  return useRender({
36
36
  defaultTagName: "p",
37
37
  props: mergeProps<"p">(
38
- {className: cn("font-mono text-xs font-medium text-muted-foreground uppercase", className)},
38
+ {className: cn("font-alt text-xs font-medium text-muted-foreground uppercase", className)},
39
39
  props,
40
40
  ),
41
41
  render,
@@ -39,15 +39,27 @@ function TooltipContent({
39
39
  <TooltipPrimitive.Popup
40
40
  data-slot="tooltip-content"
41
41
  className={cn(
42
- // Inverted chip: max contrast against any surface, so it reads in high contrast too.
42
+ // Popover-coloured chip: the popup surface and border, small mono text.
43
43
  popupMotion,
44
- "z-50 inline-flex min-h-6 w-fit max-w-xs items-center gap-1.5 rounded-md bg-foreground px-2 py-1 text-xs text-background shadow-md shadow-(color:--shadow-color) has-data-[slot=kbd]:pr-1 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:h-4 **:data-[slot=kbd]:min-w-4 **:data-[slot=kbd]:rounded-xs",
44
+ "z-50 inline-flex min-h-6 w-fit max-w-xs items-center gap-1.5 rounded-md border border-border bg-popover px-2 py-1 font-alt text-xs text-popover-foreground shadow-md shadow-(color:--shadow-color) has-data-[slot=kbd]:pr-1 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:h-4 **:data-[slot=kbd]:min-w-4 **:data-[slot=kbd]:rounded-xs",
45
45
  className,
46
46
  )}
47
47
  {...props}
48
48
  >
49
49
  {children}
50
- <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-xs bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
50
+ <TooltipPrimitive.Arrow
51
+ className={cn(
52
+ // A rotated square in the popup colours; the border sits on the two edges that face out,
53
+ // centred on the popup's border line so the outline runs on without a seam.
54
+ "z-50 size-2.5 rotate-45 rounded-xs border-border bg-popover",
55
+ "data-[side=top]:-bottom-1.25 data-[side=top]:border-r data-[side=top]:border-b",
56
+ "data-[side=bottom]:-top-1.25 data-[side=bottom]:border-t data-[side=bottom]:border-l",
57
+ "data-[side=left]:top-1/2! data-[side=left]:-right-1.25 data-[side=left]:-translate-y-1/2 data-[side=left]:border-t data-[side=left]:border-r",
58
+ "data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1.25 data-[side=inline-start]:-translate-y-1/2 data-[side=inline-start]:border-t data-[side=inline-start]:border-r",
59
+ "data-[side=right]:top-1/2! data-[side=right]:-left-1.25 data-[side=right]:-translate-y-1/2 data-[side=right]:border-b data-[side=right]:border-l",
60
+ "data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1.25 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-end]:border-b data-[side=inline-end]:border-l",
61
+ )}
62
+ />
51
63
  </TooltipPrimitive.Popup>
52
64
  </TooltipPrimitive.Positioner>
53
65
  </TooltipPrimitive.Portal>
@@ -67,7 +67,7 @@ function TraceDescription({className, ...props}: React.ComponentProps<"span">) {
67
67
  return (
68
68
  <span
69
69
  data-slot="trace-description"
70
- className={cn("shrink-0 font-mono text-xs text-muted-foreground tabular-nums", className)}
70
+ className={cn("shrink-0 font-alt text-xs text-muted-foreground tabular-nums", className)}
71
71
  {...props}
72
72
  />
73
73
  );
@@ -23,9 +23,16 @@ export const popupSurface =
23
23
  export const popupMotion =
24
24
  "origin-(--transform-origin) duration-100 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 motion-reduce:animate-none";
25
25
 
26
+ /**
27
+ * The sidebar's current-row indicator (§7c): a 2px accent pill inset on the row's left edge, hidden until
28
+ * the row's state shows it (`data-highlighted:after:opacity-100`, `data-active:after:opacity-100`...).
29
+ * The row keeps its own rounding on every corner.
30
+ */
31
+ export const rowIndicator =
32
+ "after:pointer-events-none after:absolute after:top-1.5 after:bottom-1.5 after:left-0 after:w-0.5 after:rounded-pill after:bg-primary-text after:opacity-0";
33
+
26
34
  /** Row inside a popup or list: menu item, select item, command item (§4, §6). */
27
- export const listItem =
28
- "relative flex min-h-8 cursor-default items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-highlighted:[&_svg:not([class*='text-'])]:text-accent-foreground";
35
+ export const listItem = `${rowIndicator} data-highlighted:after:opacity-100 relative flex min-h-8 cursor-default items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-highlighted:[&_svg:not([class*='text-'])]:text-accent-foreground`;
29
36
 
30
37
  /** Group label inside a popup or list. */
31
38
  export const listLabel = "px-2 py-1.5 text-xs font-medium text-muted-foreground";
@@ -45,3 +52,17 @@ export const placeholderFill = "bg-foreground/10 hc:bg-border";
45
52
 
46
53
  /** Selected row (table, data table, code line, list): accent tint + 2px inset primary indicator (§6). */
47
54
  export const selectedRow = "bg-accent text-accent-foreground shadow-[inset_2px_0_0_var(--primary-text)]";
55
+
56
+ /**
57
+ * Status surface shared by Alert and Callout (§2): one `soft` / `outline` axis, same values in both.
58
+ * Expects a color variant (`--c`, `--c-tint`) on the same element. Neutral swaps the hue border for the
59
+ * plain border on a card surface (`statusSurfaceNeutral`).
60
+ */
61
+ export const statusSurface = {
62
+ // status tint behind foreground text; the hue lives in the border, icon and accents (TermiColor rule)
63
+ soft: "border-(--c)/30 bg-(--c-tint) hc:border-(--c)",
64
+ // no wash: card surface with a hue border, for dense layouts
65
+ outline: "border-(--c)/50 bg-card hc:border-(--c)",
66
+ } as const;
67
+
68
+ export const statusSurfaceNeutral = "border-border bg-card hc:border-border";
@@ -32,6 +32,10 @@ export interface ThemeContextValue extends ThemeState {
32
32
  setContrast: (contrast: ThemeState["contrast"]) => void;
33
33
  setAccent: (accent: ThemeState["accent"]) => void;
34
34
  setRadius: (radius: ThemeState["radius"]) => void;
35
+ /** The values `resetTheme` restores: the provider's `defaultTheme` over the kit defaults. */
36
+ defaults: ThemeState;
37
+ /** Forget the stored choice and go back to `defaults`. */
38
+ resetTheme: () => void;
35
39
  }
36
40
 
37
41
  const ThemeContext = createContext<ThemeContextValue | null>(null);
@@ -139,6 +143,17 @@ export function ThemeProvider({
139
143
  [storageKey, fallback],
140
144
  );
141
145
 
146
+ const resetTheme = useCallback(() => {
147
+ if (storageKey) {
148
+ try {
149
+ window.localStorage.removeItem(storageKey);
150
+ } catch {
151
+ // storage blocked: the in-memory reset still applies
152
+ }
153
+ }
154
+ setStored(fallback);
155
+ }, [storageKey, fallback]);
156
+
142
157
  const value = useMemo<ThemeContextValue>(
143
158
  () => ({
144
159
  ...theme,
@@ -148,8 +163,10 @@ export function ThemeProvider({
148
163
  setContrast: (contrast) => setTheme({contrast}),
149
164
  setAccent: (accent) => setTheme({accent}),
150
165
  setRadius: (radius) => setTheme({radius}),
166
+ defaults: fallback,
167
+ resetTheme,
151
168
  }),
152
- [theme, resolvedMode, setTheme],
169
+ [theme, resolvedMode, setTheme, fallback, resetTheme],
153
170
  );
154
171
 
155
172
  return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
@@ -133,6 +133,9 @@
133
133
  --kk-font-mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
134
134
  /* Titles: card, dialog, sheet, empty, alert. Mono by default (the TermiColor look). */
135
135
  --kk-font-heading: var(--kk-font-mono);
136
+ /* Labels and readouts: badges, tooltips, breadcrumbs, kbd, shortcuts, eyebrows, nav, values. Mono by
137
+ default (the TermiColor look); point it at any face. Code always stays on --kk-font-mono. */
138
+ --kk-font-alt: var(--kk-font-mono);
136
139
  /* Site shell (site-header, site-layout): content width, and the sticky header's height that
137
140
  sidebars and anchors offset by. A site with a banner above the header row adds it here. */
138
141
  --kk-site-width: 80rem;
@@ -159,6 +162,7 @@
159
162
  --font-sans: var(--kk-font-sans);
160
163
  --font-mono: var(--kk-font-mono);
161
164
  --font-heading: var(--kk-font-heading);
165
+ --font-alt: var(--kk-font-alt);
162
166
 
163
167
  --radius-xs: calc(var(--kk-radius) * 0.25);
164
168
  --radius-sm: calc(var(--kk-radius) * 0.5);