@tangle-network/ui 11.16.11 → 11.19.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/index.d.ts +14 -1
  3. package/dist/index.js +14 -1
  4. package/dist/primitives/alert-dialog.d.ts +34 -0
  5. package/dist/primitives/alert-dialog.js +68 -0
  6. package/dist/primitives/badge.d.ts +7 -0
  7. package/dist/primitives/badge.js +3 -3
  8. package/dist/primitives/checkbox.d.ts +12 -0
  9. package/dist/primitives/checkbox.js +34 -0
  10. package/dist/primitives/chip.d.ts +38 -0
  11. package/dist/primitives/chip.js +41 -0
  12. package/dist/primitives/collapsible.d.ts +12 -0
  13. package/dist/primitives/collapsible.js +13 -0
  14. package/dist/primitives/context-menu.d.ts +36 -0
  15. package/dist/primitives/context-menu.js +86 -0
  16. package/dist/primitives/icon-tile.d.ts +32 -0
  17. package/dist/primitives/icon-tile.js +49 -0
  18. package/dist/primitives/popover.d.ts +15 -0
  19. package/dist/primitives/popover.js +25 -0
  20. package/dist/primitives/separator.d.ts +11 -0
  21. package/dist/primitives/separator.js +21 -0
  22. package/dist/primitives/sheet.d.ts +28 -0
  23. package/dist/primitives/sheet.js +70 -0
  24. package/dist/primitives/slider.d.ts +14 -0
  25. package/dist/primitives/slider.js +37 -0
  26. package/dist/primitives/tag.d.ts +36 -0
  27. package/dist/primitives/tag.js +42 -0
  28. package/dist/primitives/theme-toggle.js +8 -0
  29. package/dist/primitives/tone.d.ts +40 -0
  30. package/dist/primitives/tone.js +106 -0
  31. package/dist/primitives/tooltip.d.ts +16 -0
  32. package/dist/primitives/tooltip.js +32 -0
  33. package/dist/primitives.d.ts +14 -1
  34. package/dist/primitives.js +14 -1
  35. package/package.json +9 -2
  36. package/src/primitives/alert-dialog.tsx +139 -0
  37. package/src/primitives/badge.tsx +17 -4
  38. package/src/primitives/checkbox-separator-sheet-tooltip.stories.tsx +92 -0
  39. package/src/primitives/checkbox-separator-sheet-tooltip.test.tsx +107 -0
  40. package/src/primitives/checkbox.tsx +42 -0
  41. package/src/primitives/chip.test.tsx +178 -0
  42. package/src/primitives/chip.tsx +108 -0
  43. package/src/primitives/collapsible.tsx +16 -0
  44. package/src/primitives/context-menu.tsx +168 -0
  45. package/src/primitives/icon-tile.stories.tsx +74 -0
  46. package/src/primitives/icon-tile.tsx +96 -0
  47. package/src/primitives/index.ts +69 -0
  48. package/src/primitives/popover-collapsible-alert-dialog-context-menu-slider.stories.tsx +124 -0
  49. package/src/primitives/popover-collapsible-alert-dialog-context-menu-slider.test.tsx +165 -0
  50. package/src/primitives/popover.tsx +45 -0
  51. package/src/primitives/separator.tsx +30 -0
  52. package/src/primitives/sheet.tsx +134 -0
  53. package/src/primitives/slider.tsx +71 -0
  54. package/src/primitives/tag-chip.stories.tsx +226 -0
  55. package/src/primitives/tag.tsx +94 -0
  56. package/src/primitives/theme-toggle.test.tsx +2 -1
  57. package/src/primitives/theme-toggle.tsx +2 -0
  58. package/src/primitives/tone.ts +130 -0
  59. package/src/primitives/tooltip.tsx +54 -0
  60. package/src/stories/theme-showcase.stories.tsx +2 -0
@@ -2,6 +2,11 @@ import { Button, buttonVariants } from "./primitives/button.js";
2
2
  import { Heading } from "./primitives/heading.js";
3
3
  import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./primitives/card.js";
4
4
  import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger } from "./primitives/dialog.js";
5
+ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger } from "./primitives/alert-dialog.js";
6
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./primitives/collapsible.js";
7
+ import { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger } from "./primitives/context-menu.js";
8
+ import { Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger } from "./primitives/popover.js";
9
+ import { Slider } from "./primitives/slider.js";
5
10
  import { Label } from "./primitives/label.js";
6
11
  import { Input, Textarea } from "./primitives/input.js";
7
12
  import { Badge, badgeVariants } from "./primitives/badge.js";
@@ -13,6 +18,10 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "./primitives/tabs.js";
13
18
  import { SegmentedControl } from "./primitives/segmented-control.js";
14
19
  import { Progress } from "./primitives/progress.js";
15
20
  import { Switch } from "./primitives/switch.js";
21
+ import { Checkbox } from "./primitives/checkbox.js";
22
+ import { Separator } from "./primitives/separator.js";
23
+ import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger } from "./primitives/sheet.js";
24
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./primitives/tooltip.js";
16
25
  import { Skeleton, SkeletonCard, SkeletonTable } from "./primitives/skeleton.js";
17
26
  import { TextShimmer } from "./primitives/text-shimmer.js";
18
27
  import { ToastContainer, ToastProvider, useToast } from "./primitives/toast.js";
@@ -29,7 +38,11 @@ import { ThemeToggle, useTheme } from "./primitives/theme-toggle.js";
29
38
  import { PageShell } from "./primitives/page-shell.js";
30
39
  import { PageHeader } from "./primitives/page-header.js";
31
40
  import { StatusPill } from "./primitives/status-pill.js";
41
+ import { CATEGORY_TONES, TONE_CLASSES, toneFor } from "./primitives/tone.js";
42
+ import { Tag } from "./primitives/tag.js";
43
+ import { Chip } from "./primitives/chip.js";
44
+ import { IconTile, initialsOf } from "./primitives/icon-tile.js";
32
45
  import { Metric, MetricStrip } from "./primitives/metric-strip.js";
33
46
  import { FilterField, Toolbar } from "./primitives/toolbar.js";
34
47
  import { Logo, TangleKnot } from "@tangle-network/brand";
35
- export { ArtifactPane, Avatar, AvatarFallback, AvatarImage, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CodeBlock, CopyButton, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, FilterField, Heading, InlineCode, Input, Label, Logo, Metric, MetricStrip, PageHeader, PageShell, Progress, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, StatCard, StatusPill, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, ToastContainer, ToastProvider, Toolbar, UploadProgress, badgeVariants, buttonVariants, useTheme, useToast };
48
+ export { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, ArtifactPane, Avatar, AvatarFallback, AvatarImage, Badge, Button, CATEGORY_TONES, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, FilterField, Heading, IconTile, InlineCode, Input, Label, Logo, Metric, MetricStrip, PageHeader, PageShell, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, StatusPill, Switch, TONE_CLASSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, ToastContainer, ToastProvider, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, badgeVariants, buttonVariants, initialsOf, toneFor, useTheme, useToast };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "11.16.11",
3
+ "version": "11.19.0",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -116,16 +116,23 @@
116
116
  "dependencies": {
117
117
  "@pierre/theme": "1.1.0",
118
118
  "@pierre/trees": "1.0.0-beta.6",
119
+ "@radix-ui/react-alert-dialog": "^1.1.23",
119
120
  "@radix-ui/react-avatar": "^1.2.6",
121
+ "@radix-ui/react-checkbox": "^1.3.11",
120
122
  "@radix-ui/react-collapsible": "^1.1.20",
123
+ "@radix-ui/react-context-menu": "^2.3.7",
121
124
  "@radix-ui/react-dialog": "^1.1.23",
122
125
  "@radix-ui/react-dropdown-menu": "^2.1.24",
123
126
  "@radix-ui/react-label": "^2.1.15",
127
+ "@radix-ui/react-popover": "^1.1.23",
124
128
  "@radix-ui/react-progress": "^1.1.16",
125
129
  "@radix-ui/react-select": "^2.3.7",
130
+ "@radix-ui/react-separator": "^1.1.15",
131
+ "@radix-ui/react-slider": "^1.4.7",
126
132
  "@radix-ui/react-slot": "^1.3.3",
127
133
  "@radix-ui/react-switch": "^1.3.7",
128
134
  "@radix-ui/react-tabs": "^1.1.21",
135
+ "@radix-ui/react-tooltip": "^1.2.16",
129
136
  "class-variance-authority": "^0.7.1",
130
137
  "clsx": "^2.1.1",
131
138
  "github-slugger": "^2.0.0",
@@ -142,7 +149,7 @@
142
149
  "peerDependencies": {
143
150
  "@hocuspocus/provider": ">=2.15.0 <5.0.0",
144
151
  "@nanostores/react": ">=0.8.4 <3.0.0",
145
- "@tangle-network/brand": "^1.10.2",
152
+ "@tangle-network/brand": "^1.13.0",
146
153
  "@tiptap/core": ">=3.20.4 <4.0.0",
147
154
  "@tiptap/extension-collaboration": ">=3.20.4 <4.0.0",
148
155
  "@tiptap/extension-collaboration-caret": ">=3.7.0 <4.0.0",
@@ -0,0 +1,139 @@
1
+ "use client";
2
+
3
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
4
+ import * as React from "react";
5
+ import { cn } from "../lib/utils";
6
+ import { buttonVariants } from "./button";
7
+
8
+ /**
9
+ * A dialog that interrupts for a decision. Unlike `Dialog` it has no close
10
+ * button and ignores outside clicks; Escape and `AlertDialogCancel` dismiss it.
11
+ * Radix focuses `AlertDialogCancel` on open, so a destructive action is never
12
+ * under the Enter key when the dialog appears.
13
+ */
14
+ const AlertDialog = AlertDialogPrimitive.Root;
15
+
16
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
17
+
18
+ const AlertDialogPortal = AlertDialogPrimitive.Portal;
19
+
20
+ const AlertDialogOverlay = React.forwardRef<
21
+ React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
22
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
23
+ >(({ className, ...props }, ref) => (
24
+ <AlertDialogPrimitive.Overlay
25
+ ref={ref}
26
+ className={cn(
27
+ "fixed inset-0 z-50 bg-black/40",
28
+ "data-[state=closed]:animate-out data-[state=open]:animate-in",
29
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
30
+ className,
31
+ )}
32
+ {...props}
33
+ />
34
+ ));
35
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
36
+
37
+ const AlertDialogContent = React.forwardRef<
38
+ React.ElementRef<typeof AlertDialogPrimitive.Content>,
39
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
40
+ >(({ className, ...props }, ref) => (
41
+ <AlertDialogPortal>
42
+ <AlertDialogOverlay />
43
+ <AlertDialogPrimitive.Content
44
+ ref={ref}
45
+ className={cn(
46
+ "fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] sm:max-w-lg",
47
+ "gap-4 rounded-lg border border-border bg-card p-6 text-foreground shadow-lg duration-200 outline-hidden",
48
+ "data-[state=closed]:animate-out data-[state=open]:animate-in",
49
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
50
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
51
+ "motion-reduce:animate-none",
52
+ className,
53
+ )}
54
+ {...props}
55
+ />
56
+ </AlertDialogPortal>
57
+ ));
58
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
59
+
60
+ const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
61
+ <div className={cn("flex flex-col gap-2 text-center sm:text-left", className)} {...props} />
62
+ );
63
+ AlertDialogHeader.displayName = "AlertDialogHeader";
64
+
65
+ const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
66
+ <div
67
+ className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
68
+ {...props}
69
+ />
70
+ );
71
+ AlertDialogFooter.displayName = "AlertDialogFooter";
72
+
73
+ const AlertDialogTitle = React.forwardRef<
74
+ React.ElementRef<typeof AlertDialogPrimitive.Title>,
75
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
76
+ >(({ className, ...props }, ref) => (
77
+ <AlertDialogPrimitive.Title
78
+ ref={ref}
79
+ className={cn("font-semibold text-lg leading-snug [overflow-wrap:anywhere]", className)}
80
+ {...props}
81
+ />
82
+ ));
83
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
84
+
85
+ const AlertDialogDescription = React.forwardRef<
86
+ React.ElementRef<typeof AlertDialogPrimitive.Description>,
87
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
88
+ >(({ className, ...props }, ref) => (
89
+ <AlertDialogPrimitive.Description
90
+ ref={ref}
91
+ className={cn("text-muted-foreground text-sm [overflow-wrap:anywhere]", className)}
92
+ {...props}
93
+ />
94
+ ));
95
+ AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
96
+
97
+ /**
98
+ * The confirming action. `tone="destructive"` paints it with the danger fill;
99
+ * the default is the primary button.
100
+ */
101
+ const AlertDialogAction = React.forwardRef<
102
+ React.ElementRef<typeof AlertDialogPrimitive.Action>,
103
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action> & {
104
+ tone?: "default" | "destructive";
105
+ }
106
+ >(({ className, tone = "default", ...props }, ref) => (
107
+ <AlertDialogPrimitive.Action
108
+ ref={ref}
109
+ className={cn(buttonVariants({ variant: tone === "destructive" ? "destructive" : "default" }), className)}
110
+ {...props}
111
+ />
112
+ ));
113
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
114
+
115
+ const AlertDialogCancel = React.forwardRef<
116
+ React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
117
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
118
+ >(({ className, ...props }, ref) => (
119
+ <AlertDialogPrimitive.Cancel
120
+ ref={ref}
121
+ className={cn(buttonVariants({ variant: "outline" }), className)}
122
+ {...props}
123
+ />
124
+ ));
125
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
126
+
127
+ export {
128
+ AlertDialog,
129
+ AlertDialogAction,
130
+ AlertDialogCancel,
131
+ AlertDialogContent,
132
+ AlertDialogDescription,
133
+ AlertDialogFooter,
134
+ AlertDialogHeader,
135
+ AlertDialogOverlay,
136
+ AlertDialogPortal,
137
+ AlertDialogTitle,
138
+ AlertDialogTrigger,
139
+ };
@@ -22,15 +22,21 @@ const badgeVariants = cva(
22
22
  "border-[var(--surface-info-border)] bg-[var(--surface-info-bg)] text-[var(--surface-info-text)]",
23
23
  sandbox:
24
24
  "border-border bg-[var(--accent-surface-soft)] text-[var(--accent-text)]",
25
- /* Operational status variants */
25
+ /* Sandbox lifecycle variants: compatibility only, painted with the
26
+ * status triples. A lifecycle state is domain meaning, so its mapping
27
+ * lives in sandbox-ui's lifecycle adapter over `StatusPill`; these
28
+ * stay until consumers move. Running is up and healthy (success);
29
+ * creating is still in progress (info, as StatusPill's `running`);
30
+ * stopped and warm can be resumed (warning); cold is archived (info);
31
+ * deleted is gone (neutral). */
26
32
  running:
27
- "border-[var(--surface-teal-border)] bg-[var(--surface-teal-bg)] text-[var(--surface-teal-text)]",
33
+ "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]",
28
34
  creating:
29
- "border-[var(--surface-violet-border)] bg-[var(--surface-violet-bg)] text-[var(--surface-violet-text)]",
35
+ "border-[var(--surface-info-border)] bg-[var(--surface-info-bg)] text-[var(--surface-info-text)]",
30
36
  stopped:
31
37
  "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] text-[var(--surface-warning-text)]",
32
38
  warm:
33
- "border-[var(--surface-orange-border)] bg-[var(--surface-orange-bg)] text-[var(--surface-orange-text)]",
39
+ "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] text-[var(--surface-warning-text)]",
34
40
  cold:
35
41
  "border-[var(--surface-info-border)] bg-[var(--surface-info-bg)] text-[var(--surface-info-text)]",
36
42
  deleted:
@@ -43,6 +49,13 @@ const badgeVariants = cva(
43
49
  },
44
50
  );
45
51
 
52
+ /**
53
+ * `success`, `warning`, `error` and `info` paint the same triples as
54
+ * `StatusPill`; for a status, prefer `StatusPill`, which adds the glyph that
55
+ * survives greyscale. The lifecycle variants (`running`, `creating`,
56
+ * `stopped`, `warm`, `cold`, `deleted`) are deprecated: use sandbox-ui's
57
+ * lifecycle adapter.
58
+ */
46
59
  export type BadgeVariant =
47
60
  | "default"
48
61
  | "secondary"
@@ -0,0 +1,92 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { Info } from 'lucide-react'
3
+ import { Button } from './button'
4
+ import { Checkbox } from './checkbox'
5
+ import { Label } from './label'
6
+ import { Separator } from './separator'
7
+ import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from './sheet'
8
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip'
9
+
10
+ const meta: Meta = {
11
+ title: 'Primitives/Checkbox, Separator, Sheet, Tooltip',
12
+ parameters: { layout: 'centered' },
13
+ }
14
+
15
+ export default meta
16
+ type Story = StoryObj
17
+
18
+ export const Checkboxes: Story = {
19
+ render: () => (
20
+ <div className="flex flex-col gap-3">
21
+ {[
22
+ { id: 'a', label: 'Record a video of every run', checked: true },
23
+ { id: 'b', label: 'Retry failed steps once', checked: false },
24
+ { id: 'c', label: 'Select every suite in this workspace', checked: 'indeterminate' as const },
25
+ { id: 'd', label: 'Disabled until billing is set up', checked: false, disabled: true },
26
+ ].map((row) => (
27
+ <div key={row.id} className="flex items-center gap-2">
28
+ <Checkbox id={row.id} defaultChecked={row.checked} disabled={row.disabled} />
29
+ <Label htmlFor={row.id}>{row.label}</Label>
30
+ </div>
31
+ ))}
32
+ </div>
33
+ ),
34
+ }
35
+
36
+ export const Separators: Story = {
37
+ render: () => (
38
+ <div className="w-72 text-sm">
39
+ <p>Account</p>
40
+ <Separator className="my-3" />
41
+ <div className="flex h-5 items-center gap-3">
42
+ <span>Runs</span>
43
+ <Separator orientation="vertical" />
44
+ <span>Suites</span>
45
+ <Separator orientation="vertical" />
46
+ <span>Settings</span>
47
+ </div>
48
+ </div>
49
+ ),
50
+ }
51
+
52
+ export const Sheets: Story = {
53
+ render: () => (
54
+ <div className="flex gap-2">
55
+ {(['left', 'right', 'bottom'] as const).map((side) => (
56
+ <Sheet key={side}>
57
+ <SheetTrigger asChild>
58
+ <Button variant="outline">Open {side}</Button>
59
+ </SheetTrigger>
60
+ <SheetContent side={side}>
61
+ <SheetHeader>
62
+ <SheetTitle>Run assistant</SheetTitle>
63
+ <SheetDescription>Ask why a step failed or what to test next.</SheetDescription>
64
+ </SheetHeader>
65
+ <SheetFooter>
66
+ <Button>Send</Button>
67
+ </SheetFooter>
68
+ </SheetContent>
69
+ </Sheet>
70
+ ))}
71
+ </div>
72
+ ),
73
+ }
74
+
75
+ export const Tooltips: Story = {
76
+ render: () => (
77
+ <TooltipProvider>
78
+ <div className="flex gap-2">
79
+ {['Runs', 'Test suites', 'A much longer label that wraps at the maximum tooltip width'].map((label) => (
80
+ <Tooltip key={label}>
81
+ <TooltipTrigger asChild>
82
+ <Button size="icon" variant="ghost" aria-label={label}>
83
+ <Info className="size-4" />
84
+ </Button>
85
+ </TooltipTrigger>
86
+ <TooltipContent>{label}</TooltipContent>
87
+ </Tooltip>
88
+ ))}
89
+ </div>
90
+ </TooltipProvider>
91
+ ),
92
+ }
@@ -0,0 +1,107 @@
1
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { describe, expect, it } from "vitest";
4
+ import { Checkbox } from "./checkbox";
5
+ import { Label } from "./label";
6
+ import { Separator } from "./separator";
7
+ import { Sheet, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from "./sheet";
8
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
9
+
10
+ describe("Checkbox", () => {
11
+ it("toggles from its label and exposes checkbox state", () => {
12
+ render(
13
+ <>
14
+ <Checkbox id="notify" />
15
+ <Label htmlFor="notify">Email me</Label>
16
+ </>,
17
+ );
18
+ const box = screen.getByRole("checkbox", { name: "Email me" });
19
+ expect(box).toHaveAttribute("aria-checked", "false");
20
+ fireEvent.click(screen.getByText("Email me"));
21
+ expect(box).toHaveAttribute("aria-checked", "true");
22
+ });
23
+
24
+ it("reports the indeterminate state as mixed", () => {
25
+ render(<Checkbox aria-label="Select all" checked="indeterminate" />);
26
+ expect(screen.getByRole("checkbox", { name: "Select all" })).toHaveAttribute(
27
+ "aria-checked",
28
+ "mixed",
29
+ );
30
+ });
31
+
32
+ it("draws the dash for an uncontrolled indeterminate box too", () => {
33
+ const { container } = render(<Checkbox aria-label="Some" defaultChecked="indeterminate" />);
34
+ expect(screen.getByRole("checkbox")).toHaveAttribute("aria-checked", "mixed");
35
+ const dash = container.querySelector("svg.lucide-minus");
36
+ const tick = container.querySelector("svg.lucide-check");
37
+ expect(dash?.getAttribute("class")).toContain("group-data-[state=indeterminate]/checkbox:block");
38
+ expect(tick?.getAttribute("class")).toContain("group-data-[state=indeterminate]/checkbox:hidden");
39
+ expect(screen.getByRole("checkbox")).toHaveAttribute("data-state", "indeterminate");
40
+ });
41
+
42
+ it("paints with tokens, never palette utilities", () => {
43
+ render(<Checkbox aria-label="Agree" defaultChecked />);
44
+ const cls = screen.getByRole("checkbox").className;
45
+ expect(cls).toContain("data-[state=checked]:bg-primary");
46
+ expect(cls).not.toMatch(/\b(?:bg|text|border)-(?:slate|zinc|gray|neutral|blue|green|red|amber|emerald)-\d/);
47
+ });
48
+ });
49
+
50
+ describe("Separator", () => {
51
+ it("is hidden from assistive technology unless it is semantic", () => {
52
+ const { container, rerender } = render(<Separator />);
53
+ expect(container.firstElementChild).toHaveAttribute("role", "none");
54
+ rerender(<Separator decorative={false} orientation="vertical" />);
55
+ const rule = screen.getByRole("separator");
56
+ expect(rule).toHaveAttribute("aria-orientation", "vertical");
57
+ expect(rule.className).toContain("w-px");
58
+ });
59
+ });
60
+
61
+ describe("Sheet", () => {
62
+ it("opens as a labelled dialog and closes from its close button", async () => {
63
+ render(
64
+ <Sheet>
65
+ <SheetTrigger>Open chat</SheetTrigger>
66
+ <SheetContent side="left">
67
+ <SheetTitle>Assistant</SheetTitle>
68
+ <SheetDescription>Ask about this run.</SheetDescription>
69
+ </SheetContent>
70
+ </Sheet>,
71
+ );
72
+ fireEvent.click(screen.getByText("Open chat"));
73
+ const dialog = await screen.findByRole("dialog", { name: "Assistant" });
74
+ expect(dialog.className).toContain("slide-in-from-left");
75
+ fireEvent.click(screen.getByRole("button", { name: "Close" }));
76
+ await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument());
77
+ });
78
+
79
+ it("can hide its close button", async () => {
80
+ render(
81
+ <Sheet defaultOpen>
82
+ <SheetContent hideCloseButton>
83
+ <SheetTitle>Filters</SheetTitle>
84
+ <SheetDescription>Narrow the list.</SheetDescription>
85
+ </SheetContent>
86
+ </Sheet>,
87
+ );
88
+ await screen.findByRole("dialog", { name: "Filters" });
89
+ expect(screen.queryByRole("button", { name: "Close" })).not.toBeInTheDocument();
90
+ });
91
+ });
92
+
93
+ describe("Tooltip", () => {
94
+ it("shows its label when the trigger receives keyboard focus", async () => {
95
+ const user = userEvent.setup();
96
+ render(
97
+ <TooltipProvider delayDuration={0}>
98
+ <Tooltip>
99
+ <TooltipTrigger aria-label="Runs">R</TooltipTrigger>
100
+ <TooltipContent>Runs</TooltipContent>
101
+ </Tooltip>
102
+ </TooltipProvider>,
103
+ );
104
+ await user.tab();
105
+ expect(await screen.findByRole("tooltip")).toHaveTextContent("Runs");
106
+ });
107
+ });
@@ -0,0 +1,42 @@
1
+ "use client";
2
+
3
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
4
+ import { Check, Minus } from "lucide-react";
5
+ import * as React from "react";
6
+ import { focusRing } from "../lib/focus";
7
+ import { cn } from "../lib/utils";
8
+
9
+ /**
10
+ * A two- or three-state checkbox. An indeterminate state, controlled
11
+ * (`checked`) or uncontrolled (`defaultChecked`), draws a dash.
12
+ * Pair it with a `Label` through `id`/`htmlFor`; `aria-invalid` paints the
13
+ * danger hairline.
14
+ */
15
+ const Checkbox = React.forwardRef<
16
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
17
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
18
+ >(({ className, ...props }, ref) => (
19
+ <CheckboxPrimitive.Root
20
+ ref={ref}
21
+ className={cn(
22
+ "peer group/checkbox inline-grid size-4 shrink-0 cursor-pointer place-content-center rounded-[4px] border border-[var(--border-strong)] bg-transparent transition-[background-color,border-color,box-shadow] duration-150 ease-out",
23
+ "disabled:cursor-not-allowed disabled:opacity-50",
24
+ "aria-invalid:border-[var(--surface-danger-border)]",
25
+ "data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
26
+ "data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground",
27
+ focusRing,
28
+ className,
29
+ )}
30
+ {...props}
31
+ >
32
+ <CheckboxPrimitive.Indicator className="grid place-content-center text-current">
33
+ {/* Both glyphs render and the Root's Radix state picks one, so a
34
+ controlled or uncontrolled indeterminate box draws the same dash. */}
35
+ <Check aria-hidden="true" className="size-3.5 group-data-[state=indeterminate]/checkbox:hidden" strokeWidth={3} />
36
+ <Minus aria-hidden="true" className="hidden size-3.5 group-data-[state=indeterminate]/checkbox:block" strokeWidth={3} />
37
+ </CheckboxPrimitive.Indicator>
38
+ </CheckboxPrimitive.Root>
39
+ ));
40
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName;
41
+
42
+ export { Checkbox };