panelui-web 0.7.0 → 0.8.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/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 +6 -2
- package/dist/index.js +5 -1
- package/package.json +1 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
export interface DialogProps {
|
|
3
|
+
children?: ReactNode;
|
|
4
|
+
/** Whether it is open, when the caller holds that. */
|
|
5
|
+
open?: boolean;
|
|
6
|
+
/** Whether it starts open, when the caller does not hold it. */
|
|
7
|
+
defaultOpen?: boolean;
|
|
8
|
+
/** Called when it opens or closes. */
|
|
9
|
+
onOpenChange?: (open: boolean) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Whether a press outside it closes it. Turn it off while a form in it has
|
|
12
|
+
* changes that would be lost; Escape and the close button still work.
|
|
13
|
+
* Default `true`.
|
|
14
|
+
*/
|
|
15
|
+
dismissible?: boolean;
|
|
16
|
+
}
|
|
17
|
+
declare function DialogRoot({ children, open, defaultOpen, onOpenChange, dismissible, }: DialogProps): import("react").JSX.Element;
|
|
18
|
+
declare namespace DialogRoot {
|
|
19
|
+
var displayName: string;
|
|
20
|
+
}
|
|
21
|
+
export interface DialogTriggerProps extends Omit<ComponentProps<'button'>, 'children'> {
|
|
22
|
+
children?: ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* Your own element to be the trigger, such as a styled button. It is given
|
|
25
|
+
* the click handler and the attributes that tie it to the dialog.
|
|
26
|
+
*/
|
|
27
|
+
render?: ReactElement;
|
|
28
|
+
}
|
|
29
|
+
/** What opens the dialog. A plain button unless `render` supplies one. */
|
|
30
|
+
declare function DialogTrigger({ render, children, ...props }: DialogTriggerProps): import("react").JSX.Element;
|
|
31
|
+
declare namespace DialogTrigger {
|
|
32
|
+
var displayName: string;
|
|
33
|
+
}
|
|
34
|
+
export interface DialogContentProps extends Omit<ComponentProps<'div'>, 'children'> {
|
|
35
|
+
children?: ReactNode;
|
|
36
|
+
/**
|
|
37
|
+
* `inset` sets the task in a panel inside a darker band that holds the
|
|
38
|
+
* footer. `plain` is one surface with the footer ruled off. Default `inset`.
|
|
39
|
+
*/
|
|
40
|
+
variant?: 'inset' | 'plain';
|
|
41
|
+
/** How wide it may grow: `sm` 24rem, `md` 28rem, `lg` 36rem. Default `md`. */
|
|
42
|
+
size?: 'sm' | 'md' | 'lg';
|
|
43
|
+
/** Show the round close button in the top trailing corner. Default `true`. */
|
|
44
|
+
showClose?: boolean;
|
|
45
|
+
/** Frost the page behind it instead of only dimming it. */
|
|
46
|
+
blur?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Draw it in the page instead of over it: no backdrop, no trigger, always
|
|
49
|
+
* there. For previews; `open` is ignored.
|
|
50
|
+
*/
|
|
51
|
+
inline?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/** The dialog itself, with the dimmed page behind it. */
|
|
54
|
+
declare function DialogContent({ variant, size, showClose, blur, inline, className, children, ...props }: DialogContentProps): import("react").JSX.Element;
|
|
55
|
+
declare namespace DialogContent {
|
|
56
|
+
var displayName: string;
|
|
57
|
+
}
|
|
58
|
+
export interface DialogHeaderProps extends Omit<ComponentProps<'div'>, 'title'> {
|
|
59
|
+
/** The heading. It is also what the dialog is announced as. */
|
|
60
|
+
title?: ReactNode;
|
|
61
|
+
/** A line under the heading. */
|
|
62
|
+
description?: ReactNode;
|
|
63
|
+
}
|
|
64
|
+
/** The heading block. It leaves room for the close button beside the title. */
|
|
65
|
+
declare function DialogHeader({ title, description, className, children, ...props }: DialogHeaderProps): import("react").JSX.Element;
|
|
66
|
+
declare namespace DialogHeader {
|
|
67
|
+
var displayName: string;
|
|
68
|
+
}
|
|
69
|
+
export interface DialogTitleProps extends ComponentProps<'h2'> {
|
|
70
|
+
}
|
|
71
|
+
/** The dialog's name, announced when it opens. */
|
|
72
|
+
declare function DialogTitle({ className, ...props }: DialogTitleProps): import("react").JSX.Element;
|
|
73
|
+
declare namespace DialogTitle {
|
|
74
|
+
var displayName: string;
|
|
75
|
+
}
|
|
76
|
+
export interface DialogDescriptionProps extends ComponentProps<'p'> {
|
|
77
|
+
}
|
|
78
|
+
/** A sentence under the title, read out after it. */
|
|
79
|
+
declare function DialogDescription({ className, ...props }: DialogDescriptionProps): import("react").JSX.Element;
|
|
80
|
+
declare namespace DialogDescription {
|
|
81
|
+
var displayName: string;
|
|
82
|
+
}
|
|
83
|
+
export interface DialogBodyProps extends ComponentProps<'div'> {
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The part that scrolls, between the header and the footer. Its children are
|
|
87
|
+
* laid out in a column with a gap, so a form's fields need no wrapper.
|
|
88
|
+
*/
|
|
89
|
+
declare function DialogBody({ className, children, ...props }: DialogBodyProps): import("react").JSX.Element;
|
|
90
|
+
declare namespace DialogBody {
|
|
91
|
+
var displayName: string;
|
|
92
|
+
}
|
|
93
|
+
export interface DialogFooterProps extends ComponentProps<'div'> {
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The buttons. In the inset look they sit in the band under the panel as
|
|
97
|
+
* equal pills; in `plain` they sit at the end of a ruled strip. Keep it a
|
|
98
|
+
* direct child of `Dialog.Content`, so the band can find it.
|
|
99
|
+
*/
|
|
100
|
+
declare function DialogFooter({ className, ...props }: DialogFooterProps): import("react").JSX.Element;
|
|
101
|
+
declare namespace DialogFooter {
|
|
102
|
+
var displayName: string;
|
|
103
|
+
}
|
|
104
|
+
export interface DialogCloseProps extends ComponentProps<'button'> {
|
|
105
|
+
/** Your own element to close the dialog with, such as a Cancel button. */
|
|
106
|
+
render?: ReactElement;
|
|
107
|
+
/** Draw the cross glyph. Set for you on the built-in close button. */
|
|
108
|
+
icon?: boolean;
|
|
109
|
+
}
|
|
110
|
+
/** Closes the dialog. */
|
|
111
|
+
declare function DialogClose({ render, icon, children, ...props }: DialogCloseProps): import("react").JSX.Element;
|
|
112
|
+
declare namespace DialogClose {
|
|
113
|
+
var displayName: string;
|
|
114
|
+
}
|
|
115
|
+
export interface DialogListProps extends ComponentProps<'div'> {
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* A list of one-line choices in the body. The arrow keys, Home and End move
|
|
119
|
+
* between its items, and it opens scrolled to the selected one.
|
|
120
|
+
*/
|
|
121
|
+
declare function DialogList({ className, onKeyDown, children, ...props }: DialogListProps): import("react").JSX.Element;
|
|
122
|
+
declare namespace DialogList {
|
|
123
|
+
var displayName: string;
|
|
124
|
+
}
|
|
125
|
+
export interface DialogListGroupProps extends Omit<ComponentProps<'div'>, 'title'> {
|
|
126
|
+
/** The heading over the group, which stays pinned while its items scroll under it. */
|
|
127
|
+
label: ReactNode;
|
|
128
|
+
}
|
|
129
|
+
/** A run of items in `Dialog.List` under a heading of their own. */
|
|
130
|
+
declare function DialogListGroup({ label, className, children, ...props }: DialogListGroupProps): import("react").JSX.Element;
|
|
131
|
+
declare namespace DialogListGroup {
|
|
132
|
+
var displayName: string;
|
|
133
|
+
}
|
|
134
|
+
export interface DialogListItemProps extends Omit<ComponentProps<'div'>, 'onSelect'> {
|
|
135
|
+
/** Whether it is the chosen one: it is tinted and checked. */
|
|
136
|
+
selected?: boolean;
|
|
137
|
+
/** Called when it is pressed, or chosen with Enter or Space. */
|
|
138
|
+
onSelect?: () => void;
|
|
139
|
+
/** Quieter text at its end, in figures that line up: a time, a count. */
|
|
140
|
+
meta?: ReactNode;
|
|
141
|
+
/** Shown, but cannot be chosen. */
|
|
142
|
+
disabled?: boolean;
|
|
143
|
+
}
|
|
144
|
+
/** One choice in `Dialog.List`, on one line. */
|
|
145
|
+
declare function DialogListItem({ selected, onSelect, meta, disabled, className, onClick, onKeyDown, children, ...props }: DialogListItemProps): import("react").JSX.Element;
|
|
146
|
+
declare namespace DialogListItem {
|
|
147
|
+
var displayName: string;
|
|
148
|
+
}
|
|
149
|
+
export declare const Dialog: typeof DialogRoot & {
|
|
150
|
+
Trigger: typeof DialogTrigger;
|
|
151
|
+
Content: typeof DialogContent;
|
|
152
|
+
Header: typeof DialogHeader;
|
|
153
|
+
Title: typeof DialogTitle;
|
|
154
|
+
Description: typeof DialogDescription;
|
|
155
|
+
Body: typeof DialogBody;
|
|
156
|
+
Footer: typeof DialogFooter;
|
|
157
|
+
Close: typeof DialogClose;
|
|
158
|
+
List: typeof DialogList;
|
|
159
|
+
ListGroup: typeof DialogListGroup;
|
|
160
|
+
ListItem: typeof DialogListItem;
|
|
161
|
+
};
|
|
162
|
+
export { DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter, DialogClose, DialogList, DialogListGroup, DialogListItem, };
|
|
@@ -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
|
}
|