@devalok/shilp-sutra 0.51.0 → 0.53.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 +1 -1
- package/MIGRATION.md +12 -0
- package/dist/_chunks/motion-provider.js +7 -6
- package/dist/_chunks/motion-provider.js.map +1 -1
- package/dist/_chunks/success.js +53 -53
- package/dist/ai/command-bar.js +158 -158
- package/dist/ai/command-bar.js.map +1 -1
- package/dist/ai/conversation.js +5 -5
- package/dist/composed/command-palette.js +17 -17
- package/dist/composed/command-palette.js.map +1 -1
- package/dist/composed/priority-indicator.d.ts +15 -6
- package/dist/composed/priority-indicator.d.ts.map +1 -1
- package/dist/composed/priority-indicator.js +37 -88
- package/dist/composed/priority-indicator.js.map +1 -1
- package/dist/composed/schedule-view.d.ts +17 -2
- package/dist/composed/schedule-view.d.ts.map +1 -1
- package/dist/composed/schedule-view.js +163 -64
- package/dist/composed/schedule-view.js.map +1 -1
- package/dist/motion/motion-provider.d.ts.map +1 -1
- package/dist/shell/bottom-navbar.d.ts +32 -6
- package/dist/shell/bottom-navbar.d.ts.map +1 -1
- package/dist/shell/bottom-navbar.js +156 -129
- package/dist/shell/bottom-navbar.js.map +1 -1
- package/dist/tokens/primitives.css +6 -2
- package/dist/tokens/semantic.css +20 -1
- package/dist/tokens/utilities.css +6 -0
- package/dist/ui/autocomplete.d.ts +34 -32
- package/dist/ui/autocomplete.d.ts.map +1 -1
- package/dist/ui/autocomplete.js +115 -110
- package/dist/ui/autocomplete.js.map +1 -1
- package/dist/ui/button-group.js +2 -0
- package/dist/ui/button-group.js.map +1 -1
- package/dist/ui/button-processing.d.ts.map +1 -1
- package/dist/ui/button-processing.js +1 -0
- package/dist/ui/button-processing.js.map +1 -1
- package/dist/ui/button.d.ts +3 -3
- package/dist/ui/button.d.ts.map +1 -1
- package/dist/ui/button.js +26 -0
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/color-input.d.ts.map +1 -1
- package/dist/ui/color-input.js +82 -82
- package/dist/ui/color-input.js.map +1 -1
- package/dist/ui/combobox.js +4 -4
- package/dist/ui/icon.d.ts +2 -0
- package/dist/ui/icon.d.ts.map +1 -1
- package/dist/ui/icon.js +31 -26
- package/dist/ui/icon.js.map +1 -1
- package/dist/ui/index.js +30 -30
- package/dist/ui/search-input.d.ts.map +1 -1
- package/dist/ui/search-input.js +5 -4
- package/dist/ui/search-input.js.map +1 -1
- package/dist/ui/segmented-control.d.ts +28 -6
- package/dist/ui/segmented-control.d.ts.map +1 -1
- package/dist/ui/segmented-control.js +61 -43
- package/dist/ui/segmented-control.js.map +1 -1
- package/dist/ui/sidebar.js +7 -7
- package/dist/ui/split-button.d.ts.map +1 -1
- package/dist/ui/split-button.js +7 -0
- package/dist/ui/split-button.js.map +1 -1
- package/docs/components/composed/priority-indicator.md +22 -11
- package/docs/components/composed/schedule-view.md +20 -5
- package/docs/components/shell/bottom-navbar.md +24 -5
- package/docs/components/ui/autocomplete.md +26 -10
- package/docs/components/ui/button.md +2 -2
- package/docs/components/ui/segmented-control.md +31 -11
- package/docs/recipes/install-remix.md +3 -3
- package/docs/recipes/install-vite.md +3 -3
- package/docs/recipes/server-components.md +2 -2
- package/llms.txt +1 -1
- package/make-kit/foundations/color.md +20 -0
- package/make-kit/foundations/dark-mode.md +6 -9
- package/make-kit/foundations/icons.md +3 -3
- package/make-kit/setup.md +4 -4
- package/mcp-manifest.json +239 -35
- package/package.json +1 -1
- package/scripts/welcome.mjs +59 -1
- package/skill/SKILL.md +1 -1
- package/skill/references/components.md +1 -1
- package/skill/references/server-components.md +2 -2
- package/skill/references/setup-remix.md +3 -3
- package/skill/references/setup-vite.md +3 -3
|
@@ -2,20 +2,42 @@
|
|
|
2
2
|
import { IconInput } from './lib/icon-input';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
export type SegmentedControlSize = 'sm' | 'md' | 'lg';
|
|
5
|
-
export type SegmentedControlVariant = '
|
|
5
|
+
export type SegmentedControlVariant = 'soft' | 'solid';
|
|
6
|
+
/** @deprecated `variant="default"` was renamed to `"soft"`. Still accepted at
|
|
7
|
+
* runtime (maps to `"soft"`); update call sites — the alias is removed in a
|
|
8
|
+
* future major. */
|
|
9
|
+
type DeprecatedVariant = 'default';
|
|
6
10
|
export interface SegmentedControlOption {
|
|
7
11
|
id: string;
|
|
8
|
-
|
|
12
|
+
/** Visible label. Accepts a `ReactNode` so an option can carry a count badge
|
|
13
|
+
* or custom node, not just a string. Optional — omit for an icon-only
|
|
14
|
+
* segment, in which case set `ariaLabel`. */
|
|
15
|
+
text?: React.ReactNode;
|
|
9
16
|
/** Optional icon rendered before the text label. Accepts any `IconInput`. */
|
|
10
17
|
icon?: IconInput;
|
|
18
|
+
/** Accessible name for the segment. Required for icon-only segments (no
|
|
19
|
+
* `text`); otherwise the visible text provides the name. */
|
|
20
|
+
ariaLabel?: string;
|
|
11
21
|
}
|
|
12
|
-
export interface SegmentedControlProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {
|
|
22
|
+
export interface SegmentedControlProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect' | 'defaultValue'> {
|
|
13
23
|
size?: SegmentedControlSize;
|
|
14
|
-
variant?: SegmentedControlVariant;
|
|
24
|
+
variant?: SegmentedControlVariant | DeprecatedVariant;
|
|
15
25
|
options: SegmentedControlOption[];
|
|
16
|
-
|
|
17
|
-
|
|
26
|
+
/** Selected option id (controlled). Aligns with Tabs/ToggleGroup. */
|
|
27
|
+
value?: string;
|
|
28
|
+
/** Initial selected id for uncontrolled mode. Ignored when `value` is set;
|
|
29
|
+
* defaults to the first option. */
|
|
30
|
+
defaultValue?: string;
|
|
31
|
+
/** Fires with the newly-selected id (both modes). */
|
|
32
|
+
onValueChange?: (id: string) => void;
|
|
18
33
|
disabled?: boolean;
|
|
34
|
+
/** Fill the container: segments split the available width equally instead of
|
|
35
|
+
* hugging their content. Use for full-width toggles and view switchers. */
|
|
36
|
+
fullWidth?: boolean;
|
|
37
|
+
/** @deprecated Use `value`. Still accepted; kept for back-compat. */
|
|
38
|
+
selectedId?: string;
|
|
39
|
+
/** @deprecated Use `onValueChange`. Still accepted; kept for back-compat. */
|
|
40
|
+
onSelect?: (id: string) => void;
|
|
19
41
|
}
|
|
20
42
|
declare const SegmentedControl: React.ForwardRefExoticComponent<SegmentedControlProps & React.RefAttributes<HTMLDivElement>>;
|
|
21
43
|
export { SegmentedControl };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"segmented-control.d.ts","sourceRoot":"","sources":["../../src/ui/segmented-control.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAMjD,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AACrD,MAAM,MAAM,uBAAuB,GAAG,
|
|
1
|
+
{"version":3,"file":"segmented-control.d.ts","sourceRoot":"","sources":["../../src/ui/segmented-control.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAMjD,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AACrD,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,OAAO,CAAA;AAEtD;;oBAEoB;AACpB,KAAK,iBAAiB,GAAG,SAAS,CAAA;AAElC,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV;;kDAE8C;IAC9C,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;iEAC6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,cAAc,CAAC;IACpH,IAAI,CAAC,EAAE,oBAAoB,CAAA;IAC3B,OAAO,CAAC,EAAE,uBAAuB,GAAG,iBAAiB,CAAA;IACrD,OAAO,EAAE,sBAAsB,EAAE,CAAA;IACjC,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;wCACoC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,qDAAqD;IACrD,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;gFAC4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAA;IAEnB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAChC;AA2CD,QAAA,MAAM,gBAAgB,8FAkLrB,CAAA;AAKD,OAAO,EAAE,gBAAgB,EAAE,CAAA"}
|
|
@@ -4,9 +4,19 @@ import { cn as t } from "./lib/utils.js";
|
|
|
4
4
|
import { normalizeIcon as n } from "../_chunks/normalize-icon.js";
|
|
5
5
|
import * as r from "react";
|
|
6
6
|
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
7
|
-
import { LayoutGroup as o, motion as s } from "framer-motion";
|
|
7
|
+
import { LayoutGroup as o, motion as s, useReducedMotion as c } from "framer-motion";
|
|
8
8
|
//#region src/ui/segmented-control.tsx
|
|
9
|
-
|
|
9
|
+
function l(e) {
|
|
10
|
+
if (!e) return !1;
|
|
11
|
+
let t = e.closest("[dir]");
|
|
12
|
+
if (t) return (t.getAttribute("dir") ?? "").toLowerCase() === "rtl";
|
|
13
|
+
try {
|
|
14
|
+
return getComputedStyle(e).direction === "rtl";
|
|
15
|
+
} catch {
|
|
16
|
+
return !1;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
var u = {
|
|
10
20
|
sm: {
|
|
11
21
|
button: "h-7 px-ds-04 text-body-sm",
|
|
12
22
|
icon: "h-3.5 w-3.5"
|
|
@@ -19,76 +29,84 @@ var c = {
|
|
|
19
29
|
button: "h-10 px-ds-06 text-body-md",
|
|
20
30
|
icon: "h-4 w-4"
|
|
21
31
|
}
|
|
22
|
-
}, l = {
|
|
23
|
-
default: "bg-surface-overlay shadow-raised",
|
|
24
|
-
solid: "bg-accent-9"
|
|
25
|
-
}, u = {
|
|
26
|
-
default: "text-surface-fg",
|
|
27
|
-
solid: "text-accent-fg"
|
|
28
32
|
}, d = {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
soft: "bg-segment-thumb shadow-segment",
|
|
34
|
+
solid: "bg-accent-9 shadow-segment"
|
|
35
|
+
}, f = {
|
|
36
|
+
soft: "text-surface-fg",
|
|
37
|
+
solid: "text-accent-fg"
|
|
38
|
+
}, p = r.forwardRef(function({ size: p = "md", variant: m = "soft", options: h, value: g, defaultValue: _, onValueChange: v, selectedId: y, onSelect: b, disabled: x = !1, fullWidth: S = !1, className: C, ...w }, T) {
|
|
39
|
+
let E = r.useId(), D = r.useRef(null), O = c(), k = m === "default" ? "soft" : m, A = g ?? y, j = A !== void 0, [M, N] = r.useState(_ ?? h[0]?.id), P = j ? A : M, F = r.useCallback((e) => {
|
|
40
|
+
j || N(e), (v ?? b)?.(e);
|
|
41
|
+
}, [
|
|
42
|
+
j,
|
|
43
|
+
v,
|
|
44
|
+
b
|
|
45
|
+
]), I = O ? { duration: 0 } : {
|
|
46
|
+
type: "spring",
|
|
47
|
+
duration: .3,
|
|
48
|
+
bounce: 0
|
|
49
|
+
}, L = r.useCallback((e) => {
|
|
50
|
+
D.current = e, typeof T == "function" ? T(e) : T && (T.current = e);
|
|
51
|
+
}, [T]), R = (e) => {
|
|
52
|
+
if (x) return;
|
|
53
|
+
let t = h.findIndex((e) => e.id === P), n = t, r = t > 0 ? t - 1 : h.length - 1, i = t < h.length - 1 ? t + 1 : 0, a = l(D.current);
|
|
38
54
|
switch (e.key) {
|
|
39
55
|
case "ArrowLeft":
|
|
40
|
-
e.preventDefault(), n =
|
|
56
|
+
e.preventDefault(), n = a ? i : r;
|
|
41
57
|
break;
|
|
42
58
|
case "ArrowRight":
|
|
43
|
-
e.preventDefault(), n =
|
|
59
|
+
e.preventDefault(), n = a ? r : i;
|
|
44
60
|
break;
|
|
45
61
|
case "Home":
|
|
46
62
|
e.preventDefault(), n = 0;
|
|
47
63
|
break;
|
|
48
64
|
case "End":
|
|
49
|
-
e.preventDefault(), n =
|
|
65
|
+
e.preventDefault(), n = h.length - 1;
|
|
50
66
|
break;
|
|
51
67
|
default: return;
|
|
52
68
|
}
|
|
53
|
-
|
|
54
|
-
(
|
|
69
|
+
F(h[n].id), requestAnimationFrame(() => {
|
|
70
|
+
(D.current?.querySelectorAll("[role=\"radio\"]"))?.[n]?.focus();
|
|
55
71
|
});
|
|
56
|
-
}, { button:
|
|
72
|
+
}, { button: z, icon: B } = u[p];
|
|
57
73
|
return /* @__PURE__ */ i("div", {
|
|
58
|
-
ref:
|
|
74
|
+
ref: L,
|
|
59
75
|
role: "radiogroup",
|
|
60
76
|
tabIndex: -1,
|
|
61
|
-
"aria-label":
|
|
62
|
-
onKeyDown:
|
|
63
|
-
className: t("
|
|
64
|
-
...
|
|
77
|
+
"aria-label": w["aria-label"] ?? "Segmented control",
|
|
78
|
+
onKeyDown: R,
|
|
79
|
+
className: t("p-ds-01 rounded-surface bg-segment-track", S ? "flex w-full" : "inline-flex w-fit", x && "opacity-action-disabled pointer-events-none", C),
|
|
80
|
+
...w,
|
|
65
81
|
children: /* @__PURE__ */ i(o, {
|
|
66
|
-
id:
|
|
67
|
-
children:
|
|
68
|
-
let o = r.id ===
|
|
82
|
+
id: E,
|
|
83
|
+
children: h.map((r) => {
|
|
84
|
+
let o = r.id === P;
|
|
69
85
|
return /* @__PURE__ */ a("button", {
|
|
70
86
|
type: "button",
|
|
71
87
|
role: "radio",
|
|
72
88
|
"aria-checked": o,
|
|
89
|
+
"aria-label": r.ariaLabel,
|
|
73
90
|
tabIndex: o ? 0 : -1,
|
|
74
|
-
disabled:
|
|
75
|
-
onClick: () =>
|
|
76
|
-
className: t("relative inline-flex items-center justify-center gap-ds-02 rounded-
|
|
91
|
+
disabled: x,
|
|
92
|
+
onClick: () => F(r.id),
|
|
93
|
+
className: t("relative inline-flex items-center justify-center gap-ds-02 rounded-control", "font-medium outline-hidden", "touch-target", "transition-[color,transform] duration-fast-02 ease-productive-standard", "motion-safe:active:scale-[0.97]", "focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent", z, S && "flex-1 min-w-16", o ? f[k] : "text-surface-fg-muted hover:text-surface-fg"),
|
|
77
94
|
children: [
|
|
78
95
|
o && /* @__PURE__ */ i(s.span, {
|
|
79
|
-
layoutId: "segment-
|
|
80
|
-
|
|
81
|
-
|
|
96
|
+
layoutId: "segment-thumb",
|
|
97
|
+
initial: !1,
|
|
98
|
+
className: t("absolute inset-0 rounded-control pointer-events-none", d[k]),
|
|
99
|
+
transition: I
|
|
82
100
|
}),
|
|
83
101
|
r.icon && /* @__PURE__ */ i("span", {
|
|
84
|
-
className: t("relative
|
|
102
|
+
className: t("relative shrink-0 inline-flex items-center justify-center", B),
|
|
85
103
|
children: /* @__PURE__ */ i(e, {
|
|
86
|
-
size:
|
|
104
|
+
size: p === "lg" ? "sm" : "xs",
|
|
87
105
|
children: n(r.icon)
|
|
88
106
|
})
|
|
89
107
|
}),
|
|
90
|
-
/* @__PURE__ */ i("span", {
|
|
91
|
-
className: "relative
|
|
108
|
+
r.text != null && /* @__PURE__ */ i("span", {
|
|
109
|
+
className: "relative",
|
|
92
110
|
children: r.text
|
|
93
111
|
})
|
|
94
112
|
]
|
|
@@ -97,6 +115,6 @@ var c = {
|
|
|
97
115
|
})
|
|
98
116
|
});
|
|
99
117
|
});
|
|
100
|
-
|
|
118
|
+
p.displayName = "SegmentedControl";
|
|
101
119
|
//#endregion
|
|
102
|
-
export {
|
|
120
|
+
export { p as SegmentedControl };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"segmented-control.js","names":[],"sources":["../../src/ui/segmented-control.tsx"],"sourcesContent":["'use client'\n\nimport { LayoutGroup,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { IconProvider } from './icon-context'\nimport type { IconInput } from './lib/icon-input'\nimport { normalizeIcon } from './lib/normalize-icon'\nimport { cn } from './lib/utils'\n\n/* ── Types ─────────────────────────────────────────────────── */\n\nexport type SegmentedControlSize = 'sm' | 'md' | 'lg'\nexport type SegmentedControlVariant = 'default' | 'solid'\n\nexport interface SegmentedControlOption {\n id: string\n text: string\n /** Optional icon rendered before the text label. Accepts any `IconInput`. */\n icon?: IconInput\n}\n\nexport interface SegmentedControlProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n size?: SegmentedControlSize\n variant?: SegmentedControlVariant\n options: SegmentedControlOption[]\n selectedId: string\n onSelect: (id: string) => void\n disabled?: boolean\n}\n\n/* ── Size config ───────────────────────────────────────────── */\n\nconst sizeConfig = {\n sm: { button: 'h-7 px-ds-04 text-body-sm', icon: 'h-3.5 w-3.5' },\n md: { button: 'h-8 px-ds-05 text-body-md', icon: 'h-4 w-4' },\n lg: { button: 'h-10 px-ds-06 text-body-md', icon: 'h-4 w-4' },\n} as const\n\n/* ── Pill styles per variant ───────────────────────────────── */\n\nconst pillStyles = {\n default: 'bg-surface-overlay shadow-raised',\n solid: 'bg-accent-9',\n} as const\n\nconst selectedTextStyles = {\n default: 'text-surface-fg',\n solid: 'text-accent-fg',\n} as const\n\n/* ── Spring config (snappy, minimal overshoot) ─────────────── */\n/* Intentionally softer than springs.snappy (500/30/0.5) for pill slide feel */\n\nconst pillSpring = { type: 'spring' as const, stiffness: 400, damping: 30 }\n\n/* ── SegmentedControl ──────────────────────────────────────── */\n\nconst SegmentedControl = React.forwardRef<HTMLDivElement, SegmentedControlProps>(\n function SegmentedControl(\n {\n size = 'md',\n variant = 'default',\n options,\n selectedId,\n onSelect,\n disabled = false,\n className,\n ...props\n },\n ref,\n ) {\n const instanceId = React.useId()\n const tablistRef = React.useRef<HTMLDivElement | null>(null)\n\n // Compose refs\n const mergedRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n tablistRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node\n },\n [ref],\n )\n\n // Keyboard navigation\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return\n\n const currentIndex = options.findIndex((o) => o.id === selectedId)\n let nextIndex = currentIndex\n\n switch (e.key) {\n case 'ArrowLeft':\n e.preventDefault()\n nextIndex = currentIndex > 0 ? currentIndex - 1 : options.length - 1\n break\n case 'ArrowRight':\n e.preventDefault()\n nextIndex = currentIndex < options.length - 1 ? currentIndex + 1 : 0\n break\n case 'Home':\n e.preventDefault()\n nextIndex = 0\n break\n case 'End':\n e.preventDefault()\n nextIndex = options.length - 1\n break\n default:\n return\n }\n\n onSelect(options[nextIndex].id)\n requestAnimationFrame(() => {\n const buttons = tablistRef.current?.querySelectorAll<HTMLButtonElement>('[role=\"radio\"]')\n buttons?.[nextIndex]?.focus()\n })\n }\n\n const { button: buttonSize, icon: iconSize } = sizeConfig[size]\n\n return (\n <div\n ref={mergedRef}\n role=\"radiogroup\"\n tabIndex={-1}\n aria-label={props['aria-label'] ?? 'Segmented control'}\n onKeyDown={handleKeyDown}\n className={cn(\n 'inline-flex p-[3px] rounded-pill',\n 'bg-surface-raised-hover border border-surface-border-subtle shadow-inset',\n disabled && 'opacity-action-disabled pointer-events-none',\n className,\n )}\n {...props}\n >\n <LayoutGroup id={instanceId}>\n {options.map((option) => {\n const isSelected = option.id === selectedId\n\n return (\n <button\n key={option.id}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n tabIndex={isSelected ? 0 : -1}\n disabled={disabled}\n onClick={() => onSelect(option.id)}\n className={cn(\n 'relative inline-flex items-center justify-center gap-ds-02 rounded-pill',\n 'font-medium transition-colors duration-fast-02 ease-productive-standard',\n 'outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2',\n buttonSize,\n isSelected\n ? selectedTextStyles[variant]\n : 'text-surface-fg-muted hover:text-surface-fg',\n )}\n >\n {/* Sliding pill indicator */}\n {isSelected && (\n <motion.span\n layoutId=\"segment-pill\"\n className={cn(\n 'absolute inset-0 rounded-pill pointer-events-none',\n pillStyles[variant],\n )}\n transition={pillSpring}\n />\n )}\n\n {/* Content (above pill via z-index) */}\n {option.icon && (\n <span className={cn('relative z-[1] shrink-0 inline-flex items-center justify-center', iconSize)}>\n <IconProvider size={size === 'lg' ? 'sm' : 'xs'}>\n {normalizeIcon(option.icon)}\n </IconProvider>\n </span>\n )}\n <span className=\"relative z-[1]\">{option.text}</span>\n </button>\n )\n })}\n </LayoutGroup>\n </div>\n )\n },\n)\nSegmentedControl.displayName = 'SegmentedControl'\n\n/* ── Exports ───────────────────────────────────────────────── */\n\nexport { SegmentedControl }\n"],"mappings":";;;;;;;;AAiCA,IAAM,IAAa;CACjB,IAAI;EAAE,QAAQ;EAA6B,MAAM;CAAc;CAC/D,IAAI;EAAE,QAAQ;EAA6B,MAAM;CAAU;CAC3D,IAAI;EAAE,QAAQ;EAA8B,MAAM;CAAU;AAC9D,GAIM,IAAa;CACjB,SAAS;CACT,OAAO;AACT,GAEM,IAAqB;CACzB,SAAS;CACT,OAAO;AACT,GAKM,IAAa;CAAE,MAAM;CAAmB,WAAW;CAAK,SAAS;AAAG,GAIpE,IAAmB,EAAM,WAC7B,SACE,EACE,UAAO,MACP,aAAU,WACV,YACA,eACA,aACA,cAAW,IACX,cACA,GAAG,KAEL,GACA;CACA,IAAM,IAAa,EAAM,MAAM,GACzB,IAAa,EAAM,OAA8B,IAAI,GAGrD,IAAY,EAAM,aACrB,MAAgC;EAE/B,AADA,EAAW,UAAU,GACjB,OAAO,KAAQ,aAAY,EAAI,CAAI,IAC9B,MAAK,EAAuD,UAAU;CACjF,GACA,CAAC,CAAG,CACN,GAGM,KAAiB,MAA2C;EAChE,IAAI,GAAU;EAEd,IAAM,IAAe,EAAQ,WAAW,MAAM,EAAE,OAAO,CAAU,GAC7D,IAAY;EAEhB,QAAQ,EAAE,KAAV;GACE,KAAK;IAEH,AADA,EAAE,eAAe,GACjB,IAAY,IAAe,IAAI,IAAe,IAAI,EAAQ,SAAS;IACnE;GACF,KAAK;IAEH,AADA,EAAE,eAAe,GACjB,IAAY,IAAe,EAAQ,SAAS,IAAI,IAAe,IAAI;IACnE;GACF,KAAK;IAEH,AADA,EAAE,eAAe,GACjB,IAAY;IACZ;GACF,KAAK;IAEH,AADA,EAAE,eAAe,GACjB,IAAY,EAAQ,SAAS;IAC7B;GACF,SACE;EACJ;EAGA,AADA,EAAS,EAAQ,EAAU,CAAC,EAAE,GAC9B,4BAA4B;GAE1B,CADgB,EAAW,SAAS,iBAAoC,kBAAgB,EAAA,GAC9E,EAAU,EAAE,MAAM;EAC9B,CAAC;CACH,GAEM,EAAE,QAAQ,GAAY,MAAM,MAAa,EAAW;CAE1D,OACE,kBAAC,OAAD;EACE,KAAK;EACL,MAAK;EACL,UAAU;EACV,cAAY,EAAM,iBAAiB;EACnC,WAAW;EACX,WAAW,EACT,oCACA,4EACA,KAAY,+CACZ,CACF;EACA,GAAI;YAEJ,kBAAC,GAAD;GAAa,IAAI;aACd,EAAQ,KAAK,MAAW;IACvB,IAAM,IAAa,EAAO,OAAO;IAEjC,OACE,kBAAC,UAAD;KAEE,MAAK;KACL,MAAK;KACL,gBAAc;KACd,UAAU,IAAa,IAAI;KACjB;KACV,eAAe,EAAS,EAAO,EAAE;KACjC,WAAW,EACT,2EACA,2EACA,+FACA,GACA,IACI,EAAmB,KACnB,6CACN;eAhBF;MAmBG,KACC,kBAAC,EAAO,MAAR;OACE,UAAS;OACT,WAAW,EACT,qDACA,EAAW,EACb;OACA,YAAY;MACb,CAAA;MAIF,EAAO,QACN,kBAAC,QAAD;OAAM,WAAW,EAAG,mEAAmE,CAAQ;iBAC7F,kBAAC,GAAD;QAAc,MAAM,MAAS,OAAO,OAAO;kBACxC,EAAc,EAAO,IAAI;OACd,CAAA;MACV,CAAA;MAER,kBAAC,QAAD;OAAM,WAAU;iBAAkB,EAAO;MAAW,CAAA;KAC9C;OAtCD,EAAO,EAsCN;GAEZ,CAAC;EACU,CAAA;CACV,CAAA;AAET,CACF;AACA,EAAiB,cAAc"}
|
|
1
|
+
{"version":3,"file":"segmented-control.js","names":[],"sources":["../../src/ui/segmented-control.tsx"],"sourcesContent":["'use client'\n\nimport { LayoutGroup, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { IconProvider } from './icon-context'\nimport type { IconInput } from './lib/icon-input'\nimport { normalizeIcon } from './lib/normalize-icon'\nimport { cn } from './lib/utils'\n\n/* ── Types ─────────────────────────────────────────────────── */\n\nexport type SegmentedControlSize = 'sm' | 'md' | 'lg'\nexport type SegmentedControlVariant = 'soft' | 'solid'\n\n/** @deprecated `variant=\"default\"` was renamed to `\"soft\"`. Still accepted at\n * runtime (maps to `\"soft\"`); update call sites — the alias is removed in a\n * future major. */\ntype DeprecatedVariant = 'default'\n\nexport interface SegmentedControlOption {\n id: string\n /** Visible label. Accepts a `ReactNode` so an option can carry a count badge\n * or custom node, not just a string. Optional — omit for an icon-only\n * segment, in which case set `ariaLabel`. */\n text?: React.ReactNode\n /** Optional icon rendered before the text label. Accepts any `IconInput`. */\n icon?: IconInput\n /** Accessible name for the segment. Required for icon-only segments (no\n * `text`); otherwise the visible text provides the name. */\n ariaLabel?: string\n}\n\nexport interface SegmentedControlProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect' | 'defaultValue'> {\n size?: SegmentedControlSize\n variant?: SegmentedControlVariant | DeprecatedVariant\n options: SegmentedControlOption[]\n /** Selected option id (controlled). Aligns with Tabs/ToggleGroup. */\n value?: string\n /** Initial selected id for uncontrolled mode. Ignored when `value` is set;\n * defaults to the first option. */\n defaultValue?: string\n /** Fires with the newly-selected id (both modes). */\n onValueChange?: (id: string) => void\n disabled?: boolean\n /** Fill the container: segments split the available width equally instead of\n * hugging their content. Use for full-width toggles and view switchers. */\n fullWidth?: boolean\n\n /** @deprecated Use `value`. Still accepted; kept for back-compat. */\n selectedId?: string\n /** @deprecated Use `onValueChange`. Still accepted; kept for back-compat. */\n onSelect?: (id: string) => void\n}\n\n/* ── RTL detection ─────────────────────────────────────────────\n * Reads the nearest `dir` attribute (testable, the common case), falling\n * back to the computed style for CSS-set direction. In RTL the horizontal\n * arrow keys invert so Arrow keys always track reading order. */\n\nfunction isRtl(el: HTMLElement | null): boolean {\n if (!el) return false\n const dirEl = el.closest('[dir]') as HTMLElement | null\n if (dirEl) return (dirEl.getAttribute('dir') ?? '').toLowerCase() === 'rtl'\n try {\n return getComputedStyle(el).direction === 'rtl'\n } catch {\n return false\n }\n}\n\n/* ── Size config ───────────────────────────────────────────── */\n\nconst sizeConfig = {\n sm: { button: 'h-7 px-ds-04 text-body-sm', icon: 'h-3.5 w-3.5' },\n md: { button: 'h-8 px-ds-05 text-body-md', icon: 'h-4 w-4' },\n lg: { button: 'h-10 px-ds-06 text-body-md', icon: 'h-4 w-4' },\n} as const\n\n/* ── Thumb (sliding indicator) styles per variant ──────────────\n * The track carries no border/inset (see tokens/semantic.css\n * --color-segment-track), so the thumb defines its own edge with a\n * single ring-less soft shadow (--shadow-segment). */\n\nconst thumbStyles = {\n soft: 'bg-segment-thumb shadow-segment',\n solid: 'bg-accent-9 shadow-segment',\n} as const\n\nconst selectedTextStyles = {\n soft: 'text-surface-fg',\n solid: 'text-accent-fg',\n} as const\n\n/* ── SegmentedControl ──────────────────────────────────────── */\n\nconst SegmentedControl = React.forwardRef<HTMLDivElement, SegmentedControlProps>(\n function SegmentedControl(\n {\n size = 'md',\n variant = 'soft',\n options,\n value,\n defaultValue,\n onValueChange,\n selectedId,\n onSelect,\n disabled = false,\n fullWidth = false,\n className,\n ...props\n },\n ref,\n ) {\n const instanceId = React.useId()\n const tablistRef = React.useRef<HTMLDivElement | null>(null)\n const reduceMotion = useReducedMotion()\n\n // Back-compat: 'default' was renamed to 'soft'. Map silently.\n const resolvedVariant: SegmentedControlVariant = variant === 'default' ? 'soft' : variant\n\n // Controlled/uncontrolled resolution. `value` is canonical; `selectedId` is\n // the deprecated alias. Controlled when either is provided; otherwise the\n // hook owns state, seeded from `defaultValue` (or the first option).\n const controlledValue = value ?? selectedId\n const isControlled = controlledValue !== undefined\n const [uncontrolled, setUncontrolled] = React.useState(defaultValue ?? options[0]?.id)\n const selected = isControlled ? controlledValue : uncontrolled\n\n const emit = React.useCallback(\n (id: string) => {\n if (!isControlled) setUncontrolled(id)\n ;(onValueChange ?? onSelect)?.(id)\n },\n [isControlled, onValueChange, onSelect],\n )\n\n // Thumb glide. This is a frequent, functional toggle, so the motion is\n // crisp and bounce-free (Apple spring notation: settles ~300ms, no\n // overshoot) rather than playful. Snaps instantly under reduced motion —\n // the slide is the only movement here.\n const thumbTransition = reduceMotion\n ? { duration: 0 }\n : { type: 'spring' as const, duration: 0.3, bounce: 0 }\n\n // Compose refs\n const mergedRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n tablistRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node\n },\n [ref],\n )\n\n // Keyboard navigation\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return\n\n const currentIndex = options.findIndex((o) => o.id === selected)\n let nextIndex = currentIndex\n\n const prev = currentIndex > 0 ? currentIndex - 1 : options.length - 1\n const next = currentIndex < options.length - 1 ? currentIndex + 1 : 0\n const rtl = isRtl(tablistRef.current)\n\n switch (e.key) {\n case 'ArrowLeft':\n e.preventDefault()\n nextIndex = rtl ? next : prev\n break\n case 'ArrowRight':\n e.preventDefault()\n nextIndex = rtl ? prev : next\n break\n case 'Home':\n e.preventDefault()\n nextIndex = 0\n break\n case 'End':\n e.preventDefault()\n nextIndex = options.length - 1\n break\n default:\n return\n }\n\n emit(options[nextIndex].id)\n requestAnimationFrame(() => {\n const buttons = tablistRef.current?.querySelectorAll<HTMLButtonElement>('[role=\"radio\"]')\n buttons?.[nextIndex]?.focus()\n })\n }\n\n const { button: buttonSize, icon: iconSize } = sizeConfig[size]\n\n return (\n <div\n ref={mergedRef}\n role=\"radiogroup\"\n tabIndex={-1}\n aria-label={props['aria-label'] ?? 'Segmented control'}\n onKeyDown={handleKeyDown}\n className={cn(\n 'p-ds-01 rounded-surface bg-segment-track',\n fullWidth ? 'flex w-full' : 'inline-flex w-fit',\n disabled && 'opacity-action-disabled pointer-events-none',\n className,\n )}\n {...props}\n >\n <LayoutGroup id={instanceId}>\n {options.map((option) => {\n const isSelected = option.id === selected\n\n return (\n <button\n key={option.id}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n aria-label={option.ariaLabel}\n tabIndex={isSelected ? 0 : -1}\n disabled={disabled}\n onClick={() => emit(option.id)}\n className={cn(\n 'relative inline-flex items-center justify-center gap-ds-02 rounded-control',\n 'font-medium outline-hidden',\n // `touch-target` adds a 44px min hit area via ::before without\n // changing the visual (dense) height.\n 'touch-target',\n // Exact properties (not `all`); press-scale gives instant\n // tactile feedback, gated to motion-safe so reduced-motion\n // users keep only the color change.\n 'transition-[color,transform] duration-fast-02 ease-productive-standard',\n 'motion-safe:active:scale-[0.97]',\n 'focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent',\n buttonSize,\n fullWidth && 'flex-1 min-w-16',\n isSelected\n ? selectedTextStyles[resolvedVariant]\n : 'text-surface-fg-muted hover:text-surface-fg',\n )}\n >\n {/* Sliding thumb indicator. Rendered before the content so the\n positioned content siblings paint above it (no z-index). */}\n {isSelected && (\n <motion.span\n layoutId=\"segment-thumb\"\n initial={false}\n className={cn(\n 'absolute inset-0 rounded-control pointer-events-none',\n thumbStyles[resolvedVariant],\n )}\n transition={thumbTransition}\n />\n )}\n\n {/* Content — `relative` so it paints over the thumb */}\n {option.icon && (\n <span className={cn('relative shrink-0 inline-flex items-center justify-center', iconSize)}>\n <IconProvider size={size === 'lg' ? 'sm' : 'xs'}>\n {normalizeIcon(option.icon)}\n </IconProvider>\n </span>\n )}\n {option.text != null && <span className=\"relative\">{option.text}</span>}\n </button>\n )\n })}\n </LayoutGroup>\n </div>\n )\n },\n)\nSegmentedControl.displayName = 'SegmentedControl'\n\n/* ── Exports ───────────────────────────────────────────────── */\n\nexport { SegmentedControl }\n"],"mappings":"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"}
|
package/dist/ui/sidebar.js
CHANGED
|
@@ -5,9 +5,9 @@ import { cva as n } from "../_chunks/vendor-utils.js";
|
|
|
5
5
|
import { cn as r } from "./lib/utils.js";
|
|
6
6
|
import { Icon as i } from "./icon.js";
|
|
7
7
|
import { useIsMobile as a } from "../hooks/use-mobile.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { Input as o } from "./input.js";
|
|
9
|
+
import { Skeleton as s } from "./skeleton.js";
|
|
10
|
+
import { Button as c } from "./button.js";
|
|
11
11
|
import { Separator as l } from "./separator.js";
|
|
12
12
|
import { Tooltip as u, TooltipContent as d, TooltipProvider as f, TooltipTrigger as p } from "./tooltip.js";
|
|
13
13
|
import { Sheet as m, SheetContent as h } from "./sheet.js";
|
|
@@ -139,7 +139,7 @@ var M = _(({ side: e = "left", variant: t = "sidebar", collapsible: n = "offcanv
|
|
|
139
139
|
M.displayName = "Sidebar";
|
|
140
140
|
var N = _(({ className: e, onClick: t, ...n }, a) => {
|
|
141
141
|
let { toggleSidebar: o } = A();
|
|
142
|
-
return /* @__PURE__ */ E(
|
|
142
|
+
return /* @__PURE__ */ E(c, {
|
|
143
143
|
ref: a,
|
|
144
144
|
"data-sidebar": "trigger",
|
|
145
145
|
variant: "ghost",
|
|
@@ -179,7 +179,7 @@ var F = _(({ className: e, ...t }, n) => /* @__PURE__ */ T("main", {
|
|
|
179
179
|
...t
|
|
180
180
|
}));
|
|
181
181
|
F.displayName = "SidebarInset";
|
|
182
|
-
var I = _(({ className: e, wrapperClassName: t, ...n }, i) => /* @__PURE__ */ T(
|
|
182
|
+
var I = _(({ className: e, wrapperClassName: t, ...n }, i) => /* @__PURE__ */ T(o, {
|
|
183
183
|
ref: i,
|
|
184
184
|
"data-sidebar": "input",
|
|
185
185
|
size: "sm",
|
|
@@ -326,10 +326,10 @@ var X = _(({ className: e, showIcon: t = !1, ...n }, i) => {
|
|
|
326
326
|
"data-sidebar": "menu-skeleton",
|
|
327
327
|
className: r("flex h-ds-sm items-center gap-ds-03 rounded-control px-ds-03", e),
|
|
328
328
|
...n,
|
|
329
|
-
children: [t && /* @__PURE__ */ T(
|
|
329
|
+
children: [t && /* @__PURE__ */ T(s, {
|
|
330
330
|
className: "h-ico-sm w-ico-sm rounded-control",
|
|
331
331
|
"data-sidebar": "menu-skeleton-icon"
|
|
332
|
-
}), /* @__PURE__ */ T(
|
|
332
|
+
}), /* @__PURE__ */ T(s, {
|
|
333
333
|
className: "h-4 max-w-(--skeleton-width) flex-1",
|
|
334
334
|
"data-sidebar": "menu-skeleton-text",
|
|
335
335
|
style: { "--skeleton-width": a }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"split-button.d.ts","sourceRoot":"","sources":["../../src/ui/split-button.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAMzC,KAAK,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;AACtD,KAAK,gBAAgB,GAAG,WAAW,CAAC,YAAY,CAAC,OAAO,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;AACjF,KAAK,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;AAE7E,MAAM,WAAW,gBAAgB;IAC/B,0CAA0C;IAC1C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,yCAAyC;IACzC,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAA;IACzD,2DAA2D;IAC3D,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACjC,6BAA6B;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,+DAA+D;IAC/D,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,uBAAuB;IACvB,OAAO,CAAC,EAAE,kBAAkB,CAAA;IAC5B,wBAAwB;IACxB,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACxB,oBAAoB;IACpB,IAAI,CAAC,EAAE,eAAe,CAAA;IACtB,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,yCAAyC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sEAAsE;IACtE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC9B,qEAAqE;IACrE,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;
|
|
1
|
+
{"version":3,"file":"split-button.d.ts","sourceRoot":"","sources":["../../src/ui/split-button.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAMzC,KAAK,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;AACtD,KAAK,gBAAgB,GAAG,WAAW,CAAC,YAAY,CAAC,OAAO,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;AACjF,KAAK,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;AAE7E,MAAM,WAAW,gBAAgB;IAC/B,0CAA0C;IAC1C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,yCAAyC;IACzC,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAA;IACzD,2DAA2D;IAC3D,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACjC,6BAA6B;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,+DAA+D;IAC/D,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,uBAAuB;IACvB,OAAO,CAAC,EAAE,kBAAkB,CAAA;IAC5B,wBAAwB;IACxB,KAAK,CAAC,EAAE,gBAAgB,CAAA;IACxB,oBAAoB;IACpB,IAAI,CAAC,EAAE,eAAe,CAAA;IACtB,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,yCAAyC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sEAAsE;IACtE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC9B,qEAAqE;IACrE,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA8FD,QAAA,MAAM,WAAW,yFAuIhB,CAAA;AAID,OAAO,EAAE,WAAW,EAAE,CAAA;AACtB,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAC,kBAAkB,EAAE,CAAA"}
|
package/dist/ui/split-button.js
CHANGED
|
@@ -10,6 +10,7 @@ var c = {
|
|
|
10
10
|
error: "bg-error-11/20",
|
|
11
11
|
success: "bg-success-11/20",
|
|
12
12
|
warning: "bg-warning-11/20",
|
|
13
|
+
info: "bg-info-11/20",
|
|
13
14
|
neutral: "bg-neutral-8/30"
|
|
14
15
|
},
|
|
15
16
|
soft: {
|
|
@@ -17,6 +18,7 @@ var c = {
|
|
|
17
18
|
error: "bg-error-6",
|
|
18
19
|
success: "bg-success-6",
|
|
19
20
|
warning: "bg-warning-6",
|
|
21
|
+
info: "bg-info-6",
|
|
20
22
|
neutral: "bg-surface-border"
|
|
21
23
|
},
|
|
22
24
|
outline: {
|
|
@@ -24,6 +26,7 @@ var c = {
|
|
|
24
26
|
error: "bg-error-7",
|
|
25
27
|
success: "bg-success-7",
|
|
26
28
|
warning: "bg-warning-7",
|
|
29
|
+
info: "bg-info-7",
|
|
27
30
|
neutral: "bg-surface-border-strong"
|
|
28
31
|
}
|
|
29
32
|
}, l = {
|
|
@@ -69,6 +72,7 @@ function m(e, t) {
|
|
|
69
72
|
error: "bg-error-9 text-error-fg hover:bg-error-10 active:bg-error-11",
|
|
70
73
|
success: "bg-success-9 text-success-fg hover:bg-success-10 active:bg-success-11",
|
|
71
74
|
warning: "bg-warning-9 text-warning-fg hover:bg-warning-10 active:bg-warning-11",
|
|
75
|
+
info: "bg-info-9 text-info-fg hover:bg-info-10 active:bg-info-11",
|
|
72
76
|
neutral: "bg-neutral-5 text-surface-fg hover:bg-neutral-7 active:bg-neutral-8"
|
|
73
77
|
},
|
|
74
78
|
soft: {
|
|
@@ -76,6 +80,7 @@ function m(e, t) {
|
|
|
76
80
|
error: "bg-error-3 text-error-11 hover:bg-error-4 active:bg-error-5",
|
|
77
81
|
success: "bg-success-3 text-success-11 hover:bg-success-4 active:bg-success-5",
|
|
78
82
|
warning: "bg-warning-3 text-warning-11 hover:bg-warning-4 active:bg-warning-5",
|
|
83
|
+
info: "bg-info-3 text-info-11 hover:bg-info-4 active:bg-info-5",
|
|
79
84
|
neutral: "bg-surface-raised-hover text-surface-fg-muted hover:bg-surface-raised-active active:bg-neutral-5"
|
|
80
85
|
},
|
|
81
86
|
outline: {
|
|
@@ -83,6 +88,7 @@ function m(e, t) {
|
|
|
83
88
|
error: "bg-transparent text-error-11 hover:bg-error-3 active:bg-error-4",
|
|
84
89
|
success: "bg-transparent text-success-11 hover:bg-success-3 active:bg-success-4",
|
|
85
90
|
warning: "bg-transparent text-warning-11 hover:bg-warning-3 active:bg-warning-4",
|
|
91
|
+
info: "bg-transparent text-info-11 hover:bg-info-3 active:bg-info-4",
|
|
86
92
|
neutral: "bg-transparent text-surface-fg hover:bg-surface-raised-hover active:bg-surface-raised-active"
|
|
87
93
|
}
|
|
88
94
|
};
|
|
@@ -94,6 +100,7 @@ function h(e) {
|
|
|
94
100
|
error: "border-error-7",
|
|
95
101
|
success: "border-success-7",
|
|
96
102
|
warning: "border-warning-7",
|
|
103
|
+
info: "border-info-7",
|
|
97
104
|
neutral: "border-surface-border-strong"
|
|
98
105
|
};
|
|
99
106
|
return t[e] ?? t.accent;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"split-button.js","names":[],"sources":["../../src/ui/split-button.tsx"],"sourcesContent":["'use client'\n\nimport { type Placement } from '@floating-ui/dom'\nimport { type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { buttonVariants } from './button'\nimport { cn } from './lib/utils'\nimport { Popover, PopoverContent, PopoverTrigger } from './popover'\n\n// ── Types ───────────────────────────────────────────────────────\n\ntype SplitButtonVariant = 'solid' | 'soft' | 'outline'\ntype SplitButtonColor = NonNullable<VariantProps<typeof buttonVariants>['color']>\ntype SplitButtonSize = 'xs' | 'sm' | 'md' | 'icon-xs' | 'icon-sm' | 'icon-md'\n\nexport interface SplitButtonProps {\n /** Primary action content (left side). */\n children: React.ReactNode\n /** Primary click handler (left side). */\n onClick: (e: React.MouseEvent<HTMLButtonElement>) => void\n /** Content rendered inside the floating dropdown panel. */\n dropdownContent?: React.ReactNode\n /** Controlled open state. */\n open?: boolean\n /** Called when open state changes. Uncontrolled if omitted. */\n onOpenChange?: (open: boolean) => void\n /** @default 'solid' */\n variant?: SplitButtonVariant\n /** @default 'accent' */\n color?: SplitButtonColor\n /** @default 'md' */\n size?: SplitButtonSize\n /** Disable both halves. */\n disabled?: boolean\n /** aria-label for the primary button. */\n 'aria-label'?: string\n /** aria-label for the dropdown trigger. @default 'More options' */\n dropdownLabel?: string\n /** Custom icon for the dropdown trigger. Defaults to chevron-down. */\n dropdownIcon?: React.ReactNode\n /** Which side the dropdown trigger sits on. @default 'right' */\n triggerSide?: 'left' | 'right'\n /** Preferred placement for the dropdown panel. @default 'top-end' */\n placement?: Placement\n /** Width of the trigger (chevron) half. @default 'auto' */\n triggerWidth?: number | string\n className?: string\n}\n\n// ── Styling maps ────────────────────────────────────────────────\n\nconst dividerColor: Record<SplitButtonVariant, Record<string, string>> = {\n solid: {\n accent: 'bg-accent-11/20', error: 'bg-error-11/20', success: 'bg-success-11/20',\n warning: 'bg-warning-11/20', neutral: 'bg-neutral-8/30',\n },\n soft: {\n accent: 'bg-accent-6', error: 'bg-error-6', success: 'bg-success-6',\n warning: 'bg-warning-6', neutral: 'bg-surface-border',\n },\n outline: {\n accent: 'bg-accent-7', error: 'bg-error-7', success: 'bg-success-7',\n warning: 'bg-warning-7', neutral: 'bg-surface-border-strong',\n },\n}\n\nconst heightClass: Record<SplitButtonSize, string> = {\n xs: 'h-ds-xs-plus', sm: 'h-ds-sm', md: 'h-ds-md',\n 'icon-xs': 'h-ds-xs-plus', 'icon-sm': 'h-ds-sm', 'icon-md': 'h-ds-md',\n}\n\nconst textClass: Record<SplitButtonSize, string> = {\n xs: 'text-body-sm', sm: 'text-body-sm', md: 'text-body-md',\n 'icon-xs': 'text-body-sm', 'icon-sm': 'text-body-sm', 'icon-md': 'text-body-md',\n}\n\nconst primaryPadding: Record<SplitButtonSize, string> = {\n xs: 'px-ds-03 gap-1', sm: 'px-ds-04 gap-1.5', md: 'px-ds-05 gap-2',\n 'icon-xs': 'px-ds-02', 'icon-sm': 'px-ds-03', 'icon-md': 'px-ds-04',\n}\n\nconst triggerPadding: Record<SplitButtonSize, string> = {\n xs: 'px-ds-02', sm: 'px-ds-02', md: 'px-ds-03',\n 'icon-xs': 'px-ds-01', 'icon-sm': 'px-ds-02', 'icon-md': 'px-ds-02',\n}\n\nconst radiusClass: Record<SplitButtonSize, string> = {\n xs: 'rounded-control', sm: 'rounded-control', md: 'rounded-control',\n 'icon-xs': 'rounded-control', 'icon-sm': 'rounded-control', 'icon-md': 'rounded-control',\n}\n\nfunction getHalfClasses(variant: SplitButtonVariant, color: string): string {\n const map: Record<SplitButtonVariant, Record<string, string>> = {\n solid: {\n accent: 'bg-accent-9 text-accent-fg hover:bg-accent-10 active:bg-accent-11',\n error: 'bg-error-9 text-error-fg hover:bg-error-10 active:bg-error-11',\n success: 'bg-success-9 text-success-fg hover:bg-success-10 active:bg-success-11',\n warning: 'bg-warning-9 text-warning-fg hover:bg-warning-10 active:bg-warning-11',\n neutral: 'bg-neutral-5 text-surface-fg hover:bg-neutral-7 active:bg-neutral-8',\n },\n soft: {\n accent: 'bg-accent-3 text-accent-11 hover:bg-accent-4 active:bg-accent-5',\n error: 'bg-error-3 text-error-11 hover:bg-error-4 active:bg-error-5',\n success: 'bg-success-3 text-success-11 hover:bg-success-4 active:bg-success-5',\n warning: 'bg-warning-3 text-warning-11 hover:bg-warning-4 active:bg-warning-5',\n neutral: 'bg-surface-raised-hover text-surface-fg-muted hover:bg-surface-raised-active active:bg-neutral-5',\n },\n outline: {\n accent: 'bg-transparent text-accent-11 hover:bg-accent-3 active:bg-accent-4',\n error: 'bg-transparent text-error-11 hover:bg-error-3 active:bg-error-4',\n success: 'bg-transparent text-success-11 hover:bg-success-3 active:bg-success-4',\n warning: 'bg-transparent text-warning-11 hover:bg-warning-3 active:bg-warning-4',\n neutral: 'bg-transparent text-surface-fg hover:bg-surface-raised-hover active:bg-surface-raised-active',\n },\n }\n return map[variant][color] ?? map[variant].accent\n}\n\nfunction getOutlineBorderColor(color: string): string {\n const map: Record<string, string> = {\n accent: 'border-accent-7', error: 'border-error-7', success: 'border-success-7',\n warning: 'border-warning-7', neutral: 'border-surface-border-strong',\n }\n return map[color] ?? map.accent\n}\n\n// ── Chevron SVG ─────────────────────────────────────────────────\n\nfunction ChevronDown({ className }: { className?: string }) {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" className={className} aria-hidden=\"true\">\n <path d=\"M3 4.5L6 7.5L9 4.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )\n}\n\n// ── Component ───────────────────────────────────────────────────\n\nconst SplitButton = React.forwardRef<HTMLDivElement, SplitButtonProps>(\n function SplitButton(\n {\n children,\n onClick,\n dropdownContent,\n open: openProp,\n onOpenChange,\n variant = 'solid',\n color = 'accent',\n size = 'md',\n disabled = false,\n 'aria-label': ariaLabel,\n dropdownLabel = 'More options',\n dropdownIcon,\n triggerSide = 'right',\n placement: placementProp = 'top-end',\n triggerWidth,\n className,\n },\n ref,\n ) {\n // Map the floating-ui `placement` (e.g. 'top-end') to Radix side + align.\n // Positioning, focus-in/return, outside-click, and Escape all come from the\n // Popover primitive now — no hand-rolled floating-ui / listeners.\n const [side, align] = React.useMemo(() => {\n const [s, a] = placementProp.split('-')\n return [\n s as 'top' | 'right' | 'bottom' | 'left',\n (a === 'start' ? 'start' : a === 'end' ? 'end' : 'center') as\n | 'start'\n | 'center'\n | 'end',\n ]\n }, [placementProp])\n\n const halfClasses = getHalfClasses(variant, color)\n const divider = dividerColor[variant][color] ?? dividerColor[variant].accent\n\n const triggerStyle = triggerWidth != null\n ? { width: typeof triggerWidth === 'number' ? `${triggerWidth}px` : triggerWidth }\n : undefined\n\n // Dropdown trigger (the chevron half). PopoverTrigger wires aria-haspopup /\n // aria-expanded / focus-return for us.\n const dropdownTrigger = (\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n disabled={disabled}\n aria-label={dropdownLabel}\n className={cn(\n 'inline-flex items-center justify-center select-none',\n 'transition-colors duration-fast-01 ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-inset',\n 'disabled:pointer-events-none disabled:opacity-action-disabled',\n halfClasses,\n heightClass[size],\n triggerPadding[size],\n )}\n style={triggerStyle}\n >\n {dropdownIcon ?? <ChevronDown />}\n </button>\n </PopoverTrigger>\n )\n\n return (\n <Popover open={openProp} onOpenChange={onOpenChange}>\n {/* Button group */}\n <div\n ref={ref}\n role=\"group\"\n aria-label={ariaLabel ?? undefined}\n className={cn('relative inline-flex', className)}\n >\n <div\n className={cn(\n 'inline-flex items-stretch overflow-hidden',\n radiusClass[size],\n variant === 'solid' && 'shadow-raised',\n variant === 'outline' && `border ${getOutlineBorderColor(color)}`,\n )}\n >\n {triggerSide === 'left' && (\n <>\n {dropdownTrigger}\n <div className={cn('w-px self-stretch', divider)} />\n </>\n )}\n\n {/* Primary action */}\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex items-center justify-center font-semibold select-none whitespace-nowrap',\n 'transition-[colors,transform] duration-fast-01 ease-productive-standard',\n 'active:scale-[0.97]',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-inset',\n 'disabled:pointer-events-none disabled:opacity-action-disabled',\n halfClasses,\n heightClass[size],\n textClass[size],\n primaryPadding[size],\n )}\n >\n {children}\n </button>\n\n {triggerSide === 'right' && (\n <>\n <div className={cn('w-px self-stretch', divider)} />\n {dropdownTrigger}\n </>\n )}\n </div>\n </div>\n\n {/* Dropdown panel — Popover handles focus, Escape, outside-click, return */}\n {dropdownContent && (\n <PopoverContent\n side={side}\n align={align}\n sideOffset={8}\n className=\"w-auto min-w-[10rem] p-ds-01\"\n >\n {dropdownContent}\n </PopoverContent>\n )}\n </Popover>\n )\n },\n)\n\nSplitButton.displayName = 'SplitButton'\n\nexport { SplitButton }\nexport type { SplitButtonColor, SplitButtonSize,SplitButtonVariant }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"split-button.js","names":[],"sources":["../../src/ui/split-button.tsx"],"sourcesContent":["'use client'\n\nimport { type Placement } from '@floating-ui/dom'\nimport { type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { buttonVariants } from './button'\nimport { cn } from './lib/utils'\nimport { Popover, PopoverContent, PopoverTrigger } from './popover'\n\n// ── Types ───────────────────────────────────────────────────────\n\ntype SplitButtonVariant = 'solid' | 'soft' | 'outline'\ntype SplitButtonColor = NonNullable<VariantProps<typeof buttonVariants>['color']>\ntype SplitButtonSize = 'xs' | 'sm' | 'md' | 'icon-xs' | 'icon-sm' | 'icon-md'\n\nexport interface SplitButtonProps {\n /** Primary action content (left side). */\n children: React.ReactNode\n /** Primary click handler (left side). */\n onClick: (e: React.MouseEvent<HTMLButtonElement>) => void\n /** Content rendered inside the floating dropdown panel. */\n dropdownContent?: React.ReactNode\n /** Controlled open state. */\n open?: boolean\n /** Called when open state changes. Uncontrolled if omitted. */\n onOpenChange?: (open: boolean) => void\n /** @default 'solid' */\n variant?: SplitButtonVariant\n /** @default 'accent' */\n color?: SplitButtonColor\n /** @default 'md' */\n size?: SplitButtonSize\n /** Disable both halves. */\n disabled?: boolean\n /** aria-label for the primary button. */\n 'aria-label'?: string\n /** aria-label for the dropdown trigger. @default 'More options' */\n dropdownLabel?: string\n /** Custom icon for the dropdown trigger. Defaults to chevron-down. */\n dropdownIcon?: React.ReactNode\n /** Which side the dropdown trigger sits on. @default 'right' */\n triggerSide?: 'left' | 'right'\n /** Preferred placement for the dropdown panel. @default 'top-end' */\n placement?: Placement\n /** Width of the trigger (chevron) half. @default 'auto' */\n triggerWidth?: number | string\n className?: string\n}\n\n// ── Styling maps ────────────────────────────────────────────────\n\nconst dividerColor: Record<SplitButtonVariant, Record<string, string>> = {\n solid: {\n accent: 'bg-accent-11/20', error: 'bg-error-11/20', success: 'bg-success-11/20',\n warning: 'bg-warning-11/20', info: 'bg-info-11/20', neutral: 'bg-neutral-8/30',\n },\n soft: {\n accent: 'bg-accent-6', error: 'bg-error-6', success: 'bg-success-6',\n warning: 'bg-warning-6', info: 'bg-info-6', neutral: 'bg-surface-border',\n },\n outline: {\n accent: 'bg-accent-7', error: 'bg-error-7', success: 'bg-success-7',\n warning: 'bg-warning-7', info: 'bg-info-7', neutral: 'bg-surface-border-strong',\n },\n}\n\nconst heightClass: Record<SplitButtonSize, string> = {\n xs: 'h-ds-xs-plus', sm: 'h-ds-sm', md: 'h-ds-md',\n 'icon-xs': 'h-ds-xs-plus', 'icon-sm': 'h-ds-sm', 'icon-md': 'h-ds-md',\n}\n\nconst textClass: Record<SplitButtonSize, string> = {\n xs: 'text-body-sm', sm: 'text-body-sm', md: 'text-body-md',\n 'icon-xs': 'text-body-sm', 'icon-sm': 'text-body-sm', 'icon-md': 'text-body-md',\n}\n\nconst primaryPadding: Record<SplitButtonSize, string> = {\n xs: 'px-ds-03 gap-1', sm: 'px-ds-04 gap-1.5', md: 'px-ds-05 gap-2',\n 'icon-xs': 'px-ds-02', 'icon-sm': 'px-ds-03', 'icon-md': 'px-ds-04',\n}\n\nconst triggerPadding: Record<SplitButtonSize, string> = {\n xs: 'px-ds-02', sm: 'px-ds-02', md: 'px-ds-03',\n 'icon-xs': 'px-ds-01', 'icon-sm': 'px-ds-02', 'icon-md': 'px-ds-02',\n}\n\nconst radiusClass: Record<SplitButtonSize, string> = {\n xs: 'rounded-control', sm: 'rounded-control', md: 'rounded-control',\n 'icon-xs': 'rounded-control', 'icon-sm': 'rounded-control', 'icon-md': 'rounded-control',\n}\n\nfunction getHalfClasses(variant: SplitButtonVariant, color: string): string {\n const map: Record<SplitButtonVariant, Record<string, string>> = {\n solid: {\n accent: 'bg-accent-9 text-accent-fg hover:bg-accent-10 active:bg-accent-11',\n error: 'bg-error-9 text-error-fg hover:bg-error-10 active:bg-error-11',\n success: 'bg-success-9 text-success-fg hover:bg-success-10 active:bg-success-11',\n warning: 'bg-warning-9 text-warning-fg hover:bg-warning-10 active:bg-warning-11',\n info: 'bg-info-9 text-info-fg hover:bg-info-10 active:bg-info-11',\n neutral: 'bg-neutral-5 text-surface-fg hover:bg-neutral-7 active:bg-neutral-8',\n },\n soft: {\n accent: 'bg-accent-3 text-accent-11 hover:bg-accent-4 active:bg-accent-5',\n error: 'bg-error-3 text-error-11 hover:bg-error-4 active:bg-error-5',\n success: 'bg-success-3 text-success-11 hover:bg-success-4 active:bg-success-5',\n warning: 'bg-warning-3 text-warning-11 hover:bg-warning-4 active:bg-warning-5',\n info: 'bg-info-3 text-info-11 hover:bg-info-4 active:bg-info-5',\n neutral: 'bg-surface-raised-hover text-surface-fg-muted hover:bg-surface-raised-active active:bg-neutral-5',\n },\n outline: {\n accent: 'bg-transparent text-accent-11 hover:bg-accent-3 active:bg-accent-4',\n error: 'bg-transparent text-error-11 hover:bg-error-3 active:bg-error-4',\n success: 'bg-transparent text-success-11 hover:bg-success-3 active:bg-success-4',\n warning: 'bg-transparent text-warning-11 hover:bg-warning-3 active:bg-warning-4',\n info: 'bg-transparent text-info-11 hover:bg-info-3 active:bg-info-4',\n neutral: 'bg-transparent text-surface-fg hover:bg-surface-raised-hover active:bg-surface-raised-active',\n },\n }\n return map[variant][color] ?? map[variant].accent\n}\n\nfunction getOutlineBorderColor(color: string): string {\n const map: Record<string, string> = {\n accent: 'border-accent-7', error: 'border-error-7', success: 'border-success-7',\n warning: 'border-warning-7', info: 'border-info-7', neutral: 'border-surface-border-strong',\n }\n return map[color] ?? map.accent\n}\n\n// ── Chevron SVG ─────────────────────────────────────────────────\n\nfunction ChevronDown({ className }: { className?: string }) {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" className={className} aria-hidden=\"true\">\n <path d=\"M3 4.5L6 7.5L9 4.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )\n}\n\n// ── Component ───────────────────────────────────────────────────\n\nconst SplitButton = React.forwardRef<HTMLDivElement, SplitButtonProps>(\n function SplitButton(\n {\n children,\n onClick,\n dropdownContent,\n open: openProp,\n onOpenChange,\n variant = 'solid',\n color = 'accent',\n size = 'md',\n disabled = false,\n 'aria-label': ariaLabel,\n dropdownLabel = 'More options',\n dropdownIcon,\n triggerSide = 'right',\n placement: placementProp = 'top-end',\n triggerWidth,\n className,\n },\n ref,\n ) {\n // Map the floating-ui `placement` (e.g. 'top-end') to Radix side + align.\n // Positioning, focus-in/return, outside-click, and Escape all come from the\n // Popover primitive now — no hand-rolled floating-ui / listeners.\n const [side, align] = React.useMemo(() => {\n const [s, a] = placementProp.split('-')\n return [\n s as 'top' | 'right' | 'bottom' | 'left',\n (a === 'start' ? 'start' : a === 'end' ? 'end' : 'center') as\n | 'start'\n | 'center'\n | 'end',\n ]\n }, [placementProp])\n\n const halfClasses = getHalfClasses(variant, color)\n const divider = dividerColor[variant][color] ?? dividerColor[variant].accent\n\n const triggerStyle = triggerWidth != null\n ? { width: typeof triggerWidth === 'number' ? `${triggerWidth}px` : triggerWidth }\n : undefined\n\n // Dropdown trigger (the chevron half). PopoverTrigger wires aria-haspopup /\n // aria-expanded / focus-return for us.\n const dropdownTrigger = (\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n disabled={disabled}\n aria-label={dropdownLabel}\n className={cn(\n 'inline-flex items-center justify-center select-none',\n 'transition-colors duration-fast-01 ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-inset',\n 'disabled:pointer-events-none disabled:opacity-action-disabled',\n halfClasses,\n heightClass[size],\n triggerPadding[size],\n )}\n style={triggerStyle}\n >\n {dropdownIcon ?? <ChevronDown />}\n </button>\n </PopoverTrigger>\n )\n\n return (\n <Popover open={openProp} onOpenChange={onOpenChange}>\n {/* Button group */}\n <div\n ref={ref}\n role=\"group\"\n aria-label={ariaLabel ?? undefined}\n className={cn('relative inline-flex', className)}\n >\n <div\n className={cn(\n 'inline-flex items-stretch overflow-hidden',\n radiusClass[size],\n variant === 'solid' && 'shadow-raised',\n variant === 'outline' && `border ${getOutlineBorderColor(color)}`,\n )}\n >\n {triggerSide === 'left' && (\n <>\n {dropdownTrigger}\n <div className={cn('w-px self-stretch', divider)} />\n </>\n )}\n\n {/* Primary action */}\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex items-center justify-center font-semibold select-none whitespace-nowrap',\n 'transition-[colors,transform] duration-fast-01 ease-productive-standard',\n 'active:scale-[0.97]',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-inset',\n 'disabled:pointer-events-none disabled:opacity-action-disabled',\n halfClasses,\n heightClass[size],\n textClass[size],\n primaryPadding[size],\n )}\n >\n {children}\n </button>\n\n {triggerSide === 'right' && (\n <>\n <div className={cn('w-px self-stretch', divider)} />\n {dropdownTrigger}\n </>\n )}\n </div>\n </div>\n\n {/* Dropdown panel — Popover handles focus, Escape, outside-click, return */}\n {dropdownContent && (\n <PopoverContent\n side={side}\n align={align}\n sideOffset={8}\n className=\"w-auto min-w-[10rem] p-ds-01\"\n >\n {dropdownContent}\n </PopoverContent>\n )}\n </Popover>\n )\n },\n)\n\nSplitButton.displayName = 'SplitButton'\n\nexport { SplitButton }\nexport type { SplitButtonColor, SplitButtonSize,SplitButtonVariant }\n"],"mappings":"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"}
|
|
@@ -1,37 +1,48 @@
|
|
|
1
1
|
# PriorityIndicator
|
|
2
2
|
|
|
3
3
|
- Import: @devalok/shilp-sutra/composed/priority-indicator
|
|
4
|
-
- Server-safe:
|
|
4
|
+
- Server-safe: No
|
|
5
5
|
- Category: composed
|
|
6
6
|
|
|
7
7
|
## Props
|
|
8
8
|
priority: Priority
|
|
9
|
-
|
|
9
|
+
iconOnly?: boolean (icon-only chip, no visible text)
|
|
10
|
+
display?: "compact" | "full" (@deprecated — use iconOnly)
|
|
11
|
+
children?: ReactNode (override the label, e.g. i18n)
|
|
10
12
|
|
|
11
13
|
Priority = 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT' | 'low' | 'medium' | 'high' | 'urgent'
|
|
12
14
|
|
|
13
15
|
## Defaults
|
|
14
|
-
|
|
16
|
+
iconOnly: false
|
|
15
17
|
|
|
16
18
|
## Example
|
|
17
19
|
```jsx
|
|
18
20
|
<PriorityIndicator priority="HIGH" />
|
|
19
|
-
<PriorityIndicator priority="low"
|
|
21
|
+
<PriorityIndicator priority="low" iconOnly />
|
|
22
|
+
<PriorityIndicator priority="URGENT">Critical</PriorityIndicator>
|
|
20
23
|
```
|
|
21
24
|
|
|
22
25
|
## Composability
|
|
23
|
-
- **
|
|
26
|
+
- **Composes `Badge`** — radius, color semantics, a11y labelling, and reduced-motion handling all come from one place (no bespoke re-roll).
|
|
24
27
|
- **Composes inside list rows, DataTable cells, Card headers, task panels** — anywhere a priority flag fits.
|
|
25
|
-
-
|
|
26
|
-
- **
|
|
27
|
-
-
|
|
28
|
+
- **`iconOnly`** shows only the icon with a real accessible name (`role="img"` + `aria-label`), for tight cells. Omit it (default) for icon + label.
|
|
29
|
+
- **Severity by weight, not motion** — URGENT renders as a solid fill so the top tier reads at a glance; the others are soft. No animation (removes the prior perpetual pulse).
|
|
30
|
+
- **Case-insensitive priority** — accepts both UPPERCASE and lowercase; unknown values fall back to MEDIUM instead of throwing.
|
|
31
|
+
- **`children`** overrides the label for i18n / custom copy.
|
|
32
|
+
- Color semantics: LOW = slate (neutral), MEDIUM = warning, HIGH = error (soft), URGENT = error (solid).
|
|
28
33
|
|
|
29
34
|
## Gotchas
|
|
30
|
-
- Case-insensitive — "low" and "LOW" both work
|
|
31
|
-
-
|
|
32
|
-
- `
|
|
35
|
+
- Case-insensitive — "low" and "LOW" both work; unknown values fall back to MEDIUM
|
|
36
|
+
- `iconOnly` shows only the icon (accessible-named); default shows icon + text label
|
|
37
|
+
- `display` is deprecated — use `iconOnly`
|
|
33
38
|
|
|
34
39
|
## Changes
|
|
40
|
+
### v0.53.0
|
|
41
|
+
- **Changed** Recomposed on the `Badge` primitive (was a bespoke re-rolled chip): inherits pill radius, color semantics, accessible labelling.
|
|
42
|
+
- **Changed** URGENT is now a solid static fill; the perpetual scale-pulse is removed (was unguarded infinite motion, WCAG 2.2.2). Severity reads without animation.
|
|
43
|
+
- **Added** `iconOnly` (replaces deprecated `display`) and `children` (label override for i18n).
|
|
44
|
+
- **Fixed** Icon-only chip now has a real accessible name (`role="img"` + `aria-label`), not a mouse-only `title`. Unknown priority no longer throws.
|
|
45
|
+
|
|
35
46
|
### v0.2.0
|
|
36
47
|
- **Added** Identified as server-safe component
|
|
37
48
|
|
|
@@ -10,14 +10,19 @@
|
|
|
10
10
|
events: ScheduleEvent[] (REQUIRED) — { id, title, start: Date, end: Date, color? }
|
|
11
11
|
onEventClick?: (event: ScheduleEvent) => void
|
|
12
12
|
onSlotClick?: (start: Date, end: Date) => void
|
|
13
|
-
startHour
|
|
14
|
-
endHour
|
|
15
|
-
slotDuration
|
|
13
|
+
startHour?: number (default: 8)
|
|
14
|
+
endHour?: number (default: 18, exclusive)
|
|
15
|
+
slotDuration?: number (minutes, default: 30)
|
|
16
|
+
selectedEventId?: string (rings the active event)
|
|
17
|
+
renderEvent?: (event) => ReactNode (custom event body)
|
|
18
|
+
header?: ReactNode (toolbar slot above the grid)
|
|
19
|
+
emptyState?: ReactNode (shown when events is empty)
|
|
20
|
+
height?: number | string (grid body height, default 480)
|
|
16
21
|
|
|
17
22
|
Event colors: "accent" | "success" | "warning" | "error" | "info" | "neutral"
|
|
18
23
|
|
|
19
24
|
## Defaults
|
|
20
|
-
startHour=8, endHour=18, slotDuration=30
|
|
25
|
+
startHour=8, endHour=18, slotDuration=30, height=480
|
|
21
26
|
|
|
22
27
|
## Example
|
|
23
28
|
```jsx
|
|
@@ -32,7 +37,12 @@ Event colors: "accent" | "success" | "warning" | "error" | "info" | "neutral"
|
|
|
32
37
|
## Composability
|
|
33
38
|
- **Day / Week calendar view** for time-block display (meetings, shifts, availability). Not a full calendar app — no month view, no drag-to-create.
|
|
34
39
|
- **Event data is consumer-owned:** You pass `events` as an array; ScheduleView doesn't fetch, doesn't cache, doesn't expand recurring events. All scheduling logic lives in your app.
|
|
35
|
-
- **Event click + slot click** — `onEventClick` for existing events; `onSlotClick` for creating new events (fires with start/end of the empty slot).
|
|
40
|
+
- **Event click + slot click** — `onEventClick` for existing events; `onSlotClick` for creating new events (fires with start/end of the empty slot). **Slots are only interactive (focusable + keyboard-navigable) when `onSlotClick` is set** — otherwise they render as inert grid lines, so a read-only schedule adds no keyboard/AT tab stops.
|
|
41
|
+
- **Keyboard (interactive slots):** roving tabindex — Arrow keys move between slots (up/down within a day, left/right across days, RTL-aware), Home/End jump within the day; only one slot is in the tab order at a time.
|
|
42
|
+
- **Overlapping events** are partitioned into side-by-side columns automatically so double-booked times stay legible.
|
|
43
|
+
- **Live now-line** ticks every minute and scrolls into view on mount.
|
|
44
|
+
- **`renderEvent`** customizes the event block body; **`header`** adds a toolbar; **`selectedEventId`** rings the active event; **`emptyState`** shows when there are no events.
|
|
45
|
+
- **Composes the DS card shell tokens** (`surface-2` + `rounded-surface` + border) and is RTL-safe (logical properties throughout).
|
|
36
46
|
- **Color vocabulary matches the DS** — `accent/success/warning/error/info/neutral`. Map your event types to these at the data layer.
|
|
37
47
|
- **endHour is exclusive:** `endHour=18` means the last visible slot starts at 17:30 (with 30min slots). Match your UX expectation: 9-5 typically means `startHour=9, endHour=18`.
|
|
38
48
|
- **Pairs with date-picker/composed** — use DatePicker or DateRangePicker to choose which date to show; pass that as ScheduleView's `date`.
|
|
@@ -43,6 +53,11 @@ Event colors: "accent" | "success" | "warning" | "error" | "info" | "neutral"
|
|
|
43
53
|
- Events that span outside `startHour`/`endHour` may be clipped
|
|
44
54
|
|
|
45
55
|
## Changes
|
|
56
|
+
### v0.53.0
|
|
57
|
+
- **Changed** Read-only schedules no longer flood the tab order — slots are interactive only when `onSlotClick` is set; otherwise inert grid lines. Interactive slots use roving tabindex + Arrow/Home/End keyboard navigation (RTL-aware).
|
|
58
|
+
- **Changed** Overlapping events now lay out in side-by-side columns instead of stacking illegibly. The now-line ticks live (per-minute) and scrolls into view; shell uses the `surface-2` card tier (fixed the prior `surface-raised` + dead-`border-card-strong` regression); layout uses logical (RTL-safe) properties; magic-number sizes tokenized.
|
|
59
|
+
- **Added** `selectedEventId`, `renderEvent`, `header`, `emptyState`, and `height` props.
|
|
60
|
+
|
|
46
61
|
### v0.49.0
|
|
47
62
|
- **BREAKING** `ScheduleEvent.color` value `"primary"` renamed `"accent"` (DS colour vocabulary). It was the default, so untyped events are unaffected.
|
|
48
63
|
- **Added** keyboard focus rings on slot cells + event blocks; current-time indicator uses the shared `<Dot>`.
|