@stackone/malachite 2.61.0 → 2.62.1

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/COMPONENTS.md CHANGED
@@ -26,8 +26,8 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
26
26
  - **`OverflowRow`** — Horizontal row that collapses items that do not fit into an overflow affordance.
27
27
  - **`ActionBar`** — A row of actions that collapses into a single `ActionMenu` once its container drops below `collapseBelow`. Measures the nearest genuinely-wider ancestor rather than itself, since the row shrinks to its own content. `dividerBefore` renders a vertical rule in the row and a divider in the collapsed menu, so both forms group the actions the same way. _Key props:_ `buttons` (`label`, `onClick`, `icon`, `variant`, `size`, `radius`, `tooltip`, `disabled`, `dividerBefore`), `collapseBelow`, `menuLabel`, `className`.
28
28
  - **`Popover`** — An anchored, non-modal panel for arbitrary content — a workspace switcher, an org flyout. The panel renders in its own shadow root, so malachite components inside it are styled, and joins the top layer like the other overlays. It prefers its `placement` and flips to the opposite side when there is no room, clamping along the other axis. Click-opened panels take focus and hand it back to the trigger on close; `openOn="hover"` also opens on focus and stays open while the pointer or focus is in the panel. A nested Popover counts as inside its parent for outside-press and hover dismissal, and Escape closes the topmost only. A click panel also closes when focus moves somewhere outside it and its trigger, and on close it hands focus back to the trigger only if focus would otherwise fall to the page. _Key props:_ `trigger`, `children`, `open`, `defaultOpen`, `onOpenChange`, `placement` (`bottom-start` | `bottom-end` | `top-start` | `top-center` | `right-start` | `right-end` | `left-start`), `openOn` (`click` | `hover`), `width` (`content` | `trigger`), `padding` (`none` | `small` | `medium`), `anchorRef` (position against another element, e.g. the section a trigger row sits in), `offset` (`small` 8px | `medium` 12px | `large` 14px), `elevation` (`default` | `raised`), `initialFocusRef` (what takes focus on open, in place of the first focusable), `role` (`dialog` | `menu` | `listbox`; `menu` brings no roving focus of its own), `aria-label` or `aria-labelledby` (one is required), `className`. The panel is capped to the viewport width, and only overlays opened from inside it (menus, dropdowns, modals) count as inside for outside-press dismissal. A side placement stays level with its anchor and scrolls within the room below, sliding up only when under 160px is left. Parts for the panel body: `Popover.Header` (`title`, `action`), `Popover.Action` (`icon`, `aria-label`, `onClick`, `href` (a link that opens in a new tab on Cmd/Ctrl-click or a middle click; a plain click runs `onClick` when one is given), `disabled`, `tooltip`), `Popover.List` (a scrolling list), `Popover.Section` (a bordered section) and `Popover.Empty` (an empty-state line).
29
- - **`Modal`** — Centred dialog over a scrim, with header slots (icon/title/subtitle/extra), a scrolling body and a pinned footer. Locks background scroll and dismisses on ESC, the close button, or a scrim click unless `closeOnOverlayClick` is off; `busy` suppresses the two pointer routes while a request is in flight and sets `aria-busy`, so a stray click cannot tear the dialog down mid-write — but never ESC, which is the only guaranteed keyboard exit from a focus trap and so always reaches `onClose`. ESC, the Tab focus trap and `aria-modal` all apply to the topmost overlay only, so these may be stacked — a dialog opened from a panel keeps Tab to itself and the panel stops claiming to be modal. The Tab trap follows focus into a malachite popup portaled out of the dialog’s own tree — a `MultiSelect` listbox or a `Popover` opened from the body — and traps Tab within that popup while it holds focus, so Tab walks and wraps that popup’s own stops rather than being yanked back to the dialog’s first control or escaping the dialog altogether. A `Dropdown` list or an `ActionMenu` is dismissed by Tab rather than trapping it: it closes and focus returns to its trigger, so Tab carries on from there. A consumer’s own portaled popup is not known to the trap and Tab will pull focus out of it. Set `role="alertdialog"` for a consequential choice, which asks assistive tech to announce the body and not just the name, and point `aria-describedby` at whichever element carries it. Width comes from the `size` union (`small` 420px | `medium` 612px | `large` 760px) rather than a raw pixel prop. `titleAdornment` sits immediately after the title behind a hairline divider, for naming the thing being acted on — distinct from `headerExtra`, which pins to the far right. `tray` paints a tinted plate around the card and hangs a `caption` beneath it; the plate is the dialog element itself, so that caption stays inside the `aria-modal` subtree rather than being stranded outside it by the visual nesting, and it is added to `aria-describedby` automatically because it says what the dialog is asking. It takes a tone (`neutral` | `warning` | `error` | `success`) rather than a colour, so no consumer can paint it off-palette — and the tone sets the plate and its caption colour together, so the two cannot disagree. The caption is a `{ label?, message }` pair rather than a node, because its row height, type scale and colour belong to the plate. `size` names the card, and the plate’s 4px ring is added on top. Like `SidePanel` it sits in a z-index band from 1000 — one step per stacked overlay, so what paints on top is what holds the keyboard — and stays out of the browser top layer, so a host app’s own dialogs can still cover it. For a confirmation use `ConfirmationDialog`, which is a preset over this. _Key props:_ `open`, `onClose`, `title`, `subtitle`, `icon`, `headerExtra`, `titleAdornment`, `tray`, `headerDivider`, `footer`, `size`, `showCloseButton`, `closeOnOverlayClick`, `bodyPadding`, `busy`, `role` (`dialog` | `alertdialog`), `aria-label`, `aria-labelledby`, `aria-describedby`.
30
- - **`SidePanel`** — Right-hand slide-in overlay panel (drawer) with header slots (back/icon/title/subtitle/extra), scrollable body and optional footer. Locks background scroll and dismisses on ESC, an overlay click or close. ESC and the Tab focus trap apply to the topmost overlay only, so a `Modal` may be stacked over it. The Tab trap follows focus into a malachite popup portaled out of the panel’s own tree — a `MultiSelect` listbox or a `Popover` opened from the body — and traps Tab within that popup while it holds focus, so Tab walks and wraps that popup’s own stops rather than being yanked back to the panel’s first control or escaping the panel altogether. A `Dropdown` list or an `ActionMenu` is dismissed by Tab rather than trapping it: it closes and focus returns to its trigger, so Tab carries on from there. A consumer’s own portaled popup is not known to the trap and Tab will pull focus out of it. `callouts` stacks full-bleed `Alert` bands at the top of the body, above `children` and unaffected by `bodyPadding` — the same band `ConfirmationDialog` gives its `callout` — and `renderLink` routes their root-relative markdown links through the app’s router.
29
+ - **`Modal`** — Centred dialog over a scrim, with header slots (icon/title/subtitle/extra), a scrolling body and a pinned footer. Locks background scroll and dismisses on ESC, the close button, or a scrim click unless `closeOnOverlayClick` is off; `busy` suppresses the two pointer routes while a request is in flight and sets `aria-busy`, so a stray click cannot tear the dialog down mid-write — but never ESC, which is the only guaranteed keyboard exit from a focus trap and so always reaches `onClose`. ESC, the Tab focus trap and `aria-modal` all apply to the topmost overlay only, so these may be stacked — a dialog opened from a panel keeps Tab to itself and the panel stops claiming to be modal. The Tab trap follows focus into a malachite popup portaled out of the dialog’s own tree — a `MultiSelect` listbox or a `Popover` opened from the body — and traps Tab within that popup while it holds focus, so Tab walks and wraps that popup’s own stops rather than being yanked back to the dialog’s first control or escaping the dialog altogether. A `Dropdown` list or an `ActionMenu` is dismissed by Tab rather than trapping it: it closes and focus returns to its trigger, so Tab carries on from there. A consumer’s own portaled popup is not known to the trap and Tab will pull focus out of it. Set `role="alertdialog"` for a consequential choice, which asks assistive tech to announce the body and not just the name, and point `aria-describedby` at whichever element carries it. Width comes from the `size` union (`small` 420px | `medium` 612px | `large` 760px) rather than a raw pixel prop. `titleAdornment` sits immediately after the title behind a hairline divider, for naming the thing being acted on — distinct from `headerExtra`, which pins to the far right. `tray` paints a tinted plate around the card and hangs a `caption` beneath it; the plate is the dialog element itself, so that caption stays inside the `aria-modal` subtree rather than being stranded outside it by the visual nesting, and it is added to `aria-describedby` automatically because it says what the dialog is asking. It takes a tone (`neutral` | `warning` | `error` | `success`) rather than a colour, so no consumer can paint it off-palette — and the tone sets the plate and its caption colour together, so the two cannot disagree. The caption is a `{ label?, message }` pair rather than a node, because its row height, type scale and colour belong to the plate. `size` names the card, and the plate’s 4px ring is added on top. Like `SidePanel` it sits in a z-index band from 1000 — one step per stacked overlay, so what paints on top is what holds the keyboard — and stays out of the browser top layer, so a host app’s own dialogs can still cover it. `callouts` stacks full-bleed `Alert` bands at the top of the body, as on `SidePanel`; they ignore `bodyPadding` and lead `aria-describedby`, ahead of the consumer’s own description and the tray caption. For a confirmation use `ConfirmationDialog`, which is a preset over this. _Key props:_ `open`, `onClose`, `title`, `subtitle`, `icon`, `headerExtra`, `titleAdornment`, `tray`, `callouts`, `renderLink`, `headerDivider`, `footer`, `size`, `showCloseButton`, `closeOnOverlayClick`, `bodyPadding`, `busy`, `role` (`dialog` | `alertdialog`), `aria-label`, `aria-labelledby`, `aria-describedby`.
30
+ - **`SidePanel`** — Right-hand slide-in overlay panel (drawer) with header slots (back/icon/title/subtitle/extra), scrollable body and optional footer. Locks background scroll and dismisses on ESC, an overlay click or close. ESC and the Tab focus trap apply to the topmost overlay only, so a `Modal` may be stacked over it. The Tab trap follows focus into a malachite popup portaled out of the panel’s own tree — a `MultiSelect` listbox or a `Popover` opened from the body — and traps Tab within that popup while it holds focus, so Tab walks and wraps that popup’s own stops rather than being yanked back to the panel’s first control or escaping the panel altogether. A `Dropdown` list or an `ActionMenu` is dismissed by Tab rather than trapping it: it closes and focus returns to its trigger, so Tab carries on from there. A consumer’s own portaled popup is not known to the trap and Tab will pull focus out of it. `callouts` stacks full-bleed `Alert` bands at the top of the body, above `children` and unaffected by `bodyPadding` — the same band `ConfirmationDialog` gives its `callout` — and `renderLink` routes their root-relative markdown links through the app’s router. The callouts are announced as the panel’s description, ahead of `description`.
31
31
  - **`useHasOpenOverlay`** — Hook reporting whether any modal-type overlay - `SidePanel` or `Modal` - is open anywhere in the app. For host chrome that has to get out of an overlay’s way, a floating support widget being the case it was added for: overlays render through a portal, so a host has no DOM signal of its own to read, and reading the library’s internal markers would couple it to something it does not own. Dropdowns and tooltips are deliberately not counted - they do not trap focus, and chrome does not need to move for them. _Key props:_ No arguments. Returns a boolean.
32
32
  - **`SidePanelNavFooter`** — Keyboard-hint footer for SidePanel: up/down keycaps to step between records and an esc-to-close hint, with an optional right-aligned actions slot.
33
33
 
@@ -1,109 +1,118 @@
1
- import { jsxs as g, jsx as o } from "react/jsx-runtime";
2
- import { useRef as A, useState as C, useEffect as D } from "react";
3
- import { GLASS_SURFACE_STYLE as H } from "./glassSurface.js";
4
- const T = ({
1
+ import { jsxs as A, jsx as c } from "react/jsx-runtime";
2
+ import { useRef as H, useState as P, useEffect as _ } from "react";
3
+ import { GLASS_SURFACE_STYLE as g } from "./glassSurface.js";
4
+ const D = /^\d*\.?\d+(px|rem|em)$/, G = (a) => {
5
+ const o = a.trim().split(/\s+/);
6
+ if (o.length > 4 || !o.every((p) => D.test(p)))
7
+ return;
8
+ const [r, n = r, , d = n] = o;
9
+ return parseFloat(d) > 0 && parseFloat(n) > 0 ? { paddingLeft: d, paddingRight: n } : void 0;
10
+ }, U = ({
5
11
  title: a,
6
- children: u,
7
- padding: d = "20px",
8
- size: r = "default",
9
- width: v,
10
- minWidth: y,
11
- dimmed: b = !1,
12
- direction: c,
12
+ children: o,
13
+ padding: r = "20px",
14
+ size: n = "default",
15
+ width: d,
16
+ minWidth: p,
17
+ dimmed: R = !1,
18
+ direction: f,
13
19
  align: h,
14
- fullHeight: f = !1,
15
- border: i = "base",
16
- alignSelf: R,
17
- height: s,
18
- footer: e,
19
- glassFooter: t = !1,
20
- className: S = "",
21
- style: B = {},
22
- headerConfig: l = {}
20
+ fullHeight: x = !1,
21
+ border: m = "base",
22
+ alignSelf: S,
23
+ height: i,
24
+ footer: t,
25
+ glassFooter: e = !1,
26
+ className: N = "",
27
+ style: w = {},
28
+ headerConfig: s = {}
23
29
  }) => {
24
- const n = A(null), [m, p] = C(0);
25
- D(() => {
26
- if (t && e && n.current) {
27
- const x = new ResizeObserver((E) => {
28
- for (const L of E)
29
- p(L.contentRect.height);
30
+ const l = H(null), [u, v] = P(0);
31
+ _(() => {
32
+ if (e && t && l.current) {
33
+ const b = new ResizeObserver((E) => {
34
+ for (const j of E)
35
+ v(j.contentRect.height);
30
36
  });
31
- return x.observe(n.current), p(n.current.offsetHeight), () => x.disconnect();
37
+ return b.observe(l.current), v(l.current.offsetHeight), () => b.disconnect();
32
38
  }
33
- }, [t, e]);
34
- const N = [
39
+ }, [e, t]);
40
+ const L = [
35
41
  "card-base",
36
- b ? "card-dimmed" : "",
37
- i === "rounded" ? "card-rounded" : "",
38
- f ? "h-full" : "",
39
- s ? "flex flex-col" : "",
40
- r === "small" ? "text-sm" : "",
41
- S
42
- ].filter(Boolean).join(" "), w = parseInt(d) || 20, j = {
43
- padding: d,
44
- display: c ? "flex" : void 0,
45
- flexDirection: c,
42
+ R ? "card-dimmed" : "",
43
+ m === "rounded" ? "card-rounded" : "",
44
+ x ? "h-full" : "",
45
+ i ? "flex flex-col" : "",
46
+ n === "small" ? "text-sm" : "",
47
+ N
48
+ ].filter(Boolean).join(" "), y = s != null && s.padding ? void 0 : G(r), B = parseInt(r) || 20, I = {
49
+ padding: r,
50
+ // The card clips overflow, so a long token in flowing text breaks rather than vanishing.
51
+ // Flex and grid items keep their min-content width; those need their own wrapping.
52
+ overflowWrap: "break-word",
53
+ display: f ? "flex" : void 0,
54
+ flexDirection: f,
46
55
  alignItems: h,
47
- ...s && { overflow: "auto", height: "100%" },
48
- ...t && e && m > 0 && {
49
- paddingBottom: `${w + m + 10}px`
56
+ ...i && { overflow: "auto", height: "100%" },
57
+ ...e && t && u > 0 && {
58
+ paddingBottom: `${B + u + 10}px`
50
59
  }
51
60
  }, z = {
52
- width: v,
53
- minWidth: y,
54
- alignSelf: R,
55
- ...f && { height: "100%" },
56
- ...s && { height: s, display: "flex", flexDirection: "column" },
57
- ...t && e && { position: "relative" },
61
+ width: d,
62
+ minWidth: p,
63
+ alignSelf: S,
64
+ ...x && { height: "100%" },
65
+ ...i && { height: i, display: "flex", flexDirection: "column" },
66
+ ...e && t && { position: "relative" },
58
67
  overflow: "hidden",
59
- ...B
68
+ ...w
60
69
  };
61
- return /* @__PURE__ */ g("div", { className: N, style: z, children: [
62
- a && /* @__PURE__ */ o(
70
+ return /* @__PURE__ */ A("div", { className: L, style: z, children: [
71
+ a && /* @__PURE__ */ c(
63
72
  "div",
64
73
  {
65
- className: "card-header px-3 py-2",
74
+ className: y ? "card-header py-2" : "card-header px-3 py-2",
66
75
  style: {
67
76
  marginBottom: "1px",
68
- fontSize: r === "small" ? "12px" : "13px",
77
+ fontSize: n === "small" ? "12px" : "13px",
69
78
  fontWeight: "normal",
70
- padding: l == null ? void 0 : l.padding
79
+ ...s != null && s.padding ? { padding: s.padding } : y
71
80
  },
72
- children: /* @__PURE__ */ o("div", { className: "font-normal overflow-visible", children: a })
81
+ children: /* @__PURE__ */ c("div", { className: "font-normal overflow-visible", children: a })
73
82
  }
74
83
  ),
75
- /* @__PURE__ */ o(
84
+ /* @__PURE__ */ c(
76
85
  "div",
77
86
  {
78
87
  className: "flex-1",
79
88
  style: {
80
- ...j,
81
- ...t && e && { position: "relative" }
89
+ ...I,
90
+ ...e && t && { position: "relative" }
82
91
  },
83
- children: u
92
+ children: o
84
93
  }
85
94
  ),
86
- e && /* @__PURE__ */ o(
95
+ t && /* @__PURE__ */ c(
87
96
  "div",
88
97
  {
89
- ref: t ? n : void 0,
98
+ ref: e ? l : void 0,
90
99
  className: "card-footer px-4 py-3",
91
- style: t ? {
92
- ...H,
100
+ style: e ? {
101
+ ...g,
93
102
  position: "absolute",
94
103
  bottom: 0,
95
104
  left: 0,
96
105
  right: 0,
97
- borderBottomLeftRadius: i === "rounded" ? "20px" : "12px",
98
- borderBottomRightRadius: i === "rounded" ? "20px" : "12px"
106
+ borderBottomLeftRadius: m === "rounded" ? "20px" : "12px",
107
+ borderBottomRightRadius: m === "rounded" ? "20px" : "12px"
99
108
  } : void 0,
100
- children: e
109
+ children: t
101
110
  }
102
111
  )
103
112
  ] });
104
113
  };
105
114
  export {
106
- T as Card,
107
- T as default
115
+ U as Card,
116
+ U as default
108
117
  };
109
118
  //# sourceMappingURL=Card.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Card.js","sources":["../../src/components/Card.tsx"],"sourcesContent":["import React, { useRef, useEffect, useState } from 'react';\nimport { GLASS_SURFACE_STYLE } from './glassSurface';\n\ninterface CardProps {\n title?: string | React.ReactNode;\n padding?: string;\n children?: React.ReactNode;\n size?: 'default' | 'small';\n width?: string;\n minWidth?: string;\n direction?: 'row' | 'column';\n align?: 'center';\n dimmed?: boolean;\n fullHeight?: boolean;\n border?: 'base' | 'rounded';\n alignSelf?: 'center';\n height?: string;\n footer?: React.ReactNode;\n glassFooter?: boolean;\n className?: string;\n style?: React.CSSProperties;\n headerConfig?: {\n padding?: string;\n };\n}\n\nconst Card: React.FC<CardProps> = ({\n title,\n children,\n padding = '20px',\n size = 'default',\n width,\n minWidth,\n dimmed = false,\n direction,\n align,\n fullHeight = false,\n border = 'base',\n alignSelf,\n height,\n footer,\n glassFooter = false,\n className = '',\n style = {},\n headerConfig = {},\n}) => {\n const footerRef = useRef<HTMLDivElement>(null);\n const [footerHeight, setFooterHeight] = useState(0);\n\n useEffect(() => {\n if (glassFooter && footer && footerRef.current) {\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n setFooterHeight(entry.contentRect.height);\n }\n });\n\n observer.observe(footerRef.current);\n setFooterHeight(footerRef.current.offsetHeight);\n\n return () => observer.disconnect();\n }\n }, [glassFooter, footer]);\n\n // Size-specific padding\n const sizeClasses = size === 'small' ? 'text-sm' : '';\n\n // Dynamic classes based on props\n const cardClasses = [\n 'card-base',\n dimmed ? 'card-dimmed' : '',\n border === 'rounded' ? 'card-rounded' : '',\n fullHeight ? 'h-full' : '',\n height ? 'flex flex-col' : '',\n sizeClasses,\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n // Content styles\n const paddingValue = parseInt(padding) || 20;\n const extraPadding = 10;\n const contentStyles: React.CSSProperties = {\n padding,\n display: direction ? 'flex' : undefined,\n flexDirection: direction,\n alignItems: align,\n ...(height && { overflow: 'auto', height: '100%' }),\n ...(glassFooter &&\n footer &&\n footerHeight > 0 && {\n paddingBottom: `${paddingValue + footerHeight + extraPadding}px`,\n }),\n };\n\n // Card container styles\n const cardContainerStyles: React.CSSProperties = {\n width,\n minWidth,\n alignSelf,\n ...(fullHeight && { height: '100%' }),\n ...(height && { height, display: 'flex', flexDirection: 'column' }),\n ...(glassFooter && footer && { position: 'relative' }),\n overflow: 'hidden',\n ...style,\n };\n\n return (\n <div className={cardClasses} style={cardContainerStyles}>\n {/* Header */}\n {title && (\n <div\n className=\"card-header px-3 py-2\"\n style={{\n marginBottom: '1px',\n fontSize: size === 'small' ? '12px' : '13px',\n fontWeight: 'normal',\n padding: headerConfig?.padding,\n }}\n >\n <div className=\"font-normal overflow-visible\">{title}</div>\n </div>\n )}\n\n {/* Body */}\n <div\n className=\"flex-1\"\n style={{\n ...contentStyles,\n ...(glassFooter && footer && { position: 'relative' }),\n }}\n >\n {children}\n </div>\n\n {/* Footer */}\n {footer && (\n <div\n ref={glassFooter ? footerRef : undefined}\n className=\"card-footer px-4 py-3\"\n style={\n glassFooter\n ? {\n ...GLASS_SURFACE_STYLE,\n position: 'absolute',\n bottom: 0,\n left: 0,\n right: 0,\n borderBottomLeftRadius: border === 'rounded' ? '20px' : '12px',\n borderBottomRightRadius: border === 'rounded' ? '20px' : '12px',\n }\n : undefined\n }\n >\n {footer}\n </div>\n )}\n </div>\n );\n};\n\nexport default Card;\nexport { Card };\n"],"names":["Card","title","children","padding","size","width","minWidth","dimmed","direction","align","fullHeight","border","alignSelf","height","footer","glassFooter","className","style","headerConfig","footerRef","useRef","footerHeight","setFooterHeight","useState","useEffect","observer","entries","entry","cardClasses","paddingValue","contentStyles","cardContainerStyles","jsxs","jsx","GLASS_SURFACE_STYLE"],"mappings":"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"}
1
+ {"version":3,"file":"Card.js","sources":["../../src/components/Card.tsx"],"sourcesContent":["import React, { useRef, useEffect, useState } from 'react';\nimport { GLASS_SURFACE_STYLE } from './glassSurface';\n\ninterface CardProps {\n title?: string | React.ReactNode;\n padding?: string;\n children?: React.ReactNode;\n size?: 'default' | 'small';\n width?: string;\n minWidth?: string;\n direction?: 'row' | 'column';\n align?: 'center';\n dimmed?: boolean;\n fullHeight?: boolean;\n border?: 'base' | 'rounded';\n alignSelf?: 'center';\n height?: string;\n footer?: React.ReactNode;\n glassFooter?: boolean;\n className?: string;\n style?: React.CSSProperties;\n headerConfig?: {\n padding?: string;\n };\n}\n\nconst PLAIN_LENGTH = /^\\d*\\.?\\d+(px|rem|em)$/;\n\n// Only plain positive lengths are copied: calc() and var() can't be split from a shorthand, and a\n// zero inset would put the title against the border, so those keep the header's own 12px.\nconst horizontalInset = (padding: string): React.CSSProperties | undefined => {\n const parts = padding.trim().split(/\\s+/);\n if (parts.length > 4 || !parts.every((part) => PLAIN_LENGTH.test(part))) {\n return undefined;\n }\n const [top, right = top, , left = right] = parts;\n return parseFloat(left) > 0 && parseFloat(right) > 0\n ? { paddingLeft: left, paddingRight: right }\n : undefined;\n};\n\nconst Card: React.FC<CardProps> = ({\n title,\n children,\n padding = '20px',\n size = 'default',\n width,\n minWidth,\n dimmed = false,\n direction,\n align,\n fullHeight = false,\n border = 'base',\n alignSelf,\n height,\n footer,\n glassFooter = false,\n className = '',\n style = {},\n headerConfig = {},\n}) => {\n const footerRef = useRef<HTMLDivElement>(null);\n const [footerHeight, setFooterHeight] = useState(0);\n\n useEffect(() => {\n if (glassFooter && footer && footerRef.current) {\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n setFooterHeight(entry.contentRect.height);\n }\n });\n\n observer.observe(footerRef.current);\n setFooterHeight(footerRef.current.offsetHeight);\n\n return () => observer.disconnect();\n }\n }, [glassFooter, footer]);\n\n // Size-specific padding\n const sizeClasses = size === 'small' ? 'text-sm' : '';\n\n // Dynamic classes based on props\n const cardClasses = [\n 'card-base',\n dimmed ? 'card-dimmed' : '',\n border === 'rounded' ? 'card-rounded' : '',\n fullHeight ? 'h-full' : '',\n height ? 'flex flex-col' : '',\n sizeClasses,\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const headerInset = headerConfig?.padding ? undefined : horizontalInset(padding);\n\n // Content styles\n const paddingValue = parseInt(padding) || 20;\n const extraPadding = 10;\n const contentStyles: React.CSSProperties = {\n padding,\n // The card clips overflow, so a long token in flowing text breaks rather than vanishing.\n // Flex and grid items keep their min-content width; those need their own wrapping.\n overflowWrap: 'break-word',\n display: direction ? 'flex' : undefined,\n flexDirection: direction,\n alignItems: align,\n ...(height && { overflow: 'auto', height: '100%' }),\n ...(glassFooter &&\n footer &&\n footerHeight > 0 && {\n paddingBottom: `${paddingValue + footerHeight + extraPadding}px`,\n }),\n };\n\n // Card container styles\n const cardContainerStyles: React.CSSProperties = {\n width,\n minWidth,\n alignSelf,\n ...(fullHeight && { height: '100%' }),\n ...(height && { height, display: 'flex', flexDirection: 'column' }),\n ...(glassFooter && footer && { position: 'relative' }),\n overflow: 'hidden',\n ...style,\n };\n\n return (\n <div className={cardClasses} style={cardContainerStyles}>\n {/* Header */}\n {title && (\n <div\n className={headerInset ? 'card-header py-2' : 'card-header px-3 py-2'}\n style={{\n marginBottom: '1px',\n fontSize: size === 'small' ? '12px' : '13px',\n fontWeight: 'normal',\n ...(headerConfig?.padding\n ? { padding: headerConfig.padding }\n : headerInset),\n }}\n >\n <div className=\"font-normal overflow-visible\">{title}</div>\n </div>\n )}\n\n {/* Body */}\n <div\n className=\"flex-1\"\n style={{\n ...contentStyles,\n ...(glassFooter && footer && { position: 'relative' }),\n }}\n >\n {children}\n </div>\n\n {/* Footer */}\n {footer && (\n <div\n ref={glassFooter ? footerRef : undefined}\n className=\"card-footer px-4 py-3\"\n style={\n glassFooter\n ? {\n ...GLASS_SURFACE_STYLE,\n position: 'absolute',\n bottom: 0,\n left: 0,\n right: 0,\n borderBottomLeftRadius: border === 'rounded' ? '20px' : '12px',\n borderBottomRightRadius: border === 'rounded' ? '20px' : '12px',\n }\n : undefined\n }\n >\n {footer}\n </div>\n )}\n </div>\n );\n};\n\nexport default Card;\nexport { Card };\n"],"names":["PLAIN_LENGTH","horizontalInset","padding","parts","part","top","right","left","Card","title","children","size","width","minWidth","dimmed","direction","align","fullHeight","border","alignSelf","height","footer","glassFooter","className","style","headerConfig","footerRef","useRef","footerHeight","setFooterHeight","useState","useEffect","observer","entries","entry","cardClasses","headerInset","paddingValue","contentStyles","cardContainerStyles","jsxs","jsx","GLASS_SURFACE_STYLE"],"mappings":"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"}
@@ -1,141 +1,148 @@
1
- import { jsx as r, jsxs as a, Fragment as D } from "react/jsx-runtime";
2
- import { useId as Z, useState as f, useRef as i, useEffect as j, useCallback as P } from "react";
3
- import { createPortal as J } from "react-dom";
4
- import { ShadowRootWrapper as Q } from "../ShadowRootWrapper.js";
5
- import ee from "./CustomIcons/DismissIcon.js";
6
- import { getDeepActiveElement as re } from "./activeElement.js";
7
- import { GLASS_SURFACE_STYLE as ae } from "./glassSurface.js";
8
- import { MODAL_TRAY_BACKGROUND as le, MODAL_TRAY_CAPTION_COLOR as ie } from "./modalTray.js";
9
- import { useOverlayBehaviour as te, OVERLAY_ICON_BUTTON_CLASSES as ne } from "./overlayBehaviour.js";
10
- import { OVERLAY_ELEVATED_ATTR as oe, overlayIdentity as V } from "./portalOverlay.js";
11
- const Y = {
1
+ import { jsx as r, jsxs as l, Fragment as P } from "react/jsx-runtime";
2
+ import { useId as V, useState as h, useRef as n, useEffect as Y, useCallback as B } from "react";
3
+ import { createPortal as ae } from "react-dom";
4
+ import { ShadowRootWrapper as le } from "../ShadowRootWrapper.js";
5
+ import te from "./CalloutStrip.js";
6
+ import ie from "./CustomIcons/DismissIcon.js";
7
+ import { getDeepActiveElement as ne } from "./activeElement.js";
8
+ import { GLASS_SURFACE_STYLE as oe } from "./glassSurface.js";
9
+ import { MODAL_TRAY_BACKGROUND as se, MODAL_TRAY_CAPTION_COLOR as ce } from "./modalTray.js";
10
+ import { useOverlayBehaviour as de, OVERLAY_ICON_BUTTON_CLASSES as me } from "./overlayBehaviour.js";
11
+ import { OVERLAY_ELEVATED_ATTR as ue, overlayIdentity as F } from "./portalOverlay.js";
12
+ const U = {
12
13
  small: "420px",
13
14
  medium: "612px",
14
15
  large: "760px"
15
- }, x = 200, be = ({
16
- open: s,
17
- onClose: c,
18
- title: d,
19
- subtitle: m,
20
- icon: h,
21
- headerExtra: v,
22
- titleAdornment: b,
16
+ }, v = 200, Te = ({
17
+ open: c,
18
+ onClose: d,
19
+ title: u,
20
+ subtitle: p,
21
+ icon: b,
22
+ headerExtra: g,
23
+ titleAdornment: N,
23
24
  tray: e,
24
- headerDivider: g = !1,
25
- footer: N,
26
- size: w = "medium",
27
- showCloseButton: y = !0,
28
- closeOnOverlayClick: B = !0,
29
- bodyPadding: R = !0,
30
- role: F = "dialog",
31
- "aria-label": U,
32
- "aria-labelledby": A,
33
- "aria-describedby": W,
34
- busy: t = !1,
35
- children: T
25
+ headerDivider: w = !1,
26
+ footer: R,
27
+ size: A = "medium",
28
+ showCloseButton: W = !0,
29
+ closeOnOverlayClick: z = !0,
30
+ bodyPadding: T = !0,
31
+ callouts: f,
32
+ renderLink: G,
33
+ role: q = "dialog",
34
+ "aria-label": H,
35
+ "aria-labelledby": I,
36
+ "aria-describedby": K,
37
+ busy: o = !1,
38
+ children: _
36
39
  }) => {
37
- const I = Z(), [u, _] = f(!1), [z, k] = f(!1), [G, S] = f(!1), E = i(null), n = i(null), l = i(null), O = i(t), C = i(c);
38
- j(() => {
39
- O.current = t, C.current = c;
40
- }, [t, c]);
41
- const $ = P(() => {
42
- O.current || C.current();
43
- }, []), L = i(null), {
44
- ref: M,
45
- isTopmost: q,
46
- zIndex: H,
47
- elevated: K
48
- } = te({
49
- open: s,
50
- onClose: c,
51
- hostRef: L
52
- }), X = P(
53
- (o) => {
54
- E.current = o, M(o);
40
+ const k = V(), S = V(), m = (f == null ? void 0 : f.filter((a) => a.message.trim())) ?? [], [x, $] = h(!1), [X, E] = h(!1), [Z, O] = h(!1), C = n(null), s = n(null), t = n(null), L = n(o), M = n(d);
41
+ Y(() => {
42
+ L.current = o, M.current = d;
43
+ }, [o, d]);
44
+ const y = B(() => {
45
+ L.current || M.current();
46
+ }, []), D = n(null), {
47
+ ref: j,
48
+ isTopmost: J,
49
+ zIndex: Q,
50
+ elevated: ee
51
+ } = de({
52
+ open: c,
53
+ onClose: d,
54
+ hostRef: D
55
+ }), re = B(
56
+ (a) => {
57
+ C.current = a, j(a);
55
58
  },
56
- [M]
59
+ [j]
57
60
  );
58
- return j(() => {
59
- var o;
60
- if (s)
61
- k(!0), S(!1), l.current && (clearTimeout(l.current), l.current = null), n.current = requestAnimationFrame(() => {
62
- n.current = null, _(!0);
61
+ return Y(() => {
62
+ var a;
63
+ if (c)
64
+ E(!0), O(!1), t.current && (clearTimeout(t.current), t.current = null), s.current = requestAnimationFrame(() => {
65
+ s.current = null, $(!0);
63
66
  });
64
67
  else {
65
- const p = re();
66
- p && ((o = E.current) != null && o.contains(p)) && p.blur(), S(!0), _(!1), l.current = setTimeout(() => {
67
- l.current = null, k(!1);
68
- }, x);
68
+ const i = ne();
69
+ i && ((a = C.current) != null && a.contains(i)) && i.blur(), O(!0), $(!1), t.current = setTimeout(() => {
70
+ t.current = null, E(!1);
71
+ }, v);
69
72
  }
70
73
  return () => {
71
- n.current !== null && (cancelAnimationFrame(n.current), n.current = null), l.current && (clearTimeout(l.current), l.current = null);
74
+ s.current !== null && (cancelAnimationFrame(s.current), s.current = null), t.current && (clearTimeout(t.current), t.current = null);
72
75
  };
73
- }, [s]), z ? J(
76
+ }, [c]), X ? ae(
74
77
  // Host carries the stacking context — ShadowRootWrapper's `isolation` would otherwise
75
78
  // flatten our z-index. Not promoted to the top layer, so host dialogs can cover us.
76
79
  /* @__PURE__ */ r(
77
- Q,
80
+ le,
78
81
  {
79
- hostRef: L,
80
- hostStyle: { position: "relative", zIndex: H },
81
- hostProps: K ? { ...V("modal"), [oe]: "" } : V("modal"),
82
- children: /* @__PURE__ */ a(
82
+ hostRef: D,
83
+ hostStyle: { position: "relative", zIndex: Q },
84
+ hostProps: ee ? { ...F("modal"), [ue]: "" } : F("modal"),
85
+ children: /* @__PURE__ */ l(
83
86
  "div",
84
87
  {
85
- className: `fixed inset-0 z-[1000] flex items-center justify-center p-[16px] ${s ? "" : "pointer-events-none"}`,
88
+ className: `fixed inset-0 z-[1000] flex items-center justify-center p-[16px] ${c ? "" : "pointer-events-none"}`,
86
89
  children: [
87
90
  /* @__PURE__ */ r(
88
91
  "div",
89
92
  {
90
93
  "aria-hidden": "true",
91
- onClick: B ? $ : void 0,
92
- className: `absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${u ? "opacity-100" : "opacity-0"}`
94
+ onClick: z ? y : void 0,
95
+ className: `absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${x ? "opacity-100" : "opacity-0"}`
93
96
  }
94
97
  ),
95
- /* @__PURE__ */ a(
98
+ /* @__PURE__ */ l(
96
99
  "div",
97
100
  {
98
- ref: X,
99
- role: F,
100
- "aria-hidden": G ? "true" : void 0,
101
- "aria-modal": q ? "true" : void 0,
102
- "aria-describedby": [W, e != null && e.caption ? I : null].filter(Boolean).join(" ") || void 0,
103
- "aria-busy": t || void 0,
101
+ ref: re,
102
+ role: q,
103
+ "aria-hidden": Z ? "true" : void 0,
104
+ "aria-modal": J ? "true" : void 0,
105
+ "aria-describedby": [
106
+ ...m.map((a, i) => `${S}-${i}`),
107
+ K,
108
+ e != null && e.caption ? k : null
109
+ ].filter(Boolean).join(" ") || void 0,
110
+ "aria-busy": o || void 0,
104
111
  tabIndex: -1,
105
- "aria-label": A ? void 0 : U ?? (typeof d == "string" ? d : void 0),
106
- "aria-labelledby": A,
112
+ "aria-label": I ? void 0 : H ?? (typeof u == "string" ? u : void 0),
113
+ "aria-labelledby": I,
107
114
  className: e ? "relative flex max-h-full w-full flex-col gap-[4px] rounded-[20px] p-[4px] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-glow,var(--malachite-shadow-lg))]" : "relative flex max-h-full w-full flex-col overflow-hidden rounded-[20px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-glow,var(--malachite-shadow-lg))]",
108
115
  style: {
109
- maxWidth: e ? `calc(${Y[w]} + 8px)` : Y[w],
116
+ maxWidth: e ? `calc(${U[A]} + 8px)` : U[A],
110
117
  ...e ? {
111
- backgroundColor: le[e.tone ?? "neutral"]
118
+ backgroundColor: se[e.tone ?? "neutral"]
112
119
  } : {},
113
120
  // Inline: Tailwind scale/translate utilities silently break inside
114
121
  // shadow roots (see CLAUDE.md Shadow DOM caveats).
115
- transform: u ? "scale(1)" : "scale(0.96)",
116
- opacity: u ? 1 : 0,
117
- transition: `transform ${x}ms ease-out, opacity ${x}ms ease-out`
122
+ transform: x ? "scale(1)" : "scale(0.96)",
123
+ opacity: x ? 1 : 0,
124
+ transition: `transform ${v}ms ease-out, opacity ${v}ms ease-out`
118
125
  },
119
126
  children: [
120
- /* @__PURE__ */ a(
127
+ /* @__PURE__ */ l(
121
128
  "div",
122
129
  {
123
130
  className: e ? "flex min-h-0 flex-col overflow-hidden rounded-[16px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] [box-shadow:var(--malachite-shadow-glow-lg,var(--malachite-shadow-xl))]" : "contents",
124
131
  children: [
125
- /* @__PURE__ */ a(
132
+ /* @__PURE__ */ l(
126
133
  "div",
127
134
  {
128
- className: `flex gap-[20px] p-[20px] ${m ? "items-start" : "items-center"} ${g ? "border-0 border-b border-solid border-[var(--malachite-card-border)]" : ""}`,
135
+ className: `flex gap-[20px] p-[20px] ${p ? "items-start" : "items-center"} ${w ? "border-0 border-b border-solid border-[var(--malachite-card-border)]" : ""}`,
129
136
  children: [
130
- /* @__PURE__ */ a("div", { className: "flex h-[18px] min-w-0 flex-1 items-center gap-[12px]", children: [
131
- /* @__PURE__ */ a("div", { className: "flex min-w-0 items-center gap-[8px]", children: [
132
- h ? /* @__PURE__ */ r("span", { className: "flex shrink-0 items-center", children: h }) : null,
133
- /* @__PURE__ */ a("div", { className: "flex min-w-0 flex-col gap-[4px]", children: [
134
- /* @__PURE__ */ r("span", { className: "truncate text-[14px] font-medium leading-[1.25] tracking-[-0.14px] text-[var(--malachite-text-primary)]", children: d }),
135
- m ? /* @__PURE__ */ r("span", { className: "text-[13px] leading-[1.6] text-[var(--malachite-text-secondary)]", children: m }) : null
137
+ /* @__PURE__ */ l("div", { className: "flex h-[18px] min-w-0 flex-1 items-center gap-[12px]", children: [
138
+ /* @__PURE__ */ l("div", { className: "flex min-w-0 items-center gap-[8px]", children: [
139
+ b ? /* @__PURE__ */ r("span", { className: "flex shrink-0 items-center", children: b }) : null,
140
+ /* @__PURE__ */ l("div", { className: "flex min-w-0 flex-col gap-[4px]", children: [
141
+ /* @__PURE__ */ r("span", { className: "truncate text-[14px] font-medium leading-[1.25] tracking-[-0.14px] text-[var(--malachite-text-primary)]", children: u }),
142
+ p ? /* @__PURE__ */ r("span", { className: "text-[13px] leading-[1.6] text-[var(--malachite-text-secondary)]", children: p }) : null
136
143
  ] })
137
144
  ] }),
138
- b ? /* @__PURE__ */ a(D, { children: [
145
+ N ? /* @__PURE__ */ l(P, { children: [
139
146
  /* @__PURE__ */ r(
140
147
  "span",
141
148
  {
@@ -143,37 +150,55 @@ const Y = {
143
150
  className: "h-[12px] w-px shrink-0 bg-[var(--malachite-card-border)]"
144
151
  }
145
152
  ),
146
- /* @__PURE__ */ r("span", { className: "flex min-w-0 items-center overflow-hidden", children: b })
153
+ /* @__PURE__ */ r("span", { className: "flex min-w-0 items-center overflow-hidden", children: N })
147
154
  ] }) : null
148
155
  ] }),
149
- v ? /* @__PURE__ */ r("span", { className: "flex shrink-0 items-center", children: v }) : null,
150
- y ? /* @__PURE__ */ r(
156
+ g ? /* @__PURE__ */ r("span", { className: "flex shrink-0 items-center", children: g }) : null,
157
+ W ? /* @__PURE__ */ r(
151
158
  "button",
152
159
  {
153
160
  type: "button",
154
161
  "aria-label": "Close",
155
- onClick: $,
156
- disabled: t,
157
- className: ne,
158
- children: /* @__PURE__ */ r(ee, { size: 10 })
162
+ onClick: y,
163
+ disabled: o,
164
+ className: me,
165
+ children: /* @__PURE__ */ r(ie, { size: 10 })
159
166
  }
160
167
  ) : null
161
168
  ]
162
169
  }
163
170
  ),
164
- /* @__PURE__ */ r(
171
+ /* @__PURE__ */ l(
165
172
  "div",
166
173
  {
167
- className: `min-h-0 flex-1 overflow-y-auto ${R ? "px-[20px]" : ""}`,
168
- children: R ? /* @__PURE__ */ r("div", { className: `pb-[20px] ${g ? "pt-[18px]" : ""}`, children: T }) : T
174
+ className: `min-h-0 flex-1 overflow-y-auto ${T && m.length === 0 ? "px-[20px]" : ""}`,
175
+ children: [
176
+ m.map((a, i) => /* @__PURE__ */ r(
177
+ te,
178
+ {
179
+ id: `${S}-${i}`,
180
+ type: a.type,
181
+ message: a.message,
182
+ renderLink: G
183
+ },
184
+ i
185
+ )),
186
+ T ? /* @__PURE__ */ r(
187
+ "div",
188
+ {
189
+ className: m.length > 0 ? "px-[20px] pt-[18px] pb-[20px]" : `pb-[20px] ${w ? "pt-[18px]" : ""}`,
190
+ children: _
191
+ }
192
+ ) : _
193
+ ]
169
194
  }
170
195
  ),
171
- N ? /* @__PURE__ */ r(
196
+ R ? /* @__PURE__ */ r(
172
197
  "div",
173
198
  {
174
199
  className: "flex min-h-[56px] shrink-0 items-center border-0 border-t border-solid border-[var(--malachite-card-border)] px-[12px]",
175
- style: ae,
176
- children: N
200
+ style: oe,
201
+ children: R
177
202
  }
178
203
  ) : null
179
204
  ]
@@ -182,11 +207,11 @@ const Y = {
182
207
  e != null && e.caption ? /* @__PURE__ */ r(
183
208
  "div",
184
209
  {
185
- id: I,
210
+ id: k,
186
211
  className: "flex h-[48px] shrink-0 items-center justify-center px-[16px] text-[13px] tracking-[0.13px]",
187
- style: { color: ie[e.tone ?? "neutral"] },
188
- children: /* @__PURE__ */ a("span", { children: [
189
- e.caption.label ? /* @__PURE__ */ a(D, { children: [
212
+ style: { color: ce[e.tone ?? "neutral"] },
213
+ children: /* @__PURE__ */ l("span", { children: [
214
+ e.caption.label ? /* @__PURE__ */ l(P, { children: [
190
215
  /* @__PURE__ */ r("span", { className: "font-medium", children: e.caption.label }),
191
216
  " "
192
217
  ] }) : null,
@@ -206,7 +231,7 @@ const Y = {
206
231
  ) : null;
207
232
  };
208
233
  export {
209
- be as Modal,
210
- be as default
234
+ Te as Modal,
235
+ Te as default
211
236
  };
212
237
  //# sourceMappingURL=Modal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.js","sources":["../../src/components/Modal.tsx"],"sourcesContent":["import type React from 'react';\nimport { useCallback, useEffect, useId, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ShadowRootWrapper } from '../ShadowRootWrapper';\nimport DismissIcon from './CustomIcons/DismissIcon';\nimport { getDeepActiveElement } from './activeElement';\nimport { GLASS_SURFACE_STYLE } from './glassSurface';\nimport { MODAL_TRAY_BACKGROUND, MODAL_TRAY_CAPTION_COLOR, type ModalTrayTone } from './modalTray';\nimport { OVERLAY_ICON_BUTTON_CLASSES, useOverlayBehaviour } from './overlayBehaviour';\nimport { OVERLAY_ELEVATED_ATTR, overlayIdentity } from './portalOverlay';\n\nexport type ModalSize = 'small' | 'medium' | 'large';\n\n/** Widths keyed to the size union — a raw `width` prop would let a consumer off the scale. */\nconst SIZE_MAX_WIDTH: Record<ModalSize, string> = {\n small: '420px',\n medium: '612px',\n large: '760px',\n};\n\nconst TRANSITION_MS = 200;\n\nexport interface ModalProps {\n open: boolean;\n /** Dismiss handler — close button, ESC, or overlay click. */\n onClose: () => void;\n title: React.ReactNode;\n /** Secondary line beneath the title. */\n subtitle?: React.ReactNode;\n /** Leading header slot, typically an icon or logo. */\n icon?: React.ReactNode;\n /** Content pinned right in the header, before the close button. */\n headerExtra?: React.ReactNode;\n titleAdornment?: React.ReactNode;\n tray?: { tone?: ModalTrayTone; caption?: { label?: string; message: string } };\n /** Divider under the header. Default: false. */\n headerDivider?: boolean;\n /** Pinned action row below the scrolling body. */\n footer?: React.ReactNode;\n size?: ModalSize;\n /** Default: true. */\n showCloseButton?: boolean;\n /** Default: true. Turn off for a dialog whose choice must be made deliberately. */\n closeOnOverlayClick?: boolean;\n /** Whether the body gets default padding. Default: true. */\n bodyPadding?: boolean;\n /**\n * `alertdialog` for a dialog interrupting the user over a consequential choice — it\n * tells assistive tech to announce the body, not just the name.\n */\n role?: 'dialog' | 'alertdialog';\n /** Accessible dialog name; required when `title` isn't a plain string. */\n 'aria-label'?: string;\n /** ID of a visible element naming the dialog (alternative to `aria-label`). */\n 'aria-labelledby'?: string;\n /** ID of the element describing what the dialog is asking. */\n 'aria-describedby'?: string;\n /**\n * Work is in flight: suppresses the pointer dismissal routes and announces the busy\n * state. Never ESC — that is the only guaranteed keyboard exit from a focus trap, so it\n * always reaches `onClose` and whether to abort is left to the caller.\n */\n busy?: boolean;\n children: React.ReactNode;\n}\n\nconst Modal: React.FC<ModalProps> = ({\n open,\n onClose,\n title,\n subtitle,\n icon,\n headerExtra,\n titleAdornment,\n tray,\n headerDivider = false,\n footer,\n size = 'medium',\n showCloseButton = true,\n closeOnOverlayClick = true,\n bodyPadding = true,\n role = 'dialog',\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n busy = false,\n children,\n}) => {\n const captionId = useId();\n const [isVisible, setIsVisible] = useState(false);\n // Starts false even when `open` does: portals are unsupported on the server, so the\n // dialog must reach `document.body` from the client effect below and nowhere else.\n const [isMounted, setIsMounted] = useState(false);\n // Distinct from `!isVisible`, which is also false on the frame the dialog opens.\n const [isExiting, setIsExiting] = useState(false);\n const panelRef = useRef<HTMLElement | null>(null);\n const frameRef = useRef<number | null>(null);\n const unmountTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const busyRef = useRef(busy);\n const onCloseRef = useRef(onClose);\n useEffect(() => {\n busyRef.current = busy;\n onCloseRef.current = onClose;\n }, [busy, onClose]);\n\n // Pointer routes only. Removing ESC too would leave a focus-trapped keyboard user no\n // exit while a request hangs (WCAG 2.1.2), so ESC always reaches onClose.\n const dismissByPointer = useCallback(() => {\n if (!busyRef.current) {\n onCloseRef.current();\n }\n }, []);\n const hostRef = useRef<HTMLDivElement | null>(null);\n // No prop of its own: it stays in the base band unless a SidePanel elevated the stack,\n // and follows it up when one has.\n const {\n ref: takePanelRef,\n isTopmost,\n zIndex,\n elevated,\n } = useOverlayBehaviour({\n open,\n onClose,\n hostRef,\n });\n const handlePanelRef = useCallback(\n (node: HTMLElement | null) => {\n panelRef.current = node;\n takePanelRef(node);\n },\n [takePanelRef],\n );\n\n // Mounted outlives `open` by one transition, so the dialog can animate out.\n useEffect(() => {\n if (open) {\n setIsMounted(true);\n setIsExiting(false);\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n frameRef.current = requestAnimationFrame(() => {\n frameRef.current = null;\n setIsVisible(true);\n });\n } else {\n // A browser refuses `aria-hidden` on a subtree still holding focus, so the\n // attribute waits for this point — after useOverlayBehaviour's cleanup has\n // returned focus to the trigger — and blurs what is left if the trigger has gone.\n const active = getDeepActiveElement();\n if (active && panelRef.current?.contains(active)) {\n active.blur();\n }\n setIsExiting(true);\n setIsVisible(false);\n unmountTimerRef.current = setTimeout(() => {\n unmountTimerRef.current = null;\n setIsMounted(false);\n }, TRANSITION_MS);\n }\n return () => {\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current);\n frameRef.current = null;\n }\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n };\n }, [open]);\n\n if (!isMounted) {\n return null;\n }\n\n return createPortal(\n // Host carries the stacking context — ShadowRootWrapper's `isolation` would otherwise\n // flatten our z-index. Not promoted to the top layer, so host dialogs can cover us.\n <ShadowRootWrapper\n hostRef={hostRef}\n hostStyle={{ position: 'relative', zIndex }}\n hostProps={\n elevated\n ? { ...overlayIdentity('modal'), [OVERLAY_ELEVATED_ATTR]: '' }\n : overlayIdentity('modal')\n }\n >\n {/* The exit transition outlives `open`, so a closed-but-mounted dialog has to stop\n taking clicks and leave the a11y tree — a faded opacity does neither. */}\n <div\n className={`fixed inset-0 z-[1000] flex items-center justify-center p-[16px] ${\n open ? '' : 'pointer-events-none'\n }`}\n >\n <div\n aria-hidden=\"true\"\n onClick={closeOnOverlayClick ? dismissByPointer : undefined}\n className={`absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${\n isVisible ? 'opacity-100' : 'opacity-0'\n }`}\n />\n <div\n ref={handlePanelRef}\n role={role}\n aria-hidden={isExiting ? 'true' : undefined}\n aria-modal={isTopmost ? 'true' : undefined}\n aria-describedby={\n [ariaDescribedBy, tray?.caption ? captionId : null]\n .filter(Boolean)\n .join(' ') || undefined\n }\n aria-busy={busy || undefined}\n tabIndex={-1}\n aria-label={\n ariaLabelledBy\n ? undefined\n : (ariaLabel ?? (typeof title === 'string' ? title : undefined))\n }\n aria-labelledby={ariaLabelledBy}\n className={\n tray\n ? 'relative flex max-h-full w-full flex-col gap-[4px] rounded-[20px] p-[4px] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-glow,var(--malachite-shadow-lg))]'\n : 'relative flex max-h-full w-full flex-col overflow-hidden rounded-[20px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-glow,var(--malachite-shadow-lg))]'\n }\n style={{\n maxWidth: tray\n ? `calc(${SIZE_MAX_WIDTH[size]} + 8px)`\n : SIZE_MAX_WIDTH[size],\n ...(tray\n ? {\n backgroundColor: MODAL_TRAY_BACKGROUND[tray.tone ?? 'neutral'],\n }\n : {}),\n // Inline: Tailwind scale/translate utilities silently break inside\n // shadow roots (see CLAUDE.md Shadow DOM caveats).\n transform: isVisible ? 'scale(1)' : 'scale(0.96)',\n opacity: isVisible ? 1 : 0,\n transition: `transform ${TRANSITION_MS}ms ease-out, opacity ${TRANSITION_MS}ms ease-out`,\n }}\n >\n <div\n className={\n tray\n ? 'flex min-h-0 flex-col overflow-hidden rounded-[16px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] [box-shadow:var(--malachite-shadow-glow-lg,var(--malachite-shadow-xl))]'\n : 'contents'\n }\n >\n <div\n className={`flex gap-[20px] p-[20px] ${\n subtitle ? 'items-start' : 'items-center'\n } ${\n headerDivider\n ? 'border-0 border-b border-solid border-[var(--malachite-card-border)]'\n : ''\n }`}\n >\n <div className=\"flex h-[18px] min-w-0 flex-1 items-center gap-[12px]\">\n <div className=\"flex min-w-0 items-center gap-[8px]\">\n {icon ? (\n <span className=\"flex shrink-0 items-center\">{icon}</span>\n ) : null}\n <div className=\"flex min-w-0 flex-col gap-[4px]\">\n <span className=\"truncate text-[14px] font-medium leading-[1.25] tracking-[-0.14px] text-[var(--malachite-text-primary)]\">\n {title}\n </span>\n {subtitle ? (\n <span className=\"text-[13px] leading-[1.6] text-[var(--malachite-text-secondary)]\">\n {subtitle}\n </span>\n ) : null}\n </div>\n </div>\n {titleAdornment ? (\n <>\n <span\n aria-hidden=\"true\"\n className=\"h-[12px] w-px shrink-0 bg-[var(--malachite-card-border)]\"\n />\n <span className=\"flex min-w-0 items-center overflow-hidden\">\n {titleAdornment}\n </span>\n </>\n ) : null}\n </div>\n {headerExtra ? (\n <span className=\"flex shrink-0 items-center\">{headerExtra}</span>\n ) : null}\n {showCloseButton ? (\n <button\n type=\"button\"\n aria-label=\"Close\"\n onClick={dismissByPointer}\n disabled={busy}\n className={OVERLAY_ICON_BUTTON_CLASSES}\n >\n <DismissIcon size={10} />\n </button>\n ) : null}\n </div>\n <div\n className={`min-h-0 flex-1 overflow-y-auto ${\n bodyPadding ? 'px-[20px]' : ''\n }`}\n >\n {bodyPadding ? (\n <div className={`pb-[20px] ${headerDivider ? 'pt-[18px]' : ''}`}>\n {children}\n </div>\n ) : (\n children\n )}\n </div>\n {footer ? (\n <div\n className=\"flex min-h-[56px] shrink-0 items-center border-0 border-t border-solid border-[var(--malachite-card-border)] px-[12px]\"\n style={GLASS_SURFACE_STYLE}\n >\n {footer}\n </div>\n ) : null}\n </div>\n {tray?.caption ? (\n <div\n id={captionId}\n className=\"flex h-[48px] shrink-0 items-center justify-center px-[16px] text-[13px] tracking-[0.13px]\"\n style={{ color: MODAL_TRAY_CAPTION_COLOR[tray.tone ?? 'neutral'] }}\n >\n <span>\n {tray.caption.label ? (\n <>\n <span className=\"font-medium\">\n {tray.caption.label}\n </span>{' '}\n </>\n ) : null}\n {tray.caption.message}\n </span>\n </div>\n ) : null}\n </div>\n </div>\n </ShadowRootWrapper>,\n document.body,\n );\n};\n\nexport default Modal;\nexport { Modal };\n"],"names":["SIZE_MAX_WIDTH","TRANSITION_MS","Modal","open","onClose","title","subtitle","icon","headerExtra","titleAdornment","tray","headerDivider","footer","size","showCloseButton","closeOnOverlayClick","bodyPadding","role","ariaLabel","ariaLabelledBy","ariaDescribedBy","busy","children","captionId","useId","isVisible","setIsVisible","useState","isMounted","setIsMounted","isExiting","setIsExiting","panelRef","useRef","frameRef","unmountTimerRef","busyRef","onCloseRef","useEffect","dismissByPointer","useCallback","hostRef","takePanelRef","isTopmost","zIndex","elevated","useOverlayBehaviour","handlePanelRef","node","active","getDeepActiveElement","_a","createPortal","jsx","ShadowRootWrapper","overlayIdentity","OVERLAY_ELEVATED_ATTR","jsxs","MODAL_TRAY_BACKGROUND","Fragment","OVERLAY_ICON_BUTTON_CLASSES","DismissIcon","GLASS_SURFACE_STYLE","MODAL_TRAY_CAPTION_COLOR"],"mappings":"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"}
1
+ {"version":3,"file":"Modal.js","sources":["../../src/components/Modal.tsx"],"sourcesContent":["import type React from 'react';\nimport { useCallback, useEffect, useId, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ShadowRootWrapper } from '../ShadowRootWrapper';\nimport type { AlertType } from './Alert';\nimport CalloutStrip from './CalloutStrip';\nimport DismissIcon from './CustomIcons/DismissIcon';\nimport type { RenderLink } from './PageLayout/types';\nimport { getDeepActiveElement } from './activeElement';\nimport { GLASS_SURFACE_STYLE } from './glassSurface';\nimport { MODAL_TRAY_BACKGROUND, MODAL_TRAY_CAPTION_COLOR, type ModalTrayTone } from './modalTray';\nimport { OVERLAY_ICON_BUTTON_CLASSES, useOverlayBehaviour } from './overlayBehaviour';\nimport { OVERLAY_ELEVATED_ATTR, overlayIdentity } from './portalOverlay';\n\nexport type ModalSize = 'small' | 'medium' | 'large';\n\n/** Widths keyed to the size union — a raw `width` prop would let a consumer off the scale. */\nconst SIZE_MAX_WIDTH: Record<ModalSize, string> = {\n small: '420px',\n medium: '612px',\n large: '760px',\n};\n\nconst TRANSITION_MS = 200;\n\nexport interface ModalProps {\n open: boolean;\n /** Dismiss handler — close button, ESC, or overlay click. */\n onClose: () => void;\n title: React.ReactNode;\n /** Secondary line beneath the title. */\n subtitle?: React.ReactNode;\n /** Leading header slot, typically an icon or logo. */\n icon?: React.ReactNode;\n /** Content pinned right in the header, before the close button. */\n headerExtra?: React.ReactNode;\n titleAdornment?: React.ReactNode;\n tray?: { tone?: ModalTrayTone; caption?: { label?: string; message: string } };\n /** Divider under the header. Default: false. */\n headerDivider?: boolean;\n /** Pinned action row below the scrolling body. */\n footer?: React.ReactNode;\n size?: ModalSize;\n /** Default: true. */\n showCloseButton?: boolean;\n /** Default: true. Turn off for a dialog whose choice must be made deliberately. */\n closeOnOverlayClick?: boolean;\n /** Whether the body gets default padding. Default: true. */\n bodyPadding?: boolean;\n /** Full-width Alert bands at the top of the body, above `children`, in order. Unaffected by\n * `bodyPadding`, and announced ahead of the dialog's description. The message is markdown. */\n callouts?: { type?: AlertType; message: string }[];\n /** Routes root-relative links in `callouts` through the app's router; see `MarkdownBlock`. */\n renderLink?: RenderLink;\n /**\n * `alertdialog` for a dialog interrupting the user over a consequential choice — it\n * tells assistive tech to announce the body, not just the name.\n */\n role?: 'dialog' | 'alertdialog';\n /** Accessible dialog name; required when `title` isn't a plain string. */\n 'aria-label'?: string;\n /** ID of a visible element naming the dialog (alternative to `aria-label`). */\n 'aria-labelledby'?: string;\n /** ID of the element describing what the dialog is asking. */\n 'aria-describedby'?: string;\n /**\n * Work is in flight: suppresses the pointer dismissal routes and announces the busy\n * state. Never ESC — that is the only guaranteed keyboard exit from a focus trap, so it\n * always reaches `onClose` and whether to abort is left to the caller.\n */\n busy?: boolean;\n children: React.ReactNode;\n}\n\nconst Modal: React.FC<ModalProps> = ({\n open,\n onClose,\n title,\n subtitle,\n icon,\n headerExtra,\n titleAdornment,\n tray,\n headerDivider = false,\n footer,\n size = 'medium',\n showCloseButton = true,\n closeOnOverlayClick = true,\n bodyPadding = true,\n callouts,\n renderLink,\n role = 'dialog',\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n busy = false,\n children,\n}) => {\n const captionId = useId();\n const calloutId = useId();\n const shownCallouts = callouts?.filter((callout) => callout.message.trim()) ?? [];\n const [isVisible, setIsVisible] = useState(false);\n // Starts false even when `open` does: portals are unsupported on the server, so the\n // dialog must reach `document.body` from the client effect below and nowhere else.\n const [isMounted, setIsMounted] = useState(false);\n // Distinct from `!isVisible`, which is also false on the frame the dialog opens.\n const [isExiting, setIsExiting] = useState(false);\n const panelRef = useRef<HTMLElement | null>(null);\n const frameRef = useRef<number | null>(null);\n const unmountTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const busyRef = useRef(busy);\n const onCloseRef = useRef(onClose);\n useEffect(() => {\n busyRef.current = busy;\n onCloseRef.current = onClose;\n }, [busy, onClose]);\n\n // Pointer routes only. Removing ESC too would leave a focus-trapped keyboard user no\n // exit while a request hangs (WCAG 2.1.2), so ESC always reaches onClose.\n const dismissByPointer = useCallback(() => {\n if (!busyRef.current) {\n onCloseRef.current();\n }\n }, []);\n const hostRef = useRef<HTMLDivElement | null>(null);\n // No prop of its own: it stays in the base band unless a SidePanel elevated the stack,\n // and follows it up when one has.\n const {\n ref: takePanelRef,\n isTopmost,\n zIndex,\n elevated,\n } = useOverlayBehaviour({\n open,\n onClose,\n hostRef,\n });\n const handlePanelRef = useCallback(\n (node: HTMLElement | null) => {\n panelRef.current = node;\n takePanelRef(node);\n },\n [takePanelRef],\n );\n\n // Mounted outlives `open` by one transition, so the dialog can animate out.\n useEffect(() => {\n if (open) {\n setIsMounted(true);\n setIsExiting(false);\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n frameRef.current = requestAnimationFrame(() => {\n frameRef.current = null;\n setIsVisible(true);\n });\n } else {\n // A browser refuses `aria-hidden` on a subtree still holding focus, so the\n // attribute waits for this point — after useOverlayBehaviour's cleanup has\n // returned focus to the trigger — and blurs what is left if the trigger has gone.\n const active = getDeepActiveElement();\n if (active && panelRef.current?.contains(active)) {\n active.blur();\n }\n setIsExiting(true);\n setIsVisible(false);\n unmountTimerRef.current = setTimeout(() => {\n unmountTimerRef.current = null;\n setIsMounted(false);\n }, TRANSITION_MS);\n }\n return () => {\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current);\n frameRef.current = null;\n }\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n };\n }, [open]);\n\n if (!isMounted) {\n return null;\n }\n\n return createPortal(\n // Host carries the stacking context — ShadowRootWrapper's `isolation` would otherwise\n // flatten our z-index. Not promoted to the top layer, so host dialogs can cover us.\n <ShadowRootWrapper\n hostRef={hostRef}\n hostStyle={{ position: 'relative', zIndex }}\n hostProps={\n elevated\n ? { ...overlayIdentity('modal'), [OVERLAY_ELEVATED_ATTR]: '' }\n : overlayIdentity('modal')\n }\n >\n {/* The exit transition outlives `open`, so a closed-but-mounted dialog has to stop\n taking clicks and leave the a11y tree — a faded opacity does neither. */}\n <div\n className={`fixed inset-0 z-[1000] flex items-center justify-center p-[16px] ${\n open ? '' : 'pointer-events-none'\n }`}\n >\n <div\n aria-hidden=\"true\"\n onClick={closeOnOverlayClick ? dismissByPointer : undefined}\n className={`absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${\n isVisible ? 'opacity-100' : 'opacity-0'\n }`}\n />\n <div\n ref={handlePanelRef}\n role={role}\n aria-hidden={isExiting ? 'true' : undefined}\n aria-modal={isTopmost ? 'true' : undefined}\n aria-describedby={\n [\n ...shownCallouts.map((_, index) => `${calloutId}-${index}`),\n ariaDescribedBy,\n tray?.caption ? captionId : null,\n ]\n .filter(Boolean)\n .join(' ') || undefined\n }\n aria-busy={busy || undefined}\n tabIndex={-1}\n aria-label={\n ariaLabelledBy\n ? undefined\n : (ariaLabel ?? (typeof title === 'string' ? title : undefined))\n }\n aria-labelledby={ariaLabelledBy}\n className={\n tray\n ? 'relative flex max-h-full w-full flex-col gap-[4px] rounded-[20px] p-[4px] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-glow,var(--malachite-shadow-lg))]'\n : 'relative flex max-h-full w-full flex-col overflow-hidden rounded-[20px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-glow,var(--malachite-shadow-lg))]'\n }\n style={{\n maxWidth: tray\n ? `calc(${SIZE_MAX_WIDTH[size]} + 8px)`\n : SIZE_MAX_WIDTH[size],\n ...(tray\n ? {\n backgroundColor: MODAL_TRAY_BACKGROUND[tray.tone ?? 'neutral'],\n }\n : {}),\n // Inline: Tailwind scale/translate utilities silently break inside\n // shadow roots (see CLAUDE.md Shadow DOM caveats).\n transform: isVisible ? 'scale(1)' : 'scale(0.96)',\n opacity: isVisible ? 1 : 0,\n transition: `transform ${TRANSITION_MS}ms ease-out, opacity ${TRANSITION_MS}ms ease-out`,\n }}\n >\n <div\n className={\n tray\n ? 'flex min-h-0 flex-col overflow-hidden rounded-[16px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] [box-shadow:var(--malachite-shadow-glow-lg,var(--malachite-shadow-xl))]'\n : 'contents'\n }\n >\n <div\n className={`flex gap-[20px] p-[20px] ${\n subtitle ? 'items-start' : 'items-center'\n } ${\n headerDivider\n ? 'border-0 border-b border-solid border-[var(--malachite-card-border)]'\n : ''\n }`}\n >\n <div className=\"flex h-[18px] min-w-0 flex-1 items-center gap-[12px]\">\n <div className=\"flex min-w-0 items-center gap-[8px]\">\n {icon ? (\n <span className=\"flex shrink-0 items-center\">{icon}</span>\n ) : null}\n <div className=\"flex min-w-0 flex-col gap-[4px]\">\n <span className=\"truncate text-[14px] font-medium leading-[1.25] tracking-[-0.14px] text-[var(--malachite-text-primary)]\">\n {title}\n </span>\n {subtitle ? (\n <span className=\"text-[13px] leading-[1.6] text-[var(--malachite-text-secondary)]\">\n {subtitle}\n </span>\n ) : null}\n </div>\n </div>\n {titleAdornment ? (\n <>\n <span\n aria-hidden=\"true\"\n className=\"h-[12px] w-px shrink-0 bg-[var(--malachite-card-border)]\"\n />\n <span className=\"flex min-w-0 items-center overflow-hidden\">\n {titleAdornment}\n </span>\n </>\n ) : null}\n </div>\n {headerExtra ? (\n <span className=\"flex shrink-0 items-center\">{headerExtra}</span>\n ) : null}\n {showCloseButton ? (\n <button\n type=\"button\"\n aria-label=\"Close\"\n onClick={dismissByPointer}\n disabled={busy}\n className={OVERLAY_ICON_BUTTON_CLASSES}\n >\n <DismissIcon size={10} />\n </button>\n ) : null}\n </div>\n <div\n className={`min-h-0 flex-1 overflow-y-auto ${\n bodyPadding && shownCallouts.length === 0 ? 'px-[20px]' : ''\n }`}\n >\n {shownCallouts.map((callout, index) => (\n <CalloutStrip\n key={index}\n id={`${calloutId}-${index}`}\n type={callout.type}\n message={callout.message}\n renderLink={renderLink}\n />\n ))}\n {bodyPadding ? (\n <div\n className={\n shownCallouts.length > 0\n ? 'px-[20px] pt-[18px] pb-[20px]'\n : `pb-[20px] ${headerDivider ? 'pt-[18px]' : ''}`\n }\n >\n {children}\n </div>\n ) : (\n children\n )}\n </div>\n {footer ? (\n <div\n className=\"flex min-h-[56px] shrink-0 items-center border-0 border-t border-solid border-[var(--malachite-card-border)] px-[12px]\"\n style={GLASS_SURFACE_STYLE}\n >\n {footer}\n </div>\n ) : null}\n </div>\n {tray?.caption ? (\n <div\n id={captionId}\n className=\"flex h-[48px] shrink-0 items-center justify-center px-[16px] text-[13px] tracking-[0.13px]\"\n style={{ color: MODAL_TRAY_CAPTION_COLOR[tray.tone ?? 'neutral'] }}\n >\n <span>\n {tray.caption.label ? (\n <>\n <span className=\"font-medium\">\n {tray.caption.label}\n </span>{' '}\n </>\n ) : null}\n {tray.caption.message}\n </span>\n </div>\n ) : null}\n </div>\n </div>\n </ShadowRootWrapper>,\n document.body,\n );\n};\n\nexport default Modal;\nexport { Modal };\n"],"names":["SIZE_MAX_WIDTH","TRANSITION_MS","Modal","open","onClose","title","subtitle","icon","headerExtra","titleAdornment","tray","headerDivider","footer","size","showCloseButton","closeOnOverlayClick","bodyPadding","callouts","renderLink","role","ariaLabel","ariaLabelledBy","ariaDescribedBy","busy","children","captionId","useId","calloutId","shownCallouts","callout","isVisible","setIsVisible","useState","isMounted","setIsMounted","isExiting","setIsExiting","panelRef","useRef","frameRef","unmountTimerRef","busyRef","onCloseRef","useEffect","dismissByPointer","useCallback","hostRef","takePanelRef","isTopmost","zIndex","elevated","useOverlayBehaviour","handlePanelRef","node","active","getDeepActiveElement","_a","createPortal","jsx","ShadowRootWrapper","overlayIdentity","OVERLAY_ELEVATED_ATTR","jsxs","_","index","MODAL_TRAY_BACKGROUND","Fragment","OVERLAY_ICON_BUTTON_CLASSES","DismissIcon","CalloutStrip","GLASS_SURFACE_STYLE","MODAL_TRAY_CAPTION_COLOR"],"mappings":"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"}
@@ -1,166 +1,178 @@
1
1
  import { jsx as e, jsxs as a } from "react/jsx-runtime";
2
- import { useState as R, useRef as d, useEffect as F } from "react";
3
- import { createPortal as X } from "react-dom";
4
- import { ShadowRootWrapper as Y } from "../ShadowRootWrapper.js";
5
- import q from "./CalloutStrip.js";
6
- import U from "./CustomIcons/ArrowLeftIcon.js";
7
- import G from "./CustomIcons/DismissIcon.js";
8
- import A from "./Typography/EllipsisText.js";
9
- import { useOverlayBehaviour as H, OVERLAY_ICON_BUTTON_CLASSES as k } from "./overlayBehaviour.js";
10
- import { OVERLAY_ELEVATED_ATTR as J, overlayIdentity as z } from "./portalOverlay.js";
11
- const E = 200, K = (t) => typeof t == "number" ? `${t}px` : t, oe = ({
2
+ import { useId as k, useState as _, useRef as u, useEffect as q } from "react";
3
+ import { createPortal as U } from "react-dom";
4
+ import { ShadowRootWrapper as G } from "../ShadowRootWrapper.js";
5
+ import H from "./CalloutStrip.js";
6
+ import J from "./CustomIcons/ArrowLeftIcon.js";
7
+ import K from "./CustomIcons/DismissIcon.js";
8
+ import z from "./Typography/EllipsisText.js";
9
+ import { useOverlayBehaviour as Q, OVERLAY_ICON_BUTTON_CLASSES as E } from "./overlayBehaviour.js";
10
+ import { OVERLAY_ELEVATED_ATTR as Z, overlayIdentity as O } from "./portalOverlay.js";
11
+ const P = 200, ee = (t) => typeof t == "number" ? `${t}px` : t, me = ({
12
12
  open: t,
13
- onClose: o,
14
- title: n,
15
- subtitle: c,
16
- titleExtra: p,
17
- description: u,
18
- headerDivider: O = !1,
19
- icon: f,
20
- headerExtra: x,
13
+ onClose: c,
14
+ title: o,
15
+ subtitle: d,
16
+ titleExtra: f,
17
+ description: m,
18
+ headerDivider: V = !1,
19
+ icon: x,
20
+ headerExtra: b,
21
21
  footer: h,
22
- onBack: b,
23
- maxWidth: _ = 1080,
24
- "aria-label": $,
25
- "aria-labelledby": v,
26
- bodyPadding: y = !0,
27
- stacking: P = "default",
28
- callouts: m,
29
- renderLink: V,
30
- children: N
22
+ onBack: v,
23
+ maxWidth: C = 1080,
24
+ "aria-label": L,
25
+ "aria-labelledby": y,
26
+ bodyPadding: N = !0,
27
+ stacking: j = "default",
28
+ callouts: p,
29
+ renderLink: B,
30
+ children: g
31
31
  }) => {
32
- const [g, T] = R(!1), [C, w] = R(!1), i = d(null), r = d(null), S = d(null), {
33
- ref: L,
34
- isTopmost: M,
35
- zIndex: W,
36
- elevated: j
37
- } = H({
32
+ const T = k(), I = k(), [w, S] = _(!1), [M, R] = _(!1), i = u(null), r = u(null), $ = u(null), {
33
+ ref: W,
34
+ isTopmost: D,
35
+ zIndex: F,
36
+ elevated: X
37
+ } = Q({
38
38
  open: t,
39
- onClose: o,
40
- elevated: P === "above-app-overlays",
41
- hostRef: S
39
+ onClose: c,
40
+ elevated: j === "above-app-overlays",
41
+ hostRef: $
42
42
  });
43
- if (F(() => (t ? (w(!0), r.current && (clearTimeout(r.current), r.current = null), i.current = requestAnimationFrame(() => {
44
- i.current = null, T(!0);
45
- })) : (T(!1), r.current = setTimeout(() => {
46
- r.current = null, w(!1);
47
- }, E)), () => {
43
+ if (q(() => (t ? (R(!0), r.current && (clearTimeout(r.current), r.current = null), i.current = requestAnimationFrame(() => {
44
+ i.current = null, S(!0);
45
+ })) : (S(!1), r.current = setTimeout(() => {
46
+ r.current = null, R(!1);
47
+ }, P)), () => {
48
48
  i.current !== null && (cancelAnimationFrame(i.current), i.current = null), r.current && (clearTimeout(r.current), r.current = null);
49
- }), [t]), !C)
49
+ }), [t]), !M)
50
50
  return null;
51
- const l = m == null ? void 0 : m.filter((s) => s.message.trim()), I = !!(l != null && l.length), B = y ? /* @__PURE__ */ e("div", { className: I ? "p-[28px]" : "py-[28px]", children: N }) : N;
52
- return X(
51
+ const l = p == null ? void 0 : p.filter((n) => n.message.trim()), A = !!(l != null && l.length), Y = N ? /* @__PURE__ */ e("div", { className: A ? "p-[28px]" : "py-[28px]", children: g }) : g;
52
+ return U(
53
53
  // Host carries the stacking context — ShadowRootWrapper's `isolation` would
54
54
  // otherwise flatten our z-index and let sticky page elements paint over us.
55
55
  /* @__PURE__ */ e(
56
- Y,
56
+ G,
57
57
  {
58
- hostRef: S,
59
- hostStyle: { position: "relative", zIndex: W },
60
- hostProps: j ? { ...z("sidepanel"), [J]: "" } : z("sidepanel"),
58
+ hostRef: $,
59
+ hostStyle: { position: "relative", zIndex: F },
60
+ hostProps: X ? { ...O("sidepanel"), [Z]: "" } : O("sidepanel"),
61
61
  children: /* @__PURE__ */ a("div", { className: "fixed inset-0 z-[1000]", children: [
62
62
  /* @__PURE__ */ e(
63
63
  "div",
64
64
  {
65
65
  "aria-hidden": "true",
66
- onClick: o,
67
- className: `absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${g ? "opacity-100" : "opacity-0"}`
66
+ onClick: c,
67
+ className: `absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${w ? "opacity-100" : "opacity-0"}`
68
68
  }
69
69
  ),
70
70
  /* @__PURE__ */ a(
71
71
  "div",
72
72
  {
73
- ref: L,
73
+ ref: W,
74
74
  role: "dialog",
75
- "aria-modal": M ? "true" : void 0,
75
+ "aria-modal": D ? "true" : void 0,
76
+ "aria-describedby": [
77
+ ...(l ?? []).map((n, s) => `${T}-${s}`),
78
+ m ? I : null
79
+ ].filter(Boolean).join(" ") || void 0,
76
80
  tabIndex: -1,
77
- "aria-label": v ? void 0 : $ ?? (typeof n == "string" ? n : void 0),
78
- "aria-labelledby": v,
81
+ "aria-label": y ? void 0 : L ?? (typeof o == "string" ? o : void 0),
82
+ "aria-labelledby": y,
79
83
  className: "absolute top-[8px] right-[8px] bottom-[8px] flex flex-col overflow-hidden rounded-[20px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-lg)]",
80
84
  style: {
81
- width: `min(calc(100% - 16px), ${K(_)})`,
85
+ width: `min(calc(100% - 16px), ${ee(C)})`,
82
86
  // Inline: Tailwind translate utilities silently break inside
83
87
  // shadow roots (see CLAUDE.md Shadow DOM caveats).
84
- transform: g ? "translateX(0)" : "translateX(calc(100% + 16px))",
85
- transition: `transform ${E}ms ease-in-out`
88
+ transform: w ? "translateX(0)" : "translateX(calc(100% + 16px))",
89
+ transition: `transform ${P}ms ease-in-out`
86
90
  },
87
91
  children: [
88
92
  /* @__PURE__ */ a(
89
93
  "div",
90
94
  {
91
- className: `flex flex-col gap-[20px] px-[20px] py-[18px] ${O ? "border-0 border-b border-solid border-[var(--malachite-card-border)]" : ""}`,
95
+ className: `flex flex-col gap-[20px] px-[20px] py-[18px] ${V ? "border-0 border-b border-solid border-[var(--malachite-card-border)]" : ""}`,
92
96
  children: [
93
97
  /* @__PURE__ */ a("div", { className: "flex items-center gap-[12px]", children: [
94
- b ? /* @__PURE__ */ e(
98
+ v ? /* @__PURE__ */ e(
95
99
  "button",
96
100
  {
97
101
  type: "button",
98
102
  "aria-label": "Back",
99
- onClick: b,
100
- className: k,
101
- children: /* @__PURE__ */ e(U, { size: 16 })
103
+ onClick: v,
104
+ className: E,
105
+ children: /* @__PURE__ */ e(J, { size: 16 })
102
106
  }
103
107
  ) : null,
104
- f ? /* @__PURE__ */ e("span", { className: "flex shrink-0 items-center", children: f }) : null,
108
+ x ? /* @__PURE__ */ e("span", { className: "flex shrink-0 items-center", children: x }) : null,
105
109
  /* @__PURE__ */ a("div", { className: "flex min-w-0 flex-1 items-baseline gap-[12px]", children: [
106
110
  /* @__PURE__ */ a("div", { className: "flex min-w-0 flex-1 items-baseline gap-[8px]", children: [
107
111
  /* @__PURE__ */ a("div", { className: "flex min-w-0 items-baseline gap-[8px]", children: [
108
112
  /* @__PURE__ */ e("span", { className: "flex min-w-0 max-w-full shrink-0", children: /* @__PURE__ */ e(
109
- A,
113
+ z,
110
114
  {
111
- tooltip: n,
115
+ tooltip: o,
112
116
  tooltipWhenTruncated: !0,
113
117
  tooltipPlacement: "bottom",
114
118
  className: "min-w-0 text-[length:var(--malachite-font-size-15,15px)] font-semibold",
115
- children: n
119
+ children: o
116
120
  }
117
121
  ) }),
118
- c ? /* @__PURE__ */ e(
119
- A,
122
+ d ? /* @__PURE__ */ e(
123
+ z,
120
124
  {
121
- tooltip: c,
125
+ tooltip: d,
122
126
  tooltipWhenTruncated: !0,
123
127
  tooltipPlacement: "bottom",
124
128
  color: "secondary",
125
129
  className: "min-w-0 text-[13px]",
126
- children: c
130
+ children: d
127
131
  }
128
132
  ) : null
129
133
  ] }),
130
- p ? /* @__PURE__ */ e("span", { className: "flex shrink-0 items-center", children: p }) : null
134
+ f ? /* @__PURE__ */ e("span", { className: "flex shrink-0 items-center", children: f }) : null
131
135
  ] }),
132
- x ? /* @__PURE__ */ e("span", { className: "flex shrink-0 items-baseline", children: x }) : null
136
+ b ? /* @__PURE__ */ e("span", { className: "flex shrink-0 items-baseline", children: b }) : null
133
137
  ] }),
134
138
  /* @__PURE__ */ e(
135
139
  "button",
136
140
  {
137
141
  type: "button",
138
142
  "aria-label": "Close",
139
- onClick: o,
140
- className: k,
141
- children: /* @__PURE__ */ e(G, { size: 10 })
143
+ onClick: c,
144
+ className: E,
145
+ children: /* @__PURE__ */ e(K, { size: 10 })
142
146
  }
143
147
  )
144
148
  ] }),
145
- u ? /* @__PURE__ */ e("div", { className: "text-[13px] leading-[1.6] text-[var(--malachite-text-secondary)]", children: u }) : null
149
+ m ? /* @__PURE__ */ e(
150
+ "div",
151
+ {
152
+ id: I,
153
+ className: "text-[13px] leading-[1.6] text-[var(--malachite-text-secondary)]",
154
+ children: m
155
+ }
156
+ ) : null
146
157
  ]
147
158
  }
148
159
  ),
149
160
  /* @__PURE__ */ a(
150
161
  "div",
151
162
  {
152
- className: `min-h-0 flex-1 overflow-y-auto ${y && !I ? "px-[28px]" : ""}`,
163
+ className: `min-h-0 flex-1 overflow-y-auto ${N && !A ? "px-[28px]" : ""}`,
153
164
  children: [
154
- l == null ? void 0 : l.map((s, D) => /* @__PURE__ */ e(
155
- q,
165
+ l == null ? void 0 : l.map((n, s) => /* @__PURE__ */ e(
166
+ H,
156
167
  {
157
- type: s.type,
158
- message: s.message,
159
- renderLink: V
168
+ id: `${T}-${s}`,
169
+ type: n.type,
170
+ message: n.message,
171
+ renderLink: B
160
172
  },
161
- D
173
+ s
162
174
  )),
163
- B
175
+ Y
164
176
  ]
165
177
  }
166
178
  ),
@@ -175,7 +187,7 @@ const E = 200, K = (t) => typeof t == "number" ? `${t}px` : t, oe = ({
175
187
  );
176
188
  };
177
189
  export {
178
- oe as SidePanel,
179
- oe as default
190
+ me as SidePanel,
191
+ me as default
180
192
  };
181
193
  //# sourceMappingURL=SidePanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SidePanel.js","sources":["../../src/components/SidePanel.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ShadowRootWrapper } from '../ShadowRootWrapper';\nimport type { AlertType } from './Alert';\nimport CalloutStrip from './CalloutStrip';\nimport ArrowLeftIcon from './CustomIcons/ArrowLeftIcon';\nimport DismissIcon from './CustomIcons/DismissIcon';\nimport type { RenderLink } from './PageLayout/types';\nimport EllipsisText from './Typography/EllipsisText';\nimport { OVERLAY_ICON_BUTTON_CLASSES, useOverlayBehaviour } from './overlayBehaviour';\nimport { OVERLAY_ELEVATED_ATTR, overlayIdentity } from './portalOverlay';\n\nexport interface SidePanelProps {\n open: boolean;\n /** Dismiss handler — close button, ESC, or overlay click. */\n onClose: () => void;\n title: React.ReactNode;\n /** Secondary text, inline after the title. */\n subtitle?: React.ReactNode;\n /** Content at the end of the title row, after the subtitle (e.g. a status tag). */\n titleExtra?: React.ReactNode;\n /** Block paragraph below the title row, spanning the header width. */\n description?: React.ReactNode;\n /** Divider under the header. Default: false. */\n headerDivider?: boolean;\n /** Leading header slot, typically a logo or icon. */\n icon?: React.ReactNode;\n /** Content pinned right in the header, before the close button. */\n headerExtra?: React.ReactNode;\n footer?: React.ReactNode;\n /** Renders a back button before the header icon. */\n onBack?: () => void;\n /** Maximum panel width. Default: 1080. */\n maxWidth?: number | string;\n /** Accessible dialog name; required when `title` isn't a plain string. */\n 'aria-label'?: string;\n /** ID of a visible element naming the dialog (alternative to `aria-label`). */\n 'aria-labelledby'?: string;\n /** Whether the body gets default padding. Default: true. */\n bodyPadding?: boolean;\n /** `above-app-overlays` lifts the overlay stack clear of chrome pinned near max-int. */\n stacking?: 'default' | 'above-app-overlays';\n /** Full-width Alert bands at the top of the body, above `children`, in order. Unaffected by\n * `bodyPadding`. The message is markdown. */\n callouts?: { type?: AlertType; message: string }[];\n /** Routes root-relative links in `callouts` through the app's router; see `MarkdownBlock`. */\n renderLink?: RenderLink;\n children: React.ReactNode;\n}\n\nconst TRANSITION_MS = 200;\n\nconst toCssSize = (value: number | string): string =>\n typeof value === 'number' ? `${value}px` : value;\n\n/**\n * A right-hand slide-in overlay panel (drawer) for inspecting or editing an entity\n * without leaving the page. Portals to `document.body`, locks background scroll and\n * traps keyboard focus while open (restoring both on close), dismisses on ESC\n * (topmost open overlay only), overlay click or the close button, and exposes header\n * (back/icon/title/subtitle/extra), scrollable body and optional footer slots.\n */\nconst SidePanel: React.FC<SidePanelProps> = ({\n open,\n onClose,\n title,\n subtitle,\n titleExtra,\n description,\n headerDivider = false,\n icon,\n headerExtra,\n footer,\n onBack,\n maxWidth = 1080,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n bodyPadding = true,\n stacking = 'default',\n callouts,\n renderLink,\n children,\n}) => {\n const [isVisible, setIsVisible] = useState(false);\n const [isMounted, setIsMounted] = useState(false);\n const frameRef = useRef<number | null>(null);\n const unmountTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const hostRef = useRef<HTMLDivElement | null>(null);\n const {\n ref: handlePanelRef,\n isTopmost,\n zIndex,\n elevated,\n } = useOverlayBehaviour({\n open,\n onClose,\n elevated: stacking === 'above-app-overlays',\n hostRef,\n });\n\n useEffect(() => {\n if (open) {\n setIsMounted(true);\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n frameRef.current = requestAnimationFrame(() => {\n frameRef.current = null;\n setIsVisible(true);\n });\n } else {\n setIsVisible(false);\n unmountTimerRef.current = setTimeout(() => {\n unmountTimerRef.current = null;\n setIsMounted(false);\n }, TRANSITION_MS);\n }\n return () => {\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current);\n frameRef.current = null;\n }\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n };\n }, [open]);\n\n if (!isMounted) {\n return null;\n }\n\n const shownCallouts = callouts?.filter((callout) => callout.message.trim());\n const hasCallouts = !!shownCallouts?.length;\n const paddedBody = bodyPadding ? (\n <div className={hasCallouts ? 'p-[28px]' : 'py-[28px]'}>{children}</div>\n ) : (\n children\n );\n\n return createPortal(\n // Host carries the stacking context — ShadowRootWrapper's `isolation` would\n // otherwise flatten our z-index and let sticky page elements paint over us.\n <ShadowRootWrapper\n hostRef={hostRef}\n hostStyle={{ position: 'relative', zIndex }}\n hostProps={\n elevated\n ? { ...overlayIdentity('sidepanel'), [OVERLAY_ELEVATED_ATTR]: '' }\n : overlayIdentity('sidepanel')\n }\n >\n <div className=\"fixed inset-0 z-[1000]\">\n <div\n aria-hidden=\"true\"\n onClick={onClose}\n className={`absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${\n isVisible ? 'opacity-100' : 'opacity-0'\n }`}\n />\n <div\n ref={handlePanelRef}\n role=\"dialog\"\n // Only the topmost overlay may claim it, or a screen reader can still\n // browse the one underneath.\n aria-modal={isTopmost ? 'true' : undefined}\n tabIndex={-1}\n // Prefer aria-labelledby; only fall back to aria-label when it's absent.\n aria-label={\n ariaLabelledBy\n ? undefined\n : (ariaLabel ?? (typeof title === 'string' ? title : undefined))\n }\n aria-labelledby={ariaLabelledBy}\n className=\"absolute top-[8px] right-[8px] bottom-[8px] flex flex-col overflow-hidden rounded-[20px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-lg)]\"\n style={{\n width: `min(calc(100% - 16px), ${toCssSize(maxWidth)})`,\n // Inline: Tailwind translate utilities silently break inside\n // shadow roots (see CLAUDE.md Shadow DOM caveats).\n transform: isVisible ? 'translateX(0)' : 'translateX(calc(100% + 16px))',\n transition: `transform ${TRANSITION_MS}ms ease-in-out`,\n }}\n >\n <div\n className={`flex flex-col gap-[20px] px-[20px] py-[18px] ${\n headerDivider\n ? 'border-0 border-b border-solid border-[var(--malachite-card-border)]'\n : ''\n }`}\n >\n <div className=\"flex items-center gap-[12px]\">\n {onBack ? (\n <button\n type=\"button\"\n aria-label=\"Back\"\n onClick={onBack}\n className={OVERLAY_ICON_BUTTON_CLASSES}\n >\n <ArrowLeftIcon size={16} />\n </button>\n ) : null}\n {icon ? (\n <span className=\"flex shrink-0 items-center\">{icon}</span>\n ) : null}\n <div className=\"flex min-w-0 flex-1 items-baseline gap-[12px]\">\n <div className=\"flex min-w-0 flex-1 items-baseline gap-[8px]\">\n <div className=\"flex min-w-0 items-baseline gap-[8px]\">\n <span className=\"flex min-w-0 max-w-full shrink-0\">\n <EllipsisText\n tooltip={title}\n tooltipWhenTruncated\n tooltipPlacement=\"bottom\"\n className=\"min-w-0 text-[length:var(--malachite-font-size-15,15px)] font-semibold\"\n >\n {title}\n </EllipsisText>\n </span>\n {subtitle ? (\n <EllipsisText\n tooltip={subtitle}\n tooltipWhenTruncated\n tooltipPlacement=\"bottom\"\n color=\"secondary\"\n className=\"min-w-0 text-[13px]\"\n >\n {subtitle}\n </EllipsisText>\n ) : null}\n </div>\n {titleExtra ? (\n <span className=\"flex shrink-0 items-center\">\n {titleExtra}\n </span>\n ) : null}\n </div>\n {headerExtra ? (\n <span className=\"flex shrink-0 items-baseline\">\n {headerExtra}\n </span>\n ) : null}\n </div>\n <button\n type=\"button\"\n aria-label=\"Close\"\n onClick={onClose}\n className={OVERLAY_ICON_BUTTON_CLASSES}\n >\n <DismissIcon size={10} />\n </button>\n </div>\n {description ? (\n <div className=\"text-[13px] leading-[1.6] text-[var(--malachite-text-secondary)]\">\n {description}\n </div>\n ) : null}\n </div>\n {/* Callouts move the horizontal padding inward so their strips run full-bleed. */}\n <div\n className={`min-h-0 flex-1 overflow-y-auto ${bodyPadding && !hasCallouts ? 'px-[28px]' : ''}`}\n >\n {shownCallouts?.map((callout, index) => (\n <CalloutStrip\n key={index}\n type={callout.type}\n message={callout.message}\n renderLink={renderLink}\n />\n ))}\n {paddedBody}\n </div>\n {footer ? (\n <div className=\"border-0 border-t border-solid border-[var(--malachite-card-border)] px-[24px] py-[16px]\">\n {footer}\n </div>\n ) : null}\n </div>\n </div>\n </ShadowRootWrapper>,\n document.body,\n );\n};\n\nexport default SidePanel;\nexport { SidePanel };\n"],"names":["TRANSITION_MS","toCssSize","value","SidePanel","open","onClose","title","subtitle","titleExtra","description","headerDivider","icon","headerExtra","footer","onBack","maxWidth","ariaLabel","ariaLabelledBy","bodyPadding","stacking","callouts","renderLink","children","isVisible","setIsVisible","useState","isMounted","setIsMounted","frameRef","useRef","unmountTimerRef","hostRef","handlePanelRef","isTopmost","zIndex","elevated","useOverlayBehaviour","useEffect","shownCallouts","callout","hasCallouts","paddedBody","jsx","createPortal","ShadowRootWrapper","overlayIdentity","OVERLAY_ELEVATED_ATTR","jsxs","OVERLAY_ICON_BUTTON_CLASSES","ArrowLeftIcon","EllipsisText","DismissIcon","index","CalloutStrip"],"mappings":"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"}
1
+ {"version":3,"file":"SidePanel.js","sources":["../../src/components/SidePanel.tsx"],"sourcesContent":["import React, { useEffect, useId, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ShadowRootWrapper } from '../ShadowRootWrapper';\nimport type { AlertType } from './Alert';\nimport CalloutStrip from './CalloutStrip';\nimport ArrowLeftIcon from './CustomIcons/ArrowLeftIcon';\nimport DismissIcon from './CustomIcons/DismissIcon';\nimport type { RenderLink } from './PageLayout/types';\nimport EllipsisText from './Typography/EllipsisText';\nimport { OVERLAY_ICON_BUTTON_CLASSES, useOverlayBehaviour } from './overlayBehaviour';\nimport { OVERLAY_ELEVATED_ATTR, overlayIdentity } from './portalOverlay';\n\nexport interface SidePanelProps {\n open: boolean;\n /** Dismiss handler — close button, ESC, or overlay click. */\n onClose: () => void;\n title: React.ReactNode;\n /** Secondary text, inline after the title. */\n subtitle?: React.ReactNode;\n /** Content at the end of the title row, after the subtitle (e.g. a status tag). */\n titleExtra?: React.ReactNode;\n /** Block paragraph below the title row, spanning the header width. Announced as the panel's description. */\n description?: React.ReactNode;\n /** Divider under the header. Default: false. */\n headerDivider?: boolean;\n /** Leading header slot, typically a logo or icon. */\n icon?: React.ReactNode;\n /** Content pinned right in the header, before the close button. */\n headerExtra?: React.ReactNode;\n footer?: React.ReactNode;\n /** Renders a back button before the header icon. */\n onBack?: () => void;\n /** Maximum panel width. Default: 1080. */\n maxWidth?: number | string;\n /** Accessible dialog name; required when `title` isn't a plain string. */\n 'aria-label'?: string;\n /** ID of a visible element naming the dialog (alternative to `aria-label`). */\n 'aria-labelledby'?: string;\n /** Whether the body gets default padding. Default: true. */\n bodyPadding?: boolean;\n /** `above-app-overlays` lifts the overlay stack clear of chrome pinned near max-int. */\n stacking?: 'default' | 'above-app-overlays';\n /** Full-width Alert bands at the top of the body, above `children`, in order. Unaffected by\n * `bodyPadding`, and announced ahead of `description`. The message is markdown. */\n callouts?: { type?: AlertType; message: string }[];\n /** Routes root-relative links in `callouts` through the app's router; see `MarkdownBlock`. */\n renderLink?: RenderLink;\n children: React.ReactNode;\n}\n\nconst TRANSITION_MS = 200;\n\nconst toCssSize = (value: number | string): string =>\n typeof value === 'number' ? `${value}px` : value;\n\n/**\n * A right-hand slide-in overlay panel (drawer) for inspecting or editing an entity\n * without leaving the page. Portals to `document.body`, locks background scroll and\n * traps keyboard focus while open (restoring both on close), dismisses on ESC\n * (topmost open overlay only), overlay click or the close button, and exposes header\n * (back/icon/title/subtitle/extra), scrollable body and optional footer slots.\n */\nconst SidePanel: React.FC<SidePanelProps> = ({\n open,\n onClose,\n title,\n subtitle,\n titleExtra,\n description,\n headerDivider = false,\n icon,\n headerExtra,\n footer,\n onBack,\n maxWidth = 1080,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n bodyPadding = true,\n stacking = 'default',\n callouts,\n renderLink,\n children,\n}) => {\n const calloutId = useId();\n const descriptionId = useId();\n const [isVisible, setIsVisible] = useState(false);\n const [isMounted, setIsMounted] = useState(false);\n const frameRef = useRef<number | null>(null);\n const unmountTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const hostRef = useRef<HTMLDivElement | null>(null);\n const {\n ref: handlePanelRef,\n isTopmost,\n zIndex,\n elevated,\n } = useOverlayBehaviour({\n open,\n onClose,\n elevated: stacking === 'above-app-overlays',\n hostRef,\n });\n\n useEffect(() => {\n if (open) {\n setIsMounted(true);\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n frameRef.current = requestAnimationFrame(() => {\n frameRef.current = null;\n setIsVisible(true);\n });\n } else {\n setIsVisible(false);\n unmountTimerRef.current = setTimeout(() => {\n unmountTimerRef.current = null;\n setIsMounted(false);\n }, TRANSITION_MS);\n }\n return () => {\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current);\n frameRef.current = null;\n }\n if (unmountTimerRef.current) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n };\n }, [open]);\n\n if (!isMounted) {\n return null;\n }\n\n const shownCallouts = callouts?.filter((callout) => callout.message.trim());\n const hasCallouts = !!shownCallouts?.length;\n const paddedBody = bodyPadding ? (\n <div className={hasCallouts ? 'p-[28px]' : 'py-[28px]'}>{children}</div>\n ) : (\n children\n );\n\n return createPortal(\n // Host carries the stacking context — ShadowRootWrapper's `isolation` would\n // otherwise flatten our z-index and let sticky page elements paint over us.\n <ShadowRootWrapper\n hostRef={hostRef}\n hostStyle={{ position: 'relative', zIndex }}\n hostProps={\n elevated\n ? { ...overlayIdentity('sidepanel'), [OVERLAY_ELEVATED_ATTR]: '' }\n : overlayIdentity('sidepanel')\n }\n >\n <div className=\"fixed inset-0 z-[1000]\">\n <div\n aria-hidden=\"true\"\n onClick={onClose}\n className={`absolute inset-0 bg-[var(--malachite-overlay-scrim)] transition-opacity duration-200 ease-in-out ${\n isVisible ? 'opacity-100' : 'opacity-0'\n }`}\n />\n <div\n ref={handlePanelRef}\n role=\"dialog\"\n // Only the topmost overlay may claim it, or a screen reader can still\n // browse the one underneath.\n aria-modal={isTopmost ? 'true' : undefined}\n aria-describedby={\n [\n ...(shownCallouts ?? []).map((_, index) => `${calloutId}-${index}`),\n description ? descriptionId : null,\n ]\n .filter(Boolean)\n .join(' ') || undefined\n }\n tabIndex={-1}\n // Prefer aria-labelledby; only fall back to aria-label when it's absent.\n aria-label={\n ariaLabelledBy\n ? undefined\n : (ariaLabel ?? (typeof title === 'string' ? title : undefined))\n }\n aria-labelledby={ariaLabelledBy}\n className=\"absolute top-[8px] right-[8px] bottom-[8px] flex flex-col overflow-hidden rounded-[20px] border border-solid border-[var(--malachite-card-border)] bg-[var(--malachite-card-background)] text-[var(--malachite-card-foreground)] outline-none [box-shadow:var(--malachite-shadow-lg)]\"\n style={{\n width: `min(calc(100% - 16px), ${toCssSize(maxWidth)})`,\n // Inline: Tailwind translate utilities silently break inside\n // shadow roots (see CLAUDE.md Shadow DOM caveats).\n transform: isVisible ? 'translateX(0)' : 'translateX(calc(100% + 16px))',\n transition: `transform ${TRANSITION_MS}ms ease-in-out`,\n }}\n >\n <div\n className={`flex flex-col gap-[20px] px-[20px] py-[18px] ${\n headerDivider\n ? 'border-0 border-b border-solid border-[var(--malachite-card-border)]'\n : ''\n }`}\n >\n <div className=\"flex items-center gap-[12px]\">\n {onBack ? (\n <button\n type=\"button\"\n aria-label=\"Back\"\n onClick={onBack}\n className={OVERLAY_ICON_BUTTON_CLASSES}\n >\n <ArrowLeftIcon size={16} />\n </button>\n ) : null}\n {icon ? (\n <span className=\"flex shrink-0 items-center\">{icon}</span>\n ) : null}\n <div className=\"flex min-w-0 flex-1 items-baseline gap-[12px]\">\n <div className=\"flex min-w-0 flex-1 items-baseline gap-[8px]\">\n <div className=\"flex min-w-0 items-baseline gap-[8px]\">\n <span className=\"flex min-w-0 max-w-full shrink-0\">\n <EllipsisText\n tooltip={title}\n tooltipWhenTruncated\n tooltipPlacement=\"bottom\"\n className=\"min-w-0 text-[length:var(--malachite-font-size-15,15px)] font-semibold\"\n >\n {title}\n </EllipsisText>\n </span>\n {subtitle ? (\n <EllipsisText\n tooltip={subtitle}\n tooltipWhenTruncated\n tooltipPlacement=\"bottom\"\n color=\"secondary\"\n className=\"min-w-0 text-[13px]\"\n >\n {subtitle}\n </EllipsisText>\n ) : null}\n </div>\n {titleExtra ? (\n <span className=\"flex shrink-0 items-center\">\n {titleExtra}\n </span>\n ) : null}\n </div>\n {headerExtra ? (\n <span className=\"flex shrink-0 items-baseline\">\n {headerExtra}\n </span>\n ) : null}\n </div>\n <button\n type=\"button\"\n aria-label=\"Close\"\n onClick={onClose}\n className={OVERLAY_ICON_BUTTON_CLASSES}\n >\n <DismissIcon size={10} />\n </button>\n </div>\n {description ? (\n <div\n id={descriptionId}\n className=\"text-[13px] leading-[1.6] text-[var(--malachite-text-secondary)]\"\n >\n {description}\n </div>\n ) : null}\n </div>\n {/* Callouts move the horizontal padding inward so their strips run full-bleed. */}\n <div\n className={`min-h-0 flex-1 overflow-y-auto ${bodyPadding && !hasCallouts ? 'px-[28px]' : ''}`}\n >\n {shownCallouts?.map((callout, index) => (\n <CalloutStrip\n key={index}\n id={`${calloutId}-${index}`}\n type={callout.type}\n message={callout.message}\n renderLink={renderLink}\n />\n ))}\n {paddedBody}\n </div>\n {footer ? (\n <div className=\"border-0 border-t border-solid border-[var(--malachite-card-border)] px-[24px] py-[16px]\">\n {footer}\n </div>\n ) : null}\n </div>\n </div>\n </ShadowRootWrapper>,\n document.body,\n );\n};\n\nexport default SidePanel;\nexport { SidePanel };\n"],"names":["TRANSITION_MS","toCssSize","value","SidePanel","open","onClose","title","subtitle","titleExtra","description","headerDivider","icon","headerExtra","footer","onBack","maxWidth","ariaLabel","ariaLabelledBy","bodyPadding","stacking","callouts","renderLink","children","calloutId","useId","descriptionId","isVisible","setIsVisible","useState","isMounted","setIsMounted","frameRef","useRef","unmountTimerRef","hostRef","handlePanelRef","isTopmost","zIndex","elevated","useOverlayBehaviour","useEffect","shownCallouts","callout","hasCallouts","paddedBody","jsx","createPortal","ShadowRootWrapper","overlayIdentity","OVERLAY_ELEVATED_ATTR","jsxs","_","index","OVERLAY_ICON_BUTTON_CLASSES","ArrowLeftIcon","EllipsisText","DismissIcon","CalloutStrip"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -3380,6 +3380,14 @@ export declare interface ModalProps {
3380
3380
  closeOnOverlayClick?: boolean;
3381
3381
  /** Whether the body gets default padding. Default: true. */
3382
3382
  bodyPadding?: boolean;
3383
+ /** Full-width Alert bands at the top of the body, above `children`, in order. Unaffected by
3384
+ * `bodyPadding`, and announced ahead of the dialog's description. The message is markdown. */
3385
+ callouts?: {
3386
+ type?: AlertType;
3387
+ message: string;
3388
+ }[];
3389
+ /** Routes root-relative links in `callouts` through the app's router; see `MarkdownBlock`. */
3390
+ renderLink?: RenderLink;
3383
3391
  /**
3384
3392
  * `alertdialog` for a dialog interrupting the user over a consequential choice — it
3385
3393
  * tells assistive tech to announce the body, not just the name.
@@ -4389,7 +4397,7 @@ export declare interface SidePanelProps {
4389
4397
  subtitle?: default_2.ReactNode;
4390
4398
  /** Content at the end of the title row, after the subtitle (e.g. a status tag). */
4391
4399
  titleExtra?: default_2.ReactNode;
4392
- /** Block paragraph below the title row, spanning the header width. */
4400
+ /** Block paragraph below the title row, spanning the header width. Announced as the panel's description. */
4393
4401
  description?: default_2.ReactNode;
4394
4402
  /** Divider under the header. Default: false. */
4395
4403
  headerDivider?: boolean;
@@ -4411,7 +4419,7 @@ export declare interface SidePanelProps {
4411
4419
  /** `above-app-overlays` lifts the overlay stack clear of chrome pinned near max-int. */
4412
4420
  stacking?: 'default' | 'above-app-overlays';
4413
4421
  /** Full-width Alert bands at the top of the body, above `children`, in order. Unaffected by
4414
- * `bodyPadding`. The message is markdown. */
4422
+ * `bodyPadding`, and announced ahead of `description`. The message is markdown. */
4415
4423
  callouts?: {
4416
4424
  type?: AlertType;
4417
4425
  message: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackone/malachite",
3
- "version": "2.61.0",
3
+ "version": "2.62.1",
4
4
  "description": "StackOne's themeable React component library — Shadow DOM isolated, scoped Tailwind, theme-token driven.",
5
5
  "license": "MIT",
6
6
  "author": "StackOne",