@streamsai/ui 0.0.0-stage → 0.1.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 +21 -0
- package/README.md +57 -2
- package/dist/components/ui/agent-avatar.d.ts +50 -0
- package/dist/components/ui/agent-avatar.js +38 -0
- package/dist/components/ui/alert-dialog.d.ts +12 -0
- package/dist/components/ui/alert-dialog.js +37 -0
- package/dist/components/ui/alert.d.ts +10 -0
- package/dist/components/ui/alert.js +28 -0
- package/dist/components/ui/assets/loader.json.js +1 -0
- package/dist/components/ui/avatar-frame.d.ts +11 -0
- package/dist/components/ui/avatar-frame.js +20 -0
- package/dist/components/ui/avatar.d.ts +23 -0
- package/dist/components/ui/avatar.js +54 -0
- package/dist/components/ui/badge.d.ts +7 -0
- package/dist/components/ui/badge.js +36 -0
- package/dist/components/ui/bar-list.d.ts +40 -0
- package/dist/components/ui/bar-list.js +37 -0
- package/dist/components/ui/breadcrumb.d.ts +8 -0
- package/dist/components/ui/breadcrumb.js +21 -0
- package/dist/components/ui/button-group.d.ts +10 -0
- package/dist/components/ui/button-group.js +41 -0
- package/dist/components/ui/button.d.ts +9 -0
- package/dist/components/ui/button.js +39 -0
- package/dist/components/ui/card.d.ts +9 -0
- package/dist/components/ui/card.js +25 -0
- package/dist/components/ui/checkbox.d.ts +3 -0
- package/dist/components/ui/checkbox.js +16 -0
- package/dist/components/ui/chip.d.ts +13 -0
- package/dist/components/ui/chip.js +22 -0
- package/dist/components/ui/clickable.d.ts +27 -0
- package/dist/components/ui/clickable.js +54 -0
- package/dist/components/ui/code-block.d.ts +68 -0
- package/dist/components/ui/code-block.js +112 -0
- package/dist/components/ui/code-view.d.ts +21 -0
- package/dist/components/ui/code-view.js +20 -0
- package/dist/components/ui/collapsible.d.ts +6 -0
- package/dist/components/ui/collapsible.js +18 -0
- package/dist/components/ui/combobox.d.ts +14 -0
- package/dist/components/ui/combobox.js +37 -0
- package/dist/components/ui/command.d.ts +15 -0
- package/dist/components/ui/command.js +34 -0
- package/dist/components/ui/context-menu.d.ts +21 -0
- package/dist/components/ui/context-menu.js +60 -0
- package/dist/components/ui/counter.d.ts +11 -0
- package/dist/components/ui/counter.js +20 -0
- package/dist/components/ui/dialog.d.ts +17 -0
- package/dist/components/ui/dialog.js +41 -0
- package/dist/components/ui/disabled-reason.d.ts +22 -0
- package/dist/components/ui/disabled-reason.js +16 -0
- package/dist/components/ui/dropdown-menu.d.ts +33 -0
- package/dist/components/ui/dropdown-menu.js +58 -0
- package/dist/components/ui/editable-text.d.ts +95 -0
- package/dist/components/ui/editable-text.js +191 -0
- package/dist/components/ui/heading.d.ts +24 -0
- package/dist/components/ui/heading.js +92 -0
- package/dist/components/ui/hover-card.d.ts +9 -0
- package/dist/components/ui/hover-card.js +16 -0
- package/dist/components/ui/icon-button.d.ts +23 -0
- package/dist/components/ui/icon-button.js +32 -0
- package/dist/components/ui/input.d.ts +3 -0
- package/dist/components/ui/input.js +8 -0
- package/dist/components/ui/kbd.d.ts +12 -0
- package/dist/components/ui/kbd.js +12 -0
- package/dist/components/ui/label.d.ts +3 -0
- package/dist/components/ui/label.js +7 -0
- package/dist/components/ui/loader.d.ts +19 -0
- package/dist/components/ui/loader.js +157 -0
- package/dist/components/ui/message-toolbar.d.ts +20 -0
- package/dist/components/ui/message-toolbar.js +17 -0
- package/dist/components/ui/modal.d.ts +30 -0
- package/dist/components/ui/modal.js +41 -0
- package/dist/components/ui/nav-item.d.ts +25 -0
- package/dist/components/ui/nav-item.js +66 -0
- package/dist/components/ui/panel-section.d.ts +22 -0
- package/dist/components/ui/panel-section.js +26 -0
- package/dist/components/ui/popover.d.ts +14 -0
- package/dist/components/ui/popover.js +30 -0
- package/dist/components/ui/progress.d.ts +5 -0
- package/dist/components/ui/progress.js +16 -0
- package/dist/components/ui/provider-badge.d.ts +22 -0
- package/dist/components/ui/provider-badge.js +38 -0
- package/dist/components/ui/radio-group.d.ts +5 -0
- package/dist/components/ui/radio-group.js +21 -0
- package/dist/components/ui/reply-summary.d.ts +16 -0
- package/dist/components/ui/reply-summary.js +8 -0
- package/dist/components/ui/resizable.d.ts +8 -0
- package/dist/components/ui/resizable.js +15 -0
- package/dist/components/ui/rich-text-editor.d.ts +447 -0
- package/dist/components/ui/rich-text-editor.js +2419 -0
- package/dist/components/ui/running-indicator.d.ts +17 -0
- package/dist/components/ui/running-indicator.js +34 -0
- package/dist/components/ui/sandboxed-frame.d.ts +29 -0
- package/dist/components/ui/sandboxed-frame.js +77 -0
- package/dist/components/ui/scroll-area.d.ts +14 -0
- package/dist/components/ui/scroll-area.js +17 -0
- package/dist/components/ui/section-header.d.ts +12 -0
- package/dist/components/ui/section-header.js +12 -0
- package/dist/components/ui/select.d.ts +15 -0
- package/dist/components/ui/select.js +40 -0
- package/dist/components/ui/separator.d.ts +3 -0
- package/dist/components/ui/separator.js +13 -0
- package/dist/components/ui/sheet.d.ts +23 -0
- package/dist/components/ui/sheet.js +48 -0
- package/dist/components/ui/shimmer-text.d.ts +26 -0
- package/dist/components/ui/shimmer-text.js +32 -0
- package/dist/components/ui/sidebar.d.ts +63 -0
- package/dist/components/ui/sidebar.js +283 -0
- package/dist/components/ui/skeleton.d.ts +14 -0
- package/dist/components/ui/skeleton.js +12 -0
- package/dist/components/ui/sonner.d.ts +4 -0
- package/dist/components/ui/sonner.js +14 -0
- package/dist/components/ui/spark-bars.d.ts +29 -0
- package/dist/components/ui/spark-bars.js +24 -0
- package/dist/components/ui/spinner.d.ts +16 -0
- package/dist/components/ui/spinner.js +26 -0
- package/dist/components/ui/stacked-bar.d.ts +37 -0
- package/dist/components/ui/stacked-bar.js +56 -0
- package/dist/components/ui/status-dot.d.ts +16 -0
- package/dist/components/ui/status-dot.js +32 -0
- package/dist/components/ui/switch.d.ts +5 -0
- package/dist/components/ui/switch.js +13 -0
- package/dist/components/ui/tabs.d.ts +14 -0
- package/dist/components/ui/tabs.js +55 -0
- package/dist/components/ui/text.d.ts +27 -0
- package/dist/components/ui/text.js +100 -0
- package/dist/components/ui/textarea.d.ts +3 -0
- package/dist/components/ui/textarea.js +7 -0
- package/dist/components/ui/theme-toggle.d.ts +21 -0
- package/dist/components/ui/theme-toggle.js +17 -0
- package/dist/components/ui/toggle-group.d.ts +6 -0
- package/dist/components/ui/toggle-group.js +12 -0
- package/dist/components/ui/tooltip.d.ts +11 -0
- package/dist/components/ui/tooltip.js +21 -0
- package/dist/components/ui/trace.d.ts +24 -0
- package/dist/components/ui/trace.js +57 -0
- package/dist/components/ui/trend-line.d.ts +93 -0
- package/dist/components/ui/trend-line.js +215 -0
- package/dist/components/ui/truncate.d.ts +30 -0
- package/dist/components/ui/truncate.js +54 -0
- package/dist/components/ui/viewer-shell.d.ts +55 -0
- package/dist/components/ui/viewer-shell.js +15 -0
- package/dist/components/ui/virtual-list.d.ts +91 -0
- package/dist/components/ui/virtual-list.js +406 -0
- package/dist/components/ui/visually-hidden.d.ts +15 -0
- package/dist/components/ui/visually-hidden.js +19 -0
- package/dist/direction/index.d.ts +19 -0
- package/dist/direction/index.js +32 -0
- package/dist/hooks/use-code-highlighting.d.ts +22 -0
- package/dist/hooks/use-code-highlighting.js +47 -0
- package/dist/hooks/use-is-truncated.d.ts +7 -0
- package/dist/hooks/use-is-truncated.js +23 -0
- package/dist/hooks/use-mobile.d.ts +2 -0
- package/dist/hooks/use-mobile.js +15 -0
- package/dist/icons/custom.d.ts +29 -0
- package/dist/icons/custom.js +48 -0
- package/dist/icons/index.d.ts +49 -0
- package/dist/icons/index.js +50 -0
- package/dist/icons/provider-logos.d.ts +17 -0
- package/dist/icons/provider-logos.js +28 -0
- package/dist/icons/providers.d.ts +9 -0
- package/dist/icons/providers.js +51 -0
- package/dist/index.d.ts +102 -0
- package/dist/index.js +124 -0
- package/dist/lib/code-highlighter.d.ts +9 -0
- package/dist/lib/code-highlighter.js +65 -0
- package/dist/lib/code-language.d.ts +13 -0
- package/dist/lib/code-language.js +103 -0
- package/dist/lib/code-tokens.d.ts +9 -0
- package/dist/lib/code-tokens.js +27 -0
- package/dist/lib/copy-text.d.ts +6 -0
- package/dist/lib/copy-text.js +46 -0
- package/dist/lib/home-tile.d.ts +12 -0
- package/dist/lib/home-tile.js +45 -0
- package/dist/lib/reveal-mask.d.ts +9 -0
- package/dist/lib/reveal-mask.js +16 -0
- package/dist/lib/text-direction.d.ts +28 -0
- package/dist/lib/text-direction.js +119 -0
- package/dist/lib/utils.d.ts +11 -0
- package/dist/lib/utils.js +30 -0
- package/dist/motion/components.d.ts +184 -0
- package/dist/motion/components.js +211 -0
- package/dist/motion/config.d.ts +849 -0
- package/dist/motion/config.js +429 -0
- package/dist/motion/index.d.ts +8 -0
- package/dist/motion/index.js +8 -0
- package/dist/motion/modal-presence.d.ts +5 -0
- package/dist/motion/modal-presence.js +27 -0
- package/dist/motion/popup.d.ts +77 -0
- package/dist/motion/popup.js +103 -0
- package/dist/platform/index.d.ts +29 -0
- package/dist/platform/index.js +63 -0
- package/dist/styles/base.css +319 -0
- package/dist/styles/fonts/Figtree-Variable.woff2 +0 -0
- package/dist/styles/fonts/OFL.txt +93 -0
- package/dist/styles/fonts/Poppins-Medium.woff2 +0 -0
- package/dist/styles/fonts/Poppins-Regular.woff2 +0 -0
- package/dist/styles/fonts/Poppins-SemiBold.woff2 +0 -0
- package/dist/styles/fonts.css +28 -0
- package/dist/styles/globals.css +8 -0
- package/dist/styles/mcp-host.css +76 -0
- package/dist/styles/tokens.css +387 -0
- package/dist/styles/tokens.d.mts +57 -0
- package/dist/styles/tokens.mjs +59 -0
- package/dist/theme/index.d.ts +28 -0
- package/dist/theme/index.js +65 -0
- package/package.json +136 -3
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { mergeProps } from '@base-ui/react/merge-props';
|
|
4
|
+
import { useRender } from '@base-ui/react/use-render';
|
|
5
|
+
import { cva } from 'class-variance-authority';
|
|
6
|
+
import { cn } from '../../lib/utils.js';
|
|
7
|
+
import { motion } from '../../motion/index.js';
|
|
8
|
+
import { transitions } from '../../motion/config.js';
|
|
9
|
+
import { useIsMobile } from '../../hooks/use-mobile.js';
|
|
10
|
+
import { Button } from './button.js';
|
|
11
|
+
import { Input } from './input.js';
|
|
12
|
+
import { Separator } from './separator.js';
|
|
13
|
+
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from './sheet.js';
|
|
14
|
+
import { Skeleton } from './skeleton.js';
|
|
15
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip.js';
|
|
16
|
+
import { PanelLeft as PanelLeftIcon } from '../../icons/index.js';
|
|
17
|
+
const SIDEBAR_WIDTH = 'var(--nav-w)';
|
|
18
|
+
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
|
19
|
+
const SIDEBAR_WIDTH_ICON = 'var(--nav-w-icon)';
|
|
20
|
+
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
|
21
|
+
// Attribute selector, not `isContentEditable`: jsdom does not implement that property.
|
|
22
|
+
const EDITABLE_SELECTOR = 'input, textarea, select, [contenteditable]:not([contenteditable="false"])';
|
|
23
|
+
const isEditableTarget = (target) => target instanceof Element && target.closest(EDITABLE_SELECTOR) !== null;
|
|
24
|
+
const SidebarContext = React.createContext(null);
|
|
25
|
+
function useSidebar() {
|
|
26
|
+
const context = React.useContext(SidebarContext);
|
|
27
|
+
if (!context) {
|
|
28
|
+
throw new Error('useSidebar must be used within a SidebarProvider.');
|
|
29
|
+
}
|
|
30
|
+
return context;
|
|
31
|
+
}
|
|
32
|
+
function SidebarProvider({ defaultOpen = true, open: openProp, onOpenChange: setOpenProp, className, style, children, ...props }) {
|
|
33
|
+
const isMobile = useIsMobile();
|
|
34
|
+
const [openMobile, setOpenMobile] = React.useState(false);
|
|
35
|
+
// This is the internal state of the sidebar.
|
|
36
|
+
// We use openProp and setOpenProp for control from outside the component.
|
|
37
|
+
const [_open, _setOpen] = React.useState(defaultOpen);
|
|
38
|
+
const open = openProp ?? _open;
|
|
39
|
+
const setOpen = React.useCallback((value) => {
|
|
40
|
+
const openState = typeof value === 'function' ? value(open) : value;
|
|
41
|
+
if (setOpenProp) {
|
|
42
|
+
setOpenProp(openState);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
_setOpen(openState);
|
|
46
|
+
}
|
|
47
|
+
// This sets the cookie to keep the sidebar state.
|
|
48
|
+
}, [setOpenProp, open]);
|
|
49
|
+
// Helper to toggle the sidebar.
|
|
50
|
+
const toggleSidebar = React.useCallback(() => {
|
|
51
|
+
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
|
|
52
|
+
}, [isMobile, setOpen, setOpenMobile]);
|
|
53
|
+
// Adds a keyboard shortcut to toggle the sidebar. A focused editor owns the chord (the rich
|
|
54
|
+
// text editor makes text bold on it), so the shortcut yields when the key press was already
|
|
55
|
+
// handled or comes from an editable field.
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
const handleKeyDown = (event) => {
|
|
58
|
+
if (event.defaultPrevented || isEditableTarget(event.target))
|
|
59
|
+
return;
|
|
60
|
+
if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
toggleSidebar();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
66
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
67
|
+
}, [toggleSidebar]);
|
|
68
|
+
// We add a state so that we can do data-state="expanded" or "collapsed".
|
|
69
|
+
// This makes it easier to style the sidebar with Tailwind classes.
|
|
70
|
+
const state = open ? 'expanded' : 'collapsed';
|
|
71
|
+
const contextValue = React.useMemo(() => ({
|
|
72
|
+
state,
|
|
73
|
+
open,
|
|
74
|
+
setOpen,
|
|
75
|
+
isMobile,
|
|
76
|
+
openMobile,
|
|
77
|
+
setOpenMobile,
|
|
78
|
+
toggleSidebar,
|
|
79
|
+
}), [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]);
|
|
80
|
+
return (_jsx(SidebarContext.Provider, { value: contextValue, children: _jsx("div", { "data-slot": "sidebar-wrapper", style: {
|
|
81
|
+
'--sidebar-width': SIDEBAR_WIDTH,
|
|
82
|
+
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
|
|
83
|
+
...style,
|
|
84
|
+
}, className: cn('group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar', className), ...props, children: children }) }));
|
|
85
|
+
}
|
|
86
|
+
function Sidebar({ side = 'left', variant = 'sidebar', collapsible = 'offcanvas', className, children, dir, ...props }) {
|
|
87
|
+
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
|
|
88
|
+
if (collapsible === 'none') {
|
|
89
|
+
return (_jsx("div", { "data-slot": "sidebar", className: cn('flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground', className), ...props, children: children }));
|
|
90
|
+
}
|
|
91
|
+
if (isMobile) {
|
|
92
|
+
return (_jsx(Sheet, { open: openMobile, onOpenChange: setOpenMobile, ...props, children: _jsxs(SheetContent, { dir: dir, "data-sidebar": "sidebar", "data-slot": "sidebar", "data-mobile": "true", className: "w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden", style: {
|
|
93
|
+
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
|
|
94
|
+
}, side: side, children: [_jsxs(SheetHeader, { className: "sr-only", children: [_jsx(SheetTitle, { children: "Sidebar" }), _jsx(SheetDescription, { children: "Displays the mobile sidebar." })] }), _jsx("div", { className: "flex h-full w-full flex-col", children: children })] }) }));
|
|
95
|
+
}
|
|
96
|
+
const iconGapWidth = variant === 'floating' || variant === 'inset'
|
|
97
|
+
? 'calc(var(--sidebar-width-icon) + 1rem)'
|
|
98
|
+
: 'var(--sidebar-width-icon)';
|
|
99
|
+
const gapWidth = state === 'collapsed'
|
|
100
|
+
? collapsible === 'offcanvas'
|
|
101
|
+
? '0px'
|
|
102
|
+
: iconGapWidth
|
|
103
|
+
: 'var(--sidebar-width)';
|
|
104
|
+
const containerX = state === 'collapsed' && collapsible === 'offcanvas'
|
|
105
|
+
? side === 'left'
|
|
106
|
+
? 'calc(var(--sidebar-width) * -1)'
|
|
107
|
+
: 'var(--sidebar-width)'
|
|
108
|
+
: '0px';
|
|
109
|
+
const containerWidth = state === 'collapsed' && collapsible === 'icon'
|
|
110
|
+
? variant === 'floating' || variant === 'inset'
|
|
111
|
+
? 'calc(var(--sidebar-width-icon) + 1rem + 2px)'
|
|
112
|
+
: 'var(--sidebar-width-icon)'
|
|
113
|
+
: 'var(--sidebar-width)';
|
|
114
|
+
return (_jsxs("div", { className: "group peer hidden text-sidebar-foreground md:block", "data-state": state, "data-collapsible": state === 'collapsed' ? collapsible : '', "data-variant": variant, "data-side": side, "data-slot": "sidebar", children: [_jsx(motion.div, { "data-slot": "sidebar-gap", initial: false, animate: { width: gapWidth }, transition: transitions.base, className: cn('relative bg-transparent', side === 'right' && 'rotate-180') }), _jsx(motion.div, { "data-slot": "sidebar-container", "data-side": side, initial: false, animate: { x: containerX, width: containerWidth }, transition: transitions.base, className: cn('fixed inset-y-0 z-(--z-sticky) hidden h-svh w-(--sidebar-width) md:flex', side === 'left' ? 'left-0' : 'right-0',
|
|
115
|
+
// Adjust the padding for floating and inset variants.
|
|
116
|
+
variant === 'floating' || variant === 'inset'
|
|
117
|
+
? 'p-2'
|
|
118
|
+
: side === 'left'
|
|
119
|
+
? 'border-r'
|
|
120
|
+
: 'border-l', className), ...props, children: _jsx("div", { "data-sidebar": "sidebar", "data-slot": "sidebar-inner", className: "flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border", children: children }) })] }));
|
|
121
|
+
}
|
|
122
|
+
function SidebarTrigger({ className, onClick, ...props }) {
|
|
123
|
+
const { toggleSidebar } = useSidebar();
|
|
124
|
+
return (_jsxs(Button, { "data-sidebar": "trigger", "data-slot": "sidebar-trigger", variant: "ghost", size: "icon-sm", className: cn(className), onClick: (event) => {
|
|
125
|
+
onClick?.(event);
|
|
126
|
+
toggleSidebar();
|
|
127
|
+
}, ...props, children: [_jsx(PanelLeftIcon, {}), _jsx("span", { className: "sr-only", children: "Toggle Sidebar" })] }));
|
|
128
|
+
}
|
|
129
|
+
function SidebarRail({ className, ...props }) {
|
|
130
|
+
const { toggleSidebar } = useSidebar();
|
|
131
|
+
return (_jsx("button", { "data-sidebar": "rail", "data-slot": "sidebar-rail", "aria-label": "Toggle Sidebar", tabIndex: -1, onClick: toggleSidebar, title: "Toggle Sidebar", className: cn('absolute inset-y-0 z-20 hidden w-4 transition-colors group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2', 'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize', '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize', 'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar', '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2', '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2', className), ...props }));
|
|
132
|
+
}
|
|
133
|
+
function SidebarInset({ className, ...props }) {
|
|
134
|
+
return (_jsx("main", { "data-slot": "sidebar-inset", className: cn('relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ms-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ms-2', className), ...props }));
|
|
135
|
+
}
|
|
136
|
+
function SidebarInput({ className, ...props }) {
|
|
137
|
+
return (_jsx(Input, { "data-slot": "sidebar-input", "data-sidebar": "input", className: cn('h-8 w-full bg-background shadow-none', className), ...props }));
|
|
138
|
+
}
|
|
139
|
+
function SidebarHeader({ className, ...props }) {
|
|
140
|
+
return (_jsx("div", { "data-slot": "sidebar-header", "data-sidebar": "header", className: cn('flex flex-col gap-2 p-2', className), ...props }));
|
|
141
|
+
}
|
|
142
|
+
function SidebarFooter({ className, ...props }) {
|
|
143
|
+
return (_jsx("div", { "data-slot": "sidebar-footer", "data-sidebar": "footer", className: cn('flex flex-col gap-2 p-2', className), ...props }));
|
|
144
|
+
}
|
|
145
|
+
function SidebarSeparator({ className, ...props }) {
|
|
146
|
+
return (_jsx(Separator, { "data-slot": "sidebar-separator", "data-sidebar": "separator", className: cn('mx-2 w-auto bg-sidebar-border', className), ...props }));
|
|
147
|
+
}
|
|
148
|
+
function SidebarContent({ className, ...props }) {
|
|
149
|
+
return (_jsx("div", { "data-slot": "sidebar-content", "data-sidebar": "content", className: cn('no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden', className), ...props }));
|
|
150
|
+
}
|
|
151
|
+
function SidebarGroup({ className, ...props }) {
|
|
152
|
+
return (_jsx("div", { "data-slot": "sidebar-group", "data-sidebar": "group", className: cn('relative flex w-full min-w-0 flex-col p-2', className), ...props }));
|
|
153
|
+
}
|
|
154
|
+
function SidebarGroupLabel({ className, render, ...props }) {
|
|
155
|
+
const { state: sidebarState } = useSidebar();
|
|
156
|
+
// Animate whenever collapsed; in offcanvas mode the sidebar is off-screen so the
|
|
157
|
+
// animation is hidden. In icon mode this collapses the label up into the sidebar.
|
|
158
|
+
const isIconCollapsed = sidebarState === 'collapsed';
|
|
159
|
+
const label = useRender({
|
|
160
|
+
defaultTagName: 'div',
|
|
161
|
+
props: mergeProps({
|
|
162
|
+
className: cn('flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0', className),
|
|
163
|
+
}, props),
|
|
164
|
+
render,
|
|
165
|
+
state: {
|
|
166
|
+
slot: 'sidebar-group-label',
|
|
167
|
+
sidebar: 'group-label',
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
return (_jsx(motion.div, { initial: false, animate: { marginTop: isIconCollapsed ? '-32px' : '0px', opacity: isIconCollapsed ? 0 : 1 }, transition: transitions.base, children: label }));
|
|
171
|
+
}
|
|
172
|
+
function SidebarGroupAction({ className, render, ...props }) {
|
|
173
|
+
return useRender({
|
|
174
|
+
defaultTagName: 'button',
|
|
175
|
+
props: mergeProps({
|
|
176
|
+
className: cn('absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0', className),
|
|
177
|
+
}, props),
|
|
178
|
+
render,
|
|
179
|
+
state: {
|
|
180
|
+
slot: 'sidebar-group-action',
|
|
181
|
+
sidebar: 'group-action',
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function SidebarGroupContent({ className, ...props }) {
|
|
186
|
+
return (_jsx("div", { "data-slot": "sidebar-group-content", "data-sidebar": "group-content", className: cn('w-full text-sm', className), ...props }));
|
|
187
|
+
}
|
|
188
|
+
function SidebarMenu({ className, ...props }) {
|
|
189
|
+
return (_jsx("ul", { "data-slot": "sidebar-menu", "data-sidebar": "menu", className: cn('flex w-full min-w-0 flex-col gap-0', className), ...props }));
|
|
190
|
+
}
|
|
191
|
+
function SidebarMenuItem({ className, ...props }) {
|
|
192
|
+
return (_jsx("li", { "data-slot": "sidebar-menu-item", "data-sidebar": "menu-item", className: cn('group/menu-item relative', className), ...props }));
|
|
193
|
+
}
|
|
194
|
+
const sidebarMenuButtonVariants = cva('peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground data-[active=true]:bg-selected data-[active=true]:font-medium data-[active=true]:text-selected-foreground [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate', {
|
|
195
|
+
variants: {
|
|
196
|
+
variant: {
|
|
197
|
+
default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
|
|
198
|
+
outline: 'bg-background ring-1 ring-sidebar-border hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:ring-sidebar-accent',
|
|
199
|
+
},
|
|
200
|
+
size: {
|
|
201
|
+
default: 'h-8 text-sm',
|
|
202
|
+
sm: 'h-7 text-xs',
|
|
203
|
+
lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
defaultVariants: {
|
|
207
|
+
variant: 'default',
|
|
208
|
+
size: 'default',
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
function SidebarMenuButton({ render, isActive = false, variant = 'default', size = 'default', tooltip, className, ...props }) {
|
|
212
|
+
const { isMobile, state } = useSidebar();
|
|
213
|
+
const comp = useRender({
|
|
214
|
+
defaultTagName: 'button',
|
|
215
|
+
props: mergeProps({
|
|
216
|
+
className: cn(sidebarMenuButtonVariants({ variant, size }), className),
|
|
217
|
+
}, props),
|
|
218
|
+
render: !tooltip ? render : _jsx(TooltipTrigger, { render: render }),
|
|
219
|
+
state: {
|
|
220
|
+
slot: 'sidebar-menu-button',
|
|
221
|
+
sidebar: 'menu-button',
|
|
222
|
+
size,
|
|
223
|
+
active: isActive,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
if (!tooltip) {
|
|
227
|
+
return comp;
|
|
228
|
+
}
|
|
229
|
+
if (typeof tooltip === 'string') {
|
|
230
|
+
tooltip = {
|
|
231
|
+
children: tooltip,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return (_jsxs(Tooltip, { children: [comp, _jsx(TooltipContent, { side: "right", align: "center", hidden: state !== 'collapsed' || isMobile, ...tooltip })] }));
|
|
235
|
+
}
|
|
236
|
+
function SidebarMenuAction({ className, render, showOnHover = false, ...props }) {
|
|
237
|
+
return useRender({
|
|
238
|
+
defaultTagName: 'button',
|
|
239
|
+
props: mergeProps({
|
|
240
|
+
className: cn('absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0', showOnHover &&
|
|
241
|
+
'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground aria-expanded:opacity-100 md:opacity-0', className),
|
|
242
|
+
}, props),
|
|
243
|
+
render,
|
|
244
|
+
state: {
|
|
245
|
+
slot: 'sidebar-menu-action',
|
|
246
|
+
sidebar: 'menu-action',
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function SidebarMenuBadge({ className, ...props }) {
|
|
251
|
+
return (_jsx("div", { "data-slot": "sidebar-menu-badge", "data-sidebar": "menu-badge", className: cn('pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none group-data-[collapsible=icon]:hidden peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground', className), ...props }));
|
|
252
|
+
}
|
|
253
|
+
function SidebarMenuSkeleton({ className, showIcon = false, ...props }) {
|
|
254
|
+
// Random width between 50 to 90%.
|
|
255
|
+
const [width] = React.useState(() => {
|
|
256
|
+
return `${Math.floor(Math.random() * 40) + 50}%`;
|
|
257
|
+
});
|
|
258
|
+
return (_jsxs("div", { "data-slot": "sidebar-menu-skeleton", "data-sidebar": "menu-skeleton", className: cn('flex h-8 items-center gap-2 rounded-md px-2', className), ...props, children: [showIcon && _jsx(Skeleton, { className: "size-4 rounded-md", "data-sidebar": "menu-skeleton-icon" }), _jsx(Skeleton, { className: "h-4 max-w-(--skeleton-width) flex-1", "data-sidebar": "menu-skeleton-text", style: {
|
|
259
|
+
'--skeleton-width': width,
|
|
260
|
+
} })] }));
|
|
261
|
+
}
|
|
262
|
+
function SidebarMenuSub({ className, ...props }) {
|
|
263
|
+
return (_jsx("ul", { "data-slot": "sidebar-menu-sub", "data-sidebar": "menu-sub", className: cn('mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5 group-data-[collapsible=icon]:hidden', className), ...props }));
|
|
264
|
+
}
|
|
265
|
+
function SidebarMenuSubItem({ className, ...props }) {
|
|
266
|
+
return (_jsx("li", { "data-slot": "sidebar-menu-sub-item", "data-sidebar": "menu-sub-item", className: cn('group/menu-sub-item relative', className), ...props }));
|
|
267
|
+
}
|
|
268
|
+
function SidebarMenuSubButton({ render, size = 'md', isActive = false, className, ...props }) {
|
|
269
|
+
return useRender({
|
|
270
|
+
defaultTagName: 'a',
|
|
271
|
+
props: mergeProps({
|
|
272
|
+
className: cn('flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden group-data-[collapsible=icon]:hidden hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-sm data-[size=sm]:text-xs data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground', className),
|
|
273
|
+
}, props),
|
|
274
|
+
render,
|
|
275
|
+
state: {
|
|
276
|
+
slot: 'sidebar-menu-sub-button',
|
|
277
|
+
sidebar: 'menu-sub-button',
|
|
278
|
+
size,
|
|
279
|
+
active: isActive,
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar, };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { HTMLMotionProps } from '../../motion/index.js';
|
|
3
|
+
type SkeletonProps = Omit<HTMLMotionProps<'div'>, 'children'> & {
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
/**
|
|
6
|
+
* `pulse` fades the block in and out. `shimmer` is Vibe's skeleton: a still block with a light
|
|
7
|
+
* band sweeping across it. Under reduced motion `shimmer` falls back to `pulse`, because the
|
|
8
|
+
* sweep is a transform and would otherwise freeze mid-flight.
|
|
9
|
+
*/
|
|
10
|
+
variant?: 'pulse' | 'shimmer';
|
|
11
|
+
};
|
|
12
|
+
declare function Skeleton({ className, variant, children, ...props }: SkeletonProps): import("react").JSX.Element;
|
|
13
|
+
export { Skeleton };
|
|
14
|
+
export type { SkeletonProps };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { motion, useReducedMotion } from '../../motion/index.js';
|
|
3
|
+
import { loops } from '../../motion/config.js';
|
|
4
|
+
import { cn } from '../../lib/utils.js';
|
|
5
|
+
function Skeleton({ className, variant = 'pulse', children, ...props }) {
|
|
6
|
+
const reduceMotion = useReducedMotion();
|
|
7
|
+
if (variant === 'shimmer' && reduceMotion !== true) {
|
|
8
|
+
return (_jsxs(motion.div, { "data-slot": "skeleton", "data-variant": "shimmer", className: cn('relative isolate overflow-hidden rounded-md bg-muted', className), ...props, children: [_jsx(motion.span, { "aria-hidden": true, className: "absolute inset-0 bg-linear-to-r from-transparent via-background/70 to-transparent", initial: { transform: 'translateX(-100%)' }, animate: { transform: ['translateX(-100%)', 'translateX(100%)'] }, transition: loops.sweep }), children] }));
|
|
9
|
+
}
|
|
10
|
+
return (_jsx(motion.div, { "data-slot": "skeleton", animate: { opacity: [1, 0.4, 1] }, transition: loops.pulse, className: cn('rounded-md bg-muted', className), ...props, children: children }));
|
|
11
|
+
}
|
|
12
|
+
export { Skeleton };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Toaster as SonnerToaster } from 'sonner';
|
|
3
|
+
export { toast } from 'sonner';
|
|
4
|
+
function Toaster({ ...props }) {
|
|
5
|
+
return (_jsx(SonnerToaster, { "data-slot": "toaster", className: "toaster", toastOptions: {
|
|
6
|
+
classNames: {
|
|
7
|
+
toast: 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
|
8
|
+
description: 'group-[.toast]:text-muted-foreground',
|
|
9
|
+
actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
|
10
|
+
cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
|
|
11
|
+
},
|
|
12
|
+
}, ...props }));
|
|
13
|
+
}
|
|
14
|
+
export { Toaster };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface SparkBarsBar {
|
|
2
|
+
/** Short label for the bar (bucket range, day, …). */
|
|
3
|
+
label: string;
|
|
4
|
+
value: number;
|
|
5
|
+
}
|
|
6
|
+
export interface SparkBarsProps extends Omit<React.ComponentProps<'figure'>, 'children'> {
|
|
7
|
+
bars: readonly SparkBarsBar[];
|
|
8
|
+
/** Names the chart for assistive tech. Required. */
|
|
9
|
+
'aria-label': string;
|
|
10
|
+
/**
|
|
11
|
+
* `default` — full-width spark with axis end labels (standalone summary).
|
|
12
|
+
* `sm` — lean inline spark for table cells: short height, no axis labels.
|
|
13
|
+
*/
|
|
14
|
+
size?: 'default' | 'sm';
|
|
15
|
+
/** Formats values in the tooltip and the screen-reader table; defaults to `String`. */
|
|
16
|
+
formatValue?: (value: number) => string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional secondary string under the hover readout (e.g. exact spend when cents rounding
|
|
19
|
+
* hid precision).
|
|
20
|
+
*/
|
|
21
|
+
formatValueDetail?: (value: number) => string | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compact spark histogram: equal-width bars, soft entrance, one hover/focus readout.
|
|
25
|
+
* Hand-rolled (no chart library); colours from `--chart-1` only. Empty bars stay as a hairline
|
|
26
|
+
* so the distribution shape stays readable when a bucket has zero runs.
|
|
27
|
+
*/
|
|
28
|
+
declare function SparkBars({ bars, size, formatValue, formatValueDetail, className, 'aria-label': ariaLabel, ...props }: SparkBarsProps): import("react").JSX.Element;
|
|
29
|
+
export { SparkBars };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useState } from 'react';
|
|
3
|
+
import { AnimatePresence, motion } from '../../motion/index.js';
|
|
4
|
+
import { presets, staggerDelay, transitions } from '../../motion/config.js';
|
|
5
|
+
import { cn } from '../../lib/utils.js';
|
|
6
|
+
/**
|
|
7
|
+
* Compact spark histogram: equal-width bars, soft entrance, one hover/focus readout.
|
|
8
|
+
* Hand-rolled (no chart library); colours from `--chart-1` only. Empty bars stay as a hairline
|
|
9
|
+
* so the distribution shape stays readable when a bucket has zero runs.
|
|
10
|
+
*/
|
|
11
|
+
function SparkBars({ bars, size = 'default', formatValue = String, formatValueDetail, className, 'aria-label': ariaLabel, ...props }) {
|
|
12
|
+
const [active, setActive] = useState(null);
|
|
13
|
+
const tableId = useId();
|
|
14
|
+
const max = Math.max(1, ...bars.map((bar) => bar.value));
|
|
15
|
+
const activeBar = active === null ? null : bars[active];
|
|
16
|
+
const activeDetail = activeBar ? formatValueDetail?.(activeBar.value) : undefined;
|
|
17
|
+
const activeLeft = active === null || bars.length < 1 ? 50 : ((active + 0.5) / bars.length) * 100;
|
|
18
|
+
const lean = size === 'sm';
|
|
19
|
+
return (_jsxs("figure", { "data-slot": "spark-bars", "data-size": size, "aria-label": ariaLabel, className: cn('relative flex flex-col text-xs text-muted-foreground', lean ? 'h-4 w-16 shrink-0 gap-0' : 'h-16 w-full gap-1', className), ...props, children: [_jsxs("div", { className: "relative min-h-0 flex-1", onMouseLeave: () => setActive(null), "aria-describedby": tableId, children: [_jsx("div", { className: "absolute inset-0 flex items-end gap-px", "aria-hidden": "true", children: bars.map((bar, index) => {
|
|
20
|
+
const heightPct = bar.value <= 0 ? 0 : Math.max(lean ? 12 : 8, (bar.value / max) * 100);
|
|
21
|
+
return (_jsx("div", { className: "flex h-full min-w-0 flex-1 items-end", children: _jsx(motion.div, { initial: { height: 0 }, animate: { height: bar.value <= 0 ? '1px' : `${heightPct}%` }, transition: { ...transitions.slow, delay: staggerDelay(index) }, "data-active": active === index || undefined, className: cn('w-full rounded-sm bg-chart-1/35', bar.value <= 0 && 'bg-border', active === index && 'bg-chart-1') }) }, `${bar.label}-${index}`));
|
|
22
|
+
}) }), _jsx("div", { className: "absolute inset-0 flex", children: bars.map((bar, index) => (_jsx("button", { type: "button", "aria-label": `${bar.label}: ${formatValue(bar.value)}`, "data-active": active === index || undefined, className: "min-w-0 flex-1 cursor-crosshair rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring/50", onMouseEnter: () => setActive(index), onFocus: () => setActive(index), onBlur: () => setActive(null) }, `${bar.label}-${index}`))) }), _jsx(AnimatePresence, { children: activeBar ? (_jsxs(motion.div, { ...presets.fade, role: "presentation", "aria-hidden": "true", "data-slot": "spark-bars-tooltip", className: cn('pointer-events-none absolute z-10 -translate-x-1/2 -translate-y-full whitespace-nowrap rounded-md bg-foreground text-xs text-background shadow-md', lean ? '-top-0.5 px-1.5 py-0.5' : '-top-1 px-2 py-1'), style: { left: `${activeLeft}%` }, children: [_jsx("span", { className: "font-semibold tabular-nums", children: formatValue(activeBar.value) }), _jsx("span", { className: "ms-1.5 opacity-70", children: activeBar.label }), activeDetail ? (_jsx("div", { className: "mt-0.5 opacity-70 tabular-nums", children: activeDetail })) : null] }, "tooltip")) : null })] }), !lean && bars.length > 0 ? (_jsxs("div", { className: "flex justify-between tabular-nums", "aria-hidden": "true", children: [_jsx("span", { className: "truncate", children: bars[0]?.label }), bars.length > 1 ? (_jsx("span", { className: "truncate text-end", children: bars[bars.length - 1]?.label })) : null] })) : null, _jsxs("table", { id: tableId, className: "sr-only", children: [_jsx("caption", { children: ariaLabel }), _jsx("tbody", { children: bars.map((bar, index) => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: bar.label }), _jsx("td", { children: formatValue(bar.value) })] }, `${bar.label}-${index}`))) })] })] }));
|
|
23
|
+
}
|
|
24
|
+
export { SparkBars };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HTMLMotionProps } from '../../motion/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Spinning loading indicator. The rotation is driven by motion.dev, so it honours
|
|
4
|
+
* `MotionProvider`'s `reducedMotion` setting and the `disableMotion()` test helper.
|
|
5
|
+
* The wrapper span carries the ARIA role; the SVG is hidden from assistive tech.
|
|
6
|
+
* Pass `className` to size/colour the icon; all other props forward to the wrapper span.
|
|
7
|
+
*
|
|
8
|
+
* Both loops run as browser (WAAPI) animations, off the main thread: the turn animates the whole
|
|
9
|
+
* `transform` (motion would drive `rotate` frame by frame in JS).
|
|
10
|
+
*
|
|
11
|
+
* Reduced motion: the spinner stops turning and only pulses, so it still reads as loading.
|
|
12
|
+
* `MotionProvider` drops `rotate`, `x` and the like under `prefers-reduced-motion`, but not a
|
|
13
|
+
* whole `transform`, so the spinner checks for itself.
|
|
14
|
+
*/
|
|
15
|
+
declare function Spinner({ className, ...props }: HTMLMotionProps<'span'>): import("react").JSX.Element;
|
|
16
|
+
export { Spinner };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { motion, useReducedMotion } from '../../motion/index.js';
|
|
3
|
+
import { loopAnimate, loopTransition } from '../../motion/config.js';
|
|
4
|
+
import { LoaderCircle as Loader2Icon } from '../../icons/index.js';
|
|
5
|
+
import { cn } from '../../lib/utils.js';
|
|
6
|
+
/**
|
|
7
|
+
* Spinning loading indicator. The rotation is driven by motion.dev, so it honours
|
|
8
|
+
* `MotionProvider`'s `reducedMotion` setting and the `disableMotion()` test helper.
|
|
9
|
+
* The wrapper span carries the ARIA role; the SVG is hidden from assistive tech.
|
|
10
|
+
* Pass `className` to size/colour the icon; all other props forward to the wrapper span.
|
|
11
|
+
*
|
|
12
|
+
* Both loops run as browser (WAAPI) animations, off the main thread: the turn animates the whole
|
|
13
|
+
* `transform` (motion would drive `rotate` frame by frame in JS).
|
|
14
|
+
*
|
|
15
|
+
* Reduced motion: the spinner stops turning and only pulses, so it still reads as loading.
|
|
16
|
+
* `MotionProvider` drops `rotate`, `x` and the like under `prefers-reduced-motion`, but not a
|
|
17
|
+
* whole `transform`, so the spinner checks for itself.
|
|
18
|
+
*/
|
|
19
|
+
function Spinner({ className, ...props }) {
|
|
20
|
+
const reduceMotion = useReducedMotion();
|
|
21
|
+
return (_jsx(motion.span, { role: "status", "aria-label": "Loading", "data-slot": "spinner",
|
|
22
|
+
// Stable refs from `loopAnimate` / `loopTransition` — a fresh animate object each render
|
|
23
|
+
// restarts the loop (agent Trace re-renders often; leave/return looked frozen until refresh).
|
|
24
|
+
animate: reduceMotion ? loopAnimate.spinnerReduced : loopAnimate.spinner, transition: loopTransition.spinner, className: "inline-flex", ...props, children: _jsx(Loader2Icon, { className: cn('size-4', className), "aria-hidden": "true" }) }));
|
|
25
|
+
}
|
|
26
|
+
export { Spinner };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface StackedBarSegment {
|
|
2
|
+
label: string;
|
|
3
|
+
value: number;
|
|
4
|
+
/** Picks one of the five chart steps; wraps past 5. */
|
|
5
|
+
colorIndex: number;
|
|
6
|
+
}
|
|
7
|
+
export interface StackedBarProps extends Omit<React.ComponentProps<'div'>, 'children'> {
|
|
8
|
+
segments: readonly StackedBarSegment[];
|
|
9
|
+
/** Names the bar for assistive tech. Required. */
|
|
10
|
+
'aria-label': string;
|
|
11
|
+
formatValue?: (value: number) => string;
|
|
12
|
+
/** Hide the legend under the bar when the host lists the segments itself. */
|
|
13
|
+
legend?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Widths as percentages. A segment that would draw thinner than 2% is pinned to 2% so it stays
|
|
17
|
+
* visible and hoverable; the rest are scaled down to fill what remains.
|
|
18
|
+
*/
|
|
19
|
+
export declare const stackedBarWidths: (values: readonly number[]) => number[];
|
|
20
|
+
/**
|
|
21
|
+
* One bar split into shares. Each segment is a focusable trigger with its own tooltip, so the
|
|
22
|
+
* breakdown is reachable by pointer and keyboard alike; the legend repeats every label with its
|
|
23
|
+
* share so nothing depends on hovering.
|
|
24
|
+
*/
|
|
25
|
+
declare function StackedBar({ segments, formatValue, legend, className, 'aria-label': ariaLabel, ...props }: StackedBarProps): import("react").JSX.Element;
|
|
26
|
+
export interface StackedBarSkeletonProps extends Omit<React.ComponentProps<'div'>, 'children'> {
|
|
27
|
+
/** Segments and legend entries to draw, 1 to 3. */
|
|
28
|
+
segments?: 1 | 2 | 3;
|
|
29
|
+
/** Draw the legend line, as `StackedBar` does by default. */
|
|
30
|
+
legend?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The loading shape of `StackedBar`: the same track split into still segments, and a legend line
|
|
34
|
+
* with a dot and a label stub for each segment.
|
|
35
|
+
*/
|
|
36
|
+
declare function StackedBarSkeleton({ segments, legend, className, ...props }: StackedBarSkeletonProps): import("react").JSX.Element;
|
|
37
|
+
export { StackedBar, StackedBarSkeleton };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { motion } from '../../motion/index.js';
|
|
3
|
+
import { staggerDelay, transitions } from '../../motion/config.js';
|
|
4
|
+
import { cn } from '../../lib/utils.js';
|
|
5
|
+
import { Skeleton } from './skeleton.js';
|
|
6
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip.js';
|
|
7
|
+
// Shared with `StackedBarSkeleton`: change a size here and the skeleton follows.
|
|
8
|
+
const ROOT_CLASS = 'flex flex-col gap-3';
|
|
9
|
+
const BAR_CLASS = 'flex h-3 w-full gap-px overflow-hidden rounded-full bg-muted';
|
|
10
|
+
const LEGEND_CLASS = 'flex flex-wrap gap-x-4 gap-y-1 text-xs';
|
|
11
|
+
const MIN_SHARE_PCT = 2;
|
|
12
|
+
const CHART_CLASS = ['bg-chart-1', 'bg-chart-2', 'bg-chart-3', 'bg-chart-4', 'bg-chart-5'];
|
|
13
|
+
const chartClass = (colorIndex) => CHART_CLASS[Math.abs(colorIndex) % CHART_CLASS.length];
|
|
14
|
+
/**
|
|
15
|
+
* Widths as percentages. A segment that would draw thinner than 2% is pinned to 2% so it stays
|
|
16
|
+
* visible and hoverable; the rest are scaled down to fill what remains.
|
|
17
|
+
*/
|
|
18
|
+
export const stackedBarWidths = (values) => {
|
|
19
|
+
const total = values.reduce((sum, value) => sum + value, 0);
|
|
20
|
+
if (total <= 0)
|
|
21
|
+
return values.map(() => 0);
|
|
22
|
+
const raw = values.map((value) => (value / total) * 100);
|
|
23
|
+
const pinned = raw.map((share) => share > 0 && share < MIN_SHARE_PCT);
|
|
24
|
+
const pinnedTotal = pinned.filter(Boolean).length * MIN_SHARE_PCT;
|
|
25
|
+
const freeTotal = raw.reduce((sum, share, i) => (pinned[i] ? sum : sum + share), 0);
|
|
26
|
+
if (freeTotal <= 0)
|
|
27
|
+
return raw.map((share, i) => (pinned[i] ? MIN_SHARE_PCT : share));
|
|
28
|
+
const scale = (100 - pinnedTotal) / freeTotal;
|
|
29
|
+
return raw.map((share, i) => (pinned[i] ? MIN_SHARE_PCT : share * scale));
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* One bar split into shares. Each segment is a focusable trigger with its own tooltip, so the
|
|
33
|
+
* breakdown is reachable by pointer and keyboard alike; the legend repeats every label with its
|
|
34
|
+
* share so nothing depends on hovering.
|
|
35
|
+
*/
|
|
36
|
+
function StackedBar({ segments, formatValue = String, legend = true, className, 'aria-label': ariaLabel, ...props }) {
|
|
37
|
+
const total = segments.reduce((sum, segment) => sum + segment.value, 0);
|
|
38
|
+
const widths = stackedBarWidths(segments.map((segment) => segment.value));
|
|
39
|
+
const percent = (value) => (total > 0 ? Math.round((value / total) * 100) : 0);
|
|
40
|
+
return (_jsxs("div", { "data-slot": "stacked-bar", className: cn(ROOT_CLASS, className), ...props, children: [_jsx("div", { role: "group", "aria-label": ariaLabel, className: BAR_CLASS, children: segments.map((segment, index) => {
|
|
41
|
+
const width = widths[index] ?? 0;
|
|
42
|
+
if (width <= 0)
|
|
43
|
+
return null;
|
|
44
|
+
const description = `${segment.label}: ${formatValue(segment.value)} (${percent(segment.value)}%)`;
|
|
45
|
+
return (_jsx(motion.div, { initial: { width: 0 }, animate: { width: `${width}%` }, transition: { ...transitions.slow, delay: staggerDelay(index) }, className: "h-full min-w-0 shrink-0", children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { "aria-label": description, "data-slot": "stacked-bar-segment", className: cn('h-full w-full outline-none transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset', chartClass(segment.colorIndex)) }), _jsx(TooltipContent, { children: description })] }) }, `${segment.label}-${index}`));
|
|
46
|
+
}) }), legend && segments.length > 0 && (_jsx("ul", { "data-slot": "stacked-bar-legend", className: LEGEND_CLASS, children: segments.map((segment, index) => (_jsxs("li", { className: "flex items-center gap-1.5", children: [_jsx("span", { "aria-hidden": "true", className: cn('size-2 shrink-0 rounded-full', chartClass(segment.colorIndex)) }), _jsx("span", { className: "text-muted-foreground", children: segment.label }), _jsxs("span", { className: "font-medium tabular-nums", children: [percent(segment.value), "%"] })] }, `${segment.label}-${index}`))) }))] }));
|
|
47
|
+
}
|
|
48
|
+
const SKELETON_SHARES = ['w-1/2', 'w-[30%]', 'w-1/5'];
|
|
49
|
+
/**
|
|
50
|
+
* The loading shape of `StackedBar`: the same track split into still segments, and a legend line
|
|
51
|
+
* with a dot and a label stub for each segment.
|
|
52
|
+
*/
|
|
53
|
+
function StackedBarSkeleton({ segments = 3, legend = true, className, ...props }) {
|
|
54
|
+
return (_jsxs("div", { "data-slot": "stacked-bar-skeleton", "aria-hidden": "true", className: cn(ROOT_CLASS, className), ...props, children: [_jsx("div", { className: BAR_CLASS, children: SKELETON_SHARES.slice(0, segments).map((share) => (_jsx(Skeleton, { variant: "shimmer", className: cn('h-full shrink-0 rounded-none bg-foreground/10', share) }, share))) }), legend && (_jsx("div", { className: LEGEND_CLASS, children: Array.from({ length: segments }, (_, index) => (_jsxs("div", { className: "flex h-4 items-center gap-1.5", children: [_jsx(Skeleton, { variant: "shimmer", className: "size-2 shrink-0 rounded-full" }), _jsx(Skeleton, { variant: "shimmer", className: "h-3 w-16" })] }, index))) }))] }));
|
|
55
|
+
}
|
|
56
|
+
export { StackedBar, StackedBarSkeleton };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
2
|
+
declare const statusDotVariants: (props?: ({
|
|
3
|
+
status?: "done" | "working" | "stuck" | "idle" | "attention" | "unread" | null | undefined;
|
|
4
|
+
size?: "sm" | "md" | null | undefined;
|
|
5
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
6
|
+
interface StatusDotProps extends VariantProps<typeof statusDotVariants> {
|
|
7
|
+
label: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
/**
|
|
10
|
+
* `home` is the mobile Home dot: an 8px core in a fixed 12px box so every state lines up.
|
|
11
|
+
* Working is the `RunningIndicator` (green, pulsing halo) instead of the purple fade.
|
|
12
|
+
*/
|
|
13
|
+
appearance?: 'default' | 'home';
|
|
14
|
+
}
|
|
15
|
+
declare function StatusDot({ status, size, label, className, appearance }: StatusDotProps): import("react").JSX.Element;
|
|
16
|
+
export { StatusDot, statusDotVariants, type StatusDotProps };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cva } from 'class-variance-authority';
|
|
3
|
+
import { motion } from '../../motion/index.js';
|
|
4
|
+
import { loops } from '../../motion/config.js';
|
|
5
|
+
import { cn } from '../../lib/utils.js';
|
|
6
|
+
import { RunningIndicator } from './running-indicator.js';
|
|
7
|
+
const statusDotVariants = cva('inline-block shrink-0 rounded-full', {
|
|
8
|
+
variants: {
|
|
9
|
+
status: {
|
|
10
|
+
idle: 'bg-status-idle',
|
|
11
|
+
working: 'bg-status-working',
|
|
12
|
+
done: 'bg-status-done',
|
|
13
|
+
stuck: 'bg-status-stuck',
|
|
14
|
+
attention: 'bg-warning',
|
|
15
|
+
unread: 'bg-primary',
|
|
16
|
+
},
|
|
17
|
+
size: { sm: 'size-1.5', md: 'size-2' },
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: { status: 'idle', size: 'md' },
|
|
20
|
+
});
|
|
21
|
+
function StatusDot({ status, size, label, className, appearance = 'default' }) {
|
|
22
|
+
const isWorking = status === 'working';
|
|
23
|
+
if (appearance === 'home') {
|
|
24
|
+
return isWorking ? (_jsx(RunningIndicator, { label: label, className: className })) : (_jsx(HomeStatusDot, { status: status, label: label, className: className }));
|
|
25
|
+
}
|
|
26
|
+
return (_jsx(motion.span, { role: "img", "aria-label": label, "data-slot": "status-dot", animate: isWorking ? { opacity: [1, 0.3, 1] } : { opacity: 1 }, transition: isWorking ? loops.pulse : { duration: 0 }, className: cn(statusDotVariants({ status, size }), className) }));
|
|
27
|
+
}
|
|
28
|
+
/** A resting Home dot, in the same 12px box as the `RunningIndicator` so rows line up. */
|
|
29
|
+
function HomeStatusDot({ status, label, className, }) {
|
|
30
|
+
return (_jsx("span", { role: "img", "aria-label": label, "data-slot": "status-dot", "data-appearance": "home", className: cn('relative inline-grid size-3 shrink-0 place-items-center', className), children: _jsx("span", { "aria-hidden": true, "data-slot": "status-dot-core", className: cn('relative size-2 rounded-full', statusDotVariants({ status })) }) }));
|
|
31
|
+
}
|
|
32
|
+
export { StatusDot, statusDotVariants };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Switch as SwitchPrimitive } from '@base-ui/react/switch';
|
|
3
|
+
import { motion } from '../../motion/index.js';
|
|
4
|
+
import { transitions } from '../../motion/config.js';
|
|
5
|
+
import { cn } from '../../lib/utils.js';
|
|
6
|
+
/** Thumb slides to the checked position with a spring layout transition. */
|
|
7
|
+
function renderSwitchThumb(props, state) {
|
|
8
|
+
return (_jsx(motion.div, { ...props, initial: false, animate: { x: state.checked ? 'calc(100% - 2px)' : '0px' }, transition: transitions.layout }));
|
|
9
|
+
}
|
|
10
|
+
function Switch({ className, size = 'default', ...props }) {
|
|
11
|
+
return (_jsx(SwitchPrimitive.Root, { "data-slot": "switch", "data-size": size, className: cn('peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-colors outline-none group-has-[:focus-visible]/field-label:border-transparent group-has-[:focus-visible]/field-label:ring-0 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50', className), ...props, children: _jsx(SwitchPrimitive.Thumb, { "data-slot": "switch-thumb", render: renderSwitchThumb, className: "pointer-events-none block rounded-full bg-background ring-0 group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 dark:data-checked:bg-primary-foreground dark:data-unchecked:bg-foreground" }) }));
|
|
12
|
+
}
|
|
13
|
+
export { Switch };
|