@stackone/malachite 2.71.0 → 2.72.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 +3 -3
- package/dist/components/Accordion.js +59 -53
- package/dist/components/Accordion.js.map +1 -1
- package/dist/components/Button.js +36 -31
- package/dist/components/Button.js.map +1 -1
- package/dist/components/SidePanel.js +124 -106
- package/dist/components/SidePanel.js.map +1 -1
- package/dist/index.d.ts +14 -2
- package/dist/tailwind.css.js +1 -1
- package/package.json +1 -1
package/COMPONENTS.md
CHANGED
|
@@ -27,7 +27,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
|
|
|
27
27
|
- **`ActionBar`** — A row of actions that collapses into a single `ActionMenu` once its container drops below `collapseBelow`. Measures the nearest genuinely-wider ancestor rather than itself, since the row shrinks to its own content. `dividerBefore` renders a vertical rule in the row and a divider in the collapsed menu, so both forms group the actions the same way. _Key props:_ `buttons` (`label`, `onClick`, `icon`, `variant`, `size`, `radius`, `tooltip`, `disabled`, `dividerBefore`), `collapseBelow`, `menuLabel`, `className`.
|
|
28
28
|
- **`Popover`** — An anchored, non-modal panel for arbitrary content — a workspace switcher, an org flyout. The panel renders in its own shadow root, so malachite components inside it are styled, and joins the top layer like the other overlays. It prefers its `placement` and flips to the opposite side when there is no room, clamping along the other axis. Click-opened panels take focus and hand it back to the trigger on close; `openOn="hover"` also opens on focus and stays open while the pointer or focus is in the panel. A nested Popover counts as inside its parent for outside-press and hover dismissal, and Escape closes the topmost only. A click panel also closes when focus moves somewhere outside it and its trigger, and on close it hands focus back to the trigger only if focus would otherwise fall to the page. _Key props:_ `trigger`, `children`, `open`, `defaultOpen`, `onOpenChange`, `placement` (`bottom-start` | `bottom-end` | `top-start` | `top-center` | `right-start` | `right-end` | `left-start`), `openOn` (`click` | `hover`), `width` (`content` | `trigger`), `padding` (`none` | `small` | `medium`), `anchorRef` (position against another element, e.g. the section a trigger row sits in), `offset` (`small` 8px | `medium` 12px | `large` 14px), `elevation` (`default` | `raised`), `initialFocus` (a ref, or a function read when the panel is shown, for what takes focus in place of the first focusable; it is read again as the panel's content changes, so focus follows content that mounts late or a focused element that is removed, but never leaves an element the user moved to; `initialFocusRef` is its deprecated form, read on open only), `openOnArrowKey` (a click panel also opens, focus inside, on the arrow key its placement points at — ArrowDown for a `bottom-*` placement; a text field trigger keeps its arrow keys), `role` (`dialog` | `menu` | `listbox`; `menu` brings no roving focus of its own), `aria-label` or `aria-labelledby` (one is required), `className`. The panel is capped to the viewport width, and only overlays opened from inside it (menus, dropdowns, modals) count as inside for outside-press dismissal. A side placement stays level with its anchor and scrolls within the room below, sliding up only when under 160px is left. Parts for the panel body: `Popover.Header` (`title`, `action`, `divider` — `false` drops the rule and sits it 4px over the list, as its label), `Popover.Action` (`icon`, `aria-label`, `onClick`, `href` (a link that opens in a new tab on Cmd/Ctrl-click or a middle click; a plain click runs `onClick` when one is given), `disabled`, `tooltip`), `Popover.List` (a scrolling list; `scrollToSelected` scrolls the list, never the page, to its selected row, one marked `aria-current`, `aria-selected` or a checked radio, each time the popover is shown, once that row mounts), `Popover.Section` (a bordered section), `Popover.Empty` (an empty-state line), `Popover.Search` (pads a `Search` above the list without a second rule), `Popover.Note` (a footnote section in small secondary text) and `Popover.Status` (`message`, `tone` (`default` | `error`), `action` (`label`, `onClick`, `pending`), `focusable`): one `status` live region for the panel's life, announcing each message it is given and staying mounted, visually hidden, while there is none, so clear `message` rather than unmounting it. Errors announce through the same region. `focusable` with a ref makes it an `initialFocus` target; resolve to it only while it has a message, since an empty one is invisible. Reach for `Status` when the message changes while the panel is open or carries an action (loading, failed, sign-in needed), and `Empty` for a static line in a list that simply has nothing in it.
|
|
29
29
|
- **`Modal`** — Centred dialog over a scrim, with header slots (icon/title/subtitle/extra), a scrolling body and a pinned footer. Locks background scroll and dismisses on ESC, the close button, or a scrim click unless `closeOnOverlayClick` is off; `busy` suppresses the two pointer routes while a request is in flight and sets `aria-busy`, so a stray click cannot tear the dialog down mid-write — but never ESC, which is the only guaranteed keyboard exit from a focus trap and so always reaches `onClose`. ESC, the Tab focus trap and `aria-modal` all apply to the topmost overlay only, so these may be stacked — a dialog opened from a panel keeps Tab to itself and the panel stops claiming to be modal. The Tab trap follows focus into a malachite popup portaled out of the dialog’s own tree — a `MultiSelect` listbox or a `Popover` opened from the body — and traps Tab within that popup while it holds focus, so Tab walks and wraps that popup’s own stops rather than being yanked back to the dialog’s first control or escaping the dialog altogether. A `Dropdown` list or an `ActionMenu` is dismissed by Tab rather than trapping it: it closes and focus returns to its trigger, so Tab carries on from there. A consumer’s own portaled popup is not known to the trap and Tab will pull focus out of it. Set `role="alertdialog"` for a consequential choice, which asks assistive tech to announce the body and not just the name, and point `aria-describedby` at whichever element carries it. Width comes from the `size` union (`small` 420px | `medium` 612px | `large` 760px) rather than a raw pixel prop. `titleAdornment` sits immediately after the title behind a hairline divider, for naming the thing being acted on — distinct from `headerExtra`, which pins to the far right. `tray` paints a tinted plate around the card and hangs a `caption` beneath it; the plate is the dialog element itself, so that caption stays inside the `aria-modal` subtree rather than being stranded outside it by the visual nesting, and it is added to `aria-describedby` automatically because it says what the dialog is asking. It takes a tone (`neutral` | `warning` | `error` | `success`) rather than a colour, so no consumer can paint it off-palette — and the tone sets the plate and its caption colour together, so the two cannot disagree. The caption is a `{ label?, message }` pair rather than a node, because its row height, type scale and colour belong to the plate. `size` names the card, and the plate’s 4px ring is added on top. Like `SidePanel` it sits in a z-index band from 1000 — one step per stacked overlay, so what paints on top is what holds the keyboard — and stays out of the browser top layer, so a host app’s own dialogs can still cover it. `callouts` stacks full-bleed `Alert` bands at the top of the body, as on `SidePanel`; they ignore `bodyPadding` and lead `aria-describedby`, ahead of the consumer’s own description and the tray caption. For a confirmation use `ConfirmationDialog`, which is a preset over this. _Key props:_ `open`, `onClose`, `title`, `subtitle`, `icon`, `headerExtra`, `titleAdornment`, `tray`, `callouts`, `renderLink`, `headerDivider`, `footer`, `size`, `showCloseButton`, `closeOnOverlayClick`, `bodyPadding`, `bodyHeight` (`auto` | `tall` — 600px, or the viewport less 160px when smaller, for an embedded iframe at `height: 100%`), `busy`, `role` (`dialog` | `alertdialog`), `aria-label`, `aria-labelledby`, `aria-describedby`.
|
|
30
|
-
- **`SidePanel`** — Right-hand slide-in overlay panel (drawer) with header slots (back/icon/title/subtitle/extra), scrollable body and optional footer. Locks background scroll and dismisses on ESC, an overlay click or close. ESC and the Tab focus trap apply to the topmost overlay only, so a `Modal` may be stacked over it. The Tab trap follows focus into a malachite popup portaled out of the panel’s own tree — a `MultiSelect` listbox or a `Popover` opened from the body — and traps Tab within that popup while it holds focus, so Tab walks and wraps that popup’s own stops rather than being yanked back to the panel’s first control or escaping the panel altogether. A `Dropdown` list or an `ActionMenu` is dismissed by Tab rather than trapping it: it closes and focus returns to its trigger, so Tab carries on from there. A consumer’s own portaled popup is not known to the trap and Tab will pull focus out of it. `callouts` stacks full-bleed `Alert` bands at the top of the body, above `children` and unaffected by `bodyPadding` — the same band `ConfirmationDialog` gives its `callout` — and `renderLink` routes their root-relative markdown links through the app’s router. The callouts are announced as the panel’s description, ahead of `description`.
|
|
30
|
+
- **`SidePanel`** — Right-hand slide-in overlay panel (drawer) with header slots (back/icon/title/subtitle/extra), scrollable body and optional footer. `subtitlePlacement="below"` stacks the subtitle on its own line under the title row, so neither truncates the other in a narrow panel. `toolbar` pins content between the header and the scrolling body (16px sides, 16px above, 8px below, no divider), such as a Search over the list it filters. Locks background scroll and dismisses on ESC, an overlay click or close. ESC and the Tab focus trap apply to the topmost overlay only, so a `Modal` may be stacked over it. The Tab trap follows focus into a malachite popup portaled out of the panel’s own tree — a `MultiSelect` listbox or a `Popover` opened from the body — and traps Tab within that popup while it holds focus, so Tab walks and wraps that popup’s own stops rather than being yanked back to the panel’s first control or escaping the panel altogether. A `Dropdown` list or an `ActionMenu` is dismissed by Tab rather than trapping it: it closes and focus returns to its trigger, so Tab carries on from there. A consumer’s own portaled popup is not known to the trap and Tab will pull focus out of it. `callouts` stacks full-bleed `Alert` bands at the top of the body, above `children` and unaffected by `bodyPadding` — the same band `ConfirmationDialog` gives its `callout` — and `renderLink` routes their root-relative markdown links through the app’s router. The callouts are announced as the panel’s description, ahead of `description`.
|
|
31
31
|
- **`useHasOpenOverlay`** — Hook reporting whether any modal-type overlay - `SidePanel` or `Modal` - is open anywhere in the app. For host chrome that has to get out of an overlay’s way, a floating support widget being the case it was added for: overlays render through a portal, so a host has no DOM signal of its own to read, and reading the library’s internal markers would couple it to something it does not own. Dropdowns and tooltips are deliberately not counted - they do not trap focus, and chrome does not need to move for them. _Key props:_ No arguments. Returns a boolean.
|
|
32
32
|
- **`SidePanelNavFooter`** — Keyboard-hint footer for SidePanel: up/down keycaps to step between records and an esc-to-close hint, with an optional right-aligned actions slot.
|
|
33
33
|
|
|
@@ -56,7 +56,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
|
|
|
56
56
|
## Lists, rows & navigation
|
|
57
57
|
|
|
58
58
|
- **`ButtonList`** — Vertical list of pill-style button/link rows with built-in hover/focus/active states. _Key props:_ `buttons[]` (`key`, `children`, `onClick`, `href`), `compact`.
|
|
59
|
-
- **`Accordion`** — Expand/collapse section with a clickable header (disclosure arrow + title/header + trailing slot). Use `header` for rich content and `trailing` for an interactive control (e.g. a Checkbox) — trailing sits outside the toggle button, so a selectable+expandable row is just an Accordion. Controlled or uncontrolled. Pass `stickyHeader` (+ optional `stickyTopOffset`) to pin the header while the body scrolls — needs a scroll ancestor that clips without trapping sticky (e.g. `<Surface overflow="clip">`). `animate={false}` opens instantly. _Key props:_ `title` or `header`, `trailing`, `disabled`, `isExpanded`/`defaultExpanded`, `onToggle`, `stickyHeader`, `stickyTopOffset`, `animate`.
|
|
59
|
+
- **`Accordion`** — Expand/collapse section with a clickable header (disclosure arrow + title/header + trailing slot). Use `header` for rich content and `trailing` for an interactive control (e.g. a Checkbox) — trailing sits outside the toggle button, so a selectable+expandable row is just an Accordion. Controlled or uncontrolled. Pass `stickyHeader` (+ optional `stickyTopOffset`) to pin the header while the body scrolls — needs a scroll ancestor that clips without trapping sticky (e.g. `<Surface overflow="clip">`). `animate={false}` opens instantly. _Key props:_ `title` or `header`, `leading` (before the title, outside the toggle, e.g. a group's tri-state Checkbox), `trailing`, `arrowPosition` (`start` | `end`), `highlightExpanded` (default `true`; `false` leaves an open row on the card ground), `disabled`, `isExpanded`/`defaultExpanded`, `onToggle`, `stickyHeader`, `stickyTopOffset`, `animate`.
|
|
60
60
|
- **`DefinitionRow`** — Row showing an icon, label, optional tag and description — for definition/detail lists. _Key props:_ `icon`, `label`, `tag`, `description`.
|
|
61
61
|
- **`PreviewTree`** — Nested, indented tree for previewing hierarchical structure.
|
|
62
62
|
- **`PreviewTreeRow`** — A single labelled row within a PreviewTree. _Key props:_ `label`.
|
|
@@ -99,7 +99,7 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
|
|
|
99
99
|
|
|
100
100
|
## Buttons & actions
|
|
101
101
|
|
|
102
|
-
- **`Button`** — Primary button. Variants: `filled`, `outline`, `surface` (outline's geometry on a ground of its own, for a control sitting on a tinted surface), `assistant` (the violet the assistant is named in — provenance, not a severity: it says who is being asked, not that anything is wrong), `destructive`, `success`, `warning`, `error`, `ghost`, `no-style`. Supports `iconOnly`, `loading`, `tooltip`. `leading` puts something that is not an icon, such as an avatar, before the label at its own size; `contentAlign="start"` starts the label at the leading edge and pins an end `icon` to the far edge, which with `flexGrow` makes a full-width switcher trigger — a project pill whose width comes from its container (e.g. a box `SIDEBAR_POPOVER_WIDTH_PX` wide, so it lines up with the `Sidebar.Panel` it opens). _Key props:_ `variant`, `size`, `icon`, `iconPosition`, `leading`, `contentAlign` (`center` | `start`), `flexGrow` (fills the container), `
|
|
102
|
+
- **`Button`** — Primary button. Variants: `filled`, `outline`, `surface` (outline's geometry on a ground of its own, for a control sitting on a tinted surface), `assistant` (the violet the assistant is named in — provenance, not a severity: it says who is being asked, not that anything is wrong), `destructive`, `success`, `warning`, `error`, `ghost`, `quiet` (text only in the accent colour, for a secondary action such as Reset beside a primary one — accent, not success, because it is something to press rather than a status), `no-style`. Supports `flush` (pulls a borderless button out by its padding so its label lines up with the text around it), `iconOnly`, `loading`, `tooltip`. `leading` puts something that is not an icon, such as an avatar, before the label at its own size; `contentAlign="start"` starts the label at the leading edge and pins an end `icon` to the far edge, which with `flexGrow` makes a full-width switcher trigger — a project pill whose width comes from its container (e.g. a box `SIDEBAR_POPOVER_WIDTH_PX` wide, so it lines up with the `Sidebar.Panel` it opens). _Key props:_ `variant`, `size`, `icon`, `iconPosition`, `leading`, `contentAlign` (`center` | `start`), `flexGrow` (fills the container), `width` (`auto` | `wide` — a 120px floor for a panel's or dialog's primary action), `iconOnly`, `loading`, `disabled`, `fill`, `tooltip`.
|
|
103
103
|
- **`CloseButton`** — The bare X that dismisses a screen, panel or wizard: secondary at rest, primary on hover and keyboard focus, with no box or background at either. Use it instead of a `ghost` `Button` (which draws a hover box) or `no-style` (which has no disabled look). `surface="inverse"` is for a panel that stays dark in every theme, such as `SplitScreenLayout`’s, and reads the `closeButton` theme tokens. `loading` swaps the X for a spinner and sets `aria-busy`. Disabled and loading both use `aria-disabled` rather than `disabled`, so they ignore clicks but stay focusable and can still trigger a `Tooltip` explaining why. Named "Close" unless `aria-label` says otherwise. _Key props:_ `onClick`, `aria-label`, `size` (`small` 24px | `medium` 32px, 12px glyph), `surface` (`default` | `inverse`), `disabled`, `loading`.
|
|
104
104
|
- **`AccessPills`** — Three toggle pills, R / W / D, saying which effects an account grants (Read, Write, Delete) — on a linked-account row and in the header above the rows. Each pill is a button with `aria-pressed`, named in full by its state (`Write: some actions off`), and a tooltip on hover and focus spells that name out, since a letter alone is cryptic. On paints the effect’s tint (Read in `Tag`’s primary, Write in warning, Delete in error, the colours the dashboard already gives these effects), `"mixed"` (some, not all, of its actions on) an empty pill with a dashed border, off a neutral one. The colours come from the theme and there is no colour prop. It reports only which pill was pressed through `onToggle`: any cascade between effects (Read off turning Write and Delete off, say) and what pressing a mixed pill means belong to the caller. A pill in `disabled` stays focusable with `aria-disabled`, ignores clicks and keys, and its tooltip shows `disabledReason` instead, which is also its accessible description. `size="dense"` draws 24×20 pills in place of 26×24, for a narrow screen. _Key props:_ `value` (`{ read, write, delete }`, each `true` | `false` | `"mixed"`), `onToggle(effect)`, `disabled` (per effect), `disabledReason` (per effect, or one string), `size` (`default` | `dense`), `aria-label`, `aria-labelledby`.
|
|
105
105
|
- **`PillButton`** — Pill-shaped button, often used for filters/toggleable chips. `shape="tab"` squares the top corners so a row of them reads as attached to whatever sits above — the controls under a chat composer. Pass `href` and an interactive, non-disabled pill renders a real anchor (cmd-click, copy-link) instead of navigating from `onClick`; a disabled or `interactive={false}` pill stays static, and an unsafe scheme such as `javascript:` is dropped. `target="_blank"` always carries `noopener noreferrer`. _Key props:_ `label`, `icon`, `trailingIcon`, `selected`, `disabled`, `interactive`, `size`, `variant`, `shape` (`pill` | `tab`), `onClick`, `href`, `target`, `rel`.
|
|
@@ -1,85 +1,91 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { useId as
|
|
3
|
-
import { DisclosureArrow as
|
|
4
|
-
import { FOCUS_RING as
|
|
5
|
-
import { isRenderableNode as
|
|
6
|
-
const
|
|
7
|
-
title:
|
|
8
|
-
header:
|
|
9
|
-
className:
|
|
1
|
+
import { jsxs as c, jsx as r } from "react/jsx-runtime";
|
|
2
|
+
import { useId as O, useState as f } from "react";
|
|
3
|
+
import { DisclosureArrow as v } from "./DisclosureArrow.js";
|
|
4
|
+
import { FOCUS_RING as U, FOCUS_RING_INSET as z } from "./focusRing.js";
|
|
5
|
+
import { isRenderableNode as h } from "./renderableNode.js";
|
|
6
|
+
const T = ({
|
|
7
|
+
title: g,
|
|
8
|
+
header: w,
|
|
9
|
+
className: N,
|
|
10
10
|
children: a,
|
|
11
|
-
trailing:
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
trailing: i,
|
|
12
|
+
leading: p,
|
|
13
|
+
arrowPosition: m = "start",
|
|
14
|
+
highlightExpanded: k = !0,
|
|
15
|
+
variant: y = "default",
|
|
16
|
+
defaultExpanded: I = !1,
|
|
14
17
|
isExpanded: n,
|
|
15
|
-
onToggle:
|
|
16
|
-
disabled:
|
|
17
|
-
stickyHeader:
|
|
18
|
-
stickyTopOffset:
|
|
19
|
-
animate:
|
|
18
|
+
onToggle: s,
|
|
19
|
+
disabled: d = !1,
|
|
20
|
+
stickyHeader: b = !1,
|
|
21
|
+
stickyTopOffset: B = 0,
|
|
22
|
+
animate: x = !0
|
|
20
23
|
}) => {
|
|
21
|
-
const
|
|
24
|
+
const t = y === "sunken", l = `${O()}-body`, [C, R] = f(I), e = n !== void 0 ? n : C, S = x || e && !!a, [j, $] = f(!1), E = () => {
|
|
22
25
|
const o = !e;
|
|
23
|
-
n === void 0 &&
|
|
24
|
-
},
|
|
26
|
+
n === void 0 && R(o), s == null || s(o);
|
|
27
|
+
}, _ = [
|
|
25
28
|
"flex flex-col w-full text-left",
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
].filter(Boolean).join(" "),
|
|
29
|
+
t ? "rounded-xl bg-[var(--malachite-card-dimmed-background)] p-[4px]" : "",
|
|
30
|
+
N
|
|
31
|
+
].filter(Boolean).join(" "), A = [
|
|
29
32
|
// `group` only when enabled, so the arrow doesn't darken on hover while disabled.
|
|
30
|
-
|
|
33
|
+
d ? "" : "group",
|
|
31
34
|
"flex items-center w-full transition-colors",
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
].filter(Boolean).join(" "),
|
|
35
|
+
t ? "" : "border-0 border-b border-solid border-[var(--malachite-card-border)]",
|
|
36
|
+
b ? "sticky z-10" : "",
|
|
37
|
+
t ? "bg-transparent" : e && k ? "bg-[var(--malachite-buttonlist-hover-background)]" : d ? "bg-transparent" : "bg-transparent hover:bg-[var(--malachite-buttonlist-hover-background)]"
|
|
38
|
+
].filter(Boolean).join(" "), u = h(p), F = [
|
|
36
39
|
"flex flex-1 items-center min-w-0 appearance-none border-0 bg-transparent text-left cursor-pointer select-none disabled:cursor-not-allowed",
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
t ? `gap-[16px] px-[16px] py-[12px] rounded-lg ${U}` : `gap-[12px] p-[16px] ${z}`,
|
|
41
|
+
u ? "!pl-[12px]" : ""
|
|
42
|
+
].filter(Boolean).join(" "), G = b ? {
|
|
43
|
+
top: `${B}px`,
|
|
44
|
+
backgroundColor: t ? "var(--malachite-card-dimmed-background)" : "var(--malachite-card-background)"
|
|
41
45
|
} : {};
|
|
42
|
-
return /* @__PURE__ */
|
|
43
|
-
/* @__PURE__ */
|
|
44
|
-
/* @__PURE__ */
|
|
46
|
+
return /* @__PURE__ */ c("div", { className: _, children: [
|
|
47
|
+
/* @__PURE__ */ c("div", { className: A, style: G, "data-accordion-header": "", children: [
|
|
48
|
+
u && /* @__PURE__ */ r("div", { className: "shrink-0 flex items-center pl-[16px]", children: p }),
|
|
49
|
+
/* @__PURE__ */ c(
|
|
45
50
|
"button",
|
|
46
51
|
{
|
|
47
52
|
type: "button",
|
|
48
|
-
className:
|
|
49
|
-
onClick:
|
|
53
|
+
className: F,
|
|
54
|
+
onClick: E,
|
|
50
55
|
"aria-expanded": e,
|
|
51
|
-
"aria-controls":
|
|
52
|
-
disabled:
|
|
56
|
+
"aria-controls": S ? l : void 0,
|
|
57
|
+
disabled: d,
|
|
53
58
|
children: [
|
|
54
|
-
/* @__PURE__ */
|
|
55
|
-
|
|
59
|
+
m === "start" && /* @__PURE__ */ r(v, { expanded: e }),
|
|
60
|
+
w ?? /* @__PURE__ */ r("span", { className: "flex-1 min-w-0 truncate font-medium text-[13px] tracking-[0.13px] text-[var(--malachite-text-primary)]", children: g }),
|
|
61
|
+
m === "end" && /* @__PURE__ */ r(v, { expanded: e, className: "ml-auto" })
|
|
56
62
|
]
|
|
57
63
|
}
|
|
58
64
|
),
|
|
59
|
-
|
|
65
|
+
h(i) && /* @__PURE__ */ r("div", { className: "shrink-0 flex items-center pr-[16px] text-[var(--malachite-text-secondary)]", children: i })
|
|
60
66
|
] }),
|
|
61
|
-
|
|
67
|
+
x ? (
|
|
62
68
|
/* Height animates via grid-template-rows (0fr → 1fr); body clips during.
|
|
63
69
|
grid-cols-[minmax(0,1fr)] pins the single column to the track width so
|
|
64
70
|
wide, non-wrapping children (e.g. a table with long cell text) can't grow
|
|
65
71
|
the implicit `auto` column to their max-content and overflow the container. */
|
|
66
|
-
/* @__PURE__ */
|
|
72
|
+
/* @__PURE__ */ r(
|
|
67
73
|
"div",
|
|
68
74
|
{
|
|
69
75
|
id: l,
|
|
70
76
|
className: "grid grid-cols-[minmax(0,1fr)] w-full transition-[grid-template-rows] duration-200 ease-in-out",
|
|
71
77
|
style: { gridTemplateRows: e ? "1fr" : "0fr" },
|
|
72
78
|
onTransitionEnd: (o) => {
|
|
73
|
-
o.propertyName === "grid-template-rows" &&
|
|
79
|
+
o.propertyName === "grid-template-rows" && $(e);
|
|
74
80
|
},
|
|
75
|
-
children: /* @__PURE__ */
|
|
81
|
+
children: /* @__PURE__ */ r(
|
|
76
82
|
"div",
|
|
77
83
|
{
|
|
78
|
-
className:
|
|
79
|
-
children: a && /* @__PURE__ */
|
|
84
|
+
className: j && e ? "overflow-visible" : "overflow-hidden",
|
|
85
|
+
children: a && /* @__PURE__ */ r(
|
|
80
86
|
"div",
|
|
81
87
|
{
|
|
82
|
-
className:
|
|
88
|
+
className: t ? "pt-[4px]" : "border-b border-solid border-[var(--malachite-card-border)]",
|
|
83
89
|
children: a
|
|
84
90
|
}
|
|
85
91
|
)
|
|
@@ -89,11 +95,11 @@ const K = ({
|
|
|
89
95
|
)
|
|
90
96
|
) : (
|
|
91
97
|
/* Instant open — no clip, so a sticky child inside pins immediately. */
|
|
92
|
-
e && a && /* @__PURE__ */
|
|
98
|
+
e && a && /* @__PURE__ */ r(
|
|
93
99
|
"div",
|
|
94
100
|
{
|
|
95
101
|
id: l,
|
|
96
|
-
className:
|
|
102
|
+
className: t ? "pt-[4px]" : "border-b border-solid border-[var(--malachite-card-border)]",
|
|
97
103
|
children: a
|
|
98
104
|
}
|
|
99
105
|
)
|
|
@@ -101,7 +107,7 @@ const K = ({
|
|
|
101
107
|
] });
|
|
102
108
|
};
|
|
103
109
|
export {
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
T as Accordion,
|
|
111
|
+
T as default
|
|
106
112
|
};
|
|
107
113
|
//# sourceMappingURL=Accordion.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Accordion.js","sources":["../../src/components/Accordion.tsx"],"sourcesContent":["import React, { useId, useState } from 'react';\nimport DisclosureArrow from './DisclosureArrow';\nimport { FOCUS_RING, FOCUS_RING_INSET } from './focusRing';\nimport { isRenderableNode } from './renderableNode';\n\ninterface AccordionBaseProps {\n className?: string;\n children?: React.ReactNode;\n /**\n * Visual style. `default` is the flat list row (row dividers, hover background).\n * `sunken` draws the section as a sunken card (dimmed rounded container, no\n * dividers/hover) — used for stacked detail sections in side panels and detail\n * pages (see `CollapsibleSection`). Both lead with the arrow: it is the affordance\n * that says the section opens, and a reader scanning a stack of them finds it in\n * one column rather than at whatever width each header happens to end.\n */\n variant?: 'default' | 'sunken';\n /**\n * Content pinned to the right of the header, OUTSIDE the toggle button — so it may\n * be interactive (e.g. a Checkbox) without nesting a button inside a button.\n */\n trailing?: React.ReactNode;\n defaultExpanded?: boolean;\n isExpanded?: boolean;\n onToggle?: (expanded: boolean) => void;\n /** Disables the toggle (and its hover). Trailing controls manage their own state. */\n disabled?: boolean;\n /**\n * Pin the header row to the top of the nearest scroll container so it stays\n * reachable while the body scrolls. Needs a scroll ancestor that clips without\n * trapping sticky — e.g. a `<Surface overflow=\"clip\">` (`overflow: hidden` traps it).\n */\n stickyHeader?: boolean;\n /** Offset (px) from the top of the scroll container when `stickyHeader` is set. */\n stickyTopOffset?: number;\n /**\n * Animate the expand/collapse height (default `true`). Set `false` for an instant\n * open — the body isn't clipped, so a `position: sticky` child pins immediately with\n * no post-animation settle.\n */\n animate?: boolean;\n}\n\n/**\n * Requires at least one of `title` (simple text header) or `header` (rich ReactNode\n * content for the toggle area, e.g. avatar + badges) so the toggle is never empty.\n */\ntype AccordionProps = AccordionBaseProps &\n ({ title: string; header?: React.ReactNode } | { title?: string; header: React.ReactNode });\n\nconst Accordion: React.FC<AccordionProps> = ({\n title,\n header,\n className,\n children,\n trailing,\n variant = 'default',\n defaultExpanded = false,\n isExpanded,\n onToggle,\n disabled = false,\n stickyHeader = false,\n stickyTopOffset = 0,\n animate = true,\n}) => {\n const sunken = variant === 'sunken';\n const instanceId = useId();\n const bodyId = `${instanceId}-body`;\n const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);\n\n // Use controlled state if isExpanded is provided, otherwise use internal state\n const expanded = isExpanded !== undefined ? isExpanded : internalExpanded;\n\n // The instant path only renders the body while expanded; don't advertise\n // aria-controls when the referenced element isn't in the DOM.\n const bodyRendered = animate || (expanded && !!children);\n\n // The body clips overflow so the expand/collapse height animation works. Once fully\n // expanded we release the clip (after the transition) so a `position: sticky` child\n // inside can pin — `overflow: hidden` would otherwise trap it.\n //\n // Owned entirely by the transition: the same `onTransitionEnd` that sets it on expand\n // clears it on collapse, so no effect has to mirror `expanded` into it.\n const [bodySettled, setBodySettled] = useState(false);\n\n const handleToggle = () => {\n const newExpanded = !expanded;\n if (isExpanded === undefined) {\n setInternalExpanded(newExpanded);\n }\n onToggle?.(newExpanded);\n };\n\n const containerClasses = [\n 'flex flex-col w-full text-left',\n sunken ? 'rounded-xl bg-[var(--malachite-card-dimmed-background)] p-[4px]' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n // The whole header row is a `group` so the disclosure arrow darkens on hover; the\n // hover background and arrow react to hovering anywhere on the row. The sunken\n // variant carries its own card chrome instead of row dividers/hover.\n const rowBackground = sunken\n ? 'bg-transparent'\n : expanded\n ? 'bg-[var(--malachite-buttonlist-hover-background)]'\n : disabled\n ? 'bg-transparent'\n : 'bg-transparent hover:bg-[var(--malachite-buttonlist-hover-background)]';\n const headerRowClasses = [\n // `group` only when enabled, so the arrow doesn't darken on hover while disabled.\n disabled ? '' : 'group',\n 'flex items-center w-full transition-colors',\n sunken ? '' : 'border-0 border-b border-solid border-[var(--malachite-card-border)]',\n stickyHeader ? 'sticky z-10' : '',\n rowBackground,\n ]\n .filter(Boolean)\n .join(' ');\n // Full-bleed default rows use an inset ring; the padded sunken toggle fits an outset one.\n const toggleButtonClasses = [\n 'flex flex-1 items-center min-w-0 appearance-none border-0 bg-transparent text-left cursor-pointer select-none disabled:cursor-not-allowed',\n sunken\n ? `gap-[16px] px-[16px] py-[12px] rounded-lg ${FOCUS_RING}`\n : `gap-[12px] p-[16px] ${FOCUS_RING_INSET}`,\n ].join(' ');\n\n // A pinned header needs an opaque background so the scrolling body doesn't bleed\n // through. Inline (not a Tailwind arbitrary class) so the offset always applies. It is\n // the variant's own ground, not the card's: a sunken section pinned in card white is a\n // white bar floating over the grey it belongs to.\n const headerRowStyle: React.CSSProperties = stickyHeader\n ? {\n top: `${stickyTopOffset}px`,\n backgroundColor: sunken\n ? 'var(--malachite-card-dimmed-background)'\n : 'var(--malachite-card-background)',\n }\n : {};\n\n return (\n <div className={containerClasses}>\n <div className={headerRowClasses} style={headerRowStyle} data-accordion-header=\"\">\n {/* Toggle area — arrow + header/title. Interactive trailing sits outside it. */}\n <button\n type=\"button\"\n className={toggleButtonClasses}\n onClick={handleToggle}\n aria-expanded={expanded}\n aria-controls={bodyRendered ? bodyId : undefined}\n disabled={disabled}\n >\n <DisclosureArrow expanded={expanded} />\n {header ?? (\n <span className=\"flex-1 min-w-0 truncate font-medium text-[13px] tracking-[0.13px] text-[var(--malachite-text-primary)]\">\n {title}\n </span>\n )}\n </button>\n {isRenderableNode(trailing) && (\n <div className=\"shrink-0 flex items-center pr-[16px] text-[var(--malachite-text-secondary)]\">\n {trailing}\n </div>\n )}\n </div>\n\n {animate ? (\n /* Height animates via grid-template-rows (0fr → 1fr); body clips during.\n grid-cols-[minmax(0,1fr)] pins the single column to the track width so\n wide, non-wrapping children (e.g. a table with long cell text) can't grow\n the implicit `auto` column to their max-content and overflow the container. */\n <div\n id={bodyId}\n className=\"grid grid-cols-[minmax(0,1fr)] w-full transition-[grid-template-rows] duration-200 ease-in-out\"\n style={{ gridTemplateRows: expanded ? '1fr' : '0fr' }}\n onTransitionEnd={(event) => {\n if (event.propertyName === 'grid-template-rows') {\n setBodySettled(expanded);\n }\n }}\n >\n <div\n className={bodySettled && expanded ? 'overflow-visible' : 'overflow-hidden'}\n >\n {children && (\n <div\n className={\n sunken\n ? 'pt-[4px]'\n : 'border-b border-solid border-[var(--malachite-card-border)]'\n }\n >\n {children}\n </div>\n )}\n </div>\n </div>\n ) : (\n /* Instant open — no clip, so a sticky child inside pins immediately. */\n expanded &&\n children && (\n <div\n id={bodyId}\n className={\n sunken\n ? 'pt-[4px]'\n : 'border-b border-solid border-[var(--malachite-card-border)]'\n }\n >\n {children}\n </div>\n )\n )}\n </div>\n );\n};\n\nexport default Accordion;\nexport { Accordion };\nexport type { AccordionProps };\n"],"names":["Accordion","title","header","className","children","trailing","variant","defaultExpanded","isExpanded","onToggle","disabled","stickyHeader","stickyTopOffset","animate","sunken","bodyId","useId","internalExpanded","setInternalExpanded","useState","expanded","bodyRendered","bodySettled","setBodySettled","handleToggle","newExpanded","containerClasses","headerRowClasses","toggleButtonClasses","FOCUS_RING","FOCUS_RING_INSET","headerRowStyle","jsxs","jsx","DisclosureArrow","isRenderableNode","event"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Accordion.js","sources":["../../src/components/Accordion.tsx"],"sourcesContent":["import React, { useId, useState } from 'react';\nimport DisclosureArrow from './DisclosureArrow';\nimport { FOCUS_RING, FOCUS_RING_INSET } from './focusRing';\nimport { isRenderableNode } from './renderableNode';\n\ninterface AccordionBaseProps {\n className?: string;\n children?: React.ReactNode;\n /**\n * Visual style. `default` is the flat list row (row dividers, hover background).\n * `sunken` draws the section as a sunken card (dimmed rounded container, no\n * dividers/hover) — used for stacked detail sections in side panels and detail\n * pages (see `CollapsibleSection`). Both lead with the arrow: it is the affordance\n * that says the section opens, and a reader scanning a stack of them finds it in\n * one column rather than at whatever width each header happens to end.\n */\n variant?: 'default' | 'sunken';\n /**\n * Content pinned to the right of the header, OUTSIDE the toggle button — so it may\n * be interactive (e.g. a Checkbox) without nesting a button inside a button.\n */\n trailing?: React.ReactNode;\n /** Before the title, outside the toggle button. */\n leading?: React.ReactNode;\n arrowPosition?: 'start' | 'end';\n /** `false` keeps an open row on the card ground. */\n highlightExpanded?: boolean;\n defaultExpanded?: boolean;\n isExpanded?: boolean;\n onToggle?: (expanded: boolean) => void;\n /** Disables the toggle (and its hover). Trailing controls manage their own state. */\n disabled?: boolean;\n /**\n * Pin the header row to the top of the nearest scroll container so it stays\n * reachable while the body scrolls. Needs a scroll ancestor that clips without\n * trapping sticky — e.g. a `<Surface overflow=\"clip\">` (`overflow: hidden` traps it).\n */\n stickyHeader?: boolean;\n /** Offset (px) from the top of the scroll container when `stickyHeader` is set. */\n stickyTopOffset?: number;\n /**\n * Animate the expand/collapse height (default `true`). Set `false` for an instant\n * open — the body isn't clipped, so a `position: sticky` child pins immediately with\n * no post-animation settle.\n */\n animate?: boolean;\n}\n\n/**\n * Requires at least one of `title` (simple text header) or `header` (rich ReactNode\n * content for the toggle area, e.g. avatar + badges) so the toggle is never empty.\n */\ntype AccordionProps = AccordionBaseProps &\n ({ title: string; header?: React.ReactNode } | { title?: string; header: React.ReactNode });\n\nconst Accordion: React.FC<AccordionProps> = ({\n title,\n header,\n className,\n children,\n trailing,\n leading,\n arrowPosition = 'start',\n highlightExpanded = true,\n variant = 'default',\n defaultExpanded = false,\n isExpanded,\n onToggle,\n disabled = false,\n stickyHeader = false,\n stickyTopOffset = 0,\n animate = true,\n}) => {\n const sunken = variant === 'sunken';\n const instanceId = useId();\n const bodyId = `${instanceId}-body`;\n const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);\n\n // Use controlled state if isExpanded is provided, otherwise use internal state\n const expanded = isExpanded !== undefined ? isExpanded : internalExpanded;\n\n // The instant path only renders the body while expanded; don't advertise\n // aria-controls when the referenced element isn't in the DOM.\n const bodyRendered = animate || (expanded && !!children);\n\n // The body clips overflow so the expand/collapse height animation works. Once fully\n // expanded we release the clip (after the transition) so a `position: sticky` child\n // inside can pin — `overflow: hidden` would otherwise trap it.\n //\n // Owned entirely by the transition: the same `onTransitionEnd` that sets it on expand\n // clears it on collapse, so no effect has to mirror `expanded` into it.\n const [bodySettled, setBodySettled] = useState(false);\n\n const handleToggle = () => {\n const newExpanded = !expanded;\n if (isExpanded === undefined) {\n setInternalExpanded(newExpanded);\n }\n onToggle?.(newExpanded);\n };\n\n const containerClasses = [\n 'flex flex-col w-full text-left',\n sunken ? 'rounded-xl bg-[var(--malachite-card-dimmed-background)] p-[4px]' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n // The whole header row is a `group` so the disclosure arrow darkens on hover; the\n // hover background and arrow react to hovering anywhere on the row. The sunken\n // variant carries its own card chrome instead of row dividers/hover.\n const rowBackground = sunken\n ? 'bg-transparent'\n : expanded && highlightExpanded\n ? 'bg-[var(--malachite-buttonlist-hover-background)]'\n : disabled\n ? 'bg-transparent'\n : 'bg-transparent hover:bg-[var(--malachite-buttonlist-hover-background)]';\n const headerRowClasses = [\n // `group` only when enabled, so the arrow doesn't darken on hover while disabled.\n disabled ? '' : 'group',\n 'flex items-center w-full transition-colors',\n sunken ? '' : 'border-0 border-b border-solid border-[var(--malachite-card-border)]',\n stickyHeader ? 'sticky z-10' : '',\n rowBackground,\n ]\n .filter(Boolean)\n .join(' ');\n const hasLeading = isRenderableNode(leading);\n // Full-bleed default rows use an inset ring; the padded sunken toggle fits an outset one.\n const toggleButtonClasses = [\n 'flex flex-1 items-center min-w-0 appearance-none border-0 bg-transparent text-left cursor-pointer select-none disabled:cursor-not-allowed',\n sunken\n ? `gap-[16px] px-[16px] py-[12px] rounded-lg ${FOCUS_RING}`\n : `gap-[12px] p-[16px] ${FOCUS_RING_INSET}`,\n hasLeading ? '!pl-[12px]' : '',\n ]\n .filter(Boolean)\n .join(' ');\n\n // A pinned header needs an opaque background so the scrolling body doesn't bleed\n // through. Inline (not a Tailwind arbitrary class) so the offset always applies. It is\n // the variant's own ground, not the card's: a sunken section pinned in card white is a\n // white bar floating over the grey it belongs to.\n const headerRowStyle: React.CSSProperties = stickyHeader\n ? {\n top: `${stickyTopOffset}px`,\n backgroundColor: sunken\n ? 'var(--malachite-card-dimmed-background)'\n : 'var(--malachite-card-background)',\n }\n : {};\n\n return (\n <div className={containerClasses}>\n <div className={headerRowClasses} style={headerRowStyle} data-accordion-header=\"\">\n {hasLeading && (\n <div className=\"shrink-0 flex items-center pl-[16px]\">{leading}</div>\n )}\n {/* Toggle area — arrow + header/title. Interactive trailing sits outside it. */}\n <button\n type=\"button\"\n className={toggleButtonClasses}\n onClick={handleToggle}\n aria-expanded={expanded}\n aria-controls={bodyRendered ? bodyId : undefined}\n disabled={disabled}\n >\n {arrowPosition === 'start' && <DisclosureArrow expanded={expanded} />}\n {header ?? (\n <span className=\"flex-1 min-w-0 truncate font-medium text-[13px] tracking-[0.13px] text-[var(--malachite-text-primary)]\">\n {title}\n </span>\n )}\n {arrowPosition === 'end' && (\n <DisclosureArrow expanded={expanded} className=\"ml-auto\" />\n )}\n </button>\n {isRenderableNode(trailing) && (\n <div className=\"shrink-0 flex items-center pr-[16px] text-[var(--malachite-text-secondary)]\">\n {trailing}\n </div>\n )}\n </div>\n\n {animate ? (\n /* Height animates via grid-template-rows (0fr → 1fr); body clips during.\n grid-cols-[minmax(0,1fr)] pins the single column to the track width so\n wide, non-wrapping children (e.g. a table with long cell text) can't grow\n the implicit `auto` column to their max-content and overflow the container. */\n <div\n id={bodyId}\n className=\"grid grid-cols-[minmax(0,1fr)] w-full transition-[grid-template-rows] duration-200 ease-in-out\"\n style={{ gridTemplateRows: expanded ? '1fr' : '0fr' }}\n onTransitionEnd={(event) => {\n if (event.propertyName === 'grid-template-rows') {\n setBodySettled(expanded);\n }\n }}\n >\n <div\n className={bodySettled && expanded ? 'overflow-visible' : 'overflow-hidden'}\n >\n {children && (\n <div\n className={\n sunken\n ? 'pt-[4px]'\n : 'border-b border-solid border-[var(--malachite-card-border)]'\n }\n >\n {children}\n </div>\n )}\n </div>\n </div>\n ) : (\n /* Instant open — no clip, so a sticky child inside pins immediately. */\n expanded &&\n children && (\n <div\n id={bodyId}\n className={\n sunken\n ? 'pt-[4px]'\n : 'border-b border-solid border-[var(--malachite-card-border)]'\n }\n >\n {children}\n </div>\n )\n )}\n </div>\n );\n};\n\nexport default Accordion;\nexport { Accordion };\nexport type { AccordionProps };\n"],"names":["Accordion","title","header","className","children","trailing","leading","arrowPosition","highlightExpanded","variant","defaultExpanded","isExpanded","onToggle","disabled","stickyHeader","stickyTopOffset","animate","sunken","bodyId","useId","internalExpanded","setInternalExpanded","useState","expanded","bodyRendered","bodySettled","setBodySettled","handleToggle","newExpanded","containerClasses","headerRowClasses","hasLeading","isRenderableNode","toggleButtonClasses","FOCUS_RING","FOCUS_RING_INSET","headerRowStyle","jsxs","jsx","DisclosureArrow","event"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as r, jsxs as i, Fragment as d } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef as
|
|
3
|
-
import
|
|
4
|
-
import { getIconOnlySizeClasses as
|
|
2
|
+
import { forwardRef as E } from "react";
|
|
3
|
+
import H from "./Tooltip.js";
|
|
4
|
+
import { getIconOnlySizeClasses as M, getIconSizeClass as q } from "./buttonSizing.js";
|
|
5
5
|
import { resolveRel as D } from "./linkRel.js";
|
|
6
6
|
const O = (t) => {
|
|
7
7
|
switch (t) {
|
|
@@ -57,8 +57,9 @@ const O = (t) => {
|
|
|
57
57
|
success: "focus-visible:[outline:2px_solid_var(--malachite-text-success)]",
|
|
58
58
|
warning: "focus-visible:[outline:2px_solid_var(--malachite-text-warning)]",
|
|
59
59
|
error: "focus-visible:[outline:2px_solid_var(--malachite-text-error)]",
|
|
60
|
-
assistant: "focus-visible:[outline:2px_solid_var(--malachite-assistant-foreground)]"
|
|
61
|
-
|
|
60
|
+
assistant: "focus-visible:[outline:2px_solid_var(--malachite-assistant-foreground)]",
|
|
61
|
+
accent: "focus-visible:[outline:2px_solid_var(--malachite-accent-foreground)]"
|
|
62
|
+
}, J = (t, a) => {
|
|
62
63
|
const e = `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 ${a} cursor-pointer`;
|
|
63
64
|
switch (t) {
|
|
64
65
|
case "filled":
|
|
@@ -79,12 +80,14 @@ const O = (t) => {
|
|
|
79
80
|
return `${e} ${s.error} btn-glass-error`;
|
|
80
81
|
case "ghost":
|
|
81
82
|
return `${e} ${s.neutral} btn-ghost`;
|
|
83
|
+
case "quiet":
|
|
84
|
+
return `${e} ${s.accent} btn-quiet`;
|
|
82
85
|
case "no-style":
|
|
83
86
|
return `${e} ${s.neutral} btn-no-style border-none`;
|
|
84
87
|
default:
|
|
85
88
|
return `${e} ${s.neutral} btn-filled shadow-default`;
|
|
86
89
|
}
|
|
87
|
-
},
|
|
90
|
+
}, K = () => /* @__PURE__ */ i(
|
|
88
91
|
"svg",
|
|
89
92
|
{
|
|
90
93
|
className: "animate-spin -ml-1 mr-2 h-4 w-4",
|
|
@@ -113,7 +116,7 @@ const O = (t) => {
|
|
|
113
116
|
)
|
|
114
117
|
]
|
|
115
118
|
}
|
|
116
|
-
),
|
|
119
|
+
), Q = E(
|
|
117
120
|
({
|
|
118
121
|
variant: t,
|
|
119
122
|
size: a = "large",
|
|
@@ -125,28 +128,30 @@ const O = (t) => {
|
|
|
125
128
|
leading: v,
|
|
126
129
|
contentAlign: k = "center",
|
|
127
130
|
flexGrow: m = !1,
|
|
128
|
-
|
|
131
|
+
width: j = "auto",
|
|
132
|
+
fill: B = !1,
|
|
129
133
|
tooltip: x,
|
|
130
|
-
tooltipPlacement:
|
|
134
|
+
tooltipPlacement: L = "top",
|
|
131
135
|
iconOnly: p = !1,
|
|
132
|
-
flush:
|
|
136
|
+
flush: z = !1,
|
|
133
137
|
children: n,
|
|
134
|
-
className:
|
|
138
|
+
className: F = "",
|
|
135
139
|
href: w,
|
|
136
140
|
target: $,
|
|
137
|
-
rel:
|
|
138
|
-
...
|
|
139
|
-
},
|
|
140
|
-
const
|
|
141
|
+
rel: U,
|
|
142
|
+
..._
|
|
143
|
+
}, C) => {
|
|
144
|
+
const A = e ? G(e) : W(a, p), l = I || g, N = k === "start", y = [
|
|
141
145
|
N ? "flex items-center justify-start text-left" : "flex items-center justify-center",
|
|
142
|
-
p ? `p-0 ${
|
|
143
|
-
|
|
146
|
+
p ? `p-0 ${M(a)}` : O(a),
|
|
147
|
+
J(t, A),
|
|
144
148
|
m ? "w-full" : "",
|
|
145
|
-
j ? "
|
|
149
|
+
j === "wide" ? "min-w-[120px]" : "",
|
|
150
|
+
B ? "max-w-full" : "",
|
|
146
151
|
l ? "cursor-not-allowed" : "",
|
|
147
152
|
x && l ? "pointer-events-none" : "",
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
z && !p ? t === "no-style" ? V : T : "",
|
|
154
|
+
F
|
|
150
155
|
].filter(Boolean).join(" "), o = N ? /* @__PURE__ */ r("span", { className: "flex min-w-0 flex-1 items-center", children: n }) : n, u = v ? /* @__PURE__ */ r(
|
|
151
156
|
"span",
|
|
152
157
|
{
|
|
@@ -157,10 +162,10 @@ const O = (t) => {
|
|
|
157
162
|
if (g)
|
|
158
163
|
return /* @__PURE__ */ i(d, { children: [
|
|
159
164
|
u,
|
|
160
|
-
/* @__PURE__ */ r(
|
|
165
|
+
/* @__PURE__ */ r(K, {}),
|
|
161
166
|
o
|
|
162
167
|
] });
|
|
163
|
-
const f = `inline-flex items-center shrink-0 ${
|
|
168
|
+
const f = `inline-flex items-center shrink-0 ${q(a)}`;
|
|
164
169
|
return c && b === "start" ? /* @__PURE__ */ i(d, { children: [
|
|
165
170
|
u,
|
|
166
171
|
/* @__PURE__ */ r(
|
|
@@ -188,30 +193,30 @@ const O = (t) => {
|
|
|
188
193
|
}, h = w && !l ? /* @__PURE__ */ r(
|
|
189
194
|
"a",
|
|
190
195
|
{
|
|
191
|
-
ref:
|
|
196
|
+
ref: C,
|
|
192
197
|
href: w,
|
|
193
198
|
target: $,
|
|
194
|
-
rel: D(
|
|
199
|
+
rel: D(U, $),
|
|
195
200
|
className: y,
|
|
196
|
-
...
|
|
201
|
+
..._,
|
|
197
202
|
children: S()
|
|
198
203
|
}
|
|
199
204
|
) : /* @__PURE__ */ r(
|
|
200
205
|
"button",
|
|
201
206
|
{
|
|
202
|
-
ref:
|
|
207
|
+
ref: C,
|
|
203
208
|
disabled: l,
|
|
204
209
|
className: y,
|
|
205
|
-
...
|
|
210
|
+
..._,
|
|
206
211
|
children: S()
|
|
207
212
|
}
|
|
208
213
|
);
|
|
209
|
-
return x ? /* @__PURE__ */ r(
|
|
214
|
+
return x ? /* @__PURE__ */ r(H, { text: x, placement: L, fill: m, children: l ? /* @__PURE__ */ r("span", { className: `inline-flex cursor-not-allowed${m ? " w-full" : ""}`, children: h }) : h }) : h;
|
|
210
215
|
}
|
|
211
216
|
);
|
|
212
|
-
|
|
217
|
+
Q.displayName = "Button";
|
|
213
218
|
export {
|
|
214
|
-
|
|
215
|
-
|
|
219
|
+
Q as Button,
|
|
220
|
+
Q as default
|
|
216
221
|
};
|
|
217
222
|
//# sourceMappingURL=Button.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.js","sources":["../../src/components/Button.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport Tooltip from './Tooltip';\nimport { getIconOnlySizeClasses, getIconSizeClass } from './buttonSizing';\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 /** Before the label at its own size, never squeezed (an avatar); pairs with an end `icon`. */\n leading?: React.ReactNode;\n /** `start` aligns the label to the leading edge and pins an end `icon` to the far edge. */\n contentAlign?: 'center' | 'start';\n /** Fills the container's width. */\n flexGrow?: boolean;\n fill?: boolean;\n tooltip?: string;\n tooltipPlacement?: 'top' | 'bottom' | 'left' | 'right';\n iconOnly?: boolean;\n /** Pulls a borderless button out by its own padding, so its label lines up with the text around it. */\n flush?: 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 '[--btn-px:calc(var(--spacing)*2)] px-[var(--btn-px)] py-1 text-xs';\n case 'small':\n return 'min-h-[var(--malachite-control-height-small,28px)] [--btn-px:10px] px-[var(--btn-px)] 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)] [--btn-px:10px] px-[var(--btn-px)] 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)] [--btn-px:12px] px-[var(--btn-px)] text-[length:var(--malachite-font-size-15,15px)] leading-[18px] whitespace-nowrap';\n default:\n return 'min-h-[var(--malachite-control-height-large,42px)] [--btn-px:12px] px-[var(--btn-px)] text-[length:var(--malachite-font-size-15,15px)] leading-[18px] whitespace-nowrap';\n }\n};\n\n// The 1px is the transparent border, which `no-style` drops.\nconst FLUSH_MARGIN = '-mx-[calc(var(--btn-px)+1px)]';\nconst FLUSH_MARGIN_BORDERLESS = '-mx-[var(--btn-px)]';\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 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 leading,\n contentAlign = 'center',\n flexGrow = false,\n fill = false,\n tooltip,\n tooltipPlacement = 'top',\n iconOnly = false,\n flush = 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 alignStart = contentAlign === 'start';\n const baseClasses = [\n alignStart\n ? 'flex items-center justify-start text-left'\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 flush && !iconOnly\n ? variant === 'no-style'\n ? FLUSH_MARGIN_BORDERLESS\n : FLUSH_MARGIN\n : '',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const label = alignStart ? (\n <span className=\"flex min-w-0 flex-1 items-center\">{children}</span>\n ) : (\n children\n );\n\n const leadingSlot = leading ? (\n <span\n className={\n children\n ? 'btn-leading inline-flex shrink-0 items-center me-2'\n : 'btn-leading inline-flex shrink-0 items-center'\n }\n >\n {leading}\n </span>\n ) : null;\n\n const renderContent = () => {\n if (loading) {\n return (\n <>\n {leadingSlot}\n <LoadingSpinner />\n {label}\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 {leadingSlot}\n <span\n className={`${iconWrapperClasses}${children ? ` ${getIconGapClass(size, 'start')}` : ''}`}\n >\n {icon}\n </span>\n {label}\n </>\n );\n }\n\n if (icon && iconPosition === 'end') {\n return (\n <>\n {leadingSlot}\n {label}\n <span\n className={`${iconWrapperClasses}${children ? ` ${getIconGapClass(size, 'end')}` : ''}`}\n >\n {icon}\n </span>\n </>\n );\n }\n\n return (\n <>\n {leadingSlot}\n {label}\n </>\n );\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","FLUSH_MARGIN","FLUSH_MARGIN_BORDERLESS","getDefaultRadiusClass","iconOnly","getIconGapClass","position","start","getRadiusClass","radius","FOCUS_OUTLINE","getVariantClasses","variant","radiusClass","baseClasses","LoadingSpinner","jsxs","jsx","Button","forwardRef","loading","disabled","icon","iconPosition","leading","contentAlign","flexGrow","fill","tooltip","tooltipPlacement","flush","children","className","href","target","rel","props","ref","isInactive","alignStart","getIconOnlySizeClasses","label","leadingSlot","renderContent","Fragment","iconWrapperClasses","getIconSizeClass","button","resolveRel","Tooltip"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Button.js","sources":["../../src/components/Button.tsx"],"sourcesContent":["import React, { forwardRef } from 'react';\nimport Tooltip from './Tooltip';\nimport { getIconOnlySizeClasses, getIconSizeClass } from './buttonSizing';\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 /** Text only, in the accent colour, for a secondary action beside a primary one. */\n | 'quiet'\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 /** Before the label at its own size, never squeezed (an avatar); pairs with an end `icon`. */\n leading?: React.ReactNode;\n /** `start` aligns the label to the leading edge and pins an end `icon` to the far edge. */\n contentAlign?: 'center' | 'start';\n /** Fills the container's width. */\n flexGrow?: boolean;\n /** `wide` holds a 120px floor for a primary action. */\n width?: 'auto' | 'wide';\n fill?: boolean;\n tooltip?: string;\n tooltipPlacement?: 'top' | 'bottom' | 'left' | 'right';\n iconOnly?: boolean;\n /** Pulls a borderless button out by its own padding, so its label lines up with the text around it. */\n flush?: 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 '[--btn-px:calc(var(--spacing)*2)] px-[var(--btn-px)] py-1 text-xs';\n case 'small':\n return 'min-h-[var(--malachite-control-height-small,28px)] [--btn-px:10px] px-[var(--btn-px)] 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)] [--btn-px:10px] px-[var(--btn-px)] 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)] [--btn-px:12px] px-[var(--btn-px)] text-[length:var(--malachite-font-size-15,15px)] leading-[18px] whitespace-nowrap';\n default:\n return 'min-h-[var(--malachite-control-height-large,42px)] [--btn-px:12px] px-[var(--btn-px)] text-[length:var(--malachite-font-size-15,15px)] leading-[18px] whitespace-nowrap';\n }\n};\n\n// The 1px is the transparent border, which `no-style` drops.\nconst FLUSH_MARGIN = '-mx-[calc(var(--btn-px)+1px)]';\nconst FLUSH_MARGIN_BORDERLESS = '-mx-[var(--btn-px)]';\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 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 accent: 'focus-visible:[outline:2px_solid_var(--malachite-accent-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 'quiet':\n return `${baseClasses} ${FOCUS_OUTLINE.accent} btn-quiet`;\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 leading,\n contentAlign = 'center',\n flexGrow = false,\n width = 'auto',\n fill = false,\n tooltip,\n tooltipPlacement = 'top',\n iconOnly = false,\n flush = 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 alignStart = contentAlign === 'start';\n const baseClasses = [\n alignStart\n ? 'flex items-center justify-start text-left'\n : 'flex items-center justify-center',\n iconOnly ? `p-0 ${getIconOnlySizeClasses(size)}` : getSizeClasses(size),\n getVariantClasses(variant, radiusClass),\n flexGrow ? 'w-full' : '',\n width === 'wide' ? 'min-w-[120px]' : '',\n fill ? 'max-w-full' : '',\n isInactive ? 'cursor-not-allowed' : '',\n tooltip && isInactive ? 'pointer-events-none' : '',\n flush && !iconOnly\n ? variant === 'no-style'\n ? FLUSH_MARGIN_BORDERLESS\n : FLUSH_MARGIN\n : '',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const label = alignStart ? (\n <span className=\"flex min-w-0 flex-1 items-center\">{children}</span>\n ) : (\n children\n );\n\n const leadingSlot = leading ? (\n <span\n className={\n children\n ? 'btn-leading inline-flex shrink-0 items-center me-2'\n : 'btn-leading inline-flex shrink-0 items-center'\n }\n >\n {leading}\n </span>\n ) : null;\n\n const renderContent = () => {\n if (loading) {\n return (\n <>\n {leadingSlot}\n <LoadingSpinner />\n {label}\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 {leadingSlot}\n <span\n className={`${iconWrapperClasses}${children ? ` ${getIconGapClass(size, 'start')}` : ''}`}\n >\n {icon}\n </span>\n {label}\n </>\n );\n }\n\n if (icon && iconPosition === 'end') {\n return (\n <>\n {leadingSlot}\n {label}\n <span\n className={`${iconWrapperClasses}${children ? ` ${getIconGapClass(size, 'end')}` : ''}`}\n >\n {icon}\n </span>\n </>\n );\n }\n\n return (\n <>\n {leadingSlot}\n {label}\n </>\n );\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","FLUSH_MARGIN","FLUSH_MARGIN_BORDERLESS","getDefaultRadiusClass","iconOnly","getIconGapClass","position","start","getRadiusClass","radius","FOCUS_OUTLINE","getVariantClasses","variant","radiusClass","baseClasses","LoadingSpinner","jsxs","jsx","Button","forwardRef","loading","disabled","icon","iconPosition","leading","contentAlign","flexGrow","width","fill","tooltip","tooltipPlacement","flush","children","className","href","target","rel","props","ref","isInactive","alignStart","getIconOnlySizeClasses","label","leadingSlot","renderContent","Fragment","iconWrapperClasses","getIconSizeClass","button","resolveRel","Tooltip"],"mappings":"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"}
|