@stackone/malachite 2.33.2 → 2.35.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 +5 -5
- package/dist/components/Alert.js +77 -72
- package/dist/components/Alert.js.map +1 -1
- package/dist/components/Button.js +27 -30
- package/dist/components/Button.js.map +1 -1
- package/dist/components/Dropdown.js +254 -236
- package/dist/components/Dropdown.js.map +1 -1
- package/dist/components/Input/Input.js +47 -46
- package/dist/components/Input/Input.js.map +1 -1
- package/dist/components/PillButton.js +52 -33
- package/dist/components/PillButton.js.map +1 -1
- package/dist/components/Surface.js +100 -94
- package/dist/components/Surface.js.map +1 -1
- package/dist/components/Typography/MarkdownBlock.js +146 -119
- package/dist/components/Typography/MarkdownBlock.js.map +1 -1
- package/dist/components/Typography/index.js.map +1 -1
- package/dist/components/Typography/inlineMarkdown.js +23 -0
- package/dist/components/Typography/inlineMarkdown.js.map +1 -0
- package/dist/components/linkRel.js +9 -0
- package/dist/components/linkRel.js.map +1 -0
- package/dist/index.d.ts +16 -3
- package/dist/tailwind.css.js +1 -1
- package/package.json +2 -1
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`). Use `trailing` for a control that must stay interactive — it renders outside the row button, which a nested control could not be. `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`, `selected`, `selection` (`highlight` | `announce`), `trailing`, `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`). Use `trailing` for a control that must stay interactive — it renders outside the row button, which a nested control could not be. `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`, `selection` (`highlight` | `announce`), `trailing`, `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.
|
|
@@ -33,7 +33,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
|
|
|
33
33
|
## Feedback & status
|
|
34
34
|
|
|
35
35
|
- **`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.
|
|
36
|
-
- **`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.
|
|
36
|
+
- **`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. _Key props:_ `type`, `message`, `action`, `hasMargin`, `children` (expandable body), `isExpanded`, `onToggle`, `className`.
|
|
37
37
|
- **`StatusBadge`** — Large status indicator (e.g. success checkmark) for terminal/confirmation screens. _Key props:_ `type`.
|
|
38
38
|
- **`StatBadge`** — Pill showing a numeric value with a label (e.g. "3 accounts selected"). _Key props:_ `count`, `label`.
|
|
39
39
|
- **`Tag`** — Small status/label tag. Types: success, error, warning, default. _Key props:_ `value`, `type`, `paddingSize`, `textTransform`, `WidthSize`.
|
|
@@ -80,7 +80,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
|
|
|
80
80
|
- **`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`.
|
|
81
81
|
- **`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. Restyle via the `segmentedControl` theme tokens. _Key props:_ `options` (`value`, `label`, `icon`, `disabled`), `value`, `onChange`, `SegmentedControlSize` (`small` | `medium` | `large`), `fullWidth`, `disabled`, `aria-label`, `aria-labelledby`, `className`.
|
|
82
82
|
- **`Toggle`** — On/off switch. Add `label` for a visible caption, and `variant` to give it standalone toolbar-filter chrome — pick the family it sits beside: `pill` matches the filter-bar inputs (`Search`, `MultiSelect`), `button` renders the whole control as an outline `Button` with the switch as its trailing icon. `bordered` is shorthand for `variant="pill"`. `tone="pending"` paints the checked track amber, for a switch whose change has not been written yet — pair it with a `SelectionBar` reporting the same diff. The unchecked track never changes: nothing is pending about a switch that is off. Forwards a ref to its root element, so it can anchor an `InlineConfirm`. _Key props:_ `checked`, `onChange`, `disabled`, `ToggleSize` (`small` | `medium`), `ToggleTone` (`default` | `pending`), `label`, `ToggleVariant` (`pill` | `button`), `bordered`, `aria-label`, `aria-labelledby`, `aria-describedby`, `id`.
|
|
83
|
-
- **`Dropdown`** — Single-select dropdown. `DropdownSize` is `small` (28px) | `medium` (32px) | `large` (42px), matching Button; `fullWidth` fills the container instead of sizing to content, which is what a table cell or a toolbar slot needs — without it the field column sizes to its content, so a consumer has to compute a width; `width` fixes that width outright, the way `MultiSelect` does, for a toolbar lining the two up rather than letting each hug its own label — a narrower container still wins, since the width clamps to it; Until this release a `search` trigger could not go below the intrinsic width of its bare `input`, so a `width` under roughly 200px was ignored; it now shrinks with the rest. The open menu fills the trigger and left-aligns under it, as Figma draws every tier of `Dropdown Component - Standard`, so `width` sizes both — the menu is the width of the control that opened it and does not widen for a long option, which ellipsises instead, so a row of controls and their open menus stay on one set of edges; below 160px the menu keeps that floor rather than shrinking to a chip nobody can read, and `minWidth` raises that floor without widening the trigger — for a picker a column pins narrow whose options carry a subtitle the trigger never shows; near a viewport edge it anchors to the trigger's other side before falling back to the viewport, so the two share an edge wherever one fits; `small` is the chip that sits beside a page title and rounds tighter (10px) than the taller sizes. These names each moved up a tier in 0.71 — the old `xsmall`/`small`/`medium` are now `small`/`medium`/`large`. Pass `value` for a controlled selection whose source of truth lives elsewhere (URL or app state): the trigger follows it without the consumer remounting via `key`, which used to discard focus mid-interaction. `defaultValue` keeps the uncontrolled behaviour. `clearable` adds a clear control to the trigger once something is selected and reports it as `onItemSelected(undefined)` — without it a selection cannot be undone, so filter bars had to invent a sentinel "any" item to carry that meaning. `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. `showSubtitleInTrigger` promotes the selected item's `subtitle` into the closed trigger, on one line under the value, so the consequence of a choice stays on screen once the menu shuts — the trigger trades its fixed height for a floor and grows by that line, and either line too long for it ellipsises, with a single tooltip carrying whichever lines are cut off, value first. It does not combine with `search`, whose trigger is a single-line text input with nowhere to put a second line, so the type rejects the pair rather than letting a consumer ask for a subtitle and get none. `subtitleLayout` decides how a subtitle sits in the open menu: the default `inline` keeps it on the label's row after a divider, which suits a short qualifier, while `stacked` gives it its own line beneath the label — one line either way, ellipsised rather than wrapped, so inline a sentence-length subtitle is cut back to a few words. Every option row carries a tooltip for whatever it had to cut, across all three of label, subtitle and `rightSideText`, so nothing the row hides is unreadable. Passing `formControl` is enough to show validation on the same terms as `Input`, though only validation: the trigger still paints `value` or `defaultValue`, never the value the form holds. 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, while an explicit `helperText` keeps its own line above it and stays dimmed, and `error={false}` suppresses the resolver outright. _Key props:_ `DropdownItem[]`, `DropdownSize`, `fullWidth`, `width`, `value`, `defaultValue`, `label`, `search`, `showSubtitleInTrigger`, `subtitleLayout`, `clearable`, `required`, `requiredIndicator`, `error`, `helperText`, `loading`, `formControl`, `aria-label`, `aria-labelledby`, `id`.
|
|
83
|
+
- **`Dropdown`** — Single-select dropdown. `DropdownSize` is `small` (28px) | `medium` (32px) | `large` (42px), matching Button; `fullWidth` fills the container instead of sizing to content, which is what a table cell or a toolbar slot needs — without it the field column sizes to its content, so a consumer has to compute a width; `width` fixes that width outright, the way `MultiSelect` does, for a toolbar lining the two up rather than letting each hug its own label — a narrower container still wins, since the width clamps to it; Until this release a `search` trigger could not go below the intrinsic width of its bare `input`, so a `width` under roughly 200px was ignored; it now shrinks with the rest. The open menu fills the trigger and left-aligns under it, as Figma draws every tier of `Dropdown Component - Standard`, so `width` sizes both — the menu is the width of the control that opened it and does not widen for a long option, which ellipsises instead, so a row of controls and their open menus stay on one set of edges; below 160px the menu keeps that floor rather than shrinking to a chip nobody can read, and `minWidth` raises that floor without widening the trigger — for a picker a column pins narrow whose options carry a subtitle the trigger never shows; near a viewport edge it anchors to the trigger's other side before falling back to the viewport, so the two share an edge wherever one fits; `small` is the chip that sits beside a page title and rounds tighter (10px) than the taller sizes. These names each moved up a tier in 0.71 — the old `xsmall`/`small`/`medium` are now `small`/`medium`/`large`. Pass `value` for a controlled selection whose source of truth lives elsewhere (URL or app state): the trigger follows it without the consumer remounting via `key`, which used to discard focus mid-interaction. `defaultValue` keeps the uncontrolled behaviour. `clearable` adds a clear control to the trigger once something is selected and reports it as `onItemSelected(undefined)` — without it a selection cannot be undone, so filter bars had to invent a sentinel "any" item to carry that meaning. `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. `showSubtitleInTrigger` promotes the selected item's `subtitle` into the closed trigger, on one line under the value, so the consequence of a choice stays on screen once the menu shuts — the trigger trades its fixed height for a floor and grows by that line, and either line too long for it ellipsises, with a single tooltip carrying whichever lines are cut off, value first. It does not combine with `search`, whose trigger is a single-line text input with nowhere to put a second line, so the type rejects the pair rather than letting a consumer ask for a subtitle and get none. `subtitleLayout` decides how a subtitle sits in the open menu: the default `inline` keeps it on the label's row after a divider, which suits a short qualifier, while `stacked` gives it its own line beneath the label — one line either way, ellipsised rather than wrapped, so inline a sentence-length subtitle is cut back to a few words. Every option row carries a tooltip for whatever it had to cut, across all three of label, subtitle and `rightSideText`, so nothing the row hides is unreadable. Passing `formControl` is enough to show validation on the same terms as `Input`, though only validation: the trigger still paints `value` or `defaultValue`, never the value the form holds. 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, while an explicit `helperText` keeps its own line above it and stays dimmed, and `error={false}` suppresses the resolver outright. An item's `subtitle` renders inline markdown in the option, trigger and tooltip, with links drawn but inert, since the option is itself the click target; a subtitle with no markdown syntax stays a plain text node. _Key props:_ `DropdownItem[]`, `DropdownSize`, `fullWidth`, `width`, `value`, `defaultValue`, `label`, `search`, `showSubtitleInTrigger`, `subtitleLayout`, `clearable`, `required`, `requiredIndicator`, `error`, `helperText`, `loading`, `formControl`, `aria-label`, `aria-labelledby`, `id`.
|
|
84
84
|
- **`MultiSelect`** — Multi-select dropdown. `width` fixes the trigger, as `Dropdown` and `Search` take it, so a toolbar can put its controls on one width — and, as with those two, a container narrower than the value still wins; the menu fills the trigger and left-aligns under it, so that one prop sizes both; `minWidth` raises the 160px floor a narrow trigger falls back to. An option label too long for the menu ellipsises, and hovering it shows the full label — above the description in `hover` mode — as `Dropdown` does for its options. `requiredIndicator` follows the same three-way convention the single-select controls do, so a form choosing asterisks does not get "(Optional)" on one of its fields. _Key props:_ `MultiSelectOption[]`, `MultiSelectSize`, `width`, `minWidth`, `label`, `searchable`, `inlineSearch`, `chips`, `chipsRemovable`, `required`, `requiredIndicator`, `aria-label`, `aria-labelledby`, `id`.
|
|
85
85
|
- **`QueryInput`** — A textbox with inline syntax colouring and a suggestion menu anchored to the caret. It carries no grammar of its own: the consumer parses the value, hands back `tokens` to colour and `suggestions` to offer, so the vocabulary lives in the consuming app rather than here. _Key props:_ `value`, `onChange`, `tokens`, `suggestions`, `activeRange`, `onAccept`, `onQueryContextChange`, `onSubmit`, `placeholder`, `size`, `disabled`, `loading`, `emptyMessage`, `className`, `style`, `aria-label`, `aria-labelledby`.
|
|
86
86
|
- **`SettingRow`** — One setting: a labelled row with its control on the trailing edge, and the sentence that explains it underneath. `control` may be a render prop receiving the label’s id, so a control that no `<label htmlFor>` can reach — `Toggle` renders a `<button role="switch">` — still names itself via `aria-labelledby`. _Key props:_ `label`, `badge`, `description`, `control`, `className`.
|
|
@@ -88,7 +88,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
|
|
|
88
88
|
## Buttons & actions
|
|
89
89
|
|
|
90
90
|
- **`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`. _Key props:_ `variant`, `size`, `icon`, `iconOnly`, `loading`, `disabled`, `fill`, `tooltip`.
|
|
91
|
-
- **`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. _Key props:_ `label`, `icon`, `trailingIcon`, `selected`, `disabled`, `interactive`, `size`, `variant`, `shape` (`pill` | `tab`), `onClick`.
|
|
91
|
+
- **`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`.
|
|
92
92
|
- **`Link`** — Styled anchor/link text. _Key props:_ `LinkProps`.
|
|
93
93
|
- **`InlineConfirm`** — A confirmation asked on the control that triggered it rather than in a modal, for a reversible bulk edit where a dialog would be heavier than the change it guards. Drawn to the design's chip: 33px tall, 12px radius, its confirm cell running to the pill's own edge between two rules. Reads its own `inlineConfirm` theme tokens — a shade off the `SelectionBar` it appears beside, by design. Carries a spinning glyph by default — its own, so it takes the pill's colour rather than the sheet's dimmed grey — standing for a change waiting on an answer; pass `icon={null}` to drop it or a node to replace it. Anchored placement is in viewport coordinates, so the chip must not sit inside an ancestor carrying `transform`, `filter` or `contain`. It takes focus on open so `Enter` confirms, dismisses on `Escape` (through the shared escape-layer stack, so the topmost overlay answers) or a click outside, and returns focus to whatever opened it. `role="alertdialog"` named by the message itself, so the confirm button is never announced bare. Only one can be open at a time: opening a second dismisses the first, so it is never ambiguous what Yes answers. Pass `anchorTo` (a ref to the control it answers for) and it pins itself to that control's left, follows it while the page or a sticky header scrolls, dismisses once it scrolls out of view, and treats clicks on it as inside — without which the same click would dismiss and reopen. Left unanchored it renders in flow for the consumer to place, and must then not sit in a container that clips overflow (`Table` header cells among them). Nothing renders while `open` is false, and its listeners are attached only while it is open. _Key props:_ `open`, `message`, `confirmLabel`, `onConfirm`, `onDismiss`, `anchorTo`, `icon`, `className`, `style`.
|
|
94
94
|
- **`SelectionBar`** — A bar reporting a pending selection and the actions that resolve it, for flows where changes accumulate and save once rather than writing on every toggle. Paints its own inverted surface from the `selectionBar` theme tokens, so its buttons are its own rather than `Button`. A count renders as one phrase (`3 Added`), not a number styled apart from its label, and an empty `value` carries a bare message instead; the separators between counts and before an action are glyphs (`•`, `|`) in the muted colour, not drawn rules. `emphasis` runs `primary` (a light filled button with its own edge, for the action that commits) → `secondary`, the default (a filled chip) → `quiet` (unfilled, taking a wash on hover). The pill is edgeless in light mode and keeps a hairline `border` token in dark, where a shadow alone would not separate it from the sheet. Defaults to `placement="sticky"` — render it as the last child of the scrolling content and it holds its place over a long list. Counts are announced through a live region that outlives the bar, so the first change is not lost; `open` drives an animated mount so it can appear and leave as the selection goes dirty and clean. Every action closes the bar, so the caller must move focus deliberately in `onClick`. `notice` wraps the whole bar in a warning sleeve carrying that message, for state the bar is reporting rather than an action it offers — a refused exit, say — and it leads the announcement; an empty string renders no sleeve. _Key props:_ `open`, `counts` (`value`, `label`), `actions` (`label`, `onClick`, `emphasis`, `icon`, `iconPosition`, `disabled`, `dividerBefore`), `placement`, `notice`, `aria-label`, `className`.
|
|
@@ -96,7 +96,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
|
|
|
96
96
|
## Typography
|
|
97
97
|
|
|
98
98
|
- **`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.
|
|
99
|
-
- **`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"`; `Typography.CodeText` takes `variant="inline"` for a chip mid-sentence; `Typography.MarkdownBlock` takes `variant="flow"` for use inside `ClampText` and `normalizeSource` for machine-generated descriptions that omit blank lines between blocks. `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.
|
|
99
|
+
- **`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"`; `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, and `normalizeSource` for machine-generated descriptions that omit blank lines between blocks. `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.
|
|
100
100
|
|
|
101
101
|
## Data display
|
|
102
102
|
|
package/dist/components/Alert.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { useState as
|
|
1
|
+
import { jsxs as a, jsx as e, Fragment as y } from "react/jsx-runtime";
|
|
2
|
+
import { useState as k } from "react";
|
|
3
3
|
import { Button as I } from "./Button.js";
|
|
4
4
|
import j from "./CustomIcons/CaretDownIcon.js";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import { getTextColor as
|
|
13
|
-
const
|
|
5
|
+
import B from "./CustomIcons/CaretUpIcon.js";
|
|
6
|
+
import E from "./CustomIcons/CheckCircleFilled.js";
|
|
7
|
+
import F from "./CustomIcons/ErrorCircleFilled.js";
|
|
8
|
+
import S from "./CustomIcons/InfoCircleFilled.js";
|
|
9
|
+
import _ from "./CustomIcons/SparkleIcon.js";
|
|
10
|
+
import A from "./CustomIcons/WarningCircleFilled.js";
|
|
11
|
+
import W from "./Typography/MarkdownBlock.js";
|
|
12
|
+
import { getTextColor as z } from "./Typography/textStyles.js";
|
|
13
|
+
const h = (r) => {
|
|
14
14
|
switch (r) {
|
|
15
15
|
case "info":
|
|
16
16
|
return "primary";
|
|
@@ -27,110 +27,115 @@ const p = (r) => {
|
|
|
27
27
|
default:
|
|
28
28
|
return "primary";
|
|
29
29
|
}
|
|
30
|
-
},
|
|
30
|
+
}, i = {
|
|
31
31
|
width: "13px",
|
|
32
32
|
height: "13px",
|
|
33
33
|
flexShrink: 0
|
|
34
|
-
},
|
|
34
|
+
}, x = {
|
|
35
35
|
info: /* @__PURE__ */ e(
|
|
36
|
-
|
|
36
|
+
S,
|
|
37
37
|
{
|
|
38
|
-
style: { ...
|
|
38
|
+
style: { ...i, color: "var(--malachite-alert-info-foreground)" }
|
|
39
39
|
}
|
|
40
40
|
),
|
|
41
41
|
success: /* @__PURE__ */ e(
|
|
42
|
-
|
|
42
|
+
E,
|
|
43
43
|
{
|
|
44
|
-
style: { ...
|
|
44
|
+
style: { ...i, color: "var(--malachite-alert-success-foreground)" }
|
|
45
45
|
}
|
|
46
46
|
),
|
|
47
47
|
error: /* @__PURE__ */ e(
|
|
48
|
-
|
|
48
|
+
F,
|
|
49
49
|
{
|
|
50
|
-
style: { ...
|
|
50
|
+
style: { ...i, color: "var(--malachite-alert-error-foreground)" }
|
|
51
51
|
}
|
|
52
52
|
),
|
|
53
53
|
warning: /* @__PURE__ */ e(
|
|
54
|
-
|
|
54
|
+
A,
|
|
55
55
|
{
|
|
56
|
-
style: { ...
|
|
56
|
+
style: { ...i, color: "var(--malachite-alert-warning-foreground)" }
|
|
57
57
|
}
|
|
58
58
|
),
|
|
59
59
|
// The same sparkle the control that produced the message carries, so the two read as
|
|
60
60
|
// one thread rather than two unrelated affordances.
|
|
61
61
|
assistant: /* @__PURE__ */ e(
|
|
62
|
-
|
|
62
|
+
_,
|
|
63
63
|
{
|
|
64
|
-
style: { ...
|
|
64
|
+
style: { ...i, color: "var(--malachite-alert-assistant-foreground)" }
|
|
65
65
|
}
|
|
66
66
|
)
|
|
67
|
-
},
|
|
67
|
+
}, p = ({ text: r, type: n }) => /* @__PURE__ */ e(W, { text: r, color: h(n), size: "small", omitMargin: !0 }), J = ({
|
|
68
68
|
type: r = "info",
|
|
69
|
-
message:
|
|
70
|
-
hasMargin:
|
|
71
|
-
className:
|
|
72
|
-
children:
|
|
73
|
-
isExpanded:
|
|
74
|
-
onToggle:
|
|
69
|
+
message: n,
|
|
70
|
+
hasMargin: u = !0,
|
|
71
|
+
className: v,
|
|
72
|
+
children: d,
|
|
73
|
+
isExpanded: s,
|
|
74
|
+
onToggle: c,
|
|
75
|
+
action: o
|
|
75
76
|
}) => {
|
|
76
|
-
const l = !!
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
},
|
|
77
|
+
const l = !!d, [g, w] = k(!1), t = s !== void 0 ? s : g, C = () => {
|
|
78
|
+
const f = !t;
|
|
79
|
+
s === void 0 && w(f), c == null || c(f);
|
|
80
|
+
}, m = [
|
|
80
81
|
"alert-base",
|
|
81
82
|
`alert-${r}`,
|
|
82
83
|
"flex",
|
|
83
84
|
"w-full",
|
|
84
85
|
// Ensure full width
|
|
85
86
|
l && t ? "flex-col" : "gap-2 items-center min-h-[2rem]",
|
|
86
|
-
|
|
87
|
+
u ? "" : "alert-no-margin",
|
|
87
88
|
"text-left",
|
|
88
89
|
l ? "rounded-xl !p-0" : "!px-2.5 !py-1.5",
|
|
89
90
|
// Slightly more vertical padding for centering
|
|
90
|
-
|
|
91
|
-
].filter(Boolean).join(" "),
|
|
91
|
+
v
|
|
92
|
+
].filter(Boolean).join(" "), b = [
|
|
92
93
|
"flex items-center justify-between w-full",
|
|
93
94
|
l ? "gap-2 px-2.5 py-1" : "gap-2",
|
|
94
95
|
l && t ? "rounded-t-xl" : l ? "rounded-xl" : ""
|
|
95
|
-
].filter(Boolean).join(" "),
|
|
96
|
-
return l ? /* @__PURE__ */
|
|
97
|
-
/* @__PURE__ */
|
|
98
|
-
/* @__PURE__ */
|
|
99
|
-
|
|
100
|
-
/* @__PURE__ */ e("div", { className: "flex-1 flex items-center", children: /* @__PURE__ */ e("div", { className: "[&>div]:mb-0", children: /* @__PURE__ */ e(
|
|
96
|
+
].filter(Boolean).join(" "), N = ["w-full", "overflow-hidden"].filter(Boolean).join(" ");
|
|
97
|
+
return l ? /* @__PURE__ */ a("div", { className: `${m} alert-expandable`, children: [
|
|
98
|
+
/* @__PURE__ */ a("div", { className: b, children: [
|
|
99
|
+
/* @__PURE__ */ a("div", { className: "flex-1 flex gap-2 items-center", children: [
|
|
100
|
+
x[r],
|
|
101
|
+
/* @__PURE__ */ e("div", { className: "flex-1 flex items-center", children: /* @__PURE__ */ e("div", { className: "[&>div]:mb-0", children: /* @__PURE__ */ e(p, { text: n, type: r }) }) })
|
|
101
102
|
] }),
|
|
102
|
-
/* @__PURE__ */
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
103
|
+
/* @__PURE__ */ a("div", { className: "flex items-center gap-2", children: [
|
|
104
|
+
o && /* @__PURE__ */ e("div", { className: "alert-action flex items-center", children: o }),
|
|
105
|
+
/* @__PURE__ */ e(
|
|
106
|
+
I,
|
|
107
|
+
{
|
|
108
|
+
variant: "no-style",
|
|
109
|
+
size: "xsmall",
|
|
110
|
+
onClick: C,
|
|
111
|
+
"aria-label": t ? "Collapse" : "Expand",
|
|
112
|
+
style: {
|
|
113
|
+
color: z(h(r))
|
|
114
|
+
},
|
|
115
|
+
children: t ? /* @__PURE__ */ e(
|
|
116
|
+
B,
|
|
117
|
+
{
|
|
118
|
+
style: { minWidth: "13px", minHeight: "13px" }
|
|
119
|
+
}
|
|
120
|
+
) : /* @__PURE__ */ e(
|
|
121
|
+
j,
|
|
122
|
+
{
|
|
123
|
+
style: { minWidth: "13px", minHeight: "13px" }
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
] })
|
|
125
129
|
] }),
|
|
126
|
-
t && /* @__PURE__ */ e(
|
|
127
|
-
] }) : /* @__PURE__ */
|
|
128
|
-
|
|
129
|
-
/* @__PURE__ */ e("div", { className: "flex-1 flex items-center", children: /* @__PURE__ */ e("div", { className: "[&>div]:mb-0", children: /* @__PURE__ */ e(
|
|
130
|
+
t && /* @__PURE__ */ e(y, { children: /* @__PURE__ */ e("div", { className: N, 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: d }) }) })
|
|
131
|
+
] }) : /* @__PURE__ */ a("div", { className: m, children: [
|
|
132
|
+
x[r],
|
|
133
|
+
/* @__PURE__ */ e("div", { className: "flex-1 flex items-center", children: /* @__PURE__ */ e("div", { className: "[&>div]:mb-0", children: /* @__PURE__ */ e(p, { text: n, type: r }) }) }),
|
|
134
|
+
o && /* @__PURE__ */ e("div", { className: "alert-action shrink-0 flex items-center", children: o })
|
|
130
135
|
] });
|
|
131
136
|
};
|
|
132
137
|
export {
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
J as Alert,
|
|
139
|
+
J as default
|
|
135
140
|
};
|
|
136
141
|
//# 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 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}\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 AlertIcon = {\n info: (\n <InfoCircleFilled\n style={{ ...ICON_STYLE, color: 'var(--malachite-alert-info-foreground)' }}\n />\n ),\n success: (\n <CheckCircleFilled\n style={{ ...ICON_STYLE, color: 'var(--malachite-alert-success-foreground)' }}\n />\n ),\n error: (\n <ErrorCircleFilled\n style={{ ...ICON_STYLE, color: 'var(--malachite-alert-error-foreground)' }}\n />\n ),\n warning: (\n <WarningCircleFilled\n style={{ ...ICON_STYLE, color: 'var(--malachite-alert-warning-foreground)' }}\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: 'var(--malachite-alert-assistant-foreground)' }}\n />\n ),\n};\n\nconst SimpleTextRenderer: React.FC<{ text: string; type: AlertType }> = ({ text, type }) => {\n return <MarkdownBlock text={text} color={getAlertTextColor(type)} size=\"small\" omitMargin />;\n};\n\nconst Alert: React.FC<AlertProps> = ({\n type = 'info',\n message,\n hasMargin = true,\n className,\n children,\n isExpanded,\n onToggle,\n}) => {\n const isExpandable = !!children;\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 {AlertIcon[type]}\n <div className=\"flex-1 flex items-center\">\n <div className=\"[&>div]:mb-0\">\n <SimpleTextRenderer text={message} type={type} />\n </div>\n </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 {AlertIcon[type]}\n <div className=\"flex-1 flex items-center\">\n <div className=\"[&>div]:mb-0\">\n <SimpleTextRenderer text={message} type={type} />\n </div>\n </div>\n </div>\n\n {/* Right side buttons container */}\n <div className=\"flex items-center gap-2\">\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","AlertIcon","jsx","InfoCircleFilled","CheckCircleFilled","ErrorCircleFilled","WarningCircleFilled","SparkleIcon","SimpleTextRenderer","text","type","MarkdownBlock","Alert","message","hasMargin","className","children","isExpanded","onToggle","isExpandable","internalExpanded","setInternalExpanded","useState","expanded","handleToggle","newExpanded","alertClasses","headerClasses","expandableContentClasses","jsxs","Button","getTextColor","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 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}\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 AlertIcon = {\n info: (\n <InfoCircleFilled\n style={{ ...ICON_STYLE, color: 'var(--malachite-alert-info-foreground)' }}\n />\n ),\n success: (\n <CheckCircleFilled\n style={{ ...ICON_STYLE, color: 'var(--malachite-alert-success-foreground)' }}\n />\n ),\n error: (\n <ErrorCircleFilled\n style={{ ...ICON_STYLE, color: 'var(--malachite-alert-error-foreground)' }}\n />\n ),\n warning: (\n <WarningCircleFilled\n style={{ ...ICON_STYLE, color: 'var(--malachite-alert-warning-foreground)' }}\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: 'var(--malachite-alert-assistant-foreground)' }}\n />\n ),\n};\n\nconst SimpleTextRenderer: React.FC<{ text: string; type: AlertType }> = ({ text, type }) => {\n return <MarkdownBlock text={text} color={getAlertTextColor(type)} size=\"small\" omitMargin />;\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}) => {\n const isExpandable = !!children;\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 {AlertIcon[type]}\n <div className=\"flex-1 flex items-center\">\n <div className=\"[&>div]:mb-0\">\n <SimpleTextRenderer text={message} type={type} />\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 {AlertIcon[type]}\n <div className=\"flex-1 flex items-center\">\n <div className=\"[&>div]:mb-0\">\n <SimpleTextRenderer text={message} type={type} />\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","AlertIcon","jsx","InfoCircleFilled","CheckCircleFilled","ErrorCircleFilled","WarningCircleFilled","SparkleIcon","SimpleTextRenderer","text","type","MarkdownBlock","Alert","message","hasMargin","className","children","isExpanded","onToggle","action","isExpandable","internalExpanded","setInternalExpanded","useState","expanded","handleToggle","newExpanded","alertClasses","headerClasses","expandableContentClasses","jsxs","Button","getTextColor","CaretUpIcon","CaretDownIcon","Fragment"],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as r, jsxs as c, Fragment as h } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef as
|
|
3
|
-
import
|
|
4
|
-
|
|
2
|
+
import { forwardRef as j } from "react";
|
|
3
|
+
import B from "./Tooltip.js";
|
|
4
|
+
import { resolveRel as R } from "./linkRel.js";
|
|
5
|
+
const F = (t) => {
|
|
5
6
|
switch (t) {
|
|
6
7
|
// Figma has no text X-Small button, only the icon-only square — nothing to align to.
|
|
7
8
|
case "xsmall":
|
|
@@ -15,7 +16,7 @@ const R = (t) => {
|
|
|
15
16
|
default:
|
|
16
17
|
return "min-h-[var(--malachite-control-height-large,42px)] px-[12px] text-[length:var(--malachite-font-size-15,15px)] leading-[18px] whitespace-nowrap";
|
|
17
18
|
}
|
|
18
|
-
},
|
|
19
|
+
}, L = (t) => {
|
|
19
20
|
switch (t) {
|
|
20
21
|
case "xsmall":
|
|
21
22
|
return "size-[20px] shrink-0";
|
|
@@ -26,7 +27,7 @@ const R = (t) => {
|
|
|
26
27
|
default:
|
|
27
28
|
return "size-[42px] shrink-0";
|
|
28
29
|
}
|
|
29
|
-
},
|
|
30
|
+
}, O = (t, s) => {
|
|
30
31
|
switch (t) {
|
|
31
32
|
// Only the square rounds at 8px — text buttons at this size keep their 6px corner.
|
|
32
33
|
case "xsmall":
|
|
@@ -40,7 +41,7 @@ const R = (t) => {
|
|
|
40
41
|
default:
|
|
41
42
|
return "rounded-[var(--malachite-radius-xxl,14px)]";
|
|
42
43
|
}
|
|
43
|
-
},
|
|
44
|
+
}, _ = (t, s) => {
|
|
44
45
|
const e = s === "start";
|
|
45
46
|
switch (t) {
|
|
46
47
|
case "small":
|
|
@@ -49,7 +50,7 @@ const R = (t) => {
|
|
|
49
50
|
default:
|
|
50
51
|
return e ? "mr-2" : "ml-2";
|
|
51
52
|
}
|
|
52
|
-
},
|
|
53
|
+
}, T = (t) => {
|
|
53
54
|
switch (t) {
|
|
54
55
|
case "xsmall":
|
|
55
56
|
return "[&_svg]:size-[var(--malachite-icon-size-xsmall)]!";
|
|
@@ -62,7 +63,7 @@ const R = (t) => {
|
|
|
62
63
|
default:
|
|
63
64
|
return "[&_svg]:size-[var(--malachite-icon-size-large)]!";
|
|
64
65
|
}
|
|
65
|
-
},
|
|
66
|
+
}, U = (t) => {
|
|
66
67
|
switch (t) {
|
|
67
68
|
case "md":
|
|
68
69
|
return "rounded-md";
|
|
@@ -73,10 +74,6 @@ const R = (t) => {
|
|
|
73
74
|
default:
|
|
74
75
|
return "rounded-xl";
|
|
75
76
|
}
|
|
76
|
-
}, U = (t, s) => {
|
|
77
|
-
if (s !== "_blank") return t;
|
|
78
|
-
const e = new Set(t == null ? void 0 : t.split(/\s+/).filter(Boolean));
|
|
79
|
-
return e.add("noopener"), e.add("noreferrer"), [...e].join(" ");
|
|
80
77
|
}, a = {
|
|
81
78
|
neutral: "focus-visible:[outline:2px_solid_var(--malachite-text-primary)]",
|
|
82
79
|
destructive: "focus-visible:[outline:2px_solid_var(--malachite-text-destructive)]",
|
|
@@ -139,7 +136,7 @@ const R = (t) => {
|
|
|
139
136
|
)
|
|
140
137
|
]
|
|
141
138
|
}
|
|
142
|
-
), A =
|
|
139
|
+
), A = j(
|
|
143
140
|
({
|
|
144
141
|
variant: t,
|
|
145
142
|
size: s = "large",
|
|
@@ -149,38 +146,38 @@ const R = (t) => {
|
|
|
149
146
|
icon: i,
|
|
150
147
|
iconPosition: p = "start",
|
|
151
148
|
flexGrow: o = !1,
|
|
152
|
-
fill:
|
|
149
|
+
fill: y = !1,
|
|
153
150
|
tooltip: u,
|
|
154
|
-
tooltipPlacement:
|
|
151
|
+
tooltipPlacement: N = "top",
|
|
155
152
|
iconOnly: f = !1,
|
|
156
153
|
children: n,
|
|
157
|
-
className:
|
|
154
|
+
className: k = "",
|
|
158
155
|
href: g,
|
|
159
156
|
target: v,
|
|
160
|
-
rel:
|
|
157
|
+
rel: I,
|
|
161
158
|
...w
|
|
162
159
|
}, $) => {
|
|
163
|
-
const
|
|
160
|
+
const S = e ? U(e) : O(s, f), l = C || x, b = [
|
|
164
161
|
"flex items-center justify-center",
|
|
165
|
-
f ? `p-0 ${
|
|
166
|
-
V(t,
|
|
162
|
+
f ? `p-0 ${L(s)}` : F(s),
|
|
163
|
+
V(t, S),
|
|
167
164
|
o ? "w-full" : "",
|
|
168
|
-
|
|
165
|
+
y ? "max-w-full" : "",
|
|
169
166
|
l ? "cursor-not-allowed" : "",
|
|
170
167
|
u && l ? "pointer-events-none" : "",
|
|
171
|
-
|
|
172
|
-
].filter(Boolean).join(" "),
|
|
168
|
+
k
|
|
169
|
+
].filter(Boolean).join(" "), z = () => {
|
|
173
170
|
if (x)
|
|
174
171
|
return /* @__PURE__ */ c(h, { children: [
|
|
175
172
|
/* @__PURE__ */ r(W, {}),
|
|
176
173
|
n
|
|
177
174
|
] });
|
|
178
|
-
const m = `inline-flex items-center shrink-0 ${
|
|
175
|
+
const m = `inline-flex items-center shrink-0 ${T(s)}`;
|
|
179
176
|
return i && p === "start" ? /* @__PURE__ */ c(h, { children: [
|
|
180
177
|
/* @__PURE__ */ r(
|
|
181
178
|
"span",
|
|
182
179
|
{
|
|
183
|
-
className: `${m}${n ? ` ${
|
|
180
|
+
className: `${m}${n ? ` ${_(s, "start")}` : ""}`,
|
|
184
181
|
children: i
|
|
185
182
|
}
|
|
186
183
|
),
|
|
@@ -190,7 +187,7 @@ const R = (t) => {
|
|
|
190
187
|
/* @__PURE__ */ r(
|
|
191
188
|
"span",
|
|
192
189
|
{
|
|
193
|
-
className: `${m}${n ? ` ${
|
|
190
|
+
className: `${m}${n ? ` ${_(s, "end")}` : ""}`,
|
|
194
191
|
children: i
|
|
195
192
|
}
|
|
196
193
|
)
|
|
@@ -201,10 +198,10 @@ const R = (t) => {
|
|
|
201
198
|
ref: $,
|
|
202
199
|
href: g,
|
|
203
200
|
target: v,
|
|
204
|
-
rel:
|
|
201
|
+
rel: R(I, v),
|
|
205
202
|
className: b,
|
|
206
203
|
...w,
|
|
207
|
-
children:
|
|
204
|
+
children: z()
|
|
208
205
|
}
|
|
209
206
|
) : /* @__PURE__ */ r(
|
|
210
207
|
"button",
|
|
@@ -213,10 +210,10 @@ const R = (t) => {
|
|
|
213
210
|
disabled: l,
|
|
214
211
|
className: b,
|
|
215
212
|
...w,
|
|
216
|
-
children:
|
|
213
|
+
children: z()
|
|
217
214
|
}
|
|
218
215
|
);
|
|
219
|
-
return u ? /* @__PURE__ */ r(
|
|
216
|
+
return u ? /* @__PURE__ */ r(B, { text: u, placement: N, fill: o, children: l ? /* @__PURE__ */ r("span", { className: `inline-flex cursor-not-allowed${o ? " w-full" : ""}`, children: d }) : d }) : d;
|
|
220
217
|
}
|
|
221
218
|
);
|
|
222
219
|
A.displayName = "Button";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.js","sources":["../../src/components/Button.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport Tooltip from './Tooltip';\n\n// Button variant types matching the old component\nexport type ButtonVariant =\n | 'filled'\n | 'outline'\n /**\n * `outline`'s geometry on a ground of its own. For a button sitting on a tinted or\n * patterned surface, where `outline`'s transparent background lets that surface through\n * and the control stops reading as a button.\n */\n | 'surface'\n /**\n * The violet the assistant is named in elsewhere — for a control that hands work to it, or\n * acts on what it produced. Provenance, not a severity: it says who is being asked, not\n * that anything is wrong.\n */\n | 'assistant'\n | 'destructive'\n | 'success'\n | 'warning'\n | 'error'\n | 'ghost'\n | 'no-style';\n\nexport type ButtonSize = 'xsmall' | 'small' | 'medium' | 'large';\n\nexport type ButtonRadius = 'md' | 'lg' | 'xl';\n\ninterface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n variant: ButtonVariant;\n size?: ButtonSize;\n radius?: ButtonRadius;\n loading?: boolean;\n disabled?: boolean;\n icon?: React.ReactNode;\n iconPosition?: 'start' | 'end';\n flexGrow?: boolean;\n fill?: boolean;\n tooltip?: string;\n tooltipPlacement?: 'top' | 'bottom' | 'left' | 'right';\n iconOnly?: boolean;\n children?: React.ReactNode;\n onClick?: React.MouseEventHandler<HTMLButtonElement & HTMLAnchorElement>;\n /** Renders the button as a real anchor, so a link action is never an <a> nested in a <button>. */\n href?: string;\n target?: React.HTMLAttributeAnchorTarget;\n rel?: string;\n}\n\n// Geometry from the Figma component library, which is variant-independent.\n// `min-h` not `h`: Tailwind emits `h-*` after `min-h-*`, so a hard height would beat a\n// consumer's own `h-*`. Paired with `whitespace-nowrap` (as Figma has it) so a long label\n// cannot wrap into a line the height would clip.\nconst getSizeClasses = (size: ButtonSize) => {\n switch (size) {\n // Figma has no text X-Small button, only the icon-only square — nothing to align to.\n case 'xsmall':\n return 'px-2 py-1 text-xs';\n case 'small':\n return 'min-h-[var(--malachite-control-height-small,28px)] px-[10px] text-[length:var(--malachite-font-size-small,13px)] leading-[16px] whitespace-nowrap';\n case 'medium':\n return 'min-h-[var(--malachite-control-height-medium,32px)] px-[10px] text-[length:var(--malachite-font-size-small,13px)] leading-[16px] whitespace-nowrap';\n case 'large':\n return 'min-h-[var(--malachite-control-height-large,42px)] px-[12px] text-[length:var(--malachite-font-size-15,15px)] leading-[18px] whitespace-nowrap';\n default:\n return 'min-h-[var(--malachite-control-height-large,42px)] px-[12px] text-[length:var(--malachite-font-size-15,15px)] leading-[18px] whitespace-nowrap';\n }\n};\n\n/** An icon-only button is a square on the control ladder; without this it collapses to the\n * glyph plus its border. */\nconst getIconOnlySizeClasses = (size: ButtonSize): string => {\n switch (size) {\n case 'xsmall':\n return 'size-[20px] shrink-0';\n case 'small':\n return 'size-[28px] shrink-0';\n case 'medium':\n return 'size-[32px] shrink-0';\n default:\n return 'size-[42px] shrink-0';\n }\n};\n\n// `rounded-*` reads the malachite radius tokens via `@theme`, so these follow the theme.\n// An explicit `radius` prop still wins, via getRadiusClass.\nconst getDefaultRadiusClass = (size: ButtonSize, iconOnly: boolean): string => {\n switch (size) {\n // Only the square rounds at 8px — text buttons at this size keep their 6px corner.\n case 'xsmall':\n return iconOnly ? 'rounded-lg' : 'rounded-md';\n case 'small':\n return 'rounded-[var(--malachite-radius-10,10px)]';\n case 'medium':\n return 'rounded-xl';\n case 'large':\n return 'rounded-[var(--malachite-radius-xxl,14px)]';\n default:\n return 'rounded-[var(--malachite-radius-xxl,14px)]';\n }\n};\n\n// Figma pairs a 6px icon gap with the 13px tiers and 8px with the 15px tier.\nconst getIconGapClass = (size: ButtonSize, position: 'start' | 'end'): string => {\n const start = position === 'start';\n switch (size) {\n case 'small':\n case 'medium':\n return start ? 'mr-[6px]' : 'ml-[6px]';\n default:\n return start ? 'mr-2' : 'ml-2';\n }\n};\n\nconst getIconSizeClass = (size: ButtonSize): string => {\n switch (size) {\n case 'xsmall':\n return '[&_svg]:size-[var(--malachite-icon-size-xsmall)]!';\n case 'small':\n return '[&_svg]:size-[var(--malachite-icon-size-small)]!';\n case 'medium':\n return '[&_svg]:size-[var(--malachite-icon-size-medium)]!';\n case 'large':\n return '[&_svg]:size-[var(--malachite-icon-size-large)]!';\n default:\n return '[&_svg]:size-[var(--malachite-icon-size-large)]!';\n }\n};\n\nconst getRadiusClass = (radius: ButtonRadius): string => {\n switch (radius) {\n case 'md':\n return 'rounded-md';\n case 'lg':\n return 'rounded-lg';\n case 'xl':\n return 'rounded-xl';\n default:\n return 'rounded-xl';\n }\n};\n\n// Merged, never replaced: a caller's own `rel` must not drop `_blank`'s noopener/noreferrer.\nconst resolveRel = (\n rel: string | undefined,\n target: React.HTMLAttributeAnchorTarget | undefined,\n): string | undefined => {\n if (target !== '_blank') return rel;\n\n const tokens = new Set(rel?.split(/\\s+/).filter(Boolean));\n tokens.add('noopener');\n tokens.add('noreferrer');\n return [...tokens].join(' ');\n};\n\n// Focus ring: a `focus-visible` outline (keyboard only), coloured to match the variant.\n// Uses `outline` rather than Tailwind's `ring-*` because ring composes `--tw-ring-*`\n// custom properties via box-shadow that rely on document-level @property registration\n// and silently don't paint inside a shadow root (same class of issue as transform\n// utilities). The classes are full literals so Tailwind's JIT can generate them.\nconst FOCUS_OUTLINE = {\n neutral: 'focus-visible:[outline:2px_solid_var(--malachite-text-primary)]',\n destructive: 'focus-visible:[outline:2px_solid_var(--malachite-text-destructive)]',\n success: 'focus-visible:[outline:2px_solid_var(--malachite-text-success)]',\n warning: 'focus-visible:[outline:2px_solid_var(--malachite-text-warning)]',\n error: 'focus-visible:[outline:2px_solid_var(--malachite-text-error)]',\n assistant: 'focus-visible:[outline:2px_solid_var(--malachite-assistant-foreground)]',\n};\n\nconst getVariantClasses = (variant: ButtonVariant, radiusClass: string): string => {\n const baseClasses = `font-medium transition-[color,background-color,border-color,opacity,box-shadow] duration-200 ease-in-out outline-none focus-visible:outline-offset-2 border border-solid ${radiusClass} cursor-pointer`;\n\n switch (variant) {\n case 'filled':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-filled shadow-default`;\n\n case 'outline':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-outline shadow-default`;\n\n case 'surface':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-surface shadow-default`;\n\n case 'assistant':\n return `${baseClasses} ${FOCUS_OUTLINE.assistant} btn-assistant shadow-default`;\n\n case 'destructive':\n return `${baseClasses} ${FOCUS_OUTLINE.destructive} btn-destructive`;\n\n case 'success':\n return `${baseClasses} ${FOCUS_OUTLINE.success} btn-glass-success`;\n\n case 'warning':\n return `${baseClasses} ${FOCUS_OUTLINE.warning} btn-glass-warning`;\n\n case 'error':\n return `${baseClasses} ${FOCUS_OUTLINE.error} btn-glass-error`;\n\n case 'ghost':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-ghost`;\n\n case 'no-style':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-no-style border-none`;\n\n default:\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-filled shadow-default`;\n }\n};\n\nconst LoadingSpinner = () => (\n <svg\n className=\"animate-spin -ml-1 mr-2 h-4 w-4\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n);\n\nexport const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(\n (\n {\n variant,\n size = 'large',\n radius,\n loading = false,\n disabled = false,\n icon,\n iconPosition = 'start',\n flexGrow = false,\n fill = false,\n tooltip,\n tooltipPlacement = 'top',\n iconOnly = false,\n children,\n className = '',\n href,\n target,\n rel,\n ...props\n },\n ref,\n ) => {\n const radiusClass = radius ? getRadiusClass(radius) : getDefaultRadiusClass(size, iconOnly);\n const isInactive = disabled || loading;\n const baseClasses = [\n 'flex items-center justify-center',\n iconOnly ? `p-0 ${getIconOnlySizeClasses(size)}` : getSizeClasses(size),\n getVariantClasses(variant, radiusClass),\n flexGrow ? 'w-full' : '',\n fill ? 'max-w-full' : '',\n isInactive ? 'cursor-not-allowed' : '',\n tooltip && isInactive ? 'pointer-events-none' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const renderContent = () => {\n if (loading) {\n return (\n <>\n <LoadingSpinner />\n {children}\n </>\n );\n }\n\n const iconWrapperClasses = `inline-flex items-center shrink-0 ${getIconSizeClass(size)}`;\n\n if (icon && iconPosition === 'start') {\n return (\n <>\n <span\n className={`${iconWrapperClasses}${children ? ` ${getIconGapClass(size, 'start')}` : ''}`}\n >\n {icon}\n </span>\n {children}\n </>\n );\n }\n\n if (icon && iconPosition === 'end') {\n return (\n <>\n {children}\n <span\n className={`${iconWrapperClasses}${children ? ` ${getIconGapClass(size, 'end')}` : ''}`}\n >\n {icon}\n </span>\n </>\n );\n }\n\n return children;\n };\n\n // An anchor cannot be disabled, so an inactive link still renders as a <button>.\n const button =\n href && !isInactive ? (\n <a\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={resolveRel(rel, target)}\n className={baseClasses}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n {renderContent()}\n </a>\n ) : (\n <button\n ref={ref as React.Ref<HTMLButtonElement>}\n disabled={isInactive}\n className={baseClasses}\n {...props}\n >\n {renderContent()}\n </button>\n );\n\n if (tooltip) {\n const trigger = isInactive ? (\n <span className={`inline-flex cursor-not-allowed${flexGrow ? ' w-full' : ''}`}>\n {button}\n </span>\n ) : (\n button\n );\n return (\n <Tooltip text={tooltip} placement={tooltipPlacement} fill={flexGrow}>\n {trigger}\n </Tooltip>\n );\n }\n\n return button;\n },\n);\n\nButton.displayName = 'Button';\n\nexport default Button;\n"],"names":["getSizeClasses","size","getIconOnlySizeClasses","getDefaultRadiusClass","iconOnly","getIconGapClass","position","start","getIconSizeClass","getRadiusClass","radius","resolveRel","rel","target","tokens","FOCUS_OUTLINE","getVariantClasses","variant","radiusClass","baseClasses","LoadingSpinner","jsxs","jsx","Button","forwardRef","loading","disabled","icon","iconPosition","flexGrow","fill","tooltip","tooltipPlacement","children","className","href","props","ref","isInactive","renderContent","Fragment","iconWrapperClasses","button","Tooltip"],"mappings":";;;AAuDA,MAAMA,IAAiB,CAACC,MAAqB;AACzC,UAAQA,GAAA;AAAA;AAAA,IAEJ,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX;AACI,aAAO;AAAA,EAAA;AAEnB,GAIMC,IAAyB,CAACD,MAA6B;AACzD,UAAQA,GAAA;AAAA,IACJ,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX;AACI,aAAO;AAAA,EAAA;AAEnB,GAIME,IAAwB,CAACF,GAAkBG,MAA8B;AAC3E,UAAQH,GAAA;AAAA;AAAA,IAEJ,KAAK;AACD,aAAOG,IAAW,eAAe;AAAA,IACrC,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX;AACI,aAAO;AAAA,EAAA;AAEnB,GAGMC,IAAkB,CAACJ,GAAkBK,MAAsC;AAC7E,QAAMC,IAAQD,MAAa;AAC3B,UAAQL,GAAA;AAAA,IACJ,KAAK;AAAA,IACL,KAAK;AACD,aAAOM,IAAQ,aAAa;AAAA,IAChC;AACI,aAAOA,IAAQ,SAAS;AAAA,EAAA;AAEpC,GAEMC,IAAmB,CAACP,MAA6B;AACnD,UAAQA,GAAA;AAAA,IACJ,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX;AACI,aAAO;AAAA,EAAA;AAEnB,GAEMQ,IAAiB,CAACC,MAAiC;AACrD,UAAQA,GAAA;AAAA,IACJ,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX;AACI,aAAO;AAAA,EAAA;AAEnB,GAGMC,IAAa,CACfC,GACAC,MACqB;AACrB,MAAIA,MAAW,SAAU,QAAOD;AAEhC,QAAME,IAAS,IAAI,IAAIF,KAAA,gBAAAA,EAAK,MAAM,OAAO,OAAO,QAAQ;AACxD,SAAAE,EAAO,IAAI,UAAU,GACrBA,EAAO,IAAI,YAAY,GAChB,CAAC,GAAGA,CAAM,EAAE,KAAK,GAAG;AAC/B,GAOMC,IAAgB;AAAA,EAClB,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AAAA,EACP,WAAW;AACf,GAEMC,IAAoB,CAACC,GAAwBC,MAAgC;AAC/E,QAAMC,IAAc,4KAA4KD,CAAW;AAE3M,UAAQD,GAAA;AAAA,IACJ,KAAK;AACD,aAAO,GAAGE,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,SAAS;AAAA,IAEpD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,WAAW;AAAA,IAEtD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,KAAK;AAAA,IAEhD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD,KAAK;AACD,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,IAElD;AACI,aAAO,GAAGI,CAAW,IAAIJ,EAAc,OAAO;AAAA,EAAA;AAE1D,GAEMK,IAAiB,MACnB,gBAAAC;AAAA,EAAC;AAAA,EAAA;AAAA,IACG,WAAU;AAAA,IACV,OAAM;AAAA,IACN,MAAK;AAAA,IACL,SAAQ;AAAA,IAER,UAAA;AAAA,MAAA,gBAAAC;AAAA,QAAC;AAAA,QAAA;AAAA,UACG,WAAU;AAAA,UACV,IAAG;AAAA,UACH,IAAG;AAAA,UACH,GAAE;AAAA,UACF,QAAO;AAAA,UACP,aAAY;AAAA,QAAA;AAAA,MAAA;AAAA,MAEhB,gBAAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACG,WAAU;AAAA,UACV,MAAK;AAAA,UACL,GAAE;AAAA,QAAA;AAAA,MAAA;AAAA,IACN;AAAA,EAAA;AACJ,GAGSC,IAASC;AAAA,EAClB,CACI;AAAA,IACI,SAAAP;AAAA,IACA,MAAAhB,IAAO;AAAA,IACP,QAAAS;AAAA,IACA,SAAAe,IAAU;AAAA,IACV,UAAAC,IAAW;AAAA,IACX,MAAAC;AAAA,IACA,cAAAC,IAAe;AAAA,IACf,UAAAC,IAAW;AAAA,IACX,MAAAC,IAAO;AAAA,IACP,SAAAC;AAAA,IACA,kBAAAC,IAAmB;AAAA,IACnB,UAAA5B,IAAW;AAAA,IACX,UAAA6B;AAAA,IACA,WAAAC,IAAY;AAAA,IACZ,MAAAC;AAAA,IACA,QAAAtB;AAAA,IACA,KAAAD;AAAA,IACA,GAAGwB;AAAA,EAAA,GAEPC,MACC;AACD,UAAMnB,IAAcR,IAASD,EAAeC,CAAM,IAAIP,EAAsBF,GAAMG,CAAQ,GACpFkC,IAAaZ,KAAYD,GACzBN,IAAc;AAAA,MAChB;AAAA,MACAf,IAAW,OAAOF,EAAuBD,CAAI,CAAC,KAAKD,EAAeC,CAAI;AAAA,MACtEe,EAAkBC,GAASC,CAAW;AAAA,MACtCW,IAAW,WAAW;AAAA,MACtBC,IAAO,eAAe;AAAA,MACtBQ,IAAa,uBAAuB;AAAA,MACpCP,KAAWO,IAAa,wBAAwB;AAAA,MAChDJ;AAAA,IAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG,GAEPK,IAAgB,MAAM;AACxB,UAAId;AACA,eACI,gBAAAJ,EAAAmB,GAAA,EACI,UAAA;AAAA,UAAA,gBAAAlB,EAACF,GAAA,EAAe;AAAA,UACfa;AAAA,QAAA,GACL;AAIR,YAAMQ,IAAqB,qCAAqCjC,EAAiBP,CAAI,CAAC;AAEtF,aAAI0B,KAAQC,MAAiB,UAErB,gBAAAP,EAAAmB,GAAA,EACI,UAAA;AAAA,QAAA,gBAAAlB;AAAA,UAAC;AAAA,UAAA;AAAA,YACG,WAAW,GAAGmB,CAAkB,GAAGR,IAAW,IAAI5B,EAAgBJ,GAAM,OAAO,CAAC,KAAK,EAAE;AAAA,YAEtF,UAAA0B;AAAA,UAAA;AAAA,QAAA;AAAA,QAEJM;AAAA,MAAA,GACL,IAIJN,KAAQC,MAAiB,QAErB,gBAAAP,EAAAmB,GAAA,EACK,UAAA;AAAA,QAAAP;AAAA,QACD,gBAAAX;AAAA,UAAC;AAAA,UAAA;AAAA,YACG,WAAW,GAAGmB,CAAkB,GAAGR,IAAW,IAAI5B,EAAgBJ,GAAM,KAAK,CAAC,KAAK,EAAE;AAAA,YAEpF,UAAA0B;AAAA,UAAA;AAAA,QAAA;AAAA,MACL,GACJ,IAIDM;AAAA,IACX,GAGMS,IACFP,KAAQ,CAACG,IACL,gBAAAhB;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,KAAAe;AAAA,QACA,MAAAF;AAAA,QACA,QAAAtB;AAAA,QACA,KAAKF,EAAWC,GAAKC,CAAM;AAAA,QAC3B,WAAWM;AAAA,QACV,GAAIiB;AAAA,QAEJ,UAAAG,EAAA;AAAA,MAAc;AAAA,IAAA,IAGnB,gBAAAjB;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,KAAAe;AAAA,QACA,UAAUC;AAAA,QACV,WAAWnB;AAAA,QACV,GAAGiB;AAAA,QAEH,UAAAG,EAAA;AAAA,MAAc;AAAA,IAAA;AAI3B,WAAIR,IASI,gBAAAT,EAACqB,KAAQ,MAAMZ,GAAS,WAAWC,GAAkB,MAAMH,GACtD,UATOS,IACZ,gBAAAhB,EAAC,QAAA,EAAK,WAAW,iCAAiCO,IAAW,YAAY,EAAE,IACtE,UAAAa,EAAA,CACL,IAEAA,EAIK,CACL,IAIDA;AAAA,EACX;AACJ;AAEAnB,EAAO,cAAc;"}
|
|
1
|
+
{"version":3,"file":"Button.js","sources":["../../src/components/Button.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport Tooltip from './Tooltip';\nimport { resolveRel } from './linkRel';\n\n// Button variant types matching the old component\nexport type ButtonVariant =\n | 'filled'\n | 'outline'\n /**\n * `outline`'s geometry on a ground of its own. For a button sitting on a tinted or\n * patterned surface, where `outline`'s transparent background lets that surface through\n * and the control stops reading as a button.\n */\n | 'surface'\n /**\n * The violet the assistant is named in elsewhere — for a control that hands work to it, or\n * acts on what it produced. Provenance, not a severity: it says who is being asked, not\n * that anything is wrong.\n */\n | 'assistant'\n | 'destructive'\n | 'success'\n | 'warning'\n | 'error'\n | 'ghost'\n | 'no-style';\n\nexport type ButtonSize = 'xsmall' | 'small' | 'medium' | 'large';\n\nexport type ButtonRadius = 'md' | 'lg' | 'xl';\n\ninterface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n variant: ButtonVariant;\n size?: ButtonSize;\n radius?: ButtonRadius;\n loading?: boolean;\n disabled?: boolean;\n icon?: React.ReactNode;\n iconPosition?: 'start' | 'end';\n flexGrow?: boolean;\n fill?: boolean;\n tooltip?: string;\n tooltipPlacement?: 'top' | 'bottom' | 'left' | 'right';\n iconOnly?: boolean;\n children?: React.ReactNode;\n onClick?: React.MouseEventHandler<HTMLButtonElement & HTMLAnchorElement>;\n /** Renders the button as a real anchor, so a link action is never an <a> nested in a <button>. */\n href?: string;\n target?: React.HTMLAttributeAnchorTarget;\n rel?: string;\n}\n\n// Geometry from the Figma component library, which is variant-independent.\n// `min-h` not `h`: Tailwind emits `h-*` after `min-h-*`, so a hard height would beat a\n// consumer's own `h-*`. Paired with `whitespace-nowrap` (as Figma has it) so a long label\n// cannot wrap into a line the height would clip.\nconst getSizeClasses = (size: ButtonSize) => {\n switch (size) {\n // Figma has no text X-Small button, only the icon-only square — nothing to align to.\n case 'xsmall':\n return 'px-2 py-1 text-xs';\n case 'small':\n return 'min-h-[var(--malachite-control-height-small,28px)] px-[10px] text-[length:var(--malachite-font-size-small,13px)] leading-[16px] whitespace-nowrap';\n case 'medium':\n return 'min-h-[var(--malachite-control-height-medium,32px)] px-[10px] text-[length:var(--malachite-font-size-small,13px)] leading-[16px] whitespace-nowrap';\n case 'large':\n return 'min-h-[var(--malachite-control-height-large,42px)] px-[12px] text-[length:var(--malachite-font-size-15,15px)] leading-[18px] whitespace-nowrap';\n default:\n return 'min-h-[var(--malachite-control-height-large,42px)] px-[12px] text-[length:var(--malachite-font-size-15,15px)] leading-[18px] whitespace-nowrap';\n }\n};\n\n/** An icon-only button is a square on the control ladder; without this it collapses to the\n * glyph plus its border. */\nconst getIconOnlySizeClasses = (size: ButtonSize): string => {\n switch (size) {\n case 'xsmall':\n return 'size-[20px] shrink-0';\n case 'small':\n return 'size-[28px] shrink-0';\n case 'medium':\n return 'size-[32px] shrink-0';\n default:\n return 'size-[42px] shrink-0';\n }\n};\n\n// `rounded-*` reads the malachite radius tokens via `@theme`, so these follow the theme.\n// An explicit `radius` prop still wins, via getRadiusClass.\nconst getDefaultRadiusClass = (size: ButtonSize, iconOnly: boolean): string => {\n switch (size) {\n // Only the square rounds at 8px — text buttons at this size keep their 6px corner.\n case 'xsmall':\n return iconOnly ? 'rounded-lg' : 'rounded-md';\n case 'small':\n return 'rounded-[var(--malachite-radius-10,10px)]';\n case 'medium':\n return 'rounded-xl';\n case 'large':\n return 'rounded-[var(--malachite-radius-xxl,14px)]';\n default:\n return 'rounded-[var(--malachite-radius-xxl,14px)]';\n }\n};\n\n// Figma pairs a 6px icon gap with the 13px tiers and 8px with the 15px tier.\nconst getIconGapClass = (size: ButtonSize, position: 'start' | 'end'): string => {\n const start = position === 'start';\n switch (size) {\n case 'small':\n case 'medium':\n return start ? 'mr-[6px]' : 'ml-[6px]';\n default:\n return start ? 'mr-2' : 'ml-2';\n }\n};\n\nconst getIconSizeClass = (size: ButtonSize): string => {\n switch (size) {\n case 'xsmall':\n return '[&_svg]:size-[var(--malachite-icon-size-xsmall)]!';\n case 'small':\n return '[&_svg]:size-[var(--malachite-icon-size-small)]!';\n case 'medium':\n return '[&_svg]:size-[var(--malachite-icon-size-medium)]!';\n case 'large':\n return '[&_svg]:size-[var(--malachite-icon-size-large)]!';\n default:\n return '[&_svg]:size-[var(--malachite-icon-size-large)]!';\n }\n};\n\nconst getRadiusClass = (radius: ButtonRadius): string => {\n switch (radius) {\n case 'md':\n return 'rounded-md';\n case 'lg':\n return 'rounded-lg';\n case 'xl':\n return 'rounded-xl';\n default:\n return 'rounded-xl';\n }\n};\n\n// Focus ring: a `focus-visible` outline (keyboard only), coloured to match the variant.\n// Uses `outline` rather than Tailwind's `ring-*` because ring composes `--tw-ring-*`\n// custom properties via box-shadow that rely on document-level @property registration\n// and silently don't paint inside a shadow root (same class of issue as transform\n// utilities). The classes are full literals so Tailwind's JIT can generate them.\nconst FOCUS_OUTLINE = {\n neutral: 'focus-visible:[outline:2px_solid_var(--malachite-text-primary)]',\n destructive: 'focus-visible:[outline:2px_solid_var(--malachite-text-destructive)]',\n success: 'focus-visible:[outline:2px_solid_var(--malachite-text-success)]',\n warning: 'focus-visible:[outline:2px_solid_var(--malachite-text-warning)]',\n error: 'focus-visible:[outline:2px_solid_var(--malachite-text-error)]',\n assistant: 'focus-visible:[outline:2px_solid_var(--malachite-assistant-foreground)]',\n};\n\nconst getVariantClasses = (variant: ButtonVariant, radiusClass: string): string => {\n const baseClasses = `font-medium transition-[color,background-color,border-color,opacity,box-shadow] duration-200 ease-in-out outline-none focus-visible:outline-offset-2 border border-solid ${radiusClass} cursor-pointer`;\n\n switch (variant) {\n case 'filled':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-filled shadow-default`;\n\n case 'outline':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-outline shadow-default`;\n\n case 'surface':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-surface shadow-default`;\n\n case 'assistant':\n return `${baseClasses} ${FOCUS_OUTLINE.assistant} btn-assistant shadow-default`;\n\n case 'destructive':\n return `${baseClasses} ${FOCUS_OUTLINE.destructive} btn-destructive`;\n\n case 'success':\n return `${baseClasses} ${FOCUS_OUTLINE.success} btn-glass-success`;\n\n case 'warning':\n return `${baseClasses} ${FOCUS_OUTLINE.warning} btn-glass-warning`;\n\n case 'error':\n return `${baseClasses} ${FOCUS_OUTLINE.error} btn-glass-error`;\n\n case 'ghost':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-ghost`;\n\n case 'no-style':\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-no-style border-none`;\n\n default:\n return `${baseClasses} ${FOCUS_OUTLINE.neutral} btn-filled shadow-default`;\n }\n};\n\nconst LoadingSpinner = () => (\n <svg\n className=\"animate-spin -ml-1 mr-2 h-4 w-4\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n);\n\nexport const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(\n (\n {\n variant,\n size = 'large',\n radius,\n loading = false,\n disabled = false,\n icon,\n iconPosition = 'start',\n flexGrow = false,\n fill = false,\n tooltip,\n tooltipPlacement = 'top',\n iconOnly = false,\n children,\n className = '',\n href,\n target,\n rel,\n ...props\n },\n ref,\n ) => {\n const radiusClass = radius ? getRadiusClass(radius) : getDefaultRadiusClass(size, iconOnly);\n const isInactive = disabled || loading;\n const baseClasses = [\n 'flex items-center justify-center',\n iconOnly ? `p-0 ${getIconOnlySizeClasses(size)}` : getSizeClasses(size),\n getVariantClasses(variant, radiusClass),\n flexGrow ? 'w-full' : '',\n fill ? 'max-w-full' : '',\n isInactive ? 'cursor-not-allowed' : '',\n tooltip && isInactive ? 'pointer-events-none' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const renderContent = () => {\n if (loading) {\n return (\n <>\n <LoadingSpinner />\n {children}\n </>\n );\n }\n\n const iconWrapperClasses = `inline-flex items-center shrink-0 ${getIconSizeClass(size)}`;\n\n if (icon && iconPosition === 'start') {\n return (\n <>\n <span\n className={`${iconWrapperClasses}${children ? ` ${getIconGapClass(size, 'start')}` : ''}`}\n >\n {icon}\n </span>\n {children}\n </>\n );\n }\n\n if (icon && iconPosition === 'end') {\n return (\n <>\n {children}\n <span\n className={`${iconWrapperClasses}${children ? ` ${getIconGapClass(size, 'end')}` : ''}`}\n >\n {icon}\n </span>\n </>\n );\n }\n\n return children;\n };\n\n // An anchor cannot be disabled, so an inactive link still renders as a <button>.\n const button =\n href && !isInactive ? (\n <a\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={resolveRel(rel, target)}\n className={baseClasses}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n {renderContent()}\n </a>\n ) : (\n <button\n ref={ref as React.Ref<HTMLButtonElement>}\n disabled={isInactive}\n className={baseClasses}\n {...props}\n >\n {renderContent()}\n </button>\n );\n\n if (tooltip) {\n const trigger = isInactive ? (\n <span className={`inline-flex cursor-not-allowed${flexGrow ? ' w-full' : ''}`}>\n {button}\n </span>\n ) : (\n button\n );\n return (\n <Tooltip text={tooltip} placement={tooltipPlacement} fill={flexGrow}>\n {trigger}\n </Tooltip>\n );\n }\n\n return button;\n },\n);\n\nButton.displayName = 'Button';\n\nexport default Button;\n"],"names":["getSizeClasses","size","getIconOnlySizeClasses","getDefaultRadiusClass","iconOnly","getIconGapClass","position","start","getIconSizeClass","getRadiusClass","radius","FOCUS_OUTLINE","getVariantClasses","variant","radiusClass","baseClasses","LoadingSpinner","jsxs","jsx","Button","forwardRef","loading","disabled","icon","iconPosition","flexGrow","fill","tooltip","tooltipPlacement","children","className","href","target","rel","props","ref","isInactive","renderContent","Fragment","iconWrapperClasses","button","resolveRel","Tooltip"],"mappings":"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"}
|