@azmr/ui 0.1.0 → 0.3.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 (95) hide show
  1. package/README.md +171 -0
  2. package/dist/avatar.d.ts +58 -0
  3. package/dist/avatar.js +84 -0
  4. package/dist/breadcrumb.d.ts +47 -0
  5. package/dist/breadcrumb.js +120 -0
  6. package/dist/button-CYt2Zw3D.d.ts +64 -0
  7. package/dist/button.d.ts +54 -0
  8. package/dist/button.js +19 -0
  9. package/dist/chunk-2RIWL363.js +27 -0
  10. package/dist/chunk-2TQSHVXA.js +12 -0
  11. package/dist/chunk-7EOSDFS3.js +10 -0
  12. package/dist/chunk-C2HKIPLQ.js +118 -0
  13. package/dist/chunk-CAJLW6K2.js +16 -0
  14. package/dist/chunk-CZJZYN54.js +36 -0
  15. package/dist/chunk-EE4V6XMB.js +35 -0
  16. package/dist/chunk-EFHRRLAW.js +211 -0
  17. package/dist/chunk-GLB75NT3.js +21 -0
  18. package/dist/chunk-GX2YEJ43.js +36 -0
  19. package/dist/chunk-LTOLV67A.js +19 -0
  20. package/dist/chunk-LZMX27IZ.js +10 -0
  21. package/dist/chunk-NIIDYIZA.js +1 -0
  22. package/dist/chunk-Q4PPM2SU.js +97 -0
  23. package/dist/chunk-WVE2E37B.js +20 -0
  24. package/dist/chunk-XRZHP6BD.js +92 -0
  25. package/dist/disclosure.d.ts +72 -0
  26. package/dist/disclosure.js +221 -0
  27. package/dist/display.d.ts +111 -0
  28. package/dist/display.js +237 -0
  29. package/dist/features-dom-MCGKRLN5.js +7 -0
  30. package/dist/features-max-COBTQCO6.js +7 -0
  31. package/dist/floating.d.ts +81 -0
  32. package/dist/floating.js +231 -0
  33. package/dist/hooks.d.ts +33 -0
  34. package/dist/hooks.js +55 -0
  35. package/dist/icons/lucide.d.ts +77 -0
  36. package/dist/icons/lucide.js +135 -0
  37. package/dist/icons/mui-map.d.ts +1268 -0
  38. package/dist/icons/mui-map.js +352 -0
  39. package/dist/icons.d.ts +48 -0
  40. package/dist/icons.js +19 -0
  41. package/dist/index.d.ts +238 -3
  42. package/dist/index.js +242 -15
  43. package/dist/input.d.ts +14 -0
  44. package/dist/input.js +8 -0
  45. package/dist/layout/mui-map.d.ts +80 -0
  46. package/dist/layout/mui-map.js +104 -0
  47. package/dist/layout.d.ts +89 -0
  48. package/dist/layout.js +455 -0
  49. package/dist/lib.d.ts +11 -0
  50. package/dist/lib.js +6 -0
  51. package/dist/menu.d.ts +105 -0
  52. package/dist/menu.js +56 -0
  53. package/dist/modal.d.ts +105 -0
  54. package/dist/modal.js +273 -0
  55. package/dist/motion.css +295 -0
  56. package/dist/motion.d.ts +49 -0
  57. package/dist/motion.js +48 -0
  58. package/dist/pagination.d.ts +82 -0
  59. package/dist/pagination.js +211 -0
  60. package/dist/presets.d.ts +97 -0
  61. package/dist/presets.js +94 -0
  62. package/dist/progress.d.ts +70 -0
  63. package/dist/progress.js +138 -0
  64. package/dist/react.d.ts +81 -0
  65. package/dist/react.js +281 -0
  66. package/dist/responsive-YJBnvsjt.d.ts +69 -0
  67. package/dist/scroll-area.d.ts +30 -0
  68. package/dist/scroll-area.js +80 -0
  69. package/dist/shared-Fkx-sjPt.d.ts +15 -0
  70. package/dist/spinner.d.ts +18 -0
  71. package/dist/spinner.js +39 -0
  72. package/dist/split-button.d.ts +32 -0
  73. package/dist/split-button.js +54 -0
  74. package/dist/stepper.d.ts +27 -0
  75. package/dist/stepper.js +104 -0
  76. package/dist/styles.css +24 -0
  77. package/dist/table.d.ts +52 -0
  78. package/dist/table.js +124 -0
  79. package/dist/theme.d.ts +111 -0
  80. package/dist/theme.js +414 -0
  81. package/dist/toast.d.ts +77 -0
  82. package/dist/toast.js +203 -0
  83. package/dist/toggle.d.ts +55 -0
  84. package/dist/toggle.js +88 -0
  85. package/dist/tokens.css +337 -0
  86. package/dist/transition.d.ts +51 -0
  87. package/dist/transition.js +79 -0
  88. package/dist/types-BBudf463.d.ts +39 -0
  89. package/dist/typography/mui-map.d.ts +91 -0
  90. package/dist/typography/mui-map.js +62 -0
  91. package/dist/typography.d.ts +90 -0
  92. package/dist/typography.js +193 -0
  93. package/dist/useSignal-C-3ZGURN.d.ts +21 -0
  94. package/dist/variants-De0c0ucO.d.ts +60 -0
  95. package/package.json +179 -13
package/dist/toast.js ADDED
@@ -0,0 +1,203 @@
1
+ "use client";
2
+ import {
3
+ Icon
4
+ } from "./chunk-XRZHP6BD.js";
5
+ import {
6
+ Spinner,
7
+ buttonVariants
8
+ } from "./chunk-C2HKIPLQ.js";
9
+ import {
10
+ cn
11
+ } from "./chunk-2TQSHVXA.js";
12
+
13
+ // src/primitives/toast.ts
14
+ import { Toast } from "@base-ui/react/toast";
15
+
16
+ // src/components/toast/toast.ts
17
+ var toastManager = Toast.createToastManager();
18
+ function toBaseOptions(title, type, o = {}) {
19
+ const { description, duration, id, priority, action, onClose } = o;
20
+ return {
21
+ ...id !== void 0 && { id },
22
+ ...title !== void 0 && { title },
23
+ ...description !== void 0 && { description },
24
+ ...type && { type },
25
+ ...priority && { priority },
26
+ ...duration !== void 0 && { timeout: duration },
27
+ ...onClose && { onClose },
28
+ ...action && { actionProps: { children: action.label, onClick: action.onClick } }
29
+ };
30
+ }
31
+ var defaultPriority = (type) => type === "error" ? "high" : "low";
32
+ function show(type) {
33
+ return (title, options) => toastManager.add({
34
+ priority: defaultPriority(type),
35
+ ...toBaseOptions(title, type, options)
36
+ });
37
+ }
38
+ var toast = Object.assign(show(void 0), {
39
+ success: show("success"),
40
+ error: show("error"),
41
+ info: show("info"),
42
+ warning: show("warning"),
43
+ /** Stays until updated or dismissed. Pair it with `toast.update`, or use `toast.promise`. */
44
+ loading: show("loading"),
45
+ /** Show `loading`, then `success` or `error` when the promise settles. Returns the same promise. */
46
+ promise(promise, messages) {
47
+ return toastManager.promise(promise, {
48
+ loading: { title: messages.loading, priority: "low" },
49
+ success: (value) => ({
50
+ title: typeof messages.success === "function" ? messages.success(value) : messages.success,
51
+ priority: "low",
52
+ timeout: void 0
53
+ }),
54
+ error: (error) => ({
55
+ title: typeof messages.error === "function" ? messages.error(error) : messages.error,
56
+ priority: "high",
57
+ timeout: void 0
58
+ })
59
+ });
60
+ },
61
+ /** Change a toast that is showing: its text, its type or its timer. */
62
+ update(id, changes) {
63
+ const { title, type, ...rest } = changes;
64
+ toastManager.update(id, {
65
+ // Only re-derive urgency when the type changes; a text-only update must not downgrade an error.
66
+ ...type && { priority: defaultPriority(type === "default" ? void 0 : type) },
67
+ ...toBaseOptions(title, type === "default" ? void 0 : type, rest),
68
+ ...type === "default" && { type: void 0 }
69
+ });
70
+ },
71
+ /** Dismiss one toast, or all of them when called without an id. */
72
+ dismiss(id) {
73
+ toastManager.close(id);
74
+ }
75
+ });
76
+
77
+ // src/components/toast/toaster.tsx
78
+ import { CircleAlert, CircleCheck, Info, TriangleAlert, X } from "lucide-react";
79
+ import { jsx, jsxs } from "react/jsx-runtime";
80
+ var VIEWPORT = "fixed z-(--z-toast) w-[calc(100vw-2rem)] outline-none sm:w-96";
81
+ var PLACEMENT = {
82
+ "top-left": "top-4 left-4",
83
+ "top-center": "top-4 left-1/2 -translate-x-1/2",
84
+ "top-right": "top-4 right-4",
85
+ "bottom-left": "bottom-4 left-4",
86
+ "bottom-center": "bottom-4 left-1/2 -translate-x-1/2",
87
+ "bottom-right": "bottom-4 right-4"
88
+ };
89
+ var SWIPE = {
90
+ "top-left": ["up", "left"],
91
+ "top-center": ["up"],
92
+ "top-right": ["up", "right"],
93
+ "bottom-left": ["down", "left"],
94
+ "bottom-center": ["down"],
95
+ "bottom-right": ["down", "right"]
96
+ };
97
+ var ROOT_COMMON = cn(
98
+ "[--gap:0.75rem] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))] [--height:var(--toast-frontmost-height,var(--toast-height))]",
99
+ "[--offset-y:calc(var(--dir)*(var(--toast-offset-y)+var(--toast-index)*var(--gap))+var(--toast-swipe-movement-y))]",
100
+ "absolute right-0 left-auto z-[calc(1000-var(--toast-index))] mr-0 h-(--height) w-full select-none",
101
+ "[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+var(--dir)*(var(--toast-index)*var(--peek)+var(--shrink)*var(--height))))_scale(var(--scale))]",
102
+ "data-[expanded]:h-(--toast-height) data-[expanded]:[transform:translateX(var(--toast-swipe-movement-x))_translateY(var(--offset-y))]",
103
+ "rounded-lg border border-border bg-popover text-popover-foreground shadow-elevation-3",
104
+ "transition-[transform,opacity,height] duration-(--motion-duration-slow) ease-decelerate data-[swiping]:transition-none",
105
+ "data-[limited]:opacity-0 data-[ending-style]:opacity-0",
106
+ // Entering and leaving without a swipe: slide toward the anchored edge.
107
+ "data-[starting-style]:[transform:translateY(calc(var(--dir)*-150%))] [&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(calc(var(--dir)*-150%))]",
108
+ // Leaving by swipe: carry on in the swipe direction.
109
+ "data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]",
110
+ "data-[ending-style]:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))] data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))]",
111
+ // Keeps the pointer "inside" the stack across the gap between toasts, so it does not collapse.
112
+ "after:absolute after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']"
113
+ );
114
+ var ROOT_BOTTOM = "[--dir:-1] bottom-0 origin-bottom after:top-full";
115
+ var ROOT_TOP = "[--dir:1] top-0 origin-top after:bottom-full";
116
+ var ICONS = {
117
+ success: { icon: CircleCheck, colour: "text-success" },
118
+ error: { icon: CircleAlert, colour: "text-destructive" },
119
+ warning: { icon: TriangleAlert, colour: "text-warning-text" },
120
+ info: { icon: Info, colour: "text-info" }
121
+ };
122
+ function TypeIcon({ type }) {
123
+ if (type === "loading") return /* @__PURE__ */ jsx(Spinner, { className: "mt-0.5 shrink-0 text-muted-foreground" });
124
+ const entry = ICONS[type];
125
+ if (!entry) return null;
126
+ return /* @__PURE__ */ jsx(
127
+ Icon,
128
+ {
129
+ icon: entry.icon,
130
+ "data-azmr-toast-icon": "",
131
+ className: cn("mt-0.5 shrink-0", entry.colour)
132
+ }
133
+ );
134
+ }
135
+ function ToastList({ position }) {
136
+ const { toasts } = Toast.useToastManager();
137
+ const edge = position.startsWith("top") ? ROOT_TOP : ROOT_BOTTOM;
138
+ return toasts.map((t) => /* @__PURE__ */ jsx(
139
+ Toast.Root,
140
+ {
141
+ toast: t,
142
+ swipeDirection: [...SWIPE[position]],
143
+ "data-toast": "",
144
+ className: cn(ROOT_COMMON, edge),
145
+ children: /* @__PURE__ */ jsxs(
146
+ Toast.Content,
147
+ {
148
+ "data-toast-content": "",
149
+ className: "flex items-start gap-3 overflow-hidden p-4 transition-opacity duration-(--motion-duration-base) ease-standard data-[behind]:opacity-0 data-[expanded]:opacity-100",
150
+ children: [
151
+ /* @__PURE__ */ jsx(TypeIcon, { type: t.type }),
152
+ /* @__PURE__ */ jsxs("div", { className: "grid min-w-0 flex-1 gap-1", children: [
153
+ /* @__PURE__ */ jsx(Toast.Title, { className: "text-sm font-medium leading-tight" }),
154
+ /* @__PURE__ */ jsx(Toast.Description, { className: "text-sm text-muted-foreground" }),
155
+ /* @__PURE__ */ jsx(
156
+ Toast.Action,
157
+ {
158
+ onClick: () => toastManager.close(t.id),
159
+ className: cn(
160
+ buttonVariants({ variant: "outline", size: "sm" }),
161
+ "mt-2 justify-self-start"
162
+ )
163
+ }
164
+ )
165
+ ] }),
166
+ /* @__PURE__ */ jsx(
167
+ Toast.Close,
168
+ {
169
+ "aria-label": "Dismiss notification",
170
+ className: cn(
171
+ buttonVariants({ variant: "ghost", size: "icon" }),
172
+ "-mr-2 -mt-2 size-8 shrink-0"
173
+ ),
174
+ children: /* @__PURE__ */ jsx(Icon, { icon: X })
175
+ }
176
+ )
177
+ ]
178
+ }
179
+ )
180
+ },
181
+ t.id
182
+ ));
183
+ }
184
+ function Toaster({
185
+ position = "bottom-right",
186
+ limit = 3,
187
+ duration = 5e3,
188
+ label = "Notifications",
189
+ className
190
+ }) {
191
+ return /* @__PURE__ */ jsx(Toast.Provider, { toastManager, limit, timeout: duration, children: /* @__PURE__ */ jsx(Toast.Portal, { children: /* @__PURE__ */ jsx(
192
+ Toast.Viewport,
193
+ {
194
+ "aria-label": label,
195
+ className: cn(VIEWPORT, PLACEMENT[position], className),
196
+ children: /* @__PURE__ */ jsx(ToastList, { position })
197
+ }
198
+ ) }) });
199
+ }
200
+ export {
201
+ Toaster,
202
+ toast
203
+ };
@@ -0,0 +1,55 @@
1
+ import * as react from 'react';
2
+ import { ComponentPropsWithoutRef } from 'react';
3
+ import * as class_variance_authority_types from 'class-variance-authority/types';
4
+ import { Toggle as Toggle$1 } from '@base-ui/react/toggle';
5
+ import { ToggleGroup as ToggleGroup$1 } from '@base-ui/react/toggle-group';
6
+
7
+ type Variant = "default" | "outline";
8
+ type TextSize = "sm" | "md" | "lg";
9
+ type Named = {
10
+ "aria-label": string;
11
+ } | {
12
+ "aria-labelledby": string;
13
+ };
14
+ declare const toggleVariants: (props?: ({
15
+ variant?: "default" | "outline" | null | undefined;
16
+ size?: "sm" | "md" | "lg" | "icon" | null | undefined;
17
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
18
+ interface ToggleOwnProps {
19
+ /** `outline` draws a border. Default `default`. */
20
+ variant?: Variant;
21
+ className?: string;
22
+ }
23
+ /** An icon-only toggle has no text, so it must be given an accessible name. */
24
+ type IconOnly = {
25
+ size: "icon";
26
+ } & Named;
27
+ type ToggleBase = Omit<ComponentPropsWithoutRef<typeof Toggle$1>, "className"> & ToggleOwnProps;
28
+ type ToggleProps = ToggleBase & ({
29
+ size?: TextSize;
30
+ } | IconOnly);
31
+ /** A button that stays pressed. It reports its state as `aria-pressed`. */
32
+ declare const Toggle: react.ForwardRefExoticComponent<ToggleProps & react.RefAttributes<HTMLButtonElement>>;
33
+ type ToggleGroupProps = Omit<ComponentPropsWithoutRef<typeof ToggleGroup$1>, "className" | "aria-label" | "aria-labelledby"> & Named & {
34
+ /** Applied to every item unless it sets its own. */
35
+ variant?: Variant;
36
+ size?: TextSize | "icon";
37
+ /** Join the items into one control (default), or keep them apart with a gap. */
38
+ attached?: boolean;
39
+ className?: string;
40
+ };
41
+ /**
42
+ * A group of toggles that behaves as one control. By default one item can be pressed at a time
43
+ * (pressing it again releases it); with `multiple`, any number can. The value is an array of the
44
+ * pressed items' `value`s either way.
45
+ *
46
+ * Keyboard: Tab enters the group at one stop; the arrow keys (Left and Right, or Up and Down when
47
+ * vertical) move focus between items and wrap; Home and End jump; Space or Enter presses the
48
+ * focused item. Moving focus never changes the selection.
49
+ */
50
+ declare const ToggleGroup: react.ForwardRefExoticComponent<ToggleGroupProps & react.RefAttributes<HTMLDivElement>>;
51
+ type ToggleGroupItemProps = ToggleProps;
52
+ /** One toggle in a `ToggleGroup`. Give it a `value`. */
53
+ declare const ToggleGroupItem: react.ForwardRefExoticComponent<ToggleProps & react.RefAttributes<HTMLButtonElement>>;
54
+
55
+ export { Toggle, ToggleGroup, ToggleGroupItem, type ToggleGroupItemProps, type ToggleGroupProps, type ToggleProps, toggleVariants };
package/dist/toggle.js ADDED
@@ -0,0 +1,88 @@
1
+ "use client";
2
+ import {
3
+ joinedClasses
4
+ } from "./chunk-EE4V6XMB.js";
5
+ import {
6
+ cn
7
+ } from "./chunk-2TQSHVXA.js";
8
+
9
+ // src/components/toggle/toggle.tsx
10
+ import { cva } from "class-variance-authority";
11
+ import { createContext, forwardRef, useContext } from "react";
12
+
13
+ // src/primitives/toggle.ts
14
+ import { Toggle } from "@base-ui/react/toggle";
15
+
16
+ // src/primitives/toggle-group.ts
17
+ import { ToggleGroup } from "@base-ui/react/toggle-group";
18
+
19
+ // src/components/toggle/toggle.tsx
20
+ import { jsx } from "react/jsx-runtime";
21
+ var toggleVariants = cva(
22
+ [
23
+ "inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium",
24
+ "transition-colors duration-(--motion-duration-fast) ease-standard",
25
+ "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
26
+ "hover:bg-muted hover:text-muted-foreground",
27
+ "data-[pressed]:bg-accent data-[pressed]:text-accent-foreground",
28
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
29
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0"
30
+ ],
31
+ {
32
+ variants: {
33
+ variant: {
34
+ default: "bg-transparent",
35
+ outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground"
36
+ },
37
+ size: {
38
+ sm: "h-8 px-2.5 text-xs",
39
+ md: "h-(--density-control-height) px-3",
40
+ lg: "h-11 px-5",
41
+ icon: "size-(--density-control-height) p-0"
42
+ }
43
+ },
44
+ defaultVariants: { variant: "default", size: "md" }
45
+ }
46
+ );
47
+ var Toggle2 = forwardRef(function Toggle3({ variant, size, className, ...props }, ref) {
48
+ return /* @__PURE__ */ jsx(
49
+ Toggle,
50
+ {
51
+ ref,
52
+ className: cn(toggleVariants({ variant, size }), className),
53
+ ...props
54
+ }
55
+ );
56
+ });
57
+ var GroupContext = createContext({});
58
+ var ToggleGroup2 = forwardRef(function ToggleGroup3({ variant, size, attached = true, orientation = "horizontal", className, ...props }, ref) {
59
+ return /* @__PURE__ */ jsx(GroupContext.Provider, { value: { variant, size }, children: /* @__PURE__ */ jsx(
60
+ ToggleGroup,
61
+ {
62
+ ref,
63
+ orientation,
64
+ className: cn(joinedClasses(orientation, attached), className),
65
+ ...props
66
+ }
67
+ ) });
68
+ });
69
+ var ToggleGroupItem = forwardRef(
70
+ function ToggleGroupItem2({ variant, size, ...props }, ref) {
71
+ const group = useContext(GroupContext);
72
+ return /* @__PURE__ */ jsx(
73
+ Toggle2,
74
+ {
75
+ ref,
76
+ variant: variant ?? group.variant,
77
+ ...{ size: size ?? group.size },
78
+ ...props
79
+ }
80
+ );
81
+ }
82
+ );
83
+ export {
84
+ Toggle2 as Toggle,
85
+ ToggleGroup2 as ToggleGroup,
86
+ ToggleGroupItem,
87
+ toggleVariants
88
+ };
@@ -0,0 +1,337 @@
1
+ /*
2
+ * @azmr/ui design tokens and Tailwind v4 integration (no global rules).
3
+ *
4
+ * Usage in an app's global CSS, when the app already has its own base styles:
5
+ * @import "tailwindcss";
6
+ * @import "@azmr/ui/tokens.css";
7
+ * or, to also get our body colours and default border colour:
8
+ * @import "@azmr/ui/styles.css";
9
+ *
10
+ * Token names follow shadcn/ui so components from any shadcn registry fit
11
+ * alongside ours. Colours are OKLCH and use light-dark(), so the page follows
12
+ * the system by default and can be forced with `.dark` / `.light` or
13
+ * `data-theme="dark|light"` on any ancestor, with no JavaScript and no flash.
14
+ *
15
+ * Components must read these tokens (bg-background, text-foreground, ...) and
16
+ * never contain raw colours; a test enforces it.
17
+ */
18
+
19
+ /* The motion presets (keyframes and .motion-* classes). Only our own classes, so it is safe in an app with
20
+ its own global styles. */
21
+ @import "./motion.css";
22
+
23
+ /* Scan the distributed JS for Tailwind class names. Resolved relative to this
24
+ file, so it works from node_modules. */
25
+ @source "./**/*.js";
26
+
27
+ :root {
28
+ color-scheme: light dark;
29
+
30
+ --radius: 0.625rem;
31
+
32
+ /* Surfaces */
33
+ --background: light-dark(oklch(1 0 0), oklch(0.145 0 0));
34
+ --foreground: light-dark(oklch(0.145 0 0), oklch(0.985 0 0));
35
+ --card: light-dark(oklch(1 0 0), oklch(0.205 0 0));
36
+ --card-foreground: light-dark(oklch(0.145 0 0), oklch(0.985 0 0));
37
+ --popover: light-dark(oklch(1 0 0), oklch(0.205 0 0));
38
+ --popover-foreground: light-dark(oklch(0.145 0 0), oklch(0.985 0 0));
39
+ /* The dimming layer behind modal surfaces (dialog, drawer). Translucent black in both modes. */
40
+ --scrim: light-dark(oklch(0 0 0 / 0.5), oklch(0 0 0 / 0.65));
41
+
42
+ /* Brand and neutrals */
43
+ --primary: light-dark(oklch(0.205 0 0), oklch(0.922 0 0));
44
+ --primary-foreground: light-dark(oklch(0.985 0 0), oklch(0.205 0 0));
45
+ --secondary: light-dark(oklch(0.97 0 0), oklch(0.269 0 0));
46
+ --secondary-foreground: light-dark(oklch(0.205 0 0), oklch(0.985 0 0));
47
+ --muted: light-dark(oklch(0.97 0 0), oklch(0.269 0 0));
48
+ --muted-foreground: light-dark(oklch(0.5 0 0), oklch(0.72 0 0));
49
+ --accent: light-dark(oklch(0.97 0 0), oklch(0.269 0 0));
50
+ --accent-foreground: light-dark(oklch(0.205 0 0), oklch(0.985 0 0));
51
+
52
+ /* Status (destructive is shadcn's; success / warning / info are ours) */
53
+ --destructive: light-dark(oklch(0.53 0.22 27), oklch(0.65 0.2 22));
54
+ --destructive-foreground: light-dark(oklch(0.985 0 0), oklch(0.145 0 0));
55
+ --success: light-dark(oklch(0.46 0.13 150), oklch(0.72 0.17 150));
56
+ --success-foreground: light-dark(oklch(0.985 0 0), oklch(0.145 0 0));
57
+ --warning: light-dark(oklch(0.78 0.16 80), oklch(0.82 0.16 82));
58
+ --warning-foreground: light-dark(oklch(0.25 0.04 70), oklch(0.25 0.04 70));
59
+ --warning-text: light-dark(oklch(0.5 0.1 75), oklch(0.82 0.16 82));
60
+ --info: light-dark(oklch(0.5 0.18 255), oklch(0.72 0.13 245));
61
+ --info-foreground: light-dark(oklch(0.985 0 0), oklch(0.145 0 0));
62
+
63
+ /* Lines and focus. --ring keeps >= 3:1 against every surface (WCAG 2.2). */
64
+ --border: light-dark(oklch(0.922 0 0), oklch(1 0 0 / 12%));
65
+ --input: light-dark(oklch(0.922 0 0), oklch(1 0 0 / 16%));
66
+ --ring: light-dark(oklch(0.55 0.15 255), oklch(0.72 0.12 255));
67
+
68
+ /* Sidebar */
69
+ --sidebar: light-dark(oklch(0.985 0 0), oklch(0.205 0 0));
70
+ --sidebar-foreground: light-dark(oklch(0.145 0 0), oklch(0.985 0 0));
71
+ --sidebar-primary: light-dark(oklch(0.205 0 0), oklch(0.922 0 0));
72
+ --sidebar-primary-foreground: light-dark(oklch(0.985 0 0), oklch(0.205 0 0));
73
+ --sidebar-accent: light-dark(oklch(0.97 0 0), oklch(0.269 0 0));
74
+ --sidebar-accent-foreground: light-dark(oklch(0.205 0 0), oklch(0.985 0 0));
75
+ --sidebar-border: light-dark(oklch(0.922 0 0), oklch(1 0 0 / 12%));
76
+ --sidebar-ring: light-dark(oklch(0.55 0.15 255), oklch(0.72 0.12 255));
77
+
78
+ /* Categorical chart palette (8 series), tuned for contrast and colour-vision
79
+ deficiency. Found with a constrained search (>= 3.2:1 on surfaces, inside sRGB,
80
+ maximising the worst pairwise CIEDE2000 under normal, protan, deutan and
81
+ tritan vision); tokens.test.ts holds the thresholds. */
82
+ --chart-1: light-dark(oklch(0.52 0.19 266), oklch(0.69 0.165 273));
83
+ --chart-2: light-dark(oklch(0.64 0.105 45), oklch(0.8 0.17 73));
84
+ --chart-3: light-dark(oklch(0.58 0.062 173), oklch(0.64 0.152 158));
85
+ --chart-4: light-dark(oklch(0.5 0.189 339), oklch(0.63 0.16 336));
86
+ --chart-5: light-dark(oklch(0.64 0.124 231), oklch(0.86 0.12 204));
87
+ --chart-6: light-dark(oklch(0.64 0.138 107), oklch(0.86 0.104 114));
88
+ --chart-7: light-dark(oklch(0.5 0.19 22), oklch(0.65 0.165 35));
89
+ --chart-8: light-dark(oklch(0.46 0 0), oklch(0.7 0 0));
90
+
91
+ /* Motion */
92
+ --motion-duration-instant: 75ms;
93
+ --motion-duration-fast: 150ms;
94
+ --motion-duration-base: 200ms;
95
+ --motion-duration-slow: 300ms;
96
+ --motion-ease-standard: cubic-bezier(0.2, 0, 0, 1);
97
+ --motion-ease-emphasized: cubic-bezier(0.05, 0.7, 0.1, 1);
98
+ --motion-ease-decelerate: cubic-bezier(0, 0, 0, 1);
99
+ --motion-ease-accelerate: cubic-bezier(0.3, 0, 1, 1);
100
+ /* Delay between items of a staggered list, and the loop length of ambient effects (shimmer). */
101
+ --motion-stagger-step: 40ms;
102
+ --motion-duration-ambient: 1500ms;
103
+
104
+ /* Typography. createTheme({ font }) overrides --font-sans-stack. */
105
+ --font-sans-stack:
106
+ ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
107
+
108
+ /* Elevation */
109
+ --elevation-1: 0 1px 2px 0 oklch(0 0 0 / 0.08);
110
+ --elevation-2: 0 2px 6px -1px oklch(0 0 0 / 0.12), 0 1px 3px 0 oklch(0 0 0 / 0.08);
111
+ --elevation-3: 0 8px 16px -4px oklch(0 0 0 / 0.16), 0 2px 6px -2px oklch(0 0 0 / 0.1);
112
+ --elevation-4: 0 20px 32px -8px oklch(0 0 0 / 0.24), 0 8px 12px -4px oklch(0 0 0 / 0.12);
113
+
114
+ /* Layering */
115
+ --z-dropdown: 1000;
116
+ --z-sticky: 1100;
117
+ --z-overlay: 1300;
118
+ --z-modal: 1400;
119
+ --z-popover: 1500;
120
+ --z-toast: 1700;
121
+ --z-tooltip: 1800;
122
+
123
+ /* Density (comfortable by default) */
124
+ --density-control-height: 2.25rem;
125
+ --density-control-padding-x: 0.75rem;
126
+ --density-gap: 0.5rem;
127
+ --density-row-height: 2.75rem;
128
+ }
129
+
130
+ /* Explicit per-scope colour tokens. :root uses light-dark() for the page-level theme (system by
131
+ default). A bundler that lowers light-dark() resolves each token where it is declared and
132
+ inherits the result, so a nested scope (a docs demo, a dark panel in a light page) could not
133
+ change it. Re-declaring the tokens inside each scope works however the CSS is processed.
134
+ tokens.test.ts keeps these copies identical to the :root values. */
135
+ .light,
136
+ [data-theme="light"] {
137
+ color-scheme: light;
138
+
139
+ --background: oklch(1 0 0);
140
+ --foreground: oklch(0.145 0 0);
141
+ --card: oklch(1 0 0);
142
+ --card-foreground: oklch(0.145 0 0);
143
+ --popover: oklch(1 0 0);
144
+ --popover-foreground: oklch(0.145 0 0);
145
+ --scrim: oklch(0 0 0 / 0.5);
146
+ --primary: oklch(0.205 0 0);
147
+ --primary-foreground: oklch(0.985 0 0);
148
+ --secondary: oklch(0.97 0 0);
149
+ --secondary-foreground: oklch(0.205 0 0);
150
+ --muted: oklch(0.97 0 0);
151
+ --muted-foreground: oklch(0.5 0 0);
152
+ --accent: oklch(0.97 0 0);
153
+ --accent-foreground: oklch(0.205 0 0);
154
+ --destructive: oklch(0.53 0.22 27);
155
+ --destructive-foreground: oklch(0.985 0 0);
156
+ --success: oklch(0.46 0.13 150);
157
+ --success-foreground: oklch(0.985 0 0);
158
+ --warning: oklch(0.78 0.16 80);
159
+ --warning-foreground: oklch(0.25 0.04 70);
160
+ --warning-text: oklch(0.5 0.1 75);
161
+ --info: oklch(0.5 0.18 255);
162
+ --info-foreground: oklch(0.985 0 0);
163
+ --border: oklch(0.922 0 0);
164
+ --input: oklch(0.922 0 0);
165
+ --ring: oklch(0.55 0.15 255);
166
+ --sidebar: oklch(0.985 0 0);
167
+ --sidebar-foreground: oklch(0.145 0 0);
168
+ --sidebar-primary: oklch(0.205 0 0);
169
+ --sidebar-primary-foreground: oklch(0.985 0 0);
170
+ --sidebar-accent: oklch(0.97 0 0);
171
+ --sidebar-accent-foreground: oklch(0.205 0 0);
172
+ --sidebar-border: oklch(0.922 0 0);
173
+ --sidebar-ring: oklch(0.55 0.15 255);
174
+ --chart-1: oklch(0.52 0.19 266);
175
+ --chart-2: oklch(0.64 0.105 45);
176
+ --chart-3: oklch(0.58 0.062 173);
177
+ --chart-4: oklch(0.5 0.189 339);
178
+ --chart-5: oklch(0.64 0.124 231);
179
+ --chart-6: oklch(0.64 0.138 107);
180
+ --chart-7: oklch(0.5 0.19 22);
181
+ --chart-8: oklch(0.46 0 0);
182
+ }
183
+
184
+ .dark,
185
+ [data-theme="dark"] {
186
+ color-scheme: dark;
187
+
188
+ --background: oklch(0.145 0 0);
189
+ --foreground: oklch(0.985 0 0);
190
+ --card: oklch(0.205 0 0);
191
+ --card-foreground: oklch(0.985 0 0);
192
+ --popover: oklch(0.205 0 0);
193
+ --popover-foreground: oklch(0.985 0 0);
194
+ --scrim: oklch(0 0 0 / 0.65);
195
+ --primary: oklch(0.922 0 0);
196
+ --primary-foreground: oklch(0.205 0 0);
197
+ --secondary: oklch(0.269 0 0);
198
+ --secondary-foreground: oklch(0.985 0 0);
199
+ --muted: oklch(0.269 0 0);
200
+ --muted-foreground: oklch(0.72 0 0);
201
+ --accent: oklch(0.269 0 0);
202
+ --accent-foreground: oklch(0.985 0 0);
203
+ --destructive: oklch(0.65 0.2 22);
204
+ --destructive-foreground: oklch(0.145 0 0);
205
+ --success: oklch(0.72 0.17 150);
206
+ --success-foreground: oklch(0.145 0 0);
207
+ --warning: oklch(0.82 0.16 82);
208
+ --warning-foreground: oklch(0.25 0.04 70);
209
+ --warning-text: oklch(0.82 0.16 82);
210
+ --info: oklch(0.72 0.13 245);
211
+ --info-foreground: oklch(0.145 0 0);
212
+ --border: oklch(1 0 0 / 12%);
213
+ --input: oklch(1 0 0 / 16%);
214
+ --ring: oklch(0.72 0.12 255);
215
+ --sidebar: oklch(0.205 0 0);
216
+ --sidebar-foreground: oklch(0.985 0 0);
217
+ --sidebar-primary: oklch(0.922 0 0);
218
+ --sidebar-primary-foreground: oklch(0.205 0 0);
219
+ --sidebar-accent: oklch(0.269 0 0);
220
+ --sidebar-accent-foreground: oklch(0.985 0 0);
221
+ --sidebar-border: oklch(1 0 0 / 12%);
222
+ --sidebar-ring: oklch(0.72 0.12 255);
223
+ --chart-1: oklch(0.69 0.165 273);
224
+ --chart-2: oklch(0.8 0.17 73);
225
+ --chart-3: oklch(0.64 0.152 158);
226
+ --chart-4: oklch(0.63 0.16 336);
227
+ --chart-5: oklch(0.86 0.12 204);
228
+ --chart-6: oklch(0.86 0.104 114);
229
+ --chart-7: oklch(0.65 0.165 35);
230
+ --chart-8: oklch(0.7 0 0);
231
+ }
232
+
233
+ [data-density="compact"] {
234
+ --density-control-height: 2rem;
235
+ --density-control-padding-x: 0.625rem;
236
+ --density-gap: 0.375rem;
237
+ --density-row-height: 2.25rem;
238
+ }
239
+
240
+ [data-density="spacious"] {
241
+ --density-control-height: 2.75rem;
242
+ --density-control-padding-x: 1rem;
243
+ --density-gap: 0.75rem;
244
+ --density-row-height: 3.25rem;
245
+ }
246
+
247
+ /* Reduced motion: token-level collapse, plus a safety net for third-party and
248
+ hand-written animations. Mark genuinely essential motion (e.g. a progress
249
+ spinner) with data-motion="essential" to keep it. */
250
+ @media (prefers-reduced-motion: reduce) {
251
+ :root {
252
+ --motion-duration-instant: 0.01ms;
253
+ --motion-duration-fast: 0.01ms;
254
+ --motion-duration-base: 0.01ms;
255
+ --motion-duration-slow: 0.01ms;
256
+ --motion-stagger-step: 0ms;
257
+ --motion-duration-ambient: 0.01ms;
258
+ }
259
+
260
+ *:not([data-motion="essential"]),
261
+ *:not([data-motion="essential"])::before,
262
+ *:not([data-motion="essential"])::after {
263
+ /* biome-ignore lint/complexity/noImportantStyles: the reduced-motion safety net must beat arbitrary author animations and transitions */
264
+ animation-duration: 0.01ms !important;
265
+ /* biome-ignore lint/complexity/noImportantStyles: the reduced-motion safety net must beat arbitrary author animations and transitions */
266
+ animation-iteration-count: 1 !important;
267
+ /* biome-ignore lint/complexity/noImportantStyles: the reduced-motion safety net must beat arbitrary author animations and transitions */
268
+ transition-duration: 0.01ms !important;
269
+ /* biome-ignore lint/complexity/noImportantStyles: the reduced-motion safety net must beat arbitrary author animations and transitions */
270
+ scroll-behavior: auto !important;
271
+ }
272
+ }
273
+
274
+ /* Map the tokens into Tailwind's theme so utilities like bg-background,
275
+ text-muted-foreground, rounded-lg and shadow-elevation-2 exist. */
276
+ @theme inline {
277
+ --color-background: var(--background);
278
+ --color-foreground: var(--foreground);
279
+ --color-card: var(--card);
280
+ --color-card-foreground: var(--card-foreground);
281
+ --color-popover: var(--popover);
282
+ --color-popover-foreground: var(--popover-foreground);
283
+ --color-scrim: var(--scrim);
284
+ --color-primary: var(--primary);
285
+ --color-primary-foreground: var(--primary-foreground);
286
+ --color-secondary: var(--secondary);
287
+ --color-secondary-foreground: var(--secondary-foreground);
288
+ --color-muted: var(--muted);
289
+ --color-muted-foreground: var(--muted-foreground);
290
+ --color-accent: var(--accent);
291
+ --color-accent-foreground: var(--accent-foreground);
292
+ --color-destructive: var(--destructive);
293
+ --color-destructive-foreground: var(--destructive-foreground);
294
+ --color-success: var(--success);
295
+ --color-success-foreground: var(--success-foreground);
296
+ --color-warning: var(--warning);
297
+ --color-warning-foreground: var(--warning-foreground);
298
+ --color-warning-text: var(--warning-text);
299
+ --color-info: var(--info);
300
+ --color-info-foreground: var(--info-foreground);
301
+ --color-border: var(--border);
302
+ --color-input: var(--input);
303
+ --color-ring: var(--ring);
304
+ --color-sidebar: var(--sidebar);
305
+ --color-sidebar-foreground: var(--sidebar-foreground);
306
+ --color-sidebar-primary: var(--sidebar-primary);
307
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
308
+ --color-sidebar-accent: var(--sidebar-accent);
309
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
310
+ --color-sidebar-border: var(--sidebar-border);
311
+ --color-sidebar-ring: var(--sidebar-ring);
312
+ --color-chart-1: var(--chart-1);
313
+ --color-chart-2: var(--chart-2);
314
+ --color-chart-3: var(--chart-3);
315
+ --color-chart-4: var(--chart-4);
316
+ --color-chart-5: var(--chart-5);
317
+ --color-chart-6: var(--chart-6);
318
+ --color-chart-7: var(--chart-7);
319
+ --color-chart-8: var(--chart-8);
320
+
321
+ --radius-sm: calc(var(--radius) - 4px);
322
+ --radius-md: calc(var(--radius) - 2px);
323
+ --radius-lg: var(--radius);
324
+ --radius-xl: calc(var(--radius) + 4px);
325
+
326
+ --font-sans: var(--font-sans-stack);
327
+
328
+ --shadow-elevation-1: var(--elevation-1);
329
+ --shadow-elevation-2: var(--elevation-2);
330
+ --shadow-elevation-3: var(--elevation-3);
331
+ --shadow-elevation-4: var(--elevation-4);
332
+
333
+ --ease-standard: var(--motion-ease-standard);
334
+ --ease-emphasized: var(--motion-ease-emphasized);
335
+ --ease-decelerate: var(--motion-ease-decelerate);
336
+ --ease-accelerate: var(--motion-ease-accelerate);
337
+ }