panelui-web 0.9.1 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/choice-cards/index.d.ts +167 -0
- package/dist/components/choice-cards/index.js +132 -0
- package/dist/components/dialog/index.js +8 -1
- package/dist/components/inspector/index.d.ts +242 -0
- package/dist/components/inspector/index.js +230 -0
- package/dist/components/sheet/index.d.ts +16 -5
- package/dist/components/sheet/index.js +72 -15
- package/dist/components/slider/index.d.ts +293 -0
- package/dist/components/slider/index.js +342 -0
- package/dist/components/timeline/index.d.ts +328 -0
- package/dist/components/timeline/index.js +613 -0
- package/dist/components/toolbar/index.d.ts +185 -0
- package/dist/components/toolbar/index.js +270 -0
- package/dist/components/workbench/index.d.ts +224 -0
- package/dist/components/workbench/index.js +306 -0
- package/dist/components/xy-pad/index.d.ts +180 -0
- package/dist/components/xy-pad/index.js +278 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Inspector — the settings panel beside an editor's canvas: a rail of
|
|
5
|
+
* icons, each opening a page of titled sections.
|
|
6
|
+
*
|
|
7
|
+
* ```tsx
|
|
8
|
+
* <Inspector defaultValue="pointer">
|
|
9
|
+
* <Inspector.Rail>
|
|
10
|
+
* <Inspector.Tab value="pointer" label="Pointer"><MousePointer2Icon /></Inspector.Tab>
|
|
11
|
+
* <Inspector.Tab value="audio" label="Audio"><Volume2Icon /></Inspector.Tab>
|
|
12
|
+
* </Inspector.Rail>
|
|
13
|
+
* <Inspector.Panel>
|
|
14
|
+
* <Inspector.Page value="pointer">
|
|
15
|
+
* <Inspector.Section>
|
|
16
|
+
* <Inspector.SectionTitle>Click sound</Inspector.SectionTitle>
|
|
17
|
+
* <Inspector.SectionAction><Switch label="Click sound" /></Inspector.SectionAction>
|
|
18
|
+
* <Inspector.SectionContent>…</Inspector.SectionContent>
|
|
19
|
+
* </Inspector.Section>
|
|
20
|
+
* </Inspector.Page>
|
|
21
|
+
* </Inspector.Panel>
|
|
22
|
+
* </Inspector>
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* ## The rail stands apart
|
|
26
|
+
*
|
|
27
|
+
* The rail is a pill of its own beside the panel, not a strip inside it.
|
|
28
|
+
* It is navigation, and the panel is the thing being navigated; drawn
|
|
29
|
+
* apart, the eye knows which of the two it is looking at. A filled marker
|
|
30
|
+
* slides along the rail to the chosen page, and the page arrives from the
|
|
31
|
+
* direction the marker went.
|
|
32
|
+
*
|
|
33
|
+
* ## Sections carry their own switch
|
|
34
|
+
*
|
|
35
|
+
* A section's title sits on one line with its action: the switch that turns
|
|
36
|
+
* the whole section on, or a Reset that puts its values back. The controls
|
|
37
|
+
* under it are what that switch or reset applies to, so the relation never
|
|
38
|
+
* has to be explained. A section can fold away with `collapsible`.
|
|
39
|
+
*/
|
|
40
|
+
import { Collapsible } from '@base-ui/react/collapsible';
|
|
41
|
+
import { ScrollArea } from '@base-ui/react/scroll-area';
|
|
42
|
+
import { Tabs } from '@base-ui/react/tabs';
|
|
43
|
+
import { Tooltip } from '@base-ui/react/tooltip';
|
|
44
|
+
import { createContext, useContext } from 'react';
|
|
45
|
+
import { tv } from 'tailwind-variants';
|
|
46
|
+
const EASE = 'ease-[cubic-bezier(0.3,1.2,0.4,1)]';
|
|
47
|
+
const inspectorVariants = tv({
|
|
48
|
+
slots: {
|
|
49
|
+
root: 'group/inspector flex items-start gap-2 data-[orientation=vertical]:flex-row',
|
|
50
|
+
rail: [
|
|
51
|
+
'relative isolate flex shrink-0 flex-col items-center gap-1 rounded-[20px] bg-popover p-1.5 text-popover-foreground',
|
|
52
|
+
'shadow-[0_10px_30px_-12px_rgb(0_0_0/0.3)] ring-1 ring-foreground/[0.06] dark:ring-foreground/10',
|
|
53
|
+
],
|
|
54
|
+
marker: [
|
|
55
|
+
'absolute -z-10 rounded-[14px] bg-primary shadow-[0_1px_2px_rgb(0_0_0/0.18),inset_0_1px_0_rgb(255_255_255/0.18)]',
|
|
56
|
+
'top-(--active-tab-top) left-(--active-tab-left) h-(--active-tab-height) w-(--active-tab-width)',
|
|
57
|
+
`transition-[top,height] duration-[380ms] ${EASE} motion-reduce:transition-none`,
|
|
58
|
+
],
|
|
59
|
+
tab: [
|
|
60
|
+
'relative z-10 inline-flex size-10 shrink-0 cursor-pointer items-center justify-center rounded-[14px] outline-none select-none',
|
|
61
|
+
'text-muted-foreground transition-[color,background-color,scale] duration-150',
|
|
62
|
+
'hover:bg-foreground/[0.06] hover:text-foreground active:scale-[0.94]',
|
|
63
|
+
'data-active:bg-transparent data-active:text-primary-foreground data-active:hover:bg-transparent',
|
|
64
|
+
'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset',
|
|
65
|
+
'data-disabled:pointer-events-none data-disabled:opacity-40',
|
|
66
|
+
'[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-[1.125rem]',
|
|
67
|
+
],
|
|
68
|
+
railSeparator: 'my-1 h-px w-5 shrink-0 rounded-full bg-foreground/15',
|
|
69
|
+
tooltip: [
|
|
70
|
+
'origin-(--transform-origin) rounded-lg bg-foreground px-2 py-1 text-xs font-medium text-background shadow-md',
|
|
71
|
+
'transition-[opacity,scale] duration-150 data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:opacity-0',
|
|
72
|
+
'data-instant:duration-0 motion-reduce:transition-none',
|
|
73
|
+
],
|
|
74
|
+
panel: [
|
|
75
|
+
'relative flex w-72 min-w-0 flex-col overflow-hidden rounded-[22px] bg-popover text-popover-foreground',
|
|
76
|
+
'shadow-[0_10px_30px_-12px_rgb(0_0_0/0.3)] ring-1 ring-foreground/[0.06] dark:ring-foreground/10',
|
|
77
|
+
],
|
|
78
|
+
page: [
|
|
79
|
+
'flex min-h-0 flex-1 flex-col outline-none',
|
|
80
|
+
`transition-[opacity,translate] duration-300 ${EASE} motion-reduce:transition-none`,
|
|
81
|
+
'data-starting-style:opacity-0',
|
|
82
|
+
'data-[activation-direction=down]:data-starting-style:translate-y-3 data-[activation-direction=up]:data-starting-style:-translate-y-3',
|
|
83
|
+
'data-ending-style:hidden',
|
|
84
|
+
],
|
|
85
|
+
viewport: 'min-h-0 flex-1 overscroll-contain outline-none',
|
|
86
|
+
content: 'flex flex-col gap-1 p-3.5',
|
|
87
|
+
scrollbar: 'my-3 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',
|
|
88
|
+
thumb: 'relative flex-1 rounded-full bg-foreground/20',
|
|
89
|
+
section: 'grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 py-1.5',
|
|
90
|
+
sectionTitle: [
|
|
91
|
+
'col-start-1 row-start-1 flex min-h-8 items-center text-left text-[13px] font-medium text-foreground',
|
|
92
|
+
// A section that folds: its title is the button, with a chevron that turns.
|
|
93
|
+
'data-[slot=inspector-section-trigger]:-mx-1.5 data-[slot=inspector-section-trigger]:cursor-pointer data-[slot=inspector-section-trigger]:gap-1.5 data-[slot=inspector-section-trigger]:rounded-lg data-[slot=inspector-section-trigger]:px-1.5',
|
|
94
|
+
'data-[slot=inspector-section-trigger]:outline-none data-[slot=inspector-section-trigger]:hover:bg-foreground/[0.04] data-[slot=inspector-section-trigger]:focus-visible:ring-2 data-[slot=inspector-section-trigger]:focus-visible:ring-ring',
|
|
95
|
+
],
|
|
96
|
+
// Right after the title: up while the section is open, down while it is folded.
|
|
97
|
+
chevron: [
|
|
98
|
+
'size-3.5 shrink-0 rotate-180 text-muted-foreground transition-transform duration-200',
|
|
99
|
+
'in-data-panel-open:rotate-0 motion-reduce:transition-none',
|
|
100
|
+
],
|
|
101
|
+
sectionAction: 'col-start-2 row-start-1 flex items-center gap-1.5',
|
|
102
|
+
sectionContent: [
|
|
103
|
+
'col-span-2 flex flex-col gap-2 pt-2',
|
|
104
|
+
// Folding: the height follows the content, from and to nothing.
|
|
105
|
+
'data-[slot=inspector-section-panel]:h-(--collapsible-panel-height) data-[slot=inspector-section-panel]:overflow-hidden',
|
|
106
|
+
'data-[slot=inspector-section-panel]:transition-[height,opacity] data-[slot=inspector-section-panel]:duration-300',
|
|
107
|
+
'data-[slot=inspector-section-panel]:ease-[cubic-bezier(0.3,1.2,0.4,1)]',
|
|
108
|
+
'data-starting-style:h-0 data-starting-style:opacity-0 data-ending-style:h-0 data-ending-style:opacity-0 motion-reduce:transition-none',
|
|
109
|
+
],
|
|
110
|
+
separator: 'my-2 h-px shrink-0 bg-foreground/[0.08]',
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
const SectionContext = createContext(null);
|
|
114
|
+
/**
|
|
115
|
+
* The rail and the panel side by side. `defaultValue`, or `value` with
|
|
116
|
+
* `onValueChange`, names the page that is open.
|
|
117
|
+
*/
|
|
118
|
+
function InspectorRoot({ className, ...props }) {
|
|
119
|
+
const slots = inspectorVariants();
|
|
120
|
+
return (_jsx(Tooltip.Provider, { delay: 500, closeDelay: 0, children: _jsx(Tabs.Root, { "data-slot": "inspector", orientation: "vertical", className: slots.root({ className }), ...props }) }));
|
|
121
|
+
}
|
|
122
|
+
InspectorRoot.displayName = 'Inspector';
|
|
123
|
+
/** The column of page icons, in a pill of its own beside the panel. */
|
|
124
|
+
function InspectorRail({ className, children, ...props }) {
|
|
125
|
+
const slots = inspectorVariants();
|
|
126
|
+
return (_jsxs(Tabs.List, { "data-slot": "inspector-rail", className: slots.rail({ className }), ...props, children: [children, _jsx(Tabs.Indicator, { "data-slot": "inspector-marker", className: slots.marker(), renderBeforeHydration: true })] }));
|
|
127
|
+
}
|
|
128
|
+
InspectorRail.displayName = 'Inspector.Rail';
|
|
129
|
+
/** One page's icon on the rail. */
|
|
130
|
+
function InspectorTab({ label, className, children, ...props }) {
|
|
131
|
+
const slots = inspectorVariants();
|
|
132
|
+
return (_jsxs(Tooltip.Root, { children: [_jsx(Tooltip.Trigger, { render: _jsx(Tabs.Tab, { "data-slot": "inspector-tab", "aria-label": label, className: slots.tab({ className }), ...props, children: children }) }), _jsx(Tooltip.Portal, { children: _jsx(Tooltip.Positioner, { side: "left", sideOffset: 10, className: "z-50", children: _jsx(Tooltip.Popup, { className: slots.tooltip(), children: label }) }) })] }));
|
|
133
|
+
}
|
|
134
|
+
InspectorTab.displayName = 'Inspector.Tab';
|
|
135
|
+
/** A short line between groups of pages on the rail. */
|
|
136
|
+
function InspectorRailSeparator({ className, ...props }) {
|
|
137
|
+
const slots = inspectorVariants();
|
|
138
|
+
return _jsx("span", { "aria-hidden": true, "data-slot": "inspector-rail-separator", className: slots.railSeparator({ className }), ...props });
|
|
139
|
+
}
|
|
140
|
+
InspectorRailSeparator.displayName = 'Inspector.RailSeparator';
|
|
141
|
+
/**
|
|
142
|
+
* The card the pages open in. Give it a height, such as `h-[32rem]`, and a
|
|
143
|
+
* page longer than that scrolls inside it. Without pages, its children are
|
|
144
|
+
* one page of sections.
|
|
145
|
+
*/
|
|
146
|
+
function InspectorPanel({ className, children, ...props }) {
|
|
147
|
+
const slots = inspectorVariants();
|
|
148
|
+
return (_jsx("div", { "data-slot": "inspector-panel", className: slots.panel({ className }), ...props, children: hasPages(children) ? children : _jsx(Scroll, { children: children }) }));
|
|
149
|
+
}
|
|
150
|
+
InspectorPanel.displayName = 'Inspector.Panel';
|
|
151
|
+
/** Whether the panel's children are pages, rather than sections written straight into it. */
|
|
152
|
+
function hasPages(children) {
|
|
153
|
+
const list = Array.isArray(children) ? children : [children];
|
|
154
|
+
return list.some((child) => typeof child === 'object' &&
|
|
155
|
+
child !== null &&
|
|
156
|
+
'type' in child &&
|
|
157
|
+
child.type === InspectorPage);
|
|
158
|
+
}
|
|
159
|
+
/** The scrolling column of a page, with a thin bar that shows while it moves. */
|
|
160
|
+
function Scroll({ children }) {
|
|
161
|
+
const slots = inspectorVariants();
|
|
162
|
+
return (_jsxs(ScrollArea.Root, { className: "relative flex min-h-0 flex-1 flex-col", children: [_jsx(ScrollArea.Viewport, { className: slots.viewport(), children: _jsx(ScrollArea.Content, { className: slots.content(), children: children }) }), _jsx(ScrollArea.Scrollbar, { orientation: "vertical", className: slots.scrollbar(), children: _jsx(ScrollArea.Thumb, { className: slots.thumb() }) })] }));
|
|
163
|
+
}
|
|
164
|
+
/** What one tab opens: a column of sections that scrolls inside the panel. */
|
|
165
|
+
function InspectorPage({ className, children, ...props }) {
|
|
166
|
+
const slots = inspectorVariants();
|
|
167
|
+
return (_jsx(Tabs.Panel, { "data-slot": "inspector-page", className: slots.page({ className }), ...props, children: _jsx(Scroll, { children: children }) }));
|
|
168
|
+
}
|
|
169
|
+
InspectorPage.displayName = 'Inspector.Page';
|
|
170
|
+
/** A titled group of settings: a title, an action on its line, and the controls under them. */
|
|
171
|
+
function InspectorSection({ collapsible = false, defaultOpen = true, open, onOpenChange, className, children, ...props }) {
|
|
172
|
+
const slots = inspectorVariants();
|
|
173
|
+
if (!collapsible) {
|
|
174
|
+
return (_jsx(SectionContext.Provider, { value: { collapsible: false }, children: _jsx("div", { "data-slot": "inspector-section", className: slots.section({ className }), ...props, children: children }) }));
|
|
175
|
+
}
|
|
176
|
+
return (_jsx(SectionContext.Provider, { value: { collapsible: true }, children: _jsx(Collapsible.Root, { "data-slot": "inspector-section", defaultOpen: defaultOpen, open: open, onOpenChange: onOpenChange ? (next) => onOpenChange(next) : undefined, className: slots.section({ className }), ...props, children: children }) }));
|
|
177
|
+
}
|
|
178
|
+
InspectorSection.displayName = 'Inspector.Section';
|
|
179
|
+
function useSection(part) {
|
|
180
|
+
const section = useContext(SectionContext);
|
|
181
|
+
if (!section)
|
|
182
|
+
throw new Error(`${part} must be used inside an <Inspector.Section>.`);
|
|
183
|
+
return section;
|
|
184
|
+
}
|
|
185
|
+
/** The section's name. In a collapsible section it is the button that folds it, with a chevron. */
|
|
186
|
+
function InspectorSectionTitle({ className, children, ...props }) {
|
|
187
|
+
const { collapsible } = useSection('Inspector.SectionTitle');
|
|
188
|
+
const slots = inspectorVariants();
|
|
189
|
+
if (!collapsible) {
|
|
190
|
+
return (_jsx("h3", { "data-slot": "inspector-section-title", className: slots.sectionTitle({ className }), ...props, children: children }));
|
|
191
|
+
}
|
|
192
|
+
return (_jsx("h3", { className: "col-start-1 row-start-1 m-0", children: _jsxs(Collapsible.Trigger, { "data-slot": "inspector-section-trigger", className: slots.sectionTitle({ className: ['w-full', className] }), children: [children, _jsx("svg", { viewBox: "0 0 16 16", "aria-hidden": true, className: slots.chevron(), fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "m4 10 4-4 4 4" }) })] }) }));
|
|
193
|
+
}
|
|
194
|
+
InspectorSectionTitle.displayName = 'Inspector.SectionTitle';
|
|
195
|
+
/** What sits at the end of the title's line: the switch for the whole section, or a Reset. */
|
|
196
|
+
function InspectorSectionAction({ className, ...props }) {
|
|
197
|
+
useSection('Inspector.SectionAction');
|
|
198
|
+
const slots = inspectorVariants();
|
|
199
|
+
return _jsx("div", { "data-slot": "inspector-section-action", className: slots.sectionAction({ className }), ...props });
|
|
200
|
+
}
|
|
201
|
+
InspectorSectionAction.displayName = 'Inspector.SectionAction';
|
|
202
|
+
/** The controls under the title, stacked with an even gap. */
|
|
203
|
+
function InspectorSectionContent({ className, children, ...props }) {
|
|
204
|
+
const { collapsible } = useSection('Inspector.SectionContent');
|
|
205
|
+
const slots = inspectorVariants();
|
|
206
|
+
if (!collapsible) {
|
|
207
|
+
return (_jsx("div", { "data-slot": "inspector-section-content", className: slots.sectionContent({ className }), ...props, children: children }));
|
|
208
|
+
}
|
|
209
|
+
return (_jsx(Collapsible.Panel, { "data-slot": "inspector-section-panel", className: slots.sectionContent({ className }), style: props.style, children: children }));
|
|
210
|
+
}
|
|
211
|
+
InspectorSectionContent.displayName = 'Inspector.SectionContent';
|
|
212
|
+
/** A hairline between sections that belong to different things. */
|
|
213
|
+
function InspectorSeparator({ className, ...props }) {
|
|
214
|
+
const slots = inspectorVariants();
|
|
215
|
+
return _jsx("div", { role: "separator", "data-slot": "inspector-separator", className: slots.separator({ className }), ...props });
|
|
216
|
+
}
|
|
217
|
+
InspectorSeparator.displayName = 'Inspector.Separator';
|
|
218
|
+
export const Inspector = Object.assign(InspectorRoot, {
|
|
219
|
+
Rail: InspectorRail,
|
|
220
|
+
Tab: InspectorTab,
|
|
221
|
+
RailSeparator: InspectorRailSeparator,
|
|
222
|
+
Panel: InspectorPanel,
|
|
223
|
+
Page: InspectorPage,
|
|
224
|
+
Section: InspectorSection,
|
|
225
|
+
SectionTitle: InspectorSectionTitle,
|
|
226
|
+
SectionAction: InspectorSectionAction,
|
|
227
|
+
SectionContent: InspectorSectionContent,
|
|
228
|
+
Separator: InspectorSeparator,
|
|
229
|
+
});
|
|
230
|
+
export { InspectorRail, InspectorTab, InspectorRailSeparator, InspectorPanel, InspectorPage, InspectorSection, InspectorSectionTitle, InspectorSectionAction, InspectorSectionContent, InspectorSeparator, inspectorVariants, };
|
|
@@ -84,9 +84,16 @@ export interface SheetContentProps extends Omit<ComponentProps<'div'>, 'children
|
|
|
84
84
|
* `full` take that share of the screen, and `full` meets the edges.
|
|
85
85
|
*/
|
|
86
86
|
size?: 'auto' | 'half' | 'full';
|
|
87
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* Show the grabber. On by default for a top or bottom sheet that a swipe
|
|
89
|
+
* can close, and off when `dismissible` is.
|
|
90
|
+
*/
|
|
88
91
|
showGrabber?: boolean;
|
|
89
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Show the round close button in the top trailing corner. Default true.
|
|
94
|
+
* Write a bare `<Sheet.Close />` into the sheet to place it somewhere else,
|
|
95
|
+
* or set this to `false` for a sheet with no close button at all.
|
|
96
|
+
*/
|
|
90
97
|
showClose?: boolean;
|
|
91
98
|
/** Frost the page behind the sheet instead of only dimming it. */
|
|
92
99
|
blur?: boolean;
|
|
@@ -150,13 +157,17 @@ declare namespace SheetFooter {
|
|
|
150
157
|
export interface SheetCloseProps extends ComponentProps<'button'> {
|
|
151
158
|
/**
|
|
152
159
|
* Your own element to close the sheet with, such as a Cancel button.
|
|
153
|
-
* Without it, and without children, this is the round close button
|
|
160
|
+
* Without it, and without children, this is the round close button, drawn
|
|
161
|
+
* where it is written in place of the one in the corner.
|
|
154
162
|
*/
|
|
155
163
|
render?: ReactElement;
|
|
156
|
-
/** Draw the
|
|
164
|
+
/** Draw the round close button. A `Sheet.Close` with nothing inside it already is one. */
|
|
157
165
|
icon?: boolean;
|
|
158
166
|
}
|
|
159
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* Closes the sheet. Works even when `dismissible` is off. Written bare, it is
|
|
169
|
+
* the round close button, and the sheet stops drawing its own in the corner.
|
|
170
|
+
*/
|
|
160
171
|
declare function SheetClose({ render, icon, children, ...props }: SheetCloseProps): import("react").JSX.Element;
|
|
161
172
|
declare namespace SheetClose {
|
|
162
173
|
var displayName: string;
|
|
@@ -46,6 +46,18 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
46
46
|
* Every sheet follows a drag and leaves on a flick toward its own edge. The
|
|
47
47
|
* grabber on a top or bottom sheet says so before anybody tries.
|
|
48
48
|
*
|
|
49
|
+
* With a mouse, the text in a sheet is for reading: a press on the title,
|
|
50
|
+
* the description, the body or the footer selects text the way it does on the
|
|
51
|
+
* page, and the sheet is dragged by its grabber and the padding around the
|
|
52
|
+
* header. A finger still swipes the sheet from anywhere.
|
|
53
|
+
*
|
|
54
|
+
* ## The close button
|
|
55
|
+
*
|
|
56
|
+
* Every sheet has a round close button in its top trailing corner. Write a
|
|
57
|
+
* bare `<Sheet.Close />` somewhere in the sheet to put it there instead, for
|
|
58
|
+
* example first in `Sheet.Header` to stand before the title, and the one in
|
|
59
|
+
* the corner steps aside. `showClose={false}` leaves the sheet with none.
|
|
60
|
+
*
|
|
49
61
|
* ## Slides
|
|
50
62
|
*
|
|
51
63
|
* `Sheet.Slides` takes the place of the body with a column of rounded
|
|
@@ -58,7 +70,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
58
70
|
*/
|
|
59
71
|
import { Drawer } from '@base-ui/react/drawer';
|
|
60
72
|
import { ScrollArea } from '@base-ui/react/scroll-area';
|
|
61
|
-
import { Children, createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
73
|
+
import { Children, createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
62
74
|
import { tv } from 'tailwind-variants';
|
|
63
75
|
/*
|
|
64
76
|
* One curve for every movement: the sheet arriving, the one behind it stepping
|
|
@@ -116,12 +128,24 @@ const sheetVariants = tv({
|
|
|
116
128
|
'before:h-[5px] before:w-9 before:rounded-full before:bg-muted-foreground/35',
|
|
117
129
|
],
|
|
118
130
|
close: [
|
|
119
|
-
'
|
|
131
|
+
'relative inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-full',
|
|
120
132
|
'bg-muted text-muted-foreground outline-none transition-[background-color,color,scale] duration-150',
|
|
121
133
|
'after:absolute after:-inset-1.5 hover:bg-foreground/10 hover:text-foreground active:scale-95',
|
|
122
134
|
'focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none',
|
|
123
135
|
],
|
|
124
|
-
|
|
136
|
+
closeCorner: 'absolute end-4 top-4 z-10',
|
|
137
|
+
/*
|
|
138
|
+
* A close button written into the header shares the title's line: the
|
|
139
|
+
* header becomes three columns, the button takes the first or the last
|
|
140
|
+
* depending on where it was written, and the text keeps the middle.
|
|
141
|
+
*/
|
|
142
|
+
header: [
|
|
143
|
+
'flex shrink-0 flex-col gap-1 px-6 pt-6 pb-3',
|
|
144
|
+
'has-[>[data-slot=sheet-close]]:grid has-[>[data-slot=sheet-close]]:grid-cols-[auto_minmax(0,1fr)_auto] has-[>[data-slot=sheet-close]]:gap-x-3',
|
|
145
|
+
'[&>*]:col-start-2',
|
|
146
|
+
'[&>[data-slot=sheet-close]]:row-span-2 [&>[data-slot=sheet-close]]:row-start-1 [&>[data-slot=sheet-close]]:-my-1 [&>[data-slot=sheet-close]]:self-start',
|
|
147
|
+
'[&>[data-slot=sheet-close]:first-child]:col-start-1 [&>[data-slot=sheet-close]:not(:first-child)]:col-start-3',
|
|
148
|
+
],
|
|
125
149
|
title: 'text-[19px] font-semibold leading-6 tracking-[-0.01em]',
|
|
126
150
|
description: 'text-[15px] leading-5 text-muted-foreground',
|
|
127
151
|
body: 'relative flex min-h-0 flex-1 flex-col',
|
|
@@ -319,14 +343,16 @@ function SheetFrame({ children, open, onOpenChange, defaultOpen = false, side =
|
|
|
319
343
|
stack,
|
|
320
344
|
nested,
|
|
321
345
|
inline: false,
|
|
346
|
+
dismissible,
|
|
322
347
|
showClose: true,
|
|
348
|
+
claimClose: () => () => { },
|
|
323
349
|
register: (child) => {
|
|
324
350
|
opened.current.add(child);
|
|
325
351
|
return () => opened.current.delete(child);
|
|
326
352
|
},
|
|
327
353
|
closeTree: handle.closeTree,
|
|
328
354
|
hasOpenChild: () => [...opened.current].some((child) => child.isOpen()),
|
|
329
|
-
}), [side, stack, nested, handle]);
|
|
355
|
+
}), [side, stack, nested, dismissible, handle]);
|
|
330
356
|
return (_jsx(SheetContext.Provider, { value: context, children: _jsx(Drawer.Root, { open: isOpen, handle: drawerHandle, swipeDirection: SWIPE[side], disablePointerDismissal: !dismissible, onOpenChange: (next, details) => {
|
|
331
357
|
// The primitive has a switch for a press outside and none for
|
|
332
358
|
// Escape or a swipe, so those are refused here.
|
|
@@ -384,8 +410,16 @@ function SheetContent({ children, detached = true, size = 'auto', showGrabber, s
|
|
|
384
410
|
attached: !detached || (vertical && size === 'full'),
|
|
385
411
|
blur,
|
|
386
412
|
});
|
|
387
|
-
|
|
388
|
-
|
|
413
|
+
// Bare close buttons written into the sheet. While there is one, the
|
|
414
|
+
// corner button is not drawn.
|
|
415
|
+
const [placed, setPlaced] = useState(0);
|
|
416
|
+
const claimClose = useCallback(() => {
|
|
417
|
+
setPlaced((count) => count + 1);
|
|
418
|
+
return () => setPlaced((count) => count - 1);
|
|
419
|
+
}, []);
|
|
420
|
+
const corner = showClose && placed === 0;
|
|
421
|
+
const context = useMemo(() => ({ ...sheet, inline, showClose: corner, claimClose }), [sheet, inline, corner, claimClose]);
|
|
422
|
+
const inside = (_jsxs(_Fragment, { children: [(showGrabber ?? (vertical && sheet.dismissible)) ? _jsx("div", { "aria-hidden": true, className: slots.grabber() }) : null, corner ? _jsx(CloseButton, { className: slots.closeCorner() }) : null, children] }));
|
|
389
423
|
if (inline) {
|
|
390
424
|
return (_jsx(SheetContext.Provider, { value: context, children: _jsx("div", { className: slots.popup({ className: ['[transform:none]', className] }), ...props, children: inside }) }));
|
|
391
425
|
}
|
|
@@ -413,7 +447,7 @@ function SheetTitle({ className, ...props }) {
|
|
|
413
447
|
const slots = sheetVariants();
|
|
414
448
|
if (inline)
|
|
415
449
|
return _jsx("h2", { className: slots.title({ className }), ...props });
|
|
416
|
-
return _jsx(Drawer.Title, { className: slots.title({ className }), ...props });
|
|
450
|
+
return _jsx(Drawer.Content, { render: _jsx(Drawer.Title, { className: slots.title({ className }), ...props }) });
|
|
417
451
|
}
|
|
418
452
|
SheetTitle.displayName = 'Sheet.Title';
|
|
419
453
|
/** A sentence under the title, read out after it. */
|
|
@@ -422,7 +456,7 @@ function SheetDescription({ className, ...props }) {
|
|
|
422
456
|
const slots = sheetVariants();
|
|
423
457
|
if (inline)
|
|
424
458
|
return _jsx("p", { className: slots.description({ className }), ...props });
|
|
425
|
-
return _jsx(Drawer.Description, { className: slots.description({ className }), ...props });
|
|
459
|
+
return (_jsx(Drawer.Content, { render: _jsx(Drawer.Description, { className: slots.description({ className }), ...props }) }));
|
|
426
460
|
}
|
|
427
461
|
SheetDescription.displayName = 'Sheet.Description';
|
|
428
462
|
/**
|
|
@@ -430,26 +464,49 @@ SheetDescription.displayName = 'Sheet.Description';
|
|
|
430
464
|
* the scrollbar is a thin drawn thumb that shows while the content is moving.
|
|
431
465
|
*/
|
|
432
466
|
function SheetBody({ className, children, ...props }) {
|
|
467
|
+
const { inline } = useSheet('Sheet.Body');
|
|
433
468
|
const slots = sheetVariants();
|
|
434
|
-
|
|
469
|
+
const content = (_jsx(ScrollArea.Content, { className: slots.bodyContent({ className }), ...props, children: children }));
|
|
470
|
+
return (_jsxs(ScrollArea.Root, { className: slots.body(), children: [_jsx(ScrollArea.Viewport, { className: slots.bodyViewport(), children: inline ? content : _jsx(Drawer.Content, { render: content }) }), _jsx(ScrollArea.Scrollbar, { orientation: "vertical", className: slots.scrollbar(), children: _jsx(ScrollArea.Thumb, { className: slots.thumb() }) })] }));
|
|
435
471
|
}
|
|
436
472
|
SheetBody.displayName = 'Sheet.Body';
|
|
437
473
|
/** The actions, stacked full width at the bottom of the sheet. */
|
|
438
474
|
function SheetFooter({ className, ...props }) {
|
|
475
|
+
const { inline } = useSheet('Sheet.Footer');
|
|
439
476
|
const slots = sheetVariants();
|
|
440
|
-
|
|
477
|
+
const footer = _jsx("div", { className: slots.footer({ className }), ...props });
|
|
478
|
+
return inline ? footer : _jsx(Drawer.Content, { render: footer });
|
|
441
479
|
}
|
|
442
480
|
SheetFooter.displayName = 'Sheet.Footer';
|
|
443
|
-
|
|
481
|
+
const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
|
|
482
|
+
/**
|
|
483
|
+
* Closes the sheet. Works even when `dismissible` is off. Written bare, it is
|
|
484
|
+
* the round close button, and the sheet stops drawing its own in the corner.
|
|
485
|
+
*/
|
|
444
486
|
function SheetClose({ render, icon = false, children, ...props }) {
|
|
487
|
+
const sheet = useSheet('Sheet.Close');
|
|
488
|
+
const bare = icon || (render === undefined && children === undefined);
|
|
489
|
+
const { claimClose } = sheet;
|
|
490
|
+
useIsomorphicLayoutEffect(() => (bare ? claimClose() : undefined), [bare, claimClose]);
|
|
491
|
+
if (bare)
|
|
492
|
+
return _jsx(CloseButton, { ...props });
|
|
493
|
+
if (sheet.inline) {
|
|
494
|
+
return (_jsx("button", { type: "button", "data-slot": "sheet-close", ...props, children: children }));
|
|
495
|
+
}
|
|
496
|
+
return (_jsx(Drawer.Close, { "data-slot": "sheet-close", render: render, ...props, children: children }));
|
|
497
|
+
}
|
|
498
|
+
SheetClose.displayName = 'Sheet.Close';
|
|
499
|
+
/** The round button with a cross in it. */
|
|
500
|
+
function CloseButton({ className, 'aria-label': label = 'Close', ...props }) {
|
|
445
501
|
const { inline } = useSheet('Sheet.Close');
|
|
446
|
-
const
|
|
502
|
+
const slots = sheetVariants();
|
|
503
|
+
const glyph = (_jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": true, className: "size-4", fill: "none", stroke: "currentColor", strokeWidth: 2.4, strokeLinecap: "round", children: _jsx("path", { d: "M6 6l12 12M18 6L6 18" }) }));
|
|
504
|
+
const shared = { 'data-slot': 'sheet-close', 'aria-label': label, className: slots.close({ className }), ...props };
|
|
447
505
|
if (inline) {
|
|
448
|
-
return (_jsx("button", { type: "button", ...
|
|
506
|
+
return (_jsx("button", { type: "button", ...shared, children: glyph }));
|
|
449
507
|
}
|
|
450
|
-
return
|
|
508
|
+
return _jsx(Drawer.Close, { ...shared, children: glyph });
|
|
451
509
|
}
|
|
452
|
-
SheetClose.displayName = 'Sheet.Close';
|
|
453
510
|
const SlidesContext = createContext(null);
|
|
454
511
|
/** The rounded frame of a slide: the slide itself in a deck, inside its row in a column. */
|
|
455
512
|
const frameOf = (slide) => slide.querySelector('[data-slide-frame]') ?? slide;
|