@burtson-labs/ui 0.8.1 → 0.9.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/README.md +1 -1
- package/dist/components/composer.d.ts +12 -2
- package/dist/components/composer.js +2 -2
- package/dist/components/composer.js.map +1 -1
- package/dist/components/context-menu.d.ts +30 -0
- package/dist/components/context-menu.js +131 -0
- package/dist/components/context-menu.js.map +1 -0
- package/dist/components/dropdown-menu.js +5 -6
- package/dist/components/dropdown-menu.js.map +1 -1
- package/dist/components/editor-tabs.d.ts +47 -0
- package/dist/components/editor-tabs.js +221 -0
- package/dist/components/editor-tabs.js.map +1 -0
- package/dist/components/menubar.d.ts +30 -0
- package/dist/components/menubar.js +140 -0
- package/dist/components/menubar.js.map +1 -0
- package/dist/components/onboarding-checklist.d.ts +26 -0
- package/dist/components/onboarding-checklist.js +106 -0
- package/dist/components/onboarding-checklist.js.map +1 -0
- package/dist/components/popover.d.ts +2 -0
- package/dist/components/popover.js +3 -1
- package/dist/components/popover.js.map +1 -1
- package/dist/components/resizable.d.ts +33 -0
- package/dist/components/resizable.js +54 -0
- package/dist/components/resizable.js.map +1 -0
- package/dist/components/tour.d.ts +96 -0
- package/dist/components/tour.js +367 -0
- package/dist/components/tour.js.map +1 -0
- package/dist/components/tree-view.d.ts +76 -0
- package/dist/components/tree-view.js +286 -0
- package/dist/components/tree-view.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +10 -3
- package/dist/styles.css +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import X from "@burtson-labs/icons/react/x";
|
|
5
|
+
//#region src/components/editor-tabs.tsx
|
|
6
|
+
/**
|
|
7
|
+
* The strip of open files above an editor. One tab stop; Left/Right/Home/End
|
|
8
|
+
* move focus, Enter or Space opens, Delete asks to close, Ctrl+Shift+PageUp
|
|
9
|
+
* and PageDown move the tab. Middle-click closes. The strip scrolls when the
|
|
10
|
+
* tabs overflow and keeps the active tab in view.
|
|
11
|
+
*/
|
|
12
|
+
function EditorTabs({ tabs, activeId, onActiveChange, onClose, onMove, actions, onTabContextMenu, className, "aria-label": ariaLabel, ...props }) {
|
|
13
|
+
const baseId = React.useId();
|
|
14
|
+
const tabRefs = React.useRef(/* @__PURE__ */ new Map());
|
|
15
|
+
const listRef = React.useRef(null);
|
|
16
|
+
const [focusedId, setFocusedId] = React.useState(null);
|
|
17
|
+
const [dragId, setDragId] = React.useState(null);
|
|
18
|
+
const [dropGap, setDropGap] = React.useState(null);
|
|
19
|
+
const dragged = React.useRef(false);
|
|
20
|
+
const dragCleanup = React.useRef(null);
|
|
21
|
+
React.useEffect(() => () => dragCleanup.current?.(), []);
|
|
22
|
+
const lastIds = React.useRef([]);
|
|
23
|
+
const hadFocus = React.useRef(false);
|
|
24
|
+
const ids = tabs.map((t) => t.id);
|
|
25
|
+
const idsKey = ids.join("\0");
|
|
26
|
+
const tabStop = (focusedId && ids.includes(focusedId) ? focusedId : null) ?? (activeId && ids.includes(activeId) ? activeId : null) ?? ids[0] ?? null;
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
if (activeId) tabRefs.current.get(activeId)?.scrollIntoView?.({
|
|
29
|
+
block: "nearest",
|
|
30
|
+
inline: "nearest"
|
|
31
|
+
});
|
|
32
|
+
}, [activeId]);
|
|
33
|
+
React.useLayoutEffect(() => {
|
|
34
|
+
if (focusedId && !ids.includes(focusedId)) {
|
|
35
|
+
const next = (activeId && ids.includes(activeId) ? activeId : null) ?? ids[Math.min(Math.max(0, lastIds.current.indexOf(focusedId)), ids.length - 1)] ?? null;
|
|
36
|
+
setFocusedId(next);
|
|
37
|
+
const active = document.activeElement;
|
|
38
|
+
if (next && hadFocus.current && (!active || active === document.body)) tabRefs.current.get(next)?.focus();
|
|
39
|
+
}
|
|
40
|
+
lastIds.current = ids;
|
|
41
|
+
}, [
|
|
42
|
+
focusedId,
|
|
43
|
+
idsKey,
|
|
44
|
+
activeId
|
|
45
|
+
]);
|
|
46
|
+
/** Which gap the pointer is over, measured from the rendered tabs. */
|
|
47
|
+
const gapAt = (clientX) => {
|
|
48
|
+
const els = Array.from(listRef.current?.querySelectorAll("[data-slot=\"editor-tab\"]") ?? []);
|
|
49
|
+
const rtl = listRef.current ? getComputedStyle(listRef.current).direction === "rtl" : false;
|
|
50
|
+
for (let i = 0; i < els.length; i++) {
|
|
51
|
+
const rect = els[i].getBoundingClientRect();
|
|
52
|
+
const mid = rect.left + rect.width / 2;
|
|
53
|
+
if (rtl ? clientX > mid : clientX < mid) return i;
|
|
54
|
+
}
|
|
55
|
+
return els.length;
|
|
56
|
+
};
|
|
57
|
+
const startDrag = (event, id) => {
|
|
58
|
+
if (!onMove || event.button !== 0) return;
|
|
59
|
+
dragCleanup.current?.();
|
|
60
|
+
dragged.current = false;
|
|
61
|
+
const downX = event.clientX;
|
|
62
|
+
const move = (e) => {
|
|
63
|
+
if (!dragged.current && Math.abs(e.clientX - downX) < 4) return;
|
|
64
|
+
if (!dragged.current) {
|
|
65
|
+
dragged.current = true;
|
|
66
|
+
setDragId(id);
|
|
67
|
+
}
|
|
68
|
+
setDropGap(gapAt(e.clientX));
|
|
69
|
+
};
|
|
70
|
+
const stop = (e) => {
|
|
71
|
+
window.removeEventListener("pointermove", move);
|
|
72
|
+
window.removeEventListener("pointerup", up);
|
|
73
|
+
window.removeEventListener("pointercancel", cancel);
|
|
74
|
+
dragCleanup.current = null;
|
|
75
|
+
if (e && dragged.current) {
|
|
76
|
+
const from = lastIds.current.indexOf(id);
|
|
77
|
+
const gap = gapAt(e.clientX);
|
|
78
|
+
const to = gap > from ? gap - 1 : gap;
|
|
79
|
+
if (from !== -1 && to !== from) onMove(id, to);
|
|
80
|
+
}
|
|
81
|
+
setDragId(null);
|
|
82
|
+
setDropGap(null);
|
|
83
|
+
};
|
|
84
|
+
const up = (e) => stop(e);
|
|
85
|
+
const cancel = () => stop(null);
|
|
86
|
+
window.addEventListener("pointermove", move);
|
|
87
|
+
window.addEventListener("pointerup", up);
|
|
88
|
+
window.addEventListener("pointercancel", cancel);
|
|
89
|
+
dragCleanup.current = () => stop(null);
|
|
90
|
+
};
|
|
91
|
+
const focusTab = (id) => {
|
|
92
|
+
if (!id) return;
|
|
93
|
+
setFocusedId(id);
|
|
94
|
+
tabRefs.current.get(id)?.focus();
|
|
95
|
+
};
|
|
96
|
+
const onKeyDown = (event, tab) => {
|
|
97
|
+
const i = ids.indexOf(tab.id);
|
|
98
|
+
const [prev, next] = (listRef.current ? getComputedStyle(listRef.current).direction === "rtl" : false) ? ["ArrowRight", "ArrowLeft"] : ["ArrowLeft", "ArrowRight"];
|
|
99
|
+
let handled = true;
|
|
100
|
+
if (event.ctrlKey && event.shiftKey && (event.key === "PageUp" || event.key === "PageDown")) {
|
|
101
|
+
const to = event.key === "PageUp" ? i - 1 : i + 1;
|
|
102
|
+
if (onMove && to >= 0 && to < ids.length) onMove(tab.id, to);
|
|
103
|
+
} else if (event.key === prev) focusTab(ids[(i - 1 + ids.length) % ids.length]);
|
|
104
|
+
else if (event.key === next) focusTab(ids[(i + 1) % ids.length]);
|
|
105
|
+
else if (event.key === "Home") focusTab(ids[0]);
|
|
106
|
+
else if (event.key === "End") focusTab(ids[ids.length - 1]);
|
|
107
|
+
else if (event.key === "Delete" && tab.closeable !== false && onClose) onClose(tab.id);
|
|
108
|
+
else handled = false;
|
|
109
|
+
if (handled) event.preventDefault();
|
|
110
|
+
};
|
|
111
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
112
|
+
"data-slot": "editor-tabs",
|
|
113
|
+
className: cn("flex h-9 min-w-0 items-stretch border-b bg-surface-muted", className),
|
|
114
|
+
...props,
|
|
115
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
116
|
+
ref: listRef,
|
|
117
|
+
role: "tablist",
|
|
118
|
+
"aria-label": ariaLabel,
|
|
119
|
+
"aria-orientation": "horizontal",
|
|
120
|
+
className: "flex min-w-0 flex-1 items-stretch overflow-x-auto [scrollbar-width:thin]",
|
|
121
|
+
onFocus: () => hadFocus.current = true,
|
|
122
|
+
onBlur: (event) => {
|
|
123
|
+
if (!listRef.current?.contains(event.relatedTarget)) hadFocus.current = false;
|
|
124
|
+
},
|
|
125
|
+
children: tabs.map((tab, i) => {
|
|
126
|
+
const active = tab.id === activeId;
|
|
127
|
+
const closeable = tab.closeable !== false && Boolean(onClose);
|
|
128
|
+
const tabId = `${baseId}-tab-${i}`;
|
|
129
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
130
|
+
"data-slot": "editor-tab",
|
|
131
|
+
"data-state": active ? "active" : "inactive",
|
|
132
|
+
"data-dirty": tab.dirty || void 0,
|
|
133
|
+
"data-dragging": dragId === tab.id || void 0,
|
|
134
|
+
"data-drop": dragId === null ? void 0 : dropGap === i ? "before" : dropGap === tabs.length && i === tabs.length - 1 ? "after" : void 0,
|
|
135
|
+
onPointerDown: (event) => startDrag(event, tab.id),
|
|
136
|
+
onClickCapture: (event) => {
|
|
137
|
+
if (dragged.current) {
|
|
138
|
+
dragged.current = false;
|
|
139
|
+
event.stopPropagation();
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
onAuxClick: (event) => {
|
|
144
|
+
if (event.button === 1 && closeable) {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
onClose?.(tab.id);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
onContextMenu: onTabContextMenu ? (event) => onTabContextMenu(tab.id, event) : void 0,
|
|
150
|
+
className: cn("group/tab relative flex max-w-60 shrink-0 items-center border-e text-[13px] text-muted-foreground", "hover:bg-muted/60 data-[state=active]:bg-background data-[state=active]:text-foreground", "data-[state=active]:before:absolute data-[state=active]:before:inset-x-0 data-[state=active]:before:top-0 data-[state=active]:before:h-0.5 data-[state=active]:before:bg-brand data-[state=active]:before:content-[\"\"]", "data-[dragging]:opacity-50 data-[dragging]:select-none", "data-[drop=before]:shadow-[inset_2px_0_0_var(--brand)] data-[drop=after]:shadow-[inset_-2px_0_0_var(--brand)]"),
|
|
151
|
+
children: [
|
|
152
|
+
/* @__PURE__ */ jsxs("button", {
|
|
153
|
+
ref: (el) => {
|
|
154
|
+
if (el) tabRefs.current.set(tab.id, el);
|
|
155
|
+
else tabRefs.current.delete(tab.id);
|
|
156
|
+
},
|
|
157
|
+
type: "button",
|
|
158
|
+
role: "tab",
|
|
159
|
+
id: tabId,
|
|
160
|
+
"aria-selected": active,
|
|
161
|
+
"aria-controls": tab.controls,
|
|
162
|
+
"aria-describedby": tab.description ? `${tabId}-desc` : void 0,
|
|
163
|
+
title: tab.description,
|
|
164
|
+
tabIndex: tab.id === tabStop ? 0 : -1,
|
|
165
|
+
onFocus: () => setFocusedId(tab.id),
|
|
166
|
+
onClick: () => onActiveChange(tab.id),
|
|
167
|
+
onKeyDown: (event) => onKeyDown(event, tab),
|
|
168
|
+
className: cn("flex h-full min-w-0 items-center gap-1.5 ps-3 outline-none focus-visible:shadow-[inset_0_0_0_1px_var(--ring)]", closeable ? "pe-1" : "pe-3"),
|
|
169
|
+
children: [
|
|
170
|
+
tab.icon && /* @__PURE__ */ jsx("span", {
|
|
171
|
+
"aria-hidden": true,
|
|
172
|
+
className: "flex shrink-0 [&_svg]:size-4",
|
|
173
|
+
children: tab.icon
|
|
174
|
+
}),
|
|
175
|
+
/* @__PURE__ */ jsx("span", {
|
|
176
|
+
className: cn("truncate", tab.preview && "italic"),
|
|
177
|
+
children: tab.label
|
|
178
|
+
}),
|
|
179
|
+
tab.dirty && /* @__PURE__ */ jsx("span", {
|
|
180
|
+
className: "sr-only",
|
|
181
|
+
children: ", unsaved changes"
|
|
182
|
+
})
|
|
183
|
+
]
|
|
184
|
+
}),
|
|
185
|
+
tab.description && /* @__PURE__ */ jsx("span", {
|
|
186
|
+
id: `${tabId}-desc`,
|
|
187
|
+
hidden: true,
|
|
188
|
+
children: tab.description
|
|
189
|
+
}),
|
|
190
|
+
closeable ? /* @__PURE__ */ jsxs("button", {
|
|
191
|
+
type: "button",
|
|
192
|
+
tabIndex: -1,
|
|
193
|
+
"aria-label": `Close ${tab.label}`,
|
|
194
|
+
title: "Close (Delete)",
|
|
195
|
+
onClick: () => onClose?.(tab.id),
|
|
196
|
+
className: "me-1 flex size-5 shrink-0 items-center justify-center rounded-xs text-muted-foreground outline-none hover:bg-muted hover:text-foreground",
|
|
197
|
+
children: [tab.dirty && /* @__PURE__ */ jsx("span", {
|
|
198
|
+
"aria-hidden": true,
|
|
199
|
+
className: "size-2 rounded-full bg-foreground/70 group-hover/tab:hidden"
|
|
200
|
+
}), /* @__PURE__ */ jsx(X, {
|
|
201
|
+
"aria-hidden": true,
|
|
202
|
+
className: cn("size-3.5", tab.dirty ? "hidden group-hover/tab:block" : "opacity-0 group-hover/tab:opacity-100 group-data-[state=active]/tab:opacity-100 group-focus-within/tab:opacity-100")
|
|
203
|
+
})]
|
|
204
|
+
}) : tab.dirty ? /* @__PURE__ */ jsx("span", {
|
|
205
|
+
"aria-hidden": true,
|
|
206
|
+
className: "me-3 size-2 shrink-0 rounded-full bg-foreground/70"
|
|
207
|
+
}) : null
|
|
208
|
+
]
|
|
209
|
+
}, tab.id);
|
|
210
|
+
})
|
|
211
|
+
}), actions && /* @__PURE__ */ jsx("div", {
|
|
212
|
+
"data-slot": "editor-tabs-actions",
|
|
213
|
+
className: "flex shrink-0 items-center gap-0.5 px-1",
|
|
214
|
+
children: actions
|
|
215
|
+
})]
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
export { EditorTabs };
|
|
220
|
+
|
|
221
|
+
//# sourceMappingURL=editor-tabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor-tabs.js","names":[],"sources":["../../src/components/editor-tabs.tsx"],"sourcesContent":["import X from '@burtson-labs/icons/react/x';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nexport interface EditorTab {\n /** Stable identity, usually the file path. */\n id: string;\n label: string;\n /** Full path or context; the tooltip and the accessible description. */\n description?: string;\n icon?: React.ReactNode;\n /** Unsaved changes: a dot replaces the close button until hover. */\n dirty?: boolean;\n /** Default true. */\n closeable?: boolean;\n /** A preview tab that the next opened file replaces; shown in italics. */\n preview?: boolean;\n /** The id of the panel this tab shows, for aria-controls. */\n controls?: string;\n}\n\nexport interface EditorTabsProps extends Omit<\n React.ComponentProps<'div'>,\n 'children' | 'aria-label'\n> {\n tabs: EditorTab[];\n activeId: string | null;\n onActiveChange: (id: string) => void;\n /**\n * A request to close. The component never removes a tab itself: for a dirty\n * tab, ask the person (save, discard, cancel) and then update `tabs`.\n */\n onClose?: (id: string) => void;\n /**\n * Reorder by drag or Ctrl+Shift+PageUp/PageDown. `toIndex` is the new position.\n * Dragging uses pointer events, not HTML5 drag and drop, so it also works in\n * desktop webviews (Tauri, Electron) that intercept native drags.\n */\n onMove?: (id: string, toIndex: number) => void;\n /** Required: e.g. \"Open editors\". */\n 'aria-label': string;\n /** Right-hand slot for split, more and similar buttons. */\n actions?: React.ReactNode;\n /** Right-click on a tab. */\n onTabContextMenu?: (id: string, event: React.MouseEvent) => void;\n}\n\n/**\n * The strip of open files above an editor. One tab stop; Left/Right/Home/End\n * move focus, Enter or Space opens, Delete asks to close, Ctrl+Shift+PageUp\n * and PageDown move the tab. Middle-click closes. The strip scrolls when the\n * tabs overflow and keeps the active tab in view.\n */\nfunction EditorTabs({\n tabs,\n activeId,\n onActiveChange,\n onClose,\n onMove,\n actions,\n onTabContextMenu,\n className,\n 'aria-label': ariaLabel,\n ...props\n}: EditorTabsProps) {\n const baseId = React.useId();\n const tabRefs = React.useRef(new Map<string, HTMLButtonElement>());\n const listRef = React.useRef<HTMLDivElement>(null);\n const [focusedId, setFocusedId] = React.useState<string | null>(null);\n const [dragId, setDragId] = React.useState<string | null>(null);\n // The gap a dragged tab would land in: 0 is before the first tab,\n // tabs.length after the last.\n const [dropGap, setDropGap] = React.useState<number | null>(null);\n // Set once a press becomes a drag, so the click that ends it does nothing.\n const dragged = React.useRef(false);\n const dragCleanup = React.useRef<(() => void) | null>(null);\n React.useEffect(() => () => dragCleanup.current?.(), []);\n const lastIds = React.useRef<string[]>([]);\n const hadFocus = React.useRef(false);\n\n const ids = tabs.map((t) => t.id);\n const idsKey = ids.join('\\u0000');\n const tabStop =\n (focusedId && ids.includes(focusedId) ? focusedId : null) ??\n (activeId && ids.includes(activeId) ? activeId : null) ??\n ids[0] ??\n null;\n\n React.useEffect(() => {\n if (activeId)\n tabRefs.current.get(activeId)?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });\n }, [activeId]);\n\n // After a close, keep keyboard focus in the strip: the active tab if the\n // app picked one, else the neighbour at the closed tab's position.\n React.useLayoutEffect(() => {\n if (focusedId && !ids.includes(focusedId)) {\n const next =\n (activeId && ids.includes(activeId) ? activeId : null) ??\n ids[Math.min(Math.max(0, lastIds.current.indexOf(focusedId)), ids.length - 1)] ??\n null;\n setFocusedId(next);\n const active = document.activeElement;\n if (next && hadFocus.current && (!active || active === document.body))\n tabRefs.current.get(next)?.focus();\n }\n lastIds.current = ids;\n // ids is derived from tabs each render; idsKey is its stable form.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [focusedId, idsKey, activeId]);\n\n /** Which gap the pointer is over, measured from the rendered tabs. */\n const gapAt = (clientX: number) => {\n const els = Array.from(\n listRef.current?.querySelectorAll<HTMLElement>('[data-slot=\"editor-tab\"]') ?? [],\n );\n const rtl = listRef.current ? getComputedStyle(listRef.current).direction === 'rtl' : false;\n for (let i = 0; i < els.length; i++) {\n const rect = els[i]!.getBoundingClientRect();\n const mid = rect.left + rect.width / 2;\n if (rtl ? clientX > mid : clientX < mid) return i;\n }\n return els.length;\n };\n\n const startDrag = (event: React.PointerEvent, id: string) => {\n if (!onMove || event.button !== 0) return;\n dragCleanup.current?.();\n dragged.current = false;\n const downX = event.clientX;\n const move = (e: PointerEvent) => {\n // A few pixels of slop so an ordinary click is not read as a drag.\n if (!dragged.current && Math.abs(e.clientX - downX) < 4) return;\n if (!dragged.current) {\n dragged.current = true;\n setDragId(id);\n }\n setDropGap(gapAt(e.clientX));\n };\n const stop = (e: PointerEvent | null) => {\n window.removeEventListener('pointermove', move);\n window.removeEventListener('pointerup', up);\n window.removeEventListener('pointercancel', cancel);\n dragCleanup.current = null;\n if (e && dragged.current) {\n const from = lastIds.current.indexOf(id);\n const gap = gapAt(e.clientX);\n const to = gap > from ? gap - 1 : gap;\n if (from !== -1 && to !== from) onMove(id, to);\n }\n setDragId(null);\n setDropGap(null);\n };\n const up = (e: PointerEvent) => stop(e);\n const cancel = () => stop(null);\n window.addEventListener('pointermove', move);\n window.addEventListener('pointerup', up);\n window.addEventListener('pointercancel', cancel);\n dragCleanup.current = () => stop(null);\n };\n\n const focusTab = (id: string | undefined) => {\n if (!id) return;\n setFocusedId(id);\n tabRefs.current.get(id)?.focus();\n };\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>, tab: EditorTab) => {\n const i = ids.indexOf(tab.id);\n const rtl = listRef.current ? getComputedStyle(listRef.current).direction === 'rtl' : false;\n const [prev, next] = rtl ? ['ArrowRight', 'ArrowLeft'] : ['ArrowLeft', 'ArrowRight'];\n let handled = true;\n if (event.ctrlKey && event.shiftKey && (event.key === 'PageUp' || event.key === 'PageDown')) {\n const to = event.key === 'PageUp' ? i - 1 : i + 1;\n if (onMove && to >= 0 && to < ids.length) onMove(tab.id, to);\n } else if (event.key === prev) focusTab(ids[(i - 1 + ids.length) % ids.length]);\n else if (event.key === next) focusTab(ids[(i + 1) % ids.length]);\n else if (event.key === 'Home') focusTab(ids[0]);\n else if (event.key === 'End') focusTab(ids[ids.length - 1]);\n else if (event.key === 'Delete' && tab.closeable !== false && onClose) onClose(tab.id);\n else handled = false;\n if (handled) event.preventDefault();\n };\n\n return (\n <div\n data-slot=\"editor-tabs\"\n className={cn('flex h-9 min-w-0 items-stretch border-b bg-surface-muted', className)}\n {...props}\n >\n <div\n ref={listRef}\n role=\"tablist\"\n aria-label={ariaLabel}\n aria-orientation=\"horizontal\"\n className=\"flex min-w-0 flex-1 items-stretch overflow-x-auto [scrollbar-width:thin]\"\n onFocus={() => (hadFocus.current = true)}\n onBlur={(event) => {\n if (!listRef.current?.contains(event.relatedTarget)) hadFocus.current = false;\n }}\n >\n {tabs.map((tab, i) => {\n const active = tab.id === activeId;\n const closeable = tab.closeable !== false && Boolean(onClose);\n const tabId = `${baseId}-tab-${i}`;\n return (\n <div\n key={tab.id}\n data-slot=\"editor-tab\"\n data-state={active ? 'active' : 'inactive'}\n data-dirty={tab.dirty || undefined}\n data-dragging={dragId === tab.id || undefined}\n data-drop={\n dragId === null\n ? undefined\n : dropGap === i\n ? 'before'\n : dropGap === tabs.length && i === tabs.length - 1\n ? 'after'\n : undefined\n }\n onPointerDown={(event) => startDrag(event, tab.id)}\n onClickCapture={(event) => {\n // Swallow the click that ends a drag.\n if (dragged.current) {\n dragged.current = false;\n event.stopPropagation();\n event.preventDefault();\n }\n }}\n onAuxClick={(event) => {\n if (event.button === 1 && closeable) {\n event.preventDefault();\n onClose?.(tab.id);\n }\n }}\n onContextMenu={\n onTabContextMenu ? (event) => onTabContextMenu(tab.id, event) : undefined\n }\n className={cn(\n 'group/tab relative flex max-w-60 shrink-0 items-center border-e text-[13px] text-muted-foreground',\n 'hover:bg-muted/60 data-[state=active]:bg-background data-[state=active]:text-foreground',\n // The active tab's top edge carries the brand colour.\n 'data-[state=active]:before:absolute data-[state=active]:before:inset-x-0 data-[state=active]:before:top-0 data-[state=active]:before:h-0.5 data-[state=active]:before:bg-brand data-[state=active]:before:content-[\"\"]',\n 'data-[dragging]:opacity-50 data-[dragging]:select-none',\n // Where a dragged tab will land.\n 'data-[drop=before]:shadow-[inset_2px_0_0_var(--brand)] data-[drop=after]:shadow-[inset_-2px_0_0_var(--brand)]',\n )}\n >\n <button\n ref={(el) => {\n if (el) tabRefs.current.set(tab.id, el);\n else tabRefs.current.delete(tab.id);\n }}\n type=\"button\"\n role=\"tab\"\n id={tabId}\n aria-selected={active}\n aria-controls={tab.controls}\n aria-describedby={tab.description ? `${tabId}-desc` : undefined}\n title={tab.description}\n tabIndex={tab.id === tabStop ? 0 : -1}\n onFocus={() => setFocusedId(tab.id)}\n onClick={() => onActiveChange(tab.id)}\n onKeyDown={(event) => onKeyDown(event, tab)}\n className={cn(\n 'flex h-full min-w-0 items-center gap-1.5 ps-3 outline-none focus-visible:shadow-[inset_0_0_0_1px_var(--ring)]',\n closeable ? 'pe-1' : 'pe-3',\n )}\n >\n {tab.icon && (\n <span aria-hidden className=\"flex shrink-0 [&_svg]:size-4\">\n {tab.icon}\n </span>\n )}\n <span className={cn('truncate', tab.preview && 'italic')}>{tab.label}</span>\n {tab.dirty && <span className=\"sr-only\">, unsaved changes</span>}\n </button>\n {tab.description && (\n <span id={`${tabId}-desc`} hidden>\n {tab.description}\n </span>\n )}\n {closeable ? (\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={`Close ${tab.label}`}\n title=\"Close (Delete)\"\n onClick={() => onClose?.(tab.id)}\n className=\"me-1 flex size-5 shrink-0 items-center justify-center rounded-xs text-muted-foreground outline-none hover:bg-muted hover:text-foreground\"\n >\n {tab.dirty && (\n <span\n aria-hidden\n className=\"size-2 rounded-full bg-foreground/70 group-hover/tab:hidden\"\n />\n )}\n <X\n aria-hidden\n className={cn(\n 'size-3.5',\n tab.dirty\n ? 'hidden group-hover/tab:block'\n : 'opacity-0 group-hover/tab:opacity-100 group-data-[state=active]/tab:opacity-100 group-focus-within/tab:opacity-100',\n )}\n />\n </button>\n ) : tab.dirty ? (\n <span aria-hidden className=\"me-3 size-2 shrink-0 rounded-full bg-foreground/70\" />\n ) : null}\n </div>\n );\n })}\n </div>\n {actions && (\n <div data-slot=\"editor-tabs-actions\" className=\"flex shrink-0 items-center gap-0.5 px-1\">\n {actions}\n </div>\n )}\n </div>\n );\n}\n\nexport { EditorTabs };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Menubar as MenubarPrimitive } from 'radix-ui';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* An application menu bar (File, Edit, View). Left/Right move between menus,
|
|
5
|
+
* Down opens one, and an open menu follows the pointer across triggers. Rows
|
|
6
|
+
* match DropdownMenu and ContextMenu.
|
|
7
|
+
*/
|
|
8
|
+
declare function Menubar({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.Root>): React.JSX.Element;
|
|
9
|
+
declare function MenubarMenu(props: React.ComponentProps<typeof MenubarPrimitive.Menu>): React.JSX.Element;
|
|
10
|
+
declare function MenubarTrigger({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.Trigger>): React.JSX.Element;
|
|
11
|
+
declare function MenubarGroup(props: React.ComponentProps<typeof MenubarPrimitive.Group>): React.JSX.Element;
|
|
12
|
+
declare function MenubarContent({ className, align, sideOffset, ...props }: React.ComponentProps<typeof MenubarPrimitive.Content>): React.JSX.Element;
|
|
13
|
+
declare function MenubarItem({ className, inset, variant, ...props }: React.ComponentProps<typeof MenubarPrimitive.Item> & {
|
|
14
|
+
inset?: boolean;
|
|
15
|
+
variant?: 'default' | 'destructive';
|
|
16
|
+
}): React.JSX.Element;
|
|
17
|
+
declare function MenubarCheckboxItem({ className, children, checked, ...props }: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>): React.JSX.Element;
|
|
18
|
+
declare function MenubarRadioGroup(props: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>): React.JSX.Element;
|
|
19
|
+
declare function MenubarRadioItem({ className, children, ...props }: React.ComponentProps<typeof MenubarPrimitive.RadioItem>): React.JSX.Element;
|
|
20
|
+
declare function MenubarLabel({ className, inset, ...props }: React.ComponentProps<typeof MenubarPrimitive.Label> & {
|
|
21
|
+
inset?: boolean;
|
|
22
|
+
}): React.JSX.Element;
|
|
23
|
+
declare function MenubarSeparator({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.Separator>): React.JSX.Element;
|
|
24
|
+
declare function MenubarShortcut({ className, ...props }: React.ComponentProps<'span'>): React.JSX.Element;
|
|
25
|
+
declare function MenubarSub(props: React.ComponentProps<typeof MenubarPrimitive.Sub>): React.JSX.Element;
|
|
26
|
+
declare function MenubarSubTrigger({ className, inset, children, ...props }: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
|
|
27
|
+
inset?: boolean;
|
|
28
|
+
}): React.JSX.Element;
|
|
29
|
+
declare function MenubarSubContent({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.SubContent>): React.JSX.Element;
|
|
30
|
+
export { Menubar, MenubarMenu, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { menuItemClasses, surfaceClasses } from "./popover.js";
|
|
3
|
+
import { Menubar } from "radix-ui";
|
|
4
|
+
import "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import ChevronRight from "@burtson-labs/icons/react/chevron-right";
|
|
7
|
+
import Check from "@burtson-labs/icons/react/check";
|
|
8
|
+
//#region src/components/menubar.tsx
|
|
9
|
+
/**
|
|
10
|
+
* An application menu bar (File, Edit, View). Left/Right move between menus,
|
|
11
|
+
* Down opens one, and an open menu follows the pointer across triggers. Rows
|
|
12
|
+
* match DropdownMenu and ContextMenu.
|
|
13
|
+
*/
|
|
14
|
+
function Menubar$1({ className, ...props }) {
|
|
15
|
+
return /* @__PURE__ */ jsx(Menubar.Root, {
|
|
16
|
+
"data-slot": "menubar",
|
|
17
|
+
className: cn("flex h-8 items-center gap-0.5 rounded-md border bg-surface p-0.5", className),
|
|
18
|
+
...props
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function MenubarMenu(props) {
|
|
22
|
+
return /* @__PURE__ */ jsx(Menubar.Menu, {
|
|
23
|
+
"data-slot": "menubar-menu",
|
|
24
|
+
...props
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function MenubarTrigger({ className, ...props }) {
|
|
28
|
+
return /* @__PURE__ */ jsx(Menubar.Trigger, {
|
|
29
|
+
"data-slot": "menubar-trigger",
|
|
30
|
+
className: cn("flex h-7 items-center rounded-sm px-2.5 text-[13px] font-medium outline-none select-none hover:bg-muted focus-visible:shadow-[inset_0_0_0_1px_var(--ring)] data-[state=open]:bg-muted", className),
|
|
31
|
+
...props
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function MenubarGroup(props) {
|
|
35
|
+
return /* @__PURE__ */ jsx(Menubar.Group, {
|
|
36
|
+
"data-slot": "menubar-group",
|
|
37
|
+
...props
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function MenubarContent({ className, align = "start", sideOffset = 4, ...props }) {
|
|
41
|
+
return /* @__PURE__ */ jsx(Menubar.Portal, { children: /* @__PURE__ */ jsx(Menubar.Content, {
|
|
42
|
+
"data-slot": "menubar-content",
|
|
43
|
+
align,
|
|
44
|
+
sideOffset,
|
|
45
|
+
className: cn(surfaceClasses, "max-h-(--radix-menubar-content-available-height) min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-x-hidden overflow-y-auto p-1", className),
|
|
46
|
+
...props
|
|
47
|
+
}) });
|
|
48
|
+
}
|
|
49
|
+
function MenubarItem({ className, inset, variant = "default", ...props }) {
|
|
50
|
+
return /* @__PURE__ */ jsx(Menubar.Item, {
|
|
51
|
+
"data-slot": "menubar-item",
|
|
52
|
+
"data-inset": inset || void 0,
|
|
53
|
+
"data-variant": variant,
|
|
54
|
+
className: cn(menuItemClasses, "data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:*:[svg]:!text-destructive", className),
|
|
55
|
+
...props
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function MenubarCheckboxItem({ className, children, checked, ...props }) {
|
|
59
|
+
return /* @__PURE__ */ jsxs(Menubar.CheckboxItem, {
|
|
60
|
+
"data-slot": "menubar-checkbox-item",
|
|
61
|
+
className: cn(menuItemClasses, "pl-8", className),
|
|
62
|
+
checked,
|
|
63
|
+
...props,
|
|
64
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
65
|
+
className: "pointer-events-none absolute left-2 flex size-3.5 items-center justify-center",
|
|
66
|
+
children: /* @__PURE__ */ jsx(Menubar.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, {
|
|
67
|
+
className: "size-4 text-foreground",
|
|
68
|
+
"aria-hidden": true
|
|
69
|
+
}) })
|
|
70
|
+
}), children]
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function MenubarRadioGroup(props) {
|
|
74
|
+
return /* @__PURE__ */ jsx(Menubar.RadioGroup, {
|
|
75
|
+
"data-slot": "menubar-radio-group",
|
|
76
|
+
...props
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function MenubarRadioItem({ className, children, ...props }) {
|
|
80
|
+
return /* @__PURE__ */ jsxs(Menubar.RadioItem, {
|
|
81
|
+
"data-slot": "menubar-radio-item",
|
|
82
|
+
className: cn(menuItemClasses, "pl-8", className),
|
|
83
|
+
...props,
|
|
84
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
85
|
+
className: "pointer-events-none absolute left-2 flex size-3.5 items-center justify-center",
|
|
86
|
+
children: /* @__PURE__ */ jsx(Menubar.ItemIndicator, { children: /* @__PURE__ */ jsx("span", { className: "block size-2 rounded-full bg-foreground" }) })
|
|
87
|
+
}), children]
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function MenubarLabel({ className, inset, ...props }) {
|
|
91
|
+
return /* @__PURE__ */ jsx(Menubar.Label, {
|
|
92
|
+
"data-slot": "menubar-label",
|
|
93
|
+
"data-inset": inset || void 0,
|
|
94
|
+
className: cn("px-2 pt-2 pb-1 text-[11px] font-semibold tracking-[0.08em] text-muted-foreground uppercase data-[inset]:pl-8", className),
|
|
95
|
+
...props
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function MenubarSeparator({ className, ...props }) {
|
|
99
|
+
return /* @__PURE__ */ jsx(Menubar.Separator, {
|
|
100
|
+
"data-slot": "menubar-separator",
|
|
101
|
+
className: cn("-mx-1 my-1 h-px bg-border", className),
|
|
102
|
+
...props
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function MenubarShortcut({ className, ...props }) {
|
|
106
|
+
return /* @__PURE__ */ jsx("span", {
|
|
107
|
+
"data-slot": "menubar-shortcut",
|
|
108
|
+
className: cn("ml-auto text-xs tracking-widest text-muted-foreground", className),
|
|
109
|
+
...props
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function MenubarSub(props) {
|
|
113
|
+
return /* @__PURE__ */ jsx(Menubar.Sub, {
|
|
114
|
+
"data-slot": "menubar-sub",
|
|
115
|
+
...props
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function MenubarSubTrigger({ className, inset, children, ...props }) {
|
|
119
|
+
return /* @__PURE__ */ jsxs(Menubar.SubTrigger, {
|
|
120
|
+
"data-slot": "menubar-sub-trigger",
|
|
121
|
+
"data-inset": inset || void 0,
|
|
122
|
+
className: cn(menuItemClasses, "data-[state=open]:bg-muted", className),
|
|
123
|
+
...props,
|
|
124
|
+
children: [children, /* @__PURE__ */ jsx(ChevronRight, {
|
|
125
|
+
className: "ml-auto size-4",
|
|
126
|
+
"aria-hidden": true
|
|
127
|
+
})]
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function MenubarSubContent({ className, ...props }) {
|
|
131
|
+
return /* @__PURE__ */ jsx(Menubar.Portal, { children: /* @__PURE__ */ jsx(Menubar.SubContent, {
|
|
132
|
+
"data-slot": "menubar-sub-content",
|
|
133
|
+
className: cn(surfaceClasses, "min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden p-1", className),
|
|
134
|
+
...props
|
|
135
|
+
}) });
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
export { Menubar$1 as Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger };
|
|
139
|
+
|
|
140
|
+
//# sourceMappingURL=menubar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menubar.js","names":["Menubar","MenubarPrimitive","itemClasses"],"sources":["../../src/components/menubar.tsx"],"sourcesContent":["import Check from '@burtson-labs/icons/react/check';\nimport ChevronRight from '@burtson-labs/icons/react/chevron-right';\nimport { Menubar as MenubarPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { menuItemClasses as itemClasses, surfaceClasses } from './popover';\n\n/**\n * An application menu bar (File, Edit, View). Left/Right move between menus,\n * Down opens one, and an open menu follows the pointer across triggers. Rows\n * match DropdownMenu and ContextMenu.\n */\nfunction Menubar({ className, ...props }: React.ComponentProps<typeof MenubarPrimitive.Root>) {\n return (\n <MenubarPrimitive.Root\n data-slot=\"menubar\"\n className={cn('flex h-8 items-center gap-0.5 rounded-md border bg-surface p-0.5', className)}\n {...props}\n />\n );\n}\n\nfunction MenubarMenu(props: React.ComponentProps<typeof MenubarPrimitive.Menu>) {\n return <MenubarPrimitive.Menu data-slot=\"menubar-menu\" {...props} />;\n}\n\nfunction MenubarTrigger({\n className,\n ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {\n return (\n <MenubarPrimitive.Trigger\n data-slot=\"menubar-trigger\"\n className={cn(\n 'flex h-7 items-center rounded-sm px-2.5 text-[13px] font-medium outline-none select-none hover:bg-muted focus-visible:shadow-[inset_0_0_0_1px_var(--ring)] data-[state=open]:bg-muted',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction MenubarGroup(props: React.ComponentProps<typeof MenubarPrimitive.Group>) {\n return <MenubarPrimitive.Group data-slot=\"menubar-group\" {...props} />;\n}\n\nfunction MenubarContent({\n className,\n align = 'start',\n sideOffset = 4,\n ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Content>) {\n return (\n <MenubarPrimitive.Portal>\n <MenubarPrimitive.Content\n data-slot=\"menubar-content\"\n align={align}\n sideOffset={sideOffset}\n className={cn(\n surfaceClasses,\n 'max-h-(--radix-menubar-content-available-height) min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-x-hidden overflow-y-auto p-1',\n className,\n )}\n {...props}\n />\n </MenubarPrimitive.Portal>\n );\n}\n\nfunction MenubarItem({\n className,\n inset,\n variant = 'default',\n ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Item> & {\n inset?: boolean;\n variant?: 'default' | 'destructive';\n}) {\n return (\n <MenubarPrimitive.Item\n data-slot=\"menubar-item\"\n data-inset={inset || undefined}\n data-variant={variant}\n className={cn(\n itemClasses,\n 'data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:*:[svg]:!text-destructive',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction MenubarCheckboxItem({\n className,\n children,\n checked,\n ...props\n}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) {\n return (\n <MenubarPrimitive.CheckboxItem\n data-slot=\"menubar-checkbox-item\"\n className={cn(itemClasses, 'pl-8', className)}\n checked={checked}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <MenubarPrimitive.ItemIndicator>\n <Check className=\"size-4 text-foreground\" aria-hidden />\n </MenubarPrimitive.ItemIndicator>\n </span>\n {children}\n </MenubarPrimitive.CheckboxItem>\n );\n}\n\nfunction MenubarRadioGroup(props: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {\n return <MenubarPrimitive.RadioGroup data-slot=\"menubar-radio-group\" {...props} />;\n}\n\nfunction MenubarRadioItem({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) {\n return (\n <MenubarPrimitive.RadioItem\n data-slot=\"menubar-radio-item\"\n className={cn(itemClasses, 'pl-8', className)}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <MenubarPrimitive.ItemIndicator>\n <span className=\"block size-2 rounded-full bg-foreground\" />\n </MenubarPrimitive.ItemIndicator>\n </span>\n {children}\n </MenubarPrimitive.RadioItem>\n );\n}\n\nfunction MenubarLabel({\n className,\n inset,\n ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Label> & { inset?: boolean }) {\n return (\n <MenubarPrimitive.Label\n data-slot=\"menubar-label\"\n data-inset={inset || undefined}\n className={cn(\n 'px-2 pt-2 pb-1 text-[11px] font-semibold tracking-[0.08em] text-muted-foreground uppercase data-[inset]:pl-8',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction MenubarSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof MenubarPrimitive.Separator>) {\n return (\n <MenubarPrimitive.Separator\n data-slot=\"menubar-separator\"\n className={cn('-mx-1 my-1 h-px bg-border', className)}\n {...props}\n />\n );\n}\n\nfunction MenubarShortcut({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"menubar-shortcut\"\n className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}\n {...props}\n />\n );\n}\n\nfunction MenubarSub(props: React.ComponentProps<typeof MenubarPrimitive.Sub>) {\n return <MenubarPrimitive.Sub data-slot=\"menubar-sub\" {...props} />;\n}\n\nfunction MenubarSubTrigger({\n className,\n inset,\n children,\n ...props\n}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & { inset?: boolean }) {\n return (\n <MenubarPrimitive.SubTrigger\n data-slot=\"menubar-sub-trigger\"\n data-inset={inset || undefined}\n className={cn(itemClasses, 'data-[state=open]:bg-muted', className)}\n {...props}\n >\n {children}\n <ChevronRight className=\"ml-auto size-4\" aria-hidden />\n </MenubarPrimitive.SubTrigger>\n );\n}\n\nfunction MenubarSubContent({\n className,\n ...props\n}: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {\n return (\n <MenubarPrimitive.Portal>\n <MenubarPrimitive.SubContent\n data-slot=\"menubar-sub-content\"\n className={cn(\n surfaceClasses,\n 'min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden p-1',\n className,\n )}\n {...props}\n />\n </MenubarPrimitive.Portal>\n );\n}\n\nexport {\n Menubar,\n MenubarMenu,\n MenubarCheckboxItem,\n MenubarContent,\n MenubarGroup,\n MenubarItem,\n MenubarLabel,\n MenubarRadioGroup,\n MenubarRadioItem,\n MenubarSeparator,\n MenubarShortcut,\n MenubarSub,\n MenubarSubContent,\n MenubarSubTrigger,\n MenubarTrigger,\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface ChecklistItem {
|
|
3
|
+
id: string;
|
|
4
|
+
title: React.ReactNode;
|
|
5
|
+
description?: React.ReactNode;
|
|
6
|
+
/** Set from real app events (a repo opened, a provider tested), never from a click on the list. */
|
|
7
|
+
done: boolean;
|
|
8
|
+
/** A button or link that starts the task. Hidden once done. */
|
|
9
|
+
action?: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
export interface OnboardingChecklistProps extends Omit<React.ComponentProps<'section'>, 'title'> {
|
|
12
|
+
title: React.ReactNode;
|
|
13
|
+
description?: React.ReactNode;
|
|
14
|
+
items: ChecklistItem[];
|
|
15
|
+
/** Shows a close button. Persist the dismissal yourself. */
|
|
16
|
+
onDismiss?: () => void;
|
|
17
|
+
dismissLabel?: string;
|
|
18
|
+
/** Replaces the list once every item is done. */
|
|
19
|
+
complete?: React.ReactNode;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A short list of first tasks with progress. The app decides when each task
|
|
23
|
+
* is done and whether the list shows at all; this renders it.
|
|
24
|
+
*/
|
|
25
|
+
declare function OnboardingChecklist({ title, description, items, onDismiss, dismissLabel, complete, className, ...props }: OnboardingChecklistProps): React.JSX.Element;
|
|
26
|
+
export { OnboardingChecklist };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { Progress } from "./progress.js";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import X from "@burtson-labs/icons/react/x";
|
|
6
|
+
import CheckCircle from "@burtson-labs/icons/react/check-circle";
|
|
7
|
+
import CircleDashed from "@burtson-labs/icons/react/circle-dashed";
|
|
8
|
+
//#region src/components/onboarding-checklist.tsx
|
|
9
|
+
/**
|
|
10
|
+
* A short list of first tasks with progress. The app decides when each task
|
|
11
|
+
* is done and whether the list shows at all; this renders it.
|
|
12
|
+
*/
|
|
13
|
+
function OnboardingChecklist({ title, description, items, onDismiss, dismissLabel = "Dismiss checklist", complete, className, ...props }) {
|
|
14
|
+
const titleId = React.useId();
|
|
15
|
+
const done = items.filter((i) => i.done).length;
|
|
16
|
+
const allDone = items.length > 0 && done === items.length;
|
|
17
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
18
|
+
"data-slot": "onboarding-checklist",
|
|
19
|
+
"aria-labelledby": titleId,
|
|
20
|
+
className: cn("rounded-lg border bg-card p-4 text-[13px] text-card-foreground", className),
|
|
21
|
+
...props,
|
|
22
|
+
children: [
|
|
23
|
+
/* @__PURE__ */ jsxs("div", {
|
|
24
|
+
className: "flex items-start gap-3",
|
|
25
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
26
|
+
className: "min-w-0 flex-1",
|
|
27
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
28
|
+
id: titleId,
|
|
29
|
+
className: "text-sm font-semibold",
|
|
30
|
+
children: title
|
|
31
|
+
}), description && /* @__PURE__ */ jsx("p", {
|
|
32
|
+
className: "mt-0.5 text-muted-foreground",
|
|
33
|
+
children: description
|
|
34
|
+
})]
|
|
35
|
+
}), onDismiss && /* @__PURE__ */ jsx("button", {
|
|
36
|
+
type: "button",
|
|
37
|
+
"aria-label": dismissLabel,
|
|
38
|
+
onClick: onDismiss,
|
|
39
|
+
className: "-me-1 -mt-1 flex size-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:shadow-[inset_0_0_0_1px_var(--ring)]",
|
|
40
|
+
children: /* @__PURE__ */ jsx(X, {
|
|
41
|
+
"aria-hidden": true,
|
|
42
|
+
className: "size-4"
|
|
43
|
+
})
|
|
44
|
+
})]
|
|
45
|
+
}),
|
|
46
|
+
/* @__PURE__ */ jsxs("div", {
|
|
47
|
+
className: "mt-3 flex items-center gap-3",
|
|
48
|
+
children: [/* @__PURE__ */ jsx(Progress, {
|
|
49
|
+
value: done,
|
|
50
|
+
max: Math.max(items.length, 1),
|
|
51
|
+
"aria-label": `${done} of ${items.length} done`,
|
|
52
|
+
className: "flex-1"
|
|
53
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
54
|
+
className: "shrink-0 text-xs text-muted-foreground tabular-nums",
|
|
55
|
+
"aria-hidden": true,
|
|
56
|
+
children: [
|
|
57
|
+
done,
|
|
58
|
+
"/",
|
|
59
|
+
items.length
|
|
60
|
+
]
|
|
61
|
+
})]
|
|
62
|
+
}),
|
|
63
|
+
allDone && complete ? /* @__PURE__ */ jsx("div", {
|
|
64
|
+
className: "mt-3",
|
|
65
|
+
children: complete
|
|
66
|
+
}) : /* @__PURE__ */ jsx("ul", {
|
|
67
|
+
className: "mt-3 flex flex-col gap-1",
|
|
68
|
+
children: items.map((item) => /* @__PURE__ */ jsxs("li", {
|
|
69
|
+
"data-slot": "onboarding-checklist-item",
|
|
70
|
+
"data-state": item.done ? "done" : "todo",
|
|
71
|
+
className: "flex items-start gap-2.5 rounded-md px-1 py-1.5",
|
|
72
|
+
children: [
|
|
73
|
+
item.done ? /* @__PURE__ */ jsx(CheckCircle, {
|
|
74
|
+
"aria-hidden": true,
|
|
75
|
+
className: "mt-px size-4 shrink-0 text-success"
|
|
76
|
+
}) : /* @__PURE__ */ jsx(CircleDashed, {
|
|
77
|
+
"aria-hidden": true,
|
|
78
|
+
className: "mt-px size-4 shrink-0 text-muted-foreground"
|
|
79
|
+
}),
|
|
80
|
+
/* @__PURE__ */ jsxs("div", {
|
|
81
|
+
className: "min-w-0 flex-1",
|
|
82
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
83
|
+
className: cn("font-medium", item.done && "text-muted-foreground line-through"),
|
|
84
|
+
children: [item.title, /* @__PURE__ */ jsx("span", {
|
|
85
|
+
className: "sr-only",
|
|
86
|
+
children: item.done ? ", done" : ", not done"
|
|
87
|
+
})]
|
|
88
|
+
}), item.description && !item.done && /* @__PURE__ */ jsx("p", {
|
|
89
|
+
className: "mt-0.5 text-muted-foreground",
|
|
90
|
+
children: item.description
|
|
91
|
+
})]
|
|
92
|
+
}),
|
|
93
|
+
item.action && !item.done && /* @__PURE__ */ jsx("div", {
|
|
94
|
+
className: "shrink-0",
|
|
95
|
+
children: item.action
|
|
96
|
+
})
|
|
97
|
+
]
|
|
98
|
+
}, item.id))
|
|
99
|
+
})
|
|
100
|
+
]
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { OnboardingChecklist };
|
|
105
|
+
|
|
106
|
+
//# sourceMappingURL=onboarding-checklist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboarding-checklist.js","names":[],"sources":["../../src/components/onboarding-checklist.tsx"],"sourcesContent":["import CheckCircle from '@burtson-labs/icons/react/check-circle';\nimport CircleDashed from '@burtson-labs/icons/react/circle-dashed';\nimport X from '@burtson-labs/icons/react/x';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Progress } from './progress';\n\nexport interface ChecklistItem {\n id: string;\n title: React.ReactNode;\n description?: React.ReactNode;\n /** Set from real app events (a repo opened, a provider tested), never from a click on the list. */\n done: boolean;\n /** A button or link that starts the task. Hidden once done. */\n action?: React.ReactNode;\n}\n\nexport interface OnboardingChecklistProps extends Omit<React.ComponentProps<'section'>, 'title'> {\n title: React.ReactNode;\n description?: React.ReactNode;\n items: ChecklistItem[];\n /** Shows a close button. Persist the dismissal yourself. */\n onDismiss?: () => void;\n dismissLabel?: string;\n /** Replaces the list once every item is done. */\n complete?: React.ReactNode;\n}\n\n/**\n * A short list of first tasks with progress. The app decides when each task\n * is done and whether the list shows at all; this renders it.\n */\nfunction OnboardingChecklist({\n title,\n description,\n items,\n onDismiss,\n dismissLabel = 'Dismiss checklist',\n complete,\n className,\n ...props\n}: OnboardingChecklistProps) {\n const titleId = React.useId();\n const done = items.filter((i) => i.done).length;\n const allDone = items.length > 0 && done === items.length;\n return (\n <section\n data-slot=\"onboarding-checklist\"\n aria-labelledby={titleId}\n className={cn('rounded-lg border bg-card p-4 text-[13px] text-card-foreground', className)}\n {...props}\n >\n <div className=\"flex items-start gap-3\">\n <div className=\"min-w-0 flex-1\">\n <h2 id={titleId} className=\"text-sm font-semibold\">\n {title}\n </h2>\n {description && <p className=\"mt-0.5 text-muted-foreground\">{description}</p>}\n </div>\n {onDismiss && (\n <button\n type=\"button\"\n aria-label={dismissLabel}\n onClick={onDismiss}\n className=\"-me-1 -mt-1 flex size-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:shadow-[inset_0_0_0_1px_var(--ring)]\"\n >\n <X aria-hidden className=\"size-4\" />\n </button>\n )}\n </div>\n <div className=\"mt-3 flex items-center gap-3\">\n <Progress\n value={done}\n max={Math.max(items.length, 1)}\n aria-label={`${done} of ${items.length} done`}\n className=\"flex-1\"\n />\n <span className=\"shrink-0 text-xs text-muted-foreground tabular-nums\" aria-hidden>\n {done}/{items.length}\n </span>\n </div>\n {allDone && complete ? (\n <div className=\"mt-3\">{complete}</div>\n ) : (\n <ul className=\"mt-3 flex flex-col gap-1\">\n {items.map((item) => (\n <li\n key={item.id}\n data-slot=\"onboarding-checklist-item\"\n data-state={item.done ? 'done' : 'todo'}\n className=\"flex items-start gap-2.5 rounded-md px-1 py-1.5\"\n >\n {item.done ? (\n <CheckCircle aria-hidden className=\"mt-px size-4 shrink-0 text-success\" />\n ) : (\n <CircleDashed aria-hidden className=\"mt-px size-4 shrink-0 text-muted-foreground\" />\n )}\n <div className=\"min-w-0 flex-1\">\n <p className={cn('font-medium', item.done && 'text-muted-foreground line-through')}>\n {item.title}\n <span className=\"sr-only\">{item.done ? ', done' : ', not done'}</span>\n </p>\n {item.description && !item.done && (\n <p className=\"mt-0.5 text-muted-foreground\">{item.description}</p>\n )}\n </div>\n {item.action && !item.done && <div className=\"shrink-0\">{item.action}</div>}\n </li>\n ))}\n </ul>\n )}\n </section>\n );\n}\n\nexport { OnboardingChecklist };\n"],"mappings":";;;;;;;;;;;;AAkCA,SAAS,oBAAoB,EAC3B,OACA,aACA,OACA,WACA,eAAe,qBACf,UACA,WACA,GAAG,SACwB;CAC3B,MAAM,UAAU,MAAM,MAAM;CAC5B,MAAM,OAAO,MAAM,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC;CACzC,MAAM,UAAU,MAAM,SAAS,KAAK,SAAS,MAAM;CACnD,OACE,qBAAC,WAAD;EACE,aAAU;EACV,mBAAiB;EACjB,WAAW,GAAG,kEAAkE,SAAS;EACzF,GAAI;EAJN,UAAA;GAME,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,oBAAC,MAAD;MAAI,IAAI;MAAS,WAAU;MACxB,UAAA;KACC,CAAA,GACH,eAAe,oBAAC,KAAD;MAAG,WAAU;MAAgC,UAAA;KAAe,CAAA,CACzE;IACJ,CAAA,GAAA,aACC,oBAAC,UAAD;KACE,MAAK;KACL,cAAY;KACZ,SAAS;KACT,WAAU;KAEV,UAAA,oBAAC,GAAD;MAAG,eAAA;MAAY,WAAU;KAAU,CAAA;IAC7B,CAAA,CAEP;;GACL,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,UAAD;KACE,OAAO;KACP,KAAK,KAAK,IAAI,MAAM,QAAQ,CAAC;KAC7B,cAAY,GAAG,KAAK,MAAM,MAAM,OAAO;KACvC,WAAU;IACX,CAAA,GACD,qBAAC,QAAD;KAAM,WAAU;KAAsD,eAAA;KAAtE,UAAA;MACG;MAAK;MAAE,MAAM;KACV;IACH,CAAA,CAAA;;GACJ,WAAW,WACV,oBAAC,OAAD;IAAK,WAAU;IAAQ,UAAA;GAAc,CAAA,IAErC,oBAAC,MAAD;IAAI,WAAU;IACX,UAAA,MAAM,KAAK,SACV,qBAAC,MAAD;KAEE,aAAU;KACV,cAAY,KAAK,OAAO,SAAS;KACjC,WAAU;KAJZ,UAAA;MAMG,KAAK,OACJ,oBAAC,aAAD;OAAa,eAAA;OAAY,WAAU;MAAsC,CAAA,IAEzE,oBAAC,cAAD;OAAc,eAAA;OAAY,WAAU;MAA+C,CAAA;MAErF,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,qBAAC,KAAD;QAAG,WAAW,GAAG,eAAe,KAAK,QAAQ,oCAAoC;QAAjF,UAAA,CACG,KAAK,OACN,oBAAC,QAAD;SAAM,WAAU;SAAW,UAAA,KAAK,OAAO,WAAW;QAAmB,CAAA,CACpE;OACF,CAAA,GAAA,KAAK,eAAe,CAAC,KAAK,QACzB,oBAAC,KAAD;QAAG,WAAU;QAAgC,UAAA,KAAK;OAAe,CAAA,CAEhE;;MACJ,KAAK,UAAU,CAAC,KAAK,QAAQ,oBAAC,OAAD;OAAK,WAAU;OAAY,UAAA,KAAK;MAAY,CAAA;KACxE;IApBG,GAAA,KAAK,EAoBR,CACL;GACC,CAAA;EAEC;;AAEb"}
|
|
@@ -5,6 +5,8 @@ import * as React from 'react';
|
|
|
5
5
|
* with a strong border, and the one place a heavy shadow is allowed.
|
|
6
6
|
*/
|
|
7
7
|
export declare const surfaceClasses = "z-50 animate-in rounded-lg border border-border-strong bg-surface-raised text-popover-foreground shadow-[0_16px_48px_rgb(0_0_0_/_0.18)] data-[state=closed]:animate-out data-[side=bottom]:[--bl-ty:-6px] data-[side=top]:[--bl-ty:6px] data-[side=left]:[--bl-tx:6px] data-[side=right]:[--bl-tx:-6px]";
|
|
8
|
+
/** A menu row: 32px, 8px radius; the focused row is a quiet fill, like an active tab. */
|
|
9
|
+
export declare const menuItemClasses = "relative flex min-h-8 cursor-default items-center gap-2 rounded-sm px-2 text-[13px] outline-hidden select-none focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground";
|
|
8
10
|
declare function Popover(props: React.ComponentProps<typeof PopoverPrimitive.Root>): React.JSX.Element;
|
|
9
11
|
declare function PopoverTrigger(props: React.ComponentProps<typeof PopoverPrimitive.Trigger>): React.JSX.Element;
|
|
10
12
|
declare function PopoverAnchor(props: React.ComponentProps<typeof PopoverPrimitive.Anchor>): React.JSX.Element;
|
|
@@ -8,6 +8,8 @@ import { jsx } from "react/jsx-runtime";
|
|
|
8
8
|
* with a strong border, and the one place a heavy shadow is allowed.
|
|
9
9
|
*/
|
|
10
10
|
var surfaceClasses = "z-50 animate-in rounded-lg border border-border-strong bg-surface-raised text-popover-foreground shadow-[0_16px_48px_rgb(0_0_0_/_0.18)] data-[state=closed]:animate-out data-[side=bottom]:[--bl-ty:-6px] data-[side=top]:[--bl-ty:6px] data-[side=left]:[--bl-tx:6px] data-[side=right]:[--bl-tx:-6px]";
|
|
11
|
+
/** A menu row: 32px, 8px radius; the focused row is a quiet fill, like an active tab. */
|
|
12
|
+
var menuItemClasses = "relative flex min-h-8 cursor-default items-center gap-2 rounded-sm px-2 text-[13px] outline-hidden select-none focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground";
|
|
11
13
|
function Popover$1(props) {
|
|
12
14
|
return /* @__PURE__ */ jsx(Popover.Root, {
|
|
13
15
|
"data-slot": "popover",
|
|
@@ -36,6 +38,6 @@ function PopoverContent({ className, align = "center", sideOffset = 6, ...props
|
|
|
36
38
|
}) });
|
|
37
39
|
}
|
|
38
40
|
//#endregion
|
|
39
|
-
export { Popover$1 as Popover, PopoverAnchor, PopoverContent, PopoverTrigger, surfaceClasses };
|
|
41
|
+
export { Popover$1 as Popover, PopoverAnchor, PopoverContent, PopoverTrigger, menuItemClasses, surfaceClasses };
|
|
40
42
|
|
|
41
43
|
//# sourceMappingURL=popover.js.map
|