@devalok/shilp-sutra 0.53.0 → 0.54.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 (90) hide show
  1. package/AGENTS.md +7 -1
  2. package/BREAKING.json +29 -0
  3. package/MIGRATION.md +55 -0
  4. package/dist/composed/avatar-group.d.ts +11 -9
  5. package/dist/composed/avatar-group.d.ts.map +1 -1
  6. package/dist/composed/avatar-group.js +108 -140
  7. package/dist/composed/avatar-group.js.map +1 -1
  8. package/dist/composed/bulk-action-bar.d.ts +19 -11
  9. package/dist/composed/bulk-action-bar.d.ts.map +1 -1
  10. package/dist/composed/bulk-action-bar.js +81 -110
  11. package/dist/composed/bulk-action-bar.js.map +1 -1
  12. package/dist/composed/error-boundary.d.ts +15 -3
  13. package/dist/composed/error-boundary.d.ts.map +1 -1
  14. package/dist/composed/error-boundary.js +43 -26
  15. package/dist/composed/error-boundary.js.map +1 -1
  16. package/dist/composed/loading-skeleton.d.ts +2 -0
  17. package/dist/composed/loading-skeleton.d.ts.map +1 -1
  18. package/dist/composed/loading-skeleton.js +79 -56
  19. package/dist/composed/loading-skeleton.js.map +1 -1
  20. package/dist/composed/master-detail.d.ts +21 -8
  21. package/dist/composed/master-detail.d.ts.map +1 -1
  22. package/dist/composed/master-detail.js +91 -77
  23. package/dist/composed/master-detail.js.map +1 -1
  24. package/dist/composed/page-skeletons.d.ts +6 -0
  25. package/dist/composed/page-skeletons.d.ts.map +1 -1
  26. package/dist/composed/page-skeletons.js +93 -71
  27. package/dist/composed/page-skeletons.js.map +1 -1
  28. package/dist/shell/index.d.ts +5 -3
  29. package/dist/shell/index.d.ts.map +1 -1
  30. package/dist/shell/index.js +2 -3
  31. package/dist/shell/notification-preferences.d.ts.map +1 -1
  32. package/dist/shell/notification-preferences.js +2 -0
  33. package/dist/shell/notification-preferences.js.map +1 -1
  34. package/dist/ui/combobox.d.ts +1 -1
  35. package/dist/ui/combobox.d.ts.map +1 -1
  36. package/dist/ui/combobox.js +133 -128
  37. package/dist/ui/combobox.js.map +1 -1
  38. package/dist/ui/file-upload.d.ts.map +1 -1
  39. package/dist/ui/file-upload.js +124 -138
  40. package/dist/ui/file-upload.js.map +1 -1
  41. package/dist/ui/form.d.ts +7 -0
  42. package/dist/ui/form.d.ts.map +1 -1
  43. package/dist/ui/form.js +4 -2
  44. package/dist/ui/form.js.map +1 -1
  45. package/dist/ui/icon-button.d.ts +2 -1
  46. package/dist/ui/icon-button.d.ts.map +1 -1
  47. package/dist/ui/icon-button.js +1 -1
  48. package/dist/ui/icon-button.js.map +1 -1
  49. package/dist/ui/label.d.ts.map +1 -1
  50. package/dist/ui/label.js +7 -6
  51. package/dist/ui/label.js.map +1 -1
  52. package/dist/ui/spinner.d.ts.map +1 -1
  53. package/dist/ui/spinner.js +50 -44
  54. package/dist/ui/spinner.js.map +1 -1
  55. package/dist/ui/switch.d.ts.map +1 -1
  56. package/dist/ui/switch.js +24 -24
  57. package/dist/ui/switch.js.map +1 -1
  58. package/dist/ui/table.js +1 -1
  59. package/dist/ui/table.js.map +1 -1
  60. package/docs/components/composed/bulk-action-bar.md +9 -3
  61. package/docs/components/composed/error-boundary.md +18 -2
  62. package/docs/components/composed/master-detail.md +12 -2
  63. package/docs/components/composed/page-skeletons.md +2 -2
  64. package/docs/components/composed/rich-chat-input.md +18 -6
  65. package/docs/components/composed/simple-tooltip.md +1 -1
  66. package/docs/components/shell/app-command-palette.md +2 -1
  67. package/docs/components/shell/bottom-navbar.md +4 -4
  68. package/docs/components/shell/command-registry.md +2 -2
  69. package/docs/components/shell/link-context.md +2 -2
  70. package/docs/components/shell/top-bar.md +1 -1
  71. package/docs/components/ui/search-input.md +2 -2
  72. package/docs/components/ui/slider.md +2 -2
  73. package/docs/components/ui/split-button.md +3 -3
  74. package/llms.txt +2 -2
  75. package/make-kit/Guidelines.md +1 -1
  76. package/make-kit/components/overview.md +2 -2
  77. package/make-kit/foundations/surfaces.md +1 -1
  78. package/mcp-manifest.json +403 -147
  79. package/package.json +1 -6
  80. package/skill/SKILL.md +1 -1
  81. package/skill/chat-skill/README.md +18 -0
  82. package/skill/chat-skill/SKILL.md +78 -0
  83. package/skill/chat-skill/chatgpt-instructions.md +26 -0
  84. package/skill/chat-skill/gemini-instructions.md +25 -0
  85. package/skill/references/components.md +2 -2
  86. package/dist/shell/sidebar.d.ts +0 -113
  87. package/dist/shell/sidebar.d.ts.map +0 -1
  88. package/dist/shell/sidebar.js +0 -293
  89. package/dist/shell/sidebar.js.map +0 -1
  90. package/docs/components/shell/sidebar.md +0 -103
@@ -1 +1 @@
1
- {"version":3,"file":"label.js","names":[],"sources":["../../src/ui/label.tsx"],"sourcesContent":["'use client'\n\nimport * as LabelPrimitive from '@primitives/react-label'\nimport * as React from 'react'\n\nimport { useFormField } from './form'\nimport { cn } from './lib/utils'\n\nexport interface LabelProps\n extends React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> {\n required?: boolean\n}\n\nconst Label = React.forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n LabelProps\n>(({ className, required, children, htmlFor, ...props }, ref) => {\n const fieldCtx = useFormField()\n // Explicit htmlFor wins; otherwise fall back to FormField's inputId.\n const resolvedHtmlFor = htmlFor ?? fieldCtx.inputId\n const resolvedRequired = required ?? fieldCtx.required\n return (\n <LabelPrimitive.Root\n ref={ref}\n htmlFor={resolvedHtmlFor}\n className={cn(\n 'font-sans text-body-md font-medium text-surface-fg leading-none transition-opacity duration-fast-01 ease-productive-standard peer-disabled:opacity-action-disabled',\n className,\n )}\n {...props}\n >\n {children}\n {resolvedRequired && (\n <span className=\"text-error-11 ml-ds-01\" aria-hidden=\"true\">*</span>\n )}\n </LabelPrimitive.Root>\n )\n})\nLabel.displayName = LabelPrimitive.Root.displayName\n\nexport { Label }\n"],"mappings":";;;;;;;AAaA,IAAM,IAAQ,EAAM,YAGjB,EAAE,cAAW,aAAU,aAAU,YAAS,GAAG,KAAS,MAAQ;CAC/D,IAAM,IAAW,EAAa,GAExB,IAAkB,KAAW,EAAS,SACtC,IAAmB,KAAY,EAAS;CAC9C,OACE,kBAAC,GAAD;EACO;EACL,SAAS;EACT,WAAW,EACT,sKACA,CACF;EACA,GAAI;YAPN,CASG,GACA,KACC,kBAAC,QAAD;GAAM,WAAU;GAAyB,eAAY;aAAO;EAAO,CAAA,CAElD;;AAEzB,CAAC;AACD,EAAM,cAAA,EAAkC"}
1
+ {"version":3,"file":"label.js","names":[],"sources":["../../src/ui/label.tsx"],"sourcesContent":["'use client'\n\nimport * as LabelPrimitive from '@primitives/react-label'\nimport * as React from 'react'\n\nimport { useFormField } from './form'\nimport { cn } from './lib/utils'\n\nexport interface LabelProps\n extends React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> {\n required?: boolean\n}\n\nconst Label = React.forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n LabelProps\n>(({ className, required, children, htmlFor, id, ...props }, ref) => {\n const fieldCtx = useFormField()\n // Explicit htmlFor wins; otherwise fall back to FormField's inputId.\n const resolvedHtmlFor = htmlFor ?? fieldCtx.inputId\n // Adopt the field's labelId so non-labellable controls (Combobox div) can point\n // aria-labelledby at this label. Explicit id wins.\n const resolvedId = id ?? fieldCtx.labelId\n const resolvedRequired = required ?? fieldCtx.required\n return (\n <LabelPrimitive.Root\n ref={ref}\n id={resolvedId}\n htmlFor={resolvedHtmlFor}\n className={cn(\n 'font-sans text-body-md font-medium text-surface-fg leading-none transition-opacity duration-fast-01 ease-productive-standard peer-disabled:opacity-action-disabled',\n className,\n )}\n {...props}\n >\n {children}\n {resolvedRequired && (\n <span className=\"text-error-11 ml-ds-01\" aria-hidden=\"true\">*</span>\n )}\n </LabelPrimitive.Root>\n )\n})\nLabel.displayName = LabelPrimitive.Root.displayName\n\nexport { Label }\n"],"mappings":";;;;;;;AAaA,IAAM,IAAQ,EAAM,YAGjB,EAAE,cAAW,aAAU,aAAU,YAAS,OAAI,GAAG,KAAS,MAAQ;CACnE,IAAM,IAAW,EAAa,GAExB,IAAkB,KAAW,EAAS,SAGtC,IAAa,KAAM,EAAS,SAC5B,IAAmB,KAAY,EAAS;CAC9C,OACE,kBAAC,GAAD;EACO;EACL,IAAI;EACJ,SAAS;EACT,WAAW,EACT,sKACA,CACF;EACA,GAAI;YARN,CAUG,GACA,KACC,kBAAC,QAAD;GAAM,WAAU;GAAyB,eAAY;aAAO;EAAO,CAAA,CAElD;;AAEzB,CAAC;AACD,EAAM,cAAA,EAAkC"}
@@ -1 +1 @@
1
- {"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../src/ui/spinner.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAgB9B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,yCAAyC;IACzC,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,OAAO,CAAA;IACxC,iEAAiE;IACjE,OAAO,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;IAC3B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAyBD,QAAA,MAAM,OAAO,sFAqOZ,CAAA;AAGD,OAAO,EAAE,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../src/ui/spinner.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAgB9B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,yCAAyC;IACzC,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,OAAO,CAAA;IACxC,iEAAiE;IACjE,OAAO,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;IAC3B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAyBD,QAAA,MAAM,OAAO,sFA+OZ,CAAA;AAGD,OAAO,EAAE,OAAO,EAAE,CAAA"}
@@ -26,15 +26,21 @@ var s = {
26
26
  error: "var(--color-error-9)"
27
27
  }, m = "M7 12.5l3 3 7-7", h = "M8 8l8 8M16 8l-8 8", g = "M5 12.5l4 4 10-10", _ = "M6 6l12 12M18 6l-12 12", v = t.forwardRef(({ size: v = "md", state: y = "spinning", variant: b = "filled", delay: x = 0, onComplete: S, className: C }, w) => {
28
28
  let T = o(), [E, D] = t.useState(x === 0);
29
- if (t.useEffect(() => {
29
+ t.useEffect(() => {
30
30
  if (x <= 0) {
31
31
  D(!0);
32
32
  return;
33
33
  }
34
34
  let e = setTimeout(() => D(!0), x);
35
35
  return () => clearTimeout(e);
36
- }, [x]), !E) return null;
37
- let O = c[v], k = l[v], A = y === "spinning", j = y === "success" || y === "error", M = b === "filled", N = b === "bare" && y === "spinning" ? "currentColor" : p[y], P = M ? "white" : "currentColor", F = M ? m : g, I = M ? h : _, L = .4, R = M ? .5 : .35, z = .35;
36
+ }, [x]);
37
+ let O = t.useRef(S);
38
+ if (t.useEffect(() => {
39
+ O.current = S;
40
+ }), t.useEffect(() => {
41
+ T && (y === "success" || y === "error") && O.current?.();
42
+ }, [T, y]), !E) return null;
43
+ let k = c[v], A = l[v], j = y === "spinning", M = y === "success" || y === "error", N = b === "filled", P = b === "bare" && y === "spinning" ? "currentColor" : p[y], F = N ? "white" : "currentColor", I = N ? m : g, L = N ? h : _, R = .4, z = N ? .5 : .35, B = .35;
38
44
  return /* @__PURE__ */ r("span", {
39
45
  ref: w,
40
46
  role: "status",
@@ -45,50 +51,50 @@ var s = {
45
51
  fill: "none",
46
52
  xmlns: "http://www.w3.org/2000/svg",
47
53
  children: [
48
- T ? !(j && !M) && /* @__PURE__ */ n("circle", {
54
+ T ? !(M && !N) && /* @__PURE__ */ n("circle", {
49
55
  cx: "12",
50
56
  cy: "12",
51
57
  r: u,
52
58
  stroke: "var(--color-surface-border)",
53
- strokeWidth: O,
59
+ strokeWidth: k,
54
60
  fill: "none"
55
61
  }) : /* @__PURE__ */ n(a.circle, {
56
62
  cx: "12",
57
63
  cy: "12",
58
64
  r: u,
59
65
  stroke: "var(--color-surface-border)",
60
- strokeWidth: O,
66
+ strokeWidth: k,
61
67
  fill: "none",
62
- animate: { opacity: j && !M ? 0 : 1 },
68
+ animate: { opacity: M && !N ? 0 : 1 },
63
69
  transition: {
64
70
  duration: .2,
65
- delay: j && !M ? L : 0
71
+ delay: M && !N ? R : 0
66
72
  }
67
73
  }),
68
74
  T ? /* @__PURE__ */ n("circle", {
69
75
  cx: "12",
70
76
  cy: "12",
71
77
  r: u,
72
- stroke: N,
73
- strokeWidth: O,
78
+ stroke: P,
79
+ strokeWidth: k,
74
80
  fill: "none",
75
81
  strokeLinecap: "round",
76
- strokeDasharray: A ? `${d * .75} ${d * .25}` : `${d} 0`,
82
+ strokeDasharray: j ? `${d * .75} ${d * .25}` : `${d} 0`,
77
83
  style: {
78
84
  transformOrigin: "center",
79
- transform: A ? "rotate(-90deg)" : void 0,
80
- opacity: j && !M ? 0 : 1
85
+ transform: j ? "rotate(-90deg)" : void 0,
86
+ opacity: M && !N ? 0 : 1
81
87
  }
82
88
  }) : /* @__PURE__ */ n(a.circle, {
83
89
  cx: "12",
84
90
  cy: "12",
85
91
  r: u,
86
- stroke: N,
87
- strokeWidth: O,
92
+ stroke: P,
93
+ strokeWidth: k,
88
94
  fill: "none",
89
95
  strokeLinecap: "round",
90
96
  style: { transformOrigin: "center" },
91
- animate: A ? {
97
+ animate: j ? {
92
98
  rotate: [0, 360],
93
99
  strokeDasharray: [
94
100
  `${d * .1} ${d * .9}`,
@@ -98,9 +104,9 @@ var s = {
98
104
  } : {
99
105
  rotate: 0,
100
106
  strokeDasharray: `${d} 0`,
101
- ...!M && { opacity: 0 }
107
+ ...!N && { opacity: 0 }
102
108
  },
103
- transition: A ? {
109
+ transition: j ? {
104
110
  rotate: {
105
111
  duration: 1,
106
112
  repeat: Infinity,
@@ -117,26 +123,26 @@ var s = {
117
123
  ease: "easeOut"
118
124
  },
119
125
  strokeDasharray: {
120
- duration: L,
126
+ duration: R,
121
127
  ease: "easeInOut"
122
128
  },
123
- ...!M && { opacity: {
129
+ ...!N && { opacity: {
124
130
  duration: .2,
125
- delay: L
131
+ delay: R
126
132
  } }
127
133
  }
128
134
  }),
129
- /* @__PURE__ */ n(i, { children: j && M && (T ? /* @__PURE__ */ n("circle", {
135
+ /* @__PURE__ */ n(i, { children: M && N && (T ? /* @__PURE__ */ n("circle", {
130
136
  cx: "12",
131
137
  cy: "12",
132
- r: u + O / 2,
133
- fill: N,
138
+ r: u + k / 2,
139
+ fill: P,
134
140
  stroke: "none"
135
141
  }, "fill-static") : /* @__PURE__ */ n(a.circle, {
136
142
  cx: "12",
137
143
  cy: "12",
138
- r: u + O / 2,
139
- fill: N,
144
+ r: u + k / 2,
145
+ fill: P,
140
146
  stroke: "none",
141
147
  initial: { opacity: 0 },
142
148
  animate: { opacity: 1 },
@@ -148,16 +154,16 @@ var s = {
148
154
  style: { transformOrigin: "center" }
149
155
  }, "fill")) }),
150
156
  /* @__PURE__ */ n(i, { children: y === "success" && (T ? /* @__PURE__ */ n("path", {
151
- d: F,
152
- stroke: P,
153
- strokeWidth: k,
157
+ d: I,
158
+ stroke: F,
159
+ strokeWidth: A,
154
160
  strokeLinecap: "round",
155
161
  strokeLinejoin: "round",
156
162
  fill: "none"
157
163
  }, "check-static") : /* @__PURE__ */ n(a.path, {
158
- d: F,
159
- stroke: P,
160
- strokeWidth: k,
164
+ d: I,
165
+ stroke: F,
166
+ strokeWidth: A,
161
167
  strokeLinecap: "round",
162
168
  strokeLinejoin: "round",
163
169
  fill: "none",
@@ -171,27 +177,27 @@ var s = {
171
177
  },
172
178
  transition: {
173
179
  pathLength: {
174
- duration: z,
180
+ duration: B,
175
181
  ease: "easeOut",
176
- delay: R
182
+ delay: z
177
183
  },
178
184
  opacity: {
179
185
  duration: .1,
180
- delay: R
186
+ delay: z
181
187
  }
182
188
  },
183
189
  onAnimationComplete: () => S?.()
184
190
  }, "check")) }),
185
191
  /* @__PURE__ */ n(i, { children: y === "error" && (T ? /* @__PURE__ */ n("path", {
186
- d: I,
187
- stroke: P,
188
- strokeWidth: k,
192
+ d: L,
193
+ stroke: F,
194
+ strokeWidth: A,
189
195
  strokeLinecap: "round",
190
196
  fill: "none"
191
197
  }, "x-static") : /* @__PURE__ */ n(a.path, {
192
- d: I,
193
- stroke: P,
194
- strokeWidth: k,
198
+ d: L,
199
+ stroke: F,
200
+ strokeWidth: A,
195
201
  strokeLinecap: "round",
196
202
  fill: "none",
197
203
  initial: {
@@ -204,13 +210,13 @@ var s = {
204
210
  },
205
211
  transition: {
206
212
  pathLength: {
207
- duration: z,
213
+ duration: B,
208
214
  ease: "easeOut",
209
- delay: R
215
+ delay: z
210
216
  },
211
217
  opacity: {
212
218
  duration: .1,
213
- delay: R
219
+ delay: z
214
220
  }
215
221
  },
216
222
  onAnimationComplete: () => S?.()
@@ -1 +1 @@
1
- {"version":3,"file":"spinner.js","names":[],"sources":["../../src/ui/spinner.tsx"],"sourcesContent":["'use client'\n\nimport {\n AnimatePresence,\n motion,\n useReducedMotion,\n} from 'framer-motion'\nimport * as React from 'react'\n\nimport { cn } from './lib/utils'\n\nconst sizeClasses = {\n sm: 'h-ico-sm w-ico-sm',\n md: 'h-ico-md w-ico-md',\n lg: 'h-ico-lg w-ico-lg',\n} as const\n\n/** Arc stroke width per size */\nconst arcStrokeWidths = { sm: 3, md: 3, lg: 4 } as const\n\n/** Thinner icon stroke for checkmark / X — feels lighter and more refined */\nconst iconStrokeWidths = { sm: 1.8, md: 2, lg: 2.5 } as const\n\n/**\n * Props for Spinner — an animated arc spinner with optional success (checkmark) and error (X)\n * state transitions, powered by Framer Motion.\n *\n * **Sizes:** `sm` (16px) | `md` (20px, default) | `lg` (24px)\n *\n * **States:**\n * - `spinning` (default): Rotating arc with Material Design-style pulse\n * - `success`: Arc completes to filled green circle, white checkmark draws in\n * - `error`: Arc completes to filled red circle, white X draws in\n *\n * **Variants:**\n * - `filled` (default): Semantic colored fill + white icon — for standalone contexts\n * - `bare`: No fill, icon uses `currentColor` — for embedding in buttons, toolbars, etc.\n *\n * **Accessibility:** `role=\"status\"` with sr-only text that updates per state.\n * Respects `prefers-reduced-motion` — shows static icons with opacity crossfades.\n *\n * @example\n * <Spinner /> // basic arc spinner\n * <Spinner size=\"sm\" /> // small\n * <Spinner state=\"success\" /> // filled green circle + white check\n * <Spinner state=\"success\" variant=\"bare\" /> // just the check, currentColor\n * <Spinner state=\"error\" /> // filled red circle + white X\n * <Spinner delay={150} /> // no render until 150ms\n * <Spinner state=\"success\" onComplete={handleDone} /> // callback after animation\n */\nexport interface SpinnerProps {\n /** sm | md | lg — maps to icon size tokens. Default: 'md' */\n size?: 'sm' | 'md' | 'lg'\n /** Current state. Default: 'spinning' */\n state?: 'spinning' | 'success' | 'error'\n /** Visual variant for success/error states. Default: 'filled' */\n variant?: 'filled' | 'bare'\n /** Delay in ms before showing (avoids flicker). Default: 0 */\n delay?: number\n /** Fires when success/error transition animation completes */\n onComplete?: () => void\n className?: string\n}\n\nconst RADIUS = 10\nconst CIRCUMFERENCE = 2 * Math.PI * RADIUS\n\nconst srText: Record<string, string> = {\n spinning: 'Loading...',\n success: 'Complete',\n error: 'Error',\n}\n\nconst stateColors: Record<string, string> = {\n spinning: 'var(--color-accent-9)',\n success: 'var(--color-success-9)',\n error: 'var(--color-error-9)',\n}\n\n// Filled variant — icons fit inside the filled circle\nconst CHECKMARK_D = 'M7 12.5l3 3 7-7'\nconst X_MARK_D = 'M8 8l8 8M16 8l-8 8'\n\n// Bare variant — larger icons since there's no circle constraining them\nconst CHECKMARK_BARE_D = 'M5 12.5l4 4 10-10'\nconst X_MARK_BARE_D = 'M6 6l12 12M18 6l-12 12'\n\nconst Spinner = React.forwardRef<HTMLSpanElement, SpinnerProps>(\n ({ size = 'md', state = 'spinning', variant = 'filled', delay = 0, onComplete, className }, ref) => {\n const prefersReduced = useReducedMotion()\n const [visible, setVisible] = React.useState(delay === 0)\n\n React.useEffect(() => {\n if (delay <= 0) {\n setVisible(true)\n return\n }\n const timer = setTimeout(() => setVisible(true), delay)\n return () => clearTimeout(timer)\n }, [delay])\n\n if (!visible) return null\n\n const arcSw = arcStrokeWidths[size]\n const iconSw = iconStrokeWidths[size]\n const isSpinning = state === 'spinning'\n const isFinal = state === 'success' || state === 'error'\n const isFilled = variant === 'filled'\n const color = variant === 'bare' && state === 'spinning' ? 'currentColor' : stateColors[state]\n\n // Icon stroke: white on filled backgrounds, currentColor on bare\n const iconColor = isFilled ? 'white' : 'currentColor'\n const checkPath = isFilled ? CHECKMARK_D : CHECKMARK_BARE_D\n const xPath = isFilled ? X_MARK_D : X_MARK_BARE_D\n\n // ── Sequence timing (seconds) ──────────────────────────────────\n // Filled: arc completes → fill fades in → white icon draws\n // Bare: arc completes + fades out → currentColor icon draws\n const ARC_COMPLETE = 0.4\n const FILL_DELAY = 0.3\n const FILL_DURATION = 0.25\n const ICON_DELAY = isFilled ? 0.5 : 0.35\n const ICON_DURATION = 0.35\n\n return (\n <span ref={ref} role=\"status\" className={cn('inline-flex', className)}>\n <svg\n className={sizeClasses[size]}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n {/* Background track circle — hidden in bare final state */}\n {prefersReduced ? (\n !(isFinal && !isFilled) && (\n <circle\n cx=\"12\"\n cy=\"12\"\n r={RADIUS}\n stroke=\"var(--color-surface-border)\"\n strokeWidth={arcSw}\n fill=\"none\"\n />\n )\n ) : (\n <motion.circle\n cx=\"12\"\n cy=\"12\"\n r={RADIUS}\n stroke=\"var(--color-surface-border)\"\n strokeWidth={arcSw}\n fill=\"none\"\n animate={{ opacity: isFinal && !isFilled ? 0 : 1 }}\n transition={{ duration: 0.2, delay: isFinal && !isFilled ? ARC_COMPLETE : 0 }}\n />\n )}\n\n {/* Animated arc — spins when loading, completes to full circle on state change */}\n {prefersReduced ? (\n <circle\n cx=\"12\"\n cy=\"12\"\n r={RADIUS}\n stroke={color}\n strokeWidth={arcSw}\n fill=\"none\"\n strokeLinecap=\"round\"\n strokeDasharray={\n isSpinning\n ? `${CIRCUMFERENCE * 0.75} ${CIRCUMFERENCE * 0.25}`\n : `${CIRCUMFERENCE} 0`\n }\n style={{\n transformOrigin: 'center',\n transform: isSpinning ? 'rotate(-90deg)' : undefined,\n opacity: isFinal && !isFilled ? 0 : 1,\n }}\n />\n ) : (\n <motion.circle\n cx=\"12\"\n cy=\"12\"\n r={RADIUS}\n stroke={color}\n strokeWidth={arcSw}\n fill=\"none\"\n strokeLinecap=\"round\"\n style={{ transformOrigin: 'center' }}\n animate={\n isSpinning\n ? {\n rotate: [0, 360],\n strokeDasharray: [\n `${CIRCUMFERENCE * 0.1} ${CIRCUMFERENCE * 0.9}`,\n `${CIRCUMFERENCE * 0.75} ${CIRCUMFERENCE * 0.25}`,\n `${CIRCUMFERENCE * 0.1} ${CIRCUMFERENCE * 0.9}`,\n ],\n }\n : {\n rotate: 0,\n strokeDasharray: `${CIRCUMFERENCE} 0`,\n // In bare mode, fade out the arc after it completes\n ...(!isFilled && { opacity: 0 }),\n }\n }\n transition={\n isSpinning\n ? {\n rotate: { duration: 1, repeat: Infinity, ease: 'linear' },\n strokeDasharray: { duration: 1.5, repeat: Infinity, ease: 'easeInOut' },\n }\n : {\n rotate: { duration: 0.3, ease: 'easeOut' },\n strokeDasharray: { duration: ARC_COMPLETE, ease: 'easeInOut' },\n ...(!isFilled && { opacity: { duration: 0.2, delay: ARC_COMPLETE } }),\n }\n }\n />\n )}\n\n {/* Filled circle — fades in after arc completes, covers track (filled variant only) */}\n <AnimatePresence>\n {isFinal && isFilled &&\n (prefersReduced ? (\n <circle\n key=\"fill-static\"\n cx=\"12\"\n cy=\"12\"\n r={RADIUS + arcSw / 2}\n fill={color}\n stroke=\"none\"\n />\n ) : (\n <motion.circle\n key=\"fill\"\n cx=\"12\"\n cy=\"12\"\n r={RADIUS + arcSw / 2}\n fill={color}\n stroke=\"none\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ duration: FILL_DURATION, delay: FILL_DELAY, ease: 'easeOut' }}\n style={{ transformOrigin: 'center' }}\n />\n ))}\n </AnimatePresence>\n\n {/* Checkmark — white on filled, currentColor on bare */}\n <AnimatePresence>\n {state === 'success' &&\n (prefersReduced ? (\n <path\n key=\"check-static\"\n d={checkPath}\n stroke={iconColor}\n strokeWidth={iconSw}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n fill=\"none\"\n />\n ) : (\n <motion.path\n key=\"check\"\n d={checkPath}\n stroke={iconColor}\n strokeWidth={iconSw}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n fill=\"none\"\n initial={{ pathLength: 0, opacity: 0 }}\n animate={{ pathLength: 1, opacity: 1 }}\n transition={{\n pathLength: { duration: ICON_DURATION, ease: 'easeOut', delay: ICON_DELAY },\n opacity: { duration: 0.1, delay: ICON_DELAY },\n }}\n onAnimationComplete={() => onComplete?.()}\n />\n ))}\n </AnimatePresence>\n\n {/* X mark — white on filled, currentColor on bare */}\n <AnimatePresence>\n {state === 'error' &&\n (prefersReduced ? (\n <path\n key=\"x-static\"\n d={xPath}\n stroke={iconColor}\n strokeWidth={iconSw}\n strokeLinecap=\"round\"\n fill=\"none\"\n />\n ) : (\n <motion.path\n key=\"x\"\n d={xPath}\n stroke={iconColor}\n strokeWidth={iconSw}\n strokeLinecap=\"round\"\n fill=\"none\"\n initial={{ pathLength: 0, opacity: 0 }}\n animate={{ pathLength: 1, opacity: 1 }}\n transition={{\n pathLength: { duration: ICON_DURATION, ease: 'easeOut', delay: ICON_DELAY },\n opacity: { duration: 0.1, delay: ICON_DELAY },\n }}\n onAnimationComplete={() => onComplete?.()}\n />\n ))}\n </AnimatePresence>\n </svg>\n <span className=\"sr-only\">{srText[state]}</span>\n </span>\n )\n },\n)\nSpinner.displayName = 'Spinner'\n\nexport { Spinner }\n"],"mappings":"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"}
1
+ {"version":3,"file":"spinner.js","names":[],"sources":["../../src/ui/spinner.tsx"],"sourcesContent":["'use client'\n\nimport {\n AnimatePresence,\n motion,\n useReducedMotion,\n} from 'framer-motion'\nimport * as React from 'react'\n\nimport { cn } from './lib/utils'\n\nconst sizeClasses = {\n sm: 'h-ico-sm w-ico-sm',\n md: 'h-ico-md w-ico-md',\n lg: 'h-ico-lg w-ico-lg',\n} as const\n\n/** Arc stroke width per size */\nconst arcStrokeWidths = { sm: 3, md: 3, lg: 4 } as const\n\n/** Thinner icon stroke for checkmark / X — feels lighter and more refined */\nconst iconStrokeWidths = { sm: 1.8, md: 2, lg: 2.5 } as const\n\n/**\n * Props for Spinner — an animated arc spinner with optional success (checkmark) and error (X)\n * state transitions, powered by Framer Motion.\n *\n * **Sizes:** `sm` (16px) | `md` (20px, default) | `lg` (24px)\n *\n * **States:**\n * - `spinning` (default): Rotating arc with Material Design-style pulse\n * - `success`: Arc completes to filled green circle, white checkmark draws in\n * - `error`: Arc completes to filled red circle, white X draws in\n *\n * **Variants:**\n * - `filled` (default): Semantic colored fill + white icon — for standalone contexts\n * - `bare`: No fill, icon uses `currentColor` — for embedding in buttons, toolbars, etc.\n *\n * **Accessibility:** `role=\"status\"` with sr-only text that updates per state.\n * Respects `prefers-reduced-motion` — shows static icons with opacity crossfades.\n *\n * @example\n * <Spinner /> // basic arc spinner\n * <Spinner size=\"sm\" /> // small\n * <Spinner state=\"success\" /> // filled green circle + white check\n * <Spinner state=\"success\" variant=\"bare\" /> // just the check, currentColor\n * <Spinner state=\"error\" /> // filled red circle + white X\n * <Spinner delay={150} /> // no render until 150ms\n * <Spinner state=\"success\" onComplete={handleDone} /> // callback after animation\n */\nexport interface SpinnerProps {\n /** sm | md | lg — maps to icon size tokens. Default: 'md' */\n size?: 'sm' | 'md' | 'lg'\n /** Current state. Default: 'spinning' */\n state?: 'spinning' | 'success' | 'error'\n /** Visual variant for success/error states. Default: 'filled' */\n variant?: 'filled' | 'bare'\n /** Delay in ms before showing (avoids flicker). Default: 0 */\n delay?: number\n /** Fires when success/error transition animation completes */\n onComplete?: () => void\n className?: string\n}\n\nconst RADIUS = 10\nconst CIRCUMFERENCE = 2 * Math.PI * RADIUS\n\nconst srText: Record<string, string> = {\n spinning: 'Loading...',\n success: 'Complete',\n error: 'Error',\n}\n\nconst stateColors: Record<string, string> = {\n spinning: 'var(--color-accent-9)',\n success: 'var(--color-success-9)',\n error: 'var(--color-error-9)',\n}\n\n// Filled variant — icons fit inside the filled circle\nconst CHECKMARK_D = 'M7 12.5l3 3 7-7'\nconst X_MARK_D = 'M8 8l8 8M16 8l-8 8'\n\n// Bare variant — larger icons since there's no circle constraining them\nconst CHECKMARK_BARE_D = 'M5 12.5l4 4 10-10'\nconst X_MARK_BARE_D = 'M6 6l12 12M18 6l-12 12'\n\nconst Spinner = React.forwardRef<HTMLSpanElement, SpinnerProps>(\n ({ size = 'md', state = 'spinning', variant = 'filled', delay = 0, onComplete, className }, ref) => {\n const prefersReduced = useReducedMotion()\n const [visible, setVisible] = React.useState(delay === 0)\n\n React.useEffect(() => {\n if (delay <= 0) {\n setVisible(true)\n return\n }\n const timer = setTimeout(() => setVisible(true), delay)\n return () => clearTimeout(timer)\n }, [delay])\n\n // Under reduced-motion the success/error paths render statically (no\n // onAnimationComplete), so fire the documented onComplete contract here.\n const onCompleteRef = React.useRef(onComplete)\n React.useEffect(() => { onCompleteRef.current = onComplete })\n React.useEffect(() => {\n if (prefersReduced && (state === 'success' || state === 'error')) {\n onCompleteRef.current?.()\n }\n }, [prefersReduced, state])\n\n if (!visible) return null\n\n const arcSw = arcStrokeWidths[size]\n const iconSw = iconStrokeWidths[size]\n const isSpinning = state === 'spinning'\n const isFinal = state === 'success' || state === 'error'\n const isFilled = variant === 'filled'\n const color = variant === 'bare' && state === 'spinning' ? 'currentColor' : stateColors[state]\n\n // Icon stroke: white on filled backgrounds, currentColor on bare\n const iconColor = isFilled ? 'white' : 'currentColor'\n const checkPath = isFilled ? CHECKMARK_D : CHECKMARK_BARE_D\n const xPath = isFilled ? X_MARK_D : X_MARK_BARE_D\n\n // ── Sequence timing (seconds) ──────────────────────────────────\n // Filled: arc completes → fill fades in → white icon draws\n // Bare: arc completes + fades out → currentColor icon draws\n const ARC_COMPLETE = 0.4\n const FILL_DELAY = 0.3\n const FILL_DURATION = 0.25\n const ICON_DELAY = isFilled ? 0.5 : 0.35\n const ICON_DURATION = 0.35\n\n return (\n <span ref={ref} role=\"status\" className={cn('inline-flex', className)}>\n <svg\n className={sizeClasses[size]}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n {/* Background track circle — hidden in bare final state */}\n {prefersReduced ? (\n !(isFinal && !isFilled) && (\n <circle\n cx=\"12\"\n cy=\"12\"\n r={RADIUS}\n stroke=\"var(--color-surface-border)\"\n strokeWidth={arcSw}\n fill=\"none\"\n />\n )\n ) : (\n <motion.circle\n cx=\"12\"\n cy=\"12\"\n r={RADIUS}\n stroke=\"var(--color-surface-border)\"\n strokeWidth={arcSw}\n fill=\"none\"\n animate={{ opacity: isFinal && !isFilled ? 0 : 1 }}\n transition={{ duration: 0.2, delay: isFinal && !isFilled ? ARC_COMPLETE : 0 }}\n />\n )}\n\n {/* Animated arc — spins when loading, completes to full circle on state change */}\n {prefersReduced ? (\n <circle\n cx=\"12\"\n cy=\"12\"\n r={RADIUS}\n stroke={color}\n strokeWidth={arcSw}\n fill=\"none\"\n strokeLinecap=\"round\"\n strokeDasharray={\n isSpinning\n ? `${CIRCUMFERENCE * 0.75} ${CIRCUMFERENCE * 0.25}`\n : `${CIRCUMFERENCE} 0`\n }\n style={{\n transformOrigin: 'center',\n transform: isSpinning ? 'rotate(-90deg)' : undefined,\n opacity: isFinal && !isFilled ? 0 : 1,\n }}\n />\n ) : (\n <motion.circle\n cx=\"12\"\n cy=\"12\"\n r={RADIUS}\n stroke={color}\n strokeWidth={arcSw}\n fill=\"none\"\n strokeLinecap=\"round\"\n style={{ transformOrigin: 'center' }}\n animate={\n isSpinning\n ? {\n rotate: [0, 360],\n strokeDasharray: [\n `${CIRCUMFERENCE * 0.1} ${CIRCUMFERENCE * 0.9}`,\n `${CIRCUMFERENCE * 0.75} ${CIRCUMFERENCE * 0.25}`,\n `${CIRCUMFERENCE * 0.1} ${CIRCUMFERENCE * 0.9}`,\n ],\n }\n : {\n rotate: 0,\n strokeDasharray: `${CIRCUMFERENCE} 0`,\n // In bare mode, fade out the arc after it completes\n ...(!isFilled && { opacity: 0 }),\n }\n }\n transition={\n isSpinning\n ? {\n rotate: { duration: 1, repeat: Infinity, ease: 'linear' },\n strokeDasharray: { duration: 1.5, repeat: Infinity, ease: 'easeInOut' },\n }\n : {\n rotate: { duration: 0.3, ease: 'easeOut' },\n strokeDasharray: { duration: ARC_COMPLETE, ease: 'easeInOut' },\n ...(!isFilled && { opacity: { duration: 0.2, delay: ARC_COMPLETE } }),\n }\n }\n />\n )}\n\n {/* Filled circle — fades in after arc completes, covers track (filled variant only) */}\n <AnimatePresence>\n {isFinal && isFilled &&\n (prefersReduced ? (\n <circle\n key=\"fill-static\"\n cx=\"12\"\n cy=\"12\"\n r={RADIUS + arcSw / 2}\n fill={color}\n stroke=\"none\"\n />\n ) : (\n <motion.circle\n key=\"fill\"\n cx=\"12\"\n cy=\"12\"\n r={RADIUS + arcSw / 2}\n fill={color}\n stroke=\"none\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ duration: FILL_DURATION, delay: FILL_DELAY, ease: 'easeOut' }}\n style={{ transformOrigin: 'center' }}\n />\n ))}\n </AnimatePresence>\n\n {/* Checkmark — white on filled, currentColor on bare */}\n <AnimatePresence>\n {state === 'success' &&\n (prefersReduced ? (\n <path\n key=\"check-static\"\n d={checkPath}\n stroke={iconColor}\n strokeWidth={iconSw}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n fill=\"none\"\n />\n ) : (\n <motion.path\n key=\"check\"\n d={checkPath}\n stroke={iconColor}\n strokeWidth={iconSw}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n fill=\"none\"\n initial={{ pathLength: 0, opacity: 0 }}\n animate={{ pathLength: 1, opacity: 1 }}\n transition={{\n pathLength: { duration: ICON_DURATION, ease: 'easeOut', delay: ICON_DELAY },\n opacity: { duration: 0.1, delay: ICON_DELAY },\n }}\n onAnimationComplete={() => onComplete?.()}\n />\n ))}\n </AnimatePresence>\n\n {/* X mark — white on filled, currentColor on bare */}\n <AnimatePresence>\n {state === 'error' &&\n (prefersReduced ? (\n <path\n key=\"x-static\"\n d={xPath}\n stroke={iconColor}\n strokeWidth={iconSw}\n strokeLinecap=\"round\"\n fill=\"none\"\n />\n ) : (\n <motion.path\n key=\"x\"\n d={xPath}\n stroke={iconColor}\n strokeWidth={iconSw}\n strokeLinecap=\"round\"\n fill=\"none\"\n initial={{ pathLength: 0, opacity: 0 }}\n animate={{ pathLength: 1, opacity: 1 }}\n transition={{\n pathLength: { duration: ICON_DURATION, ease: 'easeOut', delay: ICON_DELAY },\n opacity: { duration: 0.1, delay: ICON_DELAY },\n }}\n onAnimationComplete={() => onComplete?.()}\n />\n ))}\n </AnimatePresence>\n </svg>\n <span className=\"sr-only\">{srText[state]}</span>\n </span>\n )\n },\n)\nSpinner.displayName = 'Spinner'\n\nexport { Spinner }\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"switch.d.ts","sourceRoot":"","sources":["../../src/ui/switch.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,gBAAgB,MAAM,0BAA0B,CAAA;AAE5D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,mBAAmB,CAAA;AAuBtE,MAAM,WAAW,WAAY,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,gBAAgB,CAAC,IAAI,CAAC;IAC/F,yJAAyJ;IACzJ,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAA;IACxC,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAED,QAAA,MAAM,MAAM,uFAyDV,CAAA;AAGF,OAAO,EAAE,MAAM,EAAE,CAAA"}
1
+ {"version":3,"file":"switch.d.ts","sourceRoot":"","sources":["../../src/ui/switch.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,gBAAgB,MAAM,0BAA0B,CAAA;AAE5D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,mBAAmB,CAAA;AAuBtE,MAAM,WAAW,WAAY,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,gBAAgB,CAAC,IAAI,CAAC;IAC/F,yJAAyJ;IACzJ,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAA;IACxC,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAED,QAAA,MAAM,MAAM,uFA8DV,CAAA;AAGF,OAAO,EAAE,MAAM,EAAE,CAAA"}
package/dist/ui/switch.js CHANGED
@@ -6,9 +6,9 @@ import { useFormField as i } from "./form.js";
6
6
  import { resolveFieldState as a } from "../_chunks/field-state.js";
7
7
  import * as o from "react";
8
8
  import { jsx as s } from "react/jsx-runtime";
9
- import { motion as c } from "framer-motion";
9
+ import { motion as c, useReducedMotion as l } from "framer-motion";
10
10
  //#region src/ui/switch.tsx
11
- var l = {
11
+ var u = {
12
12
  sm: {
13
13
  track: "h-6 w-[38px]",
14
14
  thumb: "h-5 w-5",
@@ -24,40 +24,40 @@ var l = {
24
24
  thumb: "h-6 w-6",
25
25
  travel: 24
26
26
  }
27
- }, u = {
27
+ }, d = {
28
28
  error: "border-error-7 data-[state=checked]:bg-error-9",
29
29
  warning: "border-warning-7 data-[state=checked]:bg-warning-9",
30
30
  success: "border-success-7 data-[state=checked]:bg-success-9"
31
- }, d = {
31
+ }, f = {
32
32
  accent: "data-[state=checked]:bg-accent-9",
33
33
  success: "data-[state=checked]:bg-success-9",
34
34
  warning: "data-[state=checked]:bg-warning-9"
35
- }, f = o.forwardRef(({ className: f, state: p, size: m = "md", color: h = "accent", thumbIcon: g, checked: _, defaultChecked: v, onCheckedChange: y, ...b }, x) => {
36
- let S = i(), C = a(p, S.state), w = b["aria-describedby"] ?? S.helperTextId, T = b["aria-required"] ?? S.required, [E, D] = o.useState(v ?? !1), O = _ === void 0 ? E : _, { track: k, thumb: A, travel: j } = l[m], M = o.useCallback((e) => {
37
- _ === void 0 && D(e), y?.(e);
38
- }, [_, y]);
35
+ }, p = o.forwardRef(({ className: p, state: m, size: h = "md", color: g = "accent", thumbIcon: _, checked: v, defaultChecked: y, onCheckedChange: b, ...x }, S) => {
36
+ let C = i(), w = a(m, C.state), T = x["aria-describedby"] ?? C.helperTextId, E = x["aria-required"] ?? C.required, [D, O] = o.useState(y ?? !1), k = v === void 0 ? D : v, A = l(), j = typeof document < "u" && (document.dir === "rtl" || document.documentElement.dir === "rtl"), { track: M, thumb: N, travel: P } = u[h], F = o.useCallback((e) => {
37
+ v === void 0 && O(e), b?.(e);
38
+ }, [v, b]);
39
39
  return /* @__PURE__ */ s(e, {
40
- className: r("touch-target peer inline-flex shrink-0 cursor-pointer items-center rounded-pill border-2 border-surface-border-strong shadow-raised transition-colors duration-fast-01 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-action-disabled data-[state=checked]:border-transparent data-[state=unchecked]:bg-surface-border-strong data-[state=unchecked]:hover:bg-surface-raised-active", k, d[h], C && u[C], f),
41
- checked: _,
42
- defaultChecked: v,
43
- onCheckedChange: M,
44
- "aria-invalid": C === "error" || void 0,
45
- "aria-describedby": w,
46
- "aria-required": T || void 0,
47
- ...b,
48
- ref: x,
40
+ className: r("touch-target peer inline-flex shrink-0 cursor-pointer items-center rounded-pill border-2 border-surface-border-strong shadow-raised transition-colors duration-fast-01 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-action-disabled data-[state=checked]:border-transparent data-[state=unchecked]:bg-surface-border-strong data-[state=unchecked]:hover:bg-surface-raised-active", M, f[g], w && d[w], p),
41
+ checked: v,
42
+ defaultChecked: y,
43
+ onCheckedChange: F,
44
+ "aria-invalid": w === "error" || void 0,
45
+ "aria-describedby": T,
46
+ "aria-required": E || void 0,
47
+ ...x,
48
+ ref: S,
49
49
  children: /* @__PURE__ */ s(t, {
50
50
  asChild: !0,
51
51
  children: /* @__PURE__ */ s(c.span, {
52
- className: r("pointer-events-none flex items-center justify-center rounded-pill bg-accent-fg shadow-raised-hover ring-0", A),
53
- animate: { x: O ? j : 0 },
54
- whileTap: { scale: .85 },
55
- transition: n.snappy,
56
- children: g
52
+ className: r("pointer-events-none flex items-center justify-center rounded-pill bg-accent-fg shadow-raised-hover ring-0", N),
53
+ animate: { x: (k ? P : 0) * (j ? -1 : 1) },
54
+ whileTap: A ? void 0 : { scale: .85 },
55
+ transition: A ? { duration: 0 } : n.snappy,
56
+ children: _
57
57
  })
58
58
  })
59
59
  });
60
60
  });
61
- f.displayName = e.displayName;
61
+ p.displayName = e.displayName;
62
62
  //#endregion
63
- export { f as Switch };
63
+ export { p as Switch };
@@ -1 +1 @@
1
- {"version":3,"file":"switch.js","names":[],"sources":["../../src/ui/switch.tsx"],"sourcesContent":["'use client'\n\nimport * as SwitchPrimitives from \"@primitives/react-switch\"\nimport { motion } from 'framer-motion'\nimport * as React from \"react\"\n\nimport { useFormField } from './form'\nimport { type FieldState, resolveFieldState } from './lib/field-state'\nimport { springs } from './lib/motion'\nimport { cn } from \"./lib/utils\"\n\nconst sizeConfig = {\n sm: { track: 'h-6 w-[38px]', thumb: 'h-5 w-5', travel: 16 },\n md: { track: 'h-6 w-11', thumb: 'h-ico-md w-ico-md', travel: 20 },\n lg: { track: 'h-7 w-[52px]', thumb: 'h-6 w-6', travel: 24 },\n} as const\n\n/** Validation border + checked-track tint per state (overrides `color` when set). */\nconst stateTintClasses: Record<Exclude<FieldState, 'default'>, string> = {\n error: 'border-error-7 data-[state=checked]:bg-error-9',\n warning: 'border-warning-7 data-[state=checked]:bg-warning-9',\n success: 'border-success-7 data-[state=checked]:bg-success-9',\n}\n\nconst colorMap = {\n accent: 'data-[state=checked]:bg-accent-9',\n success: 'data-[state=checked]:bg-success-9',\n warning: 'data-[state=checked]:bg-warning-9',\n} as const\n\nexport interface SwitchProps extends React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> {\n /** Validation/feedback state. `'error'` also sets `aria-invalid`. Inherited from `FormField` when omitted. Distinct from `color` (the ON-track tint). */\n state?: FieldState\n size?: 'sm' | 'md' | 'lg'\n color?: 'accent' | 'success' | 'warning'\n thumbIcon?: React.ReactNode\n}\n\nconst Switch = React.forwardRef<\n React.ElementRef<typeof SwitchPrimitives.Root>,\n SwitchProps\n>(({ className, state: stateProp, size = 'md', color = 'accent', thumbIcon, checked, defaultChecked, onCheckedChange, ...props }, ref) => {\n const fieldCtx = useFormField()\n const state = resolveFieldState(stateProp, fieldCtx.state)\n const ariaDescribedBy = props['aria-describedby'] ?? fieldCtx.helperTextId\n const ariaRequired = props['aria-required'] ?? fieldCtx.required\n\n // Track checked state internally to drive Framer Motion animation\n const [internalChecked, setInternalChecked] = React.useState(defaultChecked ?? false)\n const isChecked = checked !== undefined ? checked : internalChecked\n const { track, thumb, travel } = sizeConfig[size]\n\n const handleCheckedChange = React.useCallback(\n (value: boolean) => {\n if (checked === undefined) {\n setInternalChecked(value)\n }\n onCheckedChange?.(value)\n },\n [checked, onCheckedChange],\n )\n\n return (\n <SwitchPrimitives.Root\n className={cn(\n \"touch-target peer inline-flex shrink-0 cursor-pointer items-center rounded-pill border-2 border-surface-border-strong shadow-raised transition-colors duration-fast-01 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-action-disabled data-[state=checked]:border-transparent data-[state=unchecked]:bg-surface-border-strong data-[state=unchecked]:hover:bg-surface-raised-active\",\n track,\n colorMap[color],\n state && stateTintClasses[state],\n className\n )}\n checked={checked}\n defaultChecked={defaultChecked}\n onCheckedChange={handleCheckedChange}\n aria-invalid={state === 'error' || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n {...props}\n ref={ref}\n >\n <SwitchPrimitives.Thumb asChild>\n <motion.span\n className={cn(\n \"pointer-events-none flex items-center justify-center rounded-pill bg-accent-fg shadow-raised-hover ring-0\",\n thumb\n )}\n animate={{ x: isChecked ? travel : 0 }}\n whileTap={{ scale: 0.85 }}\n transition={springs.snappy}\n >\n {thumbIcon}\n </motion.span>\n </SwitchPrimitives.Thumb>\n </SwitchPrimitives.Root>\n )\n})\nSwitch.displayName = SwitchPrimitives.Root.displayName\n\nexport { Switch }\n"],"mappings":";;;;;;;;;;AAWA,IAAM,IAAa;CACjB,IAAI;EAAE,OAAO;EAAgB,OAAO;EAAW,QAAQ;CAAG;CAC1D,IAAI;EAAE,OAAO;EAAY,OAAO;EAAqB,QAAQ;CAAG;CAChE,IAAI;EAAE,OAAO;EAAgB,OAAO;EAAW,QAAQ;CAAG;AAC5D,GAGM,IAAmE;CACvE,OAAO;CACP,SAAS;CACT,SAAS;AACX,GAEM,IAAW;CACf,QAAQ;CACR,SAAS;CACT,SAAS;AACX,GAUM,IAAS,EAAM,YAGlB,EAAE,cAAW,OAAO,GAAW,UAAO,MAAM,WAAQ,UAAU,cAAW,YAAS,mBAAgB,oBAAiB,GAAG,KAAS,MAAQ;CACxI,IAAM,IAAW,EAAa,GACxB,IAAQ,EAAkB,GAAW,EAAS,KAAK,GACnD,IAAkB,EAAM,uBAAuB,EAAS,cACxD,IAAe,EAAM,oBAAoB,EAAS,UAGlD,CAAC,GAAiB,KAAsB,EAAM,SAAS,KAAkB,EAAK,GAC9E,IAAY,MAAY,KAAA,IAAsB,IAAV,GACpC,EAAE,UAAO,UAAO,cAAW,EAAW,IAEtC,IAAsB,EAAM,aAC/B,MAAmB;EAIlB,AAHI,MAAY,KAAA,KACd,EAAmB,CAAK,GAE1B,IAAkB,CAAK;CACzB,GACA,CAAC,GAAS,CAAe,CAC3B;CAEA,OACE,kBAAC,GAAD;EACE,WAAW,EACT,+dACA,GACA,EAAS,IACT,KAAS,EAAiB,IAC1B,CACF;EACS;EACO;EAChB,iBAAiB;EACjB,gBAAc,MAAU,WAAW,KAAA;EACnC,oBAAkB;EAClB,iBAAe,KAAgB,KAAA;EAC/B,GAAI;EACC;YAEL,kBAAC,GAAD;GAAwB,SAAA;aACtB,kBAAC,EAAO,MAAR;IACE,WAAW,EACT,6GACA,CACF;IACA,SAAS,EAAE,GAAG,IAAY,IAAS,EAAE;IACrC,UAAU,EAAE,OAAO,IAAK;IACxB,YAAY,EAAQ;cAEnB;GACU,CAAA;EACS,CAAA;CACH,CAAA;AAE3B,CAAC;AACD,EAAO,cAAA,EAAoC"}
1
+ {"version":3,"file":"switch.js","names":[],"sources":["../../src/ui/switch.tsx"],"sourcesContent":["'use client'\n\nimport * as SwitchPrimitives from \"@primitives/react-switch\"\nimport { motion, useReducedMotion } from 'framer-motion'\nimport * as React from \"react\"\n\nimport { useFormField } from './form'\nimport { type FieldState, resolveFieldState } from './lib/field-state'\nimport { springs } from './lib/motion'\nimport { cn } from \"./lib/utils\"\n\nconst sizeConfig = {\n sm: { track: 'h-6 w-[38px]', thumb: 'h-5 w-5', travel: 16 },\n md: { track: 'h-6 w-11', thumb: 'h-ico-md w-ico-md', travel: 20 },\n lg: { track: 'h-7 w-[52px]', thumb: 'h-6 w-6', travel: 24 },\n} as const\n\n/** Validation border + checked-track tint per state (overrides `color` when set). */\nconst stateTintClasses: Record<Exclude<FieldState, 'default'>, string> = {\n error: 'border-error-7 data-[state=checked]:bg-error-9',\n warning: 'border-warning-7 data-[state=checked]:bg-warning-9',\n success: 'border-success-7 data-[state=checked]:bg-success-9',\n}\n\nconst colorMap = {\n accent: 'data-[state=checked]:bg-accent-9',\n success: 'data-[state=checked]:bg-success-9',\n warning: 'data-[state=checked]:bg-warning-9',\n} as const\n\nexport interface SwitchProps extends React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> {\n /** Validation/feedback state. `'error'` also sets `aria-invalid`. Inherited from `FormField` when omitted. Distinct from `color` (the ON-track tint). */\n state?: FieldState\n size?: 'sm' | 'md' | 'lg'\n color?: 'accent' | 'success' | 'warning'\n thumbIcon?: React.ReactNode\n}\n\nconst Switch = React.forwardRef<\n React.ElementRef<typeof SwitchPrimitives.Root>,\n SwitchProps\n>(({ className, state: stateProp, size = 'md', color = 'accent', thumbIcon, checked, defaultChecked, onCheckedChange, ...props }, ref) => {\n const fieldCtx = useFormField()\n const state = resolveFieldState(stateProp, fieldCtx.state)\n const ariaDescribedBy = props['aria-describedby'] ?? fieldCtx.helperTextId\n const ariaRequired = props['aria-required'] ?? fieldCtx.required\n\n // Track checked state internally to drive Framer Motion animation\n const [internalChecked, setInternalChecked] = React.useState(defaultChecked ?? false)\n const isChecked = checked !== undefined ? checked : internalChecked\n const reduced = useReducedMotion()\n // Thumb travels toward the inline-end — mirror it under RTL.\n const isRtl =\n typeof document !== 'undefined' &&\n (document.dir === 'rtl' || document.documentElement.dir === 'rtl')\n const { track, thumb, travel } = sizeConfig[size]\n\n const handleCheckedChange = React.useCallback(\n (value: boolean) => {\n if (checked === undefined) {\n setInternalChecked(value)\n }\n onCheckedChange?.(value)\n },\n [checked, onCheckedChange],\n )\n\n return (\n <SwitchPrimitives.Root\n className={cn(\n \"touch-target peer inline-flex shrink-0 cursor-pointer items-center rounded-pill border-2 border-surface-border-strong shadow-raised transition-colors duration-fast-01 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-action-disabled data-[state=checked]:border-transparent data-[state=unchecked]:bg-surface-border-strong data-[state=unchecked]:hover:bg-surface-raised-active\",\n track,\n colorMap[color],\n state && stateTintClasses[state],\n className\n )}\n checked={checked}\n defaultChecked={defaultChecked}\n onCheckedChange={handleCheckedChange}\n aria-invalid={state === 'error' || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n {...props}\n ref={ref}\n >\n <SwitchPrimitives.Thumb asChild>\n <motion.span\n className={cn(\n \"pointer-events-none flex items-center justify-center rounded-pill bg-accent-fg shadow-raised-hover ring-0\",\n thumb\n )}\n animate={{ x: (isChecked ? travel : 0) * (isRtl ? -1 : 1) }}\n whileTap={reduced ? undefined : { scale: 0.85 }}\n transition={reduced ? { duration: 0 } : springs.snappy}\n >\n {thumbIcon}\n </motion.span>\n </SwitchPrimitives.Thumb>\n </SwitchPrimitives.Root>\n )\n})\nSwitch.displayName = SwitchPrimitives.Root.displayName\n\nexport { Switch }\n"],"mappings":";;;;;;;;;;AAWA,IAAM,IAAa;CACjB,IAAI;EAAE,OAAO;EAAgB,OAAO;EAAW,QAAQ;CAAG;CAC1D,IAAI;EAAE,OAAO;EAAY,OAAO;EAAqB,QAAQ;CAAG;CAChE,IAAI;EAAE,OAAO;EAAgB,OAAO;EAAW,QAAQ;CAAG;AAC5D,GAGM,IAAmE;CACvE,OAAO;CACP,SAAS;CACT,SAAS;AACX,GAEM,IAAW;CACf,QAAQ;CACR,SAAS;CACT,SAAS;AACX,GAUM,IAAS,EAAM,YAGlB,EAAE,cAAW,OAAO,GAAW,UAAO,MAAM,WAAQ,UAAU,cAAW,YAAS,mBAAgB,oBAAiB,GAAG,KAAS,MAAQ;CACxI,IAAM,IAAW,EAAa,GACxB,IAAQ,EAAkB,GAAW,EAAS,KAAK,GACnD,IAAkB,EAAM,uBAAuB,EAAS,cACxD,IAAe,EAAM,oBAAoB,EAAS,UAGlD,CAAC,GAAiB,KAAsB,EAAM,SAAS,KAAkB,EAAK,GAC9E,IAAY,MAAY,KAAA,IAAsB,IAAV,GACpC,IAAU,EAAiB,GAE3B,IACJ,OAAO,WAAa,QACnB,SAAS,QAAQ,SAAS,SAAS,gBAAgB,QAAQ,QACxD,EAAE,UAAO,UAAO,cAAW,EAAW,IAEtC,IAAsB,EAAM,aAC/B,MAAmB;EAIlB,AAHI,MAAY,KAAA,KACd,EAAmB,CAAK,GAE1B,IAAkB,CAAK;CACzB,GACA,CAAC,GAAS,CAAe,CAC3B;CAEA,OACE,kBAAC,GAAD;EACE,WAAW,EACT,+dACA,GACA,EAAS,IACT,KAAS,EAAiB,IAC1B,CACF;EACS;EACO;EAChB,iBAAiB;EACjB,gBAAc,MAAU,WAAW,KAAA;EACnC,oBAAkB;EAClB,iBAAe,KAAgB,KAAA;EAC/B,GAAI;EACC;YAEL,kBAAC,GAAD;GAAwB,SAAA;aACtB,kBAAC,EAAO,MAAR;IACE,WAAW,EACT,6GACA,CACF;IACA,SAAS,EAAE,IAAI,IAAY,IAAS,MAAM,IAAQ,KAAK,GAAG;IAC1D,UAAU,IAAU,KAAA,IAAY,EAAE,OAAO,IAAK;IAC9C,YAAY,IAAU,EAAE,UAAU,EAAE,IAAI,EAAQ;cAE/C;GACU,CAAA;EACS,CAAA;CACH,CAAA;AAE3B,CAAC;AACD,EAAO,cAAA,EAAoC"}
package/dist/ui/table.js CHANGED
@@ -35,7 +35,7 @@ var s = t.forwardRef(({ className: t, ...r }, i) => /* @__PURE__ */ n("tfoot", {
35
35
  s.displayName = "TableFooter";
36
36
  var c = t.forwardRef(({ className: t, ...r }, i) => /* @__PURE__ */ n("tr", {
37
37
  ref: i,
38
- className: e("group/row border-b border-surface-border-subtle transition-colors hover:bg-surface-raised-hover data-[state=selected]:bg-accent-3 data-[state=selected]:hover:bg-accent-4", "has-[[data-slot=row-link]:focus-visible]:outline-2 has-[[data-slot=row-link]:focus-visible]:outline-accent-9 has-[[data-slot=row-link]:focus-visible]:-outline-offset-2", t),
38
+ className: e("group/row border-b border-surface-border-subtle transition-colors hover:bg-surface-raised-hover data-[state=selected]:bg-accent-3 data-[state=selected]:hover:bg-accent-4 data-[state=selected]:forced-colors:outline data-[state=selected]:forced-colors:outline-1", "has-[[data-slot=row-link]:focus-visible]:outline-2 has-[[data-slot=row-link]:focus-visible]:outline-accent-9 has-[[data-slot=row-link]:focus-visible]:-outline-offset-2", t),
39
39
  ...r
40
40
  }));
41
41
  c.displayName = "TableRow";
@@ -1 +1 @@
1
- {"version":3,"file":"table.js","names":[],"sources":["../../src/ui/table.tsx"],"sourcesContent":["// @server-safe\nimport * as React from \"react\"\n\nimport { cn } from \"./lib/utils\"\n\n/** Row density — sets --table-py, which header and body cells both read. */\ntype TableDensity = 'compact' | 'standard' | 'comfortable'\n\n// One variable pair drives the table's spacing (same pattern as Card):\n// --table-py vertical cell padding per density (4 / 8 / 12px → rows ≈ 29 / 37 / 45px)\n// --table-edge first/last-cell inline padding — inherits --card-spacing when the table\n// sits inside a Card, so edge columns align with the card's slots;\n// falls back to ds-04 (12px) standalone.\nconst densityClasses: Record<TableDensity, string> = {\n compact: '[--table-py:var(--spacing-ds-02)]',\n standard: '[--table-py:var(--spacing-ds-03)]',\n comfortable: '[--table-py:var(--spacing-ds-04)]',\n}\n\nexport interface TableCellBaseProps {\n /**\n * Quantitative column: right-aligns and uses tabular figures so digits line up.\n * Keep decimal places consistent per column; identifier-numbers (dates, phones,\n * IDs) stay left-aligned — they're names, not quantities.\n */\n numeric?: boolean\n}\n\nexport interface TableProps extends React.HTMLAttributes<HTMLTableElement> {\n /** Row density — vertical cell padding for header + body. @default 'standard' */\n density?: TableDensity\n /**\n * Zebra striping (even body rows get the faintest surface step). Opt-in only —\n * for very wide/dense tables; hairline separators are the default row cue.\n */\n striped?: boolean\n}\n\nconst Table = React.forwardRef<HTMLTableElement, TableProps>(\n ({ className, density = 'standard', striped, ...props }, ref) => (\n <div className=\"relative w-full overflow-auto\">\n <table\n ref={ref}\n className={cn(\n // overflow-x-clip contains TableRowLink's 100vw stretch pseudo-element\n // without creating a horizontal scrollbar (the wrapper owns scrolling).\n \"w-full caption-bottom text-body-md overflow-x-clip [--table-edge:var(--card-spacing,var(--spacing-ds-04))]\",\n densityClasses[density],\n striped && \"[&_tbody_tr:nth-child(even)]:bg-surface-base\",\n className,\n )}\n {...props}\n />\n </div>\n ),\n)\nTable.displayName = \"Table\"\n\nconst TableHeader = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <thead ref={ref} className={cn(\"[&_tr]:border-b [&_tr]:border-surface-border-subtle\", className)} {...props} />\n))\nTableHeader.displayName = \"TableHeader\"\n\nconst TableBody = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tbody\n ref={ref}\n className={cn(\"[&_tr:last-child]:border-0\", className)}\n {...props}\n />\n))\nTableBody.displayName = \"TableBody\"\n\nconst TableFooter = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tfoot\n ref={ref}\n className={cn(\n // surface-base band, not raised@50% — the footer must read against the\n // card surface the table lives on (same mis-mapped shadcn muted/50 family\n // as the row-hover bug).\n \"border-t border-surface-border-subtle bg-surface-base font-medium [&>tr]:last:border-b-0\",\n className\n )}\n {...props}\n />\n))\nTableFooter.displayName = \"TableFooter\"\n\nconst TableRow = React.forwardRef<\n HTMLTableRowElement,\n React.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n <tr\n ref={ref}\n className={cn(\n // raised-hover, NOT raised — tables live on cards (surface-raised), so a\n // surface-raised hover would be invisible (the 0.44-era port bug).\n // selected+hover gets its own explicit step — without it the hover and\n // selected classes tie on specificity and stylesheet order decides.\n // `group/row` lets TableRowActions reveal on row hover/focus; the has-[]\n // rule draws a row-level focus ring when a TableRowLink inside is\n // keyboard-focused (the anchor itself suppresses its own ring).\n \"group/row border-b border-surface-border-subtle transition-colors hover:bg-surface-raised-hover data-[state=selected]:bg-accent-3 data-[state=selected]:hover:bg-accent-4\",\n \"has-[[data-slot=row-link]:focus-visible]:outline-2 has-[[data-slot=row-link]:focus-visible]:outline-accent-9 has-[[data-slot=row-link]:focus-visible]:-outline-offset-2\",\n className,\n )}\n {...props}\n />\n))\nTableRow.displayName = \"TableRow\"\n\nconst TableHead = React.forwardRef<\n HTMLTableCellElement,\n React.ThHTMLAttributes<HTMLTableCellElement> & TableCellBaseProps\n>(({ className, numeric, ...props }, ref) => (\n <th\n ref={ref}\n scope=\"col\"\n className={cn(\n // Header is quieter than the data: one step smaller, medium, muted.\n // Height tracks density via the same --table-py the body cells read.\n \"py-(--table-py) px-ds-04 first:pl-(--table-edge) last:pr-(--table-edge) text-left align-middle text-body-sm font-medium text-surface-fg-muted [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n numeric && \"text-right\",\n className,\n )}\n {...props}\n />\n))\nTableHead.displayName = \"TableHead\"\n\nconst TableCell = React.forwardRef<\n HTMLTableCellElement,\n React.TdHTMLAttributes<HTMLTableCellElement> & TableCellBaseProps\n>(({ className, numeric, ...props }, ref) => (\n <td\n ref={ref}\n className={cn(\n \"py-(--table-py) px-ds-04 first:pl-(--table-edge) last:pr-(--table-edge) align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n numeric && \"text-right tabular-nums\",\n className,\n )}\n {...props}\n />\n))\nTableCell.displayName = \"TableCell\"\n\nexport interface TableRowActionsProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Always show the actions instead of revealing on row hover/focus. Use for\n * tables where actions must be permanently discoverable (the GitLab stance),\n * or when the row has few columns and the density win doesn't matter.\n */\n persist?: boolean\n}\n\n/**\n * Right-aligned action cluster for a table row, revealed on row hover — and,\n * critically, on keyboard focus: the buttons stay in the tab order permanently\n * (opacity reveal, never display:none) and appear the moment focus enters the\n * row (WCAG 1.4.13). On touch devices (no hover) they are always visible.\n *\n * Give the actions column a visually-hidden header: `<TableHead><span className=\"sr-only\">Actions</span></TableHead>`.\n *\n * @example\n * <TableCell>\n * <TableRowActions>\n * <IconButton size=\"xs\" variant=\"ghost\" aria-label={`Download ${name}`} icon={<IconDownload />} />\n * <IconButton size=\"xs\" variant=\"ghost\" aria-label={`Delete ${name}`} icon={<IconTrash />} />\n * </TableRowActions>\n * </TableCell>\n */\nconst TableRowActions = React.forwardRef<HTMLDivElement, TableRowActionsProps>(\n ({ className, persist, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"flex items-center justify-end gap-ds-01 transition-opacity duration-fast-01 ease-productive-standard\",\n persist\n ? \"opacity-100\"\n : \"opacity-0 group-hover/row:opacity-100 group-focus-within/row:opacity-100 pointer-coarse:opacity-100\",\n className,\n )}\n {...props}\n />\n ),\n)\nTableRowActions.displayName = \"TableRowActions\"\n\nconst TableCaption = React.forwardRef<\n HTMLTableCaptionElement,\n React.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n <caption\n ref={ref}\n className={cn(\"mt-ds-05 text-body-md text-surface-fg-muted\", className)}\n {...props}\n />\n))\nTableCaption.displayName = \"TableCaption\"\n\nexport type TableRowProps = React.HTMLAttributes<HTMLTableRowElement>\nexport type TableCellProps = React.TdHTMLAttributes<HTMLTableCellElement>\nexport type { TableDensity }\n\nexport {\n Table,\n TableBody,\n TableCaption,\n TableCell,\n TableFooter,\n TableHead,\n TableHeader,\n TableRow,\n TableRowActions,\n}\n"],"mappings":";;;;AAaA,IAAM,IAA+C;CACnD,SAAS;CACT,UAAU;CACV,aAAa;AACf,GAqBM,IAAQ,EAAM,YACjB,EAAE,cAAW,aAAU,YAAY,YAAS,GAAG,KAAS,MACvD,kBAAC,OAAD;CAAK,WAAU;WACb,kBAAC,SAAD;EACO;EACL,WAAW,EAGT,8GACA,EAAe,IACf,KAAW,gDACX,CACF;EACA,GAAI;CACL,CAAA;AACE,CAAA,CAET;AACA,EAAM,cAAc;AAEpB,IAAM,IAAc,EAAM,YAGvB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,SAAD;CAAY;CAAK,WAAW,EAAG,uDAAuD,CAAS;CAAG,GAAI;AAAQ,CAAA,CAC/G;AACD,EAAY,cAAc;AAE1B,IAAM,IAAY,EAAM,YAGrB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,SAAD;CACO;CACL,WAAW,EAAG,8BAA8B,CAAS;CACrD,GAAI;AACL,CAAA,CACF;AACD,EAAU,cAAc;AAExB,IAAM,IAAc,EAAM,YAGvB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,SAAD;CACO;CACL,WAAW,EAIT,4FACA,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAY,cAAc;AAE1B,IAAM,IAAW,EAAM,YAGpB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,MAAD;CACO;CACL,WAAW,EAQT,6KACA,2KACA,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAS,cAAc;AAEvB,IAAM,IAAY,EAAM,YAGrB,EAAE,cAAW,YAAS,GAAG,KAAS,MACnC,kBAAC,MAAD;CACO;CACL,OAAM;CACN,WAAW,EAGT,qNACA,KAAW,cACX,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAU,cAAc;AAExB,IAAM,IAAY,EAAM,YAGrB,EAAE,cAAW,YAAS,GAAG,KAAS,MACnC,kBAAC,MAAD;CACO;CACL,WAAW,EACT,4JACA,KAAW,2BACX,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAU,cAAc;AA2BxB,IAAM,IAAkB,EAAM,YAC3B,EAAE,cAAW,YAAS,GAAG,KAAS,MACjC,kBAAC,OAAD;CACO;CACL,WAAW,EACT,wGACA,IACI,gBACA,uGACJ,CACF;CACA,GAAI;AACL,CAAA,CAEL;AACA,EAAgB,cAAc;AAE9B,IAAM,IAAe,EAAM,YAGxB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,WAAD;CACO;CACL,WAAW,EAAG,+CAA+C,CAAS;CACtE,GAAI;AACL,CAAA,CACF;AACD,EAAa,cAAc"}
1
+ {"version":3,"file":"table.js","names":[],"sources":["../../src/ui/table.tsx"],"sourcesContent":["// @server-safe\nimport * as React from \"react\"\n\nimport { cn } from \"./lib/utils\"\n\n/** Row density — sets --table-py, which header and body cells both read. */\ntype TableDensity = 'compact' | 'standard' | 'comfortable'\n\n// One variable pair drives the table's spacing (same pattern as Card):\n// --table-py vertical cell padding per density (4 / 8 / 12px → rows ≈ 29 / 37 / 45px)\n// --table-edge first/last-cell inline padding — inherits --card-spacing when the table\n// sits inside a Card, so edge columns align with the card's slots;\n// falls back to ds-04 (12px) standalone.\nconst densityClasses: Record<TableDensity, string> = {\n compact: '[--table-py:var(--spacing-ds-02)]',\n standard: '[--table-py:var(--spacing-ds-03)]',\n comfortable: '[--table-py:var(--spacing-ds-04)]',\n}\n\nexport interface TableCellBaseProps {\n /**\n * Quantitative column: right-aligns and uses tabular figures so digits line up.\n * Keep decimal places consistent per column; identifier-numbers (dates, phones,\n * IDs) stay left-aligned — they're names, not quantities.\n */\n numeric?: boolean\n}\n\nexport interface TableProps extends React.HTMLAttributes<HTMLTableElement> {\n /** Row density — vertical cell padding for header + body. @default 'standard' */\n density?: TableDensity\n /**\n * Zebra striping (even body rows get the faintest surface step). Opt-in only —\n * for very wide/dense tables; hairline separators are the default row cue.\n */\n striped?: boolean\n}\n\nconst Table = React.forwardRef<HTMLTableElement, TableProps>(\n ({ className, density = 'standard', striped, ...props }, ref) => (\n <div className=\"relative w-full overflow-auto\">\n <table\n ref={ref}\n className={cn(\n // overflow-x-clip contains TableRowLink's 100vw stretch pseudo-element\n // without creating a horizontal scrollbar (the wrapper owns scrolling).\n \"w-full caption-bottom text-body-md overflow-x-clip [--table-edge:var(--card-spacing,var(--spacing-ds-04))]\",\n densityClasses[density],\n striped && \"[&_tbody_tr:nth-child(even)]:bg-surface-base\",\n className,\n )}\n {...props}\n />\n </div>\n ),\n)\nTable.displayName = \"Table\"\n\nconst TableHeader = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <thead ref={ref} className={cn(\"[&_tr]:border-b [&_tr]:border-surface-border-subtle\", className)} {...props} />\n))\nTableHeader.displayName = \"TableHeader\"\n\nconst TableBody = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tbody\n ref={ref}\n className={cn(\"[&_tr:last-child]:border-0\", className)}\n {...props}\n />\n))\nTableBody.displayName = \"TableBody\"\n\nconst TableFooter = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tfoot\n ref={ref}\n className={cn(\n // surface-base band, not raised@50% — the footer must read against the\n // card surface the table lives on (same mis-mapped shadcn muted/50 family\n // as the row-hover bug).\n \"border-t border-surface-border-subtle bg-surface-base font-medium [&>tr]:last:border-b-0\",\n className\n )}\n {...props}\n />\n))\nTableFooter.displayName = \"TableFooter\"\n\nconst TableRow = React.forwardRef<\n HTMLTableRowElement,\n React.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n <tr\n ref={ref}\n className={cn(\n // raised-hover, NOT raised — tables live on cards (surface-raised), so a\n // surface-raised hover would be invisible (the 0.44-era port bug).\n // selected+hover gets its own explicit step — without it the hover and\n // selected classes tie on specificity and stylesheet order decides.\n // `group/row` lets TableRowActions reveal on row hover/focus; the has-[]\n // rule draws a row-level focus ring when a TableRowLink inside is\n // keyboard-focused (the anchor itself suppresses its own ring).\n \"group/row border-b border-surface-border-subtle transition-colors hover:bg-surface-raised-hover data-[state=selected]:bg-accent-3 data-[state=selected]:hover:bg-accent-4 data-[state=selected]:forced-colors:outline data-[state=selected]:forced-colors:outline-1\",\n \"has-[[data-slot=row-link]:focus-visible]:outline-2 has-[[data-slot=row-link]:focus-visible]:outline-accent-9 has-[[data-slot=row-link]:focus-visible]:-outline-offset-2\",\n className,\n )}\n {...props}\n />\n))\nTableRow.displayName = \"TableRow\"\n\nconst TableHead = React.forwardRef<\n HTMLTableCellElement,\n React.ThHTMLAttributes<HTMLTableCellElement> & TableCellBaseProps\n>(({ className, numeric, ...props }, ref) => (\n <th\n ref={ref}\n scope=\"col\"\n className={cn(\n // Header is quieter than the data: one step smaller, medium, muted.\n // Height tracks density via the same --table-py the body cells read.\n \"py-(--table-py) px-ds-04 first:pl-(--table-edge) last:pr-(--table-edge) text-left align-middle text-body-sm font-medium text-surface-fg-muted [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n numeric && \"text-right\",\n className,\n )}\n {...props}\n />\n))\nTableHead.displayName = \"TableHead\"\n\nconst TableCell = React.forwardRef<\n HTMLTableCellElement,\n React.TdHTMLAttributes<HTMLTableCellElement> & TableCellBaseProps\n>(({ className, numeric, ...props }, ref) => (\n <td\n ref={ref}\n className={cn(\n \"py-(--table-py) px-ds-04 first:pl-(--table-edge) last:pr-(--table-edge) align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n numeric && \"text-right tabular-nums\",\n className,\n )}\n {...props}\n />\n))\nTableCell.displayName = \"TableCell\"\n\nexport interface TableRowActionsProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Always show the actions instead of revealing on row hover/focus. Use for\n * tables where actions must be permanently discoverable (the GitLab stance),\n * or when the row has few columns and the density win doesn't matter.\n */\n persist?: boolean\n}\n\n/**\n * Right-aligned action cluster for a table row, revealed on row hover — and,\n * critically, on keyboard focus: the buttons stay in the tab order permanently\n * (opacity reveal, never display:none) and appear the moment focus enters the\n * row (WCAG 1.4.13). On touch devices (no hover) they are always visible.\n *\n * Give the actions column a visually-hidden header: `<TableHead><span className=\"sr-only\">Actions</span></TableHead>`.\n *\n * @example\n * <TableCell>\n * <TableRowActions>\n * <IconButton size=\"xs\" variant=\"ghost\" aria-label={`Download ${name}`} icon={<IconDownload />} />\n * <IconButton size=\"xs\" variant=\"ghost\" aria-label={`Delete ${name}`} icon={<IconTrash />} />\n * </TableRowActions>\n * </TableCell>\n */\nconst TableRowActions = React.forwardRef<HTMLDivElement, TableRowActionsProps>(\n ({ className, persist, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"flex items-center justify-end gap-ds-01 transition-opacity duration-fast-01 ease-productive-standard\",\n persist\n ? \"opacity-100\"\n : \"opacity-0 group-hover/row:opacity-100 group-focus-within/row:opacity-100 pointer-coarse:opacity-100\",\n className,\n )}\n {...props}\n />\n ),\n)\nTableRowActions.displayName = \"TableRowActions\"\n\nconst TableCaption = React.forwardRef<\n HTMLTableCaptionElement,\n React.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n <caption\n ref={ref}\n className={cn(\"mt-ds-05 text-body-md text-surface-fg-muted\", className)}\n {...props}\n />\n))\nTableCaption.displayName = \"TableCaption\"\n\nexport type TableRowProps = React.HTMLAttributes<HTMLTableRowElement>\nexport type TableCellProps = React.TdHTMLAttributes<HTMLTableCellElement>\nexport type { TableDensity }\n\nexport {\n Table,\n TableBody,\n TableCaption,\n TableCell,\n TableFooter,\n TableHead,\n TableHeader,\n TableRow,\n TableRowActions,\n}\n"],"mappings":";;;;AAaA,IAAM,IAA+C;CACnD,SAAS;CACT,UAAU;CACV,aAAa;AACf,GAqBM,IAAQ,EAAM,YACjB,EAAE,cAAW,aAAU,YAAY,YAAS,GAAG,KAAS,MACvD,kBAAC,OAAD;CAAK,WAAU;WACb,kBAAC,SAAD;EACO;EACL,WAAW,EAGT,8GACA,EAAe,IACf,KAAW,gDACX,CACF;EACA,GAAI;CACL,CAAA;AACE,CAAA,CAET;AACA,EAAM,cAAc;AAEpB,IAAM,IAAc,EAAM,YAGvB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,SAAD;CAAY;CAAK,WAAW,EAAG,uDAAuD,CAAS;CAAG,GAAI;AAAQ,CAAA,CAC/G;AACD,EAAY,cAAc;AAE1B,IAAM,IAAY,EAAM,YAGrB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,SAAD;CACO;CACL,WAAW,EAAG,8BAA8B,CAAS;CACrD,GAAI;AACL,CAAA,CACF;AACD,EAAU,cAAc;AAExB,IAAM,IAAc,EAAM,YAGvB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,SAAD;CACO;CACL,WAAW,EAIT,4FACA,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAY,cAAc;AAE1B,IAAM,IAAW,EAAM,YAGpB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,MAAD;CACO;CACL,WAAW,EAQT,uQACA,2KACA,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAS,cAAc;AAEvB,IAAM,IAAY,EAAM,YAGrB,EAAE,cAAW,YAAS,GAAG,KAAS,MACnC,kBAAC,MAAD;CACO;CACL,OAAM;CACN,WAAW,EAGT,qNACA,KAAW,cACX,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAU,cAAc;AAExB,IAAM,IAAY,EAAM,YAGrB,EAAE,cAAW,YAAS,GAAG,KAAS,MACnC,kBAAC,MAAD;CACO;CACL,WAAW,EACT,4JACA,KAAW,2BACX,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAU,cAAc;AA2BxB,IAAM,IAAkB,EAAM,YAC3B,EAAE,cAAW,YAAS,GAAG,KAAS,MACjC,kBAAC,OAAD;CACO;CACL,WAAW,EACT,wGACA,IACI,gBACA,uGACJ,CACF;CACA,GAAI;AACL,CAAA,CAEL;AACA,EAAgB,cAAc;AAE9B,IAAM,IAAe,EAAM,YAGxB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,WAAD;CACO;CACL,WAAW,EAAG,+CAA+C,CAAS;CACtE,GAAI;AACL,CAAA,CACF;AACD,EAAa,cAAc"}
@@ -9,14 +9,20 @@
9
9
  count: number (number of selected items — displayed in badge)
10
10
  onClearSelection: () => void
11
11
  actions: BulkActionBarAction[]
12
+ totalCount: number (optional — total selectable items; enables "Select all")
13
+ onSelectAll: () => void (optional — called by the "Select all" control)
12
14
  className: string
15
+ ...div attributes (forwardRef to the toolbar div; HTMLAttributes spread)
13
16
 
14
17
  ### BulkActionBarAction
15
18
  label: string
16
- icon: ComponentType<{ className?: string }> (optional icon component)
19
+ icon: IconInput (optional any icon component or element)
17
20
  onClick: () => void
18
- color: "default" | "error"
19
- disabled: boolean
21
+ color: "accent" | "error" | "success" | "warning" | "info" | "neutral" (default: "accent")
22
+ disabled: boolean (optional)
23
+ loading: boolean (optional — pending spinner on the action)
24
+ requiresConfirmation: boolean (optional — inline confirm before executing)
25
+ confirmMessage: string (optional — default "Are you sure?")
20
26
 
21
27
  ## Defaults
22
28
  (no optional props with defaults)
@@ -5,11 +5,27 @@
5
5
  - Category: composed
6
6
 
7
7
  ## Props
8
+
9
+ ### ErrorDisplay (the rendered UI — forwardRef<HTMLDivElement>, spreads div attrs)
8
10
  error: unknown (REQUIRED — Error object, status object, or string)
9
- onReset: () => void (optional retry button)
11
+ onReset: () => void (optional renders a "Try Again" button)
12
+ actions: ReactNode (optional — custom recovery actions; replaces the default button)
13
+ fullPage: boolean (default: true — center in a min-h-[60vh] region; false = inline)
14
+ autoFocusReset: boolean (default: false — focus the recovery button on mount)
15
+
16
+ Note: the raw `error.message` is shown ONLY in development. In production, ErrorDisplay
17
+ shows the friendly status-mapped copy (404/403/500/default); the real message stays in
18
+ the dev-only stack-trace block.
19
+
20
+ ### ErrorBoundary (class boundary — wrap a subtree)
21
+ children: ReactNode (REQUIRED)
22
+ onReset: () => void (optional — called after the boundary resets)
23
+ onError: (error: unknown, info: React.ErrorInfo) => void (optional — wire Sentry/logging)
24
+ resetKeys: unknown[] (optional — auto-reset when any value changes; react-error-boundary parity)
25
+ fallback: (props: { error: unknown; onReset: () => void }) => ReactNode (optional — defaults to ErrorDisplay)
10
26
 
11
27
  ## Defaults
12
- None
28
+ fullPage: true
13
29
 
14
30
  ## Example
15
31
  ```jsx
@@ -10,13 +10,23 @@ MasterDetail (root), MasterDetail.List, MasterDetail.Detail, MasterDetail.ListIt
10
10
  ## Props
11
11
 
12
12
  ### MasterDetail (root)
13
- selected: string | null (ID of currently selected item; null = show list on mobile)
13
+ selected: string | null (CONTROLLED selected item id; omit for uncontrolled)
14
+ defaultSelected: string | null (uncontrolled initial selection)
15
+ onSelect: (value: string) => void (called with a row's `value` when chosen)
16
+ label: string (accessible name for the listbox; default "Items")
14
17
  onBack: () => void (called when mobile back button is pressed)
15
18
  masterWidth: string (master panel width on desktop)
16
19
  breakpoint: "sm" | "md" | "lg" (below this, stacked mobile mode activates)
20
+ emptyState: ReactNode (shown in the detail pane when nothing is selected)
21
+ onNavigate: (direction: "up" | "down") => void
22
+
23
+ Selection can be controlled (`selected`) or owned by the component
24
+ (`defaultSelected` + `onSelect`). With `value` on each ListItem, `active` /
25
+ `aria-selected` derive automatically — no need to hand-wire `active` + `onClick`.
17
26
 
18
27
  ### MasterDetail.ListItem
19
- active: boolean (highlights the item)
28
+ value: string (row id — enables derived selection + auto-fires onSelect on activate)
29
+ active: boolean (explicit highlight; omit to derive from value === selected)
20
30
  (extends ButtonHTMLAttributes)
21
31
 
22
32
  ## Defaults
@@ -22,12 +22,12 @@ Exports: DashboardSkeleton, ProjectListSkeleton, TaskDetailSkeleton
22
22
  ## Composability
23
23
  - **Full-page skeleton layouts** for route-level loading states. Each mimics a common page shape (dashboard tiles, project list with filters, task detail with sidebar).
24
24
  - **Server-safe** — use in Next.js app router `loading.tsx` files for instant route-transition feedback while data streams.
25
- - **Built on LoadingSkeleton + ui/Skeleton** — these just assemble the pre-built regional skeletons into page-shaped layouts.
25
+ - **Assembled from `ui/Skeleton`** — these compose the base `Skeleton` primitive directly into page-shaped layouts (they do NOT wrap the `LoadingSkeleton` regional exports). Each root is a `role="status"` / `aria-busy` region with an sr-only label.
26
26
  - **When to use which skeleton tier:**
27
27
  - `<Skeleton>` (ui) — single shape for a single element
28
28
  - `<CardSkeleton>` / `<TableSkeleton>` (LoadingSkeleton) — individual region shape
29
29
  - `<DashboardSkeleton>` / etc. (PageSkeletons) — full page placeholder
30
- - **Fixed layout structure** — the className prop adjusts the outer container, but internal layout isn't customizable. For custom page skeletons, compose LoadingSkeleton pieces yourself.
30
+ - **Fixed layout structure** — the className prop adjusts the outer container, but internal layout isn't customizable. For custom page skeletons, compose `ui/Skeleton` (or the `LoadingSkeleton` regional exports) yourself.
31
31
 
32
32
  ## Gotchas
33
33
  - Server-safe: can be imported directly in Next.js Server Components