@braedonsaunders/appkit-ui 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +11 -0
- package/account-menu.d.ts +49 -0
- package/account-menu.d.ts.map +1 -0
- package/account-menu.js +66 -0
- package/account-menu.js.map +1 -0
- package/alert.d.ts +13 -0
- package/alert.d.ts.map +1 -0
- package/alert.js +22 -0
- package/alert.js.map +1 -0
- package/animated-number.d.ts +13 -0
- package/animated-number.d.ts.map +1 -0
- package/animated-number.js +53 -0
- package/animated-number.js.map +1 -0
- package/api-reference.d.ts +44 -0
- package/api-reference.d.ts.map +1 -0
- package/api-reference.js +82 -0
- package/api-reference.js.map +1 -0
- package/app-shell.d.ts +39 -0
- package/app-shell.d.ts.map +1 -0
- package/app-shell.js +21 -0
- package/app-shell.js.map +1 -0
- package/app-sidebar.d.ts +22 -0
- package/app-sidebar.d.ts.map +1 -0
- package/app-sidebar.js +30 -0
- package/app-sidebar.js.map +1 -0
- package/avatar.d.ts +9 -0
- package/avatar.d.ts.map +1 -0
- package/avatar.js +18 -0
- package/avatar.js.map +1 -0
- package/badge.d.ts +9 -0
- package/badge.d.ts.map +1 -0
- package/badge.js +23 -0
- package/badge.js.map +1 -0
- package/button.d.ts +24 -0
- package/button.d.ts.map +1 -0
- package/button.js +50 -0
- package/button.js.map +1 -0
- package/card.d.ts +21 -0
- package/card.d.ts.map +1 -0
- package/card.js +36 -0
- package/card.js.map +1 -0
- package/checkbox.d.ts +4 -0
- package/checkbox.d.ts.map +1 -0
- package/checkbox.js +6 -0
- package/checkbox.js.map +1 -0
- package/confirm-dialog.d.ts +23 -0
- package/confirm-dialog.d.ts.map +1 -0
- package/confirm-dialog.js +67 -0
- package/confirm-dialog.js.map +1 -0
- package/context-menu.d.ts +47 -0
- package/context-menu.d.ts.map +1 -0
- package/context-menu.js +90 -0
- package/context-menu.js.map +1 -0
- package/dialog.d.ts +25 -0
- package/dialog.d.ts.map +1 -0
- package/dialog.js +89 -0
- package/dialog.js.map +1 -0
- package/document-title.d.ts +11 -0
- package/document-title.d.ts.map +1 -0
- package/document-title.js +17 -0
- package/document-title.js.map +1 -0
- package/drawer.d.ts +63 -0
- package/drawer.d.ts.map +1 -0
- package/drawer.js +188 -0
- package/drawer.js.map +1 -0
- package/empty-state.d.ts +10 -0
- package/empty-state.d.ts.map +1 -0
- package/empty-state.js +7 -0
- package/empty-state.js.map +1 -0
- package/file-uploader.d.ts +67 -0
- package/file-uploader.d.ts.map +1 -0
- package/file-uploader.js +156 -0
- package/file-uploader.js.map +1 -0
- package/filter-chips.d.ts +47 -0
- package/filter-chips.d.ts.map +1 -0
- package/filter-chips.js +62 -0
- package/filter-chips.js.map +1 -0
- package/global-search.d.ts +41 -0
- package/global-search.d.ts.map +1 -0
- package/global-search.js +116 -0
- package/global-search.js.map +1 -0
- package/index.d.ts +70 -0
- package/index.d.ts.map +1 -0
- package/index.js +70 -0
- package/index.js.map +1 -0
- package/input.d.ts +9 -0
- package/input.d.ts.map +1 -0
- package/input.js +11 -0
- package/input.js.map +1 -0
- package/label.d.ts +3 -0
- package/label.d.ts.map +1 -0
- package/label.js +6 -0
- package/label.js.map +1 -0
- package/line-grid.d.ts +48 -0
- package/line-grid.d.ts.map +1 -0
- package/line-grid.js +155 -0
- package/line-grid.js.map +1 -0
- package/link-context.d.ts +44 -0
- package/link-context.d.ts.map +1 -0
- package/link-context.js +32 -0
- package/link-context.js.map +1 -0
- package/list-nav.d.ts +28 -0
- package/list-nav.d.ts.map +1 -0
- package/list-nav.js +41 -0
- package/list-nav.js.map +1 -0
- package/list-pagination.d.ts +27 -0
- package/list-pagination.d.ts.map +1 -0
- package/list-pagination.js +43 -0
- package/list-pagination.js.map +1 -0
- package/list-params.d.ts +41 -0
- package/list-params.d.ts.map +1 -0
- package/list-params.js +84 -0
- package/list-params.js.map +1 -0
- package/list-search-input-state.d.ts +13 -0
- package/list-search-input-state.d.ts.map +1 -0
- package/list-search-input-state.js +27 -0
- package/list-search-input-state.js.map +1 -0
- package/list-search-input.d.ts +16 -0
- package/list-search-input.d.ts.map +1 -0
- package/list-search-input.js +51 -0
- package/list-search-input.js.map +1 -0
- package/mobile-tab-bar.d.ts +14 -0
- package/mobile-tab-bar.d.ts.map +1 -0
- package/mobile-tab-bar.js +25 -0
- package/mobile-tab-bar.js.map +1 -0
- package/multi-select.d.ts +32 -0
- package/multi-select.d.ts.map +1 -0
- package/multi-select.js +52 -0
- package/multi-select.js.map +1 -0
- package/nav-overflow.d.ts +12 -0
- package/nav-overflow.d.ts.map +1 -0
- package/nav-overflow.js +30 -0
- package/nav-overflow.js.map +1 -0
- package/navigation-config-editor.d.ts +26 -0
- package/navigation-config-editor.d.ts.map +1 -0
- package/navigation-config-editor.js +57 -0
- package/navigation-config-editor.js.map +1 -0
- package/navigation-config.d.ts +48 -0
- package/navigation-config.d.ts.map +1 -0
- package/navigation-config.js +75 -0
- package/navigation-config.js.map +1 -0
- package/navigation-mode.d.ts +16 -0
- package/navigation-mode.d.ts.map +1 -0
- package/navigation-mode.js +30 -0
- package/navigation-mode.js.map +1 -0
- package/notifications-bell.d.ts +22 -0
- package/notifications-bell.d.ts.map +1 -0
- package/notifications-bell.js +30 -0
- package/notifications-bell.js.map +1 -0
- package/org-chart.d.ts +85 -0
- package/org-chart.d.ts.map +1 -0
- package/org-chart.js +193 -0
- package/org-chart.js.map +1 -0
- package/package.json +61 -0
- package/page-header.d.ts +32 -0
- package/page-header.d.ts.map +1 -0
- package/page-header.js +14 -0
- package/page-header.js.map +1 -0
- package/page-layout.d.ts +29 -0
- package/page-layout.d.ts.map +1 -0
- package/page-layout.js +23 -0
- package/page-layout.js.map +1 -0
- package/page-transition.d.ts +13 -0
- package/page-transition.d.ts.map +1 -0
- package/page-transition.js +14 -0
- package/page-transition.js.map +1 -0
- package/paged-table.d.ts +54 -0
- package/paged-table.d.ts.map +1 -0
- package/paged-table.js +74 -0
- package/paged-table.js.map +1 -0
- package/popover.d.ts +16 -0
- package/popover.d.ts.map +1 -0
- package/popover.js +99 -0
- package/popover.js.map +1 -0
- package/progress.d.ts +7 -0
- package/progress.d.ts.map +1 -0
- package/progress.js +8 -0
- package/progress.js.map +1 -0
- package/prompt-dialog.d.ts +21 -0
- package/prompt-dialog.d.ts.map +1 -0
- package/prompt-dialog.js +109 -0
- package/prompt-dialog.js.map +1 -0
- package/record-list.d.ts +76 -0
- package/record-list.d.ts.map +1 -0
- package/record-list.js +51 -0
- package/record-list.js.map +1 -0
- package/search-select.d.ts +42 -0
- package/search-select.d.ts.map +1 -0
- package/search-select.js +194 -0
- package/search-select.js.map +1 -0
- package/select-options.d.ts +16 -0
- package/select-options.d.ts.map +1 -0
- package/select-options.js +119 -0
- package/select-options.js.map +1 -0
- package/select.d.ts +24 -0
- package/select.d.ts.map +1 -0
- package/select.js +65 -0
- package/select.js.map +1 -0
- package/separator.d.ts +6 -0
- package/separator.d.ts.map +1 -0
- package/separator.js +7 -0
- package/separator.js.map +1 -0
- package/settings-layout.d.ts +79 -0
- package/settings-layout.d.ts.map +1 -0
- package/settings-layout.js +74 -0
- package/settings-layout.js.map +1 -0
- package/sidebar-nav.d.ts +67 -0
- package/sidebar-nav.d.ts.map +1 -0
- package/sidebar-nav.js +291 -0
- package/sidebar-nav.js.map +1 -0
- package/signature-pad.d.ts +16 -0
- package/signature-pad.d.ts.map +1 -0
- package/signature-pad.js +355 -0
- package/signature-pad.js.map +1 -0
- package/skeleton.d.ts +9 -0
- package/skeleton.d.ts.map +1 -0
- package/skeleton.js +12 -0
- package/skeleton.js.map +1 -0
- package/slider.d.ts +13 -0
- package/slider.d.ts.map +1 -0
- package/slider.js +15 -0
- package/slider.js.map +1 -0
- package/sortable-th.d.ts +28 -0
- package/sortable-th.d.ts.map +1 -0
- package/sortable-th.js +35 -0
- package/sortable-th.js.map +1 -0
- package/sparkline.d.ts +16 -0
- package/sparkline.d.ts.map +1 -0
- package/sparkline.js +46 -0
- package/sparkline.js.map +1 -0
- package/spinner.d.ts +11 -0
- package/spinner.d.ts.map +1 -0
- package/spinner.js +11 -0
- package/spinner.js.map +1 -0
- package/styles.css +166 -0
- package/subtab-nav.d.ts +17 -0
- package/subtab-nav.d.ts.map +1 -0
- package/subtab-nav.js +11 -0
- package/subtab-nav.js.map +1 -0
- package/switch.d.ts +9 -0
- package/switch.d.ts.map +1 -0
- package/switch.js +11 -0
- package/switch.js.map +1 -0
- package/tab-content.d.ts +24 -0
- package/tab-content.d.ts.map +1 -0
- package/tab-content.js +26 -0
- package/tab-content.js.map +1 -0
- package/table.d.ts +28 -0
- package/table.d.ts.map +1 -0
- package/table.js +21 -0
- package/table.js.map +1 -0
- package/tabs.d.ts +18 -0
- package/tabs.d.ts.map +1 -0
- package/tabs.js +18 -0
- package/tabs.js.map +1 -0
- package/text-context.d.ts +9 -0
- package/text-context.d.ts.map +1 -0
- package/text-context.js +12 -0
- package/text-context.js.map +1 -0
- package/textarea.d.ts +4 -0
- package/textarea.d.ts.map +1 -0
- package/textarea.js +6 -0
- package/textarea.js.map +1 -0
- package/theme-script.d.ts +3 -0
- package/theme-script.d.ts.map +1 -0
- package/theme-script.js +6 -0
- package/theme-script.js.map +1 -0
- package/theme.d.ts +28 -0
- package/theme.d.ts.map +1 -0
- package/theme.js +92 -0
- package/theme.js.map +1 -0
- package/toast.d.ts +52 -0
- package/toast.d.ts.map +1 -0
- package/toast.js +178 -0
- package/toast.js.map +1 -0
- package/tooltip.d.ts +12 -0
- package/tooltip.d.ts.map +1 -0
- package/tooltip.js +14 -0
- package/tooltip.js.map +1 -0
- package/top-nav.d.ts +18 -0
- package/top-nav.d.ts.map +1 -0
- package/top-nav.js +160 -0
- package/top-nav.js.map +1 -0
- package/upload-limits.d.ts +4 -0
- package/upload-limits.d.ts.map +1 -0
- package/upload-limits.js +21 -0
- package/upload-limits.js.map +1 -0
- package/use-element-size.d.ts +7 -0
- package/use-element-size.d.ts.map +1 -0
- package/use-element-size.js +21 -0
- package/use-element-size.js.map +1 -0
- package/utils.d.ts +4 -0
- package/utils.d.ts.map +1 -0
- package/utils.js +7 -0
- package/utils.js.map +1 -0
- package/workspace-launcher.d.ts +50 -0
- package/workspace-launcher.d.ts.map +1 -0
- package/workspace-launcher.js +127 -0
- package/workspace-launcher.js.map +1 -0
package/top-nav.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
// NetSuite-style top menu bar generalized around a serialized navigation tree.
|
|
4
|
+
// The same registry drives the sidebar, mobile drawer, and mobile tabs.
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { ChevronDown, ChevronRight } from 'lucide-react';
|
|
7
|
+
import { visibleTopNavGroupCount } from './nav-overflow.js';
|
|
8
|
+
import { Popover } from './popover.js';
|
|
9
|
+
import { findActiveNavHref, groupContainsActiveHref, NavIcon, toBlocks, } from './sidebar-nav.js';
|
|
10
|
+
import { defaultLinkRender } from './link-context.js';
|
|
11
|
+
import { cn } from './utils.js';
|
|
12
|
+
const MORE_MENU_INDEX = -1;
|
|
13
|
+
export function TopNav({ groups, pathname, linkRender = defaultLinkRender, moreLabel = 'More', ariaLabel = 'Primary navigation', className, itemClassName, activeItemClassName, inactiveItemClassName, }) {
|
|
14
|
+
const activeHref = findActiveNavHref(pathname, groups);
|
|
15
|
+
const [openIndex, setOpenIndex] = React.useState(null);
|
|
16
|
+
const [visibleCount, setVisibleCount] = React.useState(groups.length);
|
|
17
|
+
const navRef = React.useRef(null);
|
|
18
|
+
const measurementRef = React.useRef(null);
|
|
19
|
+
const closeTimer = React.useRef(null);
|
|
20
|
+
React.useLayoutEffect(() => {
|
|
21
|
+
const nav = navRef.current;
|
|
22
|
+
const measurement = measurementRef.current;
|
|
23
|
+
if (!nav || !measurement)
|
|
24
|
+
return;
|
|
25
|
+
let active = true;
|
|
26
|
+
function measure() {
|
|
27
|
+
if (!active)
|
|
28
|
+
return;
|
|
29
|
+
const groupWidths = Array.from(measurement.querySelectorAll('[data-top-nav-measure="group"]'), (element) => element.getBoundingClientRect().width);
|
|
30
|
+
const moreWidth = measurement
|
|
31
|
+
.querySelector('[data-top-nav-measure="more"]')
|
|
32
|
+
?.getBoundingClientRect().width ?? 0;
|
|
33
|
+
const gap = Number.parseFloat(window.getComputedStyle(measurement).columnGap) || 0;
|
|
34
|
+
const next = visibleTopNavGroupCount({
|
|
35
|
+
availableWidth: nav.clientWidth,
|
|
36
|
+
groupWidths,
|
|
37
|
+
moreWidth,
|
|
38
|
+
gap,
|
|
39
|
+
});
|
|
40
|
+
setVisibleCount((current) => (current === next ? current : next));
|
|
41
|
+
}
|
|
42
|
+
measure();
|
|
43
|
+
const frame = window.requestAnimationFrame(measure);
|
|
44
|
+
const timer = window.setTimeout(measure, 0);
|
|
45
|
+
const observer = new ResizeObserver(measure);
|
|
46
|
+
observer.observe(nav);
|
|
47
|
+
observer.observe(measurement);
|
|
48
|
+
window.addEventListener('resize', measure);
|
|
49
|
+
void document.fonts?.ready.then(measure);
|
|
50
|
+
return () => {
|
|
51
|
+
active = false;
|
|
52
|
+
window.cancelAnimationFrame(frame);
|
|
53
|
+
window.clearTimeout(timer);
|
|
54
|
+
window.removeEventListener('resize', measure);
|
|
55
|
+
observer.disconnect();
|
|
56
|
+
};
|
|
57
|
+
}, [groups, moreLabel]);
|
|
58
|
+
React.useEffect(() => {
|
|
59
|
+
if (openIndex !== null &&
|
|
60
|
+
((openIndex === MORE_MENU_INDEX && visibleCount === groups.length) || openIndex >= visibleCount)) {
|
|
61
|
+
setOpenIndex(null);
|
|
62
|
+
}
|
|
63
|
+
}, [groups.length, openIndex, visibleCount]);
|
|
64
|
+
React.useEffect(() => () => {
|
|
65
|
+
if (closeTimer.current)
|
|
66
|
+
window.clearTimeout(closeTimer.current);
|
|
67
|
+
}, []);
|
|
68
|
+
function enterMenu(index) {
|
|
69
|
+
if (closeTimer.current) {
|
|
70
|
+
window.clearTimeout(closeTimer.current);
|
|
71
|
+
closeTimer.current = null;
|
|
72
|
+
}
|
|
73
|
+
setOpenIndex(index);
|
|
74
|
+
}
|
|
75
|
+
function scheduleClose() {
|
|
76
|
+
if (closeTimer.current)
|
|
77
|
+
window.clearTimeout(closeTimer.current);
|
|
78
|
+
closeTimer.current = window.setTimeout(() => setOpenIndex(null), 150);
|
|
79
|
+
}
|
|
80
|
+
const visibleGroups = groups.slice(0, visibleCount);
|
|
81
|
+
const overflowGroups = groups.slice(visibleCount);
|
|
82
|
+
const moreOpen = openIndex === MORE_MENU_INDEX;
|
|
83
|
+
const moreActive = overflowGroups.some((group) => groupContainsActiveHref(group, activeHref));
|
|
84
|
+
return (_jsxs("nav", { ref: navRef, "aria-label": ariaLabel, className: cn('relative hidden min-w-0 flex-1 items-center gap-0.5 overflow-hidden lg:flex', className), children: [_jsxs("div", { ref: measurementRef, "aria-hidden": true, className: "pointer-events-none invisible absolute flex w-max items-center gap-0.5", children: [groups.map((group, index) => (_jsxs("span", { "data-top-nav-measure": "group", className: cn('flex h-14 shrink-0 items-center gap-1 whitespace-nowrap px-2 text-sm font-medium', itemClassName), children: [group.items.length === 1 ? group.items[0].label : group.label, group.items.length === 1 ? null : _jsx(ChevronDown, { size: 12, className: "opacity-50" })] }, `${group.label}-${index}`))), _jsxs("span", { "data-top-nav-measure": "more", className: cn('flex h-14 shrink-0 items-center gap-1 whitespace-nowrap px-2 text-sm font-medium', itemClassName), children: [moreLabel, _jsx(ChevronDown, { size: 12, className: "opacity-50" })] })] }), visibleGroups.map((group, index) => {
|
|
85
|
+
const open = openIndex === index;
|
|
86
|
+
const groupActive = groupContainsActiveHref(group, activeHref);
|
|
87
|
+
const only = group.items.length === 1 ? group.items[0] : undefined;
|
|
88
|
+
if (only) {
|
|
89
|
+
return (_jsx(React.Fragment, { children: linkRender({
|
|
90
|
+
href: only.href,
|
|
91
|
+
className: cn('flex h-14 shrink-0 items-center whitespace-nowrap px-2 text-sm font-medium transition-colors', itemClassName, activeHref === only.href
|
|
92
|
+
? cn('text-primary', activeItemClassName)
|
|
93
|
+
: cn('text-fg-muted hover:text-fg', inactiveItemClassName)),
|
|
94
|
+
children: only.label,
|
|
95
|
+
}) }, group.id ?? `${group.label}-${index}`));
|
|
96
|
+
}
|
|
97
|
+
return (_jsx(Popover, { open: open, onOpenChange: (next) => (next ? enterMenu(index) : setOpenIndex(null)), align: "start", className: "min-w-[15rem] py-1.5", trigger: _jsxs("button", { type: "button", "aria-haspopup": "menu", "aria-expanded": open, "aria-current": groupActive ? 'true' : undefined, onMouseEnter: () => enterMenu(index), onMouseLeave: scheduleClose, onClick: () => enterMenu(index), className: cn('flex h-14 shrink-0 items-center gap-1 whitespace-nowrap px-2 text-sm font-medium transition-colors', itemClassName, groupActive
|
|
98
|
+
? cn('text-primary', activeItemClassName)
|
|
99
|
+
: cn('text-fg-muted hover:text-fg', inactiveItemClassName)), children: [group.label, _jsx(ChevronDown, { size: 12, className: "opacity-50" })] }), children: _jsx(GroupMenu, { items: group.items, activeHref: activeHref, linkRender: linkRender, onEnter: () => enterMenu(index), onLeave: scheduleClose, onSelect: () => setOpenIndex(null) }) }, group.id ?? `${group.label}-${index}`));
|
|
100
|
+
}), overflowGroups.length > 0 ? (_jsx(Popover, { open: moreOpen, onOpenChange: (open) => (open ? enterMenu(MORE_MENU_INDEX) : setOpenIndex(null)), align: "start", className: "min-w-[15rem] py-1.5", trigger: _jsxs("button", { type: "button", "aria-haspopup": "menu", "aria-expanded": moreOpen, "aria-current": moreActive ? 'true' : undefined, onMouseEnter: () => enterMenu(MORE_MENU_INDEX), onMouseLeave: scheduleClose, onClick: () => enterMenu(MORE_MENU_INDEX), className: cn('flex h-14 shrink-0 items-center gap-1 whitespace-nowrap px-2 text-sm font-medium transition-colors', itemClassName, moreActive
|
|
101
|
+
? cn('text-primary', activeItemClassName)
|
|
102
|
+
: cn('text-fg-muted hover:text-fg', inactiveItemClassName)), children: [moreLabel, _jsx(ChevronDown, { size: 12, className: "opacity-50" })] }), children: _jsx("div", { role: "menu", onMouseEnter: () => enterMenu(MORE_MENU_INDEX), onMouseLeave: scheduleClose, onClick: () => setOpenIndex(null), children: overflowGroups.map((group, index) => group.items.length === 1 ? (_jsx(MenuItemLink, { item: group.items[0], active: activeHref === group.items[0].href, linkRender: linkRender }, group.id ?? `${group.label}-${visibleCount + index}`)) : (_jsx(OverflowGroupRow, { group: group, activeHref: activeHref, linkRender: linkRender }, group.id ?? `${group.label}-${visibleCount + index}`))) }) })) : null] }));
|
|
103
|
+
}
|
|
104
|
+
function GroupMenu({ items, activeHref, linkRender, onEnter, onLeave, onSelect, }) {
|
|
105
|
+
const blocks = toBlocks(items);
|
|
106
|
+
const sectioned = blocks.some((block) => block.kind === 'subgroup');
|
|
107
|
+
return (_jsx("div", { role: "menu", onMouseEnter: onEnter, onMouseLeave: onLeave, onClick: onSelect, className: cn('app-scroll max-h-[min(36rem,calc(100vh-5rem))] overflow-y-auto', sectioned && 'grid w-[32rem] grid-cols-2 gap-x-2 gap-y-1 p-1'), children: blocks.map((block, blockIndex) => block.kind === 'item' ? (_jsx(MenuItemLink, { item: block.item, active: activeHref === block.item.href, linkRender: linkRender }, block.item.href)) : (_jsx(MenuSection, { label: block.label, href: block.href, iconKey: block.iconKey, items: block.items, activeHref: activeHref, linkRender: linkRender }, `sub-${block.label}-${blockIndex}`))) }));
|
|
108
|
+
}
|
|
109
|
+
function OverflowGroupRow({ group, activeHref, linkRender, }) {
|
|
110
|
+
const [open, setOpen] = React.useState(false);
|
|
111
|
+
const [flip, setFlip] = React.useState(false);
|
|
112
|
+
const rowRef = React.useRef(null);
|
|
113
|
+
const closeTimer = React.useRef(null);
|
|
114
|
+
const active = groupContainsActiveHref(group, activeHref);
|
|
115
|
+
React.useEffect(() => () => {
|
|
116
|
+
if (closeTimer.current)
|
|
117
|
+
window.clearTimeout(closeTimer.current);
|
|
118
|
+
}, []);
|
|
119
|
+
function openMenu() {
|
|
120
|
+
if (closeTimer.current) {
|
|
121
|
+
window.clearTimeout(closeTimer.current);
|
|
122
|
+
closeTimer.current = null;
|
|
123
|
+
}
|
|
124
|
+
const rect = rowRef.current?.getBoundingClientRect();
|
|
125
|
+
const panelWidth = group.items.some((item) => item.subgroup) ? 528 : 248;
|
|
126
|
+
setFlip(rect ? rect.right + panelWidth > window.innerWidth : false);
|
|
127
|
+
setOpen(true);
|
|
128
|
+
}
|
|
129
|
+
function scheduleClose() {
|
|
130
|
+
if (closeTimer.current)
|
|
131
|
+
window.clearTimeout(closeTimer.current);
|
|
132
|
+
closeTimer.current = window.setTimeout(() => setOpen(false), 150);
|
|
133
|
+
}
|
|
134
|
+
return (_jsxs("div", { ref: rowRef, className: "relative", onMouseEnter: openMenu, onMouseLeave: scheduleClose, children: [_jsxs("button", { type: "button", role: "menuitem", "aria-haspopup": "menu", "aria-expanded": open, onClick: (event) => {
|
|
135
|
+
event.stopPropagation();
|
|
136
|
+
setOpen((current) => !current);
|
|
137
|
+
}, className: cn('flex w-full items-center gap-2.5 px-3 py-1.5 text-sm transition-colors', active ? 'text-primary' : 'text-fg-muted hover:bg-surface-hover hover:text-fg'), children: [_jsx(NavIcon, { iconKey: group.iconKey, icon: group.icon, size: 14, className: "shrink-0 text-fg-subtle" }), _jsx("span", { className: "flex-1 truncate text-left", children: group.label }), _jsx(ChevronRight, { size: 12, className: cn('shrink-0 opacity-50', flip && open && 'rotate-180') })] }), open ? (_jsx("div", { role: "menu", className: cn('absolute top-0 z-10 min-w-[15rem] rounded-md border border-border bg-elevated py-1.5 shadow-lg', flip ? 'right-full -mr-1' : 'left-full -ml-1'), children: _jsx(GroupMenu, { items: group.items, activeHref: activeHref, linkRender: linkRender }) })) : null] }));
|
|
138
|
+
}
|
|
139
|
+
function MenuItemLink({ item, active, linkRender, }) {
|
|
140
|
+
return (_jsx(React.Fragment, { children: linkRender({
|
|
141
|
+
href: item.href,
|
|
142
|
+
role: 'menuitem',
|
|
143
|
+
ariaCurrent: active ? 'page' : undefined,
|
|
144
|
+
dataWalkthrough: `nav:${item.href}`,
|
|
145
|
+
className: cn('group flex items-center gap-2.5 px-3 py-1.5 text-sm transition-colors', active ? 'bg-primary-subtle text-fg' : 'text-fg-muted hover:bg-surface-hover hover:text-fg'),
|
|
146
|
+
children: (_jsxs(_Fragment, { children: [_jsx(NavIcon, { iconKey: item.iconKey, icon: item.icon, size: 15, className: cn('shrink-0 transition-colors', active ? 'text-primary' : 'text-fg-subtle group-hover:text-fg') }), _jsx("span", { className: "truncate", children: item.label })] })),
|
|
147
|
+
}) }));
|
|
148
|
+
}
|
|
149
|
+
function MenuSection({ label, href, iconKey, items, activeHref, linkRender, }) {
|
|
150
|
+
const selfActive = href != null && activeHref === href;
|
|
151
|
+
return (_jsxs("div", { role: "group", "aria-label": label, className: "min-w-0 rounded-md py-1", children: [href ? (_jsx(React.Fragment, { children: linkRender({
|
|
152
|
+
href,
|
|
153
|
+
role: 'menuitem',
|
|
154
|
+
ariaCurrent: selfActive ? 'page' : undefined,
|
|
155
|
+
dataWalkthrough: `nav:${href}`,
|
|
156
|
+
className: cn('flex items-center gap-1.5 px-3 pb-1.5 text-[11px] font-semibold tracking-wide uppercase transition-colors', selfActive ? 'text-primary' : 'text-fg-subtle hover:text-fg'),
|
|
157
|
+
children: (_jsxs(_Fragment, { children: [iconKey ? _jsx(NavIcon, { iconKey: iconKey, size: 13 }) : null, label] })),
|
|
158
|
+
}) })) : (_jsx("div", { className: "px-3 pb-1.5 text-[11px] font-semibold tracking-wide text-fg-subtle uppercase", children: label })), items.map((item) => (_jsx(MenuItemLink, { item: item, active: activeHref === item.href, linkRender: linkRender }, item.href)))] }));
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=top-nav.js.map
|
package/top-nav.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"top-nav.js","sourceRoot":"","sources":["../src/top-nav.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\n// NetSuite-style top menu bar generalized around a serialized navigation tree.\n// The same registry drives the sidebar, mobile drawer, and mobile tabs.\n\nimport * as React from 'react'\nimport { ChevronDown, ChevronRight } from 'lucide-react'\nimport { visibleTopNavGroupCount } from './nav-overflow'\nimport { Popover } from './popover'\nimport {\n findActiveNavHref,\n groupContainsActiveHref,\n NavIcon,\n toBlocks,\n type SidebarNavGroup,\n type SidebarNavItem,\n} from './sidebar-nav'\nimport { defaultLinkRender, type LinkRender } from './link-context'\nimport { cn } from './utils'\n\nconst MORE_MENU_INDEX = -1\n\nexport function TopNav({\n groups,\n pathname,\n linkRender = defaultLinkRender,\n moreLabel = 'More',\n ariaLabel = 'Primary navigation',\n className,\n itemClassName,\n activeItemClassName,\n inactiveItemClassName,\n}: {\n groups: SidebarNavGroup[]\n pathname: string\n linkRender?: LinkRender\n moreLabel?: string\n ariaLabel?: string\n className?: string\n /** Shared typography/spacing overrides for every top-level navigation item. */\n itemClassName?: string\n /** Overrides applied only to the active top-level item. */\n activeItemClassName?: string\n /** Overrides applied only to inactive top-level items. */\n inactiveItemClassName?: string\n}) {\n const activeHref = findActiveNavHref(pathname, groups)\n const [openIndex, setOpenIndex] = React.useState<number | null>(null)\n const [visibleCount, setVisibleCount] = React.useState(groups.length)\n const navRef = React.useRef<HTMLElement>(null)\n const measurementRef = React.useRef<HTMLDivElement>(null)\n const closeTimer = React.useRef<number | null>(null)\n\n React.useLayoutEffect(() => {\n const nav = navRef.current\n const measurement = measurementRef.current\n if (!nav || !measurement) return\n let active = true\n\n function measure() {\n if (!active) return\n const groupWidths = Array.from(\n measurement!.querySelectorAll<HTMLElement>('[data-top-nav-measure=\"group\"]'),\n (element) => element.getBoundingClientRect().width,\n )\n const moreWidth =\n measurement!\n .querySelector<HTMLElement>('[data-top-nav-measure=\"more\"]')\n ?.getBoundingClientRect().width ?? 0\n const gap = Number.parseFloat(window.getComputedStyle(measurement!).columnGap) || 0\n const next = visibleTopNavGroupCount({\n availableWidth: nav!.clientWidth,\n groupWidths,\n moreWidth,\n gap,\n })\n setVisibleCount((current) => (current === next ? current : next))\n }\n\n measure()\n const frame = window.requestAnimationFrame(measure)\n const timer = window.setTimeout(measure, 0)\n const observer = new ResizeObserver(measure)\n observer.observe(nav)\n observer.observe(measurement)\n window.addEventListener('resize', measure)\n void document.fonts?.ready.then(measure)\n return () => {\n active = false\n window.cancelAnimationFrame(frame)\n window.clearTimeout(timer)\n window.removeEventListener('resize', measure)\n observer.disconnect()\n }\n }, [groups, moreLabel])\n\n React.useEffect(() => {\n if (\n openIndex !== null &&\n ((openIndex === MORE_MENU_INDEX && visibleCount === groups.length) || openIndex >= visibleCount)\n ) {\n setOpenIndex(null)\n }\n }, [groups.length, openIndex, visibleCount])\n\n React.useEffect(\n () => () => {\n if (closeTimer.current) window.clearTimeout(closeTimer.current)\n },\n [],\n )\n\n function enterMenu(index: number) {\n if (closeTimer.current) {\n window.clearTimeout(closeTimer.current)\n closeTimer.current = null\n }\n setOpenIndex(index)\n }\n\n function scheduleClose() {\n if (closeTimer.current) window.clearTimeout(closeTimer.current)\n closeTimer.current = window.setTimeout(() => setOpenIndex(null), 150)\n }\n\n const visibleGroups = groups.slice(0, visibleCount)\n const overflowGroups = groups.slice(visibleCount)\n const moreOpen = openIndex === MORE_MENU_INDEX\n const moreActive = overflowGroups.some((group) => groupContainsActiveHref(group, activeHref))\n\n return (\n <nav\n ref={navRef}\n aria-label={ariaLabel}\n className={cn('relative hidden min-w-0 flex-1 items-center gap-0.5 overflow-hidden lg:flex', className)}\n >\n <div\n ref={measurementRef}\n aria-hidden\n className=\"pointer-events-none invisible absolute flex w-max items-center gap-0.5\"\n >\n {groups.map((group, index) => (\n <span\n key={`${group.label}-${index}`}\n data-top-nav-measure=\"group\"\n className={cn(\n 'flex h-14 shrink-0 items-center gap-1 whitespace-nowrap px-2 text-sm font-medium',\n itemClassName,\n )}\n >\n {group.items.length === 1 ? group.items[0]!.label : group.label}\n {group.items.length === 1 ? null : <ChevronDown size={12} className=\"opacity-50\" />}\n </span>\n ))}\n <span\n data-top-nav-measure=\"more\"\n className={cn(\n 'flex h-14 shrink-0 items-center gap-1 whitespace-nowrap px-2 text-sm font-medium',\n itemClassName,\n )}\n >\n {moreLabel}\n <ChevronDown size={12} className=\"opacity-50\" />\n </span>\n </div>\n\n {visibleGroups.map((group, index) => {\n const open = openIndex === index\n const groupActive = groupContainsActiveHref(group, activeHref)\n const only = group.items.length === 1 ? group.items[0] : undefined\n if (only) {\n return (\n <React.Fragment key={group.id ?? `${group.label}-${index}`}>\n {linkRender({\n href: only.href,\n className: cn(\n 'flex h-14 shrink-0 items-center whitespace-nowrap px-2 text-sm font-medium transition-colors',\n itemClassName,\n activeHref === only.href\n ? cn('text-primary', activeItemClassName)\n : cn('text-fg-muted hover:text-fg', inactiveItemClassName),\n ),\n children: only.label,\n })}\n </React.Fragment>\n )\n }\n return (\n <Popover\n key={group.id ?? `${group.label}-${index}`}\n open={open}\n onOpenChange={(next) => (next ? enterMenu(index) : setOpenIndex(null))}\n align=\"start\"\n className=\"min-w-[15rem] py-1.5\"\n trigger={\n <button\n type=\"button\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-current={groupActive ? 'true' : undefined}\n onMouseEnter={() => enterMenu(index)}\n onMouseLeave={scheduleClose}\n onClick={() => enterMenu(index)}\n className={cn(\n 'flex h-14 shrink-0 items-center gap-1 whitespace-nowrap px-2 text-sm font-medium transition-colors',\n itemClassName,\n groupActive\n ? cn('text-primary', activeItemClassName)\n : cn('text-fg-muted hover:text-fg', inactiveItemClassName),\n )}\n >\n {group.label}\n <ChevronDown size={12} className=\"opacity-50\" />\n </button>\n }\n >\n <GroupMenu\n items={group.items}\n activeHref={activeHref}\n linkRender={linkRender}\n onEnter={() => enterMenu(index)}\n onLeave={scheduleClose}\n onSelect={() => setOpenIndex(null)}\n />\n </Popover>\n )\n })}\n\n {overflowGroups.length > 0 ? (\n <Popover\n open={moreOpen}\n onOpenChange={(open) => (open ? enterMenu(MORE_MENU_INDEX) : setOpenIndex(null))}\n align=\"start\"\n className=\"min-w-[15rem] py-1.5\"\n trigger={\n <button\n type=\"button\"\n aria-haspopup=\"menu\"\n aria-expanded={moreOpen}\n aria-current={moreActive ? 'true' : undefined}\n onMouseEnter={() => enterMenu(MORE_MENU_INDEX)}\n onMouseLeave={scheduleClose}\n onClick={() => enterMenu(MORE_MENU_INDEX)}\n className={cn(\n 'flex h-14 shrink-0 items-center gap-1 whitespace-nowrap px-2 text-sm font-medium transition-colors',\n itemClassName,\n moreActive\n ? cn('text-primary', activeItemClassName)\n : cn('text-fg-muted hover:text-fg', inactiveItemClassName),\n )}\n >\n {moreLabel}\n <ChevronDown size={12} className=\"opacity-50\" />\n </button>\n }\n >\n <div\n role=\"menu\"\n onMouseEnter={() => enterMenu(MORE_MENU_INDEX)}\n onMouseLeave={scheduleClose}\n onClick={() => setOpenIndex(null)}\n >\n {overflowGroups.map((group, index) =>\n group.items.length === 1 ? (\n <MenuItemLink\n key={group.id ?? `${group.label}-${visibleCount + index}`}\n item={group.items[0]!}\n active={activeHref === group.items[0]!.href}\n linkRender={linkRender}\n />\n ) : (\n <OverflowGroupRow\n key={group.id ?? `${group.label}-${visibleCount + index}`}\n group={group}\n activeHref={activeHref}\n linkRender={linkRender}\n />\n ),\n )}\n </div>\n </Popover>\n ) : null}\n </nav>\n )\n}\n\nfunction GroupMenu({\n items,\n activeHref,\n linkRender,\n onEnter,\n onLeave,\n onSelect,\n}: {\n items: SidebarNavItem[]\n activeHref: string | null\n linkRender: LinkRender\n onEnter?: () => void\n onLeave?: () => void\n onSelect?: () => void\n}) {\n const blocks = toBlocks(items)\n const sectioned = blocks.some((block) => block.kind === 'subgroup')\n return (\n <div\n role=\"menu\"\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n onClick={onSelect}\n className={cn(\n 'app-scroll max-h-[min(36rem,calc(100vh-5rem))] overflow-y-auto',\n sectioned && 'grid w-[32rem] grid-cols-2 gap-x-2 gap-y-1 p-1',\n )}\n >\n {blocks.map((block, blockIndex) =>\n block.kind === 'item' ? (\n <MenuItemLink\n key={block.item.href}\n item={block.item}\n active={activeHref === block.item.href}\n linkRender={linkRender}\n />\n ) : (\n <MenuSection\n key={`sub-${block.label}-${blockIndex}`}\n label={block.label}\n href={block.href}\n iconKey={block.iconKey}\n items={block.items}\n activeHref={activeHref}\n linkRender={linkRender}\n />\n ),\n )}\n </div>\n )\n}\n\nfunction OverflowGroupRow({\n group,\n activeHref,\n linkRender,\n}: {\n group: SidebarNavGroup\n activeHref: string | null\n linkRender: LinkRender\n}) {\n const [open, setOpen] = React.useState(false)\n const [flip, setFlip] = React.useState(false)\n const rowRef = React.useRef<HTMLDivElement>(null)\n const closeTimer = React.useRef<number | null>(null)\n const active = groupContainsActiveHref(group, activeHref)\n\n React.useEffect(\n () => () => {\n if (closeTimer.current) window.clearTimeout(closeTimer.current)\n },\n [],\n )\n\n function openMenu() {\n if (closeTimer.current) {\n window.clearTimeout(closeTimer.current)\n closeTimer.current = null\n }\n const rect = rowRef.current?.getBoundingClientRect()\n const panelWidth = group.items.some((item) => item.subgroup) ? 528 : 248\n setFlip(rect ? rect.right + panelWidth > window.innerWidth : false)\n setOpen(true)\n }\n\n function scheduleClose() {\n if (closeTimer.current) window.clearTimeout(closeTimer.current)\n closeTimer.current = window.setTimeout(() => setOpen(false), 150)\n }\n\n return (\n <div ref={rowRef} className=\"relative\" onMouseEnter={openMenu} onMouseLeave={scheduleClose}>\n <button\n type=\"button\"\n role=\"menuitem\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n onClick={(event) => {\n event.stopPropagation()\n setOpen((current) => !current)\n }}\n className={cn(\n 'flex w-full items-center gap-2.5 px-3 py-1.5 text-sm transition-colors',\n active ? 'text-primary' : 'text-fg-muted hover:bg-surface-hover hover:text-fg',\n )}\n >\n <NavIcon\n iconKey={group.iconKey}\n icon={group.icon}\n size={14}\n className=\"shrink-0 text-fg-subtle\"\n />\n <span className=\"flex-1 truncate text-left\">{group.label}</span>\n <ChevronRight size={12} className={cn('shrink-0 opacity-50', flip && open && 'rotate-180')} />\n </button>\n {open ? (\n <div\n role=\"menu\"\n className={cn(\n 'absolute top-0 z-10 min-w-[15rem] rounded-md border border-border bg-elevated py-1.5 shadow-lg',\n flip ? 'right-full -mr-1' : 'left-full -ml-1',\n )}\n >\n <GroupMenu items={group.items} activeHref={activeHref} linkRender={linkRender} />\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction MenuItemLink({\n item,\n active,\n linkRender,\n}: {\n item: SidebarNavItem\n active: boolean\n linkRender: LinkRender\n}) {\n return (\n <React.Fragment>\n {linkRender({\n href: item.href,\n role: 'menuitem',\n ariaCurrent: active ? 'page' : undefined,\n dataWalkthrough: `nav:${item.href}`,\n className: cn(\n 'group flex items-center gap-2.5 px-3 py-1.5 text-sm transition-colors',\n active ? 'bg-primary-subtle text-fg' : 'text-fg-muted hover:bg-surface-hover hover:text-fg',\n ),\n children: (\n <>\n <NavIcon\n iconKey={item.iconKey}\n icon={item.icon}\n size={15}\n className={cn(\n 'shrink-0 transition-colors',\n active ? 'text-primary' : 'text-fg-subtle group-hover:text-fg',\n )}\n />\n <span className=\"truncate\">{item.label}</span>\n </>\n ),\n })}\n </React.Fragment>\n )\n}\n\nfunction MenuSection({\n label,\n href,\n iconKey,\n items,\n activeHref,\n linkRender,\n}: {\n label: string\n href?: string\n iconKey?: string\n items: SidebarNavItem[]\n activeHref: string | null\n linkRender: LinkRender\n}) {\n const selfActive = href != null && activeHref === href\n return (\n <div role=\"group\" aria-label={label} className=\"min-w-0 rounded-md py-1\">\n {href ? (\n <React.Fragment>\n {linkRender({\n href,\n role: 'menuitem',\n ariaCurrent: selfActive ? 'page' : undefined,\n dataWalkthrough: `nav:${href}`,\n className: cn(\n 'flex items-center gap-1.5 px-3 pb-1.5 text-[11px] font-semibold tracking-wide uppercase transition-colors',\n selfActive ? 'text-primary' : 'text-fg-subtle hover:text-fg',\n ),\n children: (\n <>\n {iconKey ? <NavIcon iconKey={iconKey} size={13} /> : null}\n {label}\n </>\n ),\n })}\n </React.Fragment>\n ) : (\n <div className=\"px-3 pb-1.5 text-[11px] font-semibold tracking-wide text-fg-subtle uppercase\">\n {label}\n </div>\n )}\n {items.map((item) => (\n <MenuItemLink\n key={item.href}\n item={item}\n active={activeHref === item.href}\n linkRender={linkRender}\n />\n ))}\n </div>\n )\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type AttachmentKind = 'image' | 'document' | 'video' | 'audio' | 'signature' | 'other';
|
|
2
|
+
export declare function defaultMaxUploadBytes(kind: AttachmentKind): number;
|
|
3
|
+
export declare function formatUploadSizeLimit(sizeBytes: number): string;
|
|
4
|
+
//# sourceMappingURL=upload-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-limits.d.ts","sourceRoot":"","sources":["../src/upload-limits.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GACtB,OAAO,GACP,UAAU,GACV,OAAO,GACP,OAAO,GACP,WAAW,GACX,OAAO,CAAA;AAaX,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAElE;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAK/D"}
|
package/upload-limits.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Client mirror of the storage protocol's per-kind ceilings. Hosts still
|
|
2
|
+
// enforce their authoritative limits in requestUploadAction.
|
|
3
|
+
const DEFAULT_MAX_BY_KIND = {
|
|
4
|
+
image: 50 * 1024 * 1024,
|
|
5
|
+
signature: 10 * 1024 * 1024,
|
|
6
|
+
audio: 200 * 1024 * 1024,
|
|
7
|
+
document: 1024 * 1024 * 1024,
|
|
8
|
+
video: 500 * 1024 * 1024,
|
|
9
|
+
other: 500 * 1024 * 1024,
|
|
10
|
+
};
|
|
11
|
+
export function defaultMaxUploadBytes(kind) {
|
|
12
|
+
return DEFAULT_MAX_BY_KIND[kind];
|
|
13
|
+
}
|
|
14
|
+
export function formatUploadSizeLimit(sizeBytes) {
|
|
15
|
+
const gibibyte = 1024 * 1024 * 1024;
|
|
16
|
+
const mebibyte = 1024 * 1024;
|
|
17
|
+
if (sizeBytes % gibibyte === 0)
|
|
18
|
+
return `${sizeBytes / gibibyte} GiB`;
|
|
19
|
+
return `${Math.round(sizeBytes / mebibyte)} MiB`;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=upload-limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-limits.js","sourceRoot":"","sources":["../src/upload-limits.ts"],"names":[],"mappings":"AAQA,yEAAyE;AACzE,6DAA6D;AAC7D,MAAM,mBAAmB,GAAmC;IAC1D,KAAK,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;IACvB,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;IAC3B,KAAK,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI;IACxB,QAAQ,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI;IAC5B,KAAK,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI;IACxB,KAAK,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI;CACzB,CAAA;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAoB;IACxD,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAA;AAClC,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,SAAiB;IACrD,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACnC,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAA;IAC5B,IAAI,SAAS,GAAG,QAAQ,KAAK,CAAC;QAAE,OAAO,GAAG,SAAS,GAAG,QAAQ,MAAM,CAAA;IACpE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAA;AAClD,CAAC","sourcesContent":["export type AttachmentKind =\n | 'image'\n | 'document'\n | 'video'\n | 'audio'\n | 'signature'\n | 'other'\n\n// Client mirror of the storage protocol's per-kind ceilings. Hosts still\n// enforce their authoritative limits in requestUploadAction.\nconst DEFAULT_MAX_BY_KIND: Record<AttachmentKind, number> = {\n image: 50 * 1024 * 1024,\n signature: 10 * 1024 * 1024,\n audio: 200 * 1024 * 1024,\n document: 1024 * 1024 * 1024,\n video: 500 * 1024 * 1024,\n other: 500 * 1024 * 1024,\n}\n\nexport function defaultMaxUploadBytes(kind: AttachmentKind): number {\n return DEFAULT_MAX_BY_KIND[kind]\n}\n\nexport function formatUploadSizeLimit(sizeBytes: number): string {\n const gibibyte = 1024 * 1024 * 1024\n const mebibyte = 1024 * 1024\n if (sizeBytes % gibibyte === 0) return `${sizeBytes / gibibyte} GiB`\n return `${Math.round(sizeBytes / mebibyte)} MiB`\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/** Measured size of an element, kept current across resizes. */
|
|
3
|
+
export declare function useElementSize<T extends HTMLElement>(): [React.RefObject<T | null>, {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}];
|
|
7
|
+
//# sourceMappingURL=use-element-size.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-element-size.d.ts","sourceRoot":"","sources":["../src/use-element-size.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CActH"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
/** Measured size of an element, kept current across resizes. */
|
|
4
|
+
export function useElementSize() {
|
|
5
|
+
const ref = React.useRef(null);
|
|
6
|
+
const [size, setSize] = React.useState({ width: 0, height: 0 });
|
|
7
|
+
React.useEffect(() => {
|
|
8
|
+
const element = ref.current;
|
|
9
|
+
if (!element)
|
|
10
|
+
return;
|
|
11
|
+
const observer = new ResizeObserver((entries) => {
|
|
12
|
+
const rect = entries[0]?.contentRect;
|
|
13
|
+
if (rect)
|
|
14
|
+
setSize({ width: rect.width, height: rect.height });
|
|
15
|
+
});
|
|
16
|
+
observer.observe(element);
|
|
17
|
+
return () => observer.disconnect();
|
|
18
|
+
}, []);
|
|
19
|
+
return [ref, size];
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=use-element-size.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-element-size.js","sourceRoot":"","sources":["../src/use-element-size.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,gEAAgE;AAChE,MAAM,UAAU,cAAc;IAC5B,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAI,IAAI,CAAC,CAAA;IACjC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAA;IAC/D,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAA;QAC3B,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,CAAC,OAAO,EAAE,EAAE;YAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,WAAW,CAAA;YACpC,IAAI,IAAI;gBAAE,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QAC/D,CAAC,CAAC,CAAA;QACF,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QACzB,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAA;IACpC,CAAC,EAAE,EAAE,CAAC,CAAA;IACN,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;AACpB,CAAC","sourcesContent":["'use client'\n\nimport * as React from 'react'\n\n/** Measured size of an element, kept current across resizes. */\nexport function useElementSize<T extends HTMLElement>(): [React.RefObject<T | null>, { width: number; height: number }] {\n const ref = React.useRef<T>(null)\n const [size, setSize] = React.useState({ width: 0, height: 0 })\n React.useEffect(() => {\n const element = ref.current\n if (!element) return\n const observer = new ResizeObserver((entries) => {\n const rect = entries[0]?.contentRect\n if (rect) setSize({ width: rect.width, height: rect.height })\n })\n observer.observe(element)\n return () => observer.disconnect()\n }, [])\n return [ref, size]\n}\n"]}
|
package/utils.d.ts
ADDED
package/utils.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAG5C,mEAAmE;AACnE,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
|
package/utils.js
ADDED
package/utils.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,MAAM,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAExC,mEAAmE;AACnE,MAAM,UAAU,EAAE,CAAC,GAAG,MAAoB;IACxC,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;AAC9B,CAAC","sourcesContent":["import { clsx, type ClassValue } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\n/** Merge Tailwind class lists with correct conflict resolution. */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type BadgeProps } from './badge.js';
|
|
3
|
+
export type WorkspaceLaunchTone = 'indigo' | 'sky' | 'amber' | 'teal' | 'emerald' | 'violet' | 'rose' | 'lime' | 'cyan';
|
|
4
|
+
export type WorkspaceLaunchSegment = {
|
|
5
|
+
label: string;
|
|
6
|
+
value: number;
|
|
7
|
+
tone?: WorkspaceLaunchTone;
|
|
8
|
+
muted?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type WorkspaceLaunchBreakdown = {
|
|
11
|
+
label: string;
|
|
12
|
+
segments: WorkspaceLaunchSegment[];
|
|
13
|
+
};
|
|
14
|
+
export type WorkspaceLaunchItem<Id extends string = string> = {
|
|
15
|
+
id: Id;
|
|
16
|
+
title: string;
|
|
17
|
+
description: string;
|
|
18
|
+
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
|
19
|
+
metric: React.ReactNode;
|
|
20
|
+
metricLabel: string;
|
|
21
|
+
tone: WorkspaceLaunchTone;
|
|
22
|
+
breakdown?: WorkspaceLaunchBreakdown;
|
|
23
|
+
summary?: React.ReactNode;
|
|
24
|
+
summaryVariant?: BadgeProps['variant'];
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
ghostIcon?: boolean;
|
|
27
|
+
ariaLabel?: string;
|
|
28
|
+
};
|
|
29
|
+
export type WorkspaceLauncherProps<Id extends string = string> = {
|
|
30
|
+
items: WorkspaceLaunchItem<Id>[];
|
|
31
|
+
onSelect: (id: Id, item: WorkspaceLaunchItem<Id>) => void;
|
|
32
|
+
title?: React.ReactNode;
|
|
33
|
+
description?: React.ReactNode;
|
|
34
|
+
framed?: boolean;
|
|
35
|
+
density?: 'compact' | 'comfortable';
|
|
36
|
+
/** `scroll` keeps fixed-height cards and scrolls the grid. `fit` divides the
|
|
37
|
+
* available height evenly between rows and never introduces a grid scrollbar. */
|
|
38
|
+
layout?: 'scroll' | 'fit';
|
|
39
|
+
/** Hide composition meters without changing the data supplied by callers. */
|
|
40
|
+
showBreakdowns?: boolean;
|
|
41
|
+
/** Hide per-card summary badges in space-constrained launchers. */
|
|
42
|
+
showSummaries?: boolean;
|
|
43
|
+
className?: string;
|
|
44
|
+
gridClassName?: string;
|
|
45
|
+
cardClassName?: string;
|
|
46
|
+
itemCountLabel?: React.ReactNode;
|
|
47
|
+
formatCount?: (value: number) => React.ReactNode;
|
|
48
|
+
};
|
|
49
|
+
export declare function WorkspaceLauncher<Id extends string = string>({ items, onSelect, title, description, framed, density, layout, showBreakdowns, showSummaries, className, gridClassName, cardClassName, itemCountLabel, formatCount, }: WorkspaceLauncherProps<Id>): React.JSX.Element;
|
|
50
|
+
//# sourceMappingURL=workspace-launcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-launcher.d.ts","sourceRoot":"","sources":["../src/workspace-launcher.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,SAAS,CAAA;AAGhD,MAAM,MAAM,mBAAmB,GAC3B,QAAQ,GACR,KAAK,GACL,OAAO,GACP,MAAM,GACN,SAAS,GACT,QAAQ,GACR,MAAM,GACN,MAAM,GACN,MAAM,CAAA;AAEV,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,mBAAmB,CAAA;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,sBAAsB,EAAE,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,mBAAmB,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI;IAC5D,EAAE,EAAE,EAAE,CAAA;IACN,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAA;IACxD,MAAM,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,mBAAmB,CAAA;IACzB,SAAS,CAAC,EAAE,wBAAwB,CAAA;IACpC,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,cAAc,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAA;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,sBAAsB,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI;IAC/D,KAAK,EAAE,mBAAmB,CAAC,EAAE,CAAC,EAAE,CAAA;IAChC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,mBAAmB,CAAC,EAAE,CAAC,KAAK,IAAI,CAAA;IACzD,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,SAAS,GAAG,aAAa,CAAA;IACnC;qFACiF;IACjF,MAAM,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;IACzB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAChC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;CACjD,CAAA;AA0OD,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,EAAE,EAC5D,KAAK,EACL,QAAQ,EACR,KAAK,EACL,WAAW,EACX,MAAa,EACb,OAAmB,EACnB,MAAiB,EACjB,cAAqB,EACrB,aAAoB,EACpB,SAAS,EACT,aAAa,EACb,aAAa,EACb,cAAc,EACd,WAAgC,GACjC,EAAE,sBAAsB,CAAC,EAAE,CAAC,qBA6C5B"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { ArrowRight } from 'lucide-react';
|
|
5
|
+
import { Badge } from './badge.js';
|
|
6
|
+
import { cn } from './utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Each tone is a distinct categorical accent, because a launcher's whole job is
|
|
9
|
+
* telling peers apart at a glance. These deliberately do NOT reuse the semantic
|
|
10
|
+
* status colours: danger/warning/success/info mean something, and borrowing
|
|
11
|
+
* them here both muddies that meaning and collapses tones onto each other —
|
|
12
|
+
* nine names rendering as five colours, so cards repeated each other's colour.
|
|
13
|
+
*/
|
|
14
|
+
const toneClasses = {
|
|
15
|
+
indigo: {
|
|
16
|
+
accent: 'text-accent-indigo',
|
|
17
|
+
ghost: 'text-accent-indigo/[0.06] group-hover/card:text-accent-indigo/[0.10]',
|
|
18
|
+
hover: 'hover:border-accent-indigo/45',
|
|
19
|
+
icon: 'border-accent-indigo/25 bg-accent-indigo/10 text-accent-indigo',
|
|
20
|
+
rail: 'bg-accent-indigo',
|
|
21
|
+
wash: 'bg-accent-indigo/5',
|
|
22
|
+
},
|
|
23
|
+
sky: {
|
|
24
|
+
accent: 'text-accent-sky',
|
|
25
|
+
ghost: 'text-accent-sky/[0.06] group-hover/card:text-accent-sky/[0.10]',
|
|
26
|
+
hover: 'hover:border-accent-sky/45',
|
|
27
|
+
icon: 'border-accent-sky/25 bg-accent-sky/10 text-accent-sky',
|
|
28
|
+
rail: 'bg-accent-sky',
|
|
29
|
+
wash: 'bg-accent-sky/5',
|
|
30
|
+
},
|
|
31
|
+
amber: {
|
|
32
|
+
accent: 'text-accent-amber',
|
|
33
|
+
ghost: 'text-accent-amber/[0.06] group-hover/card:text-accent-amber/[0.10]',
|
|
34
|
+
hover: 'hover:border-accent-amber/45',
|
|
35
|
+
icon: 'border-accent-amber/25 bg-accent-amber/10 text-accent-amber',
|
|
36
|
+
rail: 'bg-accent-amber',
|
|
37
|
+
wash: 'bg-accent-amber/5',
|
|
38
|
+
},
|
|
39
|
+
teal: {
|
|
40
|
+
accent: 'text-accent-teal',
|
|
41
|
+
ghost: 'text-accent-teal/[0.06] group-hover/card:text-accent-teal/[0.10]',
|
|
42
|
+
hover: 'hover:border-accent-teal/45',
|
|
43
|
+
icon: 'border-accent-teal/25 bg-accent-teal/10 text-accent-teal',
|
|
44
|
+
rail: 'bg-accent-teal',
|
|
45
|
+
wash: 'bg-accent-teal/5',
|
|
46
|
+
},
|
|
47
|
+
emerald: {
|
|
48
|
+
accent: 'text-accent-emerald',
|
|
49
|
+
ghost: 'text-accent-emerald/[0.06] group-hover/card:text-accent-emerald/[0.10]',
|
|
50
|
+
hover: 'hover:border-accent-emerald/45',
|
|
51
|
+
icon: 'border-accent-emerald/25 bg-accent-emerald/10 text-accent-emerald',
|
|
52
|
+
rail: 'bg-accent-emerald',
|
|
53
|
+
wash: 'bg-accent-emerald/5',
|
|
54
|
+
},
|
|
55
|
+
violet: {
|
|
56
|
+
accent: 'text-accent-violet',
|
|
57
|
+
ghost: 'text-accent-violet/[0.06] group-hover/card:text-accent-violet/[0.10]',
|
|
58
|
+
hover: 'hover:border-accent-violet/45',
|
|
59
|
+
icon: 'border-accent-violet/25 bg-accent-violet/10 text-accent-violet',
|
|
60
|
+
rail: 'bg-accent-violet',
|
|
61
|
+
wash: 'bg-accent-violet/5',
|
|
62
|
+
},
|
|
63
|
+
rose: {
|
|
64
|
+
accent: 'text-accent-rose',
|
|
65
|
+
ghost: 'text-accent-rose/[0.06] group-hover/card:text-accent-rose/[0.10]',
|
|
66
|
+
hover: 'hover:border-accent-rose/45',
|
|
67
|
+
icon: 'border-accent-rose/25 bg-accent-rose/10 text-accent-rose',
|
|
68
|
+
rail: 'bg-accent-rose',
|
|
69
|
+
wash: 'bg-accent-rose/5',
|
|
70
|
+
},
|
|
71
|
+
lime: {
|
|
72
|
+
accent: 'text-accent-lime',
|
|
73
|
+
ghost: 'text-accent-lime/[0.06] group-hover/card:text-accent-lime/[0.10]',
|
|
74
|
+
hover: 'hover:border-accent-lime/45',
|
|
75
|
+
icon: 'border-accent-lime/25 bg-accent-lime/10 text-accent-lime',
|
|
76
|
+
rail: 'bg-accent-lime',
|
|
77
|
+
wash: 'bg-accent-lime/5',
|
|
78
|
+
},
|
|
79
|
+
cyan: {
|
|
80
|
+
accent: 'text-accent-cyan',
|
|
81
|
+
ghost: 'text-accent-cyan/[0.06] group-hover/card:text-accent-cyan/[0.10]',
|
|
82
|
+
hover: 'hover:border-accent-cyan/45',
|
|
83
|
+
icon: 'border-accent-cyan/25 bg-accent-cyan/10 text-accent-cyan',
|
|
84
|
+
rail: 'bg-accent-cyan',
|
|
85
|
+
wash: 'bg-accent-cyan/5',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
const segmentPalette = [
|
|
89
|
+
'indigo',
|
|
90
|
+
'cyan',
|
|
91
|
+
'sky',
|
|
92
|
+
'violet',
|
|
93
|
+
'amber',
|
|
94
|
+
'emerald',
|
|
95
|
+
'rose',
|
|
96
|
+
'lime',
|
|
97
|
+
'teal',
|
|
98
|
+
];
|
|
99
|
+
function defaultFormatCount(value) {
|
|
100
|
+
return new Intl.NumberFormat(undefined, { notation: value >= 10_000 ? 'compact' : 'standard' }).format(value);
|
|
101
|
+
}
|
|
102
|
+
function BreakdownMeter({ breakdown, formatCount, }) {
|
|
103
|
+
const total = breakdown.segments.reduce((sum, segment) => sum + (segment.muted || !Number.isFinite(segment.value) ? 0 : Math.max(0, segment.value)), 0);
|
|
104
|
+
return (_jsxs("div", { children: [_jsxs("div", { className: "mb-1.5 flex items-center justify-between gap-3 text-[11px]", children: [_jsx("span", { className: "truncate font-medium text-fg-muted", children: breakdown.label }), _jsx("span", { className: "shrink-0 tabular-nums text-fg-subtle", children: formatCount(total) })] }), _jsx("div", { className: "flex h-4 overflow-visible rounded-full bg-bg-subtle", children: breakdown.segments.map((segment, index) => {
|
|
105
|
+
const value = segment.muted ? 0 : Math.max(0, segment.value);
|
|
106
|
+
const width = segment.muted ? 100 : total > 0 ? (value / total) * 100 : 0;
|
|
107
|
+
const percent = total > 0 ? Math.round((value / total) * 100) : 0;
|
|
108
|
+
const tone = segment.tone ?? segmentPalette[index % segmentPalette.length] ?? 'indigo';
|
|
109
|
+
const label = segment.muted ? segment.label : `${segment.label} · ${percent}%`;
|
|
110
|
+
return (_jsx("div", { className: cn('group/segment relative h-full first:rounded-l-full last:rounded-r-full', segment.muted ? 'bg-bg-subtle' : toneClasses[tone].rail), style: { width: `${width}%` }, "aria-label": segment.muted ? segment.label : `${segment.label}: ${value} (${percent}%)`, children: _jsx("span", { className: "pointer-events-none absolute left-1/2 top-full z-50 mt-1.5 max-w-40 -translate-x-1/2 rounded border border-border bg-surface px-2 py-1 text-[10px] font-medium text-fg opacity-0 shadow-lg transition-opacity group-hover/segment:opacity-100", children: label }) }, `${segment.label}-${index}`));
|
|
111
|
+
}) })] }));
|
|
112
|
+
}
|
|
113
|
+
function WorkspaceLaunchCard({ item, onSelect, density, showBreakdown, showSummary, className, formatCount, }) {
|
|
114
|
+
const Icon = item.icon;
|
|
115
|
+
const tone = toneClasses[item.tone];
|
|
116
|
+
return (_jsxs("button", { type: "button", disabled: item.disabled, "aria-label": item.ariaLabel, onClick: () => onSelect(item.id, item), className: cn('group/card relative z-0 flex h-full min-h-0 min-w-0 flex-col overflow-visible rounded-lg border border-border bg-surface text-left shadow-sm transition-all duration-200', 'hover:z-20 hover:-translate-y-0.5 hover:shadow-[0_18px_48px_rgb(var(--ch-fg)/0.10)] focus-visible:z-20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35', 'disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:translate-y-0 disabled:hover:shadow-sm', density === 'comfortable' ? 'p-5' : 'p-3', tone.hover, className), children: [_jsx("span", { className: cn('pointer-events-none absolute inset-0 rounded-lg opacity-0 transition-opacity duration-200 group-hover/card:opacity-100', tone.wash) }), item.ghostIcon ? (_jsx("span", { className: "pointer-events-none absolute inset-0 overflow-hidden rounded-lg", children: _jsx(Icon, { "aria-hidden": true, strokeWidth: 1.25, className: cn('absolute -bottom-10 -right-10 size-56 transition-all duration-300 group-hover/card:scale-[1.04] group-hover/card:-rotate-2', tone.ghost) }) })) : null, _jsx("span", { className: cn('absolute inset-x-0 top-0 h-1 rounded-t-lg', tone.rail) }), _jsxs("span", { className: "relative flex items-start justify-between gap-3", children: [_jsxs("span", { className: "flex min-w-0 items-start gap-2.5", children: [_jsx("span", { className: cn('flex size-9 shrink-0 items-center justify-center rounded-lg border shadow-[inset_0_1px_0_rgb(var(--ch-fg)/0.08)]', tone.icon), children: _jsx(Icon, { className: "size-[18px]" }) }), _jsxs("span", { className: "min-w-0 pt-0.5", children: [_jsx("span", { className: "block truncate text-sm font-semibold text-fg", children: item.title }), showSummary && item.summary != null ? (_jsx(Badge, { variant: item.summaryVariant ?? 'secondary', className: "mt-1 shrink-0 whitespace-nowrap", children: item.summary })) : null, _jsx("span", { className: "mt-1 line-clamp-2 text-xs leading-relaxed text-fg-muted", children: item.description })] })] }), _jsx(ArrowRight, { className: "mt-1 size-4 shrink-0 text-fg-subtle transition-all group-hover/card:translate-x-0.5 group-hover/card:text-fg-muted" })] }), _jsxs("span", { className: cn('relative', showBreakdown && item.breakdown ? 'mt-3' : 'mt-auto pt-3'), children: [_jsx("span", { className: cn('block text-[10px] font-semibold uppercase tracking-wider', tone.accent), children: item.metricLabel }), _jsx("span", { className: "mt-1 block truncate text-3xl font-semibold leading-none tabular-nums text-fg", children: item.metric })] }), showBreakdown && item.breakdown ? (_jsx("span", { className: "relative mt-3", children: _jsx(BreakdownMeter, { breakdown: item.breakdown, formatCount: formatCount }) })) : null] }));
|
|
117
|
+
}
|
|
118
|
+
export function WorkspaceLauncher({ items, onSelect, title, description, framed = true, density = 'compact', layout = 'scroll', showBreakdowns = true, showSummaries = true, className, gridClassName, cardClassName, itemCountLabel, formatCount = defaultFormatCount, }) {
|
|
119
|
+
const content = (_jsxs(_Fragment, { children: [title != null || description != null ? (_jsxs("div", { className: cn('flex shrink-0 items-center justify-between gap-3 border-b border-border px-3 py-2', !framed && 'mb-3 rounded-lg border'), children: [_jsxs("div", { className: "min-w-0", children: [title != null ? _jsx("div", { className: "text-xs font-semibold text-fg", children: title }) : null, description != null ? _jsx("div", { className: "mt-0.5 truncate text-[11px] text-fg-muted", children: description }) : null] }), itemCountLabel != null ? _jsx(Badge, { variant: "info", className: "lg:hidden", children: itemCountLabel }) : null] })) : null, _jsx("div", { className: cn('min-h-0 flex-1 overflow-hidden', framed && 'p-3'), children: _jsx("div", { className: cn('grid h-full min-h-0 gap-3 overflow-x-hidden md:grid-cols-2 lg:grid-cols-4', layout === 'fit'
|
|
120
|
+
? 'auto-rows-fr overflow-y-hidden'
|
|
121
|
+
: 'auto-rows-[252px] overflow-y-auto pb-4 pr-1', gridClassName), children: items.map((item) => (_jsx(WorkspaceLaunchCard, { item: item, onSelect: onSelect, density: density, showBreakdown: showBreakdowns, showSummary: showSummaries, className: cardClassName, formatCount: formatCount }, item.id))) }) })] }));
|
|
122
|
+
if (!framed) {
|
|
123
|
+
return _jsx("div", { className: cn('flex h-full min-h-0 flex-col overflow-hidden', className), children: content });
|
|
124
|
+
}
|
|
125
|
+
return (_jsx("section", { className: cn('flex h-full min-h-0 flex-col overflow-hidden rounded-lg border border-border bg-surface', className), children: content }));
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=workspace-launcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-launcher.js","sourceRoot":"","sources":["../src/workspace-launcher.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { ArrowRight } from 'lucide-react'\nimport { Badge, type BadgeProps } from './badge'\nimport { cn } from './utils'\n\nexport type WorkspaceLaunchTone =\n | 'indigo'\n | 'sky'\n | 'amber'\n | 'teal'\n | 'emerald'\n | 'violet'\n | 'rose'\n | 'lime'\n | 'cyan'\n\nexport type WorkspaceLaunchSegment = {\n label: string\n value: number\n tone?: WorkspaceLaunchTone\n muted?: boolean\n}\n\nexport type WorkspaceLaunchBreakdown = {\n label: string\n segments: WorkspaceLaunchSegment[]\n}\n\nexport type WorkspaceLaunchItem<Id extends string = string> = {\n id: Id\n title: string\n description: string\n icon: React.ComponentType<React.SVGProps<SVGSVGElement>>\n metric: React.ReactNode\n metricLabel: string\n tone: WorkspaceLaunchTone\n breakdown?: WorkspaceLaunchBreakdown\n summary?: React.ReactNode\n summaryVariant?: BadgeProps['variant']\n disabled?: boolean\n ghostIcon?: boolean\n ariaLabel?: string\n}\n\nexport type WorkspaceLauncherProps<Id extends string = string> = {\n items: WorkspaceLaunchItem<Id>[]\n onSelect: (id: Id, item: WorkspaceLaunchItem<Id>) => void\n title?: React.ReactNode\n description?: React.ReactNode\n framed?: boolean\n density?: 'compact' | 'comfortable'\n /** `scroll` keeps fixed-height cards and scrolls the grid. `fit` divides the\n * available height evenly between rows and never introduces a grid scrollbar. */\n layout?: 'scroll' | 'fit'\n /** Hide composition meters without changing the data supplied by callers. */\n showBreakdowns?: boolean\n /** Hide per-card summary badges in space-constrained launchers. */\n showSummaries?: boolean\n className?: string\n gridClassName?: string\n cardClassName?: string\n itemCountLabel?: React.ReactNode\n formatCount?: (value: number) => React.ReactNode\n}\n\n/**\n * Each tone is a distinct categorical accent, because a launcher's whole job is\n * telling peers apart at a glance. These deliberately do NOT reuse the semantic\n * status colours: danger/warning/success/info mean something, and borrowing\n * them here both muddies that meaning and collapses tones onto each other —\n * nine names rendering as five colours, so cards repeated each other's colour.\n */\nconst toneClasses: Record<\n WorkspaceLaunchTone,\n { accent: string; ghost: string; hover: string; icon: string; rail: string; wash: string }\n> = {\n indigo: {\n accent: 'text-accent-indigo',\n ghost: 'text-accent-indigo/[0.06] group-hover/card:text-accent-indigo/[0.10]',\n hover: 'hover:border-accent-indigo/45',\n icon: 'border-accent-indigo/25 bg-accent-indigo/10 text-accent-indigo',\n rail: 'bg-accent-indigo',\n wash: 'bg-accent-indigo/5',\n },\n sky: {\n accent: 'text-accent-sky',\n ghost: 'text-accent-sky/[0.06] group-hover/card:text-accent-sky/[0.10]',\n hover: 'hover:border-accent-sky/45',\n icon: 'border-accent-sky/25 bg-accent-sky/10 text-accent-sky',\n rail: 'bg-accent-sky',\n wash: 'bg-accent-sky/5',\n },\n amber: {\n accent: 'text-accent-amber',\n ghost: 'text-accent-amber/[0.06] group-hover/card:text-accent-amber/[0.10]',\n hover: 'hover:border-accent-amber/45',\n icon: 'border-accent-amber/25 bg-accent-amber/10 text-accent-amber',\n rail: 'bg-accent-amber',\n wash: 'bg-accent-amber/5',\n },\n teal: {\n accent: 'text-accent-teal',\n ghost: 'text-accent-teal/[0.06] group-hover/card:text-accent-teal/[0.10]',\n hover: 'hover:border-accent-teal/45',\n icon: 'border-accent-teal/25 bg-accent-teal/10 text-accent-teal',\n rail: 'bg-accent-teal',\n wash: 'bg-accent-teal/5',\n },\n emerald: {\n accent: 'text-accent-emerald',\n ghost: 'text-accent-emerald/[0.06] group-hover/card:text-accent-emerald/[0.10]',\n hover: 'hover:border-accent-emerald/45',\n icon: 'border-accent-emerald/25 bg-accent-emerald/10 text-accent-emerald',\n rail: 'bg-accent-emerald',\n wash: 'bg-accent-emerald/5',\n },\n violet: {\n accent: 'text-accent-violet',\n ghost: 'text-accent-violet/[0.06] group-hover/card:text-accent-violet/[0.10]',\n hover: 'hover:border-accent-violet/45',\n icon: 'border-accent-violet/25 bg-accent-violet/10 text-accent-violet',\n rail: 'bg-accent-violet',\n wash: 'bg-accent-violet/5',\n },\n rose: {\n accent: 'text-accent-rose',\n ghost: 'text-accent-rose/[0.06] group-hover/card:text-accent-rose/[0.10]',\n hover: 'hover:border-accent-rose/45',\n icon: 'border-accent-rose/25 bg-accent-rose/10 text-accent-rose',\n rail: 'bg-accent-rose',\n wash: 'bg-accent-rose/5',\n },\n lime: {\n accent: 'text-accent-lime',\n ghost: 'text-accent-lime/[0.06] group-hover/card:text-accent-lime/[0.10]',\n hover: 'hover:border-accent-lime/45',\n icon: 'border-accent-lime/25 bg-accent-lime/10 text-accent-lime',\n rail: 'bg-accent-lime',\n wash: 'bg-accent-lime/5',\n },\n cyan: {\n accent: 'text-accent-cyan',\n ghost: 'text-accent-cyan/[0.06] group-hover/card:text-accent-cyan/[0.10]',\n hover: 'hover:border-accent-cyan/45',\n icon: 'border-accent-cyan/25 bg-accent-cyan/10 text-accent-cyan',\n rail: 'bg-accent-cyan',\n wash: 'bg-accent-cyan/5',\n },\n}\n\nconst segmentPalette: WorkspaceLaunchTone[] = [\n 'indigo',\n 'cyan',\n 'sky',\n 'violet',\n 'amber',\n 'emerald',\n 'rose',\n 'lime',\n 'teal',\n]\n\nfunction defaultFormatCount(value: number) {\n return new Intl.NumberFormat(undefined, { notation: value >= 10_000 ? 'compact' : 'standard' }).format(value)\n}\n\nfunction BreakdownMeter({\n breakdown,\n formatCount,\n}: {\n breakdown: WorkspaceLaunchBreakdown\n formatCount: (value: number) => React.ReactNode\n}) {\n const total = breakdown.segments.reduce(\n (sum, segment) => sum + (segment.muted || !Number.isFinite(segment.value) ? 0 : Math.max(0, segment.value)),\n 0,\n )\n\n return (\n <div>\n <div className=\"mb-1.5 flex items-center justify-between gap-3 text-[11px]\">\n <span className=\"truncate font-medium text-fg-muted\">{breakdown.label}</span>\n <span className=\"shrink-0 tabular-nums text-fg-subtle\">{formatCount(total)}</span>\n </div>\n <div className=\"flex h-4 overflow-visible rounded-full bg-bg-subtle\">\n {breakdown.segments.map((segment, index) => {\n const value = segment.muted ? 0 : Math.max(0, segment.value)\n const width = segment.muted ? 100 : total > 0 ? (value / total) * 100 : 0\n const percent = total > 0 ? Math.round((value / total) * 100) : 0\n const tone = segment.tone ?? segmentPalette[index % segmentPalette.length] ?? 'indigo'\n const label = segment.muted ? segment.label : `${segment.label} · ${percent}%`\n return (\n <div\n key={`${segment.label}-${index}`}\n className={cn(\n 'group/segment relative h-full first:rounded-l-full last:rounded-r-full',\n segment.muted ? 'bg-bg-subtle' : toneClasses[tone].rail,\n )}\n style={{ width: `${width}%` }}\n aria-label={segment.muted ? segment.label : `${segment.label}: ${value} (${percent}%)`}\n >\n <span className=\"pointer-events-none absolute left-1/2 top-full z-50 mt-1.5 max-w-40 -translate-x-1/2 rounded border border-border bg-surface px-2 py-1 text-[10px] font-medium text-fg opacity-0 shadow-lg transition-opacity group-hover/segment:opacity-100\">\n {label}\n </span>\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n\nfunction WorkspaceLaunchCard<Id extends string>({\n item,\n onSelect,\n density,\n showBreakdown,\n showSummary,\n className,\n formatCount,\n}: {\n item: WorkspaceLaunchItem<Id>\n onSelect: (id: Id, item: WorkspaceLaunchItem<Id>) => void\n density: 'compact' | 'comfortable'\n showBreakdown: boolean\n showSummary: boolean\n className?: string\n formatCount: (value: number) => React.ReactNode\n}) {\n const Icon = item.icon\n const tone = toneClasses[item.tone]\n return (\n <button\n type=\"button\"\n disabled={item.disabled}\n aria-label={item.ariaLabel}\n onClick={() => onSelect(item.id, item)}\n className={cn(\n 'group/card relative z-0 flex h-full min-h-0 min-w-0 flex-col overflow-visible rounded-lg border border-border bg-surface text-left shadow-sm transition-all duration-200',\n 'hover:z-20 hover:-translate-y-0.5 hover:shadow-[0_18px_48px_rgb(var(--ch-fg)/0.10)] focus-visible:z-20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35',\n 'disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:translate-y-0 disabled:hover:shadow-sm',\n density === 'comfortable' ? 'p-5' : 'p-3',\n tone.hover,\n className,\n )}\n >\n <span className={cn('pointer-events-none absolute inset-0 rounded-lg opacity-0 transition-opacity duration-200 group-hover/card:opacity-100', tone.wash)} />\n {item.ghostIcon ? (\n <span className=\"pointer-events-none absolute inset-0 overflow-hidden rounded-lg\">\n <Icon\n aria-hidden\n strokeWidth={1.25}\n className={cn(\n 'absolute -bottom-10 -right-10 size-56 transition-all duration-300 group-hover/card:scale-[1.04] group-hover/card:-rotate-2',\n tone.ghost,\n )}\n />\n </span>\n ) : null}\n <span className={cn('absolute inset-x-0 top-0 h-1 rounded-t-lg', tone.rail)} />\n\n <span className=\"relative flex items-start justify-between gap-3\">\n <span className=\"flex min-w-0 items-start gap-2.5\">\n <span className={cn('flex size-9 shrink-0 items-center justify-center rounded-lg border shadow-[inset_0_1px_0_rgb(var(--ch-fg)/0.08)]', tone.icon)}>\n <Icon className=\"size-[18px]\" />\n </span>\n <span className=\"min-w-0 pt-0.5\">\n <span className=\"block truncate text-sm font-semibold text-fg\">{item.title}</span>\n {showSummary && item.summary != null ? (\n <Badge variant={item.summaryVariant ?? 'secondary'} className=\"mt-1 shrink-0 whitespace-nowrap\">\n {item.summary}\n </Badge>\n ) : null}\n <span className=\"mt-1 line-clamp-2 text-xs leading-relaxed text-fg-muted\">{item.description}</span>\n </span>\n </span>\n <ArrowRight className=\"mt-1 size-4 shrink-0 text-fg-subtle transition-all group-hover/card:translate-x-0.5 group-hover/card:text-fg-muted\" />\n </span>\n\n <span className={cn('relative', showBreakdown && item.breakdown ? 'mt-3' : 'mt-auto pt-3')}>\n <span className={cn('block text-[10px] font-semibold uppercase tracking-wider', tone.accent)}>\n {item.metricLabel}\n </span>\n <span className=\"mt-1 block truncate text-3xl font-semibold leading-none tabular-nums text-fg\">\n {item.metric}\n </span>\n </span>\n\n {showBreakdown && item.breakdown ? (\n <span className=\"relative mt-3\">\n <BreakdownMeter breakdown={item.breakdown} formatCount={formatCount} />\n </span>\n ) : null}\n </button>\n )\n}\n\nexport function WorkspaceLauncher<Id extends string = string>({\n items,\n onSelect,\n title,\n description,\n framed = true,\n density = 'compact',\n layout = 'scroll',\n showBreakdowns = true,\n showSummaries = true,\n className,\n gridClassName,\n cardClassName,\n itemCountLabel,\n formatCount = defaultFormatCount,\n}: WorkspaceLauncherProps<Id>) {\n const content = (\n <>\n {title != null || description != null ? (\n <div className={cn('flex shrink-0 items-center justify-between gap-3 border-b border-border px-3 py-2', !framed && 'mb-3 rounded-lg border')}>\n <div className=\"min-w-0\">\n {title != null ? <div className=\"text-xs font-semibold text-fg\">{title}</div> : null}\n {description != null ? <div className=\"mt-0.5 truncate text-[11px] text-fg-muted\">{description}</div> : null}\n </div>\n {itemCountLabel != null ? <Badge variant=\"info\" className=\"lg:hidden\">{itemCountLabel}</Badge> : null}\n </div>\n ) : null}\n <div className={cn('min-h-0 flex-1 overflow-hidden', framed && 'p-3')}>\n <div className={cn(\n 'grid h-full min-h-0 gap-3 overflow-x-hidden md:grid-cols-2 lg:grid-cols-4',\n layout === 'fit'\n ? 'auto-rows-fr overflow-y-hidden'\n : 'auto-rows-[252px] overflow-y-auto pb-4 pr-1',\n gridClassName,\n )}>\n {items.map((item) => (\n <WorkspaceLaunchCard\n key={item.id}\n item={item}\n onSelect={onSelect}\n density={density}\n showBreakdown={showBreakdowns}\n showSummary={showSummaries}\n className={cardClassName}\n formatCount={formatCount}\n />\n ))}\n </div>\n </div>\n </>\n )\n\n if (!framed) {\n return <div className={cn('flex h-full min-h-0 flex-col overflow-hidden', className)}>{content}</div>\n }\n return (\n <section className={cn('flex h-full min-h-0 flex-col overflow-hidden rounded-lg border border-border bg-surface', className)}>\n {content}\n </section>\n )\n}\n"]}
|