@gradeui/ui 3.2.0 → 4.0.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.
@@ -2,15 +2,16 @@
2
2
  name: Button
3
3
  import: "@gradeui/ui"
4
4
  variants: [default, destructive, outline, secondary, ghost, link, raised]
5
- sizes: [sm, md, lg, icon]
5
+ sizes: [2xs, xs, sm, md, lg]
6
6
  props:
7
7
  - variant? (default | destructive | outline | secondary | ghost | link | raised) — `raised` here is a back-compat alias (the raised TRAIT on a neutral key surface); prefer the `raised` prop
8
8
  - raised?: boolean — presence TRAIT: tactile elevation (bevel + drop + hover glow + pressed sink) layered onto ANY variant — raised primary, raised outline, etc. Glow tone reads --btn-glow → --accent-glow → --selected-glow; override per-button via style={{ "--btn-glow": "var(--warning)" }}
9
- - size? (sm | md | lg | icon) — t-shirt scale aligned with Tabs/ToggleGroup heights (sm=h-7, md=h-8, lg=h-10). `default` still works as an alias for `md`.
9
+ - size? (2xs | xs | sm | md | lg) — t-shirt scale aligned with Tabs/ToggleGroup heights (2xs=h-5, xs=h-6, sm=h-7, md=h-8, lg=h-10). 2xs/xs are the dense tool-panel sizes (match Figma Button size=2xs/xs). `default` still works as an alias for `md`.
10
+ - iconOnly?: boolean — squares the button at the current `size` height (w = h, no horizontal padding) for icon-only buttons; the icon child is centered. This is THE way to make a square icon button at any density (sm→28², 2xs→20²).
10
11
  - asChild?: boolean — renders as the child element (use to wrap <a>/<Link>)
11
12
  - disabled?: boolean
12
13
  - All native button HTML attrs (onClick, type, etc.)
13
- when_to_use: Any clickable action. Use size="icon" for square icon-only buttons, variant="link" for inline links that should look like Button, the `raised` prop for high-commitment / weighty actions where the chrome can afford a tactile "physical key" treatment (composes with any variant; variant="raised" remains the neutral-key alias). A Button placed next to a TabsList of the same size lines up edge-to-edge without per-call overrides.
14
+ when_to_use: Any clickable action. Use `iconOnly` for square icon-only buttons (at any size), variant="link" for inline links that should look like Button, the `raised` prop for high-commitment / weighty actions where the chrome can afford a tactile "physical key" treatment (composes with any variant; variant="raised" remains the neutral-key alias). A Button placed next to a TabsList of the same size lines up edge-to-edge without per-call overrides.
14
15
  composes_with: [Dialog, DropdownMenu, Tooltip, Card (in CardFooter), Row, Form controls]
15
16
  aliases: [button, push button, plain button, bordered button, destructive button, capsule button, link button, action button, cta, raised button, pill button, key button]
16
17
  ---
@@ -18,7 +19,8 @@ aliases: [button, push button, plain button, bordered button, destructive button
18
19
  ```jsx
19
20
  <Button>Save</Button>
20
21
  <Button variant="outline" size="sm">Cancel</Button>
21
- <Button size="icon" variant="ghost"><Mail /></Button>
22
+ <Button iconOnly variant="ghost"><Mail /></Button>
23
+ <Button size="sm" iconOnly variant="outline"><Plus /></Button>
22
24
  ```
23
25
 
24
26
  ```jsx
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: ColorPicker
3
+ import: "@gradeui/ui"
4
+ props:
5
+ - value?: string | null — a Grade colour token NAME ("action/primary"), the literal "transparent", or null when nothing is picked
6
+ - onValueChange?: (value: string | null) => void — fired with the next value (token name, "transparent", or null)
7
+ - tokens?: { group, tokens }[] — token families offered in the list; defaults to the Grade semantic set (surface / action / status)
8
+ - searchable?: boolean — show the search input (default true)
9
+ - triggerVariant? (default | inline) — default = form-control surface (swatch + name); inline = just a clickable swatch for inspector / fill-row use
10
+ - placeholder?: string — trigger text when nothing is selected
11
+ - searchPlaceholder?: string — search-input placeholder
12
+ - emptyMessage?: string — shown when search returns no rows
13
+ - allowTransparent?: boolean — include a Transparent option at the top (default true)
14
+ - align? (start | center | end) — popover alignment (default start)
15
+ - disabled?: boolean — lock to a read-only display of the current value
16
+ when_to_use: The token-led single-select colour picker — the focused "pick one colour token" sibling of FillPicker's solid tab. Use it anywhere a value is ONE Grade colour token (a fill colour, a border colour, an accent override) rather than a full paint. Composes Popover + Command exactly like Combobox, but each row is a Swatch + the token's short name, grouped by family and searchable. triggerVariant="inline" reduces the trigger to a single clickable swatch — reach for that inside inspectors and the FillSection fill rows. For a full paint (gradient / image / shader) use FillPicker; for a list of fills use FillSection; for a multi-stop gradient use GradientEditor.
17
+ composes_with: [Popover, Command, Swatch, FillSection, GradientEditor, Field, PropertyList]
18
+ aliases: [color picker, colour picker, token picker, colour token picker, color token picker, swatch picker, paint colour, fill colour picker, accent picker, colour dropdown]
19
+ ---
20
+
21
+ ```jsx
22
+ // Token-led colour field.
23
+ <ColorPicker value={color} onValueChange={setColor} />
24
+ ```
25
+
26
+ ```jsx
27
+ // Inline swatch trigger — the inspector / fill-row affordance.
28
+ <ColorPicker
29
+ triggerVariant="inline"
30
+ value={stopColor}
31
+ onValueChange={setStopColor}
32
+ aria-label="Stop colour"
33
+ />
34
+ ```
@@ -1,12 +1,16 @@
1
1
  ---
2
2
  name: FillPicker
3
3
  import: "@gradeui/ui"
4
+ subcomponents: [FillSection]
4
5
  props:
5
6
  - value: FillValue — current paint ({ type, color?, gradient?, src?, fit?, repeat?, tileSize?, preset?, palette?, postPreset?, opacity? }) (required)
6
7
  - onChange: (value: FillValue) => void — called on any change (required)
7
- when_to_use: Grade's paint picker the control for choosing a frame's background fill, modelled on Figma's fill popover. A fill-type icon row (solid · gradient · image · pattern · video · shader) switches the panel below; a global opacity sits at the foot. Emits a FillValue that maps 1:1 onto BackgroundFill props. This is a Studio/inspector chrome control pair it with BackgroundFill, which renders the chosen paint. Not for app content.
8
- composes_with: [BackgroundFill (renders the FillValue), Popover (host it in a popover), ShaderPresetPicker (the shader tab), the inspector Fill section]
9
- aliases: [fill picker, paint picker, background picker, fill chooser, fill popover]
8
+ - FillSection: value — FillValue[]the ordered list of fills to stack as rows
9
+ - FillSection: onChange (value: FillValue[]) => void fired with the next list on add / edit / remove / visibility toggle
10
+ - FillSection: title?: string section heading (default "Fills")
11
+ when_to_use: Grade's paint picker — the control for choosing a frame's background fill, modelled on Figma's fill popover. A fill-type icon row (solid · gradient · image · pattern · video · shader) switches the panel below; a global opacity sits at the foot. Emits a FillValue that maps 1:1 onto BackgroundFill props. This is a Studio/inspector chrome control — pair it with BackgroundFill, which renders the chosen paint. Not for app content. Use the FillSection subcomponent to edit a LIST of fills (the Figma "Fill" inspector section): each row is a Solid/Gradient/Image toggle, the matching value control (ColorPicker / GradientEditor popover / image URL), an opacity %, a visibility eye, and a remove button, with an add button in the header.
12
+ composes_with: [BackgroundFill (renders the FillValue), Popover (host it in a popover), ColorPicker (the solid value), GradientEditor (the gradient value), ShaderPresetPicker (the shader tab), the inspector Fill section]
13
+ aliases: [fill picker, paint picker, background picker, fill chooser, fill popover, fill section, fill list, fills inspector, paint section]
10
14
  notes: |
11
15
  Grade is token-led, so the solid + gradient tabs lead with theme-token
12
16
  swatches (`primary`, `accent`, `secondary`, `muted`, `card`,
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: GradientEditor
3
+ import: "@gradeui/ui"
4
+ props:
5
+ - value: { type, angle?, stops } — the structured gradient (type linear/radial/angular, optional angle in deg, ordered stops). NOT a CSS string — render the string via gradientToCss(value).
6
+ - onChange: (value) => void — fired with the next structured gradient on any edit
7
+ when_to_use: Edit a multi-stop CSS gradient with token-led stops. A type Select (Linear / Radial / Angular) with reverse + rotate actions, a live full-width preview bar (a Swatch type="gradient"), then a Stops list where each stop is a position %, a colour (ColorPicker token or raw), an opacity %, and a remove button; an add button appends a stop. Token stops resolve to oklch(var(--<token>)) so the preview re-voices with the theme. Emits the structured GradientValue (kept editable + serialisable); the caller turns it into CSS with the exported gradientToCss(value). Use inside a Popover from a FillSection gradient row, or standalone in a theme builder. For a single solid colour use ColorPicker; for a full paint (solid / gradient / image / shader) use FillPicker.
8
+ composes_with: [Select, Button, Input, ColorPicker, Swatch, Popover, FillSection]
9
+ aliases: [gradient editor, gradient picker, gradient builder, css gradient editor, stop editor, gradient stops, linear gradient editor, conic gradient editor]
10
+ ---
11
+
12
+ ```jsx
13
+ <GradientEditor
14
+ value={{
15
+ type: "linear",
16
+ angle: 90,
17
+ stops: [
18
+ { id: "a", position: 0, token: "action/primary", opacity: 1 },
19
+ { id: "b", position: 100, token: "action/accent", opacity: 1 },
20
+ ],
21
+ }}
22
+ onChange={setGradient}
23
+ />
24
+ ```
25
+
26
+ ```jsx
27
+ // Render the CSS string for a background.
28
+ import { gradientToCss } from "@gradeui/ui";
29
+ <div style={{ background: gradientToCss(gradient) }} />
30
+ ```
@@ -3,7 +3,7 @@ name: MediaSurface
3
3
  import: "@gradeui/ui"
4
4
  props:
5
5
  - aspect?: "video" | "square" | "portrait" | "wide" | "auto" — when omitted, derived from `hint` (album/product/food → square, portrait/poster → portrait, landscape → wide, video/audio/embed/generic → video)
6
- - radius?: "none" | "sm" | "md" | "lg" | "xl" (default "lg") — driven by `--gds-media-radius` CSS var
6
+ - radius?: "none" | "sm" | "md" | "lg" | "xl" (default "none") — driven by `--gds-media-radius` CSS var. Square by default so a slot mounted flush at the top of a Card lets the Card clip it; set `lg`/`xl` for a standalone rounded image
7
7
  - border?: boolean (default false)
8
8
  - loading?: boolean — renders the muted skeleton overlay
9
9
  - hint?: "album" | "portrait" | "landscape" | "poster" | "product" | "food" | "video" | "audio" | "embed" | "3d" | "generic" (default "generic") — picks the placeholder glyph + the default aspect + the future generation provider
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: Section
3
+ import: "@gradeui/ui"
4
+ subcomponents: [Container, SectionEyebrow, SectionTitle, SectionSubtitle, SectionDescription, SectionActions, SectionMedia]
5
+ props:
6
+ - Section: scope? (default | inverse | brand | accent | muted | card) — colour SUBTHEME; applies the `scope-*` class so the whole band re-tones (bg/fg/card/muted/border) while action colours stay vivid. Unset = the page surface. See STUDIO-COLOR.md.
7
+ - Section: background?: ReactNode — visual band background slot: image / video / gradient / shader (drop a <BackgroundFill> here). Renders BEHIND the content; Section owns the relative/overflow/z plumbing. Works with `scope` (which re-tones the content tokens so text stays legible over the media).
8
+ - Section: pad? (none | sm | md | lg | xl) — vertical rhythm (responsive py); default lg. Section is ALWAYS full width — it never sets a max width.
9
+ - Section: as? (section | header | footer | div) — semantic element; default section.
10
+ - Container: maxW? (sm | md | lg | xl | prose | full) — centred max width + gutters; default lg. The MEASURE.
11
+ - Container: grid?: boolean — snap children to a 12-column grid (use `col-span-*` on children); default false.
12
+ - Container: as? (div | section) — semantic element; default div.
13
+ when_to_use: THE page scaffold. A page is an ordered stack of Sections — every distinct band (hero, logos, features, pricing, testimonial, CTA, footer) gets its OWN Section so each is independently themeable. `Section` is the full-width band (scope + vertical rhythm); drop a `Container` inside it for a measure, or omit the Container for a full-bleed band. Reach for Section/Container instead of hand-rolling `<section className="py-20"><div className="max-w-7xl mx-auto px-6">`. The content inside is free — use the parts (SectionEyebrow/Title/Subtitle/Description/Actions/Media) for the common heading+copy+CTA+media shape, or drop any JSX. SectionMedia is a slot for any media (MediaSurface image, Carousel, VideoPlayer, embed, or a whole app UI). Don't use Section for app chrome — that's AppShell.
14
+ composes_with: [Container, MediaSurface, Carousel, VideoPlayer, Button, Badge, Card, Grid, Stack]
15
+ aliases: [section, band, hero section, page section, content section, marketing section, landing section, full bleed, container, max width wrapper, page band, section block]
16
+ ---
17
+
18
+ ```jsx
19
+ // A page is a stack of Sections. Each band picks a scope; a Container
20
+ // holds the measure (omit it to let the band bleed full-width).
21
+ <Section scope="inverse" pad="xl">
22
+ <Container maxW="lg">
23
+ <SectionEyebrow>New</SectionEyebrow>
24
+ <SectionTitle>Use the agent you prefer.</SectionTitle>
25
+ <SectionSubtitle>Own the components. Ship on your subscription.</SectionSubtitle>
26
+ <SectionActions>
27
+ <Button size="lg">Open Studio</Button>
28
+ <Button size="lg" variant="outline">Docs</Button>
29
+ </SectionActions>
30
+ </Container>
31
+ </Section>
32
+ ```
33
+
34
+ ```jsx
35
+ // Full-bleed media band — no Container, so the media spans edge to edge.
36
+ // The scope re-tones the band; the media frames itself.
37
+ <Section scope="card" pad="lg">
38
+ <SectionMedia>
39
+ <MediaSurface hint="Studio canvas" alt="A generated screen" className="aspect-[21/9] w-full" />
40
+ </SectionMedia>
41
+ </Section>
42
+ ```
43
+
44
+ ```jsx
45
+ // Contained content on a grid — children snap to the 12-col Container grid.
46
+ <Section pad="lg">
47
+ <Container grid>
48
+ <div className="col-span-12 md:col-span-7">{/* lead */}</div>
49
+ <div className="col-span-12 md:col-span-5">{/* aside */}</div>
50
+ </Container>
51
+ </Section>
52
+ ```
@@ -2,11 +2,14 @@
2
2
  name: Swatch
3
3
  import: "@gradeui/ui"
4
4
  subcomponents: [SwatchGroup]
5
- sizes: [xs, sm, md, lg, xl]
5
+ sizes: [2xs, xs, sm, md, lg, xl]
6
6
  props:
7
7
  - color?: string — any raw CSS colour (`#1f6feb`, `oklch(...)`, `rgb(...)`, or `var(--x)`). Takes precedence over `token`. Use for one-off or external colours.
8
8
  - token?: string — a Grade colour token NAME with no `--` and no `oklch()` wrap; resolved internally to `oklch(var(--<token>))`. THE design-system path — e.g. `token="brand-3"`, `token="primary"`, `token="chart-2"`. Re-voices live when the theme changes.
9
- - size? (xs | sm | md | lg | xl) t-shirt scale, 20px 56px; default md (32px). Prefer over h-*/w-* utilities.
9
+ - type? (solid | gradient | image) fill kind; default solid (or inferred from `image` / `gradient`). Determines what the chip renders in place.
10
+ - gradient?: string — CSS gradient for `type="gradient"`, e.g. `linear-gradient(135deg,#6366f1,#ec4899)`.
11
+ - image?: string — image URL for `type="image"`; rendered cover-fit behind the chip.
12
+ - size? (2xs | xs | sm | md | lg | xl) — t-shirt scale, 16px → 56px; default md (32px). 2xs (16px) suits dense colour lists. Prefer over h-*/w-* utilities.
10
13
  - shape? (square | rounded | circle) — default rounded (rides `--radius`); circle for dot pickers; square for a hard tile.
11
14
  - selected?: boolean — draws the shared selection ring (`--selected`). For palette / accent pickers.
12
15
  - onSelect?: () => void — makes the swatch a pickable <button> (adds aria-pressed, focus ring, hover lift). Omit for a static display chip.
@@ -2,15 +2,15 @@
2
2
  name: ToggleGroup
3
3
  import: "@gradeui/ui"
4
4
  subcomponents: [ToggleGroupItem]
5
- variants: [default, outline]
6
- sizes: [sm, md, lg]
5
+ variants: [default, outline, segmented]
6
+ sizes: [2xs, xs, sm, md, lg]
7
7
  props:
8
8
  - ToggleGroup: type: "single" | "multiple" — single picks one, multiple picks any number
9
9
  - ToggleGroup: value?: string | string[] — controlled; matches `type` (string for single, string[] for multiple)
10
10
  - ToggleGroup: defaultValue?: string | string[] — uncontrolled initial
11
11
  - ToggleGroup: onValueChange?: (value: string | string[]) => void
12
- - ToggleGroup: size? (sm | md | lg, default md) — cascades to every ToggleGroupItem via context, matches Tabs/Button heights
13
- - ToggleGroup: variant? (default | outline)
12
+ - ToggleGroup: size? (2xs | xs | sm | md | lg, default md) — cascades to every ToggleGroupItem via context, matches Tabs/Button heights; 2xs/xs are the dense tool-panel sizes (2xs also drops text to text-2xs and icons to size-3 so labelled items read at panel density)
13
+ - ToggleGroup: variant? (default | outline | segmented) — segmented sits the items in a muted track with the active item as a soft raised pill, so it reads like a TabsList; reach for it in dense property panels (e.g. a Row/Stack direction toggle)
14
14
  - ToggleGroupItem: value: string — what the group reports when this item is pressed
15
15
  - ToggleGroupItem: tooltip?: ReactNode — when set, wraps the item in a Tooltip; required for icon-only items where the visible chrome doesn't carry a label
16
16
  - ToggleGroupItem: tooltipSide? ("top" | "right" | "bottom" | "left", default "top") — side the tooltip renders on
@@ -41,3 +41,20 @@ aliases: [toggle group, segmented control, segmented buttons, button group, pill
41
41
  <ToggleGroupItem value="underline" aria-label="Underline"><Underline /></ToggleGroupItem>
42
42
  </ToggleGroup>
43
43
  ```
44
+
45
+ ```jsx
46
+ // Segmented variant + 2xs — a dense property-panel toggle. Reads like a
47
+ // tab strip (muted track, active pill) but emits a value, so it's a form
48
+ // control, not panel-switching. This is the Studio Row/Stack control.
49
+ <ToggleGroup
50
+ type="single"
51
+ variant="segmented"
52
+ size="2xs"
53
+ value={direction}
54
+ onValueChange={(v) => v && setDirection(v)}
55
+ className="w-full"
56
+ >
57
+ <ToggleGroupItem value="row" className="flex-1"><Columns3 /> Row</ToggleGroupItem>
58
+ <ToggleGroupItem value="col" className="flex-1"><Rows3 /> Stack</ToggleGroupItem>
59
+ </ToggleGroup>
60
+ ```
@@ -0,0 +1,262 @@
1
+ import * as React from 'react';
2
+ import { LexicalEditor } from 'lexical';
3
+ import { a as DemoSpeed, b as DemoTrigger } from './types-DUwnWaxR.mjs';
4
+
5
+ /**
6
+ * Composer — the generic text composition surface for the design system.
7
+ *
8
+ * The answer wherever a user is composing a message: AI chat input,
9
+ * comment thread reply, post-body editor, future copilot panels.
10
+ * Replaces the textarea-with-buttons pattern that hosts kept rolling
11
+ * by hand.
12
+ *
13
+ * Built on Lexical (Meta's React-first editor framework) so it can do:
14
+ * - rich text formatting (B / I / U / S / code, headings, blockquote,
15
+ * pullquote, lists)
16
+ * - mentions and slash commands via lexical-beautiful-mentions, with
17
+ * a typeahead popover and theme-able tokens
18
+ * - image attachments (paperclip + clipboard paste) when opted in
19
+ * - scripted demo playback for marketing surfaces (types text, opens
20
+ * mention popovers, applies formatting, all via the same step
21
+ * vocabulary as <Code>)
22
+ *
23
+ * Slot-based composition for the action row: hosts that need custom
24
+ * affordances (template picker, voice button, attach-document) pass
25
+ * `leftActions` / `rightActions`. Default Send / Stop / paperclip
26
+ * render only when the host hasn't replaced the slot.
27
+ *
28
+ * Hosts that want the canned "chat composer with paperclip + send"
29
+ * preset should reach for `<AIChatComposer>` instead, which configures
30
+ * this Composer with the right slots wired up.
31
+ *
32
+ * Scripted demos: same `speed` / `trigger` / `play` / `loop` vocabulary
33
+ * as `<Code>`, sharing the underlying `useScriptedDemo` hook from
34
+ * `lib/demo/`. The Composer adds its own verbs (`mention`, `format`,
35
+ * `select`, `submit`) on top of the universal `type` / `wait` / `clear`.
36
+ */
37
+ type ComposerFormat = "bold" | "italic" | "underline" | "strikethrough" | "code" | "h1" | "h2" | "h3" | "blockquote" | "pullquote" | "ul" | "ol";
38
+ interface ComposerMentionItem {
39
+ id: string;
40
+ /** Display value (without the trigger char). */
41
+ value: string;
42
+ /** Optional secondary label shown in the suggester. */
43
+ label?: string;
44
+ /** Avatar URL or initials for the suggester row. */
45
+ avatar?: string;
46
+ /** Arbitrary payload the host can attach to the mention. */
47
+ data?: Record<string, unknown>;
48
+ }
49
+ interface ComposerTriggerConfig {
50
+ /** The trigger character, eg. "@" or "/". */
51
+ char: string;
52
+ /**
53
+ * Items to populate the suggester. Either a static array or a
54
+ * resolver function (sync or async) that receives the typed query.
55
+ * The plugin filters automatically when items is an array.
56
+ */
57
+ items: ComposerMentionItem[] | ((query: string) => ComposerMentionItem[] | Promise<ComposerMentionItem[]>);
58
+ /**
59
+ * Whether to strip the trigger char on insert. Defaults: keep for
60
+ * "@" (mentions read as "@alice"), strip for "/" (commands read as
61
+ * "Insert image" not "/insert-image").
62
+ */
63
+ stripTrigger?: boolean;
64
+ }
65
+ interface ComposerAttachmentConfig {
66
+ /** Master enable. Set true on the prop to use defaults, or pass a config object. */
67
+ enabled?: boolean;
68
+ /** HTML accept attribute on the file input. Default "image/*". */
69
+ accept?: string;
70
+ /** Max number of attachments. Default 10. */
71
+ maxItems?: number;
72
+ /** Allow multiple selection in the file picker. Default true. */
73
+ multiple?: boolean;
74
+ }
75
+ interface ComposerAttachment {
76
+ id: string;
77
+ file: File;
78
+ /** Object URL owned by the composer. Hosts must NOT revoke it. */
79
+ previewUrl: string;
80
+ name: string;
81
+ }
82
+ interface ComposerContent {
83
+ /** Plain text representation of the editor contents (whitespace preserved). */
84
+ text: string;
85
+ /** Lexical editor state serialised to JSON (for round-trip persistence). */
86
+ json: string;
87
+ /** Resolved mention tokens in document order. */
88
+ mentions: Array<{
89
+ trigger: string;
90
+ value: string;
91
+ data?: Record<string, unknown>;
92
+ }>;
93
+ }
94
+ interface ComposerHandle {
95
+ /** Run a demo script imperatively (vs. via `steps` + `trigger="manual"`). */
96
+ play: (steps: ComposerStep[]) => void;
97
+ /** Cancel an in-flight demo. Idempotent. */
98
+ stop: () => void;
99
+ /**
100
+ * One-shot replay of the configured `steps`. Cancels any in-flight
101
+ * run, clears the editor, replays from step 0. Pass a delay (ms) to
102
+ * schedule the replay (useful for chaining demos). Requires `steps`
103
+ * to be configured.
104
+ */
105
+ restart: (delayMs?: number) => void;
106
+ /** Move focus into the editor. */
107
+ focus: () => void;
108
+ /** Wipe the editor. */
109
+ clear: () => void;
110
+ /** Insert plain text at the current selection. */
111
+ insert: (text: string) => void;
112
+ /** Snapshot the current content + mentions. */
113
+ getContent: () => ComposerContent;
114
+ /** Direct access to the underlying Lexical editor (escape hatch). */
115
+ getEditor: () => LexicalEditor | null;
116
+ }
117
+ /**
118
+ * Demo step vocabulary for Composer scripts. Shares `type` / `wait` /
119
+ * `clear` with the universal `lib/demo` verbs; adds composer-specific
120
+ * `mention`, `format`, `select`, `newline`, `submit` on top.
121
+ */
122
+ type ComposerStep = {
123
+ type: "type";
124
+ text: string;
125
+ speed?: DemoSpeed;
126
+ } | {
127
+ type: "wait";
128
+ ms: number;
129
+ } | {
130
+ type: "clear";
131
+ } | {
132
+ type: "newline";
133
+ } | {
134
+ type: "submit";
135
+ } | {
136
+ type: "mention";
137
+ /** Trigger char (must match a registered ComposerTriggerConfig.char). */
138
+ trigger: string;
139
+ /** Value to insert (without the trigger). Looks up the matching item by `value`. */
140
+ value: string;
141
+ /** Optional pre-typed query — the demo types this after the trigger char before "selecting" the value, to show the typeahead in action. */
142
+ query?: string;
143
+ } | {
144
+ type: "format";
145
+ format: ComposerFormat;
146
+ } | {
147
+ type: "select";
148
+ /** Substring to find and select. First match wins. */
149
+ text: string;
150
+ };
151
+ interface ComposerProps {
152
+ /** Placeholder copy shown when empty. */
153
+ placeholder?: string;
154
+ /** Initial plain text content. For richer initial state, use `initialJson`. */
155
+ initialText?: string;
156
+ /** Initial Lexical state JSON (from a previous `onSubmit` round-trip). */
157
+ initialJson?: string;
158
+ /**
159
+ * Available formats. Pass false to disable rich text entirely
160
+ * (plain text mode, half the bundle weight, no toolbar). Default
161
+ * enables a sensible set for most chat / comment surfaces.
162
+ */
163
+ formats?: ComposerFormat[] | false;
164
+ /**
165
+ * Render the formatting toolbar. Default false because most uses
166
+ * are short-form. Set true (or "top") to show the toolbar above the
167
+ * editor. "floating" is planned; not yet implemented.
168
+ */
169
+ toolbar?: boolean | "top";
170
+ /**
171
+ * Mention / slash command configs. Each entry registers one trigger
172
+ * char and its items. Pass `[{ char: "@", items: people }, { char: "/", items: commands }]`
173
+ * for the common chat-app setup.
174
+ */
175
+ triggers?: ComposerTriggerConfig[];
176
+ /**
177
+ * Image attachments (paperclip + clipboard paste). Pass true for
178
+ * defaults, an object to customise, or omit/false to skip the
179
+ * attachment plumbing entirely.
180
+ */
181
+ attachments?: boolean | ComposerAttachmentConfig;
182
+ /** Fires when the user submits (Enter, click Send, or scripted `submit` step). */
183
+ onSubmit?: (content: ComposerContent, attachments?: ComposerAttachment[]) => void;
184
+ /**
185
+ * Fires on every editor change with the current plain text. Use for
186
+ * length validation, controlled-value bridges (eg. AIChatComposer
187
+ * forwarding to a host's `value`/`onChange` pair), or live preview
188
+ * surfaces. Cheap, called frequently; debounce if you need the
189
+ * Lexical state JSON (use `getContent()` via ref instead).
190
+ */
191
+ onChange?: (text: string) => void;
192
+ /**
193
+ * Loading state — disables the editor + paperclip and swaps the
194
+ * default Send button for Stop. Has no effect when `rightActions`
195
+ * overrides the default Send.
196
+ */
197
+ isLoading?: boolean;
198
+ /** Stop handler — required for Stop to be active when loading. */
199
+ onStop?: () => void;
200
+ /** Hard character cap. */
201
+ maxLength?: number;
202
+ /** Auto-focus the editor on mount. Default false. */
203
+ autoFocus?: boolean;
204
+ /** Whether Enter submits. Default true (Shift-Enter still inserts a newline). */
205
+ submitOnEnter?: boolean;
206
+ /**
207
+ * Custom content for the left action slot. Replaces the default
208
+ * paperclip button when `attachments` is enabled.
209
+ */
210
+ leftActions?: React.ReactNode;
211
+ /**
212
+ * Custom content for the right action slot. Replaces the default
213
+ * Send / Stop button. Use the `useComposer()` hook inside to access
214
+ * imperative methods.
215
+ */
216
+ rightActions?: React.ReactNode;
217
+ /** Hide the default Send button without replacing it. */
218
+ hideSend?: boolean;
219
+ /** Scripted demo steps. */
220
+ steps?: ComposerStep[];
221
+ /** What kicks the script off. Defaults to "mount". */
222
+ trigger?: DemoTrigger;
223
+ /** For trigger="manual" — flip true to play. */
224
+ play?: boolean;
225
+ /** Animation feel. */
226
+ speed?: DemoSpeed;
227
+ /** Loop the script forever. */
228
+ loop?: boolean;
229
+ /**
230
+ * Pause between loop iterations (ms). Defaults to 2000. Marketing
231
+ * heroes that want the demo to breathe between repeats bump this
232
+ * higher; tight inline demos drop it.
233
+ */
234
+ loopDelay?: number;
235
+ /**
236
+ * Fires once per loop cycle, AFTER the loopDelay pause and BEFORE
237
+ * the script replays. Use to reset parent state that the script
238
+ * mutated via onSubmit (e.g., wipe a messages list back to its
239
+ * seed before the demo types into it again). The editor is cleared
240
+ * automatically — you only need this hook if state outside the
241
+ * Composer needs to reset too.
242
+ */
243
+ onLoopReset?: () => void;
244
+ /**
245
+ * Bare mode — strip the card chrome (border / bg / rounding). Use
246
+ * when embedding inside an existing card or column layout.
247
+ */
248
+ bare?: boolean;
249
+ /**
250
+ * Read-only mode — disables editing AND focusability. Programmatic
251
+ * updates (including scripted demo playback) still work. Use for
252
+ * marketing surfaces that render a Composer purely for show, so the
253
+ * scripted typing doesn't steal focus from other inputs on the page.
254
+ * Hides the default Send / paperclip action row.
255
+ */
256
+ readOnly?: boolean;
257
+ className?: string;
258
+ }
259
+ declare const Composer: React.ForwardRefExoticComponent<ComposerProps & React.RefAttributes<ComposerHandle>>;
260
+ declare const ComposerReply: React.ForwardRefExoticComponent<ComposerProps & React.RefAttributes<ComposerHandle>>;
261
+
262
+ export { Composer, type ComposerAttachment, type ComposerAttachmentConfig, type ComposerContent, type ComposerFormat, type ComposerHandle, type ComposerMentionItem, type ComposerProps, ComposerReply, type ComposerStep, type ComposerTriggerConfig };