panelui-web 0.9.0 → 0.10.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.
@@ -0,0 +1,185 @@
1
+ import { Toolbar as BaseToolbar } from '@base-ui/react/toolbar';
2
+ import { type ComponentProps, type CSSProperties, type ReactElement, type ReactNode } from 'react';
3
+ export type ToolbarVariant = 'floating' | 'inline';
4
+ export type ToolbarSize = 'sm' | 'md' | 'lg';
5
+ declare const toolbarVariants: import("tailwind-variants").TVReturnType<{
6
+ variant: {
7
+ floating: {
8
+ root: string[];
9
+ };
10
+ inline: {};
11
+ };
12
+ size: {
13
+ sm: {
14
+ root: string;
15
+ };
16
+ md: {
17
+ root: string;
18
+ };
19
+ lg: {
20
+ root: string;
21
+ };
22
+ };
23
+ }, {
24
+ root: string[];
25
+ group: string;
26
+ marker: string[];
27
+ item: string[];
28
+ tool: string;
29
+ separator: string[];
30
+ caret: string[];
31
+ tooltip: string[];
32
+ kbd: string;
33
+ }, undefined, {
34
+ variant: {
35
+ floating: {
36
+ root: string[];
37
+ };
38
+ inline: {};
39
+ };
40
+ size: {
41
+ sm: {
42
+ root: string;
43
+ };
44
+ md: {
45
+ root: string;
46
+ };
47
+ lg: {
48
+ root: string;
49
+ };
50
+ };
51
+ }, {
52
+ root: string[];
53
+ group: string;
54
+ marker: string[];
55
+ item: string[];
56
+ tool: string;
57
+ separator: string[];
58
+ caret: string[];
59
+ tooltip: string[];
60
+ kbd: string;
61
+ }, import("tailwind-variants").TVReturnType<{
62
+ variant: {
63
+ floating: {
64
+ root: string[];
65
+ };
66
+ inline: {};
67
+ };
68
+ size: {
69
+ sm: {
70
+ root: string;
71
+ };
72
+ md: {
73
+ root: string;
74
+ };
75
+ lg: {
76
+ root: string;
77
+ };
78
+ };
79
+ }, {
80
+ root: string[];
81
+ group: string;
82
+ marker: string[];
83
+ item: string[];
84
+ tool: string;
85
+ separator: string[];
86
+ caret: string[];
87
+ tooltip: string[];
88
+ kbd: string;
89
+ }, undefined, unknown, unknown, undefined>>;
90
+ export interface ToolbarProps extends Omit<BaseToolbar.Root.Props, 'className' | 'style'> {
91
+ /** `floating` sits on a raised surface of its own; `inline` draws only the buttons. Default `floating`. */
92
+ variant?: ToolbarVariant;
93
+ /** The size of every button in it. Default `md`. */
94
+ size?: ToolbarSize;
95
+ /**
96
+ * Answer single-key shortcuts anywhere on the page: a key in a tool's or
97
+ * button's `shortcut` chooses that tool or presses that button. Keys typed
98
+ * into a field are left alone. Off by default; turn it on for the one
99
+ * toolbar that owns the page.
100
+ */
101
+ shortcuts?: boolean;
102
+ /** Which side of a button its tooltip opens on. Default below a row and beside a column. */
103
+ tooltipSide?: 'top' | 'bottom' | 'left' | 'right';
104
+ className?: string;
105
+ style?: CSSProperties;
106
+ }
107
+ /** The strip. `orientation="vertical"` stands it up as a rail. */
108
+ declare function ToolbarRoot({ variant, size, shortcuts, tooltipSide, orientation, className, children, ...props }: ToolbarProps): import("react").JSX.Element;
109
+ declare namespace ToolbarRoot {
110
+ var displayName: string;
111
+ }
112
+ export interface ToolbarGroupProps extends Omit<ComponentProps<'div'>, 'defaultValue' | 'onChange' | 'dir'> {
113
+ /** The chosen tool's `value`, when the caller holds it. */
114
+ value?: string;
115
+ /** The tool chosen at first, when the caller does not hold it. */
116
+ defaultValue?: string;
117
+ /** Called with the `value` of the tool that was chosen. */
118
+ onValueChange?: (value: string) => void;
119
+ /** Turns every tool in the group off. */
120
+ disabled?: boolean;
121
+ }
122
+ /**
123
+ * Tools of which exactly one is on: the select tool, the pen, the shape.
124
+ * Choosing the one already on leaves it on.
125
+ */
126
+ declare function ToolbarGroup({ value: valueProp, defaultValue, onValueChange, disabled, className, children, ...props }: ToolbarGroupProps): import("react").JSX.Element;
127
+ declare namespace ToolbarGroup {
128
+ var displayName: string;
129
+ }
130
+ export interface ToolbarToolProps extends Omit<ComponentProps<'button'>, 'value' | 'children'> {
131
+ /** Names the tool in its group's `value`. */
132
+ value: string;
133
+ /** What the tool is called: its tooltip, and what a screen reader reads. */
134
+ label?: string;
135
+ /** The key that chooses it, shown in the tooltip, such as `V`. */
136
+ shortcut?: string;
137
+ /** Usually an icon, and a `Toolbar.Caret` when it has options. */
138
+ children?: ReactNode;
139
+ }
140
+ /** One tool in a `Toolbar.Group`. Choosing it moves the marker onto it. */
141
+ declare function ToolbarTool({ value, label, shortcut, className, children, disabled, ...props }: ToolbarToolProps): import("react").JSX.Element;
142
+ declare namespace ToolbarTool {
143
+ var displayName: string;
144
+ }
145
+ export interface ToolbarButtonProps extends Omit<ComponentProps<'button'>, 'children'> {
146
+ /** What it does: its tooltip, and what a screen reader reads. */
147
+ label?: string;
148
+ /** The key or keys that press it, shown in the tooltip, such as `⌘Z`. A single key is also answered. */
149
+ shortcut?: string;
150
+ /**
151
+ * Another trigger to be the button, such as a `Menu.Trigger`, so a menu
152
+ * opens from the strip and keeps its place in the arrow-key order.
153
+ */
154
+ render?: ReactElement;
155
+ /** Usually an icon. */
156
+ children?: ReactNode;
157
+ }
158
+ /** An action on the strip: undo, zoom, export. It does not stay on. */
159
+ declare function ToolbarButton({ label, shortcut, render, className, children, ...props }: ToolbarButtonProps): import("react").JSX.Element;
160
+ declare namespace ToolbarButton {
161
+ var displayName: string;
162
+ }
163
+ export interface ToolbarSeparatorProps extends Omit<BaseToolbar.Separator.Props, 'className'> {
164
+ className?: string;
165
+ }
166
+ /** A short line between groups of tools that belong apart. */
167
+ declare function ToolbarSeparator({ className, ...props }: ToolbarSeparatorProps): import("react").JSX.Element;
168
+ declare namespace ToolbarSeparator {
169
+ var displayName: string;
170
+ }
171
+ export interface ToolbarCaretProps extends ComponentProps<'span'> {
172
+ }
173
+ /** A small chevron after a tool's icon, saying it opens more choices. */
174
+ declare function ToolbarCaret({ className, children, ...props }: ToolbarCaretProps): import("react").JSX.Element;
175
+ declare namespace ToolbarCaret {
176
+ var displayName: string;
177
+ }
178
+ export declare const Toolbar: typeof ToolbarRoot & {
179
+ Group: typeof ToolbarGroup;
180
+ Tool: typeof ToolbarTool;
181
+ Button: typeof ToolbarButton;
182
+ Separator: typeof ToolbarSeparator;
183
+ Caret: typeof ToolbarCaret;
184
+ };
185
+ export { ToolbarGroup, ToolbarTool, ToolbarButton, ToolbarSeparator, ToolbarCaret, toolbarVariants };
@@ -0,0 +1,270 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Toolbar — a strip of tools and actions for an editor: one tool chosen at a
5
+ * time, and buttons that act at once.
6
+ *
7
+ * ```tsx
8
+ * <Toolbar shortcuts>
9
+ * <Toolbar.Group defaultValue="select" aria-label="Tools">
10
+ * <Toolbar.Tool value="select" label="Select" shortcut="V"><PointerIcon /></Toolbar.Tool>
11
+ * <Toolbar.Tool value="pen" label="Pen" shortcut="P"><PenIcon /></Toolbar.Tool>
12
+ * </Toolbar.Group>
13
+ * <Toolbar.Separator />
14
+ * <Toolbar.Button label="Undo" shortcut="⌘Z"><UndoIcon /></Toolbar.Button>
15
+ * </Toolbar>
16
+ * ```
17
+ *
18
+ * ## One marker, and it travels
19
+ *
20
+ * The chosen tool sits on a filled marker in the primary colour. There is
21
+ * one marker per group and it glides from tool to tool, so changing tools
22
+ * reads as moving a hand along the strip rather than as one button going
23
+ * dark while another lights up.
24
+ *
25
+ * ## Tools and actions are different things
26
+ *
27
+ * `Toolbar.Tool` is a mode: exactly one in a `Toolbar.Group` is on, and it
28
+ * stays on until another is chosen. `Toolbar.Button` is an action, undo or
29
+ * zoom, and leaves nothing behind. Keeping the two apart is what lets the
30
+ * strip say at a glance which mode the editor is in.
31
+ *
32
+ * ## The keyboard
33
+ *
34
+ * Arrow keys move along the strip and Home and End jump to its ends. Give a
35
+ * tool or button a `shortcut` and its tooltip shows the key; set `shortcuts`
36
+ * on the toolbar and a single key press, anywhere on the page outside a text
37
+ * field, chooses that tool or presses that button.
38
+ */
39
+ import { Toggle } from '@base-ui/react/toggle';
40
+ import { ToggleGroup } from '@base-ui/react/toggle-group';
41
+ import { Toolbar as BaseToolbar } from '@base-ui/react/toolbar';
42
+ import { Tooltip } from '@base-ui/react/tooltip';
43
+ import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
44
+ import { tv } from 'tailwind-variants';
45
+ const toolbarVariants = tv({
46
+ slots: {
47
+ root: [
48
+ 'group/toolbar relative inline-flex w-fit items-center gap-0.5 outline-none',
49
+ 'data-[orientation=vertical]:flex-col',
50
+ 'data-disabled:pointer-events-none data-disabled:opacity-50',
51
+ ],
52
+ group: 'relative isolate flex items-center gap-0.5 group-data-[orientation=vertical]/toolbar:flex-col',
53
+ marker: [
54
+ 'pointer-events-none absolute -z-10 rounded-(--toolbar-radius) bg-primary opacity-0 data-ready:opacity-100',
55
+ 'shadow-[0_1px_2px_rgb(0_0_0/0.18),inset_0_1px_0_rgb(255_255_255/0.18)]',
56
+ 'top-(--marker-y) left-(--marker-x) h-(--marker-h) w-(--marker-w)',
57
+ 'data-ready:transition-[left,top,width,height] data-ready:duration-[380ms] data-ready:ease-[cubic-bezier(0.3,1.2,0.4,1)] motion-reduce:transition-none',
58
+ ],
59
+ item: [
60
+ 'relative inline-flex h-(--toolbar-item) min-w-(--toolbar-item) shrink-0 cursor-pointer items-center justify-center gap-0.5 rounded-(--toolbar-radius) px-[calc((var(--toolbar-item)-var(--toolbar-icon))/2)]',
61
+ 'text-muted-foreground outline-none select-none transition-[color,background-color,scale] duration-150',
62
+ 'hover:bg-foreground/[0.06] hover:text-foreground active:scale-[0.94]',
63
+ 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset',
64
+ 'data-disabled:pointer-events-none data-disabled:opacity-40 disabled:pointer-events-none disabled:opacity-40',
65
+ // Menus opened from the strip keep their button marked while open.
66
+ 'data-popup-open:bg-foreground/[0.08] data-popup-open:text-foreground',
67
+ '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-(--toolbar-icon)',
68
+ // A button that is only a caret is the options of the tool before it:
69
+ // a slim chevron against that tool, with no fill of its own.
70
+ 'has-[>[data-slot=toolbar-caret]:only-child]:w-3.5 has-[>[data-slot=toolbar-caret]:only-child]:min-w-0 has-[>[data-slot=toolbar-caret]:only-child]:px-0',
71
+ 'has-[>[data-slot=toolbar-caret]:only-child]:bg-transparent has-[>[data-slot=toolbar-caret]:only-child]:active:scale-100',
72
+ 'has-[>[data-slot=toolbar-caret]:only-child]:data-popup-open:bg-transparent',
73
+ ],
74
+ // A chosen tool sits on the marker; the hover fill would hide it.
75
+ tool: 'data-pressed:bg-transparent data-pressed:text-primary-foreground data-pressed:hover:bg-transparent',
76
+ separator: [
77
+ 'mx-1 h-[calc(var(--toolbar-item)*0.5)] w-px shrink-0 rounded-full bg-foreground/15',
78
+ 'data-[orientation=horizontal]:mx-0 data-[orientation=horizontal]:my-1 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-[calc(var(--toolbar-item)*0.5)]',
79
+ ],
80
+ caret: [
81
+ 'pointer-events-none -me-1 flex size-3 shrink-0 items-center justify-center opacity-60 transition-opacity duration-150 [&_svg]:size-3',
82
+ // On its own it takes its button's colour: muted until the hand is on
83
+ // it or its menu is open.
84
+ 'only:me-0 only:opacity-100 motion-reduce:transition-none',
85
+ ],
86
+ tooltip: [
87
+ 'flex origin-(--transform-origin) items-center gap-2 rounded-lg bg-foreground px-2 py-1 text-xs font-medium text-background shadow-md',
88
+ 'transition-[opacity,scale] duration-150 data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:opacity-0',
89
+ 'data-instant:duration-0 motion-reduce:transition-none',
90
+ ],
91
+ kbd: 'rounded bg-background/15 px-1 font-sans text-[11px] tabular-nums text-background/80',
92
+ },
93
+ variants: {
94
+ variant: {
95
+ floating: {
96
+ root: [
97
+ 'rounded-[calc(var(--toolbar-radius)+var(--toolbar-pad))] bg-popover p-(--toolbar-pad) text-popover-foreground',
98
+ 'shadow-[0_10px_30px_-12px_rgb(0_0_0/0.3)] ring-1 ring-foreground/[0.06] dark:ring-foreground/10',
99
+ ],
100
+ },
101
+ inline: {},
102
+ },
103
+ size: {
104
+ sm: { root: '[--toolbar-item:1.75rem] [--toolbar-icon:0.875rem] [--toolbar-radius:0.5rem] [--toolbar-pad:0.25rem]' },
105
+ md: { root: '[--toolbar-item:2.25rem] [--toolbar-icon:1.125rem] [--toolbar-radius:0.75rem] [--toolbar-pad:0.3125rem]' },
106
+ lg: { root: '[--toolbar-item:2.75rem] [--toolbar-icon:1.25rem] [--toolbar-radius:0.875rem] [--toolbar-pad:0.375rem]' },
107
+ },
108
+ },
109
+ defaultVariants: { variant: 'floating', size: 'md' },
110
+ });
111
+ const ToolbarContext = createContext(null);
112
+ function useToolbar(part) {
113
+ const context = useContext(ToolbarContext);
114
+ if (!context)
115
+ throw new Error(`${part} must be used inside a <Toolbar>.`);
116
+ return context;
117
+ }
118
+ /** Whether a key press belongs to something being typed into. */
119
+ function typing(target) {
120
+ if (!(target instanceof HTMLElement))
121
+ return false;
122
+ return Boolean(target.closest('input, textarea, select, [contenteditable=""], [contenteditable="true"], [role="textbox"]'));
123
+ }
124
+ /** The key a shortcut is pressed with, when it is one key on its own: "V" is, "⌘Z" is not. */
125
+ function plainKey(shortcut) {
126
+ return [...shortcut].length === 1 && !'⌘⌥⌃⇧'.includes(shortcut) ? shortcut.toLowerCase() : null;
127
+ }
128
+ const TooltipSideContext = createContext('bottom');
129
+ /** The strip. `orientation="vertical"` stands it up as a rail. */
130
+ function ToolbarRoot({ variant, size, shortcuts = false, tooltipSide, orientation = 'horizontal', className, children, ...props }) {
131
+ const slots = toolbarVariants({ variant, size });
132
+ const bindings = useRef(new Map());
133
+ useEffect(() => {
134
+ if (!shortcuts)
135
+ return;
136
+ const onKey = (event) => {
137
+ if (event.metaKey || event.ctrlKey || event.altKey || event.defaultPrevented || typing(event.target))
138
+ return;
139
+ const run = bindings.current.get(event.key.toLowerCase());
140
+ if (!run)
141
+ return;
142
+ event.preventDefault();
143
+ run();
144
+ };
145
+ document.addEventListener('keydown', onKey);
146
+ return () => document.removeEventListener('keydown', onKey);
147
+ }, [shortcuts]);
148
+ const context = useMemo(() => ({
149
+ orientation,
150
+ bind: (key, run) => {
151
+ bindings.current.set(key, run);
152
+ return () => {
153
+ if (bindings.current.get(key) === run)
154
+ bindings.current.delete(key);
155
+ };
156
+ },
157
+ }), [orientation]);
158
+ return (_jsx(ToolbarContext.Provider, { value: context, children: _jsx(TooltipSideContext.Provider, { value: tooltipSide ?? (orientation === 'vertical' ? 'right' : 'bottom'), children: _jsx(Tooltip.Provider, { delay: 500, closeDelay: 0, children: _jsx(BaseToolbar.Root, { "data-slot": "toolbar", orientation: orientation, className: slots.root({ className }), ...props, children: children }) }) }) }));
159
+ }
160
+ ToolbarRoot.displayName = 'Toolbar';
161
+ const GroupContext = createContext(null);
162
+ /**
163
+ * Tools of which exactly one is on: the select tool, the pen, the shape.
164
+ * Choosing the one already on leaves it on.
165
+ */
166
+ function ToolbarGroup({ value: valueProp, defaultValue, onValueChange, disabled, className, children, ...props }) {
167
+ const { orientation } = useToolbar('Toolbar.Group');
168
+ const slots = toolbarVariants();
169
+ const [inner, setInner] = useState(defaultValue ?? '');
170
+ const value = valueProp ?? inner;
171
+ const live = useRef({ onValueChange, controlled: valueProp !== undefined });
172
+ live.current = { onValueChange, controlled: valueProp !== undefined };
173
+ const choose = useCallback((next) => {
174
+ if (!live.current.controlled)
175
+ setInner(next);
176
+ live.current.onValueChange?.(next);
177
+ }, []);
178
+ // The marker follows the chosen tool, measured where it sits in the group.
179
+ const group = useRef(null);
180
+ const marker = useRef(null);
181
+ useIsomorphicLayoutEffect(() => {
182
+ const box = group.current;
183
+ const mark = marker.current;
184
+ if (!box || !mark)
185
+ return;
186
+ const place = () => {
187
+ const tool = box.querySelector(':scope > [data-pressed], :scope > * > [data-pressed]');
188
+ if (!tool) {
189
+ mark.removeAttribute('data-ready');
190
+ return;
191
+ }
192
+ mark.style.setProperty('--marker-x', `${tool.offsetLeft}px`);
193
+ mark.style.setProperty('--marker-y', `${tool.offsetTop}px`);
194
+ mark.style.setProperty('--marker-w', `${tool.offsetWidth}px`);
195
+ mark.style.setProperty('--marker-h', `${tool.offsetHeight}px`);
196
+ // Shown only once placed, so it never slides in from the corner.
197
+ requestAnimationFrame(() => mark.setAttribute('data-ready', ''));
198
+ };
199
+ place();
200
+ const observer = new ResizeObserver(place);
201
+ observer.observe(box);
202
+ return () => observer.disconnect();
203
+ }, [value]);
204
+ return (_jsx(GroupContext.Provider, { value: useMemo(() => ({ choose }), [choose]), children: _jsxs(ToggleGroup, { ref: group, "data-slot": "toolbar-group", value: value ? [value] : [], onValueChange: (next) => {
205
+ // A tool cannot be switched off by choosing it again.
206
+ const picked = next.find((each) => each !== value) ?? next[0];
207
+ if (picked && picked !== value)
208
+ choose(picked);
209
+ }, orientation: orientation, disabled: disabled, className: slots.group({ className }), ...props, children: [_jsx("span", { ref: marker, "aria-hidden": true, "data-slot": "toolbar-marker", className: slots.marker() }), children] }) }));
210
+ }
211
+ ToolbarGroup.displayName = 'Toolbar.Group';
212
+ const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
213
+ /** Wraps a button in its tooltip, when it has something to say. */
214
+ function WithTooltip({ label, shortcut, trigger }) {
215
+ const side = useContext(TooltipSideContext);
216
+ const slots = toolbarVariants();
217
+ if (!label)
218
+ return trigger;
219
+ return (_jsxs(Tooltip.Root, { children: [_jsx(Tooltip.Trigger, { render: trigger }), _jsx(Tooltip.Portal, { children: _jsx(Tooltip.Positioner, { side: side, sideOffset: 10, className: "z-50", children: _jsxs(Tooltip.Popup, { "data-slot": "toolbar-tooltip", className: slots.tooltip(), children: [label, shortcut ? _jsx("kbd", { className: slots.kbd(), children: shortcut }) : null] }) }) })] }));
220
+ }
221
+ /** Answers the toolbar's single-key shortcut, when there is one. */
222
+ function useShortcut(shortcut, run) {
223
+ const { bind } = useToolbar('A toolbar item');
224
+ const latest = useRef(run);
225
+ latest.current = run;
226
+ useEffect(() => {
227
+ const key = shortcut ? plainKey(shortcut) : null;
228
+ if (!key)
229
+ return;
230
+ return bind(key, () => latest.current());
231
+ }, [shortcut, bind]);
232
+ }
233
+ /** One tool in a `Toolbar.Group`. Choosing it moves the marker onto it. */
234
+ function ToolbarTool({ value, label, shortcut, className, children, disabled, ...props }) {
235
+ const group = useContext(GroupContext);
236
+ if (!group)
237
+ throw new Error('Toolbar.Tool must be used inside a <Toolbar.Group>.');
238
+ const slots = toolbarVariants();
239
+ useShortcut(disabled ? undefined : shortcut, () => group.choose(value));
240
+ return (_jsx(WithTooltip, { label: label, shortcut: shortcut, trigger: _jsx(BaseToolbar.Button, { "data-slot": "toolbar-tool", "aria-label": label, disabled: disabled, className: slots.item({ className: [slots.tool(), className] }), render: _jsx(Toggle, { value: value }), ...props, children: children }) }));
241
+ }
242
+ ToolbarTool.displayName = 'Toolbar.Tool';
243
+ /** An action on the strip: undo, zoom, export. It does not stay on. */
244
+ function ToolbarButton({ label, shortcut, render, className, children, ...props }) {
245
+ const slots = toolbarVariants();
246
+ const self = useRef(null);
247
+ useShortcut(props.disabled ? undefined : shortcut, () => self.current?.click());
248
+ return (_jsx(WithTooltip, { label: label, shortcut: shortcut, trigger: _jsx(BaseToolbar.Button, { ref: self, "data-slot": "toolbar-button", "aria-label": label, render: render, className: slots.item({ className }), ...props, children: children }) }));
249
+ }
250
+ ToolbarButton.displayName = 'Toolbar.Button';
251
+ /** A short line between groups of tools that belong apart. */
252
+ function ToolbarSeparator({ className, ...props }) {
253
+ const slots = toolbarVariants();
254
+ return _jsx(BaseToolbar.Separator, { "data-slot": "toolbar-separator", className: slots.separator({ className }), ...props });
255
+ }
256
+ ToolbarSeparator.displayName = 'Toolbar.Separator';
257
+ /** A small chevron after a tool's icon, saying it opens more choices. */
258
+ function ToolbarCaret({ className, children, ...props }) {
259
+ const slots = toolbarVariants();
260
+ return (_jsx("span", { "aria-hidden": true, "data-slot": "toolbar-caret", className: slots.caret({ className }), ...props, children: children ?? (_jsx("svg", { viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M3.5 4.75 6 7.25l2.5-2.5" }) })) }));
261
+ }
262
+ ToolbarCaret.displayName = 'Toolbar.Caret';
263
+ export const Toolbar = Object.assign(ToolbarRoot, {
264
+ Group: ToolbarGroup,
265
+ Tool: ToolbarTool,
266
+ Button: ToolbarButton,
267
+ Separator: ToolbarSeparator,
268
+ Caret: ToolbarCaret,
269
+ });
270
+ export { ToolbarGroup, ToolbarTool, ToolbarButton, ToolbarSeparator, ToolbarCaret, toolbarVariants };
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Workbench — the frame of an editor: a canvas with the work on it, a
3
+ * toolbar floating over it, an inspector beside it and a timeline under it.
4
+ *
5
+ * ```tsx
6
+ * <Workbench className="h-[640px]">
7
+ * <Workbench.Bar><Toolbar>…</Toolbar></Workbench.Bar>
8
+ * <Workbench.Canvas>
9
+ * <Workbench.Artboard aspect="16 / 9">…</Workbench.Artboard>
10
+ * </Workbench.Canvas>
11
+ * <Workbench.Inspector><Inspector>…</Inspector></Workbench.Inspector>
12
+ * <Workbench.Timeline><Timeline>…</Timeline></Workbench.Timeline>
13
+ * </Workbench>
14
+ * ```
15
+ *
16
+ * ## The work is in the middle
17
+ *
18
+ * Everything else stands round the canvas as a panel of its own, floating
19
+ * a few pixels clear of its neighbours, so the work reads as the thing on
20
+ * the desk and the tools as things beside it. The artboard sits centred on
21
+ * a field of dots at the largest size its shape fits.
22
+ *
23
+ * ## Focus clears the desk
24
+ *
25
+ * In focus the sidebar, the inspector and the timeline slide away and the
26
+ * canvas takes the room they leave. `\` toggles it from the keyboard, and
27
+ * `useWorkbench()` gives a button of your own the same switch.
28
+ *
29
+ * ## On a narrow screen the inspector becomes a sheet
30
+ *
31
+ * Below `compactBelow` pixels wide there is no room for a column beside the
32
+ * canvas. The inspector then opens as a sheet from the side, and
33
+ * `Workbench.InspectorTrigger` is the button that opens it. The width that
34
+ * counts is the workbench's own, not the window's, so it works in a pane.
35
+ */
36
+ import { type ComponentProps, type ReactNode } from 'react';
37
+ declare const workbenchVariants: import("tailwind-variants").TVReturnType<{
38
+ [key: string]: {
39
+ [key: string]: import("tailwind-merge").ClassNameValue | {
40
+ root?: import("tailwind-merge").ClassNameValue;
41
+ canvas?: import("tailwind-merge").ClassNameValue;
42
+ bar?: import("tailwind-merge").ClassNameValue;
43
+ resize?: import("tailwind-merge").ClassNameValue;
44
+ trigger?: import("tailwind-merge").ClassNameValue;
45
+ inspector?: import("tailwind-merge").ClassNameValue;
46
+ timeline?: import("tailwind-merge").ClassNameValue;
47
+ sidebar?: import("tailwind-merge").ClassNameValue;
48
+ artboard?: import("tailwind-merge").ClassNameValue;
49
+ sheetBody?: import("tailwind-merge").ClassNameValue;
50
+ };
51
+ };
52
+ } | {
53
+ [x: string]: {
54
+ [x: string]: import("tailwind-merge").ClassNameValue | {
55
+ root?: import("tailwind-merge").ClassNameValue;
56
+ canvas?: import("tailwind-merge").ClassNameValue;
57
+ bar?: import("tailwind-merge").ClassNameValue;
58
+ resize?: import("tailwind-merge").ClassNameValue;
59
+ trigger?: import("tailwind-merge").ClassNameValue;
60
+ inspector?: import("tailwind-merge").ClassNameValue;
61
+ timeline?: import("tailwind-merge").ClassNameValue;
62
+ sidebar?: import("tailwind-merge").ClassNameValue;
63
+ artboard?: import("tailwind-merge").ClassNameValue;
64
+ sheetBody?: import("tailwind-merge").ClassNameValue;
65
+ };
66
+ };
67
+ } | {}, {
68
+ root: string[];
69
+ sidebar: string[];
70
+ canvas: string[];
71
+ artboard: string[];
72
+ bar: string;
73
+ inspector: string[];
74
+ timeline: string[];
75
+ resize: string[];
76
+ trigger: string[];
77
+ sheetBody: string[];
78
+ }, undefined, {
79
+ [key: string]: {
80
+ [key: string]: import("tailwind-merge").ClassNameValue | {
81
+ root?: import("tailwind-merge").ClassNameValue;
82
+ canvas?: import("tailwind-merge").ClassNameValue;
83
+ bar?: import("tailwind-merge").ClassNameValue;
84
+ resize?: import("tailwind-merge").ClassNameValue;
85
+ trigger?: import("tailwind-merge").ClassNameValue;
86
+ inspector?: import("tailwind-merge").ClassNameValue;
87
+ timeline?: import("tailwind-merge").ClassNameValue;
88
+ sidebar?: import("tailwind-merge").ClassNameValue;
89
+ artboard?: import("tailwind-merge").ClassNameValue;
90
+ sheetBody?: import("tailwind-merge").ClassNameValue;
91
+ };
92
+ };
93
+ } | {}, {
94
+ root: string[];
95
+ sidebar: string[];
96
+ canvas: string[];
97
+ artboard: string[];
98
+ bar: string;
99
+ inspector: string[];
100
+ timeline: string[];
101
+ resize: string[];
102
+ trigger: string[];
103
+ sheetBody: string[];
104
+ }, import("tailwind-variants").TVReturnType<unknown, {
105
+ root: string[];
106
+ sidebar: string[];
107
+ canvas: string[];
108
+ artboard: string[];
109
+ bar: string;
110
+ inspector: string[];
111
+ timeline: string[];
112
+ resize: string[];
113
+ trigger: string[];
114
+ sheetBody: string[];
115
+ }, undefined, unknown, unknown, undefined>>;
116
+ /**
117
+ * The workbench's state, for controls of your own: a focus button in the
118
+ * toolbar, a button that opens the inspector on a narrow screen.
119
+ */
120
+ export declare function useWorkbench(): {
121
+ focus: boolean;
122
+ setFocus: (focus: boolean) => void;
123
+ compact: boolean;
124
+ inspectorOpen: boolean;
125
+ setInspectorOpen: (open: boolean) => void;
126
+ };
127
+ export interface WorkbenchProps extends ComponentProps<'div'> {
128
+ /** Whether the panels are cleared away, when the caller holds it. */
129
+ focus?: boolean;
130
+ /** Whether it starts in focus. Default false. */
131
+ defaultFocus?: boolean;
132
+ /** Called when focus turns on or off. */
133
+ onFocusChange?: (focus: boolean) => void;
134
+ /** Toggle focus with the `\` key while nothing is being typed. Default true. */
135
+ focusShortcut?: boolean;
136
+ /** Below this many pixels wide, the inspector opens as a sheet. Default 760. */
137
+ compactBelow?: number;
138
+ /** The inspector column's width in pixels. Default 348, a rail and a panel. */
139
+ inspectorWidth?: number;
140
+ /** The sidebar's width in pixels. Default 240. */
141
+ sidebarWidth?: number;
142
+ /** The timeline's height in pixels to start at. Default 232. */
143
+ defaultTimelineHeight?: number;
144
+ }
145
+ /** The editor's frame. Give it a height, such as `h-[640px]` or `h-dvh`. */
146
+ declare function WorkbenchRoot({ focus: focusProp, defaultFocus, onFocusChange, focusShortcut, compactBelow, inspectorWidth, sidebarWidth, defaultTimelineHeight, className, style, children, ...props }: WorkbenchProps): import("react").JSX.Element;
147
+ declare namespace WorkbenchRoot {
148
+ var displayName: string;
149
+ }
150
+ export interface WorkbenchBarProps extends ComponentProps<'div'> {
151
+ }
152
+ /** The strip floating at the top of the canvas, for a toolbar. */
153
+ declare function WorkbenchBar({ className, ...props }: WorkbenchBarProps): import("react").JSX.Element;
154
+ declare namespace WorkbenchBar {
155
+ var displayName: string;
156
+ }
157
+ export interface WorkbenchSidebarProps extends ComponentProps<'aside'> {
158
+ }
159
+ /** A column before the canvas, for layers, pages or assets. It is hidden on a narrow screen and in focus. */
160
+ declare function WorkbenchSidebar({ className, ...props }: WorkbenchSidebarProps): import("react").JSX.Element;
161
+ declare namespace WorkbenchSidebar {
162
+ var displayName: string;
163
+ }
164
+ export interface WorkbenchCanvasProps extends ComponentProps<'div'> {
165
+ }
166
+ /** The stage in the middle: a field of dots with the work centred on it. */
167
+ declare function WorkbenchCanvas({ className, ...props }: WorkbenchCanvasProps): import("react").JSX.Element;
168
+ declare namespace WorkbenchCanvas {
169
+ var displayName: string;
170
+ }
171
+ export interface WorkbenchArtboardProps extends ComponentProps<'div'> {
172
+ /** The work's shape, as a CSS aspect ratio such as `16 / 9` or `4 / 5`. Default `16 / 9`. */
173
+ aspect?: string;
174
+ }
175
+ /** The work itself: as large as its shape fits on the canvas, clear of the floating bar. */
176
+ declare function WorkbenchArtboard({ aspect, className, style, ...props }: WorkbenchArtboardProps): import("react").JSX.Element;
177
+ declare namespace WorkbenchArtboard {
178
+ var displayName: string;
179
+ }
180
+ export interface WorkbenchInspectorProps extends ComponentProps<'div'> {
181
+ /** The sheet's heading on a narrow screen, read out when it opens. Default `Inspector`. */
182
+ title?: string;
183
+ children?: ReactNode;
184
+ }
185
+ /**
186
+ * The column after the canvas, for an `Inspector`. On a narrow screen the
187
+ * same children open in a sheet from the side instead.
188
+ */
189
+ declare function WorkbenchInspector({ title, className, children, ...props }: WorkbenchInspectorProps): import("react").JSX.Element;
190
+ declare namespace WorkbenchInspector {
191
+ var displayName: string;
192
+ }
193
+ export interface WorkbenchInspectorTriggerProps extends ComponentProps<'button'> {
194
+ }
195
+ /**
196
+ * The button that opens the inspector's sheet on a narrow screen. It is
197
+ * only there when the inspector is a sheet, so put it where it belongs on
198
+ * either size: in the bar, beside the tools.
199
+ */
200
+ declare function WorkbenchInspectorTrigger({ className, children, onClick, ...props }: WorkbenchInspectorTriggerProps): import("react").JSX.Element | null;
201
+ declare namespace WorkbenchInspectorTrigger {
202
+ var displayName: string;
203
+ }
204
+ export interface WorkbenchTimelineProps extends ComponentProps<'div'> {
205
+ /** Let its top edge be dragged to make it taller or shorter. Default true. */
206
+ resizable?: boolean;
207
+ /** The shortest it can be dragged to, in pixels. Default 140. */
208
+ minHeight?: number;
209
+ }
210
+ /** The row under everything, for a `Timeline`. Its top edge drags to resize it. */
211
+ declare function WorkbenchTimeline({ resizable, minHeight, className, children, ...props }: WorkbenchTimelineProps): import("react").JSX.Element;
212
+ declare namespace WorkbenchTimeline {
213
+ var displayName: string;
214
+ }
215
+ export declare const Workbench: typeof WorkbenchRoot & {
216
+ Bar: typeof WorkbenchBar;
217
+ Sidebar: typeof WorkbenchSidebar;
218
+ Canvas: typeof WorkbenchCanvas;
219
+ Artboard: typeof WorkbenchArtboard;
220
+ Inspector: typeof WorkbenchInspector;
221
+ InspectorTrigger: typeof WorkbenchInspectorTrigger;
222
+ Timeline: typeof WorkbenchTimeline;
223
+ };
224
+ export { WorkbenchBar, WorkbenchSidebar, WorkbenchCanvas, WorkbenchArtboard, WorkbenchInspector, WorkbenchInspectorTrigger, WorkbenchTimeline, workbenchVariants, };