panelui-web 0.3.0 → 0.5.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.
@@ -47,7 +47,10 @@ import { tv } from 'tailwind-variants';
47
47
  import { LAND, LAND_COLUMNS, LAND_ROWS } from './land.js';
48
48
  const planetVariants = tv({
49
49
  slots: {
50
- root: 'flex w-full flex-col items-center gap-4 text-foreground',
50
+ // `isolate` keeps the markers' depth ordering inside the planet. Each marker
51
+ // sets its own z-index, and without a stacking context of its own those
52
+ // numbers compete with the page's sidebars and menus, and win.
53
+ root: 'isolate flex w-full flex-col items-center gap-4 text-foreground',
51
54
  // `touch-pan-y` leaves a vertical swipe to the page. A globe that traps
52
55
  // the scroll it sits in is worse than one that only turns sideways.
53
56
  globe: 'relative aspect-square w-full max-w-md cursor-grab touch-pan-y select-none rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background active:cursor-grabbing',
@@ -0,0 +1,414 @@
1
+ /**
2
+ * Prompt — where a message to an assistant is written, with what goes with
3
+ * it: files, a model, tools, and a button that sends it.
4
+ *
5
+ * ```tsx
6
+ * <Prompt onSubmit={({ text, files }) => send(text, files)} status={status} onStop={stop}>
7
+ * <Prompt.Field>
8
+ * <Prompt.Attachments />
9
+ * <Prompt.Textarea placeholder="Ask anything" />
10
+ * <Prompt.Footer>
11
+ * <Prompt.Tools>
12
+ * <Prompt.Menu>
13
+ * <Prompt.AttachItem />
14
+ * </Prompt.Menu>
15
+ * <Prompt.Model items={models} defaultValue="swift" />
16
+ * </Prompt.Tools>
17
+ * <Prompt.Submit />
18
+ * </Prompt.Footer>
19
+ * </Prompt.Field>
20
+ * <Prompt.Status>Reading the files</Prompt.Status>
21
+ * </Prompt>
22
+ * ```
23
+ *
24
+ * ## A field in a tray
25
+ *
26
+ * The field is a raised card. What is said about it, rather than in it, sits
27
+ * in bands that the card overlaps: `Prompt.Banner` above for something to do
28
+ * first, `Prompt.Status` below for what the answer is doing, and
29
+ * `Prompt.Context` below, inset, for where the message is going. A band opens
30
+ * and closes with its `open` prop, folding under the card, so a band can come
31
+ * and go without the page jumping by more than its own height.
32
+ *
33
+ * ## Sending
34
+ *
35
+ * Enter sends and Shift+Enter starts a new line; Enter while a character is
36
+ * still being composed is left to the input method. `onSubmit` is handed the
37
+ * text, trimmed, and the files, and the field is cleared. Nothing is sent
38
+ * while it is empty or while `status` is `submitted` or `streaming`.
39
+ *
40
+ * ## One button, four jobs
41
+ *
42
+ * `Prompt.Submit` sends. While the answer is on its way it stops it instead,
43
+ * calling `onStop`, with a ring turning round it. Given `onVoice`, it is a
44
+ * voice button while the field is empty, and its bars move while `listening`.
45
+ * The icons cross-fade in place, so the button never moves under the pointer.
46
+ *
47
+ * ## Files
48
+ *
49
+ * Files arrive from `Prompt.AttachItem` in the menu or `Prompt.Attach`, from
50
+ * ⌘U or Ctrl+U in the field, from a paste, or dropped on the field.
51
+ * `accept` and `maxFiles` decide which are taken, and `onReject` is told about
52
+ * the rest. Images are shown as thumbnails, anything else as a chip with its
53
+ * name and size.
54
+ */
55
+ import { type ComponentProps, type ReactNode } from 'react';
56
+ import { type ButtonProps } from '../button/index.js';
57
+ import { type MenuContentProps, type MenuItemProps } from '../menu/index.js';
58
+ import { type TextareaProps } from '../textarea/index.js';
59
+ export type PromptState = 'idle' | 'submitted' | 'streaming' | 'error';
60
+ export interface PromptMessage {
61
+ /** What was written, trimmed. */
62
+ text: string;
63
+ /** The files that were attached, in the order they were added. */
64
+ files: File[];
65
+ }
66
+ export interface PromptAttachment {
67
+ id: string;
68
+ file: File;
69
+ /** An object URL for an image, to show it by. */
70
+ preview?: string;
71
+ }
72
+ export type PromptRejectReason = 'accept' | 'max-files';
73
+ export interface PromptProps extends Omit<ComponentProps<'form'>, 'onSubmit' | 'defaultValue'> {
74
+ /** What is written in the field, when the caller holds it. */
75
+ value?: string;
76
+ /** What the field starts with, when the caller does not hold it. */
77
+ defaultValue?: string;
78
+ /** Called with what is written, each time it changes, and with `''` when it is sent. */
79
+ onValueChange?: (value: string) => void;
80
+ /** Called with the text and the files when the message is sent. */
81
+ onSubmit?: (message: PromptMessage) => void;
82
+ /**
83
+ * Where the answer is. `submitted` and `streaming` stop the field being
84
+ * sent and turn the submit button into a stop button; `error` marks the
85
+ * field. Default `idle`.
86
+ */
87
+ status?: PromptState;
88
+ /** Called when the stop button is pressed. Without it, the button cannot be pressed while busy. */
89
+ onStop?: () => void;
90
+ /** Turns the whole prompt off. */
91
+ disabled?: boolean;
92
+ /** Files attached to begin with, such as those of a message being edited and sent again. */
93
+ defaultFiles?: File[];
94
+ /** Which files can be attached, as for `<input type="file">`: `image/*,.pdf`. Default any. */
95
+ accept?: string;
96
+ /** How many files can be attached at once. Default no limit. */
97
+ maxFiles?: number;
98
+ /** Called with the files that were not taken, and why. */
99
+ onReject?: (files: File[], reason: PromptRejectReason) => void;
100
+ }
101
+ declare function PromptRoot({ value: controlledValue, defaultValue, onValueChange, onSubmit, status, onStop, disabled, defaultFiles, accept, maxFiles, onReject, className, children, ...props }: PromptProps): import("react").JSX.Element;
102
+ declare namespace PromptRoot {
103
+ var displayName: string;
104
+ }
105
+ export interface PromptBannerProps extends ComponentProps<'div'> {
106
+ /** Whether it is shown. It folds under the field when it is not. Default `true`. */
107
+ open?: boolean;
108
+ }
109
+ /** A band above the field, for something to do before or while writing: connect an account, pick a project. */
110
+ declare function PromptBanner({ open, ...props }: PromptBannerProps): import("react").JSX.Element;
111
+ declare namespace PromptBanner {
112
+ var displayName: string;
113
+ }
114
+ export interface PromptBannerActionProps extends ButtonProps {
115
+ }
116
+ /** The banner's button, at its end. */
117
+ declare function PromptBannerAction({ className, size, shape, ...props }: PromptBannerActionProps): import("react").JSX.Element;
118
+ declare namespace PromptBannerAction {
119
+ var displayName: string;
120
+ }
121
+ export interface PromptStatusProps extends ComponentProps<'div'> {
122
+ /** Whether it is shown. Default: while `status` is `submitted` or `streaming`. */
123
+ open?: boolean;
124
+ /** Something before the words: the model's mark, a tool's icon. */
125
+ icon?: ReactNode;
126
+ /** Whether a spinner turns at its end. Default `true`. */
127
+ spinner?: boolean;
128
+ }
129
+ /** A band under the field saying what the answer is doing, with the words lit by a moving sweep. */
130
+ declare function PromptStatus({ open, icon, spinner, children, ...props }: PromptStatusProps): import("react").JSX.Element;
131
+ declare namespace PromptStatus {
132
+ var displayName: string;
133
+ }
134
+ export interface PromptContextProps extends ComponentProps<'div'> {
135
+ }
136
+ /** A strip under the field, inset from its edges, for where the message is going: a branch, a folder, a ticket. */
137
+ declare function PromptContext({ className, ...props }: PromptContextProps): import("react").JSX.Element;
138
+ declare namespace PromptContext {
139
+ var displayName: string;
140
+ }
141
+ export interface PromptContextItemProps extends ComponentProps<'span'> {
142
+ /** Called when it is pressed. Passing this makes the item a button. */
143
+ onSelect?: () => void;
144
+ }
145
+ /** One fact in the context strip, with an icon before it. */
146
+ declare function PromptContextItem({ onSelect, className, children, ...props }: PromptContextItemProps): import("react").JSX.Element;
147
+ declare namespace PromptContextItem {
148
+ var displayName: string;
149
+ }
150
+ export interface PromptHintProps extends ComponentProps<'p'> {
151
+ }
152
+ /** A quiet line under the prompt. Keys written in `<kbd>` are drawn as keys. */
153
+ declare function PromptHint({ className, children, ...props }: PromptHintProps): import("react").JSX.Element;
154
+ declare namespace PromptHint {
155
+ var displayName: string;
156
+ }
157
+ export interface PromptFieldProps extends ComponentProps<'div'> {
158
+ /**
159
+ * `stacked` puts the text above a footer. `inline` puts every part in one
160
+ * row, the text between them, for a one-line composer; the row wraps as
161
+ * the text grows. Default `stacked`.
162
+ */
163
+ layout?: 'stacked' | 'inline';
164
+ }
165
+ /** The card that holds the text, the files and the footer. Files can be dropped on it. */
166
+ declare function PromptField({ layout, className, onDragOver, onDragLeave, onDrop, ...props }: PromptFieldProps): import("react").JSX.Element;
167
+ declare namespace PromptField {
168
+ var displayName: string;
169
+ }
170
+ export interface PromptTextareaProps extends Omit<TextareaProps, 'value' | 'defaultValue' | 'onChange' | 'variant'> {
171
+ }
172
+ /** Where the message is written. Enter sends, Shift+Enter starts a new line, and pasted files are attached. */
173
+ declare function PromptTextarea({ className, minRows, maxRows, onKeyDown, onPaste, 'aria-label': ariaLabel, ...props }: PromptTextareaProps): import("react").JSX.Element;
174
+ declare namespace PromptTextarea {
175
+ var displayName: string;
176
+ }
177
+ export interface PromptAttachmentsProps extends ComponentProps<'ul'> {
178
+ }
179
+ /** The files attached so far: images as thumbnails, anything else as a chip. Takes no room while there are none. */
180
+ declare function PromptAttachments({ className, 'aria-label': ariaLabel, ...props }: PromptAttachmentsProps): import("react").JSX.Element | null;
181
+ declare namespace PromptAttachments {
182
+ var displayName: string;
183
+ }
184
+ export interface PromptFooterProps extends ComponentProps<'div'> {
185
+ }
186
+ /** The row under the text: tools at the start, the submit button at the end. */
187
+ declare function PromptFooter({ className, ...props }: PromptFooterProps): import("react").JSX.Element;
188
+ declare namespace PromptFooter {
189
+ var displayName: string;
190
+ }
191
+ export interface PromptToolsProps extends ComponentProps<'div'> {
192
+ }
193
+ /** The tools at the start of the footer. Wraps onto a second line where it has to. */
194
+ declare function PromptTools({ className, ...props }: PromptToolsProps): import("react").JSX.Element;
195
+ declare namespace PromptTools {
196
+ var displayName: string;
197
+ }
198
+ export interface PromptToolProps extends Omit<ButtonProps, 'aria-pressed'> {
199
+ /** What it does, for a screen reader and for the tooltip. Shown beside the icon with `showLabel`. */
200
+ label: string;
201
+ /** Whether it is switched on, for a tool that stays on. Leave it out for one that acts once. */
202
+ pressed?: boolean;
203
+ /** Called with the new state when a tool with `pressed` is pressed. */
204
+ onPressedChange?: (pressed: boolean) => void;
205
+ /** Writes the label beside the icon. */
206
+ showLabel?: boolean;
207
+ }
208
+ /** A round button in the footer, holding an icon. With `pressed`, it switches on and off. */
209
+ declare function PromptTool({ label, pressed, onPressedChange, showLabel, onClick, className, children, variant, ...props }: PromptToolProps): import("react").JSX.Element;
210
+ declare namespace PromptTool {
211
+ var displayName: string;
212
+ }
213
+ export interface PromptAttachProps extends Omit<PromptToolProps, 'label' | 'pressed' | 'onPressedChange'> {
214
+ /** Default `Attach files`. */
215
+ label?: string;
216
+ }
217
+ /** A tool that opens the file picker. Files can also be pasted, or dropped on the field. */
218
+ declare function PromptAttach({ label, onClick, children, ...props }: PromptAttachProps): import("react").JSX.Element;
219
+ declare namespace PromptAttach {
220
+ var displayName: string;
221
+ }
222
+ export interface PromptModelItem {
223
+ value: string;
224
+ label: string;
225
+ /** Shown before the label, in the list and on the button once chosen. */
226
+ icon?: ReactNode;
227
+ /** A second line in the list. */
228
+ description?: ReactNode;
229
+ }
230
+ export interface PromptModelProps {
231
+ /** The models to choose from. */
232
+ items: readonly PromptModelItem[];
233
+ /** The chosen model, when the caller holds it. */
234
+ value?: string;
235
+ /** The model chosen to begin with, when the caller does not hold it. */
236
+ defaultValue?: string;
237
+ /** Called with the model's `value` when another is chosen. */
238
+ onValueChange?: (value: string) => void;
239
+ /** `pill` has an edge, like the tools beside it. `ghost` has none until hovered. Default `pill`. */
240
+ variant?: 'pill' | 'ghost';
241
+ /** What a screen reader calls the button. Default `Model`. */
242
+ label?: string;
243
+ className?: string;
244
+ }
245
+ /** Chooses the model the message goes to. The button shows the chosen one's icon and name. */
246
+ declare function PromptModel({ items, value, defaultValue, onValueChange, variant, label, className, }: PromptModelProps): import("react").JSX.Element;
247
+ declare namespace PromptModel {
248
+ var displayName: string;
249
+ }
250
+ export interface PromptMenuProps extends Omit<MenuContentProps, 'children'> {
251
+ /** What a screen reader and the tooltip call the button. Default `Add`. */
252
+ label?: string;
253
+ /** Replaces the plus on the button. */
254
+ icon?: ReactNode;
255
+ /** The menu: `Prompt.AttachItem`, and any `Menu` items, check items and submenus. */
256
+ children?: ReactNode;
257
+ /** `field` opens the menu as wide as the field, lined up with it, for items with descriptions. Default `auto`. */
258
+ width?: 'auto' | 'field';
259
+ }
260
+ /**
261
+ * One round button that opens a menu of everything that can be added to the
262
+ * message: files, tools, modes. It keeps the footer to one button however
263
+ * many there are.
264
+ */
265
+ declare function PromptMenu({ label, icon, children, side, align, width, className, ...props }: PromptMenuProps): import("react").JSX.Element;
266
+ declare namespace PromptMenu {
267
+ var displayName: string;
268
+ }
269
+ export interface PromptAttachItemProps extends Omit<MenuItemProps, 'children'> {
270
+ /** Default `Add files`. */
271
+ children?: ReactNode;
272
+ /** The keys shown at its end. Default `⌘U`; pass `null` for none. ⌘U and Ctrl+U in the field open the picker. */
273
+ shortcut?: ReactNode;
274
+ }
275
+ /** The menu item that opens the file picker. */
276
+ declare function PromptAttachItem({ children, shortcut, onClick, ...props }: PromptAttachItemProps): import("react").JSX.Element;
277
+ declare namespace PromptAttachItem {
278
+ var displayName: string;
279
+ }
280
+ export interface PromptSubmitProps extends Omit<ButtonProps, 'type' | 'children'> {
281
+ /** Makes the button a voice button while the field is empty. Called when it is pressed. */
282
+ onVoice?: () => void;
283
+ /** Whether voice is being heard. The bars move while it is. */
284
+ listening?: boolean;
285
+ /** What a screen reader calls the button while it sends. Default `Send`. */
286
+ sendLabel?: string;
287
+ /** While it stops the answer. Default `Stop`. */
288
+ stopLabel?: string;
289
+ /** While it starts voice. Default `Speak`. */
290
+ voiceLabel?: string;
291
+ }
292
+ /** Sends the message. While busy it stops the answer, and with `onVoice` it starts voice while the field is empty. */
293
+ declare function PromptSubmit({ onVoice, listening, sendLabel, stopLabel, voiceLabel, onClick, className, disabled, ...props }: PromptSubmitProps): import("react").JSX.Element;
294
+ declare namespace PromptSubmit {
295
+ var displayName: string;
296
+ }
297
+ export type PromptTaskStatus = 'pending' | 'active' | 'done' | 'error';
298
+ export interface PromptTasksProps extends Omit<ComponentProps<'div'>, 'title'> {
299
+ /** Whether the band is shown. It folds under the field when it is not. Default `true`. */
300
+ open?: boolean;
301
+ /** What the agent is doing, on the band's one line: "Preparing the release". */
302
+ title: ReactNode;
303
+ /** Whether the list is open under the title, when the caller holds it. */
304
+ expanded?: boolean;
305
+ /** Whether the list starts open, when the caller does not hold it. Default `false`. */
306
+ defaultExpanded?: boolean;
307
+ /** Called when the title is pressed to open or close the list. */
308
+ onExpandedChange?: (expanded: boolean) => void;
309
+ /** How tall the list grows before it scrolls, as a CSS length. Default `10rem`. */
310
+ maxHeight?: string;
311
+ }
312
+ /**
313
+ * A band above the field with an agent's tasks: one line with a ring that
314
+ * fills as they are done, opening into the list. The list scrolls past
315
+ * `maxHeight`, and keeps the task in progress in view.
316
+ */
317
+ declare function PromptTasks({ open, title, expanded, defaultExpanded, onExpandedChange, maxHeight, className, children, ...props }: PromptTasksProps): import("react").JSX.Element;
318
+ declare namespace PromptTasks {
319
+ var displayName: string;
320
+ }
321
+ export interface PromptTaskProps extends ComponentProps<'li'> {
322
+ /** `pending`, `active` (its words are lit while it runs), `done` (a check) or `error`. Default `pending`. */
323
+ status?: PromptTaskStatus;
324
+ }
325
+ /** One task in `Prompt.Tasks`. */
326
+ declare function PromptTask({ status, className, children, ...props }: PromptTaskProps): import("react").JSX.Element;
327
+ declare namespace PromptTask {
328
+ var displayName: string;
329
+ }
330
+ export interface PromptCommandItem {
331
+ /** What is typed after the trigger to reach it: `summarise` for `/summarise`. */
332
+ value: string;
333
+ /** Its name in the list. Default the trigger and the value. */
334
+ label?: ReactNode;
335
+ /** What it does, after the name. */
336
+ description?: ReactNode;
337
+ icon?: ReactNode;
338
+ }
339
+ export interface PromptCommandsProps {
340
+ /** The commands. */
341
+ items: readonly PromptCommandItem[];
342
+ /** The character that opens the list when the field starts with it. Default `/`. */
343
+ trigger?: string;
344
+ /**
345
+ * Called with the command chosen. With it, the field is cleared and the
346
+ * command is yours to show; without it, the command is written into the field.
347
+ */
348
+ onSelect?: (item: PromptCommandItem) => void;
349
+ /** The heading over the list. Default `Commands`. */
350
+ label?: ReactNode;
351
+ /** How tall the list grows before it scrolls, as a CSS length. Default `12rem`. */
352
+ maxHeight?: string;
353
+ }
354
+ /**
355
+ * A band above the field listing commands while the text is a trigger and a
356
+ * word, `/sum`. The arrow keys move through it, Enter or Tab chooses, Escape
357
+ * puts it away.
358
+ */
359
+ declare function PromptCommands({ items, trigger, onSelect, label, maxHeight }: PromptCommandsProps): import("react").JSX.Element;
360
+ declare namespace PromptCommands {
361
+ var displayName: string;
362
+ }
363
+ export interface PromptRecorderProps extends ComponentProps<'div'> {
364
+ /** What has been heard so far, written in as it arrives. */
365
+ transcript?: ReactNode;
366
+ /** Shown, lit by a moving sweep, until there is a transcript. Default `Listening…`. */
367
+ placeholder?: ReactNode;
368
+ /** Called when the cross is pressed, to throw the recording away. */
369
+ onCancel?: () => void;
370
+ /** Called when the check is pressed, to keep it. */
371
+ onDone?: () => void;
372
+ /**
373
+ * How loud the input is now, from 0 to 1, to draw from a real microphone.
374
+ * Without it the wave moves on its own, as a sign that it is listening.
375
+ */
376
+ level?: number;
377
+ /** What a screen reader calls the cross. Default `Cancel recording`. */
378
+ cancelLabel?: string;
379
+ /** And the check. Default `Finish recording`. */
380
+ doneLabel?: string;
381
+ }
382
+ /**
383
+ * Takes the place of the text and the footer while voice is recorded: what
384
+ * has been heard so far, the sound as a wave scrolling to the left, how long
385
+ * it has been, and buttons to throw it away or keep it.
386
+ */
387
+ declare function PromptRecorder({ transcript, placeholder, onCancel, onDone, level, cancelLabel, doneLabel, className, ...props }: PromptRecorderProps): import("react").JSX.Element;
388
+ declare namespace PromptRecorder {
389
+ var displayName: string;
390
+ }
391
+ export declare const Prompt: typeof PromptRoot & {
392
+ Banner: typeof PromptBanner;
393
+ BannerAction: typeof PromptBannerAction;
394
+ Field: typeof PromptField;
395
+ Attachments: typeof PromptAttachments;
396
+ Textarea: typeof PromptTextarea;
397
+ Footer: typeof PromptFooter;
398
+ Tools: typeof PromptTools;
399
+ Tool: typeof PromptTool;
400
+ Attach: typeof PromptAttach;
401
+ Menu: typeof PromptMenu;
402
+ AttachItem: typeof PromptAttachItem;
403
+ Model: typeof PromptModel;
404
+ Submit: typeof PromptSubmit;
405
+ Status: typeof PromptStatus;
406
+ Tasks: typeof PromptTasks;
407
+ Task: typeof PromptTask;
408
+ Commands: typeof PromptCommands;
409
+ Recorder: typeof PromptRecorder;
410
+ Context: typeof PromptContext;
411
+ ContextItem: typeof PromptContextItem;
412
+ Hint: typeof PromptHint;
413
+ };
414
+ export { PromptBanner, PromptBannerAction, PromptField, PromptAttachments, PromptTextarea, PromptFooter, PromptTools, PromptTool, PromptAttach, PromptMenu, PromptAttachItem, PromptModel, PromptSubmit, PromptStatus, PromptTasks, PromptTask, PromptCommands, PromptRecorder, PromptContext, PromptContextItem, PromptHint, };