@stackone/malachite 2.62.1 → 2.63.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 +2 -0
- package/dist/assets/icons/caret_double_left.svg.js +6 -0
- package/dist/assets/icons/caret_double_left.svg.js.map +1 -0
- package/dist/assets/icons/caret_double_right.svg.js +6 -0
- package/dist/assets/icons/caret_double_right.svg.js.map +1 -0
- package/dist/components/Chat/ChatThread.js +1 -2
- package/dist/components/Chat/ChatThread.js.map +1 -1
- package/dist/components/CustomIcons/CaretDoubleLeftIcon.js +21 -0
- package/dist/components/CustomIcons/CaretDoubleLeftIcon.js.map +1 -0
- package/dist/components/CustomIcons/CaretDoubleRightIcon.js +21 -0
- package/dist/components/CustomIcons/CaretDoubleRightIcon.js.map +1 -0
- package/dist/components/CustomIcons/index.js +306 -300
- package/dist/components/CustomIcons/index.js.map +1 -1
- package/dist/components/DatePicker.js +36 -0
- package/dist/components/DatePicker.js.map +1 -0
- package/dist/components/DatePickerField.js +246 -0
- package/dist/components/DatePickerField.js.map +1 -0
- package/dist/components/DatePickerPanel.js +399 -0
- package/dist/components/DatePickerPanel.js.map +1 -0
- package/dist/components/DatePickerTrigger.js +68 -0
- package/dist/components/DatePickerTrigger.js.map +1 -0
- package/dist/components/DateRangePicker.js +43 -0
- package/dist/components/DateRangePicker.js.map +1 -0
- package/dist/components/dateMath.js +153 -0
- package/dist/components/dateMath.js.map +1 -0
- package/dist/components/datePickerStyles.js +46 -0
- package/dist/components/datePickerStyles.js.map +1 -0
- package/dist/components/useDatePickerDraft.js +126 -0
- package/dist/components/useDatePickerDraft.js.map +1 -0
- package/dist/index.d.ts +233 -101
- package/dist/index.js +431 -423
- package/dist/index.js.map +1 -1
- package/dist/tailwind.css.js +1 -1
- package/dist/theme.js +372 -349
- package/dist/theme.js.map +1 -1
- package/package.json +1 -1
package/COMPONENTS.md
CHANGED
|
@@ -88,6 +88,8 @@ The catalog of components exported from `@stackone/malachite`. **Reach for one o
|
|
|
88
88
|
- **`SegmentedControl`** — A rounded track of mutually-exclusive options — the segmented “one of these” control. Owns the track background, the selected-thumb chrome and the `radiogroup` semantics, so consumers never hand-roll a track around `PillButton` (whose `default` variant tokens are tuned for standalone filter pills, not a sunken track). Single tab stop with arrow-key move-and-select; selection is conveyed via `aria-checked` and a foreground change, not background colour alone. An option `icon` sits before the label in a fixed 16px slot, and an option `badge` follows the label at whatever width it needs, for a mark such as a compact `Tag` naming a saving; it renders inside the segment button, so it must not be interactive. Restyle via the `segmentedControl` theme tokens. _Key props:_ `options` (`value`, `label`, `icon`, `badge`, `disabled`), `value`, `onChange`, `SegmentedControlSize` (`small` | `medium` | `large`), `fullWidth`, `disabled`, `aria-label`, `aria-labelledby`, `className`.
|
|
89
89
|
- **`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`.
|
|
90
90
|
- **`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; `menuAlign="end"` lines a menu wider than its trigger up with the trigger's right edge instead, so a control at the right of a card opens its menu inwards rather than past the card; 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. An item's `badge` (e.g. a `Tag`) sits at the right end of its option in the open menu, after any `rightSideText`, and never shows in the closed trigger or the search input; it does not shrink, and the option is still named by its label alone. _Key props:_ `DropdownItem[]` (items take `badge`), `DropdownSize`, `fullWidth`, `width`, `minWidth`, `menuAlign` (`start` | `end`), `value`, `defaultValue`, `label`, `search`, `showSubtitleInTrigger`, `subtitleLayout`, `clearable`, `required`, `requiredIndicator`, `error`, `helperText`, `loading`, `formControl`, `aria-label`, `aria-labelledby`, `id`.
|
|
91
|
+
- **`DatePicker`** — Picks one day, or one instant with `precision="minute"` or `"second"`, from Figma's calendar in a popover. Day precision commits on click; a time precision adds hour/minute(/second) columns and holds a draft until Apply, which Escape or an outside click discards. `timeZone="utc"` draws the UTC calendar day and suffixes times with "UTC" whatever the browser's zone, as the logs pages need. Values are `Date`s; ISO strings are accepted (a string without an offset, such as `2025-01-20` or `2025-01-20T10:00`, is read in `timeZone`), and a `formControl` field seeded with a string is written back as an ISO string. `min`/`max` bound the instant (days and times outside are disabled, as are month arrows and keyboard paging that would leave them; a value clamped up to `min` rounds up to the precision, so on `min`'s own day a day-precision value is `min` itself) and `isDateDisabled` rules out single days. The grid is an ARIA grid with a roving tab stop: arrows move by day and week, PageUp/PageDown by month (Shift for a year), Home/End to the week's ends, Enter or Space picks; every day of a range is `aria-selected`, and a hidden live line asks for the end date once the start is picked. The trigger sits on the 28/32/42 control ladder with Dropdown's chrome; `clearable` adds a clear control reported as `onChange(null)`. Colours come from `text`, `accent` and `card`, so a rebrand repaints the picker; the derived states (hover, outside-month text, selected fill, today ring, range band, pressed opacity) can each be overridden through the `datePicker` theme group (`applyTheme({ colors: { datePicker: { selectedBackground } } })`). _Key props:_ `value`, `defaultValue`, `onChange`, `precision` (`day` | `minute` | `second`), `minuteStep`, `timeZone` (`local` | `utc`), `weekStartsOn`, `min`, `max`, `isDateDisabled`, `presets`, `clearable`, `tag` (`{ label, type }` status beside the value), `tone` (`default` | `chosen` | `pending`, paint only), `placeholder`, `size`, `fullWidth`, `width`, `label`, `description`, `hint`, `tooltip`, `helperText`, `required`, `requiredIndicator`, `formControl`, `error`, `disabled`, `loading`, `id`, `aria-label`, `aria-labelledby`.
|
|
92
|
+
- **`DateRangePicker`** — `DatePicker`'s range form, with the same props and a `{ start, end }` value. The second click completes a day range; a click before the start restarts it. The end always covers the whole of its last unit (`23:59:59.999` for a picked day, the last millisecond of the picked minute or second), so a filter on the range keeps its final minutes. In a `formControl` field a cleared range writes `null`. With a time precision a Start/End switch picks which end the time columns edit, end times before the start are not offered, and Apply waits for both ends. `presets` list beside the calendar and apply at once; pass a function (`value: () => ({ start, end })`) for a relative preset such as "Past 1 hour", so it is measured when clicked rather than when the page loaded. _Key props:_ `value` / `defaultValue` (`{ start, end }`), `onChange`, `presets` (`DateRangePreset[]`), plus every `DatePicker` prop.
|
|
91
93
|
- **`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`.
|
|
92
94
|
- **`FilterSearch`** — One search field that filters across several categories. Clicking lists the filters, each with its description and an example; picking one lists its values behind a `Filters > Name` breadcrumb, and typing before picking searches the values of every filter at once, tagging each match with the filter it belongs to. Select All / Deselect All act on whatever the query matches. Each filter becomes a chip (`Connector: Notion`, or `Connector: (Notion, Attio)`) that reopens its values when clicked; Backspace in an empty field removes the last one. Controlled: `onChange` gets the next selection and the `changes` that produced it, an empty `items` meaning the filter was removed, so a URL-backed selection writes back only what moved. Chips keep the order filters were added in even when `value` comes back reordered, and keep their labels while `filters` reloads. _Key props:_ `filters`, `value`, `onChange`, `onClear`, `onActiveFilterChange`, `placeholder`, `size`, `width`, `loading`, `disabled`, `clearable`, `maxVisibleChips`, `aria-label`, `aria-labelledby`, `id`.
|
|
93
95
|
- **`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`.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as t from "react";
|
|
2
|
+
const l = (e) => /* @__PURE__ */ t.createElement("svg", { width: 256, height: 256, viewBox: "0 0 256 256", fill: "none", xmlns: "http://www.w3.org/2000/svg", ...e }, /* @__PURE__ */ t.createElement("path", { transform: "matrix(-1 0 0 1 256 0)", d: "M141.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L124.69,128,58.34,61.66A8,8,0,0,1,69.66,50.34l72,72A8,8,0,0,1,141.66,133.66Zm80-11.32-72-72a8,8,0,0,0-11.32,11.32L204.69,128l-66.35,66.34a8,8,0,0,0,11.32,11.32l72-72A8,8,0,0,0,221.66,122.34Z", fill: "currentColor" }));
|
|
3
|
+
export {
|
|
4
|
+
l as default
|
|
5
|
+
};
|
|
6
|
+
//# sourceMappingURL=caret_double_left.svg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caret_double_left.svg.js","sources":["../../../src/assets/icons/caret_double_left.svg?react"],"sourcesContent":["import * as React from \"react\";\nconst SvgCaretDoubleLeft = (props) => /* @__PURE__ */ React.createElement(\"svg\", { width: 256, height: 256, viewBox: \"0 0 256 256\", fill: \"none\", xmlns: \"http://www.w3.org/2000/svg\", ...props }, /* @__PURE__ */ React.createElement(\"path\", { transform: \"matrix(-1 0 0 1 256 0)\", d: \"M141.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L124.69,128,58.34,61.66A8,8,0,0,1,69.66,50.34l72,72A8,8,0,0,1,141.66,133.66Zm80-11.32-72-72a8,8,0,0,0-11.32,11.32L204.69,128l-66.35,66.34a8,8,0,0,0,11.32,11.32l72-72A8,8,0,0,0,221.66,122.34Z\", fill: \"currentColor\" }));\nexport default SvgCaretDoubleLeft;\n"],"names":["SvgCaretDoubleLeft","props","React"],"mappings":";AACK,MAACA,IAAqB,CAACC,MAA0B,gBAAAC,EAAM,cAAc,OAAO,EAAE,OAAO,KAAK,QAAQ,KAAK,SAAS,eAAe,MAAM,QAAQ,OAAO,8BAA8B,GAAGD,EAAK,GAAoB,gBAAAC,EAAM,cAAc,QAAQ,EAAE,WAAW,0BAA0B,GAAG,6OAA6O,MAAM,eAAc,CAAE,CAAC;"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as e from "react";
|
|
2
|
+
const l = (t) => /* @__PURE__ */ e.createElement("svg", { width: 256, height: 256, viewBox: "0 0 256 256", fill: "none", xmlns: "http://www.w3.org/2000/svg", ...t }, /* @__PURE__ */ e.createElement("path", { d: "M141.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L124.69,128,58.34,61.66A8,8,0,0,1,69.66,50.34l72,72A8,8,0,0,1,141.66,133.66Zm80-11.32-72-72a8,8,0,0,0-11.32,11.32L204.69,128l-66.35,66.34a8,8,0,0,0,11.32,11.32l72-72A8,8,0,0,0,221.66,122.34Z", fill: "currentColor" }));
|
|
3
|
+
export {
|
|
4
|
+
l as default
|
|
5
|
+
};
|
|
6
|
+
//# sourceMappingURL=caret_double_right.svg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caret_double_right.svg.js","sources":["../../../src/assets/icons/caret_double_right.svg?react"],"sourcesContent":["import * as React from \"react\";\nconst SvgCaretDoubleRight = (props) => /* @__PURE__ */ React.createElement(\"svg\", { width: 256, height: 256, viewBox: \"0 0 256 256\", fill: \"none\", xmlns: \"http://www.w3.org/2000/svg\", ...props }, /* @__PURE__ */ React.createElement(\"path\", { d: \"M141.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L124.69,128,58.34,61.66A8,8,0,0,1,69.66,50.34l72,72A8,8,0,0,1,141.66,133.66Zm80-11.32-72-72a8,8,0,0,0-11.32,11.32L204.69,128l-66.35,66.34a8,8,0,0,0,11.32,11.32l72-72A8,8,0,0,0,221.66,122.34Z\", fill: \"currentColor\" }));\nexport default SvgCaretDoubleRight;\n"],"names":["SvgCaretDoubleRight","props","React"],"mappings":";AACK,MAACA,IAAsB,CAACC,MAA0B,gBAAAC,EAAM,cAAc,OAAO,EAAE,OAAO,KAAK,QAAQ,KAAK,SAAS,eAAe,MAAM,QAAQ,OAAO,8BAA8B,GAAGD,EAAK,GAAoB,gBAAAC,EAAM,cAAc,QAAQ,EAAE,GAAG,6OAA6O,MAAM,eAAc,CAAE,CAAC;"}
|
|
@@ -40,8 +40,7 @@ const z = ({
|
|
|
40
40
|
}
|
|
41
41
|
) }),
|
|
42
42
|
!p && // Pinned to the side rather than centred over the newest message, which is
|
|
43
|
-
// the thing a reader scrolled back to see.
|
|
44
|
-
// working inside a shadow root (see the CardStrip arrow).
|
|
43
|
+
// the thing a reader scrolled back to see.
|
|
45
44
|
/* @__PURE__ */ l("div", { className: "absolute right-3 top-1/2 -translate-y-1/2", children: /* @__PURE__ */ l(
|
|
46
45
|
N,
|
|
47
46
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatThread.js","sources":["../../../src/components/Chat/ChatThread.tsx"],"sourcesContent":["import React from 'react';\nimport Button from '../Button';\nimport CaretDownIcon from '../CustomIcons/CaretDownIcon';\nimport Typography from '../Typography';\nimport { useStickToBottom } from './stickToBottom';\n\nexport interface ChatThreadProps {\n children?: React.ReactNode;\n /** Names the transcript for assistive tech, e.g. \"Policy assistant conversation\". */\n 'aria-label'?: string;\n /** Shown, centred, while the transcript holds nothing. Ignored when `emptyContent` is\n * supplied. */\n emptyTitle?: string;\n /** The lines under the title. A node, because guidance is rarely one sentence. Ignored\n * when `emptyContent` is supplied. */\n emptyDescription?: React.ReactNode;\n /** Ignored when `emptyContent` is supplied. */\n emptyIcon?: React.ReactNode;\n /**\n * Replaces the `emptyTitle`/`emptyDescription`/`emptyIcon` composition. Renders\n * left-aligned at the bottom of the transcript, directly above the composer — an empty\n * conversation should read as ready to start, not as a decorated void.\n */\n emptyContent?: React.ReactNode;\n className?: string;\n}\n\n/**\n * The scrolling transcript. Owns scroll ownership and the pin-to-newest behaviour so a\n * consumer never wires a scroll listener of its own.\n *\n * `flex-1 min-h-0` is load-bearing: without the floor, WebKit shrinks an `overflow` child to\n * fit its content and scrolling silently moves to an ancestor.\n */\nconst ChatThread: React.FC<ChatThreadProps> = ({\n children,\n 'aria-label': ariaLabel,\n emptyTitle,\n emptyDescription,\n emptyIcon,\n emptyContent,\n className = '',\n}) => {\n const { scrollRef, contentRef, pinned, scrollToBottom } = useStickToBottom();\n\n // `toArray` rather than `count`: it drops the nulls a caller leaves behind when a\n // conditional turn renders nothing, which `count` would tally as content.\n const isEmpty = React.Children.toArray(children).length === 0;\n const isEmptyWithCustomContent = isEmpty && Boolean(emptyContent);\n const isEmptyWithLegacyState =\n isEmpty && !emptyContent && Boolean(emptyTitle || emptyDescription || emptyIcon);\n\n let contentClassName = 'flex flex-col gap-4 p-4';\n if (isEmptyWithCustomContent) {\n // `flex-1`, not `min-h-full`: a percentage height only resolves against a parent with\n // an explicit height, and the scroll div's height comes from flex resolution rather\n // than a set value, so the percentage silently fails to resolve and the content\n // collapses to the top. `flex-1` instead asks the (now flex) scroll div's own layout\n // for a real height, which `mt-auto` below can then push against.\n contentClassName = 'flex flex-1 flex-col p-4';\n } else if (isEmptyWithLegacyState) {\n contentClassName =\n 'flex min-h-full flex-col items-center justify-center gap-2 p-6 text-center';\n }\n\n return (\n <div className={`relative flex min-h-0 flex-1 flex-col ${className}`}>\n <div ref={scrollRef} className=\"flex min-h-0 flex-1 flex-col overflow-y-auto\">\n <div\n ref={contentRef}\n role=\"log\"\n aria-live=\"polite\"\n aria-label={ariaLabel}\n className={contentClassName}\n >\n {isEmptyWithCustomContent ? (\n // `margin-top: auto` consumes the free space the scroll div's flex\n // layout gives this (now `flex-1`) content box, pinning the block to\n // the bottom. `flex-direction: column-reverse` was the mock's own\n // approach, but it inverts scroll geometry that `useStickToBottom` and\n // the jump-to-latest control both rely on — this gets the same bottom\n // anchoring without touching either.\n <div className=\"mt-auto text-left\">{emptyContent}</div>\n ) : isEmptyWithLegacyState ? (\n <>\n {emptyIcon ? (\n <span className=\"text-[var(--malachite-text-dimmed)]\">\n {emptyIcon}\n </span>\n ) : null}\n {emptyTitle ? (\n <Typography.SecondaryText fontWeight=\"bold\">\n {emptyTitle}\n </Typography.SecondaryText>\n ) : null}\n {emptyDescription ? (\n <Typography.SecondaryText size=\"small\">\n {emptyDescription}\n </Typography.SecondaryText>\n ) : null}\n </>\n ) : (\n children\n )}\n </div>\n </div>\n {!pinned && (\n // Pinned to the side rather than centred over the newest message, which is\n // the thing a reader scrolled back to see
|
|
1
|
+
{"version":3,"file":"ChatThread.js","sources":["../../../src/components/Chat/ChatThread.tsx"],"sourcesContent":["import React from 'react';\nimport Button from '../Button';\nimport CaretDownIcon from '../CustomIcons/CaretDownIcon';\nimport Typography from '../Typography';\nimport { useStickToBottom } from './stickToBottom';\n\nexport interface ChatThreadProps {\n children?: React.ReactNode;\n /** Names the transcript for assistive tech, e.g. \"Policy assistant conversation\". */\n 'aria-label'?: string;\n /** Shown, centred, while the transcript holds nothing. Ignored when `emptyContent` is\n * supplied. */\n emptyTitle?: string;\n /** The lines under the title. A node, because guidance is rarely one sentence. Ignored\n * when `emptyContent` is supplied. */\n emptyDescription?: React.ReactNode;\n /** Ignored when `emptyContent` is supplied. */\n emptyIcon?: React.ReactNode;\n /**\n * Replaces the `emptyTitle`/`emptyDescription`/`emptyIcon` composition. Renders\n * left-aligned at the bottom of the transcript, directly above the composer — an empty\n * conversation should read as ready to start, not as a decorated void.\n */\n emptyContent?: React.ReactNode;\n className?: string;\n}\n\n/**\n * The scrolling transcript. Owns scroll ownership and the pin-to-newest behaviour so a\n * consumer never wires a scroll listener of its own.\n *\n * `flex-1 min-h-0` is load-bearing: without the floor, WebKit shrinks an `overflow` child to\n * fit its content and scrolling silently moves to an ancestor.\n */\nconst ChatThread: React.FC<ChatThreadProps> = ({\n children,\n 'aria-label': ariaLabel,\n emptyTitle,\n emptyDescription,\n emptyIcon,\n emptyContent,\n className = '',\n}) => {\n const { scrollRef, contentRef, pinned, scrollToBottom } = useStickToBottom();\n\n // `toArray` rather than `count`: it drops the nulls a caller leaves behind when a\n // conditional turn renders nothing, which `count` would tally as content.\n const isEmpty = React.Children.toArray(children).length === 0;\n const isEmptyWithCustomContent = isEmpty && Boolean(emptyContent);\n const isEmptyWithLegacyState =\n isEmpty && !emptyContent && Boolean(emptyTitle || emptyDescription || emptyIcon);\n\n let contentClassName = 'flex flex-col gap-4 p-4';\n if (isEmptyWithCustomContent) {\n // `flex-1`, not `min-h-full`: a percentage height only resolves against a parent with\n // an explicit height, and the scroll div's height comes from flex resolution rather\n // than a set value, so the percentage silently fails to resolve and the content\n // collapses to the top. `flex-1` instead asks the (now flex) scroll div's own layout\n // for a real height, which `mt-auto` below can then push against.\n contentClassName = 'flex flex-1 flex-col p-4';\n } else if (isEmptyWithLegacyState) {\n contentClassName =\n 'flex min-h-full flex-col items-center justify-center gap-2 p-6 text-center';\n }\n\n return (\n <div className={`relative flex min-h-0 flex-1 flex-col ${className}`}>\n <div ref={scrollRef} className=\"flex min-h-0 flex-1 flex-col overflow-y-auto\">\n <div\n ref={contentRef}\n role=\"log\"\n aria-live=\"polite\"\n aria-label={ariaLabel}\n className={contentClassName}\n >\n {isEmptyWithCustomContent ? (\n // `margin-top: auto` consumes the free space the scroll div's flex\n // layout gives this (now `flex-1`) content box, pinning the block to\n // the bottom. `flex-direction: column-reverse` was the mock's own\n // approach, but it inverts scroll geometry that `useStickToBottom` and\n // the jump-to-latest control both rely on — this gets the same bottom\n // anchoring without touching either.\n <div className=\"mt-auto text-left\">{emptyContent}</div>\n ) : isEmptyWithLegacyState ? (\n <>\n {emptyIcon ? (\n <span className=\"text-[var(--malachite-text-dimmed)]\">\n {emptyIcon}\n </span>\n ) : null}\n {emptyTitle ? (\n <Typography.SecondaryText fontWeight=\"bold\">\n {emptyTitle}\n </Typography.SecondaryText>\n ) : null}\n {emptyDescription ? (\n <Typography.SecondaryText size=\"small\">\n {emptyDescription}\n </Typography.SecondaryText>\n ) : null}\n </>\n ) : (\n children\n )}\n </div>\n </div>\n {!pinned && (\n // Pinned to the side rather than centred over the newest message, which is\n // the thing a reader scrolled back to see.\n <div className=\"absolute right-3 top-1/2 -translate-y-1/2\">\n <Button\n variant=\"outline\"\n size=\"small\"\n iconOnly\n radius=\"xl\"\n // Inline, because no utility class can reach it: Tailwind's utilities\n // sit in `@layer utilities` while `.btn-outline`'s transparent\n // background is an unlayered rule, and unlayered wins. Without this\n // the transcript reads straight through the button.\n style={{\n backgroundColor: 'var(--malachite-card-background)',\n boxShadow: 'var(--malachite-shadow-md)',\n }}\n icon={<CaretDownIcon size={14} />}\n tooltip=\"Jump to latest\"\n aria-label=\"Jump to latest\"\n onClick={scrollToBottom}\n />\n </div>\n )}\n </div>\n );\n};\n\nexport default ChatThread;\nexport { ChatThread };\n"],"names":["ChatThread","children","ariaLabel","emptyTitle","emptyDescription","emptyIcon","emptyContent","className","scrollRef","contentRef","pinned","scrollToBottom","useStickToBottom","isEmpty","React","isEmptyWithCustomContent","isEmptyWithLegacyState","contentClassName","jsxs","jsx","Fragment","Typography","Button","CaretDownIcon"],"mappings":";;;;;;AAkCA,MAAMA,IAAwC,CAAC;AAAA,EAC3C,UAAAC;AAAA,EACA,cAAcC;AAAA,EACd,YAAAC;AAAA,EACA,kBAAAC;AAAA,EACA,WAAAC;AAAA,EACA,cAAAC;AAAA,EACA,WAAAC,IAAY;AAChB,MAAM;AACF,QAAM,EAAE,WAAAC,GAAW,YAAAC,GAAY,QAAAC,GAAQ,gBAAAC,EAAA,IAAmBC,EAAA,GAIpDC,IAAUC,EAAM,SAAS,QAAQb,CAAQ,EAAE,WAAW,GACtDc,IAA2BF,KAAW,EAAQP,GAC9CU,IACFH,KAAW,CAACP,KAAgB,GAAQH,KAAcC,KAAoBC;AAE1E,MAAIY,IAAmB;AACvB,SAAIF,IAMAE,IAAmB,6BACZD,MACPC,IACI,+EAIJ,gBAAAC,EAAC,OAAA,EAAI,WAAW,yCAAyCX,CAAS,IAC9D,UAAA;AAAA,IAAA,gBAAAY,EAAC,OAAA,EAAI,KAAKX,GAAW,WAAU,gDAC3B,UAAA,gBAAAW;AAAA,MAAC;AAAA,MAAA;AAAA,QACG,KAAKV;AAAA,QACL,MAAK;AAAA,QACL,aAAU;AAAA,QACV,cAAYP;AAAA,QACZ,WAAWe;AAAA,QAEV,UAAAF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAOG,gBAAAI,EAAC,OAAA,EAAI,WAAU,qBAAqB,UAAAb,EAAA,CAAa;AAAA,YACjDU,IACA,gBAAAE,EAAAE,GAAA,EACK,UAAA;AAAA,UAAAf,IACG,gBAAAc,EAAC,QAAA,EAAK,WAAU,uCACX,aACL,IACA;AAAA,UACHhB,sBACIkB,EAAW,eAAX,EAAyB,YAAW,QAChC,aACL,IACA;AAAA,UACHjB,sBACIiB,EAAW,eAAX,EAAyB,MAAK,SAC1B,aACL,IACA;AAAA,QAAA,EAAA,CACR,IAEApB;AAAA,MAAA;AAAA,IAAA,GAGZ;AAAA,IACC,CAACS;AAAA;AAAA,IAGE,gBAAAS,EAAC,OAAA,EAAI,WAAU,6CACX,UAAA,gBAAAA;AAAA,MAACG;AAAA,MAAA;AAAA,QACG,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,UAAQ;AAAA,QACR,QAAO;AAAA,QAKP,OAAO;AAAA,UACH,iBAAiB;AAAA,UACjB,WAAW;AAAA,QAAA;AAAA,QAEf,MAAM,gBAAAH,EAACI,GAAA,EAAc,MAAM,GAAA,CAAI;AAAA,QAC/B,SAAQ;AAAA,QACR,cAAW;AAAA,QACX,SAASZ;AAAA,MAAA;AAAA,IAAA,EACb,CACJ;AAAA,EAAA,GAER;AAER;"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
2
|
+
import f from "../../assets/icons/caret_double_left.svg.js";
|
|
3
|
+
const c = ({
|
|
4
|
+
size: t = 16,
|
|
5
|
+
className: o = "",
|
|
6
|
+
style: r = {},
|
|
7
|
+
onClick: e
|
|
8
|
+
}) => {
|
|
9
|
+
const l = {
|
|
10
|
+
width: typeof t == "number" ? `${t}px` : t,
|
|
11
|
+
height: typeof t == "number" ? `${t}px` : t,
|
|
12
|
+
display: "inline-block",
|
|
13
|
+
fill: "currentColor",
|
|
14
|
+
...r
|
|
15
|
+
};
|
|
16
|
+
return /* @__PURE__ */ n(f, { className: o, style: l, onClick: e });
|
|
17
|
+
};
|
|
18
|
+
export {
|
|
19
|
+
c as default
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=CaretDoubleLeftIcon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CaretDoubleLeftIcon.js","sources":["../../../src/components/CustomIcons/CaretDoubleLeftIcon.tsx"],"sourcesContent":["import type { CSSProperties } from 'react';\nimport CaretDoubleLeftIconSvg from '../../assets/icons/caret_double_left.svg?react';\n\nexport interface IconProps {\n size?: number | string;\n className?: string;\n style?: CSSProperties;\n onClick?: () => void;\n}\n\nconst CaretDoubleLeftIcon: React.FC<IconProps> = ({\n size = 16,\n className = '',\n style = {},\n onClick,\n}) => {\n const iconStyle: CSSProperties = {\n width: typeof size === 'number' ? `${size}px` : size,\n height: typeof size === 'number' ? `${size}px` : size,\n display: 'inline-block',\n fill: 'currentColor',\n ...style,\n };\n\n return <CaretDoubleLeftIconSvg className={className} style={iconStyle} onClick={onClick} />;\n};\n\nexport default CaretDoubleLeftIcon;\n"],"names":["CaretDoubleLeftIcon","size","className","style","onClick","iconStyle","jsx","CaretDoubleLeftIconSvg"],"mappings":";;AAUA,MAAMA,IAA2C,CAAC;AAAA,EAC9C,MAAAC,IAAO;AAAA,EACP,WAAAC,IAAY;AAAA,EACZ,OAAAC,IAAQ,CAAA;AAAA,EACR,SAAAC;AACJ,MAAM;AACF,QAAMC,IAA2B;AAAA,IAC7B,OAAO,OAAOJ,KAAS,WAAW,GAAGA,CAAI,OAAOA;AAAA,IAChD,QAAQ,OAAOA,KAAS,WAAW,GAAGA,CAAI,OAAOA;AAAA,IACjD,SAAS;AAAA,IACT,MAAM;AAAA,IACN,GAAGE;AAAA,EAAA;AAGP,SAAO,gBAAAG,EAACC,GAAA,EAAuB,WAAAL,GAAsB,OAAOG,GAAW,SAAAD,GAAkB;AAC7F;"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
2
|
+
import p from "../../assets/icons/caret_double_right.svg.js";
|
|
3
|
+
const f = ({
|
|
4
|
+
size: t = 16,
|
|
5
|
+
className: o = "",
|
|
6
|
+
style: r = {},
|
|
7
|
+
onClick: e
|
|
8
|
+
}) => {
|
|
9
|
+
const l = {
|
|
10
|
+
width: typeof t == "number" ? `${t}px` : t,
|
|
11
|
+
height: typeof t == "number" ? `${t}px` : t,
|
|
12
|
+
display: "inline-block",
|
|
13
|
+
fill: "currentColor",
|
|
14
|
+
...r
|
|
15
|
+
};
|
|
16
|
+
return /* @__PURE__ */ n(p, { className: o, style: l, onClick: e });
|
|
17
|
+
};
|
|
18
|
+
export {
|
|
19
|
+
f as default
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=CaretDoubleRightIcon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CaretDoubleRightIcon.js","sources":["../../../src/components/CustomIcons/CaretDoubleRightIcon.tsx"],"sourcesContent":["import type { CSSProperties } from 'react';\nimport CaretDoubleRightIconSvg from '../../assets/icons/caret_double_right.svg?react';\n\nexport interface IconProps {\n size?: number | string;\n className?: string;\n style?: CSSProperties;\n onClick?: () => void;\n}\n\nconst CaretDoubleRightIcon: React.FC<IconProps> = ({\n size = 16,\n className = '',\n style = {},\n onClick,\n}) => {\n const iconStyle: CSSProperties = {\n width: typeof size === 'number' ? `${size}px` : size,\n height: typeof size === 'number' ? `${size}px` : size,\n display: 'inline-block',\n fill: 'currentColor',\n ...style,\n };\n\n return <CaretDoubleRightIconSvg className={className} style={iconStyle} onClick={onClick} />;\n};\n\nexport default CaretDoubleRightIcon;\n"],"names":["CaretDoubleRightIcon","size","className","style","onClick","iconStyle","jsx","CaretDoubleRightIconSvg"],"mappings":";;AAUA,MAAMA,IAA4C,CAAC;AAAA,EAC/C,MAAAC,IAAO;AAAA,EACP,WAAAC,IAAY;AAAA,EACZ,OAAAC,IAAQ,CAAA;AAAA,EACR,SAAAC;AACJ,MAAM;AACF,QAAMC,IAA2B;AAAA,IAC7B,OAAO,OAAOJ,KAAS,WAAW,GAAGA,CAAI,OAAOA;AAAA,IAChD,QAAQ,OAAOA,KAAS,WAAW,GAAGA,CAAI,OAAOA;AAAA,IACjD,SAAS;AAAA,IACT,MAAM;AAAA,IACN,GAAGE;AAAA,EAAA;AAGP,SAAO,gBAAAG,EAACC,GAAA,EAAwB,WAAAL,GAAsB,OAAOG,GAAW,SAAAD,GAAkB;AAC9F;"}
|