@xenosystem/elements-react 0.0.1

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.
Files changed (153) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +41 -0
  3. package/dist/XenoElement.d.ts +39 -0
  4. package/dist/XenoElement.js +25 -0
  5. package/dist/containers/Avatar.d.ts +42 -0
  6. package/dist/containers/Avatar.js +29 -0
  7. package/dist/containers/Badges.d.ts +61 -0
  8. package/dist/containers/Badges.js +43 -0
  9. package/dist/containers/Card.d.ts +24 -0
  10. package/dist/containers/Card.js +5 -0
  11. package/dist/containers/Chip.d.ts +34 -0
  12. package/dist/containers/Chip.js +17 -0
  13. package/dist/containers/DataTable.d.ts +28 -0
  14. package/dist/containers/DataTable.js +13 -0
  15. package/dist/containers/ListRow.d.ts +37 -0
  16. package/dist/containers/ListRow.js +40 -0
  17. package/dist/containers/MessageBubble.d.ts +33 -0
  18. package/dist/containers/MessageBubble.js +9 -0
  19. package/dist/containers/Panel.d.ts +24 -0
  20. package/dist/containers/Panel.js +6 -0
  21. package/dist/containers/Table.d.ts +39 -0
  22. package/dist/containers/Table.js +20 -0
  23. package/dist/containers/Tile.d.ts +19 -0
  24. package/dist/containers/Tile.js +7 -0
  25. package/dist/content/Callout.d.ts +32 -0
  26. package/dist/content/Callout.js +10 -0
  27. package/dist/content/Caret.d.ts +9 -0
  28. package/dist/content/Caret.js +5 -0
  29. package/dist/content/CodeBlock.d.ts +36 -0
  30. package/dist/content/CodeBlock.js +50 -0
  31. package/dist/content/Collapsible.d.ts +24 -0
  32. package/dist/content/Collapsible.js +19 -0
  33. package/dist/content/InlineCode.d.ts +12 -0
  34. package/dist/content/InlineCode.js +5 -0
  35. package/dist/content/ModelPicker.d.ts +45 -0
  36. package/dist/content/ModelPicker.js +124 -0
  37. package/dist/content/SourceCard.d.ts +23 -0
  38. package/dist/content/SourceCard.js +18 -0
  39. package/dist/content/SourcesDisclosure.d.ts +26 -0
  40. package/dist/content/SourcesDisclosure.js +20 -0
  41. package/dist/controls/Button.d.ts +39 -0
  42. package/dist/controls/Button.js +16 -0
  43. package/dist/controls/ConfirmButton.d.ts +22 -0
  44. package/dist/controls/ConfirmButton.js +22 -0
  45. package/dist/controls/IconButton.d.ts +45 -0
  46. package/dist/controls/IconButton.js +15 -0
  47. package/dist/controls/Switch.d.ts +11 -0
  48. package/dist/controls/Switch.js +23 -0
  49. package/dist/controls/TextInput.d.ts +13 -0
  50. package/dist/controls/TextInput.js +6 -0
  51. package/dist/controls/Textarea.d.ts +8 -0
  52. package/dist/controls/Textarea.js +5 -0
  53. package/dist/controls/ToggleButton.d.ts +23 -0
  54. package/dist/controls/ToggleButton.js +23 -0
  55. package/dist/controls/util.d.ts +48 -0
  56. package/dist/controls/util.js +23 -0
  57. package/dist/forms/Checkbox.d.ts +18 -0
  58. package/dist/forms/Checkbox.js +28 -0
  59. package/dist/forms/RadioGroup.d.ts +51 -0
  60. package/dist/forms/RadioGroup.js +83 -0
  61. package/dist/index.d.ts +75 -0
  62. package/dist/index.js +81 -0
  63. package/dist/layout/ResizablePanel.d.ts +40 -0
  64. package/dist/layout/ResizablePanel.js +108 -0
  65. package/dist/nav/Sidebar.d.ts +60 -0
  66. package/dist/nav/Sidebar.js +84 -0
  67. package/dist/overlays/DateTimePicker.d.ts +71 -0
  68. package/dist/overlays/DateTimePicker.js +191 -0
  69. package/dist/overlays/Menu.d.ts +28 -0
  70. package/dist/overlays/Menu.js +32 -0
  71. package/dist/overlays/MenuItem.d.ts +71 -0
  72. package/dist/overlays/MenuItem.js +15 -0
  73. package/dist/overlays/Modal.d.ts +36 -0
  74. package/dist/overlays/Modal.js +27 -0
  75. package/dist/overlays/PickerField.d.ts +32 -0
  76. package/dist/overlays/PickerField.js +40 -0
  77. package/dist/overlays/PillFilter.d.ts +25 -0
  78. package/dist/overlays/PillFilter.js +41 -0
  79. package/dist/overlays/Popover.d.ts +32 -0
  80. package/dist/overlays/Popover.js +38 -0
  81. package/dist/overlays/Reveal.d.ts +20 -0
  82. package/dist/overlays/Reveal.js +45 -0
  83. package/dist/overlays/SegmentedControl.d.ts +23 -0
  84. package/dist/overlays/SegmentedControl.js +43 -0
  85. package/dist/overlays/Tabs.d.ts +32 -0
  86. package/dist/overlays/Tabs.js +142 -0
  87. package/dist/overlays/Tooltip.d.ts +23 -0
  88. package/dist/overlays/Tooltip.js +61 -0
  89. package/dist/status/ProgressBar.d.ts +20 -0
  90. package/dist/status/ProgressBar.js +11 -0
  91. package/dist/status/Spinner.d.ts +16 -0
  92. package/dist/status/Spinner.js +12 -0
  93. package/dist/status/StatusIndicator.d.ts +19 -0
  94. package/dist/status/StatusIndicator.js +11 -0
  95. package/dist/status/StepTimeline.d.ts +26 -0
  96. package/dist/status/StepTimeline.js +16 -0
  97. package/dist/status/ThinkingCube.d.ts +24 -0
  98. package/dist/status/ThinkingCube.js +11 -0
  99. package/dist/useDialog.d.ts +45 -0
  100. package/dist/useDialog.js +122 -0
  101. package/dist/useGooPill.d.ts +57 -0
  102. package/dist/useGooPill.js +76 -0
  103. package/dist/useMenu.d.ts +50 -0
  104. package/dist/useMenu.js +119 -0
  105. package/dist/useTabs.d.ts +88 -0
  106. package/dist/useTabs.js +93 -0
  107. package/package.json +58 -0
  108. package/src/chrome-separated.css +132 -0
  109. package/src/chrome-unified.css +72 -0
  110. package/src/containers/Badges.css +116 -0
  111. package/src/containers/avatar.css +70 -0
  112. package/src/containers/card.css +75 -0
  113. package/src/containers/chip.css +103 -0
  114. package/src/containers/list-row.css +132 -0
  115. package/src/containers/message-bubble.css +112 -0
  116. package/src/containers/panel.css +120 -0
  117. package/src/containers/table.css +102 -0
  118. package/src/containers/tile.css +17 -0
  119. package/src/content/callout.css +77 -0
  120. package/src/content/caret.css +30 -0
  121. package/src/content/code-block.css +159 -0
  122. package/src/content/collapsible.css +80 -0
  123. package/src/content/inline-code.css +14 -0
  124. package/src/content/model-picker.css +219 -0
  125. package/src/content/source-card.css +99 -0
  126. package/src/content/sources-disclosure.css +118 -0
  127. package/src/controls.css +453 -0
  128. package/src/fonts.css +34 -0
  129. package/src/forms/Checkbox.css +114 -0
  130. package/src/forms/RadioGroup.css +115 -0
  131. package/src/goo.css +97 -0
  132. package/src/icon-motion.css +1909 -0
  133. package/src/layout/ResizablePanel.css +79 -0
  134. package/src/nav/sidebar.css +296 -0
  135. package/src/overlays/date-time-picker.css +238 -0
  136. package/src/overlays/menu.css +195 -0
  137. package/src/overlays/modal.css +167 -0
  138. package/src/overlays/picker-field.css +81 -0
  139. package/src/overlays/pill-filter.css +129 -0
  140. package/src/overlays/reveal.css +56 -0
  141. package/src/overlays/segmented-control.css +185 -0
  142. package/src/overlays/tabs.css +143 -0
  143. package/src/overlays/tooltip.css +126 -0
  144. package/src/scrollbar.css +184 -0
  145. package/src/size.css +179 -0
  146. package/src/status/progress-bar.css +88 -0
  147. package/src/status/spinner.css +33 -0
  148. package/src/status/statusindicator.css +65 -0
  149. package/src/status/steptimeline.css +137 -0
  150. package/src/status/thinkingcube.css +75 -0
  151. package/src/xeno-element.css +60 -0
  152. package/src/xeno-elements.css +71 -0
  153. package/src/xeno-theme.css +99 -0
@@ -0,0 +1,39 @@
1
+ import type { HTMLAttributes, ReactElement, ReactNode, TableHTMLAttributes, TdHTMLAttributes, ThHTMLAttributes } from 'react';
2
+ /**
3
+ * Dense table PRIMITIVES — the thin, purely-presentational wrappers over the native table elements
4
+ * (`table`/`thead`/`tbody`/`tr`/`th`/`td`). They carry only the `xeno-*` classes so the locked table
5
+ * grammar (border-collapse, hairline row rules, muted 600-weight headers, NO zebra striping, NO
6
+ * vertical rules) lives entirely in `table.css`. Compose them by hand, or reach for {@link DataTable}
7
+ * for the column/row convenience. Alignment rides the `data-align` seam; a selected row rides the
8
+ * `selection` axis (`data-selection` + `aria-selected`) — monochrome, never a coloured glow.
9
+ */
10
+ /** Cell text alignment — the only three the grammar allows. */
11
+ export type ColumnAlign = 'left' | 'center' | 'right';
12
+ export interface TableProps extends TableHTMLAttributes<HTMLTableElement> {
13
+ readonly children?: ReactNode;
14
+ }
15
+ export declare function Table({ className, children, ...rest }: TableProps): ReactElement;
16
+ export interface THeadProps extends HTMLAttributes<HTMLTableSectionElement> {
17
+ readonly children?: ReactNode;
18
+ }
19
+ export declare function THead({ className, children, ...rest }: THeadProps): ReactElement;
20
+ export interface TBodyProps extends HTMLAttributes<HTMLTableSectionElement> {
21
+ readonly children?: ReactNode;
22
+ }
23
+ export declare function TBody({ className, children, ...rest }: TBodyProps): ReactElement;
24
+ export interface TrProps extends HTMLAttributes<HTMLTableRowElement> {
25
+ /** Presentational selection state — rides the `selection` axis. Omit for a plain row. */
26
+ readonly selected?: boolean;
27
+ readonly children?: ReactNode;
28
+ }
29
+ export declare function Tr({ selected, className, children, ...rest }: TrProps): ReactElement;
30
+ export interface ThProps extends Omit<ThHTMLAttributes<HTMLTableCellElement>, 'align'> {
31
+ readonly align?: ColumnAlign;
32
+ readonly children?: ReactNode;
33
+ }
34
+ export declare function Th({ align, scope, className, children, ...rest }: ThProps): ReactElement;
35
+ export interface TdProps extends Omit<TdHTMLAttributes<HTMLTableCellElement>, 'align'> {
36
+ readonly align?: ColumnAlign;
37
+ readonly children?: ReactNode;
38
+ }
39
+ export declare function Td({ align, className, children, ...rest }: TdProps): ReactElement;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from '../controls/util.js';
3
+ export function Table({ className, children, ...rest }) {
4
+ return (_jsx("table", { className: cx('xeno-table', className), ...rest, children: children }));
5
+ }
6
+ export function THead({ className, children, ...rest }) {
7
+ return (_jsx("thead", { className: cx('xeno-thead', className), ...rest, children: children }));
8
+ }
9
+ export function TBody({ className, children, ...rest }) {
10
+ return (_jsx("tbody", { className: cx('xeno-tbody', className), ...rest, children: children }));
11
+ }
12
+ export function Tr({ selected, className, children, ...rest }) {
13
+ return (_jsx("tr", { className: cx('xeno-tr', className), "data-selection": selected === undefined ? undefined : selected ? 'on' : 'off', "aria-selected": selected, ...rest, children: children }));
14
+ }
15
+ export function Th({ align, scope = 'col', className, children, ...rest }) {
16
+ return (_jsx("th", { className: cx('xeno-th', className), scope: scope, "data-align": align, ...rest, children: children }));
17
+ }
18
+ export function Td({ align, className, children, ...rest }) {
19
+ return (_jsx("td", { className: cx('xeno-td', className), "data-align": align, ...rest, children: children }));
20
+ }
@@ -0,0 +1,19 @@
1
+ import type { HTMLAttributes, ReactElement } from 'react';
2
+ import type { ElementDeclaration } from '@xenosystem/elements/schema';
3
+ /**
4
+ * `<Tile>` — a square icon tile (radius-md, `var(--xeno-surface)`) that centres a single glyph drawn by
5
+ * the shared `<XenoElement>` renderer, so it inherits the monochrome ink and adds no colour of its own.
6
+ * The chat's file/attachment affordance: a small surface square holding a file/folder/image glyph.
7
+ *
8
+ * `size` is the box edge in px (e.g. 24 / 32); the glyph defaults to half the edge and can be pinned
9
+ * with `glyphSize`.
10
+ */
11
+ export interface TileProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'style'> {
12
+ /** The glyph to centre — imported per-id from `@xenosystem/elements`. */
13
+ readonly icon: ElementDeclaration;
14
+ /** Box edge in px. Default 32. */
15
+ readonly size?: number;
16
+ /** Glyph px. Defaults to half the box edge. */
17
+ readonly glyphSize?: number;
18
+ }
19
+ export declare function Tile({ icon, size, glyphSize, className, ...rest }: TileProps): ReactElement;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { XenoElement } from '../XenoElement.js';
3
+ import { cx } from '../controls/util.js';
4
+ export function Tile({ icon, size = 32, glyphSize, className, ...rest }) {
5
+ const glyph = glyphSize ?? Math.round(size * 0.5);
6
+ return (_jsx("span", { className: cx('xeno-tile', className), style: { ['--xeno-tile-size']: `${size}px` }, ...rest, children: _jsx(XenoElement, { decl: icon, size: glyph }) }));
7
+ }
@@ -0,0 +1,32 @@
1
+ import type { HTMLAttributes, ReactElement, ReactNode } from 'react';
2
+ import type { ElementDeclaration } from '@xenosystem/elements/schema';
3
+ /**
4
+ * `<Callout>` — a left-ruled aside for markdown blockquotes. A 2px rule, `14px` of inset, muted italic
5
+ * ink — the quiet, set-apart voice. The `danger` tone is the one place a hue is allowed: the rule (and
6
+ * the glyph, if there is one) swaps to `--xeno-danger`. `data-tone` on the root is the CSS seam.
7
+ *
8
+ * ## Why `danger` gets a glyph and `default` does not
9
+ *
10
+ * A default callout is an aside — a quieter voice inside the prose — and stamping a mark on it changes
11
+ * what it is. A danger callout is the opposite: its whole job is "stop and read this", and a glyph is
12
+ * the fastest way to say so. So `danger` carries `xeno.alert` unless told otherwise, `default` carries
13
+ * nothing unless asked, and either can be overridden with `icon`.
14
+ */
15
+ export type CalloutTone = 'default' | 'danger';
16
+ export interface CalloutProps extends Omit<HTMLAttributes<HTMLQuoteElement>, 'children'> {
17
+ readonly children?: ReactNode;
18
+ /** `danger` recolours the rule and glyph to `--xeno-danger`; otherwise the block stays monochrome. */
19
+ readonly tone?: CalloutTone;
20
+ /**
21
+ * A glyph before the text. Defaults to `xeno.alert` on the `danger` tone and to none otherwise; pass
22
+ * `false` to suppress the danger default, or a declaration to set your own on either tone.
23
+ */
24
+ readonly icon?: ElementDeclaration | false;
25
+ /**
26
+ * Play the entrance on mount: the rule draws down from the top while the text arrives behind it.
27
+ * Opt-in, the same way `<Button enter>` is — a page of callouts rendered at once should not all
28
+ * animate themselves into existence, but a single one that has just SURFACED should.
29
+ */
30
+ readonly enter?: boolean;
31
+ }
32
+ export declare function Callout({ children, tone, icon, enter, className, ...rest }: CalloutProps): ReactElement;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { XenoElement } from '../XenoElement.js';
3
+ import { cx } from '../controls/util.js';
4
+ import Alert from '@xenosystem/elements/elements/alert';
5
+ /** Glyph px — sized against the italic body text, not the 24px grid. */
6
+ const GLYPH_PX = 15;
7
+ export function Callout({ children, tone = 'default', icon, enter = false, className, ...rest }) {
8
+ const glyph = icon === false ? undefined : (icon ?? (tone === 'danger' ? Alert : undefined));
9
+ return (_jsxs("blockquote", { className: cx('xeno-callout', className), "data-tone": tone, "data-icon": glyph !== undefined ? '' : undefined, "data-enter": enter ? '' : undefined, ...rest, children: [glyph !== undefined && (_jsx("span", { className: "xeno-callout-icon", "aria-hidden": "true", children: _jsx(XenoElement, { decl: glyph, size: GLYPH_PX }) })), glyph !== undefined ? _jsx("div", { className: "xeno-callout-body", children: children }) : children] }));
10
+ }
@@ -0,0 +1,9 @@
1
+ import type { HTMLAttributes, ReactElement } from 'react';
2
+ /**
3
+ * `<Caret>` — the thin blinking caret that trails streaming text. A hairline `--xeno-text` block that
4
+ * blinks via a CSS keyframe; under `prefers-reduced-motion` it holds solid (the keyframe only attaches
5
+ * under `no-preference`). Purely decorative, so it is `aria-hidden`.
6
+ */
7
+ export interface CaretProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {
8
+ }
9
+ export declare function Caret({ className, ...rest }: CaretProps): ReactElement;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from '../controls/util.js';
3
+ export function Caret({ className, ...rest }) {
4
+ return _jsx("span", { className: cx('xeno-caret', className), "aria-hidden": "true", ...rest });
5
+ }
@@ -0,0 +1,36 @@
1
+ import { type HTMLAttributes, type ReactElement } from 'react';
2
+ /**
3
+ * `<CodeBlock>` — a monochrome, self-contained code surface (NO syntax-colour highlighter, by design).
4
+ *
5
+ * Shell is greyscale (canvas card + hairline border); the ink is the code. The header carries an
6
+ * uppercase mono language label and a right-aligned row of icon actions — Copy (with a ~1.5s "copied"
7
+ * confirmation), an optional Collapse toggle, and an optional Run (only when `onRun` is given). A
8
+ * `collapsible` block hides its body behind a "N lines" summary. An optional `output` panel below
9
+ * reports a run: Running… / ok text / error text (the only place a hue — `--xeno-danger` — appears).
10
+ *
11
+ * CSS-first: `data-collapsed` on the root drives body↔summary visibility and the chevron rotation; the
12
+ * transient copied state swaps the glyph to Check and adds `.is-copied`. Behaviour is hand-rolled — the
13
+ * copy timeout is created only inside the handler (never at module scope) and cleared on unmount.
14
+ */
15
+ export type CodeBlockOutputStatus = 'running' | 'ok' | 'error';
16
+ export interface CodeBlockOutput {
17
+ /** Lifecycle of a run this block triggered. */
18
+ readonly status: CodeBlockOutputStatus;
19
+ /** stdout / stderr / result text (omitted while `running`). */
20
+ readonly text?: string;
21
+ }
22
+ export interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
23
+ /** The source to display. Rendered verbatim, monochrome, no tokenisation. */
24
+ readonly code: string;
25
+ /** Language label, shown uppercased in the header (defaults to `code`). */
26
+ readonly language?: string;
27
+ /** When true, the body can fold behind an "N lines" summary. */
28
+ readonly collapsible?: boolean;
29
+ /** Initial folded state for a `collapsible` block. */
30
+ readonly defaultCollapsed?: boolean;
31
+ /** Show a Run action in the header; called on press. */
32
+ readonly onRun?: () => void;
33
+ /** Attach a run-output panel below the body. */
34
+ readonly output?: CodeBlockOutput;
35
+ }
36
+ export declare function CodeBlock({ code, language, collapsible, defaultCollapsed, onRun, output, className, ...rest }: CodeBlockProps): ReactElement;
@@ -0,0 +1,50 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useRef, useState } from 'react';
4
+ import { IconButton } from '../controls/IconButton.js';
5
+ import { cx } from '../controls/util.js';
6
+ import Copy from '@xenosystem/elements/elements/copy';
7
+ import Check from '@xenosystem/elements/elements/check';
8
+ import ChevronDown from '@xenosystem/elements/elements/chevron-down';
9
+ import Terminal from '@xenosystem/elements/elements/terminal';
10
+ /** Count of source lines, tolerant of a single trailing newline. */
11
+ function countLines(code) {
12
+ if (code === '')
13
+ return 0;
14
+ return code.replace(/\n$/, '').split('\n').length;
15
+ }
16
+ export function CodeBlock({ code, language, collapsible = false, defaultCollapsed = false, onRun, output, className, ...rest }) {
17
+ const [copied, setCopied] = useState(false);
18
+ const [collapsed, setCollapsed] = useState(collapsible ? defaultCollapsed : false);
19
+ const timer = useRef(undefined);
20
+ const bodyId = useId();
21
+ // Clear a pending "copied" timeout if the block unmounts first.
22
+ useEffect(() => () => {
23
+ if (timer.current !== undefined)
24
+ clearTimeout(timer.current);
25
+ }, []);
26
+ /**
27
+ * The acknowledgement waits for the write to actually succeed. Firing it alongside the call meant the
28
+ * glyph swapped to Check and the label announced "Copied" whether or not anything reached the
29
+ * clipboard — and `writeText` rejects routinely: an insecure context, a denied permission, or the
30
+ * very common `NotAllowedError` when the document does not have focus. A control that reports a
31
+ * result it did not get is worse than one that reports nothing.
32
+ */
33
+ const confirm = () => {
34
+ setCopied(true);
35
+ if (timer.current !== undefined)
36
+ clearTimeout(timer.current);
37
+ timer.current = setTimeout(() => setCopied(false), 1500);
38
+ };
39
+ const handleCopy = () => {
40
+ if (typeof navigator === 'undefined' || !navigator.clipboard)
41
+ return;
42
+ navigator.clipboard.writeText(code).then(confirm, () => {
43
+ // Swallowed on purpose: the failure is already reported by the acknowledgement not appearing,
44
+ // and an unhandled rejection here would surface as a console error the user cannot act on.
45
+ });
46
+ };
47
+ const lines = countLines(code);
48
+ const lineLabel = `${lines} ${lines === 1 ? 'line' : 'lines'}`;
49
+ return (_jsxs("div", { className: cx('xeno-code', className), "data-collapsed": collapsed ? 'true' : 'false', "data-availability": "enabled", ...rest, children: [_jsxs("div", { className: "xeno-code-header", children: [_jsx("span", { className: "xeno-code-lang", children: language ?? 'code' }), _jsxs("div", { className: "xeno-code-actions", children: [onRun && (_jsx(IconButton, { icon: Terminal, "aria-label": "Run", size: "xs", className: "xeno-code-action", onClick: onRun })), collapsible && (_jsx(IconButton, { icon: ChevronDown, "aria-label": collapsed ? 'Expand' : 'Collapse', size: "xs", className: "xeno-code-action xeno-code-collapse", "aria-expanded": !collapsed, "aria-controls": bodyId, onClick: () => setCollapsed((c) => !c) })), _jsx(IconButton, { icon: copied ? Check : Copy, "aria-label": copied ? 'Copied' : 'Copy', size: "xs", className: cx('xeno-code-action', 'xeno-code-copy', copied && 'is-copied'), onClick: handleCopy })] })] }), _jsx("pre", { id: bodyId, className: "xeno-code-body", tabIndex: 0, children: _jsx("code", { className: "xeno-code-code", children: code }) }), collapsible && (_jsx("button", { type: "button", className: "xeno-code-summary", "aria-controls": bodyId, "aria-expanded": !collapsed, onClick: () => setCollapsed(false), children: lineLabel })), output && (_jsx("div", { className: "xeno-code-output", "data-status": output.status, role: "status", "aria-live": "polite", children: output.status === 'running' ? (_jsx("span", { className: "xeno-code-output-running", children: "Running\u2026" })) : (_jsx("pre", { className: "xeno-code-output-text", children: output.text ?? '' })) }))] }));
50
+ }
@@ -0,0 +1,24 @@
1
+ import { type HTMLAttributes, type ReactElement, type ReactNode } from 'react';
2
+ import type { ElementDeclaration } from '@xenosystem/elements/schema';
3
+ /**
4
+ * `<Collapsible>` — a "Thoughts"-style disclosure. The summary row pairs a lightbulb glyph (the Sun
5
+ * element, the nearest monochrome stand-in) with a label and a rotating chevron; pressing it folds the
6
+ * body. The body is set apart like a {@link Callout}: a 2px `--xeno-border` left rule with `12px` inset.
7
+ *
8
+ * State is controlled (`open` / `onOpenChange`) or uncontrolled (`defaultOpen`, internal state). The open
9
+ * axis is stamped on the root as `data-open`; the chevron rotates from CSS off that seam — no JS motion.
10
+ */
11
+ export interface CollapsibleProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
12
+ /** The always-visible label of the disclosure row. */
13
+ readonly summary: ReactNode;
14
+ /** The foldaway body. */
15
+ readonly children?: ReactNode;
16
+ /** Initial open state when uncontrolled. */
17
+ readonly defaultOpen?: boolean;
18
+ /** Controlled open state. When set, `onOpenChange` owns transitions. */
19
+ readonly open?: boolean;
20
+ readonly onOpenChange?: (open: boolean) => void;
21
+ /** The summary glyph — imported per-id from `@xenosystem/elements`. Defaults to the lightbulb-like Sun. */
22
+ readonly icon?: ElementDeclaration;
23
+ }
24
+ export declare function Collapsible({ summary, children, defaultOpen, open, onOpenChange, icon, className, ...rest }: CollapsibleProps): ReactElement;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId, useState } from 'react';
3
+ import Sun from '@xenosystem/elements/elements/sun';
4
+ import ChevronDown from '@xenosystem/elements/elements/chevron-down';
5
+ import { XenoElement } from '../XenoElement.js';
6
+ import { cx } from '../controls/util.js';
7
+ export function Collapsible({ summary, children, defaultOpen = false, open, onOpenChange, icon = Sun, className, ...rest }) {
8
+ const [uncontrolled, setUncontrolled] = useState(defaultOpen);
9
+ const isOpen = open !== undefined ? open : uncontrolled;
10
+ const bodyId = useId();
11
+ const labelId = useId();
12
+ const toggle = () => {
13
+ const next = !isOpen;
14
+ if (open === undefined)
15
+ setUncontrolled(next);
16
+ onOpenChange?.(next);
17
+ };
18
+ return (_jsxs("div", { className: cx('xeno-collapsible', className), "data-open": isOpen ? '' : undefined, ...rest, children: [_jsxs("button", { type: "button", className: "xeno-collapsible-summary", "aria-expanded": isOpen, "aria-controls": bodyId, onClick: toggle, children: [_jsx("span", { className: "xeno-collapsible-icon", "aria-hidden": "true", children: _jsx(XenoElement, { decl: icon, size: 16 }) }), _jsx("span", { id: labelId, className: "xeno-collapsible-label", children: summary }), _jsx("span", { className: "xeno-collapsible-chevron", "aria-hidden": "true", children: _jsx(XenoElement, { decl: ChevronDown, size: 16 }) })] }), _jsx("div", { id: bodyId, className: "xeno-collapsible-body", role: "region", "aria-labelledby": labelId, hidden: !isOpen, children: children })] }));
19
+ }
@@ -0,0 +1,12 @@
1
+ import type { HTMLAttributes, ReactElement, ReactNode } from 'react';
2
+ /**
3
+ * `<InlineCode>` — a `<code>` run for inline monospace spans inside prose (markdown `` `code` ``).
4
+ *
5
+ * Monochrome by design: a `--xeno-surface` chip, `radius-sm` corners, `2px 6px` padding, and — the
6
+ * reconciliation the app owed — ink at `--xeno-text` rather than the legacy peach. No hue ever appears
7
+ * here; it is pure shell. Wraps rather than overflowing so a long token cannot blow out the line box.
8
+ */
9
+ export interface InlineCodeProps extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
10
+ readonly children?: ReactNode;
11
+ }
12
+ export declare function InlineCode({ children, className, ...rest }: InlineCodeProps): ReactElement;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from '../controls/util.js';
3
+ export function InlineCode({ children, className, ...rest }) {
4
+ return (_jsx("code", { className: cx('xeno-inline-code', className), ...rest, children: children }));
5
+ }
@@ -0,0 +1,45 @@
1
+ import type { HTMLAttributes, ReactElement } from 'react';
2
+ import type { ElementDeclaration } from '@xenosystem/elements/schema';
3
+ import { type ControlSizeToken } from '../controls/util.js';
4
+ /**
5
+ * `<ModelPicker>` — a model chooser: a trigger button (leading glyph + current model + a chevron) that
6
+ * opens a panel of options. It is the same monochrome grammar as the controls — a `button` on the
7
+ * `availability` axis, options on the `selection` axis (`data-selection` + `aria-selected`), and a
8
+ * hand-rolled overlay (a relative wrapper + an absolute panel, no portal lib).
9
+ *
10
+ * Two panel layouts via `layout`:
11
+ * - `tray` — an absolute dropdown of grouped rows (a provider header + model rows); the selected row is
12
+ * marked with a check glyph on a `--xeno-control` ground.
13
+ * - `rail` — a horizontal, scrollable strip of compact chips with left/right fade edges; the selected
14
+ * chip sits on `--xeno-control`.
15
+ *
16
+ * Controlled selection (`value` / `onChange`) and optionally controlled open state (`open` /
17
+ * `onOpenChange`). Esc and an outside click close it; keyboard nav is hand-rolled (Arrow keys move the
18
+ * active option, Home/End jump, Enter/Space select) via `aria-activedescendant`.
19
+ */
20
+ export interface ModelOption {
21
+ readonly id: string;
22
+ readonly label: string;
23
+ /** The vendor/family label — becomes a group header (tray) or a chip eyebrow (rail). */
24
+ readonly provider?: string;
25
+ }
26
+ export type ModelPickerLayout = 'tray' | 'rail';
27
+ export interface ModelPickerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'> {
28
+ readonly options: readonly ModelOption[];
29
+ /** The selected option id. */
30
+ readonly value: string;
31
+ readonly onChange?: (id: string) => void;
32
+ readonly layout?: ModelPickerLayout;
33
+ readonly size?: ControlSizeToken;
34
+ /** The trigger's leading glyph — defaults to Gear (pass Terminal, etc.). */
35
+ readonly triggerIcon?: ElementDeclaration;
36
+ /** Shown on the trigger when `value` matches no option. */
37
+ readonly placeholder?: string;
38
+ readonly disabled?: boolean;
39
+ /** Controlled open state; omit to let the component own it. */
40
+ readonly open?: boolean;
41
+ readonly onOpenChange?: (open: boolean) => void;
42
+ /** Accessible name for the trigger and the listbox. */
43
+ readonly label?: string;
44
+ }
45
+ export declare function ModelPicker({ options, value, onChange, layout, size, triggerIcon, placeholder, disabled, open, onOpenChange, label, className, ...rest }: ModelPickerProps): ReactElement;
@@ -0,0 +1,124 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef, useState } from 'react';
3
+ import { XenoElement } from '../XenoElement.js';
4
+ import { sizeAttr, iconPx, cx } from '../controls/util.js';
5
+ import Gear from '@xenosystem/elements/elements/gear';
6
+ import ChevronDown from '@xenosystem/elements/elements/chevron-down';
7
+ import Check from '@xenosystem/elements/elements/check';
8
+ export function ModelPicker({ options, value, onChange, layout = 'tray', size = 'md', triggerIcon, placeholder = 'Select a model', disabled = false, open, onOpenChange, label, className, ...rest }) {
9
+ const baseId = useId();
10
+ const rootRef = useRef(null);
11
+ const triggerRef = useRef(null);
12
+ const panelRef = useRef(null);
13
+ const isControlledOpen = open !== undefined;
14
+ const [internalOpen, setInternalOpen] = useState(false);
15
+ const isOpen = isControlledOpen ? open : internalOpen;
16
+ const [activeIndex, setActiveIndex] = useState(0);
17
+ const setOpen = (next) => {
18
+ if (!isControlledOpen)
19
+ setInternalOpen(next);
20
+ onOpenChange?.(next);
21
+ };
22
+ const glyph = iconPx(size);
23
+ const n = options.length;
24
+ const selected = options.find((o) => o.id === value);
25
+ // On open: sync the active option to the selection, focus the panel, and wire outside-click to close.
26
+ useEffect(() => {
27
+ if (!isOpen)
28
+ return;
29
+ const idx = options.findIndex((o) => o.id === value);
30
+ setActiveIndex(idx >= 0 ? idx : 0);
31
+ panelRef.current?.focus();
32
+ const onDocPointer = (e) => {
33
+ if (rootRef.current && !rootRef.current.contains(e.target))
34
+ setOpen(false);
35
+ };
36
+ document.addEventListener('mousedown', onDocPointer);
37
+ return () => document.removeEventListener('mousedown', onDocPointer);
38
+ // eslint-disable-next-line react-hooks/exhaustive-deps
39
+ }, [isOpen]);
40
+ const close = () => {
41
+ setOpen(false);
42
+ triggerRef.current?.focus();
43
+ };
44
+ const select = (opt) => {
45
+ onChange?.(opt.id);
46
+ setOpen(false);
47
+ triggerRef.current?.focus();
48
+ };
49
+ const move = (delta) => {
50
+ if (n === 0)
51
+ return;
52
+ setActiveIndex((cur) => (cur + delta + n) % n);
53
+ };
54
+ const onTriggerKeyDown = (e) => {
55
+ if (disabled)
56
+ return;
57
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
58
+ e.preventDefault();
59
+ if (!isOpen)
60
+ setOpen(true);
61
+ }
62
+ };
63
+ const onPanelKeyDown = (e) => {
64
+ switch (e.key) {
65
+ case 'Escape':
66
+ e.preventDefault();
67
+ close();
68
+ break;
69
+ case 'ArrowDown':
70
+ case 'ArrowRight':
71
+ e.preventDefault();
72
+ move(1);
73
+ break;
74
+ case 'ArrowUp':
75
+ case 'ArrowLeft':
76
+ e.preventDefault();
77
+ move(-1);
78
+ break;
79
+ case 'Home':
80
+ e.preventDefault();
81
+ setActiveIndex(0);
82
+ break;
83
+ case 'End':
84
+ e.preventDefault();
85
+ setActiveIndex(n > 0 ? n - 1 : 0);
86
+ break;
87
+ case 'Enter':
88
+ case ' ': {
89
+ e.preventDefault();
90
+ const opt = options[activeIndex];
91
+ if (opt)
92
+ select(opt);
93
+ break;
94
+ }
95
+ case 'Tab':
96
+ setOpen(false);
97
+ break;
98
+ default:
99
+ break;
100
+ }
101
+ };
102
+ const availability = disabled ? 'disabled' : 'enabled';
103
+ const listLabel = label ?? 'Model options';
104
+ const activeId = `${baseId}-opt-${activeIndex}`;
105
+ // Group tray rows by provider, preserving first-seen order and each option's flat index (nav is flat).
106
+ const groups = [];
107
+ options.forEach((opt, index) => {
108
+ const found = groups.find((g) => g.provider === opt.provider);
109
+ if (found)
110
+ found.items.push({ opt, index });
111
+ else
112
+ groups.push({ items: [{ opt, index }], ...(opt.provider !== undefined ? { provider: opt.provider } : {}) });
113
+ });
114
+ return (_jsxs("div", { ref: rootRef, className: cx('xeno-model-picker', className), "data-layout": layout, "data-open": isOpen ? 'true' : 'false', "data-availability": availability, ...rest, children: [_jsxs("button", { ref: triggerRef, type: "button", className: cx('xeno-btn', 'xeno-mp-trigger'), "data-variant": "secondary", "data-availability": availability, ...sizeAttr(size), disabled: disabled, "aria-haspopup": "listbox", "aria-expanded": isOpen, ...(label !== undefined ? { 'aria-label': label } : {}), onClick: () => {
115
+ if (!disabled)
116
+ setOpen(!isOpen);
117
+ }, onKeyDown: onTriggerKeyDown, children: [_jsx(XenoElement, { decl: triggerIcon ?? Gear, size: glyph }), _jsx("span", { className: cx('xeno-mp-label', !selected && 'xeno-mp-placeholder'), children: selected ? selected.label : placeholder }), _jsx("span", { className: "xeno-mp-chevron", "aria-hidden": "true", children: _jsx(XenoElement, { decl: ChevronDown, size: glyph }) })] }), isOpen && layout === 'tray' && (_jsx("div", { ref: panelRef, className: "xeno-mp-panel xeno-mp-panel--tray", role: "listbox", tabIndex: -1, "aria-label": listLabel, "aria-activedescendant": activeId, onKeyDown: onPanelKeyDown, children: groups.map((group, gi) => (_jsxs("div", { className: "xeno-mp-group", role: "group", ...(group.provider !== undefined ? { 'aria-label': group.provider } : {}), children: [group.provider !== undefined && _jsx("div", { className: "xeno-mp-group-label", children: group.provider }), group.items.map(({ opt, index }) => {
118
+ const isSel = opt.id === value;
119
+ return (_jsxs("div", { id: `${baseId}-opt-${index}`, role: "option", "aria-selected": isSel, className: "xeno-mp-option", "data-selection": isSel ? 'on' : 'off', "data-active": index === activeIndex ? 'true' : undefined, onMouseEnter: () => setActiveIndex(index), onClick: () => select(opt), children: [_jsx("span", { className: "xeno-mp-check", "aria-hidden": "true", children: isSel && _jsx(XenoElement, { decl: Check, size: glyph }) }), _jsx("span", { className: "xeno-mp-option-name", children: opt.label })] }, opt.id));
120
+ })] }, group.provider ?? `__ungrouped-${gi}`))) })), isOpen && layout === 'rail' && (_jsx("div", { ref: panelRef, className: "xeno-mp-panel xeno-mp-panel--rail", role: "listbox", "aria-orientation": "horizontal", tabIndex: -1, "aria-label": listLabel, "aria-activedescendant": activeId, onKeyDown: onPanelKeyDown, children: _jsx("div", { className: "xeno-mp-rail-viewport", children: _jsx("div", { className: "xeno-mp-rail", children: options.map((opt, index) => {
121
+ const isSel = opt.id === value;
122
+ return (_jsxs("div", { id: `${baseId}-opt-${index}`, role: "option", "aria-selected": isSel, className: "xeno-mp-chip", "data-selection": isSel ? 'on' : 'off', "data-active": index === activeIndex ? 'true' : undefined, title: opt.provider !== undefined ? `${opt.provider} · ${opt.label}` : opt.label, onMouseEnter: () => setActiveIndex(index), onClick: () => select(opt), children: [opt.provider !== undefined && _jsx("span", { className: "xeno-mp-chip-provider", children: opt.provider }), _jsx("span", { className: "xeno-mp-chip-label", children: opt.label }), isSel && (_jsx("span", { className: "xeno-mp-chip-check", "aria-hidden": "true", children: _jsx(XenoElement, { decl: Check, size: Math.max(12, glyph - 2) }) }))] }, opt.id));
123
+ }) }) }) }))] }));
124
+ }
@@ -0,0 +1,23 @@
1
+ import type { HTMLAttributes, ReactElement } from 'react';
2
+ /**
3
+ * `sourceInitial` — the placeholder glyph for a source with no favicon: the domain's first letter,
4
+ * upper-cased, with a leading `www.` stripped. Exported so <SourcesDisclosure> draws the same avatar
5
+ * fallback as <SourceCard>.
6
+ */
7
+ export declare function sourceInitial(domain: string): string;
8
+ /**
9
+ * `<SourceCard>` — a link-embed row: a leading favicon/thumb TILE (square, radius-md — or a placeholder
10
+ * carrying the domain initial when no favicon), then the title (`--xeno-text`), the domain (`--xeno-muted`),
11
+ * and an optional snippet. Card radius, a hairline border, and on hover the border lifts to `--xeno-muted`.
12
+ *
13
+ * When `href` is set the whole row is an `<a>` (the accessible name is its own text; the tile is decorative).
14
+ * With no `href` it degrades to a passive `<div>` embed.
15
+ */
16
+ export interface SourceCardProps extends Omit<HTMLAttributes<HTMLElement>, 'title' | 'children'> {
17
+ readonly title: string;
18
+ readonly domain: string;
19
+ readonly href?: string;
20
+ readonly snippet?: string;
21
+ readonly favicon?: string;
22
+ }
23
+ export declare function SourceCard({ title, domain, href, snippet, favicon, className, ...rest }: SourceCardProps): ReactElement;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from '../controls/util.js';
3
+ /**
4
+ * `sourceInitial` — the placeholder glyph for a source with no favicon: the domain's first letter,
5
+ * upper-cased, with a leading `www.` stripped. Exported so <SourcesDisclosure> draws the same avatar
6
+ * fallback as <SourceCard>.
7
+ */
8
+ export function sourceInitial(domain) {
9
+ const cleaned = domain.replace(/^www\./i, '').trim();
10
+ const first = cleaned[0]; // noUncheckedIndexedAccess: string | undefined
11
+ return (first ?? '?').toUpperCase();
12
+ }
13
+ export function SourceCard({ title, domain, href, snippet, favicon, className, ...rest }) {
14
+ const tile = (_jsx("span", { className: "xeno-source-card-tile", "data-placeholder": favicon === undefined ? '' : undefined, "aria-hidden": "true", children: favicon !== undefined ? (_jsx("img", { className: "xeno-source-card-favicon", src: favicon, alt: "", loading: "lazy" })) : (sourceInitial(domain)) }));
15
+ const body = (_jsxs("span", { className: "xeno-source-card-body", children: [_jsx("span", { className: "xeno-source-card-title", children: title }), _jsx("span", { className: "xeno-source-card-domain", children: domain }), snippet !== undefined && _jsx("span", { className: "xeno-source-card-snippet", children: snippet })] }));
16
+ const cls = cx('xeno-source-card', className);
17
+ return href !== undefined ? (_jsxs("a", { className: cls, "data-availability": "enabled", href: href, ...rest, children: [tile, body] })) : (_jsxs("div", { className: cls, "data-availability": "enabled", ...rest, children: [tile, body] }));
18
+ }
@@ -0,0 +1,26 @@
1
+ import { type HTMLAttributes, type ReactElement } from 'react';
2
+ /** One referenced source — the row model shared by the stack avatar and the expanded <SourceCard>. */
3
+ export interface SourceRef {
4
+ readonly title: string;
5
+ readonly domain: string;
6
+ readonly href?: string;
7
+ readonly snippet?: string;
8
+ readonly favicon?: string;
9
+ }
10
+ /**
11
+ * `<SourcesDisclosure>` — a BORDERLESS header row: a rotating chevron, the word "Sources", an overlapping
12
+ * favicon STACK (square tiles, -8px overlap, +N overflow), and a mono, muted count. Clicking the header
13
+ * toggles a panel of <SourceCard> rows.
14
+ *
15
+ * Disclosure state is controlled (`open` / `onOpenChange`) or uncontrolled (`defaultOpen`, internal state).
16
+ * The open axis is stamped on the root as `data-open`; the chevron rotates from CSS off that seam.
17
+ */
18
+ export interface SourcesDisclosureProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
19
+ readonly sources: readonly SourceRef[];
20
+ readonly defaultOpen?: boolean;
21
+ readonly open?: boolean;
22
+ readonly onOpenChange?: (open: boolean) => void;
23
+ /** How many favicons to stack before collapsing the rest into a +N overflow tile. Default 4. */
24
+ readonly maxAvatars?: number;
25
+ }
26
+ export declare function SourcesDisclosure({ sources, defaultOpen, open, onOpenChange, maxAvatars, className, ...rest }: SourcesDisclosureProps): ReactElement;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId, useState } from 'react';
3
+ import ChevronDown from '@xenosystem/elements/elements/chevron-down';
4
+ import { XenoElement } from '../XenoElement.js';
5
+ import { cx } from '../controls/util.js';
6
+ import { SourceCard, sourceInitial } from './SourceCard.js';
7
+ export function SourcesDisclosure({ sources, defaultOpen = false, open, onOpenChange, maxAvatars = 4, className, ...rest }) {
8
+ const [uncontrolled, setUncontrolled] = useState(defaultOpen);
9
+ const isOpen = open !== undefined ? open : uncontrolled;
10
+ const panelId = useId();
11
+ const toggle = () => {
12
+ const next = !isOpen;
13
+ if (open === undefined)
14
+ setUncontrolled(next);
15
+ onOpenChange?.(next);
16
+ };
17
+ const shown = sources.slice(0, maxAvatars);
18
+ const overflow = sources.length - shown.length;
19
+ return (_jsxs("div", { className: cx('xeno-sources', className), "data-open": isOpen ? '' : undefined, ...rest, children: [_jsxs("button", { type: "button", className: "xeno-sources-header", "aria-expanded": isOpen, "aria-controls": panelId, onClick: toggle, children: [_jsx("span", { className: "xeno-sources-chevron", "aria-hidden": "true", children: _jsx(XenoElement, { decl: ChevronDown, size: 16 }) }), _jsx("span", { className: "xeno-sources-label", children: "Sources" }), _jsxs("span", { className: "xeno-sources-stack", "aria-hidden": "true", children: [shown.map((s, i) => (_jsx("span", { className: "xeno-sources-avatar", style: { zIndex: shown.length - i }, children: s.favicon !== undefined ? (_jsx("img", { src: s.favicon, alt: "", loading: "lazy" })) : (sourceInitial(s.domain)) }, i))), overflow > 0 && (_jsxs("span", { className: "xeno-sources-avatar xeno-sources-overflow", style: { zIndex: 0 }, children: ["+", overflow] }))] }), _jsx("span", { className: "xeno-sources-count", children: sources.length })] }), _jsx("div", { id: panelId, className: "xeno-sources-panel", role: "region", "aria-label": "Sources", hidden: !isOpen, children: sources.map((s, i) => (_jsx(SourceCard, { title: s.title, domain: s.domain, ...(s.href !== undefined ? { href: s.href } : {}), ...(s.snippet !== undefined ? { snippet: s.snippet } : {}), ...(s.favicon !== undefined ? { favicon: s.favicon } : {}) }, i))) })] }));
20
+ }
@@ -0,0 +1,39 @@
1
+ import type { ButtonHTMLAttributes, ReactNode } from 'react';
2
+ import type { ElementDeclaration } from '@xenosystem/elements/schema';
3
+ import { type ControlSizeToken } from './util.js';
4
+ /**
5
+ * `<Button>` — the first non-icon element: the `composite` that proves the "an icon is not a different
6
+ * thing from a button" unification (SPEC §3.1). It honours the SAME axes as an icon — `availability`
7
+ * (enabled/disabled/busy) via `data-availability` — and composes an icon into its slot through the very
8
+ * same `<XenoElement>` renderer. Variants and sizes come from the design tokens; behaviour is a plain
9
+ * button (Base UI later).
10
+ */
11
+ /**
12
+ * `quiet` is the chrome button: a hairline border at rest, muted ink, and it comes forward only when
13
+ * you reach for it. It is neither of its neighbours — `outline` is bordered but reads at full strength
14
+ * and brightens its BORDER on hover, `ghost` has the right ink and no border at all. The set already
15
+ * held this shape: `danger` is bordered-and-muted with a red hover, and the neutral member of the pair
16
+ * was simply missing, so products wrote it themselves in five lines of utility classes.
17
+ */
18
+ export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'outline' | 'quiet' | 'danger';
19
+ export interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'style'> {
20
+ readonly variant?: ButtonVariant;
21
+ readonly size?: ControlSizeToken;
22
+ /** A glyph before the label — imported per-id from `@xenosystem/elements`. */
23
+ readonly leadingIcon?: ElementDeclaration;
24
+ /** A glyph after the label. */
25
+ readonly trailingIcon?: ElementDeclaration;
26
+ /** Work in progress — maps to the `busy` availability. */
27
+ readonly busy?: boolean;
28
+ /** Play the entrance animation on mount (the locked "send button" appear: fade + slide + scale up). */
29
+ readonly enter?: boolean;
30
+ readonly children?: ReactNode;
31
+ }
32
+ /**
33
+ * Ref-forwarding, and it is load-bearing rather than tidy. These buttons anchor things: a popover
34
+ * positions against the one that opened it, a focus-restore puts focus back on it after a dialog
35
+ * closes. On React 19 `ref` is an ordinary prop and a plain function component would pass it through;
36
+ * on React 18 — still inside this package's peer range — `createElement` STRIPS it, so the consumer's
37
+ * `ref.current` stays null and the anchor silently lands at 0,0. Same reasoning as `RadioRow`.
38
+ */
39
+ export declare const Button: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;