panelui-web 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/alert/index.d.ts +138 -0
- package/dist/components/alert/index.js +279 -0
- package/dist/components/alert-dialog/index.d.ts +128 -0
- package/dist/components/alert-dialog/index.js +327 -0
- package/dist/components/button-group/index.d.ts +156 -0
- package/dist/components/button-group/index.js +78 -0
- package/dist/components/canvas/index.d.ts +114 -0
- package/dist/components/canvas/index.js +210 -0
- package/dist/components/dialog/index.d.ts +162 -0
- package/dist/components/dialog/index.js +299 -0
- package/dist/components/prompt/index.d.ts +34 -8
- package/dist/components/prompt/index.js +43 -13
- package/dist/components/sheet/index.d.ts +47 -1
- package/dist/components/sheet/index.js +251 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +6 -1
- package/package.json +1 -1
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Dialog — a task taken out of the page and done in a box over it: renaming
|
|
5
|
+
* something, inviting people, filling in a short form.
|
|
6
|
+
*
|
|
7
|
+
* ```tsx
|
|
8
|
+
* <Dialog>
|
|
9
|
+
* <Dialog.Trigger render={<Button />}>Rename</Dialog.Trigger>
|
|
10
|
+
* <Dialog.Content>
|
|
11
|
+
* <Dialog.Header title="Rename board" description="Everyone on the board sees the new name." />
|
|
12
|
+
* <Dialog.Body>
|
|
13
|
+
* <Input defaultValue="Spring launch" aria-label="Name" />
|
|
14
|
+
* </Dialog.Body>
|
|
15
|
+
* <Dialog.Footer>
|
|
16
|
+
* <Dialog.Close render={<Button variant="secondary" />}>Cancel</Dialog.Close>
|
|
17
|
+
* <Button>Save</Button>
|
|
18
|
+
* </Dialog.Footer>
|
|
19
|
+
* </Dialog.Content>
|
|
20
|
+
* </Dialog>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* ## A panel in a band
|
|
24
|
+
*
|
|
25
|
+
* The task sits in a panel, and `Dialog.Footer` moves out of it into the
|
|
26
|
+
* darker band underneath, where the buttons are equal pills: the same shape
|
|
27
|
+
* as Feedback and Alert Dialog, so the three read as one family.
|
|
28
|
+
* `variant="plain"` is a single surface with the footer ruled off at its
|
|
29
|
+
* foot, for a dense form where the band would be one more edge.
|
|
30
|
+
*
|
|
31
|
+
* ## The middle scrolls
|
|
32
|
+
*
|
|
33
|
+
* The dialog is never taller than the screen. The header and footer stay
|
|
34
|
+
* put and `Dialog.Body` scrolls between them, with a thin drawn thumb that
|
|
35
|
+
* shows while it moves.
|
|
36
|
+
*
|
|
37
|
+
* ## Focus goes to the task
|
|
38
|
+
*
|
|
39
|
+
* The close button is drawn in the corner but comes last in the order, so
|
|
40
|
+
* opening the dialog puts focus on its first field rather than on the way
|
|
41
|
+
* out. A press outside, Escape or the close button dismiss it.
|
|
42
|
+
*
|
|
43
|
+
* ## Lists to choose from
|
|
44
|
+
*
|
|
45
|
+
* `Dialog.List` is a list of one-line choices for the body, such as a time
|
|
46
|
+
* zone or a project to move something to: grouped under headings that stay
|
|
47
|
+
* pinned while it scrolls, moved through with the arrow keys, with a check
|
|
48
|
+
* on the chosen one. It opens scrolled to the chosen one.
|
|
49
|
+
*
|
|
50
|
+
* ## On a phone it sits low
|
|
51
|
+
*
|
|
52
|
+
* Under 640px wide it rests at the bottom of the screen, a few pixels clear
|
|
53
|
+
* of the edges, where a thumb reaches its buttons.
|
|
54
|
+
*/
|
|
55
|
+
import { Dialog as Primitive } from '@base-ui/react/dialog';
|
|
56
|
+
import { ScrollArea } from '@base-ui/react/scroll-area';
|
|
57
|
+
import { Children, createContext, isValidElement, useContext, useEffect, useId, useMemo, useRef, } from 'react';
|
|
58
|
+
import { tv } from 'tailwind-variants';
|
|
59
|
+
const dialogVariants = tv({
|
|
60
|
+
slots: {
|
|
61
|
+
backdrop: 'fixed inset-0 z-50 bg-black/40 transition-opacity duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none',
|
|
62
|
+
viewport: 'fixed inset-0 z-50 grid place-items-center overflow-hidden p-4 max-sm:items-end max-sm:p-2',
|
|
63
|
+
shell: [
|
|
64
|
+
'relative flex max-h-[calc(100dvh-2rem)] w-full min-h-0 flex-col text-popover-foreground outline-none max-sm:max-h-[calc(100dvh-1rem)]',
|
|
65
|
+
// A dialog opened from this one steps it back, smaller and veiled, so
|
|
66
|
+
// the depth can be counted.
|
|
67
|
+
'origin-top scale-[calc(1-0.05*var(--nested-dialogs,0))] translate-y-[calc(-0.75rem*var(--nested-dialogs,0))]',
|
|
68
|
+
"after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:bg-black/0 after:transition-colors after:duration-200 after:content-[''] data-nested-dialog-open:after:bg-black/10 dark:data-nested-dialog-open:after:bg-black/30",
|
|
69
|
+
],
|
|
70
|
+
popup: 'transition-[opacity,scale,translate] duration-200 ease-[cubic-bezier(0.2,0,0,1)] data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:translate-y-2 data-starting-style:scale-95 data-starting-style:opacity-0 motion-reduce:transition-none',
|
|
71
|
+
panel: 'relative flex min-h-0 flex-1 flex-col',
|
|
72
|
+
header: 'flex shrink-0 flex-col gap-1',
|
|
73
|
+
title: 'text-[19px] font-semibold leading-6 tracking-[-0.01em]',
|
|
74
|
+
description: 'text-[15px] leading-[22px] text-muted-foreground',
|
|
75
|
+
body: [
|
|
76
|
+
'relative flex min-h-0 flex-1 flex-col',
|
|
77
|
+
// A hairline at an edge the content has been scrolled past, so what is
|
|
78
|
+
// cut off reads as going under the header, not as stopping there.
|
|
79
|
+
"before:pointer-events-none before:absolute before:inset-x-0 before:top-0 before:z-20 before:h-px before:bg-border before:opacity-0 before:transition-opacity before:content-[''] data-overflow-y-start:before:opacity-100",
|
|
80
|
+
],
|
|
81
|
+
bodyViewport: 'min-h-0 flex-1 overscroll-contain rounded-[inherit] outline-none',
|
|
82
|
+
bodyContent: 'flex flex-col gap-4',
|
|
83
|
+
scrollbar: 'my-2 me-1 flex w-1.5 opacity-0 transition-opacity delay-300 data-hovering:opacity-100 data-hovering:delay-0 data-scrolling:opacity-100 data-scrolling:delay-0',
|
|
84
|
+
thumb: 'relative flex-1 rounded-full bg-foreground/20',
|
|
85
|
+
footer: 'flex shrink-0 items-center',
|
|
86
|
+
close: [
|
|
87
|
+
'absolute end-4 top-4 z-10 inline-flex size-8 cursor-pointer items-center justify-center rounded-full',
|
|
88
|
+
'bg-muted text-muted-foreground outline-none transition-[background-color,color,scale] duration-150',
|
|
89
|
+
'after:absolute after:-inset-1.5 hover:bg-foreground/10 hover:text-foreground active:scale-95',
|
|
90
|
+
'focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none [&_svg]:size-4',
|
|
91
|
+
],
|
|
92
|
+
// Pulled out by the rows' own padding, so a row's text lines up with the
|
|
93
|
+
// header's, and no further: the scrollbar runs in the 12px left at each
|
|
94
|
+
// side, and a heading or a row reaching into it shows through the thumb.
|
|
95
|
+
list: '-mx-2 flex flex-col outline-none',
|
|
96
|
+
listGroup: 'flex flex-col [&+&]:mt-2',
|
|
97
|
+
listGroupLabel: 'sticky top-0 z-10 bg-popover px-2 pb-1.5 pt-2 text-xs font-medium text-muted-foreground',
|
|
98
|
+
listItem: [
|
|
99
|
+
'flex h-10 shrink-0 cursor-pointer select-none items-center gap-3 rounded-xl px-2 text-sm outline-none',
|
|
100
|
+
// Room for the pinned heading above it when the arrow keys scroll it into view.
|
|
101
|
+
'scroll-mb-2 scroll-mt-10 transition-colors duration-100 hover:bg-foreground/[0.05]',
|
|
102
|
+
'focus-visible:bg-foreground/[0.06] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/60',
|
|
103
|
+
'data-selected:bg-[color-mix(in_oklab,var(--primary)_9%,transparent)] data-selected:font-medium',
|
|
104
|
+
'aria-disabled:cursor-default aria-disabled:opacity-50 aria-disabled:hover:bg-transparent motion-reduce:transition-none',
|
|
105
|
+
],
|
|
106
|
+
listItemLabel: 'min-w-0 flex-1 truncate',
|
|
107
|
+
listItemMeta: 'shrink-0 text-xs font-normal tabular-nums text-muted-foreground',
|
|
108
|
+
listItemCheck: 'flex size-4 shrink-0 items-center justify-center text-primary [&_svg]:size-4',
|
|
109
|
+
},
|
|
110
|
+
variants: {
|
|
111
|
+
variant: {
|
|
112
|
+
/*
|
|
113
|
+
* The band is the popover surface with black mixed in, so it is the
|
|
114
|
+
* darker of the two in either theme. 38 and 30 are one corner: the
|
|
115
|
+
* panel's radius is the band's less the 8px between them.
|
|
116
|
+
*/
|
|
117
|
+
inset: {
|
|
118
|
+
shell: 'rounded-[38px] bg-[color-mix(in_oklab,var(--popover),black_5%)] p-2 shadow-xl ring-1 ring-foreground/[0.06] dark:bg-[color-mix(in_oklab,var(--popover),black_14%)]',
|
|
119
|
+
panel: 'overflow-hidden rounded-[30px] bg-popover shadow-xs',
|
|
120
|
+
header: 'px-5 pb-3 pt-5',
|
|
121
|
+
body: 'first:pt-5',
|
|
122
|
+
bodyContent: 'px-5 pb-5',
|
|
123
|
+
// Held in from the band's edge further than the panel is, with the
|
|
124
|
+
// buttons as equal pills: a row of decisions, not a third edge.
|
|
125
|
+
footer: 'gap-3 px-[10px] pb-0.5 pt-2.5 *:h-11 *:min-w-0 *:flex-1 *:rounded-full',
|
|
126
|
+
},
|
|
127
|
+
plain: {
|
|
128
|
+
shell: 'overflow-hidden rounded-[28px] border bg-popover shadow-xl',
|
|
129
|
+
header: 'px-6 pb-4 pt-6',
|
|
130
|
+
body: 'first:pt-6',
|
|
131
|
+
bodyContent: 'px-6 pb-6',
|
|
132
|
+
footer: 'justify-end gap-2 border-t bg-muted/50 px-6 py-3.5',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
size: {
|
|
136
|
+
sm: { shell: 'max-w-sm' },
|
|
137
|
+
md: { shell: 'max-w-md' },
|
|
138
|
+
lg: { shell: 'max-w-xl' },
|
|
139
|
+
},
|
|
140
|
+
blur: {
|
|
141
|
+
true: { backdrop: 'bg-black/20 backdrop-blur-md' },
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
defaultVariants: { variant: 'inset', size: 'md' },
|
|
145
|
+
});
|
|
146
|
+
const DialogContext = createContext({ inline: false, showClose: true, variant: 'inset' });
|
|
147
|
+
function DialogRoot({ children, open, defaultOpen, onOpenChange, dismissible = true, }) {
|
|
148
|
+
return (_jsx(Primitive.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange ? (next) => onOpenChange(next) : undefined, disablePointerDismissal: !dismissible, children: children }));
|
|
149
|
+
}
|
|
150
|
+
DialogRoot.displayName = 'Dialog';
|
|
151
|
+
/** What opens the dialog. A plain button unless `render` supplies one. */
|
|
152
|
+
function DialogTrigger({ render, children, ...props }) {
|
|
153
|
+
return (_jsx(Primitive.Trigger, { render: render, ...props, children: children }));
|
|
154
|
+
}
|
|
155
|
+
DialogTrigger.displayName = 'Dialog.Trigger';
|
|
156
|
+
const isFooter = (child) => isValidElement(child) && child.type === DialogFooter;
|
|
157
|
+
/** The dialog itself, with the dimmed page behind it. */
|
|
158
|
+
function DialogContent({ variant = 'inset', size = 'md', showClose = true, blur = false, inline = false, className, children, ...props }) {
|
|
159
|
+
const slots = dialogVariants({ variant, size, blur });
|
|
160
|
+
const context = useMemo(() => ({ inline, showClose, variant }), [inline, showClose, variant]);
|
|
161
|
+
// In the inset look the footer leaves the panel for the band under it.
|
|
162
|
+
const all = Children.toArray(children);
|
|
163
|
+
const footer = variant === 'inset' ? all.filter(isFooter) : [];
|
|
164
|
+
const rest = variant === 'inset' ? all.filter((child) => !isFooter(child)) : all;
|
|
165
|
+
const inside = (_jsxs(DialogContext.Provider, { value: context, children: [_jsxs("div", { "data-slot": "dialog-panel", className: slots.panel(), children: [rest, showClose ? _jsx(DialogClose, { "aria-label": "Close", className: slots.close(), icon: true }) : null] }), footer] }));
|
|
166
|
+
if (inline) {
|
|
167
|
+
return (_jsx("div", { "data-slot": "dialog-content", "data-variant": variant, className: slots.shell({ className }), ...props, children: inside }));
|
|
168
|
+
}
|
|
169
|
+
return (_jsxs(Primitive.Portal, { children: [_jsx(Primitive.Backdrop, { className: slots.backdrop() }), _jsx(Primitive.Viewport, { className: slots.viewport(), children: _jsx(Primitive.Popup, { "data-slot": "dialog-content", "data-variant": variant, className: slots.shell({ className: [slots.popup(), className] }), ...props, children: inside }) })] }));
|
|
170
|
+
}
|
|
171
|
+
DialogContent.displayName = 'Dialog.Content';
|
|
172
|
+
/** The heading block. It leaves room for the close button beside the title. */
|
|
173
|
+
function DialogHeader({ title, description, className, children, ...props }) {
|
|
174
|
+
const { showClose, variant } = useContext(DialogContext);
|
|
175
|
+
const slots = dialogVariants({ variant });
|
|
176
|
+
return (_jsxs("div", { "data-slot": "dialog-header", className: slots.header({ className: [showClose && 'pe-14', className] }), ...props, children: [title !== undefined ? _jsx(DialogTitle, { children: title }) : null, description !== undefined ? _jsx(DialogDescription, { children: description }) : null, children] }));
|
|
177
|
+
}
|
|
178
|
+
DialogHeader.displayName = 'Dialog.Header';
|
|
179
|
+
/** The dialog's name, announced when it opens. */
|
|
180
|
+
function DialogTitle({ className, ...props }) {
|
|
181
|
+
const { inline } = useContext(DialogContext);
|
|
182
|
+
const slots = dialogVariants();
|
|
183
|
+
if (inline)
|
|
184
|
+
return _jsx("h2", { "data-slot": "dialog-title", className: slots.title({ className }), ...props });
|
|
185
|
+
return _jsx(Primitive.Title, { "data-slot": "dialog-title", className: slots.title({ className }), ...props });
|
|
186
|
+
}
|
|
187
|
+
DialogTitle.displayName = 'Dialog.Title';
|
|
188
|
+
/** A sentence under the title, read out after it. */
|
|
189
|
+
function DialogDescription({ className, ...props }) {
|
|
190
|
+
const { inline } = useContext(DialogContext);
|
|
191
|
+
const slots = dialogVariants();
|
|
192
|
+
if (inline)
|
|
193
|
+
return _jsx("p", { "data-slot": "dialog-description", className: slots.description({ className }), ...props });
|
|
194
|
+
return _jsx(Primitive.Description, { "data-slot": "dialog-description", className: slots.description({ className }), ...props });
|
|
195
|
+
}
|
|
196
|
+
DialogDescription.displayName = 'Dialog.Description';
|
|
197
|
+
/**
|
|
198
|
+
* The part that scrolls, between the header and the footer. Its children are
|
|
199
|
+
* laid out in a column with a gap, so a form's fields need no wrapper.
|
|
200
|
+
*/
|
|
201
|
+
function DialogBody({ className, children, ...props }) {
|
|
202
|
+
const { variant } = useContext(DialogContext);
|
|
203
|
+
const slots = dialogVariants({ variant });
|
|
204
|
+
return (_jsxs(ScrollArea.Root, { "data-slot": "dialog-body", className: slots.body(), children: [_jsx(ScrollArea.Viewport, { className: slots.bodyViewport(), children: _jsx(ScrollArea.Content, { className: slots.bodyContent({ className }), ...props, children: children }) }), _jsx(ScrollArea.Scrollbar, { orientation: "vertical", className: slots.scrollbar(), children: _jsx(ScrollArea.Thumb, { className: slots.thumb() }) })] }));
|
|
205
|
+
}
|
|
206
|
+
DialogBody.displayName = 'Dialog.Body';
|
|
207
|
+
/**
|
|
208
|
+
* The buttons. In the inset look they sit in the band under the panel as
|
|
209
|
+
* equal pills; in `plain` they sit at the end of a ruled strip. Keep it a
|
|
210
|
+
* direct child of `Dialog.Content`, so the band can find it.
|
|
211
|
+
*/
|
|
212
|
+
function DialogFooter({ className, ...props }) {
|
|
213
|
+
const { variant } = useContext(DialogContext);
|
|
214
|
+
return _jsx("div", { "data-slot": "dialog-footer", className: dialogVariants({ variant }).footer({ className }), ...props });
|
|
215
|
+
}
|
|
216
|
+
DialogFooter.displayName = 'Dialog.Footer';
|
|
217
|
+
/** Closes the dialog. */
|
|
218
|
+
function DialogClose({ render, icon = false, children, ...props }) {
|
|
219
|
+
const { inline } = useContext(DialogContext);
|
|
220
|
+
const content = icon ? (_jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", fill: "none", stroke: "currentColor", strokeWidth: 2.4, strokeLinecap: "round", children: _jsx("path", { d: "M6 6l12 12M18 6L6 18" }) })) : (children);
|
|
221
|
+
if (inline) {
|
|
222
|
+
return (_jsx("button", { type: "button", "data-slot": "dialog-close", ...props, children: content }));
|
|
223
|
+
}
|
|
224
|
+
return (_jsx(Primitive.Close, { "data-slot": "dialog-close", render: render, ...props, children: content }));
|
|
225
|
+
}
|
|
226
|
+
DialogClose.displayName = 'Dialog.Close';
|
|
227
|
+
/**
|
|
228
|
+
* A list of one-line choices in the body. The arrow keys, Home and End move
|
|
229
|
+
* between its items, and it opens scrolled to the selected one.
|
|
230
|
+
*/
|
|
231
|
+
function DialogList({ className, onKeyDown, children, ...props }) {
|
|
232
|
+
const list = useRef(null);
|
|
233
|
+
const items = () => [...(list.current?.querySelectorAll('[data-slot=dialog-list-item]:not([aria-disabled=true])') ?? [])];
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
const all = items();
|
|
236
|
+
const selected = all.find((item) => item.dataset.selected !== undefined);
|
|
237
|
+
// One item takes Tab: the chosen one, or the first when none is.
|
|
238
|
+
if (!selected && all[0])
|
|
239
|
+
all[0].tabIndex = 0;
|
|
240
|
+
selected?.scrollIntoView({ block: 'center' });
|
|
241
|
+
}, []);
|
|
242
|
+
const move = (event) => {
|
|
243
|
+
onKeyDown?.(event);
|
|
244
|
+
const all = items();
|
|
245
|
+
const index = all.indexOf(document.activeElement);
|
|
246
|
+
const next = event.key === 'ArrowDown'
|
|
247
|
+
? all[index < 0 ? 0 : Math.min(index + 1, all.length - 1)]
|
|
248
|
+
: event.key === 'ArrowUp'
|
|
249
|
+
? all[Math.max(index - 1, 0)]
|
|
250
|
+
: event.key === 'Home'
|
|
251
|
+
? all[0]
|
|
252
|
+
: event.key === 'End'
|
|
253
|
+
? all[all.length - 1]
|
|
254
|
+
: undefined;
|
|
255
|
+
if (!next)
|
|
256
|
+
return;
|
|
257
|
+
event.preventDefault();
|
|
258
|
+
next.focus();
|
|
259
|
+
};
|
|
260
|
+
return (_jsx("div", { ref: list, role: "listbox", "data-slot": "dialog-list", className: dialogVariants().list({ className }), onKeyDown: move, ...props, children: children }));
|
|
261
|
+
}
|
|
262
|
+
DialogList.displayName = 'Dialog.List';
|
|
263
|
+
/** A run of items in `Dialog.List` under a heading of their own. */
|
|
264
|
+
function DialogListGroup({ label, className, children, ...props }) {
|
|
265
|
+
const slots = dialogVariants();
|
|
266
|
+
const id = useId();
|
|
267
|
+
return (_jsxs("div", { role: "group", "aria-labelledby": id, "data-slot": "dialog-list-group", className: slots.listGroup({ className }), ...props, children: [_jsx("div", { id: id, className: slots.listGroupLabel(), children: label }), children] }));
|
|
268
|
+
}
|
|
269
|
+
DialogListGroup.displayName = 'Dialog.ListGroup';
|
|
270
|
+
/** One choice in `Dialog.List`, on one line. */
|
|
271
|
+
function DialogListItem({ selected = false, onSelect, meta, disabled = false, className, onClick, onKeyDown, children, ...props }) {
|
|
272
|
+
const slots = dialogVariants();
|
|
273
|
+
return (_jsxs("div", { role: "option", "aria-selected": selected, "aria-disabled": disabled || undefined, tabIndex: selected ? 0 : -1, "data-slot": "dialog-list-item", "data-selected": selected ? '' : undefined, className: slots.listItem({ className }), onClick: (event) => {
|
|
274
|
+
onClick?.(event);
|
|
275
|
+
if (!disabled)
|
|
276
|
+
onSelect?.();
|
|
277
|
+
}, onKeyDown: (event) => {
|
|
278
|
+
onKeyDown?.(event);
|
|
279
|
+
if ((event.key === 'Enter' || event.key === ' ') && !disabled) {
|
|
280
|
+
event.preventDefault();
|
|
281
|
+
onSelect?.();
|
|
282
|
+
}
|
|
283
|
+
}, ...props, children: [_jsx("span", { className: slots.listItemLabel(), children: children }), meta !== undefined ? _jsx("span", { className: slots.listItemMeta(), children: meta }) : null, _jsx("span", { "aria-hidden": "true", className: slots.listItemCheck(), children: selected ? (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "m5 12.5 4.5 4.5L19 7.5" }) })) : null })] }));
|
|
284
|
+
}
|
|
285
|
+
DialogListItem.displayName = 'Dialog.ListItem';
|
|
286
|
+
export const Dialog = Object.assign(DialogRoot, {
|
|
287
|
+
Trigger: DialogTrigger,
|
|
288
|
+
Content: DialogContent,
|
|
289
|
+
Header: DialogHeader,
|
|
290
|
+
Title: DialogTitle,
|
|
291
|
+
Description: DialogDescription,
|
|
292
|
+
Body: DialogBody,
|
|
293
|
+
Footer: DialogFooter,
|
|
294
|
+
Close: DialogClose,
|
|
295
|
+
List: DialogList,
|
|
296
|
+
ListGroup: DialogListGroup,
|
|
297
|
+
ListItem: DialogListItem,
|
|
298
|
+
});
|
|
299
|
+
export { DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter, DialogClose, DialogList, DialogListGroup, DialogListItem, };
|
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
* and closes with its `open` prop, folding under the card, so a band can come
|
|
31
31
|
* and go without the page jumping by more than its own height.
|
|
32
32
|
*
|
|
33
|
+
* ## Modes
|
|
34
|
+
*
|
|
35
|
+
* A prompt that changes with what the app is doing names its states with
|
|
36
|
+
* `mode`, and each band says which of them it belongs to with its own
|
|
37
|
+
* `mode`. The bands that match open and the others fold, in one movement,
|
|
38
|
+
* so the card moves once, by the difference between them. A band's own
|
|
39
|
+
* `open` still wins over its mode.
|
|
40
|
+
*
|
|
33
41
|
* ## Sending
|
|
34
42
|
*
|
|
35
43
|
* Enter sends and Shift+Enter starts a new line; Enter while a character is
|
|
@@ -97,17 +105,27 @@ export interface PromptProps extends Omit<ComponentProps<'form'>, 'onSubmit' | '
|
|
|
97
105
|
maxFiles?: number;
|
|
98
106
|
/** Called with the files that were not taken, and why. */
|
|
99
107
|
onReject?: (files: File[], reason: PromptRejectReason) => void;
|
|
108
|
+
/**
|
|
109
|
+
* The state the prompt is in, named by you: `"connect"`, `"tasks"`,
|
|
110
|
+
* `"writing"`. Each band given a `mode` of its own opens while this
|
|
111
|
+
* matches it and folds away otherwise.
|
|
112
|
+
*/
|
|
113
|
+
mode?: string;
|
|
100
114
|
}
|
|
101
|
-
declare function PromptRoot({ value: controlledValue, defaultValue, onValueChange, onSubmit, status, onStop, disabled, defaultFiles, accept, maxFiles, onReject, className, children, ...props }: PromptProps): import("react").JSX.Element;
|
|
115
|
+
declare function PromptRoot({ value: controlledValue, defaultValue, onValueChange, onSubmit, status, onStop, disabled, defaultFiles, accept, maxFiles, onReject, mode, className, children, ...props }: PromptProps): import("react").JSX.Element;
|
|
102
116
|
declare namespace PromptRoot {
|
|
103
117
|
var displayName: string;
|
|
104
118
|
}
|
|
119
|
+
/** A band's `mode`: the prompt mode, or modes, it is open in. */
|
|
120
|
+
export type PromptBandMode = string | string[];
|
|
105
121
|
export interface PromptBannerProps extends ComponentProps<'div'> {
|
|
106
|
-
/** Whether it is shown. It folds under the field when it is not. Default `
|
|
122
|
+
/** Whether it is shown. It folds under the field when it is not. Default: shown, or with `mode`, while the prompt is in that mode. */
|
|
107
123
|
open?: boolean;
|
|
124
|
+
/** The prompt modes it is shown in. It folds away in any other. */
|
|
125
|
+
mode?: PromptBandMode;
|
|
108
126
|
}
|
|
109
127
|
/** 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;
|
|
128
|
+
declare function PromptBanner({ open, mode, ...props }: PromptBannerProps): import("react").JSX.Element;
|
|
111
129
|
declare namespace PromptBanner {
|
|
112
130
|
var displayName: string;
|
|
113
131
|
}
|
|
@@ -119,22 +137,28 @@ declare namespace PromptBannerAction {
|
|
|
119
137
|
var displayName: string;
|
|
120
138
|
}
|
|
121
139
|
export interface PromptStatusProps extends ComponentProps<'div'> {
|
|
122
|
-
/** Whether it is shown. Default: while `status` is `submitted` or `streaming
|
|
140
|
+
/** Whether it is shown. Default: while `status` is `submitted` or `streaming`, or with `mode`, while the prompt is in that mode. */
|
|
123
141
|
open?: boolean;
|
|
142
|
+
/** The prompt modes it is shown in. It folds away in any other. */
|
|
143
|
+
mode?: PromptBandMode;
|
|
124
144
|
/** Something before the words: the model's mark, a tool's icon. */
|
|
125
145
|
icon?: ReactNode;
|
|
126
146
|
/** Whether a spinner turns at its end. Default `true`. */
|
|
127
147
|
spinner?: boolean;
|
|
128
148
|
}
|
|
129
149
|
/** 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;
|
|
150
|
+
declare function PromptStatus({ open, mode, icon, spinner, children, ...props }: PromptStatusProps): import("react").JSX.Element;
|
|
131
151
|
declare namespace PromptStatus {
|
|
132
152
|
var displayName: string;
|
|
133
153
|
}
|
|
134
154
|
export interface PromptContextProps extends ComponentProps<'div'> {
|
|
155
|
+
/** Whether it is shown. It folds under the field when it is not. Default: shown, or with `mode`, while the prompt is in that mode. */
|
|
156
|
+
open?: boolean;
|
|
157
|
+
/** The prompt modes it is shown in. It folds away in any other. */
|
|
158
|
+
mode?: PromptBandMode;
|
|
135
159
|
}
|
|
136
160
|
/** 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;
|
|
161
|
+
declare function PromptContext({ open, mode, className, ...props }: PromptContextProps): import("react").JSX.Element;
|
|
138
162
|
declare namespace PromptContext {
|
|
139
163
|
var displayName: string;
|
|
140
164
|
}
|
|
@@ -296,8 +320,10 @@ declare namespace PromptSubmit {
|
|
|
296
320
|
}
|
|
297
321
|
export type PromptTaskStatus = 'pending' | 'active' | 'done' | 'error';
|
|
298
322
|
export interface PromptTasksProps extends Omit<ComponentProps<'div'>, 'title'> {
|
|
299
|
-
/** Whether the band is shown. It folds under the field when it is not. Default `
|
|
323
|
+
/** Whether the band is shown. It folds under the field when it is not. Default: shown, or with `mode`, while the prompt is in that mode. */
|
|
300
324
|
open?: boolean;
|
|
325
|
+
/** The prompt modes it is shown in. It folds away in any other. */
|
|
326
|
+
mode?: PromptBandMode;
|
|
301
327
|
/** What the agent is doing, on the band's one line: "Preparing the release". */
|
|
302
328
|
title: ReactNode;
|
|
303
329
|
/** Whether the list is open under the title, when the caller holds it. */
|
|
@@ -314,7 +340,7 @@ export interface PromptTasksProps extends Omit<ComponentProps<'div'>, 'title'> {
|
|
|
314
340
|
* fills as they are done, opening into the list. The list scrolls past
|
|
315
341
|
* `maxHeight`, and keeps the task in progress in view.
|
|
316
342
|
*/
|
|
317
|
-
declare function PromptTasks({ open, title, expanded, defaultExpanded, onExpandedChange, maxHeight, className, children, ...props }: PromptTasksProps): import("react").JSX.Element;
|
|
343
|
+
declare function PromptTasks({ open, mode, title, expanded, defaultExpanded, onExpandedChange, maxHeight, className, children, ...props }: PromptTasksProps): import("react").JSX.Element;
|
|
318
344
|
declare namespace PromptTasks {
|
|
319
345
|
var displayName: string;
|
|
320
346
|
}
|
|
@@ -32,6 +32,14 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
32
32
|
* and closes with its `open` prop, folding under the card, so a band can come
|
|
33
33
|
* and go without the page jumping by more than its own height.
|
|
34
34
|
*
|
|
35
|
+
* ## Modes
|
|
36
|
+
*
|
|
37
|
+
* A prompt that changes with what the app is doing names its states with
|
|
38
|
+
* `mode`, and each band says which of them it belongs to with its own
|
|
39
|
+
* `mode`. The bands that match open and the others fold, in one movement,
|
|
40
|
+
* so the card moves once, by the difference between them. A band's own
|
|
41
|
+
* `open` still wins over its mode.
|
|
42
|
+
*
|
|
35
43
|
* ## Sending
|
|
36
44
|
*
|
|
37
45
|
* Enter sends and Shift+Enter starts a new line; Enter while a character is
|
|
@@ -120,8 +128,10 @@ const promptVariants = tv({
|
|
|
120
128
|
submitRing: 'pointer-events-none absolute -inset-[3px] size-[calc(100%+6px)] text-primary transition-opacity duration-200',
|
|
121
129
|
statusText: 'min-w-0 truncate bg-[linear-gradient(90deg,var(--muted-foreground)_35%,var(--foreground)_50%,var(--muted-foreground)_65%)] bg-[length:250%_100%] bg-clip-text text-transparent',
|
|
122
130
|
statusSpinner: 'ms-auto size-4 animate-spin text-muted-foreground motion-reduce:animate-none',
|
|
131
|
+
// The strip folds like a band: its tuck under the card moves with it.
|
|
132
|
+
contextFold: 'mx-4 grid transition-[grid-template-rows,margin,opacity] duration-300 ease-[cubic-bezier(0.2,0,0,1)] motion-reduce:transition-none',
|
|
123
133
|
context: [
|
|
124
|
-
'relative
|
|
134
|
+
'relative flex items-center gap-x-4 gap-y-1 rounded-b-[1rem] border bg-muted px-3.5 pb-2 pt-6.5 text-xs text-muted-foreground',
|
|
125
135
|
'flex-wrap [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-3.5',
|
|
126
136
|
],
|
|
127
137
|
contextItem: 'inline-flex min-w-0 items-center gap-1.5 whitespace-nowrap',
|
|
@@ -209,7 +219,7 @@ function attach(file) {
|
|
|
209
219
|
preview: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,
|
|
210
220
|
};
|
|
211
221
|
}
|
|
212
|
-
function PromptRoot({ value: controlledValue, defaultValue = '', onValueChange, onSubmit, status = 'idle', onStop, disabled = false, defaultFiles, accept, maxFiles, onReject, className, children, ...props }) {
|
|
222
|
+
function PromptRoot({ value: controlledValue, defaultValue = '', onValueChange, onSubmit, status = 'idle', onStop, disabled = false, defaultFiles, accept, maxFiles, onReject, mode, className, children, ...props }) {
|
|
213
223
|
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
214
224
|
const value = controlledValue ?? internalValue;
|
|
215
225
|
const [attachments, setAttachments] = useState(() => (defaultFiles ?? []).map(attach));
|
|
@@ -274,6 +284,7 @@ function PromptRoot({ value: controlledValue, defaultValue = '', onValueChange,
|
|
|
274
284
|
removeAttachment,
|
|
275
285
|
openFileDialog,
|
|
276
286
|
status,
|
|
287
|
+
mode,
|
|
277
288
|
busy,
|
|
278
289
|
disabled,
|
|
279
290
|
empty,
|
|
@@ -287,12 +298,12 @@ function PromptRoot({ value: controlledValue, defaultValue = '', onValueChange,
|
|
|
287
298
|
keyHandler,
|
|
288
299
|
listbox,
|
|
289
300
|
setListbox,
|
|
290
|
-
}), [value, setValue, attachments, addFiles, removeAttachment, openFileDialog, status, busy, disabled, empty, submit, stop, onStop, dragging, fieldId, listbox]);
|
|
301
|
+
}), [value, setValue, attachments, addFiles, removeAttachment, openFileDialog, status, mode, busy, disabled, empty, submit, stop, onStop, dragging, fieldId, listbox]);
|
|
291
302
|
const send = (event) => {
|
|
292
303
|
event.preventDefault();
|
|
293
304
|
submit();
|
|
294
305
|
};
|
|
295
|
-
return (_jsx(PromptStore.Provider, { value: context, children: _jsxs("form", { "data-slot": "prompt", "data-status": status, "aria-busy": busy || undefined, className: promptVariants().root({ className }), onSubmit: send, ...props, children: [children, _jsx("input", { ref: fileInput, type: "file", multiple: maxFiles !== 1, accept: accept, tabIndex: -1, "aria-hidden": "true", className: "hidden", onChange: (event) => {
|
|
306
|
+
return (_jsx(PromptStore.Provider, { value: context, children: _jsxs("form", { "data-slot": "prompt", "data-status": status, "data-mode": mode, "aria-busy": busy || undefined, className: promptVariants().root({ className }), onSubmit: send, ...props, children: [children, _jsx("input", { ref: fileInput, type: "file", multiple: maxFiles !== 1, accept: accept, tabIndex: -1, "aria-hidden": "true", className: "hidden", onChange: (event) => {
|
|
296
307
|
if (event.target.files)
|
|
297
308
|
addFiles(event.target.files);
|
|
298
309
|
// Cleared, so choosing the same file again still counts as a change.
|
|
@@ -311,9 +322,22 @@ function Band({ edge, open, slot, className, children, ...props }) {
|
|
|
311
322
|
}, [open]);
|
|
312
323
|
return (_jsx("div", { "data-slot": slot, "data-open": open ? '' : undefined, className: slots.band(), children: _jsx("div", { ref: clip, className: slots.bandClip(), children: _jsx("div", { className: slots.bandInner({ className }), ...props, children: children }) }) }));
|
|
313
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Whether a band is open: its own `open` if given, else whether the
|
|
327
|
+
* prompt's mode is one of the band's, else what the band does by default.
|
|
328
|
+
*/
|
|
329
|
+
function useBandOpen(part, open, mode, fallback) {
|
|
330
|
+
const prompt = usePrompt(part);
|
|
331
|
+
if (open !== undefined)
|
|
332
|
+
return open;
|
|
333
|
+
if (mode !== undefined)
|
|
334
|
+
return prompt.mode !== undefined && (Array.isArray(mode) ? mode : [mode]).includes(prompt.mode);
|
|
335
|
+
return fallback(prompt);
|
|
336
|
+
}
|
|
314
337
|
/** A band above the field, for something to do before or while writing: connect an account, pick a project. */
|
|
315
|
-
function PromptBanner({ open
|
|
316
|
-
|
|
338
|
+
function PromptBanner({ open, mode, ...props }) {
|
|
339
|
+
const shown = useBandOpen('Prompt.Banner', open, mode, () => true);
|
|
340
|
+
return _jsx(Band, { edge: "top", open: shown, slot: "prompt-banner", ...props });
|
|
317
341
|
}
|
|
318
342
|
PromptBanner.displayName = 'Prompt.Banner';
|
|
319
343
|
/** The banner's button, at its end. */
|
|
@@ -340,17 +364,22 @@ function useShimmer(active) {
|
|
|
340
364
|
return ref;
|
|
341
365
|
}
|
|
342
366
|
/** A band under the field saying what the answer is doing, with the words lit by a moving sweep. */
|
|
343
|
-
function PromptStatus({ open, icon, spinner = true, children, ...props }) {
|
|
344
|
-
const
|
|
345
|
-
const shown = open ?? prompt.busy;
|
|
367
|
+
function PromptStatus({ open, mode, icon, spinner = true, children, ...props }) {
|
|
368
|
+
const shown = useBandOpen('Prompt.Status', open, mode, (prompt) => prompt.busy);
|
|
346
369
|
const slots = promptVariants();
|
|
347
370
|
const text = useShimmer(shown);
|
|
348
371
|
return (_jsxs(Band, { edge: "bottom", open: shown, slot: "prompt-status", role: "status", ...props, children: [icon, _jsx("span", { ref: text, className: slots.statusText(), children: children }), spinner ? (_jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", className: slots.statusSpinner(), children: _jsx("path", { d: "M21 12a9 9 0 1 1-6.22-8.56" }) })) : null] }));
|
|
349
372
|
}
|
|
350
373
|
PromptStatus.displayName = 'Prompt.Status';
|
|
351
374
|
/** A strip under the field, inset from its edges, for where the message is going: a branch, a folder, a ticket. */
|
|
352
|
-
function PromptContext({ className, ...props }) {
|
|
353
|
-
|
|
375
|
+
function PromptContext({ open, mode, className, ...props }) {
|
|
376
|
+
const shown = useBandOpen('Prompt.Context', open, mode, () => true);
|
|
377
|
+
const slots = promptVariants();
|
|
378
|
+
const clip = useRef(null);
|
|
379
|
+
useEffect(() => {
|
|
380
|
+
clip.current?.toggleAttribute('inert', !shown);
|
|
381
|
+
}, [shown]);
|
|
382
|
+
return (_jsx("div", { "data-slot": "prompt-context", "data-open": shown ? '' : undefined, className: slots.contextFold({ className: shown ? '-mt-5 grid-rows-[1fr] opacity-100' : 'mt-0 grid-rows-[0fr] opacity-0' }), children: _jsx("div", { ref: clip, className: "min-h-0 overflow-hidden", children: _jsx("div", { className: slots.context({ className }), ...props }) }) }));
|
|
354
383
|
}
|
|
355
384
|
PromptContext.displayName = 'Prompt.Context';
|
|
356
385
|
/** One fact in the context strip, with an icon before it. */
|
|
@@ -567,7 +596,8 @@ function ProgressRing({ done, total }) {
|
|
|
567
596
|
* fills as they are done, opening into the list. The list scrolls past
|
|
568
597
|
* `maxHeight`, and keeps the task in progress in view.
|
|
569
598
|
*/
|
|
570
|
-
function PromptTasks({ open
|
|
599
|
+
function PromptTasks({ open, mode, title, expanded, defaultExpanded = false, onExpandedChange, maxHeight = '10rem', className, children, ...props }) {
|
|
600
|
+
const shown = useBandOpen('Prompt.Tasks', open, mode, () => true);
|
|
571
601
|
const slots = promptVariants();
|
|
572
602
|
const [inner, setInner] = useState(defaultExpanded);
|
|
573
603
|
const isExpanded = expanded ?? inner;
|
|
@@ -593,7 +623,7 @@ function PromptTasks({ open = true, title, expanded, defaultExpanded = false, on
|
|
|
593
623
|
setInner(!isExpanded);
|
|
594
624
|
onExpandedChange?.(!isExpanded);
|
|
595
625
|
};
|
|
596
|
-
return (_jsxs(Band, { edge: "top", open:
|
|
626
|
+
return (_jsxs(Band, { edge: "top", open: shown, slot: "prompt-tasks", className: ['flex-col items-stretch gap-0 px-0 pt-0.5', className].filter(Boolean).join(' '), ...props, children: [_jsxs("button", { type: "button", "aria-expanded": isExpanded, "aria-controls": listId, onClick: toggle, className: slots.tasksHeader(), children: [_jsx(ProgressRing, { done: done, total: tasks.length }), _jsx("span", { className: slots.tasksTitle(), children: title }), _jsxs("span", { className: slots.tasksCount(), children: [done, "/", tasks.length] }), _jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: slots.tasksChevron({ className: isExpanded ? 'rotate-180' : undefined }), children: _jsx("path", { d: "m6 9 6 6 6-6" }) })] }), _jsx("div", { className: slots.tasksBody({ className: isExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]' }), children: _jsx("div", { className: "min-h-0 overflow-hidden", children: _jsx("ol", { ref: list, id: listId, "aria-label": "Tasks", className: slots.tasksList(), style: { maxHeight }, children: children }) }) })] }));
|
|
597
627
|
}
|
|
598
628
|
PromptTasks.displayName = 'Prompt.Tasks';
|
|
599
629
|
/** One task in `Prompt.Tasks`. */
|
|
@@ -161,6 +161,50 @@ declare function SheetClose({ render, icon, children, ...props }: SheetCloseProp
|
|
|
161
161
|
declare namespace SheetClose {
|
|
162
162
|
var displayName: string;
|
|
163
163
|
}
|
|
164
|
+
export interface SheetSlidesProps extends Omit<ComponentProps<'div'>, 'onChange'> {
|
|
165
|
+
/**
|
|
166
|
+
* `true` makes it a deck: each slide fills the height, and a scroll
|
|
167
|
+
* glides to the next one. `false` makes it a numbered column of smaller
|
|
168
|
+
* slides that scrolls freely. Default `true`.
|
|
169
|
+
*/
|
|
170
|
+
snap?: boolean;
|
|
171
|
+
/** The current slide, counted from 0, when the caller holds it. Setting it scrolls that slide into view. */
|
|
172
|
+
index?: number;
|
|
173
|
+
/** The slide to start on, when the caller does not hold it. Default `0`. */
|
|
174
|
+
defaultIndex?: number;
|
|
175
|
+
/**
|
|
176
|
+
* Called with the new index when the current slide changes: scrolled to
|
|
177
|
+
* in a deck, pressed in a column. In a column, passing it makes each
|
|
178
|
+
* slide a button.
|
|
179
|
+
*/
|
|
180
|
+
onIndexChange?: (index: number) => void;
|
|
181
|
+
/** Show "3 / 8" under the current slide. Default: on in a deck. */
|
|
182
|
+
counter?: boolean;
|
|
183
|
+
/** The shape of a slide in a column, as a CSS aspect ratio. Default `16 / 9`. */
|
|
184
|
+
aspect?: string;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* A column of rounded slides in place of `Sheet.Body`: a deck that moves one
|
|
188
|
+
* slide at a time, or with `snap={false}`, a numbered column to choose from.
|
|
189
|
+
*/
|
|
190
|
+
declare function SheetSlides({ snap, index, defaultIndex, onIndexChange, counter, aspect, className, style, children, 'aria-label': label, ...props }: SheetSlidesProps): import("react").JSX.Element;
|
|
191
|
+
declare namespace SheetSlides {
|
|
192
|
+
var displayName: string;
|
|
193
|
+
}
|
|
194
|
+
export interface SheetSlideProps extends ComponentProps<'div'> {
|
|
195
|
+
/**
|
|
196
|
+
* What a screen reader calls it, after its place: "3 of 8, Harbour at
|
|
197
|
+
* dawn". An image's `alt` usually says it already.
|
|
198
|
+
*/
|
|
199
|
+
label?: string;
|
|
200
|
+
/** Its shape in a column, overriding `Sheet.Slides`' `aspect`. */
|
|
201
|
+
aspect?: string;
|
|
202
|
+
}
|
|
203
|
+
/** One slide: a rounded frame that fills with an image, a video or anything drawn. */
|
|
204
|
+
declare function SheetSlide({ label, aspect, className, style, children, ...props }: SheetSlideProps): import("react").JSX.Element;
|
|
205
|
+
declare namespace SheetSlide {
|
|
206
|
+
var displayName: string;
|
|
207
|
+
}
|
|
164
208
|
export interface SheetStageProps extends ComponentProps<'div'> {
|
|
165
209
|
}
|
|
166
210
|
/**
|
|
@@ -183,5 +227,7 @@ export declare const Sheet: typeof SheetRoot & {
|
|
|
183
227
|
Footer: typeof SheetFooter;
|
|
184
228
|
Close: typeof SheetClose;
|
|
185
229
|
Stage: typeof SheetStage;
|
|
230
|
+
Slides: typeof SheetSlides;
|
|
231
|
+
Slide: typeof SheetSlide;
|
|
186
232
|
};
|
|
187
|
-
export { SheetTrigger, SheetNext, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, SheetFooter, SheetClose, SheetStage, };
|
|
233
|
+
export { SheetTrigger, SheetNext, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, SheetFooter, SheetClose, SheetStage, SheetSlides, SheetSlide, };
|