@ramme-io/ui 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +4 -3
  2. package/dist/component-registry.js +55 -52
  3. package/dist/components/ai/AIChatThread/AIChatThread.js +3 -3
  4. package/dist/components/ai/NewsSummaryCard/NewsSummaryCard.js +17 -17
  5. package/dist/components/ai/StockChart/StockChart.js +35 -35
  6. package/dist/components/auth/LoginPage.d.ts +22 -0
  7. package/dist/components/auth/LoginPage.js +50 -42
  8. package/dist/components/data-display/StatusBadge.d.ts +4 -3
  9. package/dist/components/data-display/StatusBadge.js +18 -18
  10. package/dist/components/feedback/Alert.js +19 -19
  11. package/dist/components/forms/ComboBox.js +2 -2
  12. package/dist/components/forms/DatePicker.js +37 -32
  13. package/dist/components/forms/FileUpload.js +39 -37
  14. package/dist/components/forms/Input.js +19 -18
  15. package/dist/components/forms/MultiSelect.js +9 -8
  16. package/dist/components/forms/SegmentedControl.js +10 -10
  17. package/dist/components/forms/Select.js +9 -8
  18. package/dist/components/forms/Textarea.d.ts +13 -11
  19. package/dist/components/forms/Textarea.js +36 -16
  20. package/dist/components/layout/Accordion.js +25 -32
  21. package/dist/components/layout/Card.js +38 -21
  22. package/dist/components/layout/DetailDrawer.d.ts +9 -1
  23. package/dist/components/layout/DetailDrawer.js +60 -51
  24. package/dist/components/layout/Drawer.js +47 -39
  25. package/dist/components/layout/Modal.js +28 -23
  26. package/dist/components/layout/SettingsContainer.js +23 -13
  27. package/dist/components/layout/Sheet.d.ts +58 -0
  28. package/dist/components/layout/Sheet.js +146 -0
  29. package/dist/components/navigation/MobileNav.d.ts +44 -1
  30. package/dist/components/navigation/MobileNav.js +96 -70
  31. package/dist/components/navigation/Tabs.js +19 -12
  32. package/dist/components/ui/icon-loader.d.ts +1 -1
  33. package/dist/index.css +1 -1
  34. package/dist/index.d.ts +3 -0
  35. package/dist/index.js +218 -209
  36. package/dist/lib/hooks/useDialogFocus.d.ts +1 -0
  37. package/dist/lib/hooks/useDialogFocus.js +46 -0
  38. package/dist/lib/hooks/useSheetGesture.d.ts +81 -0
  39. package/dist/lib/hooks/useSheetGesture.js +70 -0
  40. package/dist/setupTests.d.ts +1 -0
  41. package/dist/wizardManifest.js +139 -14
  42. package/package.json +9 -2
  43. package/src/wizardManifest.ts +139 -14
  44. package/tailwind.preset.js +8 -0
@@ -1,24 +1,24 @@
1
- import { jsx as t } from "react/jsx-runtime";
2
- import { cn as d } from "../../utils/cn.js";
1
+ import { jsx as d } from "react/jsx-runtime";
2
+ import { cn as t } from "../../utils/cn.js";
3
3
  const m = ({
4
4
  options: n,
5
- value: i,
5
+ value: o,
6
6
  onChange: l,
7
7
  size: r = "md",
8
- className: o
9
- }) => /* @__PURE__ */ t("div", { className: d(
8
+ className: i
9
+ }) => /* @__PURE__ */ d("div", { className: t(
10
10
  // Container: Use 'muted' background for the track (works in all themes)
11
11
  "inline-flex bg-muted border border-border",
12
12
  r === "xs" ? "rounded-md p-0.5" : "rounded-lg p-1",
13
- o
13
+ i
14
14
  ), children: n.map((e) => {
15
- const s = e.value === i;
16
- return /* @__PURE__ */ t(
15
+ const s = e.value === o;
16
+ return /* @__PURE__ */ d(
17
17
  "button",
18
18
  {
19
19
  onClick: () => !e.disabled && l(e.value),
20
20
  disabled: e.disabled,
21
- className: d(
21
+ className: t(
22
22
  "relative rounded-md font-medium transition-all duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
23
23
  // Size Variants
24
24
  //
@@ -40,7 +40,7 @@ const m = ({
40
40
  // those two already clear AA and must not move.
41
41
  r === "xs" ? "inline-flex items-center justify-center min-h-6 text-2xs px-2 py-0.5" : r === "sm" ? "text-xs px-3 py-1" : "text-sm px-4 py-1.5",
42
42
  // State Styles (Theme Aware)
43
- s ? "bg-card text-foreground shadow-sm ring-1 ring-black/5 dark:ring-white/10" : "text-muted-foreground hover:text-foreground hover:bg-background/50",
43
+ s ? "bg-card text-foreground shadow-sm ring-1 ring-border" : "text-muted-foreground hover:text-foreground hover:bg-background/50",
44
44
  e.disabled && "opacity-50 cursor-not-allowed"
45
45
  ),
46
46
  type: "button",
@@ -7,21 +7,21 @@ const S = (a) => /* @__PURE__ */ e(I.DropdownIndicator, { ...a, children: /* @__
7
7
  options: a,
8
8
  value: l,
9
9
  onChange: i,
10
- label: c,
10
+ label: p,
11
11
  placeholder: m = "Select...",
12
12
  isDisabled: s = !1,
13
13
  isLoading: u = !1,
14
14
  isClearable: b = !0,
15
15
  isSearchable: g = !0,
16
16
  className: v,
17
- error: p,
18
- helperText: t
17
+ error: t,
18
+ helperText: d
19
19
  }) => {
20
- const d = h(), x = {
20
+ const c = h(), x = {
21
21
  control: (r, o) => ({
22
22
  ...r,
23
23
  backgroundColor: "rgb(var(--app-card-bg-color))",
24
- borderColor: p ? "rgb(var(--app-danger-color))" : o.isFocused ? "rgb(var(--app-primary-color))" : "rgb(var(--app-border-color))",
24
+ borderColor: t ? "rgb(var(--app-danger-color))" : o.isFocused ? "rgb(var(--app-primary-color))" : "rgb(var(--app-border-color))",
25
25
  color: "rgb(var(--app-text-color))",
26
26
  borderRadius: "var(--app-border-radius-md)",
27
27
  boxShadow: o.isFocused ? "0 0 0 1px rgb(var(--app-primary-color))" : "none",
@@ -65,11 +65,12 @@ const S = (a) => /* @__PURE__ */ e(I.DropdownIndicator, { ...a, children: /* @__
65
65
  })
66
66
  };
67
67
  return /* @__PURE__ */ f("div", { className: n("w-full", v), children: [
68
- c && /* @__PURE__ */ e("label", { htmlFor: d, className: "block text-sm font-medium text-foreground mb-1.5", children: c }),
68
+ p && /* @__PURE__ */ e("label", { htmlFor: c, className: "block text-sm font-medium text-foreground mb-1.5", children: p }),
69
69
  /* @__PURE__ */ e(
70
70
  y,
71
71
  {
72
- instanceId: d,
72
+ instanceId: c,
73
+ inputId: c,
73
74
  styles: x,
74
75
  components: { DropdownIndicator: S },
75
76
  options: a,
@@ -84,7 +85,7 @@ const S = (a) => /* @__PURE__ */ e(I.DropdownIndicator, { ...a, children: /* @__
84
85
  menuPosition: "fixed"
85
86
  }
86
87
  ),
87
- t && /* @__PURE__ */ e("p", { className: n("mt-1.5 text-xs", p ? "text-destructive" : "text-muted-foreground"), children: t })
88
+ d && /* @__PURE__ */ e("p", { className: n("mt-1.5 text-xs", t ? "text-destructive" : "text-muted-foreground"), children: d })
88
89
  ] });
89
90
  };
90
91
  export {
@@ -1,22 +1,24 @@
1
1
  import React from 'react';
2
- interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
2
+ export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
3
3
  label?: string;
4
+ helperText?: string;
5
+ error?: boolean;
6
+ required?: boolean;
7
+ showOptionalLabel?: boolean;
4
8
  containerClassName?: string;
5
9
  }
6
10
  /**
7
11
  * @wizard
8
12
  * @name Textarea
9
- * @description A theme-aware textarea component for multi-line text input.
10
- * @tags form, input, ui
11
- * @props
12
- * - name: label
13
- * type: string
14
- * description: An optional label to display above the textarea.
15
- * - name: containerClassName
16
- * type: string
17
- * description: Optional additional CSS classes for the container div.
13
+ * @description A theme-aware multi-line text input with label, helper text, and validation state support. Strategic purpose: the multi-line sibling of Input, styled identically so the two are indistinguishable when stacked in one form. Forwards all standard HTML textarea attributes via spread.
14
+ * @tags form, input, ui, text, multiline
18
15
  * @category form
19
16
  * @id textarea
17
+ * @param {string} label - Optional label displayed above the textarea.
18
+ * @param {string} helperText - Supporting text rendered below the textarea. Appears in destructive color when error is true.
19
+ * @param {boolean} error - If true, applies destructive border styling to indicate a validation error. Defaults to false.
20
+ * @param {boolean} required - If true, appends a required (*) indicator next to the label. Defaults to false.
21
+ * @param {boolean} showOptionalLabel - If true, appends an (optional) label when the field is not required. Defaults to false.
22
+ * @param {string} containerClassName - Additional CSS classes applied to the outer container div.
20
23
  */
21
24
  export declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
22
- export {};
@@ -1,19 +1,39 @@
1
- import { jsxs as n, jsx as o } from "react/jsx-runtime";
2
- import d from "react";
3
- const m = d.forwardRef(
4
- ({ label: r, className: t, containerClassName: a, ...e }, s) => /* @__PURE__ */ n("div", { className: `w-full ${a || ""}`, children: [
5
- r && /* @__PURE__ */ o("label", { htmlFor: e.id || e.name, className: "block text-sm font-medium text-muted-foreground mb-1", children: r }),
6
- /* @__PURE__ */ o(
7
- "textarea",
8
- {
9
- ref: s,
10
- className: `w-full px-3 py-2 rounded-md bg-card border border-border transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary ${t || ""}`,
11
- ...e
12
- }
13
- )
14
- ] })
1
+ import { jsxs as n, jsx as e } from "react/jsx-runtime";
2
+ import i from "react";
3
+ import { cn as o } from "../../utils/cn.js";
4
+ const p = i.forwardRef(
5
+ ({
6
+ label: r,
7
+ helperText: a,
8
+ error: d = !1,
9
+ required: t = !1,
10
+ showOptionalLabel: c = !1,
11
+ className: m,
12
+ containerClassName: f,
13
+ ...s
14
+ }, u) => {
15
+ const x = "w-full rounded-md border bg-background px-3 py-2 ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", b = d ? "border-destructive" : "border-input", g = i.useId(), l = s.id || s.name || g;
16
+ return /* @__PURE__ */ n("div", { className: o("w-full", f), children: [
17
+ r && /* @__PURE__ */ n("label", { htmlFor: l, className: "block text-sm font-medium text-foreground mb-1", children: [
18
+ r,
19
+ t && /* @__PURE__ */ e("span", { className: "text-destructive ml-1", children: "*" }),
20
+ !t && c && /* @__PURE__ */ e("span", { className: "text-xs text-muted-foreground ml-1", children: "(optional)" })
21
+ ] }),
22
+ /* @__PURE__ */ e(
23
+ "textarea",
24
+ {
25
+ ref: u,
26
+ id: l,
27
+ className: o(x, b, m),
28
+ required: t,
29
+ ...s
30
+ }
31
+ ),
32
+ a && /* @__PURE__ */ e("p", { className: o("mt-1.5 text-xs", d ? "text-destructive" : "text-muted-foreground"), children: a })
33
+ ] });
34
+ }
15
35
  );
16
- m.displayName = "Textarea";
36
+ p.displayName = "Textarea";
17
37
  export {
18
- m as Textarea
38
+ p as Textarea
19
39
  };
@@ -1,52 +1,45 @@
1
- import { jsx as t, jsxs as m } from "react/jsx-runtime";
2
- import d, { useState as u, useCallback as f } from "react";
3
- const x = ({ id: e, header: r, children: i, isOpen: n, onToggle: a }) => {
4
- const [c, o] = u(n !== void 0 ? n : !1), s = n !== void 0, l = s ? n : c, h = f(() => {
5
- s && a ? a(e, !l) : o((g) => !g);
6
- }, [e, l, s, a]), p = `transform transition-transform duration-300 ${l ? "rotate-180" : "rotate-0"}`;
1
+ import { jsx as o, jsxs as m } from "react/jsx-runtime";
2
+ import c, { useState as f, useCallback as u } from "react";
3
+ const w = ({ id: e, header: n, children: l, isOpen: t, onToggle: s }) => {
4
+ const [d, r] = f(t !== void 0 ? t : !1), a = t !== void 0, i = a ? t : d, h = u(() => {
5
+ a && s ? s(e, !i) : r((g) => !g);
6
+ }, [e, i, a, s]), p = `transform transition-transform duration-300 ${i ? "rotate-180" : "rotate-0"}`;
7
7
  return /* @__PURE__ */ m("div", { className: "border border-border rounded-md overflow-hidden", children: [
8
- /* @__PURE__ */ m(
8
+ /* @__PURE__ */ o("h3", { className: "m-0", children: /* @__PURE__ */ m(
9
9
  "button",
10
10
  {
11
11
  id: `accordion-header-${e}`,
12
- "aria-expanded": l,
12
+ "aria-expanded": i,
13
13
  "aria-controls": `accordion-panel-${e}`,
14
14
  onClick: h,
15
- className: "flex items-center justify-between w-full p-4 text-foreground bg-background hover:bg-background/80 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2",
16
- role: "heading",
17
- "aria-level": 3,
15
+ className: "flex items-center justify-between w-full p-4 text-left text-foreground bg-background hover:bg-background/80 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2",
18
16
  children: [
19
- /* @__PURE__ */ t("span", { className: "text-xl font-semibold", children: r }),
20
- /* @__PURE__ */ t("svg", { className: `w-6 h-6 text-foreground ${p}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ t("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M19 9l-7 7-7-7" }) })
17
+ /* @__PURE__ */ o("span", { className: "text-xl font-semibold", children: n }),
18
+ /* @__PURE__ */ o("svg", { className: `w-6 h-6 text-foreground shrink-0 ${p}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ o("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M19 9l-7 7-7-7" }) })
21
19
  ]
22
20
  }
23
- ),
24
- /* @__PURE__ */ t(
21
+ ) }),
22
+ /* @__PURE__ */ o(
25
23
  "div",
26
24
  {
27
25
  id: `accordion-panel-${e}`,
28
26
  role: "region",
29
27
  "aria-labelledby": `accordion-header-${e}`,
30
- style: {
31
- maxHeight: l ? "1000px" : "0",
32
- // Arbitrary large height for transition
33
- overflow: "hidden",
34
- transition: "max-height 0.3s ease-in-out"
35
- },
36
- children: /* @__PURE__ */ t("div", { className: "p-4 bg-card", children: i })
28
+ className: `grid transition-[grid-template-rows] duration-300 ease-in-out ${i ? "grid-rows-[1fr]" : "grid-rows-[0fr]"}`,
29
+ children: /* @__PURE__ */ o("div", { className: "overflow-hidden min-h-0", children: /* @__PURE__ */ o("div", { className: "p-4 bg-card", children: l }) })
37
30
  }
38
31
  )
39
32
  ] });
40
- }, w = ({ children: e, singleOpen: r = !1, className: i }) => {
41
- const [n, a] = u(null), c = f((o, s) => {
42
- r && a(s ? o : null);
43
- }, [r]);
44
- return /* @__PURE__ */ t("div", { className: `space-y-4 ${i || ""}`, children: d.Children.map(e, (o) => d.isValidElement(o) ? d.cloneElement(o, {
45
- isOpen: r ? o.props.id === n : void 0,
46
- onToggle: r ? c : void 0
47
- }) : o) });
33
+ }, x = ({ children: e, singleOpen: n = !1, className: l }) => {
34
+ const [t, s] = f(null), d = u((r, a) => {
35
+ n && s(a ? r : null);
36
+ }, [n]);
37
+ return /* @__PURE__ */ o("div", { className: `space-y-4 ${l || ""}`, children: c.Children.map(e, (r) => c.isValidElement(r) ? c.cloneElement(r, {
38
+ isOpen: n ? r.props.id === t : void 0,
39
+ onToggle: n ? d : void 0
40
+ }) : r) });
48
41
  };
49
42
  export {
50
- w as Accordion,
51
- x as AccordionItem
43
+ x as Accordion,
44
+ w as AccordionItem
52
45
  };
@@ -1,59 +1,76 @@
1
- import { jsx as d } from "react/jsx-runtime";
1
+ import { jsx as r } from "react/jsx-runtime";
2
2
  import { forwardRef as t } from "react";
3
3
  import { cn as s } from "../../utils/cn.js";
4
4
  const l = t(
5
- ({ className: e, children: a, ...r }, o) => /* @__PURE__ */ d(
5
+ ({ className: e, children: a, ...d }, o) => /* @__PURE__ */ r(
6
6
  "div",
7
7
  {
8
8
  ref: o,
9
9
  className: s(
10
- "@container bg-card text-card-foreground rounded-lg border border-border shadow-sm",
10
+ "@container bg-card text-card-foreground",
11
+ /* A card in a narrow container is chrome restating chrome: its border
12
+ sits a few pixels inside the panel's own edge, and its radius and
13
+ shadow buy nothing at 390px while its inset costs real width. So
14
+ below a 28rem container the card keeps its surface and drops the
15
+ frame — the rule above it still groups, the box no longer taxes.
16
+
17
+ A CONTAINER query, not a breakpoint: this asks how much room the
18
+ card actually has, so the same card is framed in a wide dashboard
19
+ column and flush inside a sheet or a narrow sidebar on that very
20
+ same screen. A `sm:` breakpoint would get the narrow-panel-on-a-
21
+ big-monitor case exactly backwards.
22
+
23
+ 28rem, not 24rem: inside a sheet a 390px phone gives the card a
24
+ ~358px container, but a 430px phone gives ~398px — over 24rem, so
25
+ the frame would come back on the LARGER phone. 28rem clears every
26
+ handset while leaving real desktop columns framed. */
27
+ "border-y border-border @md:rounded-lg @md:border @md:shadow-sm",
11
28
  e
12
29
  ),
13
- ...r,
30
+ ...d,
14
31
  children: a
15
32
  }
16
33
  )
17
34
  );
18
35
  l.displayName = "Card";
19
36
  const i = t(
20
- ({ className: e, ...a }, r) => /* @__PURE__ */ d(
37
+ ({ className: e, ...a }, d) => /* @__PURE__ */ r(
21
38
  "div",
22
39
  {
23
- ref: r,
40
+ ref: d,
24
41
  className: s("flex flex-col space-y-fluid-xs p-fluid-lg", e),
25
42
  ...a
26
43
  }
27
44
  )
28
45
  );
29
46
  i.displayName = "CardHeader";
30
- const n = t(
31
- ({ className: e, ...a }, r) => /* @__PURE__ */ d(
47
+ const m = t(
48
+ ({ className: e, ...a }, d) => /* @__PURE__ */ r(
32
49
  "h3",
33
50
  {
34
- ref: r,
51
+ ref: d,
35
52
  className: s("text-fluid-lg font-semibold leading-none tracking-tight", e),
36
53
  ...a
37
54
  }
38
55
  )
39
56
  );
40
- n.displayName = "CardTitle";
41
- const m = t(
42
- ({ className: e, ...a }, r) => /* @__PURE__ */ d(
57
+ m.displayName = "CardTitle";
58
+ const n = t(
59
+ ({ className: e, ...a }, d) => /* @__PURE__ */ r(
43
60
  "p",
44
61
  {
45
- ref: r,
62
+ ref: d,
46
63
  className: s("text-fluid-sm text-muted-foreground", e),
47
64
  ...a
48
65
  }
49
66
  )
50
67
  );
51
- m.displayName = "CardDescription";
68
+ n.displayName = "CardDescription";
52
69
  const c = t(
53
- ({ className: e, ...a }, r) => /* @__PURE__ */ d(
70
+ ({ className: e, ...a }, d) => /* @__PURE__ */ r(
54
71
  "h2",
55
72
  {
56
- ref: r,
73
+ ref: d,
57
74
  className: s(
58
75
  "text-xs font-semibold uppercase tracking-widest text-muted-foreground flex items-center gap-1.5",
59
76
  e
@@ -64,14 +81,14 @@ const c = t(
64
81
  );
65
82
  c.displayName = "CardLabel";
66
83
  const p = t(
67
- ({ className: e, ...a }, r) => /* @__PURE__ */ d("div", { ref: r, className: s("p-fluid-lg pt-0", e), ...a })
84
+ ({ className: e, ...a }, d) => /* @__PURE__ */ r("div", { ref: d, className: s("p-fluid-lg pt-0", e), ...a })
68
85
  );
69
86
  p.displayName = "CardContent";
70
87
  const f = t(
71
- ({ className: e, ...a }, r) => /* @__PURE__ */ d(
88
+ ({ className: e, ...a }, d) => /* @__PURE__ */ r(
72
89
  "div",
73
90
  {
74
- ref: r,
91
+ ref: d,
75
92
  className: s("flex items-center p-fluid-lg pt-0", e),
76
93
  ...a
77
94
  }
@@ -81,9 +98,9 @@ f.displayName = "CardFooter";
81
98
  export {
82
99
  l as Card,
83
100
  p as CardContent,
84
- m as CardDescription,
101
+ n as CardDescription,
85
102
  f as CardFooter,
86
103
  i as CardHeader,
87
104
  c as CardLabel,
88
- n as CardTitle
105
+ m as CardTitle
89
106
  };
@@ -8,13 +8,14 @@ import React from 'react';
8
8
  * @id detail-drawer
9
9
  * @param {boolean} isOpen - Whether the drawer is showing.
10
10
  * @param {function} onClose - Called when the drawer requests dismissal.
11
- * @param {string} variant - 'push' compresses sibling flex content so the list stays visible; 'overlay' floats fixed above the page. Defaults to push.
11
+ * @param {string} variant - 'push' compresses sibling flex content so the list stays visible; 'overlay' floats fixed above the page. Defaults to overlay.
12
12
  * @param {string} side - Which edge it docks to: 'left' or 'right'. Defaults to right.
13
13
  * @param {boolean} resizable - Enables the draggable handle on the inner border.
14
14
  * @param {number} width - Current width in px. Controlled — pair with onWidthChange.
15
15
  * @param {function} onWidthChange - Called with the new width while dragging.
16
16
  * @param {number} minWidth - Lower bound for resizing. Defaults to 320.
17
17
  * @param {number} maxWidth - Upper bound for resizing. Defaults to 60% of the window width.
18
+ * @param {string} title - Names the panel for assistive technology. Not rendered: there is no header row to put it in, and adding one is a design decision. Without it an overlay announces only as "dialog".
18
19
  * @param {ReactNode} children - The detail content.
19
20
  */
20
21
  export interface DetailDrawerProps {
@@ -28,5 +29,12 @@ export interface DetailDrawerProps {
28
29
  minWidth?: number;
29
30
  maxWidth?: number;
30
31
  side?: 'left' | 'right';
32
+ /**
33
+ * Names the panel for assistive technology. Not rendered — this component
34
+ * has no header row to put it in, and adding one is a visual change that is
35
+ * a designer's call, not this fix's. Without it an overlay announces only as
36
+ * "dialog" and a push panel as nothing at all.
37
+ */
38
+ title?: string;
31
39
  }
32
40
  export declare const DetailDrawer: React.FC<DetailDrawerProps>;
@@ -1,89 +1,98 @@
1
- import { jsxs as b, jsx as r, Fragment as k } from "react/jsx-runtime";
2
- import { useRef as E, useEffect as c } from "react";
3
- import { createPortal as D } from "react-dom";
4
- import { Icon as L } from "../ui/Icon.js";
5
- import { cn as h } from "../../utils/cn.js";
6
- const S = ({
7
- isOpen: o,
8
- onClose: n,
9
- children: g,
10
- variant: s = "overlay",
1
+ import { jsxs as p, jsx as o, Fragment as M } from "react/jsx-runtime";
2
+ import { useRef as g, useId as z, useEffect as x } from "react";
3
+ import { useDialogFocus as I } from "../../lib/hooks/useDialogFocus.js";
4
+ import { createPortal as $ } from "react-dom";
5
+ import { Icon as j } from "../ui/Icon.js";
6
+ import { cn as k } from "../../utils/cn.js";
7
+ import { useBodyScrollLock as S } from "../../lib/hooks/useBodyScrollLock.js";
8
+ const q = ({
9
+ isOpen: t,
10
+ onClose: i,
11
+ children: D,
12
+ variant: a = "overlay",
11
13
  resizable: u = !1,
12
- width: p = 420,
13
- onWidthChange: a,
14
+ width: E = 420,
15
+ onWidthChange: c,
14
16
  minWidth: m = 320,
15
17
  maxWidth: f = typeof window < "u" ? window.innerWidth * 0.6 : 800,
16
- side: t = "right"
18
+ side: n = "right",
19
+ title: l
17
20
  }) => {
18
- const i = E(!1);
19
- if (c(() => {
20
- if (!u || !a) return;
21
- const e = (y) => {
22
- if (!i.current) return;
23
- const x = t === "right" ? window.innerWidth - y.clientX : y.clientX;
24
- a(Math.max(m, Math.min(f, x)));
25
- }, d = () => {
26
- i.current && (i.current = !1, document.body.style.userSelect = "", document.body.style.cursor = "");
21
+ const d = g(!1), v = g(null), L = z();
22
+ if (I(t && a === "overlay", v), x(() => {
23
+ if (!u || !c) return;
24
+ const r = (h) => {
25
+ if (!d.current) return;
26
+ const N = n === "right" ? window.innerWidth - h.clientX : h.clientX;
27
+ c(Math.max(m, Math.min(f, N)));
28
+ }, s = () => {
29
+ d.current && (d.current = !1, document.body.style.userSelect = "", document.body.style.cursor = "");
27
30
  };
28
- return window.addEventListener("mousemove", e), window.addEventListener("mouseup", d), () => {
29
- window.removeEventListener("mousemove", e), window.removeEventListener("mouseup", d);
31
+ return window.addEventListener("mousemove", r), window.addEventListener("mouseup", s), () => {
32
+ window.removeEventListener("mousemove", r), window.removeEventListener("mouseup", s);
30
33
  };
31
- }, [u, a, m, f, t]), c(() => {
32
- const e = (d) => {
33
- d.key === "Escape" && o && n();
34
+ }, [u, c, m, f, n]), x(() => {
35
+ const r = (s) => {
36
+ s.key === "Escape" && t && i();
34
37
  };
35
- return window.addEventListener("keydown", e), () => window.removeEventListener("keydown", e);
36
- }, [o, n]), c(() => (o && s === "overlay" ? document.body.style.overflow = "hidden" : document.body.style.overflow = "", () => {
37
- document.body.style.overflow = "";
38
- }), [o, s]), !o) return null;
39
- const l = s === "overlay", w = t === "right" ? "right-0 border-l" : "left-0 border-r", v = /* @__PURE__ */ b(
38
+ return window.addEventListener("keydown", r), () => window.removeEventListener("keydown", r);
39
+ }, [t, i]), S(t && a === "overlay"), !t) return null;
40
+ const e = a === "overlay", w = L, b = n === "right" ? "right-0 border-l" : "left-0 border-r", y = /* @__PURE__ */ p(
40
41
  "div",
41
42
  {
42
- className: h(
43
+ ref: v,
44
+ role: e ? "dialog" : "region",
45
+ "aria-modal": e ? !0 : void 0,
46
+ tabIndex: e ? -1 : void 0,
47
+ "aria-labelledby": l ? w : void 0,
48
+ "aria-label": l ? void 0 : e ? "Detail panel" : "Detail",
49
+ className: k(
43
50
  "flex flex-col border-border bg-card shadow-2xl transition-transform duration-300",
44
- l ? `fixed inset-y-0 ${w} z-drawer w-full sm:w-[420px] animate-in ${t === "right" ? "slide-in-from-right" : "slide-in-from-left"}` : `relative h-full flex-shrink-0 ${w}`
51
+ e ? `fixed inset-y-0 ${b} z-drawer w-full sm:w-[420px] animate-in ${n === "right" ? "slide-in-from-right" : "slide-in-from-left"}` : `relative h-full flex-shrink-0 ${b}`
45
52
  ),
46
- style: l ? void 0 : { width: `min(100vw, ${p}px)` },
53
+ style: e ? void 0 : { width: `min(100vw, ${E}px)` },
47
54
  children: [
48
- u && !l && /* @__PURE__ */ r(
55
+ u && !e && /* @__PURE__ */ o(
49
56
  "div",
50
57
  {
51
- className: h(
58
+ className: k(
52
59
  "absolute top-0 bottom-0 w-1.5 cursor-col-resize hover:bg-primary/50 active:bg-primary z-10",
53
- t === "right" ? "left-0 -ml-0.5" : "right-0 -mr-0.5"
60
+ n === "right" ? "left-0 -ml-0.5" : "right-0 -mr-0.5"
54
61
  ),
55
- onMouseDown: (e) => {
56
- e.preventDefault(), i.current = !0, document.body.style.userSelect = "none", document.body.style.cursor = "col-resize";
62
+ onMouseDown: (r) => {
63
+ r.preventDefault(), d.current = !0, document.body.style.userSelect = "none", document.body.style.cursor = "col-resize";
57
64
  }
58
65
  }
59
66
  ),
60
- /* @__PURE__ */ r("div", { className: "flex justify-end p-2 absolute top-0 right-0 z-20", children: /* @__PURE__ */ r(
67
+ /* @__PURE__ */ o("div", { className: "flex justify-end p-2 absolute top-0 right-0 z-20", children: /* @__PURE__ */ o(
61
68
  "button",
62
69
  {
63
- onClick: n,
70
+ onClick: i,
64
71
  className: "rounded-full p-1.5 bg-background/50 backdrop-blur hover:bg-muted text-muted-foreground hover:text-foreground transition-all shadow-sm border border-border",
65
72
  "aria-label": "Close",
66
- children: /* @__PURE__ */ r(L, { name: "x", size: 16 })
73
+ children: /* @__PURE__ */ o(j, { name: "x", size: 16 })
67
74
  }
68
75
  ) }),
69
- /* @__PURE__ */ r("div", { className: "flex-1 overflow-y-auto custom-scrollbar relative h-full", children: g })
76
+ l && // sr-only, not a visible heading: see the `title` prop's note above.
77
+ /* @__PURE__ */ o("h2", { id: w, className: "sr-only", children: l }),
78
+ /* @__PURE__ */ o("div", { className: "flex-1 overflow-y-auto custom-scrollbar relative h-full", children: D })
70
79
  ]
71
80
  }
72
81
  );
73
- return l ? typeof document > "u" ? null : D(
74
- /* @__PURE__ */ b(k, { children: [
75
- /* @__PURE__ */ r(
82
+ return e ? typeof document > "u" ? null : $(
83
+ /* @__PURE__ */ p(M, { children: [
84
+ /* @__PURE__ */ o(
76
85
  "div",
77
86
  {
78
87
  className: "fixed inset-0 z-scrim bg-background/80 backdrop-blur-sm transition-opacity",
79
- onClick: n
88
+ onClick: i
80
89
  }
81
90
  ),
82
- v
91
+ y
83
92
  ] }),
84
93
  document.body
85
- ) : v;
94
+ ) : y;
86
95
  };
87
96
  export {
88
- S as DetailDrawer
97
+ q as DetailDrawer
89
98
  };