@agent-native/toolkit 0.6.0 → 0.7.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 (173) hide show
  1. package/README.md +17 -1
  2. package/dist/chat-history/ChatHistoryList.d.ts +103 -0
  3. package/dist/chat-history/ChatHistoryList.d.ts.map +1 -0
  4. package/dist/chat-history/ChatHistoryList.js +109 -0
  5. package/dist/chat-history/ChatHistoryList.js.map +1 -0
  6. package/dist/chat-history/ChatHistoryList.spec.d.ts +2 -0
  7. package/dist/chat-history/ChatHistoryList.spec.d.ts.map +1 -0
  8. package/dist/chat-history/ChatHistoryList.spec.js +283 -0
  9. package/dist/chat-history/ChatHistoryList.spec.js.map +1 -0
  10. package/dist/chat-history/index.d.ts +2 -0
  11. package/dist/chat-history/index.d.ts.map +1 -0
  12. package/dist/chat-history/index.js +2 -0
  13. package/dist/chat-history/index.js.map +1 -0
  14. package/dist/chat-history.css +314 -0
  15. package/package.json +17 -1
  16. package/src/app-shell/header-actions.tsx +84 -0
  17. package/src/app-shell/index.ts +1 -0
  18. package/src/chat-history/ChatHistoryList.spec.tsx +453 -0
  19. package/src/chat-history/ChatHistoryList.tsx +469 -0
  20. package/src/chat-history/index.ts +7 -0
  21. package/src/chat-history.css +314 -0
  22. package/src/collab-ui/AgentPresenceChip.tsx +69 -0
  23. package/src/collab-ui/LiveCursorOverlay.tsx +241 -0
  24. package/src/collab-ui/PresenceBar.tsx +346 -0
  25. package/src/collab-ui/RecentEditHighlights.tsx +225 -0
  26. package/src/collab-ui/RemoteSelectionRings.tsx +227 -0
  27. package/src/collab-ui/agent-identity.ts +16 -0
  28. package/src/collab-ui/index.ts +39 -0
  29. package/src/collab-ui/lead-client.ts +32 -0
  30. package/src/collab-ui/types.spec.ts +18 -0
  31. package/src/collab-ui/types.ts +96 -0
  32. package/src/composer/AgentComposerFrame.tsx +59 -0
  33. package/src/composer/ComposerPlusMenu.tsx +983 -0
  34. package/src/composer/MentionPopover.tsx +412 -0
  35. package/src/composer/PastedTextChip.tsx +150 -0
  36. package/src/composer/PromptComposer.spec.ts +73 -0
  37. package/src/composer/PromptComposer.tsx +718 -0
  38. package/src/composer/RealtimeVoiceMode.spec.tsx +771 -0
  39. package/src/composer/RealtimeVoiceMode.tsx +856 -0
  40. package/src/composer/TiptapComposer.spec.ts +390 -0
  41. package/src/composer/TiptapComposer.tsx +2936 -0
  42. package/src/composer/VoiceButton.spec.ts +33 -0
  43. package/src/composer/VoiceButton.tsx +255 -0
  44. package/src/composer/asset-picker-url.spec.ts +56 -0
  45. package/src/composer/asset-picker-url.ts +80 -0
  46. package/src/composer/attachment-accept.spec.ts +22 -0
  47. package/src/composer/attachment-accept.ts +71 -0
  48. package/src/composer/draft-key.spec.ts +19 -0
  49. package/src/composer/draft-key.ts +7 -0
  50. package/src/composer/extensions/FileReference.tsx +55 -0
  51. package/src/composer/extensions/MentionReference.tsx +93 -0
  52. package/src/composer/extensions/SkillReference.tsx +56 -0
  53. package/src/composer/index.ts +79 -0
  54. package/src/composer/pasted-text.spec.ts +162 -0
  55. package/src/composer/pasted-text.ts +160 -0
  56. package/src/composer/prompt-attachments.spec.ts +80 -0
  57. package/src/composer/prompt-attachments.ts +93 -0
  58. package/src/composer/realtime-voice-audio-level.spec.ts +32 -0
  59. package/src/composer/realtime-voice-audio-level.ts +64 -0
  60. package/src/composer/realtime-voice-transcript.spec.ts +95 -0
  61. package/src/composer/realtime-voice-transcript.ts +134 -0
  62. package/src/composer/runtime-adapters.tsx +343 -0
  63. package/src/composer/types.ts +66 -0
  64. package/src/composer/use-file-search.ts +64 -0
  65. package/src/composer/use-mention-search.ts +90 -0
  66. package/src/composer/use-skills.ts +45 -0
  67. package/src/composer/useRealtimeVoiceMode.spec.ts +818 -0
  68. package/src/composer/useRealtimeVoiceMode.tsx +2117 -0
  69. package/src/composer/useVoiceDictation.spec.ts +45 -0
  70. package/src/composer/useVoiceDictation.ts +1191 -0
  71. package/src/context-ui/ContextMeter.tsx +101 -0
  72. package/src/context-ui/ContextSegmentRow.tsx +184 -0
  73. package/src/context-ui/ContextTreemap.tsx +125 -0
  74. package/src/context-ui/ContextXRayPanel.tsx +377 -0
  75. package/src/context-ui/format.ts +42 -0
  76. package/src/context-ui/index.ts +15 -0
  77. package/src/context-ui/types.ts +43 -0
  78. package/src/design-tweaks/index.ts +1 -0
  79. package/src/design-tweaks/visual-style-controls.tsx +1084 -0
  80. package/src/editor/BubbleToolbar.tsx +253 -0
  81. package/src/editor/CodeBlockNode.tsx +254 -0
  82. package/src/editor/DragHandle.spec.ts +588 -0
  83. package/src/editor/DragHandle.ts +1437 -0
  84. package/src/editor/ImageExtension.roundtrip.spec.ts +117 -0
  85. package/src/editor/ImageExtension.ts +306 -0
  86. package/src/editor/RegistryBlockContext.tsx +107 -0
  87. package/src/editor/RegistryBlockNode.spec.ts +279 -0
  88. package/src/editor/RegistryBlockNode.tsx +652 -0
  89. package/src/editor/RichMarkdownEditor.collab.spec.ts +79 -0
  90. package/src/editor/RichMarkdownEditor.roundtrip.spec.ts +226 -0
  91. package/src/editor/RichMarkdownEditor.tsx +81 -0
  92. package/src/editor/RunId.ts +61 -0
  93. package/src/editor/SharedRichEditor.tsx +321 -0
  94. package/src/editor/SlashCommandMenu.tsx +310 -0
  95. package/src/editor/disableHistory.spec.ts +85 -0
  96. package/src/editor/extensions.ts +402 -0
  97. package/src/editor/gfmDoc.spec.ts +230 -0
  98. package/src/editor/gfmDoc.ts +92 -0
  99. package/src/editor/index.ts +93 -0
  100. package/src/editor/registrySlashCommands.ts +131 -0
  101. package/src/editor/structuralReorderUndo.spec.ts +144 -0
  102. package/src/editor/surgical-apply.spec.ts +279 -0
  103. package/src/editor/surgical-apply.ts +192 -0
  104. package/src/editor/useCollabReconcile.concurrent.spec.ts +422 -0
  105. package/src/editor/useCollabReconcile.idempotent.spec.ts +324 -0
  106. package/src/editor/useCollabReconcile.ts +589 -0
  107. package/src/editor.css +510 -0
  108. package/src/hooks/index.ts +2 -0
  109. package/src/hooks/use-mobile.tsx +21 -0
  110. package/src/hooks/use-toast.ts +188 -0
  111. package/src/index.ts +8 -0
  112. package/src/onboarding/index.ts +1 -0
  113. package/src/provider.tsx +47 -0
  114. package/src/sharing/VisibilityBadge.spec.ts +96 -0
  115. package/src/sharing/VisibilityBadge.tsx +37 -0
  116. package/src/sharing/index.ts +4 -0
  117. package/src/styles.css +21 -0
  118. package/src/ui/accordion.tsx +56 -0
  119. package/src/ui/alert-dialog.tsx +141 -0
  120. package/src/ui/alert.tsx +59 -0
  121. package/src/ui/aspect-ratio.tsx +5 -0
  122. package/src/ui/avatar.tsx +48 -0
  123. package/src/ui/badge.tsx +37 -0
  124. package/src/ui/breadcrumb.tsx +115 -0
  125. package/src/ui/button.tsx +78 -0
  126. package/src/ui/calendar.tsx +214 -0
  127. package/src/ui/card.tsx +86 -0
  128. package/src/ui/carousel.tsx +260 -0
  129. package/src/ui/chart.tsx +375 -0
  130. package/src/ui/checkbox.tsx +28 -0
  131. package/src/ui/collapsible.tsx +9 -0
  132. package/src/ui/command.spec.tsx +51 -0
  133. package/src/ui/command.tsx +174 -0
  134. package/src/ui/context-menu.tsx +202 -0
  135. package/src/ui/date-picker.tsx +64 -0
  136. package/src/ui/dialog.spec.tsx +43 -0
  137. package/src/ui/dialog.tsx +146 -0
  138. package/src/ui/drawer.tsx +126 -0
  139. package/src/ui/dropdown-menu.tsx +218 -0
  140. package/src/ui/form.tsx +177 -0
  141. package/src/ui/hover-card.tsx +27 -0
  142. package/src/ui/index.ts +52 -0
  143. package/src/ui/input-otp.tsx +69 -0
  144. package/src/ui/input.tsx +22 -0
  145. package/src/ui/label.tsx +24 -0
  146. package/src/ui/logo.tsx +36 -0
  147. package/src/ui/menubar.tsx +235 -0
  148. package/src/ui/navigation-menu.tsx +128 -0
  149. package/src/ui/pagination.tsx +121 -0
  150. package/src/ui/popover.tsx +44 -0
  151. package/src/ui/progress.tsx +26 -0
  152. package/src/ui/radio-group.tsx +42 -0
  153. package/src/ui/resizable.tsx +43 -0
  154. package/src/ui/scroll-area.tsx +46 -0
  155. package/src/ui/select.tsx +158 -0
  156. package/src/ui/separator.tsx +29 -0
  157. package/src/ui/sheet.tsx +160 -0
  158. package/src/ui/sidebar.tsx +777 -0
  159. package/src/ui/skeleton.tsx +18 -0
  160. package/src/ui/slider.tsx +26 -0
  161. package/src/ui/sonner.tsx +58 -0
  162. package/src/ui/spinner.tsx +17 -0
  163. package/src/ui/switch.tsx +27 -0
  164. package/src/ui/table.tsx +117 -0
  165. package/src/ui/tabs.tsx +53 -0
  166. package/src/ui/textarea.tsx +23 -0
  167. package/src/ui/toast.tsx +127 -0
  168. package/src/ui/toaster.tsx +33 -0
  169. package/src/ui/toggle-group.tsx +59 -0
  170. package/src/ui/toggle.tsx +43 -0
  171. package/src/ui/tooltip.tsx +53 -0
  172. package/src/ui/use-toast.ts +3 -0
  173. package/src/utils.ts +8 -0
@@ -0,0 +1,202 @@
1
+ import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
2
+ import {
3
+ IconCheck,
4
+ IconChevronRight,
5
+ IconCircleFilled,
6
+ } from "@tabler/icons-react";
7
+ import * as React from "react";
8
+
9
+ import { cn } from "../utils.js";
10
+
11
+ const ContextMenu = ContextMenuPrimitive.Root;
12
+
13
+ const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
14
+
15
+ const ContextMenuGroup = ContextMenuPrimitive.Group;
16
+
17
+ const ContextMenuPortal = ContextMenuPrimitive.Portal;
18
+
19
+ const ContextMenuSub = ContextMenuPrimitive.Sub;
20
+
21
+ const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
22
+
23
+ const ContextMenuSubTrigger = React.forwardRef<
24
+ React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
25
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
26
+ inset?: boolean;
27
+ }
28
+ >(({ className, inset, children, ...props }, ref) => (
29
+ <ContextMenuPrimitive.SubTrigger
30
+ ref={ref}
31
+ className={cn(
32
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
33
+ inset && "ps-8",
34
+ className,
35
+ )}
36
+ {...props}
37
+ >
38
+ {children}
39
+ <IconChevronRight className="ms-auto h-4 w-4 rtl:-scale-x-100" />
40
+ </ContextMenuPrimitive.SubTrigger>
41
+ ));
42
+ ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
43
+
44
+ const ContextMenuSubContent = React.forwardRef<
45
+ React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
46
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
47
+ >(({ className, ...props }, ref) => (
48
+ <ContextMenuPrimitive.SubContent
49
+ ref={ref}
50
+ className={cn(
51
+ "z-[290] min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md origin-[var(--radix-context-menu-content-transform-origin)] 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 motion-reduce:data-[state=open]:zoom-in-100 motion-reduce:data-[state=closed]:zoom-out-100 motion-reduce:data-[side=bottom]:slide-in-from-top-0 motion-reduce:data-[side=left]:slide-in-from-right-0 motion-reduce:data-[side=right]:slide-in-from-left-0 motion-reduce:data-[side=top]:slide-in-from-bottom-0",
52
+ className,
53
+ )}
54
+ {...props}
55
+ />
56
+ ));
57
+ ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
58
+
59
+ const ContextMenuContent = React.forwardRef<
60
+ React.ElementRef<typeof ContextMenuPrimitive.Content>,
61
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
62
+ >(({ className, ...props }, ref) => (
63
+ <ContextMenuPrimitive.Portal>
64
+ <ContextMenuPrimitive.Content
65
+ ref={ref}
66
+ className={cn(
67
+ "z-[290] min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md origin-[var(--radix-context-menu-content-transform-origin)] 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 motion-reduce:data-[state=open]:zoom-in-100 motion-reduce:data-[state=closed]:zoom-out-100 motion-reduce:data-[side=bottom]:slide-in-from-top-0 motion-reduce:data-[side=left]:slide-in-from-right-0 motion-reduce:data-[side=right]:slide-in-from-left-0 motion-reduce:data-[side=top]:slide-in-from-bottom-0",
68
+ className,
69
+ )}
70
+ {...props}
71
+ />
72
+ </ContextMenuPrimitive.Portal>
73
+ ));
74
+ ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
75
+
76
+ const ContextMenuItem = React.forwardRef<
77
+ React.ElementRef<typeof ContextMenuPrimitive.Item>,
78
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
79
+ inset?: boolean;
80
+ }
81
+ >(({ className, inset, ...props }, ref) => (
82
+ <ContextMenuPrimitive.Item
83
+ ref={ref}
84
+ className={cn(
85
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
86
+ inset && "ps-8",
87
+ className,
88
+ )}
89
+ {...props}
90
+ />
91
+ ));
92
+ ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
93
+
94
+ const ContextMenuCheckboxItem = React.forwardRef<
95
+ React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
96
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
97
+ >(({ className, children, checked, ...props }, ref) => (
98
+ <ContextMenuPrimitive.CheckboxItem
99
+ ref={ref}
100
+ className={cn(
101
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 ps-8 pe-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
102
+ className,
103
+ )}
104
+ checked={checked}
105
+ {...props}
106
+ >
107
+ <span className="absolute start-2 flex h-3.5 w-3.5 items-center justify-center">
108
+ <ContextMenuPrimitive.ItemIndicator>
109
+ <IconCheck className="h-4 w-4" />
110
+ </ContextMenuPrimitive.ItemIndicator>
111
+ </span>
112
+ {children}
113
+ </ContextMenuPrimitive.CheckboxItem>
114
+ ));
115
+ ContextMenuCheckboxItem.displayName =
116
+ ContextMenuPrimitive.CheckboxItem.displayName;
117
+
118
+ const ContextMenuRadioItem = React.forwardRef<
119
+ React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
120
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
121
+ >(({ className, children, ...props }, ref) => (
122
+ <ContextMenuPrimitive.RadioItem
123
+ ref={ref}
124
+ className={cn(
125
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 ps-8 pe-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
126
+ className,
127
+ )}
128
+ {...props}
129
+ >
130
+ <span className="absolute start-2 flex h-3.5 w-3.5 items-center justify-center">
131
+ <ContextMenuPrimitive.ItemIndicator>
132
+ <IconCircleFilled className="h-2 w-2" />
133
+ </ContextMenuPrimitive.ItemIndicator>
134
+ </span>
135
+ {children}
136
+ </ContextMenuPrimitive.RadioItem>
137
+ ));
138
+ ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
139
+
140
+ const ContextMenuLabel = React.forwardRef<
141
+ React.ElementRef<typeof ContextMenuPrimitive.Label>,
142
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
143
+ inset?: boolean;
144
+ }
145
+ >(({ className, inset, ...props }, ref) => (
146
+ <ContextMenuPrimitive.Label
147
+ ref={ref}
148
+ className={cn(
149
+ "px-2 py-1.5 text-sm font-semibold text-foreground",
150
+ inset && "ps-8",
151
+ className,
152
+ )}
153
+ {...props}
154
+ />
155
+ ));
156
+ ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
157
+
158
+ const ContextMenuSeparator = React.forwardRef<
159
+ React.ElementRef<typeof ContextMenuPrimitive.Separator>,
160
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
161
+ >(({ className, ...props }, ref) => (
162
+ <ContextMenuPrimitive.Separator
163
+ ref={ref}
164
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
165
+ {...props}
166
+ />
167
+ ));
168
+ ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
169
+
170
+ const ContextMenuShortcut = ({
171
+ className,
172
+ ...props
173
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
174
+ return (
175
+ <span
176
+ className={cn(
177
+ "ms-auto text-xs tracking-widest text-muted-foreground",
178
+ className,
179
+ )}
180
+ {...props}
181
+ />
182
+ );
183
+ };
184
+ ContextMenuShortcut.displayName = "ContextMenuShortcut";
185
+
186
+ export {
187
+ ContextMenu,
188
+ ContextMenuTrigger,
189
+ ContextMenuContent,
190
+ ContextMenuItem,
191
+ ContextMenuCheckboxItem,
192
+ ContextMenuRadioItem,
193
+ ContextMenuLabel,
194
+ ContextMenuSeparator,
195
+ ContextMenuShortcut,
196
+ ContextMenuGroup,
197
+ ContextMenuPortal,
198
+ ContextMenuSub,
199
+ ContextMenuSubContent,
200
+ ContextMenuSubTrigger,
201
+ ContextMenuRadioGroup,
202
+ };
@@ -0,0 +1,64 @@
1
+ import { IconCalendar } from "@tabler/icons-react";
2
+ import { format, parse, isValid } from "date-fns";
3
+ import { useState } from "react";
4
+
5
+ import { cn } from "../utils.js";
6
+ import { Button } from "./button.js";
7
+ import { Calendar } from "./calendar.js";
8
+ import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
9
+
10
+ interface DatePickerProps {
11
+ /** Date string in YYYY-MM-DD format */
12
+ value: string;
13
+ onChange: (value: string) => void;
14
+ className?: string;
15
+ placeholder?: string;
16
+ }
17
+
18
+ export function DatePicker({
19
+ value,
20
+ onChange,
21
+ className,
22
+ placeholder = "Pick a date",
23
+ }: DatePickerProps) {
24
+ const [open, setOpen] = useState(false);
25
+
26
+ const date = value ? parse(value, "yyyy-MM-dd", new Date()) : undefined;
27
+ const validDate = date && isValid(date) ? date : undefined;
28
+
29
+ return (
30
+ <Popover open={open} onOpenChange={setOpen}>
31
+ <PopoverTrigger asChild>
32
+ <Button
33
+ variant="outline"
34
+ className={cn(
35
+ "h-8 justify-start text-start font-normal text-xs gap-2 px-2.5",
36
+ !validDate && "text-muted-foreground",
37
+ className,
38
+ )}
39
+ >
40
+ <IconCalendar className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
41
+ {validDate ? (
42
+ format(validDate, "MMM d, yyyy")
43
+ ) : (
44
+ <span>{placeholder}</span>
45
+ )}
46
+ </Button>
47
+ </PopoverTrigger>
48
+ <PopoverContent className="w-auto p-0" align="start">
49
+ <Calendar
50
+ mode="single"
51
+ selected={validDate}
52
+ onSelect={(day) => {
53
+ if (day) {
54
+ onChange(format(day, "yyyy-MM-dd"));
55
+ setOpen(false);
56
+ }
57
+ }}
58
+ defaultMonth={validDate}
59
+ autoFocus
60
+ />
61
+ </PopoverContent>
62
+ </Popover>
63
+ );
64
+ }
@@ -0,0 +1,43 @@
1
+ import type { ForwardedRef, ReactElement } from "react";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { DialogOverlay } from "./dialog.js";
5
+
6
+ type OverlayProps = React.ComponentProps<typeof DialogOverlay>;
7
+
8
+ function renderDialogOverlay(props: OverlayProps): ReactElement<{
9
+ className: string;
10
+ }> {
11
+ const overlay = DialogOverlay as unknown as {
12
+ render: (
13
+ props: OverlayProps,
14
+ ref: ForwardedRef<HTMLDivElement>,
15
+ ) => ReactElement<{ className: string }>;
16
+ };
17
+
18
+ return overlay.render(props, null);
19
+ }
20
+
21
+ describe("DialogOverlay", () => {
22
+ it("transitions only the backdrop blur for instant dialogs", () => {
23
+ const overlay = renderDialogOverlay({ motion: "instant" });
24
+
25
+ expect(overlay.props.className).toContain(
26
+ "transition-[backdrop-filter] duration-[1000ms] ease-[var(--ease-out-strong)]",
27
+ );
28
+ expect(overlay.props.className).toContain(
29
+ "starting:[backdrop-filter:blur(0px)]",
30
+ );
31
+ expect(overlay.props.className).toContain("backdrop-blur-[1px]");
32
+ expect(overlay.props.className).not.toContain("animate-in");
33
+ });
34
+
35
+ it("keeps standard dialog overlay motion unchanged", () => {
36
+ const overlay = renderDialogOverlay({});
37
+
38
+ expect(overlay.props.className).toContain("animate-in");
39
+ expect(overlay.props.className).not.toContain(
40
+ "transition-[backdrop-filter]",
41
+ );
42
+ });
43
+ });
@@ -0,0 +1,146 @@
1
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
2
+ import { IconX } from "@tabler/icons-react";
3
+ import * as React from "react";
4
+
5
+ import { cn } from "../utils.js";
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
+ type DialogMotion = "default" | "instant";
16
+
17
+ interface DialogOverlayProps extends React.ComponentPropsWithoutRef<
18
+ typeof DialogPrimitive.Overlay
19
+ > {
20
+ motion?: DialogMotion;
21
+ }
22
+
23
+ const DialogOverlay = React.forwardRef<
24
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
25
+ DialogOverlayProps
26
+ >(({ className, motion = "default", ...props }, ref) => (
27
+ <DialogPrimitive.Overlay
28
+ ref={ref}
29
+ className={cn(
30
+ "fixed inset-0 z-[270] bg-background/45 backdrop-blur-[1px]",
31
+ motion === "default" &&
32
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
33
+ motion === "instant" &&
34
+ "transition-[backdrop-filter] duration-[1000ms] ease-[var(--ease-out-strong)] starting:[backdrop-filter:blur(0px)] motion-reduce:transition-none",
35
+ className,
36
+ )}
37
+ {...props}
38
+ />
39
+ ));
40
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
41
+
42
+ interface DialogContentProps extends React.ComponentPropsWithoutRef<
43
+ typeof DialogPrimitive.Content
44
+ > {
45
+ // When true, the default close button is hidden; the caller is responsible for
46
+ // providing a dismiss affordance (e.g. a footer Cancel action).
47
+ hideClose?: boolean;
48
+ // High-frequency keyboard surfaces can opt out without changing ordinary dialogs.
49
+ motion?: DialogMotion;
50
+ }
51
+
52
+ const DialogContent = React.forwardRef<
53
+ React.ElementRef<typeof DialogPrimitive.Content>,
54
+ DialogContentProps
55
+ >(({ className, children, hideClose, motion = "default", ...props }, ref) => (
56
+ <DialogPortal>
57
+ <DialogOverlay motion={motion} />
58
+ <DialogPrimitive.Content
59
+ ref={ref}
60
+ className={cn(
61
+ "fixed left-1/2 top-1/2 z-[280] grid max-h-[min(760px,calc(100vh-32px))] w-[calc(100vw-24px)] max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 overflow-hidden rounded-lg border border-border bg-background p-6 text-foreground shadow-lg outline-none",
62
+ motion === "default" &&
63
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-150 data-[state=open]:duration-200 data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
64
+ className,
65
+ )}
66
+ {...props}
67
+ >
68
+ {children}
69
+ {!hideClose && (
70
+ <DialogPrimitive.Close className="absolute end-3 top-3 flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring">
71
+ <IconX className="h-4 w-4" />
72
+ <span className="sr-only">Close</span>
73
+ </DialogPrimitive.Close>
74
+ )}
75
+ </DialogPrimitive.Content>
76
+ </DialogPortal>
77
+ ));
78
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
79
+
80
+ const DialogHeader = ({
81
+ className,
82
+ ...props
83
+ }: React.HTMLAttributes<HTMLDivElement>) => (
84
+ <div
85
+ className={cn(
86
+ "flex flex-col space-y-1.5 text-center sm:text-start",
87
+ className,
88
+ )}
89
+ {...props}
90
+ />
91
+ );
92
+ DialogHeader.displayName = "DialogHeader";
93
+
94
+ const DialogFooter = ({
95
+ className,
96
+ ...props
97
+ }: React.HTMLAttributes<HTMLDivElement>) => (
98
+ <div
99
+ className={cn(
100
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
101
+ className,
102
+ )}
103
+ {...props}
104
+ />
105
+ );
106
+ DialogFooter.displayName = "DialogFooter";
107
+
108
+ const DialogTitle = React.forwardRef<
109
+ React.ElementRef<typeof DialogPrimitive.Title>,
110
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
111
+ >(({ className, ...props }, ref) => (
112
+ <DialogPrimitive.Title
113
+ ref={ref}
114
+ className={cn(
115
+ "text-lg font-semibold leading-none tracking-tight text-foreground",
116
+ className,
117
+ )}
118
+ {...props}
119
+ />
120
+ ));
121
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
122
+
123
+ const DialogDescription = React.forwardRef<
124
+ React.ElementRef<typeof DialogPrimitive.Description>,
125
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
126
+ >(({ className, ...props }, ref) => (
127
+ <DialogPrimitive.Description
128
+ ref={ref}
129
+ className={cn("text-sm text-muted-foreground", className)}
130
+ {...props}
131
+ />
132
+ ));
133
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
134
+
135
+ export {
136
+ Dialog,
137
+ DialogPortal,
138
+ DialogOverlay,
139
+ DialogClose,
140
+ DialogTrigger,
141
+ DialogContent,
142
+ DialogHeader,
143
+ DialogFooter,
144
+ DialogTitle,
145
+ DialogDescription,
146
+ };
@@ -0,0 +1,126 @@
1
+ import * as React from "react";
2
+ import { Drawer as DrawerPrimitive } from "vaul";
3
+
4
+ import { cn } from "../utils.js";
5
+
6
+ const Drawer = ({
7
+ shouldScaleBackground = true,
8
+ ...props
9
+ }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
10
+ <DrawerPrimitive.Root
11
+ shouldScaleBackground={shouldScaleBackground}
12
+ {...props}
13
+ />
14
+ );
15
+ Drawer.displayName = "Drawer";
16
+
17
+ type DrawerButtonProps = React.ComponentPropsWithoutRef<"button"> & {
18
+ asChild?: boolean;
19
+ };
20
+
21
+ const DrawerTrigger = React.forwardRef<HTMLButtonElement, DrawerButtonProps>(
22
+ (props, ref) => <DrawerPrimitive.Trigger ref={ref} {...props} />,
23
+ );
24
+ DrawerTrigger.displayName = DrawerPrimitive.Trigger.displayName;
25
+
26
+ const DrawerPortal = DrawerPrimitive.Portal;
27
+
28
+ const DrawerClose = React.forwardRef<HTMLButtonElement, DrawerButtonProps>(
29
+ (props, ref) => <DrawerPrimitive.Close ref={ref} {...props} />,
30
+ );
31
+ DrawerClose.displayName = DrawerPrimitive.Close.displayName;
32
+
33
+ type DrawerDivProps = React.ComponentPropsWithoutRef<"div"> & {
34
+ asChild?: boolean;
35
+ };
36
+
37
+ const DrawerOverlay = React.forwardRef<HTMLDivElement, DrawerDivProps>(
38
+ ({ className, ...props }, ref) => (
39
+ <DrawerPrimitive.Overlay
40
+ ref={ref}
41
+ className={cn("fixed inset-0 z-[250] bg-black/80", className)}
42
+ {...props}
43
+ />
44
+ ),
45
+ );
46
+ DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
47
+
48
+ const DrawerContent = React.forwardRef<HTMLDivElement, DrawerDivProps>(
49
+ ({ className, children, ...props }, ref) => (
50
+ <DrawerPortal>
51
+ <DrawerOverlay />
52
+ <DrawerPrimitive.Content
53
+ ref={ref}
54
+ className={cn(
55
+ "fixed inset-x-0 bottom-0 z-[250] mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
56
+ className,
57
+ )}
58
+ {...props}
59
+ >
60
+ <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
61
+ {children}
62
+ </DrawerPrimitive.Content>
63
+ </DrawerPortal>
64
+ ),
65
+ );
66
+ DrawerContent.displayName = "DrawerContent";
67
+
68
+ const DrawerHeader = ({
69
+ className,
70
+ ...props
71
+ }: React.HTMLAttributes<HTMLDivElement>) => (
72
+ <div
73
+ className={cn("grid gap-1.5 p-4 text-center sm:text-start", className)}
74
+ {...props}
75
+ />
76
+ );
77
+ DrawerHeader.displayName = "DrawerHeader";
78
+
79
+ const DrawerFooter = ({
80
+ className,
81
+ ...props
82
+ }: React.HTMLAttributes<HTMLDivElement>) => (
83
+ <div
84
+ className={cn("mt-auto flex flex-col gap-2 p-4", className)}
85
+ {...props}
86
+ />
87
+ );
88
+ DrawerFooter.displayName = "DrawerFooter";
89
+
90
+ const DrawerTitle = React.forwardRef<HTMLDivElement, DrawerDivProps>(
91
+ ({ className, ...props }, ref) => (
92
+ <DrawerPrimitive.Title
93
+ ref={ref}
94
+ className={cn(
95
+ "text-lg font-semibold leading-none tracking-tight",
96
+ className,
97
+ )}
98
+ {...props}
99
+ />
100
+ ),
101
+ );
102
+ DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
103
+
104
+ const DrawerDescription = React.forwardRef<HTMLDivElement, DrawerDivProps>(
105
+ ({ className, ...props }, ref) => (
106
+ <DrawerPrimitive.Description
107
+ ref={ref}
108
+ className={cn("text-sm text-muted-foreground", className)}
109
+ {...props}
110
+ />
111
+ ),
112
+ );
113
+ DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
114
+
115
+ export {
116
+ Drawer,
117
+ DrawerPortal,
118
+ DrawerOverlay,
119
+ DrawerTrigger,
120
+ DrawerClose,
121
+ DrawerContent,
122
+ DrawerHeader,
123
+ DrawerFooter,
124
+ DrawerTitle,
125
+ DrawerDescription,
126
+ };