@stackone/malachite 2.71.0 → 2.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/COMPONENTS.md CHANGED
@@ -27,14 +27,14 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
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`), `initialFocus` (a ref, or a function read when the panel is shown, for what takes focus in place of the first focusable; it is read again as the panel's content changes, so focus follows content that mounts late or a focused element that is removed, but never leaves an element the user moved to; `initialFocusRef` is its deprecated form, read on open only), `openOnArrowKey` (a click panel also opens, focus inside, on the arrow key its placement points at — ArrowDown for a `bottom-*` placement; a text field trigger keeps its arrow keys), `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`, `divider` — `false` drops the rule and sits it 4px over the list, as its label), `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; `scrollToSelected` scrolls the list, never the page, to its selected row, one marked `aria-current`, `aria-selected` or a checked radio, each time the popover is shown, once that row mounts), `Popover.Section` (a bordered section), `Popover.Empty` (an empty-state line), `Popover.Search` (pads a `Search` above the list without a second rule), `Popover.Note` (a footnote section in small secondary text) and `Popover.Status` (`message`, `tone` (`default` | `error`), `action` (`label`, `onClick`, `pending`), `focusable`): one `status` live region for the panel's life, announcing each message it is given and staying mounted, visually hidden, while there is none, so clear `message` rather than unmounting it. Errors announce through the same region. `focusable` with a ref makes it an `initialFocus` target; resolve to it only while it has a message, since an empty one is invisible. Reach for `Status` when the message changes while the panel is open or carries an action (loading, failed, sign-in needed), and `Empty` for a static line in a list that simply has nothing in it.
29
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`, `bodyHeight` (`auto` | `tall` — 600px, or the viewport less 160px when smaller, for an embedded iframe at `height: 100%`), `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`.
30
+ - **`SidePanel`** — Right-hand slide-in overlay panel (drawer) with header slots (back/icon/title/subtitle/extra), scrollable body and optional footer. `subtitlePlacement="below"` stacks the subtitle on its own line under the title row, so neither truncates the other in a narrow panel. `toolbar` pins content between the header and the scrolling body (16px sides, 16px above, 8px below, no divider), such as a Search over the list it filters. 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
 
34
34
  ## Feedback & status
35
35
 
36
36
  - **`ChangeReview`** — Reviewing changes a machine made. `ChangeReview.Item` attaches to one changed thing: it names what changed with an `assistant` Tag, shows the value it displaced struck through, and offers approve and deny as two equally reachable icon buttons — a review affordance that made keeping easier than rejecting would not be one. Given `children`, it wraps the field the change is annotating: the tag/label/actions become a header band above it and the before/after line moves below, so "Was …" reads next to the value it describes instead of stacked with the header; without children it renders as the standalone card it always has. `ChangeReview.Summary` counts what is still undecided across the whole form, which is what a per-item control cannot do and what a caller gates a save on; at zero it either says so or collapses to nothing. `ChangeReview.Removed` is for something the assistant took out: a dashed, tinted card listing what would be lost, because a removal that simply vanished would leave nothing to reject. `ChangeReview.Jump` is a chip an assistant turn carries to point at a field it touched. _Key props:_ `Item`: `label`, `before`, `after`, `kind` (`changed` | `added` | `removed`), `origin`, `children`, `onApprove`, `onDeny`, `disabled`, `className`. `Summary`: `count`, `origin`, `onApproveAll`, `onDenyAll`, `disabled`, `settledMessage`, `className`. `Removed`: `title`, `details` (`{label, value}[]`), `onRestore`, `onDiscard`, `restoreLabel`, `discardLabel`, `origin`, `disabled`, `className`. `Jump`: `label`, `onClick`, `disabled`, `className`. `Item` also takes `onPreviewRevert(previewing)`, fired on hover and on focus.
37
- - **`Alert`** — Inline alert/callout. Four severities (info, success, warning, error) plus `assistant` — a violet, sparkle-marked ground that says a machine wrote the message rather than that anything is wrong, so provenance never has to borrow a severity colour. `action` puts a control at the end — the link or button that resolves what the alert reports — and `icon` replaces the status icon; an svg that paints with `currentColor` (a CustomIcon) is drawn in the colour and size of the built-in one. _Key props:_ `type`, `message`, `lead` (a bold sentence the message runs on from; plain text), `layout` (`inline` | `banner` — a 48px page-wide band with a 16px icon, for `WizardLayout`'s `banner` slot; an expandable alert stays inline), `action`, `icon`, `hasMargin`, `live` (makes the message a live region — `role="alert"` for error/warning, announced as it mounts; `status` otherwise, announced when its `message` changes, so keep it mounted; off by default), `renderLink` (routes root-relative links in `message` through the app’s router, as `MarkdownBlock` does), `children` (expandable body), `isExpanded`, `onToggle`, `className`.
37
+ - **`Alert`** — Inline alert/callout. Four severities (info, success, warning, error) plus `assistant` — a violet, sparkle-marked ground that says a machine wrote the message rather than that anything is wrong, so provenance never has to borrow a severity colour. `action` puts a control at the end — the link or button that resolves what the alert reports — and `icon` replaces the status icon; an svg that paints with `currentColor` (a CustomIcon) is drawn in the colour and size of the built-in one. _Key props:_ `type`, `message`, `lead` (a bold sentence the message runs on from; plain text), `layout` (`inline` | `banner` — a 48px page-wide band with a 16px icon, for `WizardLayout`'s `banner` slot; an expandable alert stays inline), `align` (`start` | `center`, the banner icon and text gathered mid-band), `action`, `icon`, `hasMargin`, `live` (makes the message a live region — `role="alert"` for error/warning, announced as it mounts; `status` otherwise, announced when its `message` changes, so keep it mounted; off by default), `renderLink` (routes root-relative links in `message` through the app’s router, as `MarkdownBlock` does), `children` (expandable body), `isExpanded`, `onToggle`, `className`.
38
38
  - **`IconIndicator`** — A non-interactive icon that carries a meaning, such as a capability shown on a page that cannot switch it on. Same square as an icon-only `Button` at each `size`, so it lines up where a sibling view shows the button. Renders `role="img"`, named by `aria-label`; with a `tooltip` it takes focus so the tooltip is reachable by keyboard. Reach for it instead of a hand-sized `<span>` around an icon. _Key props:_ `icon`, `aria-label`, `tooltip`, `size`.
39
39
  - **`IconBadge`** — A small filled tile carrying a 12px glyph, for a dialog or panel header such as `SidePanel`’s `icon`. Decorative and always `aria-hidden`: the title beside it carries the meaning, so reach for `IconIndicator` when the icon itself has to say something. `variant` picks a semantic fill (`default` neutral, `accent` the interaction colour, `warning`, `destructive`, `success`) rather than taking a colour, and the glyph is drawn in the card background. `ConfirmationDialog` draws its badge with it. _Key props:_ `icon`, `variant` (`default` | `accent` | `warning` | `destructive` | `success`), `className`.
40
40
  - **`StatusBadge`** — Large status indicator (e.g. success checkmark) for terminal/confirmation screens. _Key props:_ `type`.
@@ -56,7 +56,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
56
56
  ## Lists, rows & navigation
57
57
 
58
58
  - **`ButtonList`** — Vertical list of pill-style button/link rows with built-in hover/focus/active states. _Key props:_ `buttons[]` (`key`, `children`, `onClick`, `href`), `compact`.
59
- - **`Accordion`** — Expand/collapse section with a clickable header (disclosure arrow + title/header + trailing slot). Use `header` for rich content and `trailing` for an interactive control (e.g. a Checkbox) — trailing sits outside the toggle button, so a selectable+expandable row is just an Accordion. Controlled or uncontrolled. Pass `stickyHeader` (+ optional `stickyTopOffset`) to pin the header while the body scrolls — needs a scroll ancestor that clips without trapping sticky (e.g. `<Surface overflow="clip">`). `animate={false}` opens instantly. _Key props:_ `title` or `header`, `trailing`, `disabled`, `isExpanded`/`defaultExpanded`, `onToggle`, `stickyHeader`, `stickyTopOffset`, `animate`.
59
+ - **`Accordion`** — Expand/collapse section with a clickable header (disclosure arrow + title/header + trailing slot). Use `header` for rich content and `trailing` for an interactive control (e.g. a Checkbox) — trailing sits outside the toggle button, so a selectable+expandable row is just an Accordion. Controlled or uncontrolled. Pass `stickyHeader` (+ optional `stickyTopOffset`) to pin the header while the body scrolls — needs a scroll ancestor that clips without trapping sticky (e.g. `<Surface overflow="clip">`). `animate={false}` opens instantly. _Key props:_ `title` or `header`, `leading` (before the title, outside the toggle, e.g. a group's tri-state Checkbox), `trailing`, `arrowPosition` (`start` | `end`), `highlightExpanded` (default `true`; `false` leaves an open row on the card ground), `disabled`, `isExpanded`/`defaultExpanded`, `onToggle`, `stickyHeader`, `stickyTopOffset`, `animate`.
60
60
  - **`DefinitionRow`** — Row showing an icon, label, optional tag and description — for definition/detail lists. _Key props:_ `icon`, `label`, `tag`, `description`.
61
61
  - **`PreviewTree`** — Nested, indented tree for previewing hierarchical structure.
62
62
  - **`PreviewTreeRow`** — A single labelled row within a PreviewTree. _Key props:_ `label`.
@@ -64,7 +64,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
64
64
  - **`Breadcrumbs`** — Breadcrumb trail with caret separators, an optional back button (shown only when there is more than one crumb AND the parent — the second-to-last — carries a `url` to target) and a right-aligned `actions` row. A lone crumb marked `isRoot` switches to title mode: plain emphasised text, no trail chrome, but the `actions` row still renders. Renders as a `nav`/`ol` with `aria-current="page"` on the last crumb. Pass `renderLink` to route crumbs through the app's own router instead of a plain `<a>`. Used by `PageLayout`, but standalone too. _Key props:_ `links[]` (`name`, `url`, `isRoot`), `actions[]`, `renderLink`, `className`, `style`.
65
65
  - **`ActionMenu`** — Dropdown menu of actions (kebab/overflow style). The trigger is an icon-only `Button` by default, so `size` draws exactly the square a `Button` of that size draws beside it: `xsmall` is Figma's 20px "Button Element - Square (X-Small)", `small` the 28px rung above it. Both squares are fixed rather than read off the `controlHeights` tokens, so re-theming those does not resize the trigger. A supplied `triggerIcon` is held to the tier's glyph size — 11px at `xsmall`, 16px at `small`. At 20px the `xsmall` trigger meets WCAG 2.5.8 through the *Spacing* exception rather than the 24px minimum, so keep at least 4px between adjacent triggers. `showLabel` draws `label` beside the icon instead of leaving it to the accessible name — for a menu that is the point of its corner rather than an overflow for a row that already says what it acts on — and `variant` puts the trigger on any `Button` variant, so a menu handing work to the assistant can wear the assistant ground rather than the overflow-kebab outline. An item's `icon` leads its label: every caller's names the action itself — copy, open, delete — and an icon that is part of an item's identity belongs where the eye starts rather than right-aligned, where it reads as a status column. The menu is Figma's `Dropdown Component - Options List`: a fixed sized to its widest item between a 180px floor and the 280px Figma draws, by default under the trigger's right edge, 14px below it, flipping above or clamping sideways when the viewport has no room, rounding at the library's 12px and padding wider (8px) than the menu a `Dropdown` opens. Items read in the primary text colour, or the secondary one with `tone="muted"` (e.g. a sidebar's menus); destructive ones in red; `danger: 'hover'` keeps an item in its normal colour and turns it destructive (red text on a light red ground) only on hover or keyboard focus, for an action like signing out. A ceiling rather than a flat width: taken literally it gave a menu of four short verbs 130px of air and made every kebab in an app the same width whatever it held. `minWidth` only raises it above that ceiling, so it is the one way to go past 280px, and a label too long for the box truncates rather than wrapping. _Key props:_ `ActionMenuItem[]`, `size`, `label`, `triggerIcon`, `minWidth`, `showLabel`, `variant`, `tone` (`default` | `muted`), `trigger` (your own control in place of the kebab, cloned with the menu's ref and aria state), `placement` (`bottom-end` default | `bottom-start` | `right-start` | `top-start` | `top-center`), `offset` (`small` 8px | `medium` 12px | `large` 14px; applies to every placement; without it the default `bottom-end` keeps the options menu's 14px and the others use 8px). An item's `tag` (e.g. an "Early access" `Tag`) follows its label in a lazily loaded shadow host of its own, since the menu renders into `document.body`, and joins its accessible name; `current: true` marks the item for the page the user is on (`aria-current="page"` and the selected row paint; a `danger` item keeps its red text). An item with an `href` is a link: Cmd/Ctrl-click or a middle click opens it in a new tab and closes the menu, while a plain click runs `onClick` in its place when one is given (so a router can navigate in-app); Enter and Space both activate it. `target: '_blank'` opens it in a new tab, adds `noopener noreferrer` and tells screen readers it opens a new tab. An item with `type: 'text'` is context rather than an action (an account's email above its actions): secondary text the arrow keys skip. Opened from the keyboard, focus lands on the first item; Up/Down/Home/End move through enabled items, Tab closes the menu and moves on as usual; Escape or a picked item returns focus to the trigger.
66
66
  - **`NavItem`** — A row in an app's side navigation. Idle rows read secondary and take the list hover; the `active` row wears the accent tint and sets `aria-current="page"` on a link. `collapsed` shrinks it to a 36px icon tile whose label becomes the accessible name and a right-hand tooltip, with any `badge` pinned to the tile's corner. `variant="subitem"` indents a child row behind a dot instead of an icon; `disclosure` makes a group header button with `aria-expanded` and a rotating chevron, and `hasActiveChild` colours its text while a child is current. Renders an `<a>` with `href`, the element passed with `asChild` (a router `Link`), or a `<button>`, and forwards its ref and extra attributes so it can be a `Popover` or `ActionMenu` trigger. `disabled` keeps the row a tab stop with `aria-disabled`, drops its hover and pointer, and ignores clicks, Enter and Space; a `Popover` or `ActionMenu` it triggers stays shut on press, hover, focus and arrow keys, while other keys still reach a passed `onKeyDown` so arrow navigation through a list keeps working; a disabled `href` row renders as a button so it cannot be followed. `disabledReason` explains it in a right-hand tooltip on hover and focus and is the row's accessible description from the first focus. Not with `asChild`, whose child would still navigate. _Key props:_ `label`, `icon`, `active`, `hasActiveChild`, `collapsed`, `variant` (`item` | `subitem`), `danger`, `trailing`, `badge`, `disclosure` (`expanded` | `collapsed`), `tooltip`, `accessibleLabel` (overrides the accessible name; collapsed it defaults to the tooltip text plus `badgeLabel`), `href`, `target`, `rel`, `onClick`, `asChild` + `children`, `className`, `size` (`medium` | `small`, the tighter row for lists inside a popover), `tag` (beside the label, e.g. early access), `badgeLabel` (read in place of the badge and added to the collapsed name), `disabled`, `disabledReason`. A passed `aria-expanded` (e.g. from a `Popover`) wins over `disclosure`, and a button row forwards a passed `aria-current`.
67
- - **`Sidebar`** — An app's left navigation shell, pinned to the viewport's left edge: 260px wide, 60px `collapsed`, on the sunken page background. Composed from `Sidebar.Header` (a 56px band for `Sidebar.Switcher`, the logo + workspace + parent trigger), `Sidebar.Body` (the part that scrolls), `Sidebar.Notices`, `Sidebar.Search`, `Sidebar.Section` (rows of `NavItem`s at the gutter), `Sidebar.Group` + `Sidebar.GroupItems` (a disclosure header and its sub-items), `Sidebar.Divider`, `Sidebar.Usage` (a `UsageCard` placed at the sidebar's gutter) and `Sidebar.Footer` holding `Sidebar.Account`. Popovers opened from it (with `padding="none"`) wrap their body in `Sidebar.Panel`, sized so the popover comes to `SIDEBAR_POPOVER_WIDTH_PX`, and build it from the `Popover` parts. `onToggleCollapse` adds the edge toggle, shown in CSS while the pointer is over the sidebar or a keyboard focus is inside it, and always on a touch screen, which has no hover to reveal it. A workspace, organization or account name cut off in the expanded sidebar shows in full on hover. Notices, the divider and the usage card hide while collapsed. Render it inside `<MalachiteContext layer="sidebar">`, or the page paints over the edge toggle. _Key props:_ `collapsed`, `onToggleCollapse`, `expandLabel`, `collapseLabel`, `aria-label`; `Sidebar.Switcher`: `logo`, `title`, `subtitle`, `titleTooltip`/`subtitleTooltip` (shown when the name is cut off, the name by default; pass a node to wrap it, e.g. to keep it out of session recordings) + button attributes; `Sidebar.Account`: `avatar` or `initials` (a small chip when there is no photo), `name`, `tooltip` (on the collapsed rail, and expanded when the name is cut off; `name` by default; pass a node to wrap it) + button attributes; `Sidebar.Usage`: the `UsageCard` props; `Sidebar.Notices`: `aria-label` (required). `useSidebarCollapsed()` reads the state from inside; `SIDEBAR_WIDTH_PX` gives the page its offset beside the sidebar; `SIDEBAR_TOOLTIP_DELAY_S` is the 0.2s delay its name and rail tooltips use, for tooltips a consumer adds inside it.
67
+ - **`Sidebar`** — An app's left navigation shell, pinned to the viewport's left edge: 260px wide, 60px `collapsed`, on the sunken page background. Composed from `Sidebar.Header` (a 56px band for `Sidebar.Switcher`, the logo + workspace + parent trigger), `Sidebar.Body` (the part that scrolls), `Sidebar.Notices`, `Sidebar.Search`, `Sidebar.Section` (rows of `NavItem`s at the gutter), `Sidebar.Group` + `Sidebar.GroupItems` (a disclosure header and its sub-items), `Sidebar.Divider`, `Sidebar.Usage` (a `UsageCard` placed at the sidebar's gutter) and `Sidebar.Footer` holding `Sidebar.Account`. Popovers opened from it (with `padding="none"`) wrap their body in `Sidebar.Panel`, sized so the popover comes to `SIDEBAR_POPOVER_WIDTH_PX`, and build it from the `Popover` parts. `onToggleCollapse` adds the edge toggle, shown in CSS while the pointer is over the sidebar or a keyboard focus is inside it, and always on a touch screen, which has no hover to reveal it. A workspace, organization or account name cut off in the expanded sidebar shows in full on hover. Notices, the divider and the usage card hide while collapsed. Render it inside `<MalachiteContext layer="sidebar">`, or the page paints over the edge toggle. _Key props:_ `collapsed`, `onToggleCollapse`, `expandLabel`, `collapseLabel`, `aria-label`; `Sidebar.Switcher`: `logo`, `title`, `subtitle`, `titleTooltip`/`subtitleTooltip` (shown when the name is cut off, the name by default; pass a node to wrap it, e.g. to keep it out of session recordings) + button attributes; `Sidebar.Account`: `avatar` or `initials` (a small chip when there is no photo), `name`, `tooltip` (on the collapsed rail, and expanded when the name is cut off; `name` by default; pass a node to wrap it) + button attributes; `Sidebar.Usage`: the `UsageCard` props; `Sidebar.Notices`: `aria-label` (required); `Sidebar.Panel`: `width` (`SIDEBAR_POPOVER_WIDTH_PX` by default). `useSidebarCollapsed()` reads the state from inside; `SIDEBAR_WIDTH_PX` gives the page its offset beside the sidebar; `SIDEBAR_TOOLTIP_DELAY_S` is the 0.2s delay its name and rail tooltips use, for tooltips a consumer adds inside it.
68
68
  - **`SearchTrigger`** — A button drawn as a filled search field, for opening a command palette or search dialog from app chrome such as a sidebar. It is not an input — typing happens in what it opens. `shortcut` draws flat `<kbd>` chips at the end (not `Keycap`, whose raised shadow reads as a second button inside a filled field), kept out of the accessible name; `iconOnly` makes a 36px icon tile matching a collapsed `NavItem`, named by `label`. _Key props:_ `label` (default `Search`), `shortcut` (`ReactNode[]`), `iconOnly`, `aria-label`, `className`, plus native button attributes (`onClick`, `aria-haspopup`, `aria-expanded`, `aria-keyshortcuts`).
69
69
  - **`FooterNav`** — Footer navigation bar of links. _Key props:_ `FooterNavLink[]`.
70
70
  - **`FooterLinks`** — Inline list of footer links (e.g. terms, privacy, support).
@@ -99,7 +99,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
99
99
 
100
100
  ## Buttons & actions
101
101
 
102
- - **`Button`** — Primary button. Variants: `filled`, `outline`, `surface` (outline's geometry on a ground of its own, for a control sitting on a tinted surface), `assistant` (the violet the assistant is named in — provenance, not a severity: it says who is being asked, not that anything is wrong), `destructive`, `success`, `warning`, `error`, `ghost`, `no-style`. Supports `iconOnly`, `loading`, `tooltip`. `leading` puts something that is not an icon, such as an avatar, before the label at its own size; `contentAlign="start"` starts the label at the leading edge and pins an end `icon` to the far edge, which with `flexGrow` makes a full-width switcher trigger — a project pill whose width comes from its container (e.g. a box `SIDEBAR_POPOVER_WIDTH_PX` wide, so it lines up with the `Sidebar.Panel` it opens). _Key props:_ `variant`, `size`, `icon`, `iconPosition`, `leading`, `contentAlign` (`center` | `start`), `flexGrow` (fills the container), `flush` (pulls a borderless button out by its padding so its label lines up with the text around it), `iconOnly`, `loading`, `disabled`, `fill`, `tooltip`.
102
+ - **`Button`** — Primary button. Variants: `filled`, `outline`, `surface` (outline's geometry on a ground of its own, for a control sitting on a tinted surface), `assistant` (the violet the assistant is named in — provenance, not a severity: it says who is being asked, not that anything is wrong), `destructive`, `success`, `warning`, `error`, `ghost`, `quiet` (text only in the accent colour, for a secondary action such as Reset beside a primary one — accent, not success, because it is something to press rather than a status), `no-style`. Supports `flush` (pulls a borderless button out by its padding so its label lines up with the text around it), `iconOnly`, `loading`, `tooltip`. `leading` puts something that is not an icon, such as an avatar, before the label at its own size; `contentAlign="start"` starts the label at the leading edge and pins an end `icon` to the far edge, which with `flexGrow` makes a full-width switcher trigger — a project pill whose width comes from its container (e.g. a box `SIDEBAR_POPOVER_WIDTH_PX` wide, so it lines up with the `Sidebar.Panel` it opens). _Key props:_ `variant`, `size`, `icon`, `iconPosition`, `leading`, `contentAlign` (`center` | `start`), `flexGrow` (fills the container), `width` (`auto` | `wide` — a 120px floor for a panel's or dialog's primary action), `iconOnly`, `loading`, `disabled`, `fill`, `tooltip`.
103
103
  - **`CloseButton`** — The bare X that dismisses a screen, panel or wizard: secondary at rest, primary on hover and keyboard focus, with no box or background at either. Use it instead of a `ghost` `Button` (which draws a hover box) or `no-style` (which has no disabled look). `surface="inverse"` is for a panel that stays dark in every theme, such as `SplitScreenLayout`’s, and reads the `closeButton` theme tokens. `loading` swaps the X for a spinner and sets `aria-busy`. Disabled and loading both use `aria-disabled` rather than `disabled`, so they ignore clicks but stay focusable and can still trigger a `Tooltip` explaining why. Named "Close" unless `aria-label` says otherwise. _Key props:_ `onClick`, `aria-label`, `size` (`small` 24px | `medium` 32px, 12px glyph), `surface` (`default` | `inverse`), `disabled`, `loading`.
104
104
  - **`AccessPills`** — Three toggle pills, R / W / D, saying which effects an account grants (Read, Write, Delete) — on a linked-account row and in the header above the rows. Each pill is a button with `aria-pressed`, named in full by its state (`Write: some actions off`), and a tooltip on hover and focus spells that name out, since a letter alone is cryptic. On paints the effect’s tint (Read in `Tag`’s primary, Write in warning, Delete in error, the colours the dashboard already gives these effects), `"mixed"` (some, not all, of its actions on) an empty pill with a dashed border, off a neutral one. The colours come from the theme and there is no colour prop. It reports only which pill was pressed through `onToggle`: any cascade between effects (Read off turning Write and Delete off, say) and what pressing a mixed pill means belong to the caller. A pill in `disabled` stays focusable with `aria-disabled`, ignores clicks and keys, and its tooltip shows `disabledReason` instead, which is also its accessible description. `size="dense"` draws 24×20 pills in place of 26×24, for a narrow screen. _Key props:_ `value` (`{ read, write, delete }`, each `true` | `false` | `"mixed"`), `onToggle(effect)`, `disabled` (per effect), `disabledReason` (per effect, or one string), `size` (`default` | `dense`), `aria-label`, `aria-labelledby`.
105
105
  - **`PillButton`** — Pill-shaped button, often used for filters/toggleable chips. `shape="tab"` squares the top corners so a row of them reads as attached to whatever sits above — the controls under a chat composer. Pass `href` and an interactive, non-disabled pill renders a real anchor (cmd-click, copy-link) instead of navigating from `onClick`; a disabled or `interactive={false}` pill stays static, and an unsafe scheme such as `javascript:` is dropped. `target="_blank"` always carries `noopener noreferrer`. _Key props:_ `label`, `icon`, `trailingIcon`, `selected`, `disabled`, `interactive`, `size`, `variant`, `shape` (`pill` | `tab`), `onClick`, `href`, `target`, `rel`.
@@ -1,85 +1,91 @@
1
- import { jsxs as i, jsx as t } from "react/jsx-runtime";
2
- import { useId as $, useState as b } from "react";
3
- import { DisclosureArrow as E } from "./DisclosureArrow.js";
4
- import { FOCUS_RING as _, FOCUS_RING_INSET as A } from "./focusRing.js";
5
- import { isRenderableNode as F } from "./renderableNode.js";
6
- const K = ({
7
- title: x,
8
- header: u,
9
- className: f,
1
+ import { jsxs as c, jsx as r } from "react/jsx-runtime";
2
+ import { useId as O, useState as f } from "react";
3
+ import { DisclosureArrow as v } from "./DisclosureArrow.js";
4
+ import { FOCUS_RING as U, FOCUS_RING_INSET as z } from "./focusRing.js";
5
+ import { isRenderableNode as h } from "./renderableNode.js";
6
+ const T = ({
7
+ title: g,
8
+ header: w,
9
+ className: N,
10
10
  children: a,
11
- trailing: c,
12
- variant: v = "default",
13
- defaultExpanded: h = !1,
11
+ trailing: i,
12
+ leading: p,
13
+ arrowPosition: m = "start",
14
+ highlightExpanded: k = !0,
15
+ variant: y = "default",
16
+ defaultExpanded: I = !1,
14
17
  isExpanded: n,
15
- onToggle: d,
16
- disabled: s = !1,
17
- stickyHeader: p = !1,
18
- stickyTopOffset: g = 0,
19
- animate: m = !0
18
+ onToggle: s,
19
+ disabled: d = !1,
20
+ stickyHeader: b = !1,
21
+ stickyTopOffset: B = 0,
22
+ animate: x = !0
20
23
  }) => {
21
- const r = v === "sunken", l = `${$()}-body`, [w, N] = b(h), e = n !== void 0 ? n : w, k = m || e && !!a, [y, I] = b(!1), C = () => {
24
+ const t = y === "sunken", l = `${O()}-body`, [C, R] = f(I), e = n !== void 0 ? n : C, S = x || e && !!a, [j, $] = f(!1), E = () => {
22
25
  const o = !e;
23
- n === void 0 && N(o), d == null || d(o);
24
- }, R = [
26
+ n === void 0 && R(o), s == null || s(o);
27
+ }, _ = [
25
28
  "flex flex-col w-full text-left",
26
- r ? "rounded-xl bg-[var(--malachite-card-dimmed-background)] p-[4px]" : "",
27
- f
28
- ].filter(Boolean).join(" "), S = [
29
+ t ? "rounded-xl bg-[var(--malachite-card-dimmed-background)] p-[4px]" : "",
30
+ N
31
+ ].filter(Boolean).join(" "), A = [
29
32
  // `group` only when enabled, so the arrow doesn't darken on hover while disabled.
30
- s ? "" : "group",
33
+ d ? "" : "group",
31
34
  "flex items-center w-full transition-colors",
32
- r ? "" : "border-0 border-b border-solid border-[var(--malachite-card-border)]",
33
- p ? "sticky z-10" : "",
34
- r ? "bg-transparent" : e ? "bg-[var(--malachite-buttonlist-hover-background)]" : s ? "bg-transparent" : "bg-transparent hover:bg-[var(--malachite-buttonlist-hover-background)]"
35
- ].filter(Boolean).join(" "), B = [
35
+ t ? "" : "border-0 border-b border-solid border-[var(--malachite-card-border)]",
36
+ b ? "sticky z-10" : "",
37
+ t ? "bg-transparent" : e && k ? "bg-[var(--malachite-buttonlist-hover-background)]" : d ? "bg-transparent" : "bg-transparent hover:bg-[var(--malachite-buttonlist-hover-background)]"
38
+ ].filter(Boolean).join(" "), u = h(p), F = [
36
39
  "flex flex-1 items-center min-w-0 appearance-none border-0 bg-transparent text-left cursor-pointer select-none disabled:cursor-not-allowed",
37
- r ? `gap-[16px] px-[16px] py-[12px] rounded-lg ${_}` : `gap-[12px] p-[16px] ${A}`
38
- ].join(" "), j = p ? {
39
- top: `${g}px`,
40
- backgroundColor: r ? "var(--malachite-card-dimmed-background)" : "var(--malachite-card-background)"
40
+ t ? `gap-[16px] px-[16px] py-[12px] rounded-lg ${U}` : `gap-[12px] p-[16px] ${z}`,
41
+ u ? "!pl-[12px]" : ""
42
+ ].filter(Boolean).join(" "), G = b ? {
43
+ top: `${B}px`,
44
+ backgroundColor: t ? "var(--malachite-card-dimmed-background)" : "var(--malachite-card-background)"
41
45
  } : {};
42
- return /* @__PURE__ */ i("div", { className: R, children: [
43
- /* @__PURE__ */ i("div", { className: S, style: j, "data-accordion-header": "", children: [
44
- /* @__PURE__ */ i(
46
+ return /* @__PURE__ */ c("div", { className: _, children: [
47
+ /* @__PURE__ */ c("div", { className: A, style: G, "data-accordion-header": "", children: [
48
+ u && /* @__PURE__ */ r("div", { className: "shrink-0 flex items-center pl-[16px]", children: p }),
49
+ /* @__PURE__ */ c(
45
50
  "button",
46
51
  {
47
52
  type: "button",
48
- className: B,
49
- onClick: C,
53
+ className: F,
54
+ onClick: E,
50
55
  "aria-expanded": e,
51
- "aria-controls": k ? l : void 0,
52
- disabled: s,
56
+ "aria-controls": S ? l : void 0,
57
+ disabled: d,
53
58
  children: [
54
- /* @__PURE__ */ t(E, { expanded: e }),
55
- u ?? /* @__PURE__ */ t("span", { className: "flex-1 min-w-0 truncate font-medium text-[13px] tracking-[0.13px] text-[var(--malachite-text-primary)]", children: x })
59
+ m === "start" && /* @__PURE__ */ r(v, { expanded: e }),
60
+ w ?? /* @__PURE__ */ r("span", { className: "flex-1 min-w-0 truncate font-medium text-[13px] tracking-[0.13px] text-[var(--malachite-text-primary)]", children: g }),
61
+ m === "end" && /* @__PURE__ */ r(v, { expanded: e, className: "ml-auto" })
56
62
  ]
57
63
  }
58
64
  ),
59
- F(c) && /* @__PURE__ */ t("div", { className: "shrink-0 flex items-center pr-[16px] text-[var(--malachite-text-secondary)]", children: c })
65
+ h(i) && /* @__PURE__ */ r("div", { className: "shrink-0 flex items-center pr-[16px] text-[var(--malachite-text-secondary)]", children: i })
60
66
  ] }),
61
- m ? (
67
+ x ? (
62
68
  /* Height animates via grid-template-rows (0fr → 1fr); body clips during.
63
69
  grid-cols-[minmax(0,1fr)] pins the single column to the track width so
64
70
  wide, non-wrapping children (e.g. a table with long cell text) can't grow
65
71
  the implicit `auto` column to their max-content and overflow the container. */
66
- /* @__PURE__ */ t(
72
+ /* @__PURE__ */ r(
67
73
  "div",
68
74
  {
69
75
  id: l,
70
76
  className: "grid grid-cols-[minmax(0,1fr)] w-full transition-[grid-template-rows] duration-200 ease-in-out",
71
77
  style: { gridTemplateRows: e ? "1fr" : "0fr" },
72
78
  onTransitionEnd: (o) => {
73
- o.propertyName === "grid-template-rows" && I(e);
79
+ o.propertyName === "grid-template-rows" && $(e);
74
80
  },
75
- children: /* @__PURE__ */ t(
81
+ children: /* @__PURE__ */ r(
76
82
  "div",
77
83
  {
78
- className: y && e ? "overflow-visible" : "overflow-hidden",
79
- children: a && /* @__PURE__ */ t(
84
+ className: j && e ? "overflow-visible" : "overflow-hidden",
85
+ children: a && /* @__PURE__ */ r(
80
86
  "div",
81
87
  {
82
- className: r ? "pt-[4px]" : "border-b border-solid border-[var(--malachite-card-border)]",
88
+ className: t ? "pt-[4px]" : "border-b border-solid border-[var(--malachite-card-border)]",
83
89
  children: a
84
90
  }
85
91
  )
@@ -89,11 +95,11 @@ const K = ({
89
95
  )
90
96
  ) : (
91
97
  /* Instant open — no clip, so a sticky child inside pins immediately. */
92
- e && a && /* @__PURE__ */ t(
98
+ e && a && /* @__PURE__ */ r(
93
99
  "div",
94
100
  {
95
101
  id: l,
96
- className: r ? "pt-[4px]" : "border-b border-solid border-[var(--malachite-card-border)]",
102
+ className: t ? "pt-[4px]" : "border-b border-solid border-[var(--malachite-card-border)]",
97
103
  children: a
98
104
  }
99
105
  )
@@ -101,7 +107,7 @@ const K = ({
101
107
  ] });
102
108
  };
103
109
  export {
104
- K as Accordion,
105
- K as default
110
+ T as Accordion,
111
+ T as default
106
112
  };
107
113
  //# sourceMappingURL=Accordion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Accordion.js","sources":["../../src/components/Accordion.tsx"],"sourcesContent":["import React, { useId, useState } from 'react';\nimport DisclosureArrow from './DisclosureArrow';\nimport { FOCUS_RING, FOCUS_RING_INSET } from './focusRing';\nimport { isRenderableNode } from './renderableNode';\n\ninterface AccordionBaseProps {\n className?: string;\n children?: React.ReactNode;\n /**\n * Visual style. `default` is the flat list row (row dividers, hover background).\n * `sunken` draws the section as a sunken card (dimmed rounded container, no\n * dividers/hover) — used for stacked detail sections in side panels and detail\n * pages (see `CollapsibleSection`). Both lead with the arrow: it is the affordance\n * that says the section opens, and a reader scanning a stack of them finds it in\n * one column rather than at whatever width each header happens to end.\n */\n variant?: 'default' | 'sunken';\n /**\n * Content pinned to the right of the header, OUTSIDE the toggle button — so it may\n * be interactive (e.g. a Checkbox) without nesting a button inside a button.\n */\n trailing?: React.ReactNode;\n defaultExpanded?: boolean;\n isExpanded?: boolean;\n onToggle?: (expanded: boolean) => void;\n /** Disables the toggle (and its hover). Trailing controls manage their own state. */\n disabled?: boolean;\n /**\n * Pin the header row to the top of the nearest scroll container so it stays\n * reachable while the body scrolls. Needs a scroll ancestor that clips without\n * trapping sticky — e.g. a `<Surface overflow=\"clip\">` (`overflow: hidden` traps it).\n */\n stickyHeader?: boolean;\n /** Offset (px) from the top of the scroll container when `stickyHeader` is set. */\n stickyTopOffset?: number;\n /**\n * Animate the expand/collapse height (default `true`). Set `false` for an instant\n * open — the body isn't clipped, so a `position: sticky` child pins immediately with\n * no post-animation settle.\n */\n animate?: boolean;\n}\n\n/**\n * Requires at least one of `title` (simple text header) or `header` (rich ReactNode\n * content for the toggle area, e.g. avatar + badges) so the toggle is never empty.\n */\ntype AccordionProps = AccordionBaseProps &\n ({ title: string; header?: React.ReactNode } | { title?: string; header: React.ReactNode });\n\nconst Accordion: React.FC<AccordionProps> = ({\n title,\n header,\n className,\n children,\n trailing,\n variant = 'default',\n defaultExpanded = false,\n isExpanded,\n onToggle,\n disabled = false,\n stickyHeader = false,\n stickyTopOffset = 0,\n animate = true,\n}) => {\n const sunken = variant === 'sunken';\n const instanceId = useId();\n const bodyId = `${instanceId}-body`;\n const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);\n\n // Use controlled state if isExpanded is provided, otherwise use internal state\n const expanded = isExpanded !== undefined ? isExpanded : internalExpanded;\n\n // The instant path only renders the body while expanded; don't advertise\n // aria-controls when the referenced element isn't in the DOM.\n const bodyRendered = animate || (expanded && !!children);\n\n // The body clips overflow so the expand/collapse height animation works. Once fully\n // expanded we release the clip (after the transition) so a `position: sticky` child\n // inside can pin — `overflow: hidden` would otherwise trap it.\n //\n // Owned entirely by the transition: the same `onTransitionEnd` that sets it on expand\n // clears it on collapse, so no effect has to mirror `expanded` into it.\n const [bodySettled, setBodySettled] = useState(false);\n\n const handleToggle = () => {\n const newExpanded = !expanded;\n if (isExpanded === undefined) {\n setInternalExpanded(newExpanded);\n }\n onToggle?.(newExpanded);\n };\n\n const containerClasses = [\n 'flex flex-col w-full text-left',\n sunken ? 'rounded-xl bg-[var(--malachite-card-dimmed-background)] p-[4px]' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n // The whole header row is a `group` so the disclosure arrow darkens on hover; the\n // hover background and arrow react to hovering anywhere on the row. The sunken\n // variant carries its own card chrome instead of row dividers/hover.\n const rowBackground = sunken\n ? 'bg-transparent'\n : expanded\n ? 'bg-[var(--malachite-buttonlist-hover-background)]'\n : disabled\n ? 'bg-transparent'\n : 'bg-transparent hover:bg-[var(--malachite-buttonlist-hover-background)]';\n const headerRowClasses = [\n // `group` only when enabled, so the arrow doesn't darken on hover while disabled.\n disabled ? '' : 'group',\n 'flex items-center w-full transition-colors',\n sunken ? '' : 'border-0 border-b border-solid border-[var(--malachite-card-border)]',\n stickyHeader ? 'sticky z-10' : '',\n rowBackground,\n ]\n .filter(Boolean)\n .join(' ');\n // Full-bleed default rows use an inset ring; the padded sunken toggle fits an outset one.\n const toggleButtonClasses = [\n 'flex flex-1 items-center min-w-0 appearance-none border-0 bg-transparent text-left cursor-pointer select-none disabled:cursor-not-allowed',\n sunken\n ? `gap-[16px] px-[16px] py-[12px] rounded-lg ${FOCUS_RING}`\n : `gap-[12px] p-[16px] ${FOCUS_RING_INSET}`,\n ].join(' ');\n\n // A pinned header needs an opaque background so the scrolling body doesn't bleed\n // through. Inline (not a Tailwind arbitrary class) so the offset always applies. It is\n // the variant's own ground, not the card's: a sunken section pinned in card white is a\n // white bar floating over the grey it belongs to.\n const headerRowStyle: React.CSSProperties = stickyHeader\n ? {\n top: `${stickyTopOffset}px`,\n backgroundColor: sunken\n ? 'var(--malachite-card-dimmed-background)'\n : 'var(--malachite-card-background)',\n }\n : {};\n\n return (\n <div className={containerClasses}>\n <div className={headerRowClasses} style={headerRowStyle} data-accordion-header=\"\">\n {/* Toggle area — arrow + header/title. Interactive trailing sits outside it. */}\n <button\n type=\"button\"\n className={toggleButtonClasses}\n onClick={handleToggle}\n aria-expanded={expanded}\n aria-controls={bodyRendered ? bodyId : undefined}\n disabled={disabled}\n >\n <DisclosureArrow expanded={expanded} />\n {header ?? (\n <span className=\"flex-1 min-w-0 truncate font-medium text-[13px] tracking-[0.13px] text-[var(--malachite-text-primary)]\">\n {title}\n </span>\n )}\n </button>\n {isRenderableNode(trailing) && (\n <div className=\"shrink-0 flex items-center pr-[16px] text-[var(--malachite-text-secondary)]\">\n {trailing}\n </div>\n )}\n </div>\n\n {animate ? (\n /* Height animates via grid-template-rows (0fr → 1fr); body clips during.\n grid-cols-[minmax(0,1fr)] pins the single column to the track width so\n wide, non-wrapping children (e.g. a table with long cell text) can't grow\n the implicit `auto` column to their max-content and overflow the container. */\n <div\n id={bodyId}\n className=\"grid grid-cols-[minmax(0,1fr)] w-full transition-[grid-template-rows] duration-200 ease-in-out\"\n style={{ gridTemplateRows: expanded ? '1fr' : '0fr' }}\n onTransitionEnd={(event) => {\n if (event.propertyName === 'grid-template-rows') {\n setBodySettled(expanded);\n }\n }}\n >\n <div\n className={bodySettled && expanded ? 'overflow-visible' : 'overflow-hidden'}\n >\n {children && (\n <div\n className={\n sunken\n ? 'pt-[4px]'\n : 'border-b border-solid border-[var(--malachite-card-border)]'\n }\n >\n {children}\n </div>\n )}\n </div>\n </div>\n ) : (\n /* Instant open — no clip, so a sticky child inside pins immediately. */\n expanded &&\n children && (\n <div\n id={bodyId}\n className={\n sunken\n ? 'pt-[4px]'\n : 'border-b border-solid border-[var(--malachite-card-border)]'\n }\n >\n {children}\n </div>\n )\n )}\n </div>\n );\n};\n\nexport default Accordion;\nexport { Accordion };\nexport type { AccordionProps };\n"],"names":["Accordion","title","header","className","children","trailing","variant","defaultExpanded","isExpanded","onToggle","disabled","stickyHeader","stickyTopOffset","animate","sunken","bodyId","useId","internalExpanded","setInternalExpanded","useState","expanded","bodyRendered","bodySettled","setBodySettled","handleToggle","newExpanded","containerClasses","headerRowClasses","toggleButtonClasses","FOCUS_RING","FOCUS_RING_INSET","headerRowStyle","jsxs","jsx","DisclosureArrow","isRenderableNode","event"],"mappings":"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"}
1
+ {"version":3,"file":"Accordion.js","sources":["../../src/components/Accordion.tsx"],"sourcesContent":["import React, { useId, useState } from 'react';\nimport DisclosureArrow from './DisclosureArrow';\nimport { FOCUS_RING, FOCUS_RING_INSET } from './focusRing';\nimport { isRenderableNode } from './renderableNode';\n\ninterface AccordionBaseProps {\n className?: string;\n children?: React.ReactNode;\n /**\n * Visual style. `default` is the flat list row (row dividers, hover background).\n * `sunken` draws the section as a sunken card (dimmed rounded container, no\n * dividers/hover) — used for stacked detail sections in side panels and detail\n * pages (see `CollapsibleSection`). Both lead with the arrow: it is the affordance\n * that says the section opens, and a reader scanning a stack of them finds it in\n * one column rather than at whatever width each header happens to end.\n */\n variant?: 'default' | 'sunken';\n /**\n * Content pinned to the right of the header, OUTSIDE the toggle button — so it may\n * be interactive (e.g. a Checkbox) without nesting a button inside a button.\n */\n trailing?: React.ReactNode;\n /** Before the title, outside the toggle button. */\n leading?: React.ReactNode;\n arrowPosition?: 'start' | 'end';\n /** `false` keeps an open row on the card ground. */\n highlightExpanded?: boolean;\n defaultExpanded?: boolean;\n isExpanded?: boolean;\n onToggle?: (expanded: boolean) => void;\n /** Disables the toggle (and its hover). Trailing controls manage their own state. */\n disabled?: boolean;\n /**\n * Pin the header row to the top of the nearest scroll container so it stays\n * reachable while the body scrolls. Needs a scroll ancestor that clips without\n * trapping sticky — e.g. a `<Surface overflow=\"clip\">` (`overflow: hidden` traps it).\n */\n stickyHeader?: boolean;\n /** Offset (px) from the top of the scroll container when `stickyHeader` is set. */\n stickyTopOffset?: number;\n /**\n * Animate the expand/collapse height (default `true`). Set `false` for an instant\n * open — the body isn't clipped, so a `position: sticky` child pins immediately with\n * no post-animation settle.\n */\n animate?: boolean;\n}\n\n/**\n * Requires at least one of `title` (simple text header) or `header` (rich ReactNode\n * content for the toggle area, e.g. avatar + badges) so the toggle is never empty.\n */\ntype AccordionProps = AccordionBaseProps &\n ({ title: string; header?: React.ReactNode } | { title?: string; header: React.ReactNode });\n\nconst Accordion: React.FC<AccordionProps> = ({\n title,\n header,\n className,\n children,\n trailing,\n leading,\n arrowPosition = 'start',\n highlightExpanded = true,\n variant = 'default',\n defaultExpanded = false,\n isExpanded,\n onToggle,\n disabled = false,\n stickyHeader = false,\n stickyTopOffset = 0,\n animate = true,\n}) => {\n const sunken = variant === 'sunken';\n const instanceId = useId();\n const bodyId = `${instanceId}-body`;\n const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);\n\n // Use controlled state if isExpanded is provided, otherwise use internal state\n const expanded = isExpanded !== undefined ? isExpanded : internalExpanded;\n\n // The instant path only renders the body while expanded; don't advertise\n // aria-controls when the referenced element isn't in the DOM.\n const bodyRendered = animate || (expanded && !!children);\n\n // The body clips overflow so the expand/collapse height animation works. Once fully\n // expanded we release the clip (after the transition) so a `position: sticky` child\n // inside can pin — `overflow: hidden` would otherwise trap it.\n //\n // Owned entirely by the transition: the same `onTransitionEnd` that sets it on expand\n // clears it on collapse, so no effect has to mirror `expanded` into it.\n const [bodySettled, setBodySettled] = useState(false);\n\n const handleToggle = () => {\n const newExpanded = !expanded;\n if (isExpanded === undefined) {\n setInternalExpanded(newExpanded);\n }\n onToggle?.(newExpanded);\n };\n\n const containerClasses = [\n 'flex flex-col w-full text-left',\n sunken ? 'rounded-xl bg-[var(--malachite-card-dimmed-background)] p-[4px]' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n // The whole header row is a `group` so the disclosure arrow darkens on hover; the\n // hover background and arrow react to hovering anywhere on the row. The sunken\n // variant carries its own card chrome instead of row dividers/hover.\n const rowBackground = sunken\n ? 'bg-transparent'\n : expanded && highlightExpanded\n ? 'bg-[var(--malachite-buttonlist-hover-background)]'\n : disabled\n ? 'bg-transparent'\n : 'bg-transparent hover:bg-[var(--malachite-buttonlist-hover-background)]';\n const headerRowClasses = [\n // `group` only when enabled, so the arrow doesn't darken on hover while disabled.\n disabled ? '' : 'group',\n 'flex items-center w-full transition-colors',\n sunken ? '' : 'border-0 border-b border-solid border-[var(--malachite-card-border)]',\n stickyHeader ? 'sticky z-10' : '',\n rowBackground,\n ]\n .filter(Boolean)\n .join(' ');\n const hasLeading = isRenderableNode(leading);\n // Full-bleed default rows use an inset ring; the padded sunken toggle fits an outset one.\n const toggleButtonClasses = [\n 'flex flex-1 items-center min-w-0 appearance-none border-0 bg-transparent text-left cursor-pointer select-none disabled:cursor-not-allowed',\n sunken\n ? `gap-[16px] px-[16px] py-[12px] rounded-lg ${FOCUS_RING}`\n : `gap-[12px] p-[16px] ${FOCUS_RING_INSET}`,\n hasLeading ? '!pl-[12px]' : '',\n ]\n .filter(Boolean)\n .join(' ');\n\n // A pinned header needs an opaque background so the scrolling body doesn't bleed\n // through. Inline (not a Tailwind arbitrary class) so the offset always applies. It is\n // the variant's own ground, not the card's: a sunken section pinned in card white is a\n // white bar floating over the grey it belongs to.\n const headerRowStyle: React.CSSProperties = stickyHeader\n ? {\n top: `${stickyTopOffset}px`,\n backgroundColor: sunken\n ? 'var(--malachite-card-dimmed-background)'\n : 'var(--malachite-card-background)',\n }\n : {};\n\n return (\n <div className={containerClasses}>\n <div className={headerRowClasses} style={headerRowStyle} data-accordion-header=\"\">\n {hasLeading && (\n <div className=\"shrink-0 flex items-center pl-[16px]\">{leading}</div>\n )}\n {/* Toggle area — arrow + header/title. Interactive trailing sits outside it. */}\n <button\n type=\"button\"\n className={toggleButtonClasses}\n onClick={handleToggle}\n aria-expanded={expanded}\n aria-controls={bodyRendered ? bodyId : undefined}\n disabled={disabled}\n >\n {arrowPosition === 'start' && <DisclosureArrow expanded={expanded} />}\n {header ?? (\n <span className=\"flex-1 min-w-0 truncate font-medium text-[13px] tracking-[0.13px] text-[var(--malachite-text-primary)]\">\n {title}\n </span>\n )}\n {arrowPosition === 'end' && (\n <DisclosureArrow expanded={expanded} className=\"ml-auto\" />\n )}\n </button>\n {isRenderableNode(trailing) && (\n <div className=\"shrink-0 flex items-center pr-[16px] text-[var(--malachite-text-secondary)]\">\n {trailing}\n </div>\n )}\n </div>\n\n {animate ? (\n /* Height animates via grid-template-rows (0fr → 1fr); body clips during.\n grid-cols-[minmax(0,1fr)] pins the single column to the track width so\n wide, non-wrapping children (e.g. a table with long cell text) can't grow\n the implicit `auto` column to their max-content and overflow the container. */\n <div\n id={bodyId}\n className=\"grid grid-cols-[minmax(0,1fr)] w-full transition-[grid-template-rows] duration-200 ease-in-out\"\n style={{ gridTemplateRows: expanded ? '1fr' : '0fr' }}\n onTransitionEnd={(event) => {\n if (event.propertyName === 'grid-template-rows') {\n setBodySettled(expanded);\n }\n }}\n >\n <div\n className={bodySettled && expanded ? 'overflow-visible' : 'overflow-hidden'}\n >\n {children && (\n <div\n className={\n sunken\n ? 'pt-[4px]'\n : 'border-b border-solid border-[var(--malachite-card-border)]'\n }\n >\n {children}\n </div>\n )}\n </div>\n </div>\n ) : (\n /* Instant open — no clip, so a sticky child inside pins immediately. */\n expanded &&\n children && (\n <div\n id={bodyId}\n className={\n sunken\n ? 'pt-[4px]'\n : 'border-b border-solid border-[var(--malachite-card-border)]'\n }\n >\n {children}\n </div>\n )\n )}\n </div>\n );\n};\n\nexport default Accordion;\nexport { Accordion };\nexport type { AccordionProps };\n"],"names":["Accordion","title","header","className","children","trailing","leading","arrowPosition","highlightExpanded","variant","defaultExpanded","isExpanded","onToggle","disabled","stickyHeader","stickyTopOffset","animate","sunken","bodyId","useId","internalExpanded","setInternalExpanded","useState","expanded","bodyRendered","bodySettled","setBodySettled","handleToggle","newExpanded","containerClasses","headerRowClasses","hasLeading","isRenderableNode","toggleButtonClasses","FOCUS_RING","FOCUS_RING_INSET","headerRowStyle","jsxs","jsx","DisclosureArrow","event"],"mappings":"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"}
@@ -1,16 +1,16 @@
1
- import { jsxs as i, jsx as e, Fragment as O } from "react/jsx-runtime";
2
- import { useState as A } from "react";
3
- import { Button as R } from "./Button.js";
4
- import W from "./CustomIcons/CaretDownIcon.js";
5
- import H from "./CustomIcons/CaretUpIcon.js";
6
- import M from "./CustomIcons/CheckCircleFilled.js";
7
- import T from "./CustomIcons/ErrorCircleFilled.js";
8
- import Y from "./CustomIcons/InfoCircleFilled.js";
9
- import $ from "./CustomIcons/SparkleIcon.js";
10
- import D from "./CustomIcons/WarningCircleFilled.js";
11
- import L from "./Typography/MarkdownBlock.js";
12
- import { getTextColor as y, getFontSize as U } from "./Typography/textStyles.js";
13
- const h = (r) => {
1
+ import { jsxs as s, jsx as e, Fragment as y } from "react/jsx-runtime";
2
+ import { useState as W } from "react";
3
+ import { Button as H } from "./Button.js";
4
+ import M from "./CustomIcons/CaretDownIcon.js";
5
+ import T from "./CustomIcons/CaretUpIcon.js";
6
+ import Y from "./CustomIcons/CheckCircleFilled.js";
7
+ import $ from "./CustomIcons/ErrorCircleFilled.js";
8
+ import D from "./CustomIcons/InfoCircleFilled.js";
9
+ import L from "./CustomIcons/SparkleIcon.js";
10
+ import U from "./CustomIcons/WarningCircleFilled.js";
11
+ import q from "./Typography/MarkdownBlock.js";
12
+ import { getTextColor as j, getFontSize as G } from "./Typography/textStyles.js";
13
+ const p = (r) => {
14
14
  switch (r) {
15
15
  case "info":
16
16
  return "primary";
@@ -37,162 +37,137 @@ const h = (r) => {
37
37
  error: "var(--malachite-alert-error-foreground)",
38
38
  warning: "var(--malachite-alert-warning-foreground)",
39
39
  assistant: "var(--malachite-alert-assistant-foreground)"
40
- }, I = {
41
- info: /* @__PURE__ */ e(
42
- Y,
43
- {
44
- style: { ...a, color: o.info }
45
- }
46
- ),
47
- success: /* @__PURE__ */ e(
48
- M,
49
- {
50
- style: { ...a, color: o.success }
51
- }
52
- ),
53
- error: /* @__PURE__ */ e(
54
- T,
55
- {
56
- style: { ...a, color: o.error }
57
- }
58
- ),
59
- warning: /* @__PURE__ */ e(
60
- D,
61
- {
62
- style: { ...a, color: o.warning }
63
- }
64
- ),
40
+ }, _ = {
41
+ info: /* @__PURE__ */ e(D, { style: { ...a, color: o.info } }),
42
+ success: /* @__PURE__ */ e(Y, { style: { ...a, color: o.success } }),
43
+ error: /* @__PURE__ */ e($, { style: { ...a, color: o.error } }),
44
+ warning: /* @__PURE__ */ e(U, { style: { ...a, color: o.warning } }),
65
45
  // The same sparkle the control that produced the message carries, so the two read as
66
46
  // one thread rather than two unrelated affordances.
67
- assistant: /* @__PURE__ */ e(
68
- $,
69
- {
70
- style: { ...a, color: o.assistant }
71
- }
72
- )
73
- }, q = { ...a, width: "16px", height: "16px" }, _ = (r, l, t = !1) => !l && !t ? I[r] : /* @__PURE__ */ e(
47
+ assistant: /* @__PURE__ */ e(L, { style: { ...a, color: o.assistant } })
48
+ }, J = { ...a, width: "16px", height: "16px" }, S = (r, t, l = !1) => !t && !l ? _[r] : /* @__PURE__ */ e(
74
49
  "span",
75
50
  {
76
51
  className: "alert-status-icon inline-flex shrink-0 [&_svg]:size-full!",
77
- style: { ...t ? q : a, color: o[r] },
78
- children: l || I[r]
52
+ style: { ...l ? J : a, color: o[r] },
53
+ children: t || _[r]
79
54
  }
80
- ), u = ({ text: r, type: l, lead: t, renderLink: c }) => t ? /* @__PURE__ */ i("div", { className: "alert-lead", children: [
55
+ ), v = ({ text: r, type: t, lead: l, renderLink: c }) => l ? /* @__PURE__ */ s("div", { className: "alert-lead", children: [
81
56
  /* @__PURE__ */ e(
82
57
  "strong",
83
58
  {
84
59
  style: {
85
- color: y(h(l)),
86
- fontSize: U("small"),
60
+ color: j(p(t)),
61
+ fontSize: G("small"),
87
62
  fontWeight: 600
88
63
  },
89
- children: t
64
+ children: l
90
65
  }
91
66
  ),
92
67
  " ",
93
- /* @__PURE__ */ e(u, { text: r, type: l, renderLink: c })
68
+ /* @__PURE__ */ e(v, { text: r, type: t, renderLink: c })
94
69
  ] }) : /* @__PURE__ */ e(
95
- L,
70
+ q,
96
71
  {
97
72
  text: r,
98
- color: h(l),
73
+ color: p(t),
99
74
  size: "small",
100
75
  omitMargin: !0,
101
76
  renderLink: c
102
77
  }
103
- ), ae = ({
78
+ ), ie = ({
104
79
  type: r = "info",
105
- message: l,
106
- hasMargin: t = !0,
80
+ message: t,
81
+ hasMargin: l = !0,
107
82
  className: c,
108
- children: p,
109
- isExpanded: m,
110
- onToggle: f,
111
- action: d,
112
- icon: v,
113
- live: S = !1,
114
- renderLink: g,
115
- layout: E = "inline",
116
- lead: w
83
+ children: g,
84
+ isExpanded: x,
85
+ onToggle: h,
86
+ action: m,
87
+ icon: w,
88
+ live: E = !1,
89
+ renderLink: N,
90
+ layout: k = "inline",
91
+ lead: C,
92
+ align: B = "start"
117
93
  }) => {
118
- const s = !!p, x = E === "banner" && !s, N = S ? r === "error" || r === "warning" ? "alert" : "status" : void 0, [j, k] = A(!1), n = m !== void 0 ? m : j, B = () => {
119
- const b = !n;
120
- m === void 0 && k(b), f == null || f(b);
121
- }, C = [
94
+ const n = !!g, f = k === "banner" && !n, u = f && B === "center", b = E ? r === "error" || r === "warning" ? "alert" : "status" : void 0, [F, z] = W(!1), i = x !== void 0 ? x : F, O = () => {
95
+ const d = !i;
96
+ x === void 0 && z(d), h == null || h(d);
97
+ }, I = [
122
98
  "alert-base",
123
99
  `alert-${r}`,
124
100
  "flex",
125
101
  "w-full",
126
102
  // Ensure full width
127
- x ? "alert-banner" : s && n ? "flex-col" : "gap-2 items-center min-h-[2rem]",
128
- t ? "" : "alert-no-margin",
129
- "text-left",
130
- x ? "" : s ? "rounded-xl !p-0" : "!px-2.5 !py-1.5",
103
+ f ? "alert-banner" : n && i ? "flex-col" : "gap-2 items-center min-h-[2rem]",
104
+ l ? "" : "alert-no-margin",
105
+ u ? "text-center" : "text-left",
106
+ f ? "" : n ? "rounded-xl !p-0" : "!px-2.5 !py-1.5",
131
107
  // Slightly more vertical padding for centering
132
108
  c
133
- ].filter(Boolean).join(" "), F = [
109
+ ].filter(Boolean).join(" "), A = [
134
110
  "flex items-center justify-between w-full",
135
- s ? "gap-2 px-2.5 py-1" : "gap-2",
136
- s && n ? "rounded-t-xl" : s ? "rounded-xl" : ""
137
- ].filter(Boolean).join(" "), z = ["w-full", "overflow-hidden"].filter(Boolean).join(" ");
138
- return s ? /* @__PURE__ */ i("div", { className: `${C} alert-expandable`, children: [
139
- /* @__PURE__ */ i("div", { className: F, children: [
140
- /* @__PURE__ */ i("div", { className: "flex-1 flex gap-2 items-center", children: [
141
- _(r, v),
142
- /* @__PURE__ */ e("div", { className: "flex-1 flex items-center", children: /* @__PURE__ */ e("div", { className: "[&>div]:mb-0", role: N, children: /* @__PURE__ */ e(
143
- u,
111
+ n ? "gap-2 px-2.5 py-1" : "gap-2",
112
+ n && i ? "rounded-t-xl" : n ? "rounded-xl" : ""
113
+ ].filter(Boolean).join(" "), R = ["w-full", "overflow-hidden"].filter(Boolean).join(" ");
114
+ if (!n) {
115
+ const d = /* @__PURE__ */ s(y, { children: [
116
+ S(r, w, f),
117
+ /* @__PURE__ */ e("div", { className: u ? "flex items-center" : "flex-1 flex items-center", children: /* @__PURE__ */ e("div", { className: "[&>div]:mb-0", role: b, children: /* @__PURE__ */ e(
118
+ v,
119
+ {
120
+ text: t,
121
+ type: r,
122
+ lead: C,
123
+ renderLink: N
124
+ }
125
+ ) }) })
126
+ ] });
127
+ return /* @__PURE__ */ s("div", { className: I, children: [
128
+ u ? (
129
+ // Its own group, so an `action` stays at the end while this sits mid-band.
130
+ /* @__PURE__ */ e("div", { className: "alert-centred flex-1 flex items-center justify-center gap-[10px]", children: d })
131
+ ) : d,
132
+ m && /* @__PURE__ */ e("div", { className: "alert-action shrink-0 flex items-center", children: m })
133
+ ] });
134
+ }
135
+ return /* @__PURE__ */ s("div", { className: `${I} alert-expandable`, children: [
136
+ /* @__PURE__ */ s("div", { className: A, children: [
137
+ /* @__PURE__ */ s("div", { className: "flex-1 flex gap-2 items-center", children: [
138
+ S(r, w),
139
+ /* @__PURE__ */ e("div", { className: "flex-1 flex items-center", children: /* @__PURE__ */ e("div", { className: "[&>div]:mb-0", role: b, children: /* @__PURE__ */ e(
140
+ v,
144
141
  {
145
- text: l,
146
- lead: w,
142
+ text: t,
143
+ lead: C,
147
144
  type: r,
148
- renderLink: g
145
+ renderLink: N
149
146
  }
150
147
  ) }) })
151
148
  ] }),
152
- /* @__PURE__ */ i("div", { className: "flex items-center gap-2", children: [
153
- d && /* @__PURE__ */ e("div", { className: "alert-action flex items-center", children: d }),
149
+ /* @__PURE__ */ s("div", { className: "flex items-center gap-2", children: [
150
+ m && /* @__PURE__ */ e("div", { className: "alert-action flex items-center", children: m }),
154
151
  /* @__PURE__ */ e(
155
- R,
152
+ H,
156
153
  {
157
154
  variant: "no-style",
158
155
  size: "xsmall",
159
- onClick: B,
160
- "aria-label": n ? "Collapse" : "Expand",
156
+ onClick: O,
157
+ "aria-label": i ? "Collapse" : "Expand",
161
158
  style: {
162
- color: y(h(r))
159
+ color: j(p(r))
163
160
  },
164
- children: n ? /* @__PURE__ */ e(
165
- H,
166
- {
167
- style: { minWidth: "13px", minHeight: "13px" }
168
- }
169
- ) : /* @__PURE__ */ e(
170
- W,
171
- {
172
- style: { minWidth: "13px", minHeight: "13px" }
173
- }
174
- )
161
+ children: i ? /* @__PURE__ */ e(T, { style: { minWidth: "13px", minHeight: "13px" } }) : /* @__PURE__ */ e(M, { style: { minWidth: "13px", minHeight: "13px" } })
175
162
  }
176
163
  )
177
164
  ] })
178
165
  ] }),
179
- n && /* @__PURE__ */ e(O, { children: /* @__PURE__ */ e("div", { className: z, children: /* @__PURE__ */ e("div", { className: "bg-white rounded-[var(--malachite-radius-10,10px)] shadow-[0px_1px_0px_0px_rgba(0,0,0,0.15)] border border-[#e8e8e8] border-solid mx-2 mb-2 max-h-[553px] overflow-y-auto", children: p }) }) })
180
- ] }) : /* @__PURE__ */ i("div", { className: C, children: [
181
- _(r, v, x),
182
- /* @__PURE__ */ e("div", { className: "flex-1 flex items-center", children: /* @__PURE__ */ e("div", { className: "[&>div]:mb-0", role: N, children: /* @__PURE__ */ e(
183
- u,
184
- {
185
- text: l,
186
- type: r,
187
- lead: w,
188
- renderLink: g
189
- }
190
- ) }) }),
191
- d && /* @__PURE__ */ e("div", { className: "alert-action shrink-0 flex items-center", children: d })
166
+ i && /* @__PURE__ */ e(y, { children: /* @__PURE__ */ e("div", { className: R, children: /* @__PURE__ */ e("div", { className: "bg-white rounded-[var(--malachite-radius-10,10px)] shadow-[0px_1px_0px_0px_rgba(0,0,0,0.15)] border border-[#e8e8e8] border-solid mx-2 mb-2 max-h-[553px] overflow-y-auto", children: g }) }) })
192
167
  ] });
193
168
  };
194
169
  export {
195
- ae as Alert,
196
- ae as default
170
+ ie as Alert,
171
+ ie as default
197
172
  };
198
173
  //# sourceMappingURL=Alert.js.map