@wheelhouse/ui 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blocks/columns/columns-types.d.ts +25 -1
- package/dist/blocks/columns/columns-types.d.ts.map +1 -1
- package/dist/blocks/columns/columns-types.js +4 -0
- package/dist/blocks/columns/columns-utils.d.ts +31 -1
- package/dist/blocks/columns/columns-utils.d.ts.map +1 -1
- package/dist/blocks/columns/columns-utils.js +86 -1
- package/dist/blocks/columns/columns.d.ts +1 -1
- package/dist/blocks/columns/columns.d.ts.map +1 -1
- package/dist/blocks/columns/columns.js +81 -20
- package/dist/blocks/columns/columns.stories.d.ts +1 -0
- package/dist/blocks/columns/columns.stories.d.ts.map +1 -1
- package/dist/blocks/columns/columns.stories.js +21 -1
- package/dist/blocks/columns/index.d.ts +1 -1
- package/dist/blocks/columns/index.d.ts.map +1 -1
- package/dist/blocks/floating-menu-widget/floating-menu-widget.d.ts +26 -0
- package/dist/blocks/floating-menu-widget/floating-menu-widget.d.ts.map +1 -0
- package/dist/blocks/floating-menu-widget/floating-menu-widget.js +200 -0
- package/dist/blocks/floating-menu-widget/floating-menu-widget.stories.d.ts +15 -0
- package/dist/blocks/floating-menu-widget/floating-menu-widget.stories.d.ts.map +1 -0
- package/dist/blocks/floating-menu-widget/floating-menu-widget.stories.js +22 -0
- package/dist/blocks/floating-menu-widget/index.d.ts +3 -0
- package/dist/blocks/floating-menu-widget/index.d.ts.map +1 -0
- package/dist/blocks/floating-menu-widget/index.js +1 -0
- package/dist/blocks/index.d.ts +1 -0
- package/dist/blocks/index.d.ts.map +1 -1
- package/dist/blocks/index.js +1 -0
- package/dist/components/button/button.d.ts +18 -11
- package/dist/components/button/button.d.ts.map +1 -1
- package/dist/components/button/button.js +27 -14
- package/dist/components/button/button.stories.d.ts +11 -0
- package/dist/components/button/button.stories.d.ts.map +1 -1
- package/dist/components/button/button.stories.js +84 -0
- package/dist/components/data-grid/data-grid-column-filter.d.ts +15 -0
- package/dist/components/data-grid/data-grid-column-filter.d.ts.map +1 -0
- package/dist/components/data-grid/data-grid-column-filter.js +36 -0
- package/dist/components/data-grid/data-grid-column-header.d.ts +15 -0
- package/dist/components/data-grid/data-grid-column-header.d.ts.map +1 -0
- package/dist/components/data-grid/data-grid-column-header.js +137 -0
- package/dist/components/data-grid/data-grid-column-visibility.d.ts +8 -0
- package/dist/components/data-grid/data-grid-column-visibility.d.ts.map +1 -0
- package/dist/components/data-grid/data-grid-column-visibility.js +13 -0
- package/dist/components/data-grid/data-grid-pagination.d.ts +20 -0
- package/dist/components/data-grid/data-grid-pagination.d.ts.map +1 -0
- package/dist/components/data-grid/data-grid-pagination.js +76 -0
- package/dist/components/data-grid/data-grid-scroll-area.d.ts +11 -0
- package/dist/components/data-grid/data-grid-scroll-area.d.ts.map +1 -0
- package/dist/components/data-grid/data-grid-scroll-area.js +218 -0
- package/dist/components/data-grid/data-grid-table-dnd-rows.d.ts +12 -0
- package/dist/components/data-grid/data-grid-table-dnd-rows.d.ts.map +1 -0
- package/dist/components/data-grid/data-grid-table-dnd-rows.js +91 -0
- package/dist/components/data-grid/data-grid-table-dnd.d.ts +8 -0
- package/dist/components/data-grid/data-grid-table-dnd.d.ts.map +1 -0
- package/dist/components/data-grid/data-grid-table-dnd.js +95 -0
- package/dist/components/data-grid/data-grid-table-virtual.d.ts +28 -0
- package/dist/components/data-grid/data-grid-table-virtual.d.ts.map +1 -0
- package/dist/components/data-grid/data-grid-table-virtual.js +133 -0
- package/dist/components/data-grid/data-grid-table.d.ts +98 -0
- package/dist/components/data-grid/data-grid-table.d.ts.map +1 -0
- package/dist/components/data-grid/data-grid-table.js +560 -0
- package/dist/components/data-grid/data-grid.d.ts +94 -0
- package/dist/components/data-grid/data-grid.d.ts.map +1 -0
- package/dist/components/data-grid/data-grid.js +123 -0
- package/dist/components/data-grid/data-grid.stories.d.ts +14 -0
- package/dist/components/data-grid/data-grid.stories.d.ts.map +1 -0
- package/dist/components/data-grid/data-grid.stories.js +47 -0
- package/dist/components/data-grid/index.d.ts +14 -0
- package/dist/components/data-grid/index.d.ts.map +1 -0
- package/dist/components/data-grid/index.js +10 -0
- package/dist/components/filters/filter-date-metric-value.d.ts.map +1 -1
- package/dist/components/filters/filter-date-metric-value.js +83 -8
- package/dist/components/filters/filter-fields-listing-demo.d.ts +12 -0
- package/dist/components/filters/filter-fields-listing-demo.d.ts.map +1 -0
- package/dist/components/filters/filter-fields-listing-demo.js +565 -0
- package/dist/components/filters/filters-types.d.ts +7 -0
- package/dist/components/filters/filters-types.d.ts.map +1 -1
- package/dist/components/filters/filters.stories.d.ts.map +1 -1
- package/dist/components/filters/filters.stories.js +8 -149
- package/dist/components/filters/index.d.ts +1 -0
- package/dist/components/filters/index.d.ts.map +1 -1
- package/dist/components/filters/index.js +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +3 -0
- package/dist/components/popover/index.d.ts +1 -0
- package/dist/components/popover/index.d.ts.map +1 -1
- package/dist/components/popover/index.js +1 -0
- package/dist/components/popover/popover-handle.d.ts +6 -0
- package/dist/components/popover/popover-handle.d.ts.map +1 -0
- package/dist/components/popover/popover-handle.js +6 -0
- package/dist/components/popover/popover.d.ts +41 -7
- package/dist/components/popover/popover.d.ts.map +1 -1
- package/dist/components/popover/popover.js +50 -3
- package/dist/components/progress/progress.js +1 -1
- package/dist/components/progress/progress.stories.d.ts +11 -2
- package/dist/components/progress/progress.stories.d.ts.map +1 -1
- package/dist/components/progress/progress.stories.js +77 -4
- package/dist/components/sidebar/index.d.ts +2 -0
- package/dist/components/sidebar/index.d.ts.map +1 -0
- package/dist/components/sidebar/index.js +1 -0
- package/dist/components/sidebar/sidebar.d.ts +64 -0
- package/dist/components/sidebar/sidebar.d.ts.map +1 -0
- package/dist/components/sidebar/sidebar.js +255 -0
- package/dist/components/sidebar/sidebar.stories.d.ts +20 -0
- package/dist/components/sidebar/sidebar.stories.d.ts.map +1 -0
- package/dist/components/sidebar/sidebar.stories.js +181 -0
- package/dist/components/skeleton/index.d.ts +3 -0
- package/dist/components/skeleton/index.d.ts.map +1 -0
- package/dist/components/skeleton/index.js +1 -0
- package/dist/components/skeleton/skeleton.d.ts +7 -0
- package/dist/components/skeleton/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton/skeleton.js +8 -0
- package/dist/components/sortable/sortable.d.ts +4 -2
- package/dist/components/sortable/sortable.d.ts.map +1 -1
- package/dist/components/sortable/sortable.js +4 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/llms.txt +1 -1
- package/package.json +7 -4
- package/src/styles/globals.css +26 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Menu } from 'lucide-react';
|
|
5
|
+
import { buttonVariants } from '../../components/button';
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../../components/popover';
|
|
7
|
+
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '../../components/sheet';
|
|
8
|
+
import { useIsMobile } from '../../hooks/use-mobile';
|
|
9
|
+
import { cn } from '../../lib/utils';
|
|
10
|
+
const DEFAULT_INSET = 24;
|
|
11
|
+
const LONG_PRESS_MS = 160;
|
|
12
|
+
const LONG_PRESS_MOVE_CANCEL_PX = 22;
|
|
13
|
+
function clamp(n, min, max) {
|
|
14
|
+
return Math.min(max, Math.max(min, n));
|
|
15
|
+
}
|
|
16
|
+
function readStoredInset(key) {
|
|
17
|
+
if (typeof window === 'undefined')
|
|
18
|
+
return null;
|
|
19
|
+
try {
|
|
20
|
+
const raw = window.localStorage.getItem(key);
|
|
21
|
+
if (!raw)
|
|
22
|
+
return null;
|
|
23
|
+
const parsed = JSON.parse(raw);
|
|
24
|
+
if (!parsed || typeof parsed !== 'object')
|
|
25
|
+
return null;
|
|
26
|
+
const { right, bottom } = parsed;
|
|
27
|
+
if (typeof right !== 'number' || typeof bottom !== 'number')
|
|
28
|
+
return null;
|
|
29
|
+
return { right, bottom };
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function FloatingMenuWidget({ children, menuTitle, menuDescription, triggerIcon = _jsx(Menu, { className: "size-4" }), renderTrigger, triggerNativeButton = true, defaultInset, positionStorageKey, draggable = true, open: openControlled, onOpenChange: onOpenChangeControlled, className, ...rest }) {
|
|
36
|
+
const isMobile = useIsMobile();
|
|
37
|
+
const anchorRef = React.useRef(null);
|
|
38
|
+
const planRef = React.useRef(null);
|
|
39
|
+
const longPressTimerRef = React.useRef(null);
|
|
40
|
+
const storageKeyFull = positionStorageKey ? `wheelhouse:floating-menu-widget:${positionStorageKey}` : null;
|
|
41
|
+
const [inset, setInset] = React.useState(() => {
|
|
42
|
+
const stored = storageKeyFull ? readStoredInset(storageKeyFull) : null;
|
|
43
|
+
if (stored)
|
|
44
|
+
return stored;
|
|
45
|
+
return {
|
|
46
|
+
right: defaultInset?.right ?? DEFAULT_INSET,
|
|
47
|
+
bottom: defaultInset?.bottom ?? DEFAULT_INSET,
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
const insetRef = React.useRef(inset);
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
insetRef.current = inset;
|
|
53
|
+
}, [inset]);
|
|
54
|
+
const [isDragging, setIsDragging] = React.useState(false);
|
|
55
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
|
|
56
|
+
const isControlled = openControlled !== undefined;
|
|
57
|
+
const open = isControlled ? openControlled : uncontrolledOpen;
|
|
58
|
+
const prevIsMobileRef = React.useRef(undefined);
|
|
59
|
+
const setOpen = React.useCallback((next) => {
|
|
60
|
+
if (!isControlled)
|
|
61
|
+
setUncontrolledOpen(next);
|
|
62
|
+
onOpenChangeControlled?.(next);
|
|
63
|
+
}, [isControlled, onOpenChangeControlled]);
|
|
64
|
+
React.useEffect(() => {
|
|
65
|
+
if (prevIsMobileRef.current !== undefined && prevIsMobileRef.current !== isMobile)
|
|
66
|
+
setOpen(false);
|
|
67
|
+
prevIsMobileRef.current = isMobile;
|
|
68
|
+
}, [isMobile, setOpen]);
|
|
69
|
+
React.useEffect(() => {
|
|
70
|
+
return () => {
|
|
71
|
+
if (longPressTimerRef.current != null) {
|
|
72
|
+
window.clearTimeout(longPressTimerRef.current);
|
|
73
|
+
longPressTimerRef.current = null;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}, []);
|
|
77
|
+
React.useEffect(() => {
|
|
78
|
+
if (!isDragging || typeof document === 'undefined')
|
|
79
|
+
return;
|
|
80
|
+
const prev = document.body.style.cursor;
|
|
81
|
+
document.body.style.cursor = 'grabbing';
|
|
82
|
+
return () => {
|
|
83
|
+
document.body.style.cursor = prev;
|
|
84
|
+
};
|
|
85
|
+
}, [isDragging]);
|
|
86
|
+
const clearLongPressTimer = React.useCallback(() => {
|
|
87
|
+
if (longPressTimerRef.current != null) {
|
|
88
|
+
window.clearTimeout(longPressTimerRef.current);
|
|
89
|
+
longPressTimerRef.current = null;
|
|
90
|
+
}
|
|
91
|
+
}, []);
|
|
92
|
+
const startDrag = React.useCallback((pointerId, startX, startY) => {
|
|
93
|
+
const shell = anchorRef.current;
|
|
94
|
+
if (!shell)
|
|
95
|
+
return;
|
|
96
|
+
try {
|
|
97
|
+
shell.setPointerCapture(pointerId);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const { right, bottom } = insetRef.current;
|
|
103
|
+
planRef.current = { kind: 'drag', pointerId, startX, startY, originRight: right, originBottom: bottom };
|
|
104
|
+
setIsDragging(true);
|
|
105
|
+
}, []);
|
|
106
|
+
const onShellPointerDown = React.useCallback((event) => {
|
|
107
|
+
if (!draggable || event.button !== 0)
|
|
108
|
+
return;
|
|
109
|
+
if (!anchorRef.current?.contains(event.target))
|
|
110
|
+
return;
|
|
111
|
+
const pointerId = event.pointerId;
|
|
112
|
+
planRef.current = { kind: 'hold', pointerId, startX: event.clientX, startY: event.clientY };
|
|
113
|
+
clearLongPressTimer();
|
|
114
|
+
longPressTimerRef.current = window.setTimeout(() => {
|
|
115
|
+
longPressTimerRef.current = null;
|
|
116
|
+
const p = planRef.current;
|
|
117
|
+
if (!p || p.kind !== 'hold' || p.pointerId !== pointerId)
|
|
118
|
+
return;
|
|
119
|
+
startDrag(p.pointerId, p.startX, p.startY);
|
|
120
|
+
}, LONG_PRESS_MS);
|
|
121
|
+
}, [draggable, startDrag, clearLongPressTimer]);
|
|
122
|
+
const onShellPointerMove = React.useCallback((event) => {
|
|
123
|
+
const p = planRef.current;
|
|
124
|
+
if (!p || event.pointerId !== p.pointerId)
|
|
125
|
+
return;
|
|
126
|
+
if (p.kind === 'drag') {
|
|
127
|
+
const dx = event.clientX - p.startX;
|
|
128
|
+
const dy = event.clientY - p.startY;
|
|
129
|
+
const margin = 8;
|
|
130
|
+
const w = anchorRef.current?.offsetWidth ?? 48;
|
|
131
|
+
const h = anchorRef.current?.offsetHeight ?? 48;
|
|
132
|
+
const maxRight = Math.max(margin, window.innerWidth - w - margin);
|
|
133
|
+
const maxBottom = Math.max(margin, window.innerHeight - h - margin);
|
|
134
|
+
setInset({
|
|
135
|
+
right: clamp(p.originRight - dx, margin, maxRight),
|
|
136
|
+
bottom: clamp(p.originBottom - dy, margin, maxBottom),
|
|
137
|
+
});
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const dx = event.clientX - p.startX;
|
|
141
|
+
const dy = event.clientY - p.startY;
|
|
142
|
+
if (Math.hypot(dx, dy) > LONG_PRESS_MOVE_CANCEL_PX) {
|
|
143
|
+
clearLongPressTimer();
|
|
144
|
+
planRef.current = null;
|
|
145
|
+
}
|
|
146
|
+
}, [clearLongPressTimer]);
|
|
147
|
+
const onShellPointerUpOrCancel = React.useCallback((event) => {
|
|
148
|
+
clearLongPressTimer();
|
|
149
|
+
const p = planRef.current;
|
|
150
|
+
if (!p)
|
|
151
|
+
return;
|
|
152
|
+
if (p.kind === 'drag') {
|
|
153
|
+
if (event.pointerId !== p.pointerId)
|
|
154
|
+
return;
|
|
155
|
+
planRef.current = null;
|
|
156
|
+
setIsDragging(false);
|
|
157
|
+
try {
|
|
158
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
/* released */
|
|
162
|
+
}
|
|
163
|
+
setInset((cur) => {
|
|
164
|
+
if (storageKeyFull && typeof window !== 'undefined') {
|
|
165
|
+
try {
|
|
166
|
+
window.localStorage.setItem(storageKeyFull, JSON.stringify(cur));
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
/* quota / private mode */
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return cur;
|
|
173
|
+
});
|
|
174
|
+
const shell = anchorRef.current;
|
|
175
|
+
if (shell) {
|
|
176
|
+
shell.style.pointerEvents = 'none';
|
|
177
|
+
requestAnimationFrame(() => {
|
|
178
|
+
requestAnimationFrame(() => {
|
|
179
|
+
shell.style.pointerEvents = '';
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
planRef.current = null;
|
|
186
|
+
}, [clearLongPressTimer, storageKeyFull]);
|
|
187
|
+
const shellClass = cn('fixed z-50 flex touch-none items-center justify-center overflow-hidden rounded-full bg-background shadow-lg ring-1 ring-foreground/5', isDragging && 'cursor-grabbing', className);
|
|
188
|
+
const shellPointer = draggable
|
|
189
|
+
? {
|
|
190
|
+
onPointerDown: onShellPointerDown,
|
|
191
|
+
onPointerMove: onShellPointerMove,
|
|
192
|
+
onPointerUp: onShellPointerUpOrCancel,
|
|
193
|
+
onPointerCancel: onShellPointerUpOrCancel,
|
|
194
|
+
}
|
|
195
|
+
: {};
|
|
196
|
+
const triggerClass = cn(buttonVariants({ variant: 'default', size: 'icon' }), 'size-8 rounded-full border-0 shadow-none [&_svg]:opacity-90');
|
|
197
|
+
const body = _jsx("div", { className: "flex flex-col gap-1", children: children });
|
|
198
|
+
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx("div", { ref: anchorRef, className: shellClass, style: { right: inset.right, bottom: inset.bottom }, title: "Click to open. Press and hold to move.", ...rest, ...shellPointer, children: renderTrigger ? (_jsx(PopoverTrigger, { nativeButton: triggerNativeButton, render: renderTrigger })) : (_jsxs(PopoverTrigger, { className: triggerClass, children: [triggerIcon, _jsxs("span", { className: "sr-only", children: ["Open ", menuTitle] })] })) }), !isMobile ? (_jsx(PopoverContent, { anchor: anchorRef, align: "end", className: "max-h-[inherit] w-[min(100vw-2rem,12rem)] overflow-y-auto", positionerClassName: "max-h-[min(100dvh-2rem,24rem)]", side: "top", sideOffset: 10, children: body })) : null, isMobile ? (_jsx(Sheet, { open: open, onOpenChange: setOpen, children: _jsxs(SheetContent, { side: "bottom", className: "max-h-[85dvh] overflow-y-auto p-0", children: [_jsxs(SheetHeader, { className: "border-b border-border p-4 text-left", children: [_jsx(SheetTitle, { children: menuTitle }), menuDescription ? _jsx(SheetDescription, { children: menuDescription }) : null] }), _jsx("div", { className: "p-4 pt-2", children: body })] }) })) : null] }));
|
|
199
|
+
}
|
|
200
|
+
export { FloatingMenuWidget };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StoryObj } from '@storybook/react';
|
|
2
|
+
import { FloatingMenuWidget } from './floating-menu-widget';
|
|
3
|
+
declare const meta: {
|
|
4
|
+
title: string;
|
|
5
|
+
component: typeof FloatingMenuWidget;
|
|
6
|
+
tags: string[];
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof FloatingMenuWidget>;
|
|
13
|
+
export declare const Default: Story;
|
|
14
|
+
export declare const NotDraggable: Story;
|
|
15
|
+
//# sourceMappingURL=floating-menu-widget.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"floating-menu-widget.stories.d.ts","sourceRoot":"","sources":["../../../src/blocks/floating-menu-widget/floating-menu-widget.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAKvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,QAAA,MAAM,IAAI;;;;;;;CAOiC,CAAC;AAE5C,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAqBjD,eAAO,MAAM,OAAO,EAAE,KAYrB,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAQ1B,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Circle } from 'lucide-react';
|
|
3
|
+
import { Button } from '../../components/button';
|
|
4
|
+
import { FloatingMenuWidget } from './floating-menu-widget';
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Blocks/Floating menu widget',
|
|
7
|
+
component: FloatingMenuWidget,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: 'fullscreen',
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export default meta;
|
|
14
|
+
function DemoLinks() {
|
|
15
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "ghost", className: "justify-start font-normal", type: "button", children: [_jsx(Circle, { className: "size-3 opacity-70" }), "Action 01"] }), _jsxs(Button, { variant: "ghost", className: "justify-start font-normal", type: "button", children: [_jsx(Circle, { className: "size-3 opacity-70" }), "Action 02"] }), _jsxs(Button, { variant: "ghost", className: "justify-start font-normal", type: "button", children: [_jsx(Circle, { className: "size-3 opacity-70" }), "Action 03"] })] }));
|
|
16
|
+
}
|
|
17
|
+
export const Default = {
|
|
18
|
+
render: () => (_jsxs("div", { className: "relative min-h-[100dvh] bg-muted/30 p-8", children: [_jsx("p", { className: "max-w-md text-sm text-muted-foreground", children: "Click the circular control to open a popover on wide viewports (bottom sheet on narrow ones). Press and hold the control to drag it. Resize the preview to compare." }), _jsx(FloatingMenuWidget, { menuTitle: "Quick actions", menuDescription: "Shortcuts for this preview", positionStorageKey: "storybook-demo", children: _jsx(DemoLinks, {}) })] })),
|
|
19
|
+
};
|
|
20
|
+
export const NotDraggable = {
|
|
21
|
+
render: () => (_jsx("div", { className: "relative min-h-[100dvh] bg-muted/30 p-8", children: _jsx(FloatingMenuWidget, { menuTitle: "Menu", draggable: false, defaultInset: { right: 32, bottom: 32 }, children: _jsx(DemoLinks, {}) }) })),
|
|
22
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/floating-menu-widget/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,YAAY,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FloatingMenuWidget } from './floating-menu-widget';
|
package/dist/blocks/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/blocks/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/blocks/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC"}
|
package/dist/blocks/index.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { Button as ButtonPrimitive } from '@base-ui/react/button';
|
|
2
2
|
import { type VariantProps } from 'class-variance-authority';
|
|
3
3
|
declare const buttonVariantStyles: {
|
|
4
|
-
readonly default: "border-primary bg-primary text-primary-foreground shadow-xs shadow-primary/24 not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] hover:bg-primary/90 data-pressed:bg-primary/90 [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none";
|
|
5
|
-
readonly outline: "border-input bg-popover text-foreground shadow-xs/5 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] hover:bg-accent/50 data-pressed:bg-accent/50 dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] dark:hover:bg-input/64 dark:data-pressed:bg-input/64 [:disabled,:active,[data-pressed]]:shadow-none";
|
|
6
|
-
readonly secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/90 data-pressed:bg-secondary/90 [:active,[data-pressed]]:bg-secondary/80";
|
|
7
|
-
readonly ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50";
|
|
8
|
-
readonly destructive: "border-destructive-foreground/10 bg-destructive/50 text-destructive-foreground shadow-xs shadow-destructive-foreground/10 hover:bg-destructive/80 data-pressed:bg-destructive/70 [:disabled,:active,[data-pressed]]:shadow-none";
|
|
9
|
-
readonly 'destructive-outline': "border-destructive-foreground/10 border-destructive-foreground/15 bg-popover text-destructive-foreground shadow-xs shadow-destructive-foreground/3 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] hover:bg-destructive/50 data-pressed:border-destructive-foreground/32 data-pressed:bg-destructive-foreground/8
|
|
10
|
-
readonly link: "text-primary underline-offset-4 hover:underline";
|
|
4
|
+
readonly default: "border-primary bg-primary text-primary-foreground shadow-xs shadow-primary/24 not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] hover:bg-primary/90 data-pressed:bg-primary/90 [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none [&_[data-slot=button-loading-indicator]]:text-primary-foreground";
|
|
5
|
+
readonly outline: "border-input bg-popover text-foreground shadow-xs/5 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] hover:bg-accent/50 data-pressed:bg-accent/50 dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] dark:hover:bg-input/64 dark:data-pressed:bg-input/64 [:disabled,:active,[data-pressed]]:shadow-none [&_[data-slot=button-loading-indicator]]:text-foreground";
|
|
6
|
+
readonly secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/90 data-pressed:bg-secondary/90 [:active,[data-pressed]]:bg-secondary/80 [&_[data-slot=button-loading-indicator]]:text-secondary-foreground";
|
|
7
|
+
readonly ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50 [&_[data-slot=button-loading-indicator]]:text-foreground";
|
|
8
|
+
readonly destructive: "border-destructive-foreground/10 bg-destructive/50 text-destructive-foreground shadow-xs shadow-destructive-foreground/10 hover:bg-destructive/80 data-pressed:bg-destructive/70 [:disabled,:active,[data-pressed]]:shadow-none [&_[data-slot=button-loading-indicator]]:text-destructive-foreground";
|
|
9
|
+
readonly 'destructive-outline': "border-destructive-foreground/10 border-destructive-foreground/15 bg-popover text-destructive-foreground shadow-xs shadow-destructive-foreground/3 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] hover:bg-destructive/50 data-pressed:border-destructive-foreground/32 data-pressed:bg-destructive-foreground/8 dark:border-destructive-foreground/30 dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [&_[data-slot=button-loading-indicator]]:text-destructive-foreground";
|
|
10
|
+
readonly link: "text-primary underline-offset-4 hover:underline [&_[data-slot=button-loading-indicator]]:text-primary";
|
|
11
11
|
};
|
|
12
12
|
declare const buttonSizeStyles: {
|
|
13
|
-
readonly default: "h-8 gap-2 px-[calc(--spacing(3)-1px)] has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2";
|
|
13
|
+
readonly default: "h-8 gap-2 px-[calc(--spacing(3)-1px)] has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3 sm:[&_svg:not([class*='size-'])]:size-3";
|
|
14
14
|
readonly xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3";
|
|
15
|
-
readonly sm: "h-7 gap-1.5 rounded-[min(var(--radius-md),12px)] px-[calc(--spacing(2.5)-1px)] text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-
|
|
16
|
-
readonly lg: "h-9 gap-1.5 px-[calc(--spacing(3.5)-1px)] has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3 sm:text-base";
|
|
15
|
+
readonly sm: "h-7 gap-1.5 rounded-[min(var(--radius-md),12px)] px-[calc(--spacing(2.5)-1px)] text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3.5 sm:[&_svg:not([class*='size-'])]:size-3.5";
|
|
16
|
+
readonly lg: "h-9 gap-1.5 px-[calc(--spacing(3.5)-1px)] has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3 sm:text-base [&_svg:not([class*='size-'])]:size-3.5 sm:[&_svg:not([class*='size-'])]:size-3.5";
|
|
17
17
|
readonly icon: "size-8";
|
|
18
18
|
readonly 'icon-xs': "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3";
|
|
19
19
|
readonly 'icon-sm': "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg";
|
|
@@ -28,10 +28,17 @@ declare const buttonVariants: (props?: ({
|
|
|
28
28
|
/**
|
|
29
29
|
* Props for `Button`. Inherits additional behavior from
|
|
30
30
|
* [Base UI Button](https://base-ui.com/react/components/button).
|
|
31
|
+
*
|
|
32
|
+
* When `loading` is true: shows a centered {@link Spinner}, sets `data-loading` on the root,
|
|
33
|
+
* uses `text-transparent` so label and icons stay mounted but invisible (inherited color), sets
|
|
34
|
+
* explicit `color` on `[data-slot="button-loading-indicator"]` per variant, forces `disabled`,
|
|
35
|
+
* and sets `aria-busy`.
|
|
31
36
|
*/
|
|
32
37
|
export type ButtonProps = ButtonPrimitive.Props & VariantProps<typeof buttonVariants> & {
|
|
33
38
|
className?: string;
|
|
39
|
+
/** Shows a spinner and non-interactive disabled behavior while true. */
|
|
40
|
+
loading?: boolean;
|
|
34
41
|
};
|
|
35
|
-
declare function Button({ className, variant, size, ...props }: ButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
declare function Button({ className, variant, size, loading, disabled, children, ...props }: ButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
36
43
|
export { Button, buttonVariants };
|
|
37
44
|
//# sourceMappingURL=button.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/button/button.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/button/button.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAKlE,QAAA,MAAM,mBAAmB;;;;;;;;CAaf,CAAC;AAEX,QAAA,MAAM,gBAAgB;;;;;;;;;CAUZ,CAAC;AAEX,eAAO,MAAM,iBAAiB,EAAuC,CAAC,MAAM,OAAO,mBAAmB,CAAC,EAAE,CAAC;AAE1G,eAAO,MAAM,cAAc,EAAoC,CAAC,MAAM,OAAO,gBAAgB,CAAC,EAAE,CAAC;AAEjG,QAAA,MAAM,cAAc;;;8EAYnB,CAAC;AAaF;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC,KAAK,GAC3C,YAAY,CAAC,OAAO,cAAc,CAAC,GAAG;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEN,iBAAS,MAAM,CAAC,EAAE,SAAS,EAAE,OAAmB,EAAE,IAAgB,EAAE,OAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,WAAW,2CAqB/H;AAED,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Button as ButtonPrimitive } from '@base-ui/react/button';
|
|
3
3
|
import { cva } from 'class-variance-authority';
|
|
4
4
|
import { cn } from '../../lib/utils';
|
|
5
|
+
import { Spinner } from '../spinner';
|
|
5
6
|
const buttonVariantStyles = {
|
|
6
|
-
default: 'border-primary bg-primary text-primary-foreground shadow-xs shadow-primary/24 not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] hover:bg-primary/90 data-pressed:bg-primary/90 [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none',
|
|
7
|
-
outline: 'border-input bg-popover text-foreground shadow-xs/5 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] hover:bg-accent/50 data-pressed:bg-accent/50 dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] dark:hover:bg-input/64 dark:data-pressed:bg-input/64 [:disabled,:active,[data-pressed]]:shadow-none',
|
|
8
|
-
secondary: 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/90 data-pressed:bg-secondary/90 [:active,[data-pressed]]:bg-secondary/80',
|
|
9
|
-
ghost: 'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',
|
|
10
|
-
destructive: 'border-destructive-foreground/10 bg-destructive/50 text-destructive-foreground shadow-xs shadow-destructive-foreground/10 hover:bg-destructive/80 data-pressed:bg-destructive/70 [:disabled,:active,[data-pressed]]:shadow-none',
|
|
11
|
-
'destructive-outline': 'border-destructive-foreground/10 border-destructive-foreground/15 bg-popover text-destructive-foreground shadow-xs shadow-destructive-foreground/3 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] hover:bg-destructive/50 data-pressed:border-destructive-foreground/32 data-pressed:bg-destructive-foreground/8
|
|
12
|
-
link: 'text-primary underline-offset-4 hover:underline',
|
|
7
|
+
default: 'border-primary bg-primary text-primary-foreground shadow-xs shadow-primary/24 not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] hover:bg-primary/90 data-pressed:bg-primary/90 [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none [&_[data-slot=button-loading-indicator]]:text-primary-foreground',
|
|
8
|
+
outline: 'border-input bg-popover text-foreground shadow-xs/5 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] hover:bg-accent/50 data-pressed:bg-accent/50 dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] dark:hover:bg-input/64 dark:data-pressed:bg-input/64 [:disabled,:active,[data-pressed]]:shadow-none [&_[data-slot=button-loading-indicator]]:text-foreground',
|
|
9
|
+
secondary: 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/90 data-pressed:bg-secondary/90 [:active,[data-pressed]]:bg-secondary/80 [&_[data-slot=button-loading-indicator]]:text-secondary-foreground',
|
|
10
|
+
ghost: 'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50 [&_[data-slot=button-loading-indicator]]:text-foreground',
|
|
11
|
+
destructive: 'border-destructive-foreground/10 bg-destructive/50 text-destructive-foreground shadow-xs shadow-destructive-foreground/10 hover:bg-destructive/80 data-pressed:bg-destructive/70 [:disabled,:active,[data-pressed]]:shadow-none [&_[data-slot=button-loading-indicator]]:text-destructive-foreground',
|
|
12
|
+
'destructive-outline': 'border-destructive-foreground/10 border-destructive-foreground/15 bg-popover text-destructive-foreground shadow-xs shadow-destructive-foreground/3 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] hover:bg-destructive/50 data-pressed:border-destructive-foreground/32 data-pressed:bg-destructive-foreground/8 dark:border-destructive-foreground/30 dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [&_[data-slot=button-loading-indicator]]:text-destructive-foreground',
|
|
13
|
+
link: 'text-primary underline-offset-4 hover:underline [&_[data-slot=button-loading-indicator]]:text-primary',
|
|
13
14
|
};
|
|
14
15
|
const buttonSizeStyles = {
|
|
15
|
-
default:
|
|
16
|
+
default: "h-8 gap-2 px-[calc(--spacing(3)-1px)] has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3 sm:[&_svg:not([class*='size-'])]:size-3",
|
|
16
17
|
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
17
|
-
sm: "h-7 gap-1.5 rounded-[min(var(--radius-md),12px)] px-[calc(--spacing(2.5)-1px)] text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-
|
|
18
|
-
lg:
|
|
18
|
+
sm: "h-7 gap-1.5 rounded-[min(var(--radius-md),12px)] px-[calc(--spacing(2.5)-1px)] text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3.5 sm:[&_svg:not([class*='size-'])]:size-3.5",
|
|
19
|
+
lg: "h-9 gap-1.5 px-[calc(--spacing(3.5)-1px)] has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3 sm:text-base [&_svg:not([class*='size-'])]:size-3.5 sm:[&_svg:not([class*='size-'])]:size-3.5",
|
|
19
20
|
icon: 'size-8',
|
|
20
21
|
'icon-xs': "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
21
22
|
'icon-sm': 'size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg',
|
|
@@ -23,7 +24,7 @@ const buttonSizeStyles = {
|
|
|
23
24
|
};
|
|
24
25
|
export const buttonVariantKeys = Object.keys(buttonVariantStyles);
|
|
25
26
|
export const buttonSizeKeys = Object.keys(buttonSizeStyles);
|
|
26
|
-
const buttonVariants = cva("group/button relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md border border-transparent text-base font-medium whitespace-nowrap transition-shadow outline-none [text-box-edge:cap_alphabetic] [text-box-trim:trim-both] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 disabled:shadow-none disabled:inset-shadow-none disabled:before:shadow-none in-data-[slot=button-group]:before:rounded-none sm:text-sm pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:shadow-none data-disabled:inset-shadow-none data-disabled:before:shadow-none [&_svg]:pointer-events-none [&_svg]:-mx-0.5 [&_svg]:shrink-0 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4", {
|
|
27
|
+
const buttonVariants = cva("group/button relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md border border-transparent text-base font-medium whitespace-nowrap transition-shadow outline-none [text-box-edge:cap_alphabetic] [text-box-trim:trim-both] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 disabled:shadow-none disabled:inset-shadow-none disabled:before:shadow-none in-data-[slot=button-group]:before:rounded-none data-loading:text-transparent data-loading:select-none sm:text-sm pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:shadow-none data-disabled:inset-shadow-none data-disabled:before:shadow-none [&_svg]:pointer-events-none [&_svg]:-mx-0.5 [&_svg]:shrink-0 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4", {
|
|
27
28
|
variants: {
|
|
28
29
|
variant: buttonVariantStyles,
|
|
29
30
|
size: buttonSizeStyles,
|
|
@@ -33,7 +34,19 @@ const buttonVariants = cva("group/button relative inline-flex shrink-0 cursor-po
|
|
|
33
34
|
size: 'default',
|
|
34
35
|
},
|
|
35
36
|
});
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
const spinnerSizeByButtonSize = {
|
|
38
|
+
default: 'size-3 sm:size-3',
|
|
39
|
+
xs: 'size-3',
|
|
40
|
+
sm: 'size-3.5 sm:size-3.5',
|
|
41
|
+
lg: 'size-3.5 sm:size-3.5',
|
|
42
|
+
icon: 'size-4',
|
|
43
|
+
'icon-xs': 'size-3',
|
|
44
|
+
'icon-sm': 'size-3.5',
|
|
45
|
+
'icon-lg': 'size-4',
|
|
46
|
+
};
|
|
47
|
+
function Button({ className, variant = 'default', size = 'default', loading = false, disabled, children, ...props }) {
|
|
48
|
+
const isDisabled = Boolean(disabled || loading);
|
|
49
|
+
const spinnerClass = spinnerSizeByButtonSize[size ?? 'default'];
|
|
50
|
+
return (_jsxs(ButtonPrimitive, { "data-slot": "button", "data-loading": loading || undefined, className: cn(buttonVariants({ variant, size, className })), disabled: isDisabled, "aria-busy": loading || undefined, ...props, children: [children, loading ? (_jsx("span", { className: "pointer-events-none absolute inset-0 flex items-center justify-center", "aria-hidden": true, children: _jsx(Spinner, { "data-slot": "button-loading-indicator", "aria-hidden": true, className: cn('shrink-0', spinnerClass) }) })) : null] }));
|
|
38
51
|
}
|
|
39
52
|
export { Button, buttonVariants };
|
|
@@ -13,4 +13,15 @@ export declare const DestructiveOutline: Story;
|
|
|
13
13
|
export declare const Link: Story;
|
|
14
14
|
export declare const Small: Story;
|
|
15
15
|
export declare const Large: Story;
|
|
16
|
+
export declare const Icon: Story;
|
|
17
|
+
export declare const WithIcon: Story;
|
|
18
|
+
export declare const Rounded: Story;
|
|
19
|
+
export declare const Loading: Story;
|
|
20
|
+
export declare const LoadingInteractive: Story;
|
|
21
|
+
export declare const LoadingWithIcon: Story;
|
|
22
|
+
export declare const LoadingIconOnly: Story;
|
|
23
|
+
/** Manual composition when you need a custom indicator or copy outside the built-in `loading` prop. */
|
|
24
|
+
export declare const Spinner: Story;
|
|
25
|
+
export declare const ButtonGroup: Story;
|
|
26
|
+
export declare const AsLink: Story;
|
|
16
27
|
//# sourceMappingURL=button.stories.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.stories.d.ts","sourceRoot":"","sources":["../../../src/components/button/button.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"button.stories.d.ts","sourceRoot":"","sources":["../../../src/components/button/button.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAMvD,OAAO,EAAE,MAAM,EAAqC,MAAM,UAAU,CAAC;AAErE,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,MAAM,CA4B7B,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAgDnC,eAAO,MAAM,OAAO,EAAE,KAAU,CAAC;AAEjC,eAAO,MAAM,OAAO,EAAE,KAiBrB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,KAGrB,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,KAGvB,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAGnB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAGzB,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,KAGhC,CAAC;AAEF,eAAO,MAAM,IAAI,EAAE,KAGlB,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAGnB,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAGnB,CAAC;AAEF,eAAO,MAAM,IAAI,EAAE,KAuBlB,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,KAgBtB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,KAiBrB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,KAUrB,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,KAsBhC,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAiB7B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAqB7B,CAAC;AAEF,uGAAuG;AACvG,eAAO,MAAM,OAAO,EAAE,KAoBrB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAiBzB,CAAC;AAEF,eAAO,MAAM,MAAM,EAAE,KAepB,CAAC"}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { ChevronRight, Download, Plus } from 'lucide-react';
|
|
4
|
+
import { ButtonGroup as ButtonGroupRoot } from '../button-group';
|
|
5
|
+
import { Spinner as LoadingSpinner } from '../spinner';
|
|
2
6
|
import { Button, buttonSizeKeys, buttonVariantKeys } from './button';
|
|
3
7
|
const meta = {
|
|
4
8
|
title: 'Components/Button',
|
|
@@ -26,6 +30,7 @@ const meta = {
|
|
|
26
30
|
control: 'select',
|
|
27
31
|
options: buttonSizeKeys,
|
|
28
32
|
},
|
|
33
|
+
loading: { control: 'boolean' },
|
|
29
34
|
},
|
|
30
35
|
};
|
|
31
36
|
export default meta;
|
|
@@ -78,3 +83,82 @@ export const Large = {
|
|
|
78
83
|
tags: ['!autodocs'],
|
|
79
84
|
args: { size: 'lg' },
|
|
80
85
|
};
|
|
86
|
+
export const Icon = {
|
|
87
|
+
parameters: {
|
|
88
|
+
layout: 'centered',
|
|
89
|
+
},
|
|
90
|
+
render: () => (_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx(Button, { variant: "outline", size: "icon-xs", "aria-label": "Add", children: _jsx(Plus, {}) }), _jsx(Button, { variant: "outline", size: "icon-sm", "aria-label": "Add", children: _jsx(Plus, {}) }), _jsx(Button, { variant: "outline", size: "icon", "aria-label": "Add", children: _jsx(Plus, {}) }), _jsx(Button, { variant: "outline", size: "icon-lg", "aria-label": "Add", children: _jsx(Plus, {}) }), _jsx(Button, { variant: "default", size: "icon", "aria-label": "Add", children: _jsx(Plus, {}) })] })),
|
|
91
|
+
};
|
|
92
|
+
export const WithIcon = {
|
|
93
|
+
parameters: {
|
|
94
|
+
layout: 'centered',
|
|
95
|
+
},
|
|
96
|
+
render: () => (_jsxs("div", { className: "flex flex-col items-start gap-3", children: [_jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(Download, { "data-icon": "inline-start" }), "Download"] }), _jsxs(Button, { variant: "default", size: "sm", children: ["Continue", _jsx(ChevronRight, { "data-icon": "inline-end" })] })] })),
|
|
97
|
+
};
|
|
98
|
+
export const Rounded = {
|
|
99
|
+
parameters: {
|
|
100
|
+
layout: 'centered',
|
|
101
|
+
},
|
|
102
|
+
render: () => (_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx(Button, { className: "rounded-full", size: "sm", children: "Pill default" }), _jsx(Button, { variant: "outline", className: "rounded-full", size: "sm", children: "Pill outline" }), _jsx(Button, { variant: "secondary", className: "rounded-full", size: "sm", children: "Pill secondary" })] })),
|
|
103
|
+
};
|
|
104
|
+
export const Loading = {
|
|
105
|
+
parameters: {
|
|
106
|
+
layout: 'centered',
|
|
107
|
+
},
|
|
108
|
+
args: {
|
|
109
|
+
loading: true,
|
|
110
|
+
children: 'Submit',
|
|
111
|
+
variant: 'default',
|
|
112
|
+
size: 'sm',
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
export const LoadingInteractive = {
|
|
116
|
+
parameters: {
|
|
117
|
+
layout: 'centered',
|
|
118
|
+
controls: { disable: true },
|
|
119
|
+
},
|
|
120
|
+
render: function LoadingInteractiveRender() {
|
|
121
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
122
|
+
return (_jsx(Button, { loading: isLoading, onClick: () => {
|
|
123
|
+
setIsLoading(true);
|
|
124
|
+
window.setTimeout(() => {
|
|
125
|
+
setIsLoading(false);
|
|
126
|
+
}, 1200);
|
|
127
|
+
}, children: "Submit" }));
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
export const LoadingWithIcon = {
|
|
131
|
+
tags: ['!autodocs'],
|
|
132
|
+
parameters: {
|
|
133
|
+
layout: 'centered',
|
|
134
|
+
},
|
|
135
|
+
render: () => (_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsxs(Button, { variant: "outline", size: "sm", loading: true, children: [_jsx(Download, { "data-icon": "inline-start" }), "Download"] }), _jsxs(Button, { variant: "default", size: "sm", loading: true, children: ["Continue", _jsx(ChevronRight, { "data-icon": "inline-end" })] })] })),
|
|
136
|
+
};
|
|
137
|
+
export const LoadingIconOnly = {
|
|
138
|
+
tags: ['!autodocs'],
|
|
139
|
+
parameters: {
|
|
140
|
+
layout: 'centered',
|
|
141
|
+
},
|
|
142
|
+
render: () => (_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx(Button, { variant: "outline", size: "icon-xs", "aria-label": "Saving", loading: true, children: _jsx(Plus, { "aria-hidden": true }) }), _jsx(Button, { variant: "outline", size: "icon-sm", "aria-label": "Saving", loading: true, children: _jsx(Plus, { "aria-hidden": true }) }), _jsx(Button, { variant: "outline", size: "icon", "aria-label": "Saving", loading: true, children: _jsx(Plus, { "aria-hidden": true }) }), _jsx(Button, { variant: "outline", size: "icon-lg", "aria-label": "Saving", loading: true, children: _jsx(Plus, { "aria-hidden": true }) })] })),
|
|
143
|
+
};
|
|
144
|
+
/** Manual composition when you need a custom indicator or copy outside the built-in `loading` prop. */
|
|
145
|
+
export const Spinner = {
|
|
146
|
+
parameters: {
|
|
147
|
+
layout: 'centered',
|
|
148
|
+
},
|
|
149
|
+
render: () => (_jsxs("div", { className: "flex flex-col items-start gap-3", children: [_jsxs(Button, { variant: "outline", size: "sm", disabled: true, "aria-busy": "true", children: [_jsx("span", { "data-slot": "button-loading-indicator", className: "inline-flex", children: _jsx(LoadingSpinner, { className: "size-4", "aria-hidden": true }) }), "Saving\u2026"] }), _jsxs(Button, { variant: "destructive-outline", size: "sm", disabled: true, "aria-busy": "true", children: [_jsx("span", { "data-slot": "button-loading-indicator", className: "inline-flex", children: _jsx(LoadingSpinner, { className: "size-4", "aria-hidden": true }) }), "Removing\u2026"] })] })),
|
|
150
|
+
};
|
|
151
|
+
export const ButtonGroup = {
|
|
152
|
+
parameters: {
|
|
153
|
+
layout: 'centered',
|
|
154
|
+
},
|
|
155
|
+
render: () => (_jsxs(ButtonGroupRoot, { orientation: "horizontal", children: [_jsx(Button, { variant: "outline", size: "sm", children: "List" }), _jsx(Button, { variant: "outline", size: "sm", children: "Board" }), _jsx(Button, { variant: "outline", size: "sm", children: "Timeline" })] })),
|
|
156
|
+
};
|
|
157
|
+
export const AsLink = {
|
|
158
|
+
parameters: {
|
|
159
|
+
layout: 'centered',
|
|
160
|
+
},
|
|
161
|
+
render: () => (_jsx(Button, { variant: "link", size: "sm", nativeButton: false,
|
|
162
|
+
// eslint-disable-next-line jsx-a11y/anchor-has-content -- label provided as children
|
|
163
|
+
render: _jsx("a", { href: "https://example.com", rel: "noreferrer" }), children: "Visit example.com" })),
|
|
164
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Column } from '@tanstack/react-table';
|
|
2
|
+
interface DataGridColumnFilterProps<TData, TValue> {
|
|
3
|
+
column?: Column<TData, TValue>;
|
|
4
|
+
title?: string;
|
|
5
|
+
options: {
|
|
6
|
+
label: string;
|
|
7
|
+
value: string;
|
|
8
|
+
icon?: React.ComponentType<{
|
|
9
|
+
className?: string;
|
|
10
|
+
}>;
|
|
11
|
+
}[];
|
|
12
|
+
}
|
|
13
|
+
declare function DataGridColumnFilter<TData, TValue>({ column, title, options }: DataGridColumnFilterProps<TData, TValue>): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export { DataGridColumnFilter, type DataGridColumnFilterProps };
|
|
15
|
+
//# sourceMappingURL=data-grid-column-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-grid-column-filter.d.ts","sourceRoot":"","sources":["../../../src/components/data-grid/data-grid-column-filter.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AASpD,UAAU,yBAAyB,CAAC,KAAK,EAAE,MAAM;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QACL,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;YAAE,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACtD,EAAE,CAAC;CACP;AAED,iBAAS,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,yBAAyB,CAAC,KAAK,EAAE,MAAM,CAAC,2CA8GhH;AAED,OAAO,EAAE,oBAAoB,EAAE,KAAK,yBAAyB,EAAE,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo, useState } from 'react';
|
|
4
|
+
import { Badge } from '../badge';
|
|
5
|
+
import { Button } from '../button';
|
|
6
|
+
import { Input } from '../input';
|
|
7
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../popover';
|
|
8
|
+
import { Separator } from '../separator';
|
|
9
|
+
import { cn } from '../../lib/utils';
|
|
10
|
+
import { CheckIcon, CirclePlusIcon } from 'lucide-react';
|
|
11
|
+
function DataGridColumnFilter({ column, title, options }) {
|
|
12
|
+
const facets = column?.getFacetedUniqueValues();
|
|
13
|
+
const selectedValues = new Set(column?.getFilterValue());
|
|
14
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
15
|
+
const filteredOptions = useMemo(() => {
|
|
16
|
+
if (!searchQuery)
|
|
17
|
+
return options;
|
|
18
|
+
return options.filter((option) => option.label.toLowerCase().includes(searchQuery.toLowerCase()));
|
|
19
|
+
}, [options, searchQuery]);
|
|
20
|
+
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { render: _jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(CirclePlusIcon, { className: "size-4" }), title, selectedValues?.size > 0 && (_jsxs(_Fragment, { children: [_jsx(Separator, { orientation: "vertical", className: "mx-2 h-4" }), _jsx(Badge, { variant: "secondary", className: "rounded-sm px-1 font-normal lg:hidden", children: selectedValues.size }), _jsx("div", { className: "hidden space-x-1 lg:flex", children: selectedValues.size > 2 ? (_jsxs(Badge, { variant: "secondary", className: "rounded-sm px-1 font-normal", children: [selectedValues.size, " selected"] })) : (options
|
|
21
|
+
.filter((option) => selectedValues.has(option.value))
|
|
22
|
+
.map((option) => (_jsx(Badge, { variant: "secondary", className: "rounded-sm px-1 font-normal", children: option.label }, option.value)))) })] }))] }) }), _jsxs(PopoverContent, { className: "w-[200px] p-0", align: "start", children: [_jsx("div", { className: "p-2", children: _jsx(Input, { placeholder: title, value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "h-8" }) }), _jsxs("div", { className: "max-h-[300px] overflow-y-auto", children: [filteredOptions.length === 0 ? (_jsx("div", { className: "py-6 text-center text-sm text-muted-foreground", children: "No results found." })) : (_jsx("div", { className: "p-1", children: filteredOptions.map((option) => {
|
|
23
|
+
const isSelected = selectedValues.has(option.value);
|
|
24
|
+
return (_jsxs("button", { type: "button", onClick: () => {
|
|
25
|
+
if (isSelected) {
|
|
26
|
+
selectedValues.delete(option.value);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
selectedValues.add(option.value);
|
|
30
|
+
}
|
|
31
|
+
const filterValues = Array.from(selectedValues);
|
|
32
|
+
column?.setFilterValue(filterValues.length ? filterValues : undefined);
|
|
33
|
+
}, className: cn('relative flex w-full cursor-default items-center gap-2 rounded-sm border-0 bg-transparent px-2 py-1.5 text-left text-sm font-normal outline-hidden select-none', 'hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground'), children: [_jsx("div", { className: cn('me-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary', isSelected ? 'bg-primary text-primary-foreground' : 'opacity-50 [&_svg]:invisible'), children: _jsx(CheckIcon, { className: "h-4 w-4" }) }), option.icon && _jsx(option.icon, { className: "mr-2 h-4 w-4 text-muted-foreground" }), _jsx("span", { children: option.label }), facets?.get(option.value) && (_jsx("span", { className: "ms-auto flex h-4 w-4 items-center justify-center font-mono text-xs", children: facets.get(option.value) }))] }, option.value));
|
|
34
|
+
}) })), selectedValues.size > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "-mx-1 my-1 h-px bg-border" }), _jsx("div", { className: "p-1", children: _jsx("button", { type: "button", onClick: () => column?.setFilterValue(undefined), className: "relative flex w-full cursor-default items-center justify-center rounded-sm border-0 bg-transparent px-2 py-1.5 text-sm font-normal outline-hidden select-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground", children: "Clear filters" }) })] }))] })] })] }));
|
|
35
|
+
}
|
|
36
|
+
export { DataGridColumnFilter };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import type { Column } from '@tanstack/react-table';
|
|
3
|
+
interface DataGridColumnHeaderProps<TData, TValue> extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
column: Column<TData, TValue>;
|
|
5
|
+
/** When omitted, uses `column.columnDef.meta.headerTitle`, then a string `columnDef.header`, then `column.id`. */
|
|
6
|
+
title?: string;
|
|
7
|
+
icon?: ReactNode;
|
|
8
|
+
pinnable?: boolean;
|
|
9
|
+
filter?: ReactNode;
|
|
10
|
+
visibility?: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare function DataGridColumnHeaderInner<TData, TValue>({ column, title, icon, className, filter, visibility }: DataGridColumnHeaderProps<TData, TValue>): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
declare const DataGridColumnHeader: typeof DataGridColumnHeaderInner;
|
|
14
|
+
export { DataGridColumnHeader, type DataGridColumnHeaderProps };
|
|
15
|
+
//# sourceMappingURL=data-grid-column-header.d.ts.map
|