@stackone/malachite 2.70.0 → 2.71.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
@@ -8,7 +8,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
8
8
 
9
9
  - **`Chat`** — A chat column: `Chat.Thread` scrolls and pins itself to the newest message, `Chat.Composer` keeps its own height and owns the Enter/Shift+Enter convention, and `Chat.Message`, `Chat.ToolCall` and `Chat.Thinking` render one turn each. A user turn sits right and hugs its text; an assistant turn runs full width, and `variant="card"` frames it with its state, copy and rating controls, and a token/model line. `variant="bubble"` is a third treatment, not a repaint of either: tinted bubbles whose flattened corner points at whoever spoke, so authorship reads before the words do. `Chat.Context` lists what the conversation can see — a removable chip per source, and an add affordance only when a handler is given. `Chat.Thinking` and a loading turn draw bars in the shape of the answer rather than a spinner. The row under a composer is `ComposerChip`, `ContextMeter` and `ModelPicker` — PillButton tabs, so it reads as attached to the field; `ModelPicker` opens a portalled menu with a submenu per model. The transcript is a live `log` region, so a streamed reply is announced, and it shows a centred empty state until the first turn. _Key props:_ `Chat`: `children`, `className`. `Thread`: `aria-label`, `emptyTitle`, `emptyDescription`, `emptyIcon`. `Message`: `role`, `variant` (`plain` | `card`), `content`, `children`, `name`, `avatarSrc`, `status` (`streaming` | `completed` | `error`), `loading`, `onCopy`, `onFeedback`, `usage`. `ToolCall`: `name`, `status` (`running` | `completed` | `error`), `callId`, `input`, `output`, `defaultExpanded`. `Thinking`: `label`. `Composer`: `value`, `onChange`, `onSend`, `onStop`, `busy`, `disabled`, `placeholder`, `maxLength`, `rows`, `children`. `ComposerChip`: `label`, `icon`, `trailingIcon`, `onClick`, `disabled`. `ContextMeter`: `used`, `limit`, `cached`, `label`. `ModelPicker`: `models`, `value`, `optionValue`, `onChange`, `openUp`, `disabled`. `Context`: `label`, `items` (`{id, label, onRemove?}`), `onAdd`, `className`.
10
10
  - **`Surface`** — Rounded, bordered card container with the standard elevation shadow. Use `overflow="clip"` to keep the rounded clipping while letting a `position: sticky` descendant still pin (`hidden` would trap it); `visible` disables clipping. _Key props:_ `children`, `padding`, `overflow`, `className`, `style`.
11
- - **`SurfaceRow`** — A full-bleed row inside a Surface (used as `Surface.Row`). Owns hover highlight, colour transition and an optional bottom divider. Pass `onClick`/`href` to make the whole row clickable (renders as a real button/anchor, so keyboard nav works). For a selectable row use `role="radio"` + `selected` and put a presentational `<Radio>` inside as the indicator; `selected` then paints the same treatment `Table` gives `selectedRowKey`. Under `role="checkbox"` it only announces — a multi-select row carries its own checkbox, and a highlight marking one of many says nothing when most rows are checked. `selection` overrides that either way (`highlight` / `announce`). `selected="mixed"` marks a partly selected checkbox row (`aria-checked="mixed"`); it never paints or marks a row current, and counts as not selected under any other role. Use `trailing` for a control that must stay interactive — it renders outside the row button, which a nested control could not be. `revealTrailing` shows that control only on hover, keyboard focus or while a menu it opened is open, keeping it in the tab order and always showing it on a touch screen. `tone="muted"` tints a row whose subject is inactive, which `disabled` does not: a muted row stays clickable. _Key props:_ `divider`, `disabled`, `tone` (`default` | `muted`), `onClick`, `href`, `target`, `rel`, `selected` (`boolean` | `"mixed"`), `selection` (`highlight` | `announce`), `trailing`, `revealTrailing`, `role`, `className`, `style`.
11
+ - **`SurfaceRow`** — A full-bleed row inside a Surface (used as `Surface.Row`). Owns hover highlight, colour transition and an optional bottom divider. Pass `onClick`/`href` to make the whole row clickable (renders as a real button/anchor, so keyboard nav works). For a selectable row use `role="radio"` + `selected` and put a presentational `<Radio>` inside as the indicator; `selected` then paints the same treatment `Table` gives `selectedRowKey`. Under `role="checkbox"` it only announces — a multi-select row carries its own checkbox, and a highlight marking one of many says nothing when most rows are checked. `selection` overrides that either way (`highlight` / `announce`). `selected="mixed"` marks a partly selected checkbox row (`aria-checked="mixed"`); it never paints or marks a row current, and counts as not selected under any other role. Use `trailing` for a control that must stay interactive — it renders outside the row button, which a nested control could not be. `revealTrailing` shows that control only on hover, keyboard focus or while a menu it opened is open, keeping it in the tab order and always showing it on a touch screen. `tone="muted"` tints a row whose subject is inactive, which `disabled` does not: a muted row stays clickable. _Key props:_ `divider`, `disabled`, `tone` (`default` | `muted`), `inset` (`none` | `compact` — the row owns an 8px by 16px inset, wrapping `trailing` too, so its content needs no padding wrapper), `onClick`, `href`, `target`, `rel`, `selected` (`boolean` | `"mixed"`), `selection` (`highlight` | `announce`), `trailing`, `revealTrailing`, `role`, `className`, `style`.
12
12
  - **`SurfaceExpandableRow`** — A row that opens to reveal a body (used as `Surface.ExpandableRow`), for a line that carries its own controls. Not `Accordion`, which puts its header inside the toggle button, so a header holding a dropdown or a second button would be invalid: here the row surface takes the click and a cell opts out by wrapping its controls in `ExpandableRow.Controls`, while the arrow stays a real button so the keyboard reaches it. That opt-out is explicit rather than sniffing the event target for an interactive ancestor, because a control rendering into a shadow root retargets the event to its host. `sticky` pins the row while its body scrolls, offset by `stickyTopOffset` for whatever is already pinned above it; it only pins while open, since a closed row has nothing to scroll past. _Key props:_ `header`, `children`, `expanded`, `onToggle`, `sticky`, `stickyTopOffset`, `divider`, `tone`, `aria-label`, `className`, `style`.
13
13
  - **`DisclosureArrow`** — The expand/collapse triangle disclosure UI shares (`Accordion`, `Surface.ExpandableRow`). Owns its colour states so they never drift between usages: `text-primary` when expanded, `text-placeholder` otherwise, and dark on hover inside a `group` ancestor — put `group` on the clickable header or row so that hover state activates. _Key props:_ `expanded`, `size`, `className`.
14
14
  - **`Card`** — Generic content card.
@@ -16,17 +16,17 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
16
16
  - **`CardStrip`** — Horizontally scrolling radio group of cards, with edge arrows that appear only when the content overflows, an edge fade, scroll-snap and arrow-key navigation matching `SegmentedControl`. Owns interaction only: it finds its options by looking for `role="radio"` descendants carrying `data-value`, so any such child works (`SelectableCard` emits both). Pass a non-selectable terminal tile as `CardStrip.AddTile` — it sits inside the group but outside the radio set, and must be a direct child or it scrolls with the cards. Use `leading` to pin a card that should not scroll away; the strip tells the cards which slot they are in, so a pinned `SelectableCard` drops the left rule the scrolling ones draw. A strip too narrow to show a card beside `leading` scrolls it with the cards rather than squeezing them, so the selected card always has its full width. Set `bleed` when the strip sits in a `PageLayout`: the strip stretches past the horizontal padding of the layout so its bottom rule runs the full width of the page card, while the cards stay aligned to the gutter. With no cards at all the strip degenerates to its pinned slots alone — no trough, no scroller and no arrows — with the add tile standing beside `leading` and taking `primary` emphasis automatically, since adding the first card is the only thing on offer. _Key props:_ `value`, `tone` (`profile` | `library` | `editing` — read off the selected card unless set), `onChange`, `children`, `leading`, `arrows`, `fadeEdges`, `snap`, `bleed`, `scrollSelectedIntoView`, `aria-label`, `aria-labelledby`, `className`, `style`. `CardStrip.AddTile`: `label`, `icon`, `onClick`, `disabled`, `emphasis` (`primary` | `secondary` — left to the strip unless set: `primary` while there are no cards, `secondary` once there are), `className`.
17
17
  - **`SplitScreenLayout`** — Two-pane full-screen layout (content + preview/aside) with an optional dismiss button.
18
18
  - **`PageLayout`** — Standard page chrome: a bordered card holding a breadcrumb bar, an optional alert strip, and a header block (subtitle, title + suffix, description) above the page body. Used as `PageLayout.Regular`; `PageLayout.Empty` is the chrome-only variant for pre-auth and error screens, with `headerActions` and `background` slots. Actions are declared as data (`buttons`) rather than children, and any that do not fit collapse into an overflow `ActionMenu` via `OverflowRow`. A client-side-routed app must pass `renderLink` so crumbs and the back button go through its router instead of reloading the document. Both layouts render a `main` landmark, demoted via `role` when the app owns its own. Deliberately not a `Surface` — the page card is 16px and carries no shadow. _Key props:_ `PageLayout.Regular`: `breadcrumbLinks`, `breadcrumbActions`, `renderLink`, `title`, `titleSuffix`, `subtitle`, `description`, `buttons`, `loading`, `centered`, `noPadding`, `contentGap` (`small` | `medium` | `large` | `xlarge`, default `small`), `alert`, `role`, `aria-label`, `aria-labelledby`, `className`, `style`. `PageLayout.Empty`: `title`, `centered`, `uniformPadding`, `headerActions`, `background`, `role`, `aria-label`, `aria-labelledby`, `className`, `style`.
19
- - **`WizardLayout`** — Full-screen wizard scaffold: top bar (back button, right-aligned stepper, trailing slot), pinned `header` slot, centered scrolling content column, and a pinned footer (above sticky body content). `footerGlass` gives the footer a frosted translucent + backdrop-blur treatment. _Key props:_ `showBack`, `onBack`, `stepper`, `trailing`, `header`, `footer`, `footerGlass`, `center`, `contentWidth`, `minWidth`.
19
+ - **`WizardLayout`** — Full-screen wizard scaffold: top bar (back button, right-aligned stepper, trailing slot), pinned `header` slot, centered scrolling content column, and a pinned footer (above sticky body content). `footerGlass` gives the footer a frosted translucent + backdrop-blur treatment. `banner` pins a page-wide notice under the top bar, above `header` and outside the content column, so a warning about the whole page is not squeezed to the column's width — pair it with `Alert layout="banner"`. `topBarSize="large"` draws a 60px top bar with 28px insets (52px and 16px below 640px). `footerAlign="content"` holds the footer to the content column, inside its gutter, so a full-width action lines up with the list above it. `reserveBack={false}` drops the square held for a hidden back button, so `leading` sits at the same inset as `trailing`; the default keeps it so `leading` does not move as the wizard steps. _Key props:_ `showBack`, `onBack`, `reserveBack`, `topBarSize` (`default` | `large`), `footerAlign` (`full` | `content`), `leading`, `stepper`, `trailing`, `banner`, `header`, `footer`, `footerGlass`, `center`, `contentWidth`, `minWidth`.
20
20
  - **`PageActions`** — A page's action buttons, collapsing into an overflow menu as the row narrows. `PageLayout.Regular` renders these from its `buttons` prop; import it directly when a page builds its own header block (e.g. one that scrolls the title away) so the collapse, the dividers and the href handling come with it rather than being hand-rolled. _Key props:_ `buttons` (`PageAction[]`).
21
- - **`Toolbar`** — Flat, opaque horizontal bar for a sub-header / filter row (e.g. a Search). Owns its chrome — opaque `card-background` (so it can pin over scrolling content) + optional bottom `divider` — so consumers don’t hand-roll background/border tokens. Its `TOOLBAR_FILTER_WIDTH_PX` / `TOOLBAR_FILTER_MIN_WIDTH_PX` / `TOOLBAR_FILL_BASIS_PX` / `TOOLBAR_FILL_MIN_WIDTH_PX` are exported so a control outside the bar can line up with one inside it without re-typing the number. Positioning (incl. sticky) is left to the consumer, as is the gutter when `padding="none"` — use that when the bar must sit flush with the content it pins over. `layout="filters"` makes it own the layout too, which consumers were each re-deriving and getting wrong the same way: a `flex: 1` search with no floor collapses to nothing on a narrow viewport, and a row that cannot wrap runs off the page. Compose it as `Toolbar.Fill` for the search and `Toolbar.Item` per filter, as flat siblings; `grow={false}` holds a slot at its basis for a row that already spaces its children. The row packs greedily from its basis widths and each line's spare space is shared out by grow factor, which is deliberately lopsided: the search outweighs the filters a hundred to one, so a shared line leaves them at their 200px while a filter left alone on a wrapped line grows into the whole width instead of stranding beside a gap. A filter drops to the next line once its full 200px basis will not fit, and the filters sharing a line then compress toward a 160px floor. A search and a filter are laid out differently rather than uniformly, because a search field's useful width is "as much as you can spare" where a filter's is the width of a value. _Key props:_ `divider`, `padding` (`none` | `compact` | `default`), `layout` (`plain` | `filters`), `className`, `style`. `Fill`: `minWidth`. `Item`: `width`, `minWidth`, `grow`.
21
+ - **`Toolbar`** — Flat, opaque horizontal bar for a sub-header / filter row (e.g. a Search). Owns its chrome — an opaque ground (`card-background`, or the sunken page ground with `surface="page"` for a bar above a card), so it can pin over scrolling content, plus an optional bottom `divider` — so consumers don’t hand-roll background/border tokens. Its `TOOLBAR_FILTER_WIDTH_PX` / `TOOLBAR_FILTER_MIN_WIDTH_PX` / `TOOLBAR_FILL_BASIS_PX` / `TOOLBAR_FILL_MIN_WIDTH_PX` are exported so a control outside the bar can line up with one inside it without re-typing the number. `sticky` pins it to the top of its scroll container; any other positioning is left to the consumer, as is the gutter when `padding="none"` — use that when the bar must sit flush with the content it pins over. `layout="filters"` makes it own the layout too, which consumers were each re-deriving and getting wrong the same way: a `flex: 1` search with no floor collapses to nothing on a narrow viewport, and a row that cannot wrap runs off the page. Compose it as `Toolbar.Fill` for the search and `Toolbar.Item` per filter, as flat siblings; `grow={false}` stops a slot growing past its basis, for a row that already spaces its children, though it can still shrink toward its `minWidth`. The row packs greedily from its basis widths and each line's spare space is shared out by grow factor, which is deliberately lopsided: the search outweighs the filters a hundred to one, so a shared line leaves them at their 200px while a filter left alone on a wrapped line grows into the whole width instead of stranding beside a gap. A filter drops to the next line once its full 200px basis will not fit, and the filters sharing a line then compress toward a 160px floor. A search and a filter are laid out differently rather than uniformly, because a search field's useful width is "as much as you can spare" where a filter's is the width of a value. _Key props:_ `divider`, `padding` (`none` | `compact` | `default`), `layout` (`plain` | `filters`), `surface` (`card` | `page` — `page` paints the sunken page ground, for a bar that sits above a card rather than inside it), `sticky` (pins it to the top of its scroll container above the rows under it), `className`, `style`. `Fill`: `minWidth`. `Item`: `width`, `minWidth`, `grow`.
22
22
  - **`Flex`** — Flexbox layout helper with semantic direction/align/justify/gap props. _Key props:_ `FlexDirection`, `FlexAlign`, `FlexJustify`, `FlexGapSize`.
23
23
  - **`Padded`** — Padding wrapper applying consistent design-system spacing.
24
24
  - **`Spacer`** — Empty element that adds fixed spacing between siblings.
25
25
  - **`Divider`** — Separator; supports a dashed line style. Horizontal by default and spanning its container; `orientation="vertical"` draws a rule between inline controls (e.g. groups in a toolbar) and holds its width in a tight flex row. `length` sizes it along its own axis on a fixed scale, and it never shrinks below 1px. _Key props:_ `orientation="horizontal" | "vertical"`, `length` (`small` | `medium` | `large`), `lineStyle="solid" | "dashed"`, `DividerVariant`, `SpacingSize`, `PaddingSize`.
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
- - **`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`), `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
- - **`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`.
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
+ - **`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
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.
@@ -34,10 +34,11 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
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`, `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), `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`.
41
+ - **`StatusPill`** — A 28px pill stating a status and what it is about — "Verified · claude.ai" — with the tone's icon first and the subject set in the mono face. `live` makes it a `status` live region, so a change of state is announced; it is off by default, as on `Alert`, since a pill repeated down a table would otherwise be a region per row. The icon stays out of what is read. A status takes no name from its text, so a live pill takes `aria-label` ("Client trust"); without `live` the pill is a generic span and `aria-label` is not applied. Tones are semantic and read the `alert` tokens, so it matches an `Alert` beside it, and the border is the tone's own colour thinned. `busy` swaps the icon for a spinner and sets `aria-busy`, for a status still being worked out. Truncates with an ellipsis in a narrow slot. Reach for `Tag` for a label, `StatusBadge` for a large glyph on its own. _Key props:_ `label`, `detail`, `tone` (`success` | `neutral` | `warning` | `error`), `icon`, `busy`, `live` (default `false`), `aria-label` / `aria-labelledby` (with `live`), `className`.
41
42
  - **`StatBadge`** — Pill showing a numeric value with a label (e.g. "3 accounts selected"). _Key props:_ `count`, `label`.
42
43
  - **`Tag`** — Small status/label tag. Types: success, error, warning, default. _Key props:_ `value`, `type`, `paddingSize`, `textTransform`, `WidthSize`, `tooltip` (hover-only; wrap in `Tooltip focusableTrigger` for keyboard reach), `tooltipDescribesTag`, `onClick` (makes it a button, unless `onRemove` is set; keep `value`/`icon` non-interactive), `onRemove`.
43
44
  - **`NumberTag`** — Compact numeric badge/counter.
@@ -83,7 +84,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
83
84
  - **`FieldBlock`** — Something a form has to show but does not ask for — a computed list, a generated value — wearing a field’s anatomy: label, optional `badge` beside it and `action` at the end of the row, content, then helper text, all on a field’s own metrics and inset. A real control brings its anatomy with it; this is for everything that has no control to bring one. _Key props:_ `label`, `badge`, `action`, `helperText`, `children`, `className`, `style`.
84
85
  - **`Input`** — Single-line text input. Passing `formControl` is enough to show validation: the resolver's message renders on its own line beneath the field in the destructive colour, politely announced, with `aria-invalid` and `aria-describedby` wired and the in-field error icon standing down once the message carries the meaning, and the control reports its edits back so a rejection clears as the customer fixes the field rather than standing until the next submit. Given a `formControl` the form owns the value: the field paints an explicit `value` first, then the value the form holds, then `defaultValue`, then empty — the ladder react-hook-form's own `useController` computes — so `reset()` and `setValue()` repaint the field and the text on screen cannot drift from the value being validated, which had put a rejection under a field the customer could see was filled. Passing `defaultValue` to a bound field warns in development, naming the component and the field: where the form has an entry for it, it is ignored, and where it has none it paints text the form will never submit. Set it through `useForm({ defaultValues })` instead. A consumer's own `helperText` keeps its line above it and stays dimmed, since it explains the field rather than reporting the fault — replacing one with the other left a field ringed in red with nothing saying what was wrong. An explicit `error` still wins, for a rejection the resolver knows nothing about, and `error={false}` suppresses the resolver outright, message included. `requiredIndicator` switches the label between the library default of naming the optional fields, an `asterisk` on the required ones for designs drawn that way, and `none` for a field whose value is supplied rather than asked for — a locked control is still required, but a marker only sends the reader looking for something to fill in. Visual only: `aria-required` is set from `required` whichever is chosen. _Key props:_ `label`, `type`, `InputSize`, `InputVariant` (`default` | `ghost`), `required`, `requiredIndicator`, `error`, `helperText`, `loading`, `formControl`, `value`, `defaultValue`, `showCopyButton`, `showPasswordToggle`, `aria-label`, `aria-labelledby`, `id`.
85
86
  - **`TextArea`** — Multi-line text input. Shows resolver validation from `formControl` on exactly the same terms as `Input`: the message gets its own line beneath any `helperText`, which keeps its own, and the value climbs the same ladder — an explicit `value`, then the value the form holds, then `defaultValue`, then empty — a disabled field included, which used to show `defaultValue` whatever the form or the consumer had said. `requiredIndicator` switches the label between the library default of naming the optional fields, an `asterisk` on the required ones for designs drawn that way, and `none` for a field whose value is supplied rather than asked for — a locked control is still required, but a marker only sends the reader looking for something to fill in. Visual only: `aria-required` is set from `required` whichever is chosen. _Key props:_ `label`, `rows`, `maxLength`, `resize`, `required`, `requiredIndicator`, `error`, `helperText`, `loading`, `formControl`, `value`, `defaultValue`, `aria-label`, `aria-labelledby`, `id`.
86
- - **`Search`** — Search input with a leading icon; `default` and `muted` variants. `width` fixes the field's width, the same prop `Dropdown` and `MultiSelect` take, so a toolbar can put its controls on one width rather than letting each size itself; without it the field fills its container, and a container narrower than the value still wins. `clearable` adds a clear button while there is text, and lets Escape clear; either way focus stays in the field, and an Escape that clears does not also close a surrounding panel. Name it with `aria-label` when the placeholder is too short to stand alone. Forwards a ref to the input. _Key props:_ `value`, `onChange`, `placeholder`, `variant`, `size`, `width`, `disabled`, `clearable`, `onClear`, `onKeyDown`, `aria-label`, `aria-labelledby`.
87
+ - **`Search`** — Search input with a leading icon; `default` and `muted` variants. `width` fixes the field's width, the same prop `Dropdown` and `MultiSelect` take, so a toolbar can put its controls on one width rather than letting each size itself; without it the field fills its container, and a container narrower than the value still wins. `clearable` adds a clear button while there is text, and lets Escape clear; either way focus stays in the field, and an Escape that clears does not also close a surrounding panel. Name it with `aria-label` when the placeholder is too short to stand alone. Forwards a ref to the input. _Key props:_ `value`, `onChange`, `placeholder`, `variant`, `size`, `textSize` (`control` follows `size`; `body` sets the 14px body step at any height), `width`, `disabled`, `clearable`, `onClear`, `onKeyDown`, `aria-label`, `aria-labelledby`.
87
88
  - **`Checkbox`** — Checkbox supporting checked/indeterminate/disabled. Pass `presentational` to render a visual-only, non-focusable indicator for use inside a clickable row/card. _Key props:_ `checked`, `indeterminate`, `disabled`, `onChange`, `presentational`.
88
89
  - **`Radio`** — Single radio control with hover state; parent manages the group. Pass `presentational` to render a visual-only, non-focusable indicator for use inside a clickable row/card. _Key props:_ `checked`, `onChange`, `disabled`, `size`, `presentational`.
89
90
  - **`SegmentedControl`** — A rounded track of mutually-exclusive options — the segmented “one of these” control. Owns the track background, the selected-thumb chrome and the `radiogroup` semantics, so consumers never hand-roll a track around `PillButton` (whose `default` variant tokens are tuned for standalone filter pills, not a sunken track). Single tab stop with arrow-key move-and-select; selection is conveyed via `aria-checked` and a foreground change, not background colour alone. An option `icon` sits before the label in a fixed 16px slot, and an option `badge` follows the label at whatever width it needs, for a mark such as a compact `Tag` naming a saving; it renders inside the segment button, so it must not be interactive. Restyle via the `segmentedControl` theme tokens. _Key props:_ `options` (`value`, `label`, `icon`, `badge`, `disabled`), `value`, `onChange`, `SegmentedControlSize` (`small` | `medium` | `large`), `fullWidth`, `disabled`, `aria-label`, `aria-labelledby`, `className`.
@@ -98,7 +99,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
98
99
 
99
100
  ## Buttons & actions
100
101
 
101
- - **`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), `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`, `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
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`.
103
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`.
104
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`.
@@ -109,7 +110,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
109
110
  ## Typography
110
111
 
111
112
  - **`ClampText`** — Clamps text to N lines; expands to full text on hover/focus with a height animation. Wrapping markdown? Pass `variant="flow"` to the `Typography.MarkdownBlock` inside it, or the clamp counts blocks instead of lines.
112
- - **`Typography`** — Text primitives — use these instead of raw colour tokens on `<span>`s. Includes `Typography.Text`, `Typography.SecondaryText`, `Typography.EllipsisText`, etc., with size/weight/color props. `Typography.Text` takes `font="sans" | "heading" | "body" | "mono"`, and `as` (`p`, `h1`–`h6`) for a heading or paragraph that keeps its look; `Typography.CodeText` takes `variant="inline"` for a chip mid-sentence; `Typography.MarkdownBlock` takes `variant="flow"` for use inside `ClampText`, `variant="inline"` for one line of inline syntax set inside another component (no autolinks, images as alt text), `links="inert"` to draw links without an anchor inside a button, `renderLink` to send root-relative links (`/path`, never `//host`) through the app’s router in the same tab while every other link still opens in a new one — without it every link opens in a new tab, and `normalizeSource` for machine-generated descriptions that omit blank lines between blocks. `Typography.PathText` draws one line of path segments (`segments`, root first, each `{ text, tone, minChars }` with `tone` `default` | `secondary`) joined by a `separator` that is never cut: the segments give way in order, each down to `minChars` (2) characters, so the last stays whole the longest, and a cut never leaves a space before its ellipsis, which `text-overflow` does. The full path is always the accessible name and shows in a tooltip only when something is cut (`tooltip`, `tooltipPlacement`, `tooltipSuppressed` while the menu it labels is open). It measures with canvas text metrics against a shared ResizeObserver, so it costs no layout read per instance. `Typography.EllipsisText` takes a `size` so a truncating label can match the text beside it, and shows its `tooltip` on every hover unless `tooltipWhenTruncated` is set, which limits it to text that is actually cut off — for a tooltip that repeats the label, where a tooltip carrying something the label does not say should keep the default.
113
+ - **`Typography`** — Text primitives — use these instead of raw colour tokens on `<span>`s. Includes `Typography.Text`, `Typography.SecondaryText`, `Typography.EllipsisText`, etc., with size/weight/color props. `Typography.Text`'s `size` runs `xsmall` (11px), `12`, `small` (13px), `medium` (14px) and up; `12` is off the named ladder, like the theme's `15`, for a secondary line under a 14px label. `wrap={false}` keeps a `Typography.Text` on one line. `Typography.Text` takes `font="sans" | "heading" | "body" | "mono"`, and `as` (`p`, `h1`–`h6`) for a heading or paragraph that keeps its look; `Typography.CodeText` takes `variant="inline"` for a chip mid-sentence; `Typography.MarkdownBlock` takes `variant="flow"` for use inside `ClampText`, `variant="inline"` for one line of inline syntax set inside another component (no autolinks, images as alt text), `links="inert"` to draw links without an anchor inside a button, `renderLink` to send root-relative links (`/path`, never `//host`) through the app’s router in the same tab while every other link still opens in a new one — without it every link opens in a new tab, and `normalizeSource` for machine-generated descriptions that omit blank lines between blocks. `Typography.PathText` draws one line of path segments (`segments`, root first, each `{ text, tone, minChars }` with `tone` `default` | `secondary`) joined by a `separator` that is never cut: the segments give way in order, each down to `minChars` (2) characters, so the last stays whole the longest, and a cut never leaves a space before its ellipsis, which `text-overflow` does. The full path is always the accessible name and shows in a tooltip only when something is cut (`tooltip`, `tooltipPlacement`, `tooltipSuppressed` while the menu it labels is open). It measures with canvas text metrics against a shared ResizeObserver, so it costs no layout read per instance. `Typography.EllipsisText` takes a `size` and a `fontWeight` (Text's scale) so a truncating label can match the text beside it, and shows its `tooltip` on every hover unless `tooltipWhenTruncated` is set, which limits it to text that is actually cut off — for a tooltip that repeats the label, where a tooltip carrying something the label does not say should keep the default.
113
114
 
114
115
  ## Data display
115
116
 
@@ -1,16 +1,16 @@
1
- import { jsxs as i, jsx as e, Fragment as B } from "react/jsx-runtime";
2
- import { useState as E } from "react";
3
- import { Button as F } from "./Button.js";
4
- import S from "./CustomIcons/CaretDownIcon.js";
5
- import O from "./CustomIcons/CaretUpIcon.js";
6
- import z from "./CustomIcons/CheckCircleFilled.js";
7
- import A from "./CustomIcons/ErrorCircleFilled.js";
8
- import R from "./CustomIcons/InfoCircleFilled.js";
9
- import W from "./CustomIcons/SparkleIcon.js";
10
- import H from "./CustomIcons/WarningCircleFilled.js";
11
- import M from "./Typography/MarkdownBlock.js";
12
- import { getTextColor as $ } from "./Typography/textStyles.js";
13
- const C = (r) => {
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) => {
14
14
  switch (r) {
15
15
  case "info":
16
16
  return "primary";
@@ -27,130 +27,147 @@ const C = (r) => {
27
27
  default:
28
28
  return "primary";
29
29
  }
30
- }, s = {
30
+ }, a = {
31
31
  width: "13px",
32
32
  height: "13px",
33
33
  flexShrink: 0
34
- }, n = {
34
+ }, o = {
35
35
  info: "var(--malachite-alert-info-foreground)",
36
36
  success: "var(--malachite-alert-success-foreground)",
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
- }, D = {
40
+ }, I = {
41
41
  info: /* @__PURE__ */ e(
42
- R,
42
+ Y,
43
43
  {
44
- style: { ...s, color: n.info }
44
+ style: { ...a, color: o.info }
45
45
  }
46
46
  ),
47
47
  success: /* @__PURE__ */ e(
48
- z,
48
+ M,
49
49
  {
50
- style: { ...s, color: n.success }
50
+ style: { ...a, color: o.success }
51
51
  }
52
52
  ),
53
53
  error: /* @__PURE__ */ e(
54
- A,
54
+ T,
55
55
  {
56
- style: { ...s, color: n.error }
56
+ style: { ...a, color: o.error }
57
57
  }
58
58
  ),
59
59
  warning: /* @__PURE__ */ e(
60
- H,
60
+ D,
61
61
  {
62
- style: { ...s, color: n.warning }
62
+ style: { ...a, color: o.warning }
63
63
  }
64
64
  ),
65
65
  // The same sparkle the control that produced the message carries, so the two read as
66
66
  // one thread rather than two unrelated affordances.
67
67
  assistant: /* @__PURE__ */ e(
68
- W,
68
+ $,
69
69
  {
70
- style: { ...s, color: n.assistant }
70
+ style: { ...a, color: o.assistant }
71
71
  }
72
72
  )
73
- }, g = (r, l) => l ? /* @__PURE__ */ e(
73
+ }, q = { ...a, width: "16px", height: "16px" }, _ = (r, l, t = !1) => !l && !t ? I[r] : /* @__PURE__ */ e(
74
74
  "span",
75
75
  {
76
76
  className: "alert-status-icon inline-flex shrink-0 [&_svg]:size-full!",
77
- style: { ...s, color: n[r] },
78
- children: l
77
+ style: { ...t ? q : a, color: o[r] },
78
+ children: l || I[r]
79
79
  }
80
- ) : D[r], w = ({ text: r, type: l, renderLink: c }) => /* @__PURE__ */ e(
81
- M,
80
+ ), u = ({ text: r, type: l, lead: t, renderLink: c }) => t ? /* @__PURE__ */ i("div", { className: "alert-lead", children: [
81
+ /* @__PURE__ */ e(
82
+ "strong",
83
+ {
84
+ style: {
85
+ color: y(h(l)),
86
+ fontSize: U("small"),
87
+ fontWeight: 600
88
+ },
89
+ children: t
90
+ }
91
+ ),
92
+ " ",
93
+ /* @__PURE__ */ e(u, { text: r, type: l, renderLink: c })
94
+ ] }) : /* @__PURE__ */ e(
95
+ L,
82
96
  {
83
97
  text: r,
84
- color: C(l),
98
+ color: h(l),
85
99
  size: "small",
86
100
  omitMargin: !0,
87
101
  renderLink: c
88
102
  }
89
- ), ee = ({
103
+ ), ae = ({
90
104
  type: r = "info",
91
105
  message: l,
92
- hasMargin: c = !0,
93
- className: N,
94
- children: f,
95
- isExpanded: d,
96
- onToggle: m,
97
- action: o,
98
- icon: x,
99
- live: b = !1,
100
- renderLink: u
106
+ hasMargin: t = !0,
107
+ 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
101
117
  }) => {
102
- const t = !!f, h = b ? r === "error" || r === "warning" ? "alert" : "status" : void 0, [I, y] = E(!1), a = d !== void 0 ? d : I, k = () => {
103
- const v = !a;
104
- d === void 0 && y(v), m == null || m(v);
105
- }, p = [
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 = [
106
122
  "alert-base",
107
123
  `alert-${r}`,
108
124
  "flex",
109
125
  "w-full",
110
126
  // Ensure full width
111
- t && a ? "flex-col" : "gap-2 items-center min-h-[2rem]",
112
- c ? "" : "alert-no-margin",
127
+ x ? "alert-banner" : s && n ? "flex-col" : "gap-2 items-center min-h-[2rem]",
128
+ t ? "" : "alert-no-margin",
113
129
  "text-left",
114
- t ? "rounded-xl !p-0" : "!px-2.5 !py-1.5",
130
+ x ? "" : s ? "rounded-xl !p-0" : "!px-2.5 !py-1.5",
115
131
  // Slightly more vertical padding for centering
116
- N
117
- ].filter(Boolean).join(" "), _ = [
132
+ c
133
+ ].filter(Boolean).join(" "), F = [
118
134
  "flex items-center justify-between w-full",
119
- t ? "gap-2 px-2.5 py-1" : "gap-2",
120
- t && a ? "rounded-t-xl" : t ? "rounded-xl" : ""
121
- ].filter(Boolean).join(" "), j = ["w-full", "overflow-hidden"].filter(Boolean).join(" ");
122
- return t ? /* @__PURE__ */ i("div", { className: `${p} alert-expandable`, children: [
123
- /* @__PURE__ */ i("div", { className: _, children: [
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: [
124
140
  /* @__PURE__ */ i("div", { className: "flex-1 flex gap-2 items-center", children: [
125
- g(r, x),
126
- /* @__PURE__ */ e("div", { className: "flex-1 flex items-center", children: /* @__PURE__ */ e("div", { className: "[&>div]:mb-0", role: h, children: /* @__PURE__ */ e(
127
- w,
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,
128
144
  {
129
145
  text: l,
146
+ lead: w,
130
147
  type: r,
131
- renderLink: u
148
+ renderLink: g
132
149
  }
133
150
  ) }) })
134
151
  ] }),
135
152
  /* @__PURE__ */ i("div", { className: "flex items-center gap-2", children: [
136
- o && /* @__PURE__ */ e("div", { className: "alert-action flex items-center", children: o }),
153
+ d && /* @__PURE__ */ e("div", { className: "alert-action flex items-center", children: d }),
137
154
  /* @__PURE__ */ e(
138
- F,
155
+ R,
139
156
  {
140
157
  variant: "no-style",
141
158
  size: "xsmall",
142
- onClick: k,
143
- "aria-label": a ? "Collapse" : "Expand",
159
+ onClick: B,
160
+ "aria-label": n ? "Collapse" : "Expand",
144
161
  style: {
145
- color: $(C(r))
162
+ color: y(h(r))
146
163
  },
147
- children: a ? /* @__PURE__ */ e(
148
- O,
164
+ children: n ? /* @__PURE__ */ e(
165
+ H,
149
166
  {
150
167
  style: { minWidth: "13px", minHeight: "13px" }
151
168
  }
152
169
  ) : /* @__PURE__ */ e(
153
- S,
170
+ W,
154
171
  {
155
172
  style: { minWidth: "13px", minHeight: "13px" }
156
173
  }
@@ -159,15 +176,23 @@ const C = (r) => {
159
176
  )
160
177
  ] })
161
178
  ] }),
162
- a && /* @__PURE__ */ e(B, { children: /* @__PURE__ */ e("div", { className: j, 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: f }) }) })
163
- ] }) : /* @__PURE__ */ i("div", { className: p, children: [
164
- g(r, x),
165
- /* @__PURE__ */ e("div", { className: "flex-1 flex items-center", children: /* @__PURE__ */ e("div", { className: "[&>div]:mb-0", role: h, children: /* @__PURE__ */ e(w, { text: l, type: r, renderLink: u }) }) }),
166
- o && /* @__PURE__ */ e("div", { className: "alert-action shrink-0 flex items-center", children: o })
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 })
167
192
  ] });
168
193
  };
169
194
  export {
170
- ee as Alert,
171
- ee as default
195
+ ae as Alert,
196
+ ae as default
172
197
  };
173
198
  //# sourceMappingURL=Alert.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Alert.js","sources":["../../src/components/Alert.tsx"],"sourcesContent":["import React, { useState } from 'react';\nimport { Button, ButtonVariant } from './Button';\nimport CaretDownIcon from './CustomIcons/CaretDownIcon';\nimport CaretUpIcon from './CustomIcons/CaretUpIcon';\nimport CheckCircleFilled from './CustomIcons/CheckCircleFilled';\nimport ErrorCircleFilled from './CustomIcons/ErrorCircleFilled';\nimport InfoCircleFilled from './CustomIcons/InfoCircleFilled';\nimport SparkleIcon from './CustomIcons/SparkleIcon';\nimport WarningCircleFilled from './CustomIcons/WarningCircleFilled';\nimport type { RenderLink } from './PageLayout/types';\nimport MarkdownBlock from './Typography/MarkdownBlock';\nimport type { TextColor } from './Typography/Text';\nimport { getTextColor } from './Typography/textStyles';\n\nexport type AlertType = 'info' | 'success' | 'error' | 'warning' | 'assistant';\n\ninterface AlertButtonConfig {\n label: string;\n onClick: () => void;\n variant?: ButtonVariant;\n icon?: React.ReactNode;\n}\n\ninterface AlertProps {\n type?: AlertType;\n message: string;\n hasMargin?: boolean;\n className?: string;\n children?: React.ReactNode;\n isExpanded?: boolean;\n onToggle?: (expanded: boolean) => void;\n /** A control at the end of the alert — a link or button that resolves what it reports. */\n action?: React.ReactNode;\n /** Replaces the type's status icon: an svg painting with `currentColor`, such as a CustomIcon,\n * is drawn in the type's colour at the same size. */\n icon?: React.ReactNode;\n /** Makes its message a live region: `role=\"alert\"` for error and warning, `status` otherwise.\n * Screen readers reliably announce an `alert` as it mounts; a `status` is read when its\n * `message` changes, so keep a live info or success alert mounted. Off by default. */\n live?: boolean;\n /** Routes root-relative links in `message` through the app's router; see `MarkdownBlock`. */\n renderLink?: RenderLink;\n}\n\n// Distinct from the imported `getTextColor`, which maps a TextColor to a CSS variable.\n// This maps an AlertType to a TextColor token; both are used in this file.\nconst getAlertTextColor = (alertType: AlertType): TextColor => {\n switch (alertType) {\n case 'info':\n return 'primary';\n case 'success':\n return 'success';\n case 'error':\n return 'error';\n case 'warning':\n return 'warning';\n // The violet is on `.alert-assistant` itself; inheriting it keeps one definition\n // rather than a TextColor token that only this alert would ever name.\n case 'assistant':\n return 'inherit';\n default:\n return 'primary';\n }\n};\n\nconst ICON_STYLE = {\n width: '13px',\n height: '13px',\n flexShrink: 0,\n};\n\nconst ICON_COLOR: Record<AlertType, string> = {\n info: 'var(--malachite-alert-info-foreground)',\n success: 'var(--malachite-alert-success-foreground)',\n error: 'var(--malachite-alert-error-foreground)',\n warning: 'var(--malachite-alert-warning-foreground)',\n assistant: 'var(--malachite-alert-assistant-foreground)',\n};\n\nconst AlertIcon = {\n info: (\n <InfoCircleFilled\n style={{ ...ICON_STYLE, color: ICON_COLOR.info }}\n />\n ),\n success: (\n <CheckCircleFilled\n style={{ ...ICON_STYLE, color: ICON_COLOR.success }}\n />\n ),\n error: (\n <ErrorCircleFilled\n style={{ ...ICON_STYLE, color: ICON_COLOR.error }}\n />\n ),\n warning: (\n <WarningCircleFilled\n style={{ ...ICON_STYLE, color: ICON_COLOR.warning }}\n />\n ),\n // The same sparkle the control that produced the message carries, so the two read as\n // one thread rather than two unrelated affordances.\n assistant: (\n <SparkleIcon\n style={{ ...ICON_STYLE, color: ICON_COLOR.assistant }}\n />\n ),\n};\n\nconst statusIcon = (type: AlertType, icon: React.ReactNode) =>\n icon ? (\n <span\n className=\"alert-status-icon inline-flex shrink-0 [&_svg]:size-full!\"\n style={{ ...ICON_STYLE, color: ICON_COLOR[type] }}\n >\n {icon}\n </span>\n ) : (\n AlertIcon[type]\n );\n\nconst SimpleTextRenderer: React.FC<{\n text: string;\n type: AlertType;\n renderLink?: RenderLink;\n}> = ({ text, type, renderLink }) => {\n return (\n <MarkdownBlock\n text={text}\n color={getAlertTextColor(type)}\n size=\"small\"\n omitMargin\n renderLink={renderLink}\n />\n );\n};\n\nconst Alert: React.FC<AlertProps> = ({\n type = 'info',\n message,\n hasMargin = true,\n className,\n children,\n isExpanded,\n onToggle,\n action,\n icon,\n live = false,\n renderLink,\n}) => {\n const isExpandable = !!children;\n const urgent = type === 'error' || type === 'warning';\n const liveRole = live ? (urgent ? 'alert' : 'status') : undefined;\n const [internalExpanded, setInternalExpanded] = useState(false);\n\n // Use controlled state if isExpanded is provided, otherwise use internal state\n const expanded = isExpanded !== undefined ? isExpanded : internalExpanded;\n\n const handleToggle = () => {\n const newExpanded = !expanded;\n if (isExpanded === undefined) {\n // Uncontrolled mode\n setInternalExpanded(newExpanded);\n }\n // Call onToggle if provided (for both controlled and uncontrolled)\n onToggle?.(newExpanded);\n };\n\n const alertClasses = [\n 'alert-base',\n `alert-${type}`,\n 'flex',\n 'w-full', // Ensure full width\n isExpandable && expanded ? 'flex-col' : 'gap-2 items-center min-h-[2rem]',\n !hasMargin ? 'alert-no-margin' : '',\n 'text-left',\n isExpandable ? 'rounded-xl !p-0' : '!px-2.5 !py-1.5', // Slightly more vertical padding for centering\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const headerClasses = [\n 'flex items-center justify-between w-full',\n isExpandable ? 'gap-2 px-2.5 py-1' : 'gap-2',\n isExpandable && expanded ? 'rounded-t-xl' : isExpandable ? 'rounded-xl' : '',\n ]\n .filter(Boolean)\n .join(' ');\n\n const expandableContentClasses = ['w-full', 'overflow-hidden'].filter(Boolean).join(' ');\n\n // Non-expandable alert (original behavior)\n if (!isExpandable) {\n return (\n <div className={alertClasses}>\n {statusIcon(type, icon)}\n <div className=\"flex-1 flex items-center\">\n <div className=\"[&>div]:mb-0\" role={liveRole}>\n <SimpleTextRenderer text={message} type={type} renderLink={renderLink} />\n </div>\n </div>\n {action && <div className=\"alert-action shrink-0 flex items-center\">{action}</div>}\n </div>\n );\n }\n\n // Expandable alert\n return (\n <div className={`${alertClasses} alert-expandable`}>\n {/* Header section with title and expand/collapse button */}\n <div className={headerClasses}>\n <div className=\"flex-1 flex gap-2 items-center\">\n {statusIcon(type, icon)}\n <div className=\"flex-1 flex items-center\">\n <div className=\"[&>div]:mb-0\" role={liveRole}>\n <SimpleTextRenderer\n text={message}\n type={type}\n renderLink={renderLink}\n />\n </div>\n </div>\n </div>\n\n {/* Right side buttons container */}\n <div className=\"flex items-center gap-2\">\n {action && <div className=\"alert-action flex items-center\">{action}</div>}\n {/* Expand/collapse button */}\n <Button\n variant=\"no-style\"\n size=\"xsmall\"\n onClick={handleToggle}\n aria-label={expanded ? 'Collapse' : 'Expand'}\n style={{\n color: getTextColor(getAlertTextColor(type)),\n }}\n >\n {expanded ? (\n <CaretUpIcon\n style={{ minWidth: '13px', minHeight: '13px' }}\n />\n ) : (\n <CaretDownIcon\n style={{ minWidth: '13px', minHeight: '13px' }}\n />\n )}\n </Button>\n </div>\n </div>\n\n {/* Expandable content */}\n {expanded && (\n <>\n <div className={expandableContentClasses}>\n <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\">\n {children}\n </div>\n </div>\n </>\n )}\n </div>\n );\n};\n\nexport default Alert;\nexport { Alert };\nexport type { AlertButtonConfig };\n"],"names":["getAlertTextColor","alertType","ICON_STYLE","ICON_COLOR","AlertIcon","jsx","InfoCircleFilled","CheckCircleFilled","ErrorCircleFilled","WarningCircleFilled","SparkleIcon","statusIcon","type","icon","SimpleTextRenderer","text","renderLink","MarkdownBlock","Alert","message","hasMargin","className","children","isExpanded","onToggle","action","live","isExpandable","liveRole","internalExpanded","setInternalExpanded","useState","expanded","handleToggle","newExpanded","alertClasses","headerClasses","expandableContentClasses","jsxs","Button","getTextColor","CaretUpIcon","CaretDownIcon","Fragment"],"mappings":";;;;;;;;;;;;AA8CA,MAAMA,IAAoB,CAACC,MAAoC;AAC3D,UAAQA,GAAA;AAAA,IACJ,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA;AAAA;AAAA,IAGX,KAAK;AACD,aAAO;AAAA,IACX;AACI,aAAO;AAAA,EAAA;AAEnB,GAEMC,IAAa;AAAA,EACf,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,YAAY;AAChB,GAEMC,IAAwC;AAAA,EAC1C,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS;AAAA,EACT,WAAW;AACf,GAEMC,IAAY;AAAA,EACd,MACI,gBAAAC;AAAA,IAACC;AAAA,IAAA;AAAA,MACG,OAAO,EAAE,GAAGJ,GAAY,OAAOC,EAAW,KAAA;AAAA,IAAK;AAAA,EAAA;AAAA,EAGvD,SACI,gBAAAE;AAAA,IAACE;AAAA,IAAA;AAAA,MACG,OAAO,EAAE,GAAGL,GAAY,OAAOC,EAAW,QAAA;AAAA,IAAQ;AAAA,EAAA;AAAA,EAG1D,OACI,gBAAAE;AAAA,IAACG;AAAA,IAAA;AAAA,MACG,OAAO,EAAE,GAAGN,GAAY,OAAOC,EAAW,MAAA;AAAA,IAAM;AAAA,EAAA;AAAA,EAGxD,SACI,gBAAAE;AAAA,IAACI;AAAA,IAAA;AAAA,MACG,OAAO,EAAE,GAAGP,GAAY,OAAOC,EAAW,QAAA;AAAA,IAAQ;AAAA,EAAA;AAAA;AAAA;AAAA,EAK1D,WACI,gBAAAE;AAAA,IAACK;AAAA,IAAA;AAAA,MACG,OAAO,EAAE,GAAGR,GAAY,OAAOC,EAAW,UAAA;AAAA,IAAU;AAAA,EAAA;AAGhE,GAEMQ,IAAa,CAACC,GAAiBC,MACjCA,IACI,gBAAAR;AAAA,EAAC;AAAA,EAAA;AAAA,IACG,WAAU;AAAA,IACV,OAAO,EAAE,GAAGH,GAAY,OAAOC,EAAWS,CAAI,EAAA;AAAA,IAE7C,UAAAC;AAAA,EAAA;AACL,IAEAT,EAAUQ,CAAI,GAGhBE,IAID,CAAC,EAAE,MAAAC,GAAM,MAAAH,GAAM,YAAAI,QAEZ,gBAAAX;AAAA,EAACY;AAAA,EAAA;AAAA,IACG,MAAAF;AAAA,IACA,OAAOf,EAAkBY,CAAI;AAAA,IAC7B,MAAK;AAAA,IACL,YAAU;AAAA,IACV,YAAAI;AAAA,EAAA;AAAA,GAKNE,KAA8B,CAAC;AAAA,EACjC,MAAAN,IAAO;AAAA,EACP,SAAAO;AAAA,EACA,WAAAC,IAAY;AAAA,EACZ,WAAAC;AAAA,EACA,UAAAC;AAAA,EACA,YAAAC;AAAA,EACA,UAAAC;AAAA,EACA,QAAAC;AAAA,EACA,MAAAZ;AAAA,EACA,MAAAa,IAAO;AAAA,EACP,YAAAV;AACJ,MAAM;AACF,QAAMW,IAAe,CAAC,CAACL,GAEjBM,IAAWF,IADFd,MAAS,WAAWA,MAAS,YACV,UAAU,WAAY,QAClD,CAACiB,GAAkBC,CAAmB,IAAIC,EAAS,EAAK,GAGxDC,IAAWT,MAAe,SAAYA,IAAaM,GAEnDI,IAAe,MAAM;AACvB,UAAMC,IAAc,CAACF;AACrB,IAAIT,MAAe,UAEfO,EAAoBI,CAAW,GAGnCV,KAAA,QAAAA,EAAWU;AAAA,EACf,GAEMC,IAAe;AAAA,IACjB;AAAA,IACA,SAASvB,CAAI;AAAA,IACb;AAAA,IACA;AAAA;AAAA,IACAe,KAAgBK,IAAW,aAAa;AAAA,IACvCZ,IAAgC,KAApB;AAAA,IACb;AAAA,IACAO,IAAe,oBAAoB;AAAA;AAAA,IACnCN;AAAA,EAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG,GAEPe,IAAgB;AAAA,IAClB;AAAA,IACAT,IAAe,sBAAsB;AAAA,IACrCA,KAAgBK,IAAW,iBAAiBL,IAAe,eAAe;AAAA,EAAA,EAEzE,OAAO,OAAO,EACd,KAAK,GAAG,GAEPU,IAA2B,CAAC,UAAU,iBAAiB,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAGvF,SAAKV,IAgBD,gBAAAW,EAAC,OAAA,EAAI,WAAW,GAAGH,CAAY,qBAE3B,UAAA;AAAA,IAAA,gBAAAG,EAAC,OAAA,EAAI,WAAWF,GACZ,UAAA;AAAA,MAAA,gBAAAE,EAAC,OAAA,EAAI,WAAU,kCACV,UAAA;AAAA,QAAA3B,EAAWC,GAAMC,CAAI;AAAA,QACtB,gBAAAR,EAAC,SAAI,WAAU,4BACX,4BAAC,OAAA,EAAI,WAAU,gBAAe,MAAMuB,GAChC,UAAA,gBAAAvB;AAAA,UAACS;AAAA,UAAA;AAAA,YACG,MAAMK;AAAA,YACN,MAAAP;AAAA,YACA,YAAAI;AAAA,UAAA;AAAA,QAAA,GAER,EAAA,CACJ;AAAA,MAAA,GACJ;AAAA,MAGA,gBAAAsB,EAAC,OAAA,EAAI,WAAU,2BACV,UAAA;AAAA,QAAAb,KAAU,gBAAApB,EAAC,OAAA,EAAI,WAAU,kCAAkC,UAAAoB,GAAO;AAAA,QAEnE,gBAAApB;AAAA,UAACkC;AAAA,UAAA;AAAA,YACG,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAASN;AAAA,YACT,cAAYD,IAAW,aAAa;AAAA,YACpC,OAAO;AAAA,cACH,OAAOQ,EAAaxC,EAAkBY,CAAI,CAAC;AAAA,YAAA;AAAA,YAG9C,UAAAoB,IACG,gBAAA3B;AAAA,cAACoC;AAAA,cAAA;AAAA,gBACG,OAAO,EAAE,UAAU,QAAQ,WAAW,OAAA;AAAA,cAAO;AAAA,YAAA,IAGjD,gBAAApC;AAAA,cAACqC;AAAA,cAAA;AAAA,gBACG,OAAO,EAAE,UAAU,QAAQ,WAAW,OAAA;AAAA,cAAO;AAAA,YAAA;AAAA,UACjD;AAAA,QAAA;AAAA,MAER,EAAA,CACJ;AAAA,IAAA,GACJ;AAAA,IAGCV,KACG,gBAAA3B,EAAAsC,GAAA,EACI,UAAA,gBAAAtC,EAAC,OAAA,EAAI,WAAWgC,GACZ,UAAA,gBAAAhC,EAAC,OAAA,EAAI,WAAU,6KACV,UAAAiB,EAAA,CACL,EAAA,CACJ,EAAA,CACJ;AAAA,EAAA,GAER,IAlEI,gBAAAgB,EAAC,OAAA,EAAI,WAAWH,GACX,UAAA;AAAA,IAAAxB,EAAWC,GAAMC,CAAI;AAAA,sBACrB,OAAA,EAAI,WAAU,4BACX,UAAA,gBAAAR,EAAC,SAAI,WAAU,gBAAe,MAAMuB,GAChC,4BAACd,GAAA,EAAmB,MAAMK,GAAS,MAAAP,GAAY,YAAAI,EAAA,CAAwB,GAC3E,GACJ;AAAA,IACCS,KAAU,gBAAApB,EAAC,OAAA,EAAI,WAAU,2CAA2C,UAAAoB,EAAA,CAAO;AAAA,EAAA,GAChF;AA4DZ;"}
1
+ {"version":3,"file":"Alert.js","sources":["../../src/components/Alert.tsx"],"sourcesContent":["import React, { useState } from 'react';\nimport { Button, ButtonVariant } from './Button';\nimport CaretDownIcon from './CustomIcons/CaretDownIcon';\nimport CaretUpIcon from './CustomIcons/CaretUpIcon';\nimport CheckCircleFilled from './CustomIcons/CheckCircleFilled';\nimport ErrorCircleFilled from './CustomIcons/ErrorCircleFilled';\nimport InfoCircleFilled from './CustomIcons/InfoCircleFilled';\nimport SparkleIcon from './CustomIcons/SparkleIcon';\nimport WarningCircleFilled from './CustomIcons/WarningCircleFilled';\nimport type { RenderLink } from './PageLayout/types';\nimport MarkdownBlock from './Typography/MarkdownBlock';\nimport type { TextColor } from './Typography/Text';\nimport { getFontSize, getTextColor } from './Typography/textStyles';\n\nexport type AlertType = 'info' | 'success' | 'error' | 'warning' | 'assistant';\n\nexport type AlertLayout = 'inline' | 'banner';\n\ninterface AlertButtonConfig {\n label: string;\n onClick: () => void;\n variant?: ButtonVariant;\n icon?: React.ReactNode;\n}\n\ninterface AlertProps {\n type?: AlertType;\n message: string;\n hasMargin?: boolean;\n className?: string;\n children?: React.ReactNode;\n isExpanded?: boolean;\n onToggle?: (expanded: boolean) => void;\n /** A control at the end of the alert — a link or button that resolves what it reports. */\n action?: React.ReactNode;\n /** Replaces the type's status icon: an svg painting with `currentColor`, such as a CustomIcon,\n * is drawn in the type's colour at the same size. */\n icon?: React.ReactNode;\n /** Makes its message a live region: `role=\"alert\"` for error and warning, `status` otherwise.\n * Screen readers reliably announce an `alert` as it mounts; a `status` is read when its\n * `message` changes, so keep a live info or success alert mounted. Off by default. */\n live?: boolean;\n /** Routes root-relative links in `message` through the app's router; see `MarkdownBlock`. */\n renderLink?: RenderLink;\n /** `banner`: a 48px page-wide band. Applies only without `children`. */\n layout?: AlertLayout;\n /** A bold sentence the message runs on from. Plain text, not markdown. */\n lead?: string;\n}\n\n// Distinct from the imported `getTextColor`, which maps a TextColor to a CSS variable.\n// This maps an AlertType to a TextColor token; both are used in this file.\nconst getAlertTextColor = (alertType: AlertType): TextColor => {\n switch (alertType) {\n case 'info':\n return 'primary';\n case 'success':\n return 'success';\n case 'error':\n return 'error';\n case 'warning':\n return 'warning';\n // The violet is on `.alert-assistant` itself; inheriting it keeps one definition\n // rather than a TextColor token that only this alert would ever name.\n case 'assistant':\n return 'inherit';\n default:\n return 'primary';\n }\n};\n\nconst ICON_STYLE = {\n width: '13px',\n height: '13px',\n flexShrink: 0,\n};\n\nconst ICON_COLOR: Record<AlertType, string> = {\n info: 'var(--malachite-alert-info-foreground)',\n success: 'var(--malachite-alert-success-foreground)',\n error: 'var(--malachite-alert-error-foreground)',\n warning: 'var(--malachite-alert-warning-foreground)',\n assistant: 'var(--malachite-alert-assistant-foreground)',\n};\n\nconst AlertIcon = {\n info: (\n <InfoCircleFilled\n style={{ ...ICON_STYLE, color: ICON_COLOR.info }}\n />\n ),\n success: (\n <CheckCircleFilled\n style={{ ...ICON_STYLE, color: ICON_COLOR.success }}\n />\n ),\n error: (\n <ErrorCircleFilled\n style={{ ...ICON_STYLE, color: ICON_COLOR.error }}\n />\n ),\n warning: (\n <WarningCircleFilled\n style={{ ...ICON_STYLE, color: ICON_COLOR.warning }}\n />\n ),\n // The same sparkle the control that produced the message carries, so the two read as\n // one thread rather than two unrelated affordances.\n assistant: (\n <SparkleIcon\n style={{ ...ICON_STYLE, color: ICON_COLOR.assistant }}\n />\n ),\n};\n\nconst BANNER_ICON_STYLE = { ...ICON_STYLE, width: '16px', height: '16px' };\n\nconst statusIcon = (type: AlertType, icon: React.ReactNode, banner = false) => {\n if (!icon && !banner) return AlertIcon[type];\n const style = banner ? BANNER_ICON_STYLE : ICON_STYLE;\n return (\n <span\n className=\"alert-status-icon inline-flex shrink-0 [&_svg]:size-full!\"\n style={{ ...style, color: ICON_COLOR[type] }}\n >\n {icon || AlertIcon[type]}\n </span>\n );\n};\n\nconst SimpleTextRenderer: React.FC<{\n text: string;\n type: AlertType;\n lead?: string;\n renderLink?: RenderLink;\n}> = ({ text, type, lead, renderLink }) => {\n if (lead) {\n return (\n <div className=\"alert-lead\">\n <strong\n style={{\n color: getTextColor(getAlertTextColor(type)),\n fontSize: getFontSize('small'),\n fontWeight: 600,\n }}\n >\n {lead}\n </strong>{' '}\n <SimpleTextRenderer text={text} type={type} renderLink={renderLink} />\n </div>\n );\n }\n return (\n <MarkdownBlock\n text={text}\n color={getAlertTextColor(type)}\n size=\"small\"\n omitMargin\n renderLink={renderLink}\n />\n );\n};\n\nconst Alert: React.FC<AlertProps> = ({\n type = 'info',\n message,\n hasMargin = true,\n className,\n children,\n isExpanded,\n onToggle,\n action,\n icon,\n live = false,\n renderLink,\n layout = 'inline',\n lead,\n}) => {\n const isExpandable = !!children;\n const banner = layout === 'banner' && !isExpandable;\n const urgent = type === 'error' || type === 'warning';\n const liveRole = live ? (urgent ? 'alert' : 'status') : undefined;\n const [internalExpanded, setInternalExpanded] = useState(false);\n\n // Use controlled state if isExpanded is provided, otherwise use internal state\n const expanded = isExpanded !== undefined ? isExpanded : internalExpanded;\n\n const handleToggle = () => {\n const newExpanded = !expanded;\n if (isExpanded === undefined) {\n // Uncontrolled mode\n setInternalExpanded(newExpanded);\n }\n // Call onToggle if provided (for both controlled and uncontrolled)\n onToggle?.(newExpanded);\n };\n\n const alertClasses = [\n 'alert-base',\n `alert-${type}`,\n 'flex',\n 'w-full', // Ensure full width\n banner\n ? 'alert-banner'\n : isExpandable && expanded\n ? 'flex-col'\n : 'gap-2 items-center min-h-[2rem]',\n !hasMargin ? 'alert-no-margin' : '',\n 'text-left',\n banner ? '' : isExpandable ? 'rounded-xl !p-0' : '!px-2.5 !py-1.5', // Slightly more vertical padding for centering\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const headerClasses = [\n 'flex items-center justify-between w-full',\n isExpandable ? 'gap-2 px-2.5 py-1' : 'gap-2',\n isExpandable && expanded ? 'rounded-t-xl' : isExpandable ? 'rounded-xl' : '',\n ]\n .filter(Boolean)\n .join(' ');\n\n const expandableContentClasses = ['w-full', 'overflow-hidden'].filter(Boolean).join(' ');\n\n // Non-expandable alert (original behavior)\n if (!isExpandable) {\n return (\n <div className={alertClasses}>\n {statusIcon(type, icon, banner)}\n <div className=\"flex-1 flex items-center\">\n <div className=\"[&>div]:mb-0\" role={liveRole}>\n <SimpleTextRenderer\n text={message}\n type={type}\n lead={lead}\n renderLink={renderLink}\n />\n </div>\n </div>\n {action && <div className=\"alert-action shrink-0 flex items-center\">{action}</div>}\n </div>\n );\n }\n\n // Expandable alert\n return (\n <div className={`${alertClasses} alert-expandable`}>\n {/* Header section with title and expand/collapse button */}\n <div className={headerClasses}>\n <div className=\"flex-1 flex gap-2 items-center\">\n {statusIcon(type, icon)}\n <div className=\"flex-1 flex items-center\">\n <div className=\"[&>div]:mb-0\" role={liveRole}>\n <SimpleTextRenderer\n text={message}\n lead={lead}\n type={type}\n renderLink={renderLink}\n />\n </div>\n </div>\n </div>\n\n {/* Right side buttons container */}\n <div className=\"flex items-center gap-2\">\n {action && <div className=\"alert-action flex items-center\">{action}</div>}\n {/* Expand/collapse button */}\n <Button\n variant=\"no-style\"\n size=\"xsmall\"\n onClick={handleToggle}\n aria-label={expanded ? 'Collapse' : 'Expand'}\n style={{\n color: getTextColor(getAlertTextColor(type)),\n }}\n >\n {expanded ? (\n <CaretUpIcon\n style={{ minWidth: '13px', minHeight: '13px' }}\n />\n ) : (\n <CaretDownIcon\n style={{ minWidth: '13px', minHeight: '13px' }}\n />\n )}\n </Button>\n </div>\n </div>\n\n {/* Expandable content */}\n {expanded && (\n <>\n <div className={expandableContentClasses}>\n <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\">\n {children}\n </div>\n </div>\n </>\n )}\n </div>\n );\n};\n\nexport default Alert;\nexport { Alert };\nexport type { AlertButtonConfig };\n"],"names":["getAlertTextColor","alertType","ICON_STYLE","ICON_COLOR","AlertIcon","jsx","InfoCircleFilled","CheckCircleFilled","ErrorCircleFilled","WarningCircleFilled","SparkleIcon","BANNER_ICON_STYLE","statusIcon","type","icon","banner","SimpleTextRenderer","text","lead","renderLink","jsxs","getTextColor","getFontSize","MarkdownBlock","Alert","message","hasMargin","className","children","isExpanded","onToggle","action","live","layout","isExpandable","liveRole","internalExpanded","setInternalExpanded","useState","expanded","handleToggle","newExpanded","alertClasses","headerClasses","expandableContentClasses","Button","CaretUpIcon","CaretDownIcon","Fragment"],"mappings":"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"}