@stackone/malachite 2.72.0 → 2.74.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 +7 -5
- package/dist/components/Alert.js +94 -119
- package/dist/components/Alert.js.map +1 -1
- package/dist/components/AvatarGroup.js +74 -0
- package/dist/components/AvatarGroup.js.map +1 -0
- package/dist/components/Button.js +79 -190
- package/dist/components/Button.js.map +1 -1
- package/dist/components/ButtonSpinner.js +36 -0
- package/dist/components/ButtonSpinner.js.map +1 -0
- package/dist/components/Flex.js +14 -13
- package/dist/components/Flex.js.map +1 -1
- package/dist/components/Sidebar.js +4 -8
- package/dist/components/Sidebar.js.map +1 -1
- package/dist/components/SplitButton.js +91 -0
- package/dist/components/SplitButton.js.map +1 -0
- package/dist/components/Surface.js +68 -64
- package/dist/components/Surface.js.map +1 -1
- package/dist/components/Typography/EllipsisText.js +18 -16
- package/dist/components/Typography/EllipsisText.js.map +1 -1
- package/dist/components/buttonStyles.js +93 -0
- package/dist/components/buttonStyles.js.map +1 -0
- package/dist/components/floatingMenuGeometry.js.map +1 -1
- package/dist/index.d.ts +64 -1
- package/dist/index.js +469 -465
- package/dist/index.js.map +1 -1
- package/dist/tailwind.css.js +1 -1
- package/package.json +1 -1
package/COMPONENTS.md
CHANGED
|
@@ -19,13 +19,13 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
|
|
|
19
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
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
|
-
- **`Flex`** — Flexbox layout helper with semantic direction/align/justify/gap props. _Key props:_ `FlexDirection`, `FlexAlign`, `FlexJustify`, `FlexGapSize`.
|
|
22
|
+
- **`Flex`** — Flexbox layout helper with semantic direction/align/justify/gap props. _Key props:_ `FlexDirection`, `FlexAlign`, `FlexJustify`, `FlexGapSize`. `as` (`div` | `span`, the latter for content inside a clickable `Surface.Row`)
|
|
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`, `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.
|
|
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-end` | `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
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.
|
|
@@ -34,7 +34,7 @@ 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`, `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`.
|
|
@@ -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).
|
|
@@ -100,6 +100,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
|
|
|
100
100
|
## Buttons & actions
|
|
101
101
|
|
|
102
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
|
+
- **`SplitButton`** — One pill made of a primary action and a trailing menu trigger, sharing `Button`'s chrome: for an action with a default and a choice behind it, such as authorizing on every connected app or on one. The two segments are two real buttons, each with its own hover, active and keyboard focus, separated by a translucent seam that reads on every variant's ground; `disabled` and `loading` take both out together. The trigger shows a chevron pointing down while closed and up while `menuOpen`, which also sets `aria-expanded`, and is named by `menuLabel` since it shows no text. `trailing` sits after the label at its own size, for an `AvatarGroup` of what the action reaches. To open a `Popover` from the trigger, pass `menuTrigger`: it hands the segment to the Popover as its `trigger`, so the main segment stays a plain action rather than toggling the menu too. The menu itself is composed by the consumer from `Popover` and `Surface.Row`. _Key props:_ `variant` (every `Button` variant, default `filled`), `size` (default `medium`), `children`, `leading`, `trailing`, `onClick`, `type`, `loading`, `disabled`, `flexGrow`, `menuLabel`, `menuOpen`, `onMenuClick`, `menuIcon`, `menuHasPopup`, `menuRef`, `menuTrigger` (`(segment) => node`), `ref`, `className`, `style`.
|
|
103
104
|
- **`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
105
|
- **`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
106
|
- **`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`.
|
|
@@ -110,13 +111,14 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
|
|
|
110
111
|
## Typography
|
|
111
112
|
|
|
112
113
|
- **`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.
|
|
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.
|
|
114
|
+
- **`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. `inline` wraps the text in a span rather than a div, for inside a clickable `Surface.Row`.
|
|
114
115
|
|
|
115
116
|
## Data display
|
|
116
117
|
|
|
117
118
|
- **`Table`** — Data table with columns, sizing, pagination and row selection. `column.align` places a column's header as well as its cells. What lands on the aligned edge is the last thing in the header row rather than the title: the sort caret on a sortable column, the info icon on one carrying `info` without a sorter. `headerVariant` picks the header layout, and only `inline` lines header and body up exactly, because both are cells of one `<table>`. `detached` builds them from flex rows, where a sortable header cell is 32px narrower than a non-sortable one — its padding sits on the inner button rather than on the flex item — so its header cells sit 15–17px off their column at any alignment; reach for `inline` where exact alignment matters. Sorting is internal, so the array passed as `data` is not the order rendered once a column is sorted: `onVisibleOrderChange` reports the rows in render order — the whole of `data`, compared by `rowKey` so an equal order never re-fires — for a consumer stepping a master-detail view the way the user sees it. _Key props:_ `TableColumns` (incl. `align`), `TableSize`, `TableType`, `TableRowSelection`, `TablePaginationPosition`, `headerVariant`, `onVisibleOrderChange`.
|
|
118
119
|
- **`Statistic`** — Displays a statistic value with a label; primary/secondary sizes. _Key props:_ `StatisticValueSize`.
|
|
119
120
|
- **`Avatar`** — Logo/initial avatar. Renders the image when available, otherwise a coloured tile with the first letter of `name`, or with `fallback="placeholder"` the generic "no logo available" tile. Encapsulates the fallback colour. _Key props:_ `src`, `name`, `size`, `shape="square" | "circle"`, `fallback="initial" | "placeholder"`.
|
|
121
|
+
- **`AvatarGroup`** — A horizontal stack of overlapping `Avatar`s, first on top, for a "these apps" cluster beside a label or in a button. The gap between tiles is cut out of the tile beneath rather than painted, so it shows whatever ground the group sits on. Past `max` the rest fold into a `+N` tile. One image to assistive tech, named by its items joined with ", " unless `aria-label` says otherwise. _Key props:_ `items` (`{src?, name, fallback?}[]`), `size` (px, default 24), `shape` (`square` | `circle`), `max` (default 4), `overlap` (fraction of `size`, 0 to 0.5, default 0.3), `aria-label`, `className`, `style`.
|
|
120
122
|
- **`AppLogo`** — A connector's mark, optionally with its name and version tag. Builds on `Avatar`, so a missing or broken logo falls back to the generic "no logo available" placeholder, a neutral grey tile with a darker square, the same mark stackone-logos.com serves. The gap tightens below 18px so the mark and label still read as one unit. _Key props:_ `src`, `name`, `showName`, `size`, `nameSize`, `nameWeight`, `version`, `truncate`, `className`.
|
|
121
123
|
- **`ConnectionHeader`** — Two tilted logo tiles (from → to) showing a connection between apps. _Key props:_ `from`, `to` (`ConnectionEndpoint`: `icon`, `label`, `background`).
|
|
122
124
|
- **`CodeBlock`** — Syntax-styled code block. _Key props:_ `code`.
|
package/dist/components/Alert.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { useState as
|
|
3
|
-
import { Button as
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import { getTextColor as
|
|
13
|
-
const
|
|
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
|
-
},
|
|
41
|
-
info: /* @__PURE__ */ e(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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: { ...
|
|
78
|
-
children:
|
|
52
|
+
style: { ...l ? J : a, color: o[r] },
|
|
53
|
+
children: t || _[r]
|
|
79
54
|
}
|
|
80
|
-
),
|
|
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:
|
|
86
|
-
fontSize:
|
|
60
|
+
color: j(p(t)),
|
|
61
|
+
fontSize: G("small"),
|
|
87
62
|
fontWeight: 600
|
|
88
63
|
},
|
|
89
|
-
children:
|
|
64
|
+
children: l
|
|
90
65
|
}
|
|
91
66
|
),
|
|
92
67
|
" ",
|
|
93
|
-
/* @__PURE__ */ e(
|
|
68
|
+
/* @__PURE__ */ e(v, { text: r, type: t, renderLink: c })
|
|
94
69
|
] }) : /* @__PURE__ */ e(
|
|
95
|
-
|
|
70
|
+
q,
|
|
96
71
|
{
|
|
97
72
|
text: r,
|
|
98
|
-
color:
|
|
73
|
+
color: p(t),
|
|
99
74
|
size: "small",
|
|
100
75
|
omitMargin: !0,
|
|
101
76
|
renderLink: c
|
|
102
77
|
}
|
|
103
|
-
),
|
|
78
|
+
), ie = ({
|
|
104
79
|
type: r = "info",
|
|
105
|
-
message:
|
|
106
|
-
hasMargin:
|
|
80
|
+
message: t,
|
|
81
|
+
hasMargin: l = !0,
|
|
107
82
|
className: c,
|
|
108
|
-
children:
|
|
109
|
-
isExpanded:
|
|
110
|
-
onToggle:
|
|
111
|
-
action:
|
|
112
|
-
icon:
|
|
113
|
-
live:
|
|
114
|
-
renderLink:
|
|
115
|
-
layout:
|
|
116
|
-
lead:
|
|
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
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
},
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
"text-left",
|
|
130
|
-
|
|
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(" "),
|
|
109
|
+
].filter(Boolean).join(" "), A = [
|
|
134
110
|
"flex items-center justify-between w-full",
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
].filter(Boolean).join(" "),
|
|
138
|
-
|
|
139
|
-
/* @__PURE__ */
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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:
|
|
146
|
-
lead:
|
|
142
|
+
text: t,
|
|
143
|
+
lead: C,
|
|
147
144
|
type: r,
|
|
148
|
-
renderLink:
|
|
145
|
+
renderLink: N
|
|
149
146
|
}
|
|
150
147
|
) }) })
|
|
151
148
|
] }),
|
|
152
|
-
/* @__PURE__ */
|
|
153
|
-
|
|
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
|
-
|
|
152
|
+
H,
|
|
156
153
|
{
|
|
157
154
|
variant: "no-style",
|
|
158
155
|
size: "xsmall",
|
|
159
|
-
onClick:
|
|
160
|
-
"aria-label":
|
|
156
|
+
onClick: O,
|
|
157
|
+
"aria-label": i ? "Collapse" : "Expand",
|
|
161
158
|
style: {
|
|
162
|
-
color:
|
|
159
|
+
color: j(p(r))
|
|
163
160
|
},
|
|
164
|
-
children:
|
|
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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
170
|
+
ie as Alert,
|
|
171
|
+
ie as default
|
|
197
172
|
};
|
|
198
173
|
//# 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 { 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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"}
|
|
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 /** `center` gathers a banner's icon and text in the middle of the band, for a wide page. */\n align?: 'start' | 'center';\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: <InfoCircleFilled style={{ ...ICON_STYLE, color: ICON_COLOR.info }} />,\n success: <CheckCircleFilled style={{ ...ICON_STYLE, color: ICON_COLOR.success }} />,\n error: <ErrorCircleFilled style={{ ...ICON_STYLE, color: ICON_COLOR.error }} />,\n warning: <WarningCircleFilled style={{ ...ICON_STYLE, color: ICON_COLOR.warning }} />,\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: <SparkleIcon style={{ ...ICON_STYLE, color: ICON_COLOR.assistant }} />,\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 align = 'start',\n}) => {\n const isExpandable = !!children;\n const banner = layout === 'banner' && !isExpandable;\n const centred = banner && align === 'center';\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 centred ? 'text-center' : '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 const content = (\n <>\n {statusIcon(type, icon, banner)}\n <div className={centred ? 'flex items-center' : '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 </>\n );\n return (\n <div className={alertClasses}>\n {centred ? (\n // Its own group, so an `action` stays at the end while this sits mid-band.\n <div className=\"alert-centred flex-1 flex items-center justify-center gap-[10px]\">\n {content}\n </div>\n ) : (\n content\n )}\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 style={{ minWidth: '13px', minHeight: '13px' }} />\n ) : (\n <CaretDownIcon style={{ minWidth: '13px', minHeight: '13px' }} />\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","align","isExpandable","centred","liveRole","internalExpanded","setInternalExpanded","useState","expanded","handleToggle","newExpanded","alertClasses","headerClasses","expandableContentClasses","content","Fragment","Button","CaretUpIcon","CaretDownIcon"],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsxs as x, jsx as o } from "react/jsx-runtime";
|
|
2
|
+
import { Avatar as w } from "./Avatar.js";
|
|
3
|
+
const i = 2, p = (a, t, n) => {
|
|
4
|
+
const l = a + i * 2, c = `<svg xmlns="http://www.w3.org/2000/svg" width="${a}" height="${a}"><mask id="m"><rect width="100%" height="100%" fill="white"/><rect x="${-n - i}" y="${-i}" width="${l}" height="${l}" rx="${t + i}" fill="black"/></mask><rect width="${a}" height="${a}" fill="white" mask="url(#m)"/></svg>`;
|
|
5
|
+
return `url("data:image/svg+xml,${encodeURIComponent(c)}")`;
|
|
6
|
+
}, I = ({
|
|
7
|
+
items: a,
|
|
8
|
+
size: t = 24,
|
|
9
|
+
shape: n = "square",
|
|
10
|
+
max: l = 4,
|
|
11
|
+
overlap: c = 0.3,
|
|
12
|
+
"aria-label": $,
|
|
13
|
+
"aria-labelledby": h,
|
|
14
|
+
className: m = "",
|
|
15
|
+
style: v
|
|
16
|
+
}) => {
|
|
17
|
+
const r = a.slice(0, l), s = a.length - r.length, d = r.length + (s > 0 ? 1 : 0), g = t - Math.round(t * Math.min(Math.max(c, 0), 0.5)), u = n === "circle" ? t / 2 : Math.round(t * 0.25), f = p(t, u, g), b = (e) => e === 0 ? { zIndex: d } : {
|
|
18
|
+
zIndex: d - e,
|
|
19
|
+
marginInlineStart: g - t,
|
|
20
|
+
maskImage: f,
|
|
21
|
+
WebkitMaskImage: f
|
|
22
|
+
};
|
|
23
|
+
return /* @__PURE__ */ x(
|
|
24
|
+
"span",
|
|
25
|
+
{
|
|
26
|
+
role: "img",
|
|
27
|
+
"aria-label": h ? void 0 : $ ?? a.map((e) => e.name).join(", "),
|
|
28
|
+
"aria-labelledby": h,
|
|
29
|
+
className: `inline-flex items-center${m ? ` ${m}` : ""}`,
|
|
30
|
+
style: v,
|
|
31
|
+
children: [
|
|
32
|
+
r.map((e, k) => /* @__PURE__ */ o(
|
|
33
|
+
"span",
|
|
34
|
+
{
|
|
35
|
+
className: "relative inline-flex shrink-0",
|
|
36
|
+
style: b(k),
|
|
37
|
+
children: /* @__PURE__ */ o(
|
|
38
|
+
w,
|
|
39
|
+
{
|
|
40
|
+
src: e.src,
|
|
41
|
+
name: e.name,
|
|
42
|
+
size: t,
|
|
43
|
+
shape: n,
|
|
44
|
+
fallback: e.fallback
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
},
|
|
48
|
+
`${k}-${e.name}`
|
|
49
|
+
)),
|
|
50
|
+
s > 0 ? /* @__PURE__ */ o("span", { className: "relative inline-flex shrink-0", style: b(r.length), children: /* @__PURE__ */ x(
|
|
51
|
+
"span",
|
|
52
|
+
{
|
|
53
|
+
className: "inline-flex items-center justify-center bg-[var(--malachite-card-dimmed-background)] font-semibold text-[var(--malachite-text-secondary)] leading-none",
|
|
54
|
+
style: {
|
|
55
|
+
width: t,
|
|
56
|
+
height: t,
|
|
57
|
+
borderRadius: n === "circle" ? "50%" : u,
|
|
58
|
+
fontSize: Math.round(t * 0.4)
|
|
59
|
+
},
|
|
60
|
+
children: [
|
|
61
|
+
"+",
|
|
62
|
+
s
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
) }) : null
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
export {
|
|
71
|
+
I as AvatarGroup,
|
|
72
|
+
I as default
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=AvatarGroup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AvatarGroup.js","sources":["../../src/components/AvatarGroup.tsx"],"sourcesContent":["import type React from 'react';\nimport Avatar, { type AvatarFallback, type AvatarShape } from './Avatar';\n\nexport interface AvatarGroupItem {\n src?: string;\n name: string;\n fallback?: AvatarFallback;\n}\n\nexport interface AvatarGroupProps {\n items: AvatarGroupItem[];\n size?: number;\n shape?: AvatarShape;\n /** Tiles shown before a `+N` tile takes the rest. */\n max?: number;\n /** Fraction of `size` (0 to 0.5) each tile sits under the one before it. */\n overlap?: number;\n /** Names the group; otherwise it is named by the item names joined with \", \". */\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n className?: string;\n style?: React.CSSProperties;\n}\n\nconst GAP_PX = 2;\n\n// The gap is cut out of the lower tile, so the ground shows through; CSS masks an image by its\n// alpha, so the cut is transparent in the SVG itself.\nconst cutoutMask = (size: number, radius: number, shift: number): string => {\n const cut = size + GAP_PX * 2;\n const svg =\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\">` +\n '<mask id=\"m\"><rect width=\"100%\" height=\"100%\" fill=\"white\"/>' +\n `<rect x=\"${-shift - GAP_PX}\" y=\"${-GAP_PX}\" width=\"${cut}\" height=\"${cut}\" rx=\"${radius + GAP_PX}\" fill=\"black\"/></mask>` +\n `<rect width=\"${size}\" height=\"${size}\" fill=\"white\" mask=\"url(#m)\"/>` +\n '</svg>';\n return `url(\"data:image/svg+xml,${encodeURIComponent(svg)}\")`;\n};\n\n/** Overlapping `Avatar`s, first on top; one `img` to assistive tech, named by its items. */\nconst AvatarGroup: React.FC<AvatarGroupProps> = ({\n items,\n size = 24,\n shape = 'square',\n max = 4,\n overlap = 0.3,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n className = '',\n style,\n}) => {\n const visible = items.slice(0, max);\n const hidden = items.length - visible.length;\n const tileCount = visible.length + (hidden > 0 ? 1 : 0);\n const shift = size - Math.round(size * Math.min(Math.max(overlap, 0), 0.5));\n const radius = shape === 'circle' ? size / 2 : Math.round(size * 0.25);\n const mask = cutoutMask(size, radius, shift);\n\n const tileStyle = (index: number): React.CSSProperties =>\n index === 0\n ? { zIndex: tileCount }\n : {\n zIndex: tileCount - index,\n marginInlineStart: shift - size,\n maskImage: mask,\n WebkitMaskImage: mask,\n };\n\n return (\n <span\n role=\"img\"\n aria-label={\n ariaLabelledBy\n ? undefined\n : (ariaLabel ?? items.map((item) => item.name).join(', '))\n }\n aria-labelledby={ariaLabelledBy}\n className={`inline-flex items-center${className ? ` ${className}` : ''}`}\n style={style}\n >\n {visible.map((item, index) => (\n <span\n key={`${index}-${item.name}`}\n className=\"relative inline-flex shrink-0\"\n style={tileStyle(index)}\n >\n <Avatar\n src={item.src}\n name={item.name}\n size={size}\n shape={shape}\n fallback={item.fallback}\n />\n </span>\n ))}\n {hidden > 0 ? (\n <span className=\"relative inline-flex shrink-0\" style={tileStyle(visible.length)}>\n <span\n className=\"inline-flex items-center justify-center bg-[var(--malachite-card-dimmed-background)] font-semibold text-[var(--malachite-text-secondary)] leading-none\"\n style={{\n width: size,\n height: size,\n borderRadius: shape === 'circle' ? '50%' : radius,\n fontSize: Math.round(size * 0.4),\n }}\n >\n +{hidden}\n </span>\n </span>\n ) : null}\n </span>\n );\n};\n\nexport default AvatarGroup;\nexport { AvatarGroup };\n"],"names":["GAP_PX","cutoutMask","size","radius","shift","cut","svg","AvatarGroup","items","shape","max","overlap","ariaLabel","ariaLabelledBy","className","style","visible","hidden","tileCount","mask","tileStyle","index","jsxs","item","jsx","Avatar"],"mappings":";;AAwBA,MAAMA,IAAS,GAITC,IAAa,CAACC,GAAcC,GAAgBC,MAA0B;AACxE,QAAMC,IAAMH,IAAOF,IAAS,GACtBM,IACF,kDAAkDJ,CAAI,aAAaA,CAAI,0EAE3D,CAACE,IAAQJ,CAAM,QAAQ,CAACA,CAAM,YAAYK,CAAG,aAAaA,CAAG,SAASF,IAASH,CAAM,uCACjFE,CAAI,aAAaA,CAAI;AAEzC,SAAO,2BAA2B,mBAAmBI,CAAG,CAAC;AAC7D,GAGMC,IAA0C,CAAC;AAAA,EAC7C,OAAAC;AAAA,EACA,MAAAN,IAAO;AAAA,EACP,OAAAO,IAAQ;AAAA,EACR,KAAAC,IAAM;AAAA,EACN,SAAAC,IAAU;AAAA,EACV,cAAcC;AAAA,EACd,mBAAmBC;AAAA,EACnB,WAAAC,IAAY;AAAA,EACZ,OAAAC;AACJ,MAAM;AACF,QAAMC,IAAUR,EAAM,MAAM,GAAGE,CAAG,GAC5BO,IAAST,EAAM,SAASQ,EAAQ,QAChCE,IAAYF,EAAQ,UAAUC,IAAS,IAAI,IAAI,IAC/Cb,IAAQF,IAAO,KAAK,MAAMA,IAAO,KAAK,IAAI,KAAK,IAAIS,GAAS,CAAC,GAAG,GAAG,CAAC,GACpER,IAASM,MAAU,WAAWP,IAAO,IAAI,KAAK,MAAMA,IAAO,IAAI,GAC/DiB,IAAOlB,EAAWC,GAAMC,GAAQC,CAAK,GAErCgB,IAAY,CAACC,MACfA,MAAU,IACJ,EAAE,QAAQH,MACV;AAAA,IACI,QAAQA,IAAYG;AAAA,IACpB,mBAAmBjB,IAAQF;AAAA,IAC3B,WAAWiB;AAAA,IACX,iBAAiBA;AAAA,EAAA;AAG/B,SACI,gBAAAG;AAAA,IAAC;AAAA,IAAA;AAAA,MACG,MAAK;AAAA,MACL,cACIT,IACM,SACCD,KAAaJ,EAAM,IAAI,CAACe,MAASA,EAAK,IAAI,EAAE,KAAK,IAAI;AAAA,MAEhE,mBAAiBV;AAAA,MACjB,WAAW,2BAA2BC,IAAY,IAAIA,CAAS,KAAK,EAAE;AAAA,MACtE,OAAAC;AAAA,MAEC,UAAA;AAAA,QAAAC,EAAQ,IAAI,CAACO,GAAMF,MAChB,gBAAAG;AAAA,UAAC;AAAA,UAAA;AAAA,YAEG,WAAU;AAAA,YACV,OAAOJ,EAAUC,CAAK;AAAA,YAEtB,UAAA,gBAAAG;AAAA,cAACC;AAAA,cAAA;AAAA,gBACG,KAAKF,EAAK;AAAA,gBACV,MAAMA,EAAK;AAAA,gBACX,MAAArB;AAAA,gBACA,OAAAO;AAAA,gBACA,UAAUc,EAAK;AAAA,cAAA;AAAA,YAAA;AAAA,UACnB;AAAA,UAVK,GAAGF,CAAK,IAAIE,EAAK,IAAI;AAAA,QAAA,CAYjC;AAAA,QACAN,IAAS,IACN,gBAAAO,EAAC,QAAA,EAAK,WAAU,iCAAgC,OAAOJ,EAAUJ,EAAQ,MAAM,GAC3E,UAAA,gBAAAM;AAAA,UAAC;AAAA,UAAA;AAAA,YACG,WAAU;AAAA,YACV,OAAO;AAAA,cACH,OAAOpB;AAAA,cACP,QAAQA;AAAA,cACR,cAAcO,MAAU,WAAW,QAAQN;AAAA,cAC3C,UAAU,KAAK,MAAMD,IAAO,GAAG;AAAA,YAAA;AAAA,YAEtC,UAAA;AAAA,cAAA;AAAA,cACKe;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA,GAEV,IACA;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGhB;"}
|