@xemahq/ui-kernel 0.1.11 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/biome-host/biome-builders.d.ts +21 -0
- package/dist/lib/biome-host/biome-builders.d.ts.map +1 -0
- package/dist/lib/biome-host/biome-builders.js +25 -0
- package/dist/lib/biome-host/biome-builders.js.map +1 -0
- package/dist/lib/biome-host/biome-navigation.d.ts +3 -0
- package/dist/lib/biome-host/biome-navigation.d.ts.map +1 -0
- package/dist/lib/biome-host/biome-navigation.js +14 -0
- package/dist/lib/biome-host/biome-navigation.js.map +1 -0
- package/dist/lib/biome-host/biome-scope.d.ts +18 -0
- package/dist/lib/biome-host/biome-scope.d.ts.map +1 -0
- package/dist/lib/biome-host/biome-scope.js +42 -0
- package/dist/lib/biome-host/biome-scope.js.map +1 -0
- package/dist/lib/biome-host/biome-scoped-query.d.ts +17 -0
- package/dist/lib/biome-host/biome-scoped-query.d.ts.map +1 -0
- package/dist/lib/biome-host/biome-scoped-query.js +39 -0
- package/dist/lib/biome-host/biome-scoped-query.js.map +1 -0
- package/dist/lib/biome-host/host-bridge.d.ts +3 -0
- package/dist/lib/biome-host/host-bridge.d.ts.map +1 -1
- package/dist/lib/biome-host/host-bridge.js.map +1 -1
- package/dist/lib/biome-host/index.d.ts +4 -0
- package/dist/lib/biome-host/index.d.ts.map +1 -1
- package/dist/lib/biome-host/index.js +4 -0
- package/dist/lib/biome-host/index.js.map +1 -1
- package/dist/lib/capabilities/capability-provider.d.ts +15 -0
- package/dist/lib/capabilities/capability-provider.d.ts.map +1 -0
- package/dist/lib/capabilities/capability-provider.js +36 -0
- package/dist/lib/capabilities/capability-provider.js.map +1 -0
- package/dist/lib/capabilities/index.d.ts +4 -0
- package/dist/lib/capabilities/index.d.ts.map +1 -0
- package/dist/lib/capabilities/index.js +20 -0
- package/dist/lib/capabilities/index.js.map +1 -0
- package/dist/lib/capabilities/types.d.ts +18 -0
- package/dist/lib/capabilities/types.d.ts.map +1 -0
- package/dist/lib/capabilities/types.js +3 -0
- package/dist/lib/capabilities/types.js.map +1 -0
- package/dist/lib/capabilities/use-capability.d.ts +18 -0
- package/dist/lib/capabilities/use-capability.d.ts.map +1 -0
- package/dist/lib/capabilities/use-capability.js +21 -0
- package/dist/lib/capabilities/use-capability.js.map +1 -0
- package/dist/session/shell/SessionWorkspaceShell.js +1 -1
- package/dist/session/shell/SessionWorkspaceShell.js.map +1 -1
- package/dist/session-kit/display/ThinkingPanel.d.ts.map +1 -1
- package/dist/session-kit/display/ThinkingPanel.js +3 -0
- package/dist/session-kit/display/ThinkingPanel.js.map +1 -1
- package/dist/ui/chrome/AsyncBoundary.d.ts +22 -0
- package/dist/ui/chrome/AsyncBoundary.d.ts.map +1 -0
- package/dist/ui/chrome/AsyncBoundary.js +23 -0
- package/dist/ui/chrome/AsyncBoundary.js.map +1 -0
- package/dist/ui/chrome/EmptyState.d.ts +34 -0
- package/dist/ui/chrome/EmptyState.d.ts.map +1 -0
- package/dist/ui/chrome/EmptyState.js +27 -0
- package/dist/ui/chrome/EmptyState.js.map +1 -0
- package/dist/ui/chrome/ErrorCard.d.ts +11 -0
- package/dist/ui/chrome/ErrorCard.d.ts.map +1 -0
- package/dist/ui/chrome/ErrorCard.js +21 -0
- package/dist/ui/chrome/ErrorCard.js.map +1 -0
- package/dist/ui/chrome/LoadingState.d.ts +10 -0
- package/dist/ui/chrome/LoadingState.d.ts.map +1 -0
- package/dist/ui/chrome/LoadingState.js +17 -0
- package/dist/ui/chrome/LoadingState.js.map +1 -0
- package/dist/ui/chrome/PageHeader.d.ts +20 -0
- package/dist/ui/chrome/PageHeader.d.ts.map +1 -0
- package/dist/ui/chrome/PageHeader.js +26 -0
- package/dist/ui/chrome/PageHeader.js.map +1 -0
- package/dist/ui/chrome/StateCard.d.ts +24 -0
- package/dist/ui/chrome/StateCard.d.ts.map +1 -0
- package/dist/ui/chrome/StateCard.js +17 -0
- package/dist/ui/chrome/StateCard.js.map +1 -0
- package/dist/ui/cn.d.ts +3 -0
- package/dist/ui/cn.d.ts.map +1 -0
- package/dist/ui/cn.js +18 -0
- package/dist/ui/cn.js.map +1 -0
- package/dist/ui/index.d.ts +33 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +61 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/primitives/alert-dialog.d.ts +21 -0
- package/dist/ui/primitives/alert-dialog.d.ts.map +1 -0
- package/dist/ui/primitives/alert-dialog.js +72 -0
- package/dist/ui/primitives/alert-dialog.js.map +1 -0
- package/dist/ui/primitives/badge.d.ts +10 -0
- package/dist/ui/primitives/badge.d.ts.map +1 -0
- package/dist/ui/primitives/badge.js +60 -0
- package/dist/ui/primitives/badge.js.map +1 -0
- package/dist/ui/primitives/button.d.ts +12 -0
- package/dist/ui/primitives/button.d.ts.map +1 -0
- package/dist/ui/primitives/button.js +71 -0
- package/dist/ui/primitives/button.js.map +1 -0
- package/dist/ui/primitives/card.d.ts +9 -0
- package/dist/ui/primitives/card.d.ts.map +1 -0
- package/dist/ui/primitives/card.js +58 -0
- package/dist/ui/primitives/card.js.map +1 -0
- package/dist/ui/primitives/checkbox.d.ts +5 -0
- package/dist/ui/primitives/checkbox.d.ts.map +1 -0
- package/dist/ui/primitives/checkbox.js +45 -0
- package/dist/ui/primitives/checkbox.js.map +1 -0
- package/dist/ui/primitives/collapsible.d.ts +6 -0
- package/dist/ui/primitives/collapsible.d.ts.map +1 -0
- package/dist/ui/primitives/collapsible.js +44 -0
- package/dist/ui/primitives/collapsible.js.map +1 -0
- package/dist/ui/primitives/dialog.d.ts +22 -0
- package/dist/ui/primitives/dialog.d.ts.map +1 -0
- package/dist/ui/primitives/dialog.js +68 -0
- package/dist/ui/primitives/dialog.js.map +1 -0
- package/dist/ui/primitives/dropdown-menu.d.ts +28 -0
- package/dist/ui/primitives/dropdown-menu.d.ts.map +1 -0
- package/dist/ui/primitives/dropdown-menu.js +83 -0
- package/dist/ui/primitives/dropdown-menu.js.map +1 -0
- package/dist/ui/primitives/input.d.ts +4 -0
- package/dist/ui/primitives/input.d.ts.map +1 -0
- package/dist/ui/primitives/input.js +45 -0
- package/dist/ui/primitives/input.js.map +1 -0
- package/dist/ui/primitives/label.d.ts +6 -0
- package/dist/ui/primitives/label.d.ts.map +1 -0
- package/dist/ui/primitives/label.js +46 -0
- package/dist/ui/primitives/label.js.map +1 -0
- package/dist/ui/primitives/overflow-tabs.d.ts +18 -0
- package/dist/ui/primitives/overflow-tabs.d.ts.map +1 -0
- package/dist/ui/primitives/overflow-tabs.js +84 -0
- package/dist/ui/primitives/overflow-tabs.js.map +1 -0
- package/dist/ui/primitives/popover.d.ts +9 -0
- package/dist/ui/primitives/popover.d.ts.map +1 -0
- package/dist/ui/primitives/popover.js +48 -0
- package/dist/ui/primitives/popover.js.map +1 -0
- package/dist/ui/primitives/radio-group.d.ts +6 -0
- package/dist/ui/primitives/radio-group.d.ts.map +1 -0
- package/dist/ui/primitives/radio-group.js +52 -0
- package/dist/ui/primitives/radio-group.js.map +1 -0
- package/dist/ui/primitives/resizable.d.ts +12 -0
- package/dist/ui/primitives/resizable.d.ts.map +1 -0
- package/dist/ui/primitives/resizable.js +18 -0
- package/dist/ui/primitives/resizable.js.map +1 -0
- package/dist/ui/primitives/scroll-area.d.ts +6 -0
- package/dist/ui/primitives/scroll-area.d.ts.map +1 -0
- package/dist/ui/primitives/scroll-area.js +47 -0
- package/dist/ui/primitives/scroll-area.js.map +1 -0
- package/dist/ui/primitives/select.d.ts +14 -0
- package/dist/ui/primitives/select.d.ts.map +1 -0
- package/dist/ui/primitives/select.js +71 -0
- package/dist/ui/primitives/select.js.map +1 -0
- package/dist/ui/primitives/separator.d.ts +5 -0
- package/dist/ui/primitives/separator.d.ts.map +1 -0
- package/dist/ui/primitives/separator.js +44 -0
- package/dist/ui/primitives/separator.js.map +1 -0
- package/dist/ui/primitives/sheet.d.ts +26 -0
- package/dist/ui/primitives/sheet.d.ts.map +1 -0
- package/dist/ui/primitives/sheet.js +82 -0
- package/dist/ui/primitives/sheet.js.map +1 -0
- package/dist/ui/primitives/skeleton.d.ts +13 -0
- package/dist/ui/primitives/skeleton.d.ts.map +1 -0
- package/dist/ui/primitives/skeleton.js +29 -0
- package/dist/ui/primitives/skeleton.js.map +1 -0
- package/dist/ui/primitives/switch.d.ts +5 -0
- package/dist/ui/primitives/switch.d.ts.map +1 -0
- package/dist/ui/primitives/switch.js +44 -0
- package/dist/ui/primitives/switch.js.map +1 -0
- package/dist/ui/primitives/table.d.ts +11 -0
- package/dist/ui/primitives/table.d.ts.map +1 -0
- package/dist/ui/primitives/table.js +64 -0
- package/dist/ui/primitives/table.js.map +1 -0
- package/dist/ui/primitives/tabs.d.ts +8 -0
- package/dist/ui/primitives/tabs.d.ts.map +1 -0
- package/dist/ui/primitives/tabs.js +52 -0
- package/dist/ui/primitives/tabs.js.map +1 -0
- package/dist/ui/primitives/tag-multi-select.d.ts +19 -0
- package/dist/ui/primitives/tag-multi-select.d.ts.map +1 -0
- package/dist/ui/primitives/tag-multi-select.js +92 -0
- package/dist/ui/primitives/tag-multi-select.js.map +1 -0
- package/dist/ui/primitives/textarea.d.ts +5 -0
- package/dist/ui/primitives/textarea.d.ts.map +1 -0
- package/dist/ui/primitives/textarea.js +45 -0
- package/dist/ui/primitives/textarea.js.map +1 -0
- package/dist/ui/primitives/tooltip.d.ts +8 -0
- package/dist/ui/primitives/tooltip.d.ts.map +1 -0
- package/dist/ui/primitives/tooltip.js +50 -0
- package/dist/ui/primitives/tooltip.js.map +1 -0
- package/package.json +27 -4
- package/src/index.ts +1 -0
- package/src/lib/biome-host/biome-builders.ts +109 -0
- package/src/lib/biome-host/biome-navigation.ts +37 -0
- package/src/lib/biome-host/biome-scope.tsx +119 -0
- package/src/lib/biome-host/biome-scoped-query.ts +130 -0
- package/src/lib/biome-host/host-bridge.ts +23 -0
- package/src/lib/biome-host/index.ts +4 -0
- package/src/lib/capabilities/capability-provider.tsx +95 -0
- package/src/lib/capabilities/index.ts +16 -0
- package/src/lib/capabilities/types.ts +69 -0
- package/src/lib/capabilities/use-capability.ts +72 -0
- package/src/session/shell/SessionWorkspaceShell.tsx +2 -2
- package/src/session-kit/display/ThinkingPanel.tsx +3 -0
- package/src/ui/chrome/AsyncBoundary.tsx +66 -0
- package/src/ui/chrome/EmptyState.tsx +184 -0
- package/src/ui/chrome/ErrorCard.tsx +68 -0
- package/src/ui/chrome/LoadingState.tsx +61 -0
- package/src/ui/chrome/PageHeader.tsx +137 -0
- package/src/ui/chrome/StateCard.tsx +150 -0
- package/src/ui/cn.ts +32 -0
- package/src/ui/index.ts +53 -0
- package/src/ui/primitives/alert-dialog.tsx +104 -0
- package/src/ui/primitives/badge.tsx +32 -0
- package/src/ui/primitives/button.tsx +47 -0
- package/src/ui/primitives/card.tsx +43 -0
- package/src/ui/primitives/checkbox.tsx +26 -0
- package/src/ui/primitives/collapsible.tsx +9 -0
- package/src/ui/primitives/dialog.tsx +103 -0
- package/src/ui/primitives/dropdown-menu.tsx +179 -0
- package/src/ui/primitives/input.tsx +22 -0
- package/src/ui/primitives/label.tsx +17 -0
- package/src/ui/primitives/overflow-tabs.tsx +281 -0
- package/src/ui/primitives/popover.tsx +33 -0
- package/src/ui/primitives/radio-group.tsx +36 -0
- package/src/ui/primitives/resizable.tsx +67 -0
- package/src/ui/primitives/scroll-area.tsx +38 -0
- package/src/ui/primitives/select.tsx +143 -0
- package/src/ui/primitives/separator.tsx +20 -0
- package/src/ui/primitives/sheet.tsx +107 -0
- package/src/ui/primitives/skeleton.tsx +99 -0
- package/src/ui/primitives/switch.tsx +27 -0
- package/src/ui/primitives/table.tsx +72 -0
- package/src/ui/primitives/tabs.tsx +53 -0
- package/src/ui/primitives/tag-multi-select.tsx +241 -0
- package/src/ui/primitives/textarea.tsx +21 -0
- package/src/ui/primitives/tooltip.tsx +30 -0
- package/dist/lib/biome-host/composition-validation.d.ts +0 -22
- package/dist/lib/biome-host/composition-validation.d.ts.map +0 -1
- package/dist/lib/biome-host/composition-validation.js +0 -127
- package/dist/lib/biome-host/composition-validation.js.map +0 -1
- package/dist/registry/lib/composition-validation-host.d.ts +0 -3
- package/dist/registry/lib/composition-validation-host.d.ts.map +0 -1
- package/dist/registry/lib/composition-validation-host.js +0 -10
- package/dist/registry/lib/composition-validation-host.js.map +0 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
2
|
+
import { X } from "lucide-react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from '../cn';
|
|
6
|
+
|
|
7
|
+
const Dialog = DialogPrimitive.Root;
|
|
8
|
+
|
|
9
|
+
const DialogTrigger = DialogPrimitive.Trigger;
|
|
10
|
+
|
|
11
|
+
const DialogPortal = DialogPrimitive.Portal;
|
|
12
|
+
|
|
13
|
+
const DialogClose = DialogPrimitive.Close;
|
|
14
|
+
|
|
15
|
+
const DialogOverlay = React.forwardRef<
|
|
16
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
17
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
18
|
+
>(({ className, ...props }, ref) => (
|
|
19
|
+
<DialogPrimitive.Overlay
|
|
20
|
+
ref={ref}
|
|
21
|
+
className={cn(
|
|
22
|
+
"modal-scrim fixed inset-0 z-50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
23
|
+
className,
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
));
|
|
28
|
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
29
|
+
|
|
30
|
+
const DialogContent = React.forwardRef<
|
|
31
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
32
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & { 'data-hide-close'?: boolean }
|
|
33
|
+
>(({ className, children, 'data-hide-close': hideClose, 'aria-describedby': ariaDescribedBy, ...props }, ref) => (
|
|
34
|
+
<DialogPortal>
|
|
35
|
+
<DialogOverlay />
|
|
36
|
+
<DialogPrimitive.Content
|
|
37
|
+
ref={ref}
|
|
38
|
+
// Pulling `aria-describedby` out of props and re-passing it (even
|
|
39
|
+
// when undefined) silences Radix's "Missing Description or
|
|
40
|
+
// aria-describedby={undefined}" warning for dialogs that
|
|
41
|
+
// intentionally have no description. Dialogs that DO include a
|
|
42
|
+
// <DialogDescription> still set this through the spread.
|
|
43
|
+
aria-describedby={ariaDescribedBy}
|
|
44
|
+
className={cn(
|
|
45
|
+
"modal-surface fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg max-h-[calc(100dvh-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 overflow-y-auto p-6 duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
{!hideClose && (
|
|
52
|
+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm text-ink-3 ring-offset-background transition-colors hover:text-ink focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
|
|
53
|
+
<X className="h-4 w-4" />
|
|
54
|
+
<span className="sr-only">Close</span>
|
|
55
|
+
</DialogPrimitive.Close>
|
|
56
|
+
)}
|
|
57
|
+
</DialogPrimitive.Content>
|
|
58
|
+
</DialogPortal>
|
|
59
|
+
));
|
|
60
|
+
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
|
61
|
+
|
|
62
|
+
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
63
|
+
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
|
|
64
|
+
);
|
|
65
|
+
DialogHeader.displayName = "DialogHeader";
|
|
66
|
+
|
|
67
|
+
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
68
|
+
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
|
|
69
|
+
);
|
|
70
|
+
DialogFooter.displayName = "DialogFooter";
|
|
71
|
+
|
|
72
|
+
const DialogTitle = React.forwardRef<
|
|
73
|
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
74
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
75
|
+
>(({ className, ...props }, ref) => (
|
|
76
|
+
<DialogPrimitive.Title
|
|
77
|
+
ref={ref}
|
|
78
|
+
className={cn("text-subtitle font-semibold leading-none tracking-tight", className)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
));
|
|
82
|
+
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
|
83
|
+
|
|
84
|
+
const DialogDescription = React.forwardRef<
|
|
85
|
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
86
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
87
|
+
>(({ className, ...props }, ref) => (
|
|
88
|
+
<DialogPrimitive.Description ref={ref} className={cn("text-body-1 text-ink-3", className)} {...props} />
|
|
89
|
+
));
|
|
90
|
+
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
Dialog,
|
|
94
|
+
DialogPortal,
|
|
95
|
+
DialogOverlay,
|
|
96
|
+
DialogClose,
|
|
97
|
+
DialogTrigger,
|
|
98
|
+
DialogContent,
|
|
99
|
+
DialogHeader,
|
|
100
|
+
DialogFooter,
|
|
101
|
+
DialogTitle,
|
|
102
|
+
DialogDescription,
|
|
103
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
2
|
+
import { Check, ChevronRight, Circle } from "lucide-react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from '../cn';
|
|
6
|
+
|
|
7
|
+
const DropdownMenu = DropdownMenuPrimitive.Root;
|
|
8
|
+
|
|
9
|
+
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
10
|
+
|
|
11
|
+
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
|
12
|
+
|
|
13
|
+
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
|
14
|
+
|
|
15
|
+
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
|
16
|
+
|
|
17
|
+
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
|
18
|
+
|
|
19
|
+
const DropdownMenuSubTrigger = React.forwardRef<
|
|
20
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
|
21
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
22
|
+
inset?: boolean;
|
|
23
|
+
}
|
|
24
|
+
>(({ className, inset, children, ...props }, ref) => (
|
|
25
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
26
|
+
ref={ref}
|
|
27
|
+
className={cn(
|
|
28
|
+
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-body-1 outline-none data-[state=open]:bg-accent focus:bg-accent",
|
|
29
|
+
inset && "pl-8",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
{children}
|
|
35
|
+
<ChevronRight className="ml-auto h-4 w-4" />
|
|
36
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
37
|
+
));
|
|
38
|
+
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
|
|
39
|
+
|
|
40
|
+
const DropdownMenuSubContent = React.forwardRef<
|
|
41
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
|
42
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
|
43
|
+
>(({ className, ...props }, ref) => (
|
|
44
|
+
<DropdownMenuPrimitive.SubContent
|
|
45
|
+
ref={ref}
|
|
46
|
+
className={cn(
|
|
47
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
));
|
|
53
|
+
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
|
|
54
|
+
|
|
55
|
+
const DropdownMenuContent = React.forwardRef<
|
|
56
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
57
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
58
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
59
|
+
<DropdownMenuPrimitive.Portal>
|
|
60
|
+
<DropdownMenuPrimitive.Content
|
|
61
|
+
ref={ref}
|
|
62
|
+
sideOffset={sideOffset}
|
|
63
|
+
className={cn(
|
|
64
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
65
|
+
className,
|
|
66
|
+
)}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
</DropdownMenuPrimitive.Portal>
|
|
70
|
+
));
|
|
71
|
+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
|
72
|
+
|
|
73
|
+
const DropdownMenuItem = React.forwardRef<
|
|
74
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
75
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
|
76
|
+
inset?: boolean;
|
|
77
|
+
}
|
|
78
|
+
>(({ className, inset, ...props }, ref) => (
|
|
79
|
+
<DropdownMenuPrimitive.Item
|
|
80
|
+
ref={ref}
|
|
81
|
+
className={cn(
|
|
82
|
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-body-1 outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
|
|
83
|
+
inset && "pl-8",
|
|
84
|
+
className,
|
|
85
|
+
)}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
));
|
|
89
|
+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
|
90
|
+
|
|
91
|
+
const DropdownMenuCheckboxItem = React.forwardRef<
|
|
92
|
+
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
|
93
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
|
94
|
+
>(({ className, children, checked, ...props }, ref) => (
|
|
95
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
96
|
+
ref={ref}
|
|
97
|
+
className={cn(
|
|
98
|
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-body-1 outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
|
|
99
|
+
className,
|
|
100
|
+
)}
|
|
101
|
+
checked={checked}
|
|
102
|
+
{...props}
|
|
103
|
+
>
|
|
104
|
+
<span className="absolute left-2 flex h-4 w-4 items-center justify-center">
|
|
105
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
106
|
+
<Check className="h-4 w-4" />
|
|
107
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
108
|
+
</span>
|
|
109
|
+
{children}
|
|
110
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
111
|
+
));
|
|
112
|
+
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
|
|
113
|
+
|
|
114
|
+
const DropdownMenuRadioItem = React.forwardRef<
|
|
115
|
+
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
|
116
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
|
117
|
+
>(({ className, children, ...props }, ref) => (
|
|
118
|
+
<DropdownMenuPrimitive.RadioItem
|
|
119
|
+
ref={ref}
|
|
120
|
+
className={cn(
|
|
121
|
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-body-1 outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground",
|
|
122
|
+
className,
|
|
123
|
+
)}
|
|
124
|
+
{...props}
|
|
125
|
+
>
|
|
126
|
+
<span className="absolute left-2 flex h-4 w-4 items-center justify-center">
|
|
127
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
128
|
+
<Circle className="h-2 w-2 fill-current" />
|
|
129
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
130
|
+
</span>
|
|
131
|
+
{children}
|
|
132
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
133
|
+
));
|
|
134
|
+
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
|
135
|
+
|
|
136
|
+
const DropdownMenuLabel = React.forwardRef<
|
|
137
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
|
138
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
|
139
|
+
inset?: boolean;
|
|
140
|
+
}
|
|
141
|
+
>(({ className, inset, ...props }, ref) => (
|
|
142
|
+
<DropdownMenuPrimitive.Label
|
|
143
|
+
ref={ref}
|
|
144
|
+
className={cn("px-2 py-1.5 text-body-1 font-semibold", inset && "pl-8", className)}
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
));
|
|
148
|
+
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
|
149
|
+
|
|
150
|
+
const DropdownMenuSeparator = React.forwardRef<
|
|
151
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
152
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
153
|
+
>(({ className, ...props }, ref) => (
|
|
154
|
+
<DropdownMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
|
|
155
|
+
));
|
|
156
|
+
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
|
157
|
+
|
|
158
|
+
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
159
|
+
return <span className={cn("ml-auto text-body-1 tracking-widest opacity-60", className)} {...props} />;
|
|
160
|
+
};
|
|
161
|
+
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
DropdownMenu,
|
|
165
|
+
DropdownMenuTrigger,
|
|
166
|
+
DropdownMenuContent,
|
|
167
|
+
DropdownMenuItem,
|
|
168
|
+
DropdownMenuCheckboxItem,
|
|
169
|
+
DropdownMenuRadioItem,
|
|
170
|
+
DropdownMenuLabel,
|
|
171
|
+
DropdownMenuSeparator,
|
|
172
|
+
DropdownMenuShortcut,
|
|
173
|
+
DropdownMenuGroup,
|
|
174
|
+
DropdownMenuPortal,
|
|
175
|
+
DropdownMenuSub,
|
|
176
|
+
DropdownMenuSubContent,
|
|
177
|
+
DropdownMenuSubTrigger,
|
|
178
|
+
DropdownMenuRadioGroup,
|
|
179
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from '../cn';
|
|
4
|
+
|
|
5
|
+
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
|
6
|
+
({ className, type, ...props }, ref) => {
|
|
7
|
+
return (
|
|
8
|
+
<input
|
|
9
|
+
type={type}
|
|
10
|
+
className={cn(
|
|
11
|
+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-body-1 file:font-medium file:text-foreground placeholder:text-ink-3 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-body-1",
|
|
12
|
+
className,
|
|
13
|
+
)}
|
|
14
|
+
ref={ref}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
Input.displayName = "Input";
|
|
21
|
+
|
|
22
|
+
export { Input };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from '../cn';
|
|
6
|
+
|
|
7
|
+
const labelVariants = cva("text-body-1 font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");
|
|
8
|
+
|
|
9
|
+
const Label = React.forwardRef<
|
|
10
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
11
|
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
|
|
12
|
+
>(({ className, ...props }, ref) => (
|
|
13
|
+
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
|
|
14
|
+
));
|
|
15
|
+
Label.displayName = LabelPrimitive.Root.displayName;
|
|
16
|
+
|
|
17
|
+
export { Label };
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OverflowTabs — editorial tab strip that auto-collapses overflowing items
|
|
3
|
+
* into a "More…" dropdown instead of scrolling horizontally.
|
|
4
|
+
*
|
|
5
|
+
* Behavior:
|
|
6
|
+
* 1. All tabs render off-screen for measurement (visibility: hidden, but
|
|
7
|
+
* still laid out so widths are real).
|
|
8
|
+
* 2. A ResizeObserver tracks the container width; once known we walk the
|
|
9
|
+
* tab widths left-to-right and pick the cutoff where they still fit
|
|
10
|
+
* alongside a fixed "More…" trigger.
|
|
11
|
+
* 3. Visible tabs render inline; overflow tabs go into a dropdown.
|
|
12
|
+
*
|
|
13
|
+
* The active tab is always pulled into the visible set, even if its index
|
|
14
|
+
* would be in the overflow bucket — users should never lose sight of "where
|
|
15
|
+
* am I." Same for tabs marked `pinned: true` (indicator dot is sticky).
|
|
16
|
+
*/
|
|
17
|
+
import { ChevronDown, X } from 'lucide-react';
|
|
18
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
DropdownMenu,
|
|
22
|
+
DropdownMenuContent,
|
|
23
|
+
DropdownMenuItem,
|
|
24
|
+
DropdownMenuTrigger,
|
|
25
|
+
} from './dropdown-menu';
|
|
26
|
+
import { cn } from '../cn';
|
|
27
|
+
|
|
28
|
+
export interface OverflowTab {
|
|
29
|
+
readonly value: string;
|
|
30
|
+
readonly label: string;
|
|
31
|
+
/** Optional warning/indicator dot rendered next to the label. */
|
|
32
|
+
readonly indicator?: boolean;
|
|
33
|
+
/** Optional numeric badge. */
|
|
34
|
+
readonly count?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface OverflowTabsProps {
|
|
38
|
+
readonly tabs: readonly OverflowTab[];
|
|
39
|
+
readonly value: string;
|
|
40
|
+
readonly onChange: (value: string) => void;
|
|
41
|
+
/** Reserved px for the "More…" trigger when measuring. Default 88. */
|
|
42
|
+
readonly moreTriggerWidth?: number;
|
|
43
|
+
readonly className?: string;
|
|
44
|
+
/** Each tab's px gap from the next. Matches the visual rendering. Default 4. */
|
|
45
|
+
readonly gap?: number;
|
|
46
|
+
/**
|
|
47
|
+
* When provided, every tab renders a hover `×` that calls this with the
|
|
48
|
+
* tab value. Closable tabs (multi-thread surfaces, etc.) opt in by
|
|
49
|
+
* passing it; surfaces that just navigate omit it.
|
|
50
|
+
*/
|
|
51
|
+
readonly onClose?: (value: string) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function OverflowTabs({
|
|
55
|
+
tabs,
|
|
56
|
+
value,
|
|
57
|
+
onChange,
|
|
58
|
+
moreTriggerWidth = 88,
|
|
59
|
+
className,
|
|
60
|
+
gap = 4,
|
|
61
|
+
onClose,
|
|
62
|
+
}: OverflowTabsProps) {
|
|
63
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
64
|
+
const measureRef = useRef<HTMLDivElement>(null);
|
|
65
|
+
const [containerWidth, setContainerWidth] = useState(0);
|
|
66
|
+
const [tabWidths, setTabWidths] = useState<readonly number[]>([]);
|
|
67
|
+
|
|
68
|
+
// Measure container width and react to resizes.
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
const el = containerRef.current;
|
|
71
|
+
if (!el) return;
|
|
72
|
+
const ro = new ResizeObserver((entries) => {
|
|
73
|
+
const w = entries[0]?.contentRect.width ?? el.clientWidth;
|
|
74
|
+
setContainerWidth(w);
|
|
75
|
+
});
|
|
76
|
+
ro.observe(el);
|
|
77
|
+
setContainerWidth(el.clientWidth);
|
|
78
|
+
return () => ro.disconnect();
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
// Measure each tab's natural width once after first render. We re-measure
|
|
82
|
+
// when the tab list itself changes (new tabs, label edits).
|
|
83
|
+
useLayoutEffect(() => {
|
|
84
|
+
const el = measureRef.current;
|
|
85
|
+
if (!el) return;
|
|
86
|
+
const children = Array.from(el.children) as HTMLElement[];
|
|
87
|
+
setTabWidths(children.map((c) => c.getBoundingClientRect().width));
|
|
88
|
+
}, [tabs]);
|
|
89
|
+
|
|
90
|
+
// Decide split point: walk left-to-right, summing widths + gaps, stopping
|
|
91
|
+
// when the next tab would exceed (container - moreTrigger). The active
|
|
92
|
+
// tab and any pinned tabs are always pulled into the visible set.
|
|
93
|
+
const { visible, overflow } = useMemo(() => {
|
|
94
|
+
if (tabWidths.length !== tabs.length || containerWidth === 0) {
|
|
95
|
+
return { visible: tabs, overflow: [] as readonly OverflowTab[] };
|
|
96
|
+
}
|
|
97
|
+
const totalNatural =
|
|
98
|
+
tabWidths.reduce((s, w) => s + w, 0) + gap * Math.max(0, tabs.length - 1);
|
|
99
|
+
if (totalNatural <= containerWidth) {
|
|
100
|
+
return { visible: tabs, overflow: [] as readonly OverflowTab[] };
|
|
101
|
+
}
|
|
102
|
+
const budget = containerWidth - moreTriggerWidth;
|
|
103
|
+
const vis: OverflowTab[] = [];
|
|
104
|
+
const over: OverflowTab[] = [];
|
|
105
|
+
let used = 0;
|
|
106
|
+
for (let i = 0; i < tabs.length; i++) {
|
|
107
|
+
const w = tabWidths[i] + (vis.length > 0 ? gap : 0);
|
|
108
|
+
if (used + w <= budget) {
|
|
109
|
+
vis.push(tabs[i]);
|
|
110
|
+
used += w;
|
|
111
|
+
} else {
|
|
112
|
+
over.push(tabs[i]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Force the active tab into the visible set if it ended up in overflow.
|
|
116
|
+
const activeIdx = tabs.findIndex((t) => t.value === value);
|
|
117
|
+
if (activeIdx !== -1) {
|
|
118
|
+
const activeTab = tabs[activeIdx];
|
|
119
|
+
const inVisible = vis.some((t) => t.value === activeTab.value);
|
|
120
|
+
if (!inVisible) {
|
|
121
|
+
// Swap the last visible for the active one to keep the budget OK.
|
|
122
|
+
const dropped = vis.pop();
|
|
123
|
+
if (dropped) over.unshift(dropped);
|
|
124
|
+
vis.push(activeTab);
|
|
125
|
+
const removeIdx = over.findIndex((t) => t.value === activeTab.value);
|
|
126
|
+
if (removeIdx !== -1) over.splice(removeIdx, 1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { visible: vis, overflow: over };
|
|
130
|
+
}, [tabs, tabWidths, containerWidth, gap, moreTriggerWidth, value]);
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div className={cn('relative -mb-px flex items-end border-b border-rule', className)}>
|
|
134
|
+
{/* Off-screen measurement layer — renders ALL tabs so widths are real. */}
|
|
135
|
+
<div
|
|
136
|
+
ref={measureRef}
|
|
137
|
+
aria-hidden
|
|
138
|
+
className="pointer-events-none absolute left-0 top-0 flex items-center gap-1 opacity-0"
|
|
139
|
+
style={{ visibility: 'hidden' }}
|
|
140
|
+
>
|
|
141
|
+
{tabs.map((t) => (
|
|
142
|
+
<TabPill key={`measure-${t.value}`} tab={t} active={false} onClick={() => undefined} />
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
{/* Live visible row. */}
|
|
147
|
+
<div
|
|
148
|
+
ref={containerRef}
|
|
149
|
+
role="tablist"
|
|
150
|
+
className="flex flex-1 items-end gap-1 overflow-hidden"
|
|
151
|
+
>
|
|
152
|
+
{visible.map((t) => (
|
|
153
|
+
<TabPill
|
|
154
|
+
key={t.value}
|
|
155
|
+
tab={t}
|
|
156
|
+
active={t.value === value}
|
|
157
|
+
onClick={() => onChange(t.value)}
|
|
158
|
+
onClose={onClose ? () => onClose(t.value) : undefined}
|
|
159
|
+
/>
|
|
160
|
+
))}
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
{overflow.length > 0 && (
|
|
164
|
+
<DropdownMenu>
|
|
165
|
+
<DropdownMenuTrigger asChild>
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
className="ml-1 -mb-px inline-flex shrink-0 items-center gap-1 whitespace-nowrap px-2 py-2 text-body-1 text-ink-3 transition-colors hover:text-ink"
|
|
169
|
+
aria-label="More tabs"
|
|
170
|
+
>
|
|
171
|
+
More
|
|
172
|
+
<span className="rounded bg-paper-elev px-1 font-mono text-[10px] leading-4 text-ink-4">
|
|
173
|
+
{overflow.length}
|
|
174
|
+
</span>
|
|
175
|
+
<ChevronDown className="h-3 w-3" strokeWidth={1.6} />
|
|
176
|
+
</button>
|
|
177
|
+
</DropdownMenuTrigger>
|
|
178
|
+
<DropdownMenuContent align="end" className="min-w-[180px]">
|
|
179
|
+
{overflow.map((t) => (
|
|
180
|
+
<DropdownMenuItem
|
|
181
|
+
key={t.value}
|
|
182
|
+
onClick={() => onChange(t.value)}
|
|
183
|
+
className={cn(t.value === value && 'bg-accent/60 text-foreground')}
|
|
184
|
+
>
|
|
185
|
+
<span className="flex-1 truncate">{t.label}</span>
|
|
186
|
+
{t.count !== undefined && (
|
|
187
|
+
<span className="ml-2 text-caption tabular-nums text-ink-4">{t.count}</span>
|
|
188
|
+
)}
|
|
189
|
+
{t.indicator && (
|
|
190
|
+
<span
|
|
191
|
+
aria-hidden
|
|
192
|
+
className="ml-2 inline-block h-1.5 w-1.5 rounded-full bg-warning"
|
|
193
|
+
/>
|
|
194
|
+
)}
|
|
195
|
+
{onClose && (
|
|
196
|
+
<button
|
|
197
|
+
type="button"
|
|
198
|
+
aria-label={`Close ${t.label}`}
|
|
199
|
+
className="ml-2 rounded p-0.5 text-ink-4 hover:bg-paper-elev hover:text-ink"
|
|
200
|
+
onClick={(e) => {
|
|
201
|
+
e.stopPropagation();
|
|
202
|
+
onClose(t.value);
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
205
|
+
<X className="h-3 w-3" strokeWidth={1.8} />
|
|
206
|
+
</button>
|
|
207
|
+
)}
|
|
208
|
+
</DropdownMenuItem>
|
|
209
|
+
))}
|
|
210
|
+
</DropdownMenuContent>
|
|
211
|
+
</DropdownMenu>
|
|
212
|
+
)}
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function TabPill({
|
|
218
|
+
tab,
|
|
219
|
+
active,
|
|
220
|
+
onClick,
|
|
221
|
+
onClose,
|
|
222
|
+
}: Readonly<{
|
|
223
|
+
tab: OverflowTab;
|
|
224
|
+
active: boolean;
|
|
225
|
+
onClick: () => void;
|
|
226
|
+
onClose?: () => void;
|
|
227
|
+
}>) {
|
|
228
|
+
return (
|
|
229
|
+
<div
|
|
230
|
+
className={cn(
|
|
231
|
+
'group relative -mb-px inline-flex items-center gap-1.5 whitespace-nowrap rounded-t-md border px-3 py-2 text-body-1 transition-colors',
|
|
232
|
+
active
|
|
233
|
+
? 'border-rule border-b-transparent bg-paper-elev text-ink font-medium'
|
|
234
|
+
: 'border-transparent text-ink-3 hover:border-rule/40 hover:bg-paper-elev/60 hover:text-ink-2',
|
|
235
|
+
)}
|
|
236
|
+
>
|
|
237
|
+
<button
|
|
238
|
+
type="button"
|
|
239
|
+
role="tab"
|
|
240
|
+
aria-selected={active}
|
|
241
|
+
onClick={onClick}
|
|
242
|
+
className="inline-flex items-center gap-2 outline-none"
|
|
243
|
+
>
|
|
244
|
+
{tab.label}
|
|
245
|
+
{tab.count !== undefined && (
|
|
246
|
+
<span className="rounded bg-paper-elev px-1 font-mono text-[10px] tabular-nums leading-4 text-ink-4">
|
|
247
|
+
{tab.count}
|
|
248
|
+
</span>
|
|
249
|
+
)}
|
|
250
|
+
{tab.indicator && (
|
|
251
|
+
<span
|
|
252
|
+
aria-hidden
|
|
253
|
+
className="inline-block h-1.5 w-1.5 rounded-full bg-warning"
|
|
254
|
+
/>
|
|
255
|
+
)}
|
|
256
|
+
</button>
|
|
257
|
+
{onClose && (
|
|
258
|
+
<button
|
|
259
|
+
type="button"
|
|
260
|
+
aria-label={`Close ${tab.label}`}
|
|
261
|
+
onClick={(e) => {
|
|
262
|
+
e.stopPropagation();
|
|
263
|
+
onClose();
|
|
264
|
+
}}
|
|
265
|
+
className={cn(
|
|
266
|
+
'rounded p-0.5 text-ink-4 transition-opacity hover:bg-paper hover:text-ink',
|
|
267
|
+
active ? 'opacity-70' : 'opacity-0 group-hover:opacity-70',
|
|
268
|
+
)}
|
|
269
|
+
>
|
|
270
|
+
<X className="h-3 w-3" strokeWidth={1.8} />
|
|
271
|
+
</button>
|
|
272
|
+
)}
|
|
273
|
+
{active && (
|
|
274
|
+
<span
|
|
275
|
+
aria-hidden
|
|
276
|
+
className="absolute inset-x-2 bottom-[-1px] h-[2px] rounded-full bg-primary"
|
|
277
|
+
/>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from '../cn';
|
|
5
|
+
|
|
6
|
+
const Popover = PopoverPrimitive.Root;
|
|
7
|
+
|
|
8
|
+
const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
9
|
+
|
|
10
|
+
type PopoverContentProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {
|
|
11
|
+
container?: HTMLElement | null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const PopoverContent = React.forwardRef<
|
|
15
|
+
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
16
|
+
PopoverContentProps
|
|
17
|
+
>(({ className, align = "center", sideOffset = 4, container, ...props }, ref) => (
|
|
18
|
+
<PopoverPrimitive.Portal container={container ?? undefined}>
|
|
19
|
+
<PopoverPrimitive.Content
|
|
20
|
+
ref={ref}
|
|
21
|
+
align={align}
|
|
22
|
+
sideOffset={sideOffset}
|
|
23
|
+
className={cn(
|
|
24
|
+
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
</PopoverPrimitive.Portal>
|
|
30
|
+
));
|
|
31
|
+
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
|
32
|
+
|
|
33
|
+
export { Popover, PopoverTrigger, PopoverContent };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
|
2
|
+
import { Circle } from "lucide-react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from '../cn';
|
|
6
|
+
|
|
7
|
+
const RadioGroup = React.forwardRef<
|
|
8
|
+
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
|
9
|
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
|
|
10
|
+
>(({ className, ...props }, ref) => {
|
|
11
|
+
return <RadioGroupPrimitive.Root className={cn("grid gap-2", className)} {...props} ref={ref} />;
|
|
12
|
+
});
|
|
13
|
+
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
|
|
14
|
+
|
|
15
|
+
const RadioGroupItem = React.forwardRef<
|
|
16
|
+
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
|
17
|
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
|
|
18
|
+
>(({ className, ...props }, ref) => {
|
|
19
|
+
return (
|
|
20
|
+
<RadioGroupPrimitive.Item
|
|
21
|
+
ref={ref}
|
|
22
|
+
className={cn(
|
|
23
|
+
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
|
29
|
+
<Circle className="h-4 w-4 fill-current text-current" />
|
|
30
|
+
</RadioGroupPrimitive.Indicator>
|
|
31
|
+
</RadioGroupPrimitive.Item>
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
|
|
35
|
+
|
|
36
|
+
export { RadioGroup, RadioGroupItem };
|