@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.
- package/AGENTS.md +7 -1
- package/BREAKING.json +29 -0
- package/MIGRATION.md +55 -0
- package/dist/composed/avatar-group.d.ts +11 -9
- package/dist/composed/avatar-group.d.ts.map +1 -1
- package/dist/composed/avatar-group.js +108 -140
- package/dist/composed/avatar-group.js.map +1 -1
- package/dist/composed/bulk-action-bar.d.ts +19 -11
- package/dist/composed/bulk-action-bar.d.ts.map +1 -1
- package/dist/composed/bulk-action-bar.js +81 -110
- package/dist/composed/bulk-action-bar.js.map +1 -1
- package/dist/composed/error-boundary.d.ts +15 -3
- package/dist/composed/error-boundary.d.ts.map +1 -1
- package/dist/composed/error-boundary.js +43 -26
- package/dist/composed/error-boundary.js.map +1 -1
- package/dist/composed/loading-skeleton.d.ts +2 -0
- package/dist/composed/loading-skeleton.d.ts.map +1 -1
- package/dist/composed/loading-skeleton.js +79 -56
- package/dist/composed/loading-skeleton.js.map +1 -1
- package/dist/composed/master-detail.d.ts +21 -8
- package/dist/composed/master-detail.d.ts.map +1 -1
- package/dist/composed/master-detail.js +91 -77
- package/dist/composed/master-detail.js.map +1 -1
- package/dist/composed/page-skeletons.d.ts +6 -0
- package/dist/composed/page-skeletons.d.ts.map +1 -1
- package/dist/composed/page-skeletons.js +93 -71
- package/dist/composed/page-skeletons.js.map +1 -1
- package/dist/shell/index.d.ts +5 -3
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +2 -3
- package/dist/shell/notification-preferences.d.ts.map +1 -1
- package/dist/shell/notification-preferences.js +2 -0
- package/dist/shell/notification-preferences.js.map +1 -1
- package/dist/ui/combobox.d.ts +1 -1
- package/dist/ui/combobox.d.ts.map +1 -1
- package/dist/ui/combobox.js +133 -128
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/file-upload.d.ts.map +1 -1
- package/dist/ui/file-upload.js +124 -138
- package/dist/ui/file-upload.js.map +1 -1
- package/dist/ui/form.d.ts +7 -0
- package/dist/ui/form.d.ts.map +1 -1
- package/dist/ui/form.js +4 -2
- package/dist/ui/form.js.map +1 -1
- package/dist/ui/icon-button.d.ts +2 -1
- package/dist/ui/icon-button.d.ts.map +1 -1
- package/dist/ui/icon-button.js +1 -1
- package/dist/ui/icon-button.js.map +1 -1
- package/dist/ui/label.d.ts.map +1 -1
- package/dist/ui/label.js +7 -6
- package/dist/ui/label.js.map +1 -1
- package/dist/ui/spinner.d.ts.map +1 -1
- package/dist/ui/spinner.js +50 -44
- package/dist/ui/spinner.js.map +1 -1
- package/dist/ui/switch.d.ts.map +1 -1
- package/dist/ui/switch.js +24 -24
- package/dist/ui/switch.js.map +1 -1
- package/dist/ui/table.js +1 -1
- package/dist/ui/table.js.map +1 -1
- package/docs/components/composed/bulk-action-bar.md +9 -3
- package/docs/components/composed/error-boundary.md +18 -2
- package/docs/components/composed/master-detail.md +12 -2
- package/docs/components/composed/page-skeletons.md +2 -2
- package/docs/components/composed/rich-chat-input.md +18 -6
- package/docs/components/composed/simple-tooltip.md +1 -1
- package/docs/components/shell/app-command-palette.md +2 -1
- package/docs/components/shell/bottom-navbar.md +4 -4
- package/docs/components/shell/command-registry.md +2 -2
- package/docs/components/shell/link-context.md +2 -2
- package/docs/components/shell/top-bar.md +1 -1
- package/docs/components/ui/search-input.md +2 -2
- package/docs/components/ui/slider.md +2 -2
- package/docs/components/ui/split-button.md +3 -3
- package/llms.txt +2 -2
- package/make-kit/Guidelines.md +1 -1
- package/make-kit/components/overview.md +2 -2
- package/make-kit/foundations/surfaces.md +1 -1
- package/mcp-manifest.json +403 -147
- package/package.json +1 -6
- package/skill/SKILL.md +1 -1
- package/skill/chat-skill/README.md +18 -0
- package/skill/chat-skill/SKILL.md +78 -0
- package/skill/chat-skill/chatgpt-instructions.md +26 -0
- package/skill/chat-skill/gemini-instructions.md +25 -0
- package/skill/references/components.md +2 -2
- package/dist/shell/sidebar.d.ts +0 -113
- package/dist/shell/sidebar.d.ts.map +0 -1
- package/dist/shell/sidebar.js +0 -293
- package/dist/shell/sidebar.js.map +0 -1
- package/docs/components/shell/sidebar.md +0 -103
package/dist/ui/label.js.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/ui/spinner.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/ui/spinner.js
CHANGED
|
@@ -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
|
-
|
|
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])
|
|
37
|
-
let O =
|
|
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 ? !(
|
|
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:
|
|
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:
|
|
66
|
+
strokeWidth: k,
|
|
61
67
|
fill: "none",
|
|
62
|
-
animate: { opacity:
|
|
68
|
+
animate: { opacity: M && !N ? 0 : 1 },
|
|
63
69
|
transition: {
|
|
64
70
|
duration: .2,
|
|
65
|
-
delay:
|
|
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:
|
|
73
|
-
strokeWidth:
|
|
78
|
+
stroke: P,
|
|
79
|
+
strokeWidth: k,
|
|
74
80
|
fill: "none",
|
|
75
81
|
strokeLinecap: "round",
|
|
76
|
-
strokeDasharray:
|
|
82
|
+
strokeDasharray: j ? `${d * .75} ${d * .25}` : `${d} 0`,
|
|
77
83
|
style: {
|
|
78
84
|
transformOrigin: "center",
|
|
79
|
-
transform:
|
|
80
|
-
opacity:
|
|
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:
|
|
87
|
-
strokeWidth:
|
|
92
|
+
stroke: P,
|
|
93
|
+
strokeWidth: k,
|
|
88
94
|
fill: "none",
|
|
89
95
|
strokeLinecap: "round",
|
|
90
96
|
style: { transformOrigin: "center" },
|
|
91
|
-
animate:
|
|
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
|
-
...!
|
|
107
|
+
...!N && { opacity: 0 }
|
|
102
108
|
},
|
|
103
|
-
transition:
|
|
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:
|
|
126
|
+
duration: R,
|
|
121
127
|
ease: "easeInOut"
|
|
122
128
|
},
|
|
123
|
-
...!
|
|
129
|
+
...!N && { opacity: {
|
|
124
130
|
duration: .2,
|
|
125
|
-
delay:
|
|
131
|
+
delay: R
|
|
126
132
|
} }
|
|
127
133
|
}
|
|
128
134
|
}),
|
|
129
|
-
/* @__PURE__ */ n(i, { children:
|
|
135
|
+
/* @__PURE__ */ n(i, { children: M && N && (T ? /* @__PURE__ */ n("circle", {
|
|
130
136
|
cx: "12",
|
|
131
137
|
cy: "12",
|
|
132
|
-
r: u +
|
|
133
|
-
fill:
|
|
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 +
|
|
139
|
-
fill:
|
|
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:
|
|
152
|
-
stroke:
|
|
153
|
-
strokeWidth:
|
|
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:
|
|
159
|
-
stroke:
|
|
160
|
-
strokeWidth:
|
|
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:
|
|
180
|
+
duration: B,
|
|
175
181
|
ease: "easeOut",
|
|
176
|
-
delay:
|
|
182
|
+
delay: z
|
|
177
183
|
},
|
|
178
184
|
opacity: {
|
|
179
185
|
duration: .1,
|
|
180
|
-
delay:
|
|
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:
|
|
187
|
-
stroke:
|
|
188
|
-
strokeWidth:
|
|
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:
|
|
193
|
-
stroke:
|
|
194
|
-
strokeWidth:
|
|
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:
|
|
213
|
+
duration: B,
|
|
208
214
|
ease: "easeOut",
|
|
209
|
-
delay:
|
|
215
|
+
delay: z
|
|
210
216
|
},
|
|
211
217
|
opacity: {
|
|
212
218
|
duration: .1,
|
|
213
|
-
delay:
|
|
219
|
+
delay: z
|
|
214
220
|
}
|
|
215
221
|
},
|
|
216
222
|
onAnimationComplete: () => S?.()
|
package/dist/ui/spinner.js.map
CHANGED
|
@@ -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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"}
|
package/dist/ui/switch.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
36
|
-
let
|
|
37
|
-
|
|
38
|
-
}, [
|
|
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",
|
|
41
|
-
checked:
|
|
42
|
-
defaultChecked:
|
|
43
|
-
onCheckedChange:
|
|
44
|
-
"aria-invalid":
|
|
45
|
-
"aria-describedby":
|
|
46
|
-
"aria-required":
|
|
47
|
-
...
|
|
48
|
-
ref:
|
|
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",
|
|
53
|
-
animate: { x:
|
|
54
|
-
whileTap: { scale: .85 },
|
|
55
|
-
transition: n.snappy,
|
|
56
|
-
children:
|
|
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
|
-
|
|
61
|
+
p.displayName = e.displayName;
|
|
62
62
|
//#endregion
|
|
63
|
-
export {
|
|
63
|
+
export { p as Switch };
|
package/dist/ui/switch.js.map
CHANGED
|
@@ -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,
|
|
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";
|
package/dist/ui/table.js.map
CHANGED
|
@@ -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,
|
|
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:
|
|
19
|
+
icon: IconInput (optional — any icon component or element)
|
|
17
20
|
onClick: () => void
|
|
18
|
-
color: "
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
- **
|
|
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
|
|
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
|