canopy-ui 0.2.0 → 0.4.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/package.json +20 -1
- package/src/chat/ChatPanel.tsx +140 -0
- package/src/chat/ConnectionStatus.tsx +30 -0
- package/src/chat/MessageItem.tsx +198 -0
- package/src/chat/MessageList.tsx +95 -0
- package/src/chat/PlacementBanner.test.tsx +112 -0
- package/src/chat/PlacementBanner.tsx +92 -0
- package/src/chat/PresenceChips.tsx +52 -0
- package/src/chat/SendBox.tsx +144 -0
- package/src/chat/ToolCallPair.tsx +86 -0
- package/src/chat/drafts.test.ts +66 -0
- package/src/chat/drafts.ts +19 -0
- package/src/chat/history.test.ts +35 -0
- package/src/chat/history.ts +16 -0
- package/src/chat/index.ts +56 -0
- package/src/chat/pairToolMessages.test.ts +208 -0
- package/src/chat/pairToolMessages.ts +162 -0
- package/src/chat/protocol.ts +99 -0
- package/src/chat/sessionReducer.test.ts +284 -0
- package/src/chat/sessionReducer.ts +245 -0
- package/src/chat/useSessionSocket.ts +269 -0
- package/src/chat/useStickyBottom.ts +77 -0
- package/src/ui/AutoResizeTextarea.tsx +61 -0
- package/src/ui/dialog.tsx +131 -0
- package/src/ui/dropdown-menu.tsx +226 -0
- package/src/ui/index.ts +32 -0
- package/src/ui/sonner.tsx +23 -0
- package/src/ui/tooltip.tsx +55 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Menu } from "@base-ui/react/menu"
|
|
3
|
+
import { Check, ChevronRight, Circle } from "lucide-react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../lib/cn"
|
|
6
|
+
|
|
7
|
+
const DropdownMenu = Menu.Root
|
|
8
|
+
|
|
9
|
+
const DropdownMenuGroup = Menu.Group
|
|
10
|
+
|
|
11
|
+
function DropdownMenuTrigger({
|
|
12
|
+
className,
|
|
13
|
+
...props
|
|
14
|
+
}: React.ComponentProps<typeof Menu.Trigger>) {
|
|
15
|
+
return (
|
|
16
|
+
<Menu.Trigger
|
|
17
|
+
data-slot="dropdown-menu-trigger"
|
|
18
|
+
className={cn("outline-none", className)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function DropdownMenuContent({
|
|
25
|
+
className,
|
|
26
|
+
sideOffset = 4,
|
|
27
|
+
...props
|
|
28
|
+
}: React.ComponentProps<typeof Menu.Popup> & { sideOffset?: number; align?: string }) {
|
|
29
|
+
return (
|
|
30
|
+
<Menu.Portal>
|
|
31
|
+
<Menu.Positioner sideOffset={sideOffset}>
|
|
32
|
+
<Menu.Popup
|
|
33
|
+
data-slot="dropdown-menu-content"
|
|
34
|
+
className={cn(
|
|
35
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md transition-all data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0",
|
|
36
|
+
className
|
|
37
|
+
)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
</Menu.Positioner>
|
|
41
|
+
</Menu.Portal>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function DropdownMenuItem({
|
|
46
|
+
className,
|
|
47
|
+
inset,
|
|
48
|
+
...props
|
|
49
|
+
}: React.ComponentProps<typeof Menu.Item> & { inset?: boolean }) {
|
|
50
|
+
return (
|
|
51
|
+
<Menu.Item
|
|
52
|
+
data-slot="dropdown-menu-item"
|
|
53
|
+
className={cn(
|
|
54
|
+
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
55
|
+
inset && "pl-8",
|
|
56
|
+
className
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function DropdownMenuCheckboxItem({
|
|
64
|
+
className,
|
|
65
|
+
children,
|
|
66
|
+
checked,
|
|
67
|
+
...props
|
|
68
|
+
}: React.ComponentProps<typeof Menu.CheckboxItem>) {
|
|
69
|
+
return (
|
|
70
|
+
<Menu.CheckboxItem
|
|
71
|
+
data-slot="dropdown-menu-checkbox-item"
|
|
72
|
+
className={cn(
|
|
73
|
+
"relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none transition-colors select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
74
|
+
className
|
|
75
|
+
)}
|
|
76
|
+
checked={checked}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
80
|
+
<Menu.CheckboxItemIndicator>
|
|
81
|
+
<Check className="h-4 w-4" />
|
|
82
|
+
</Menu.CheckboxItemIndicator>
|
|
83
|
+
</span>
|
|
84
|
+
{children}
|
|
85
|
+
</Menu.CheckboxItem>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function DropdownMenuRadioGroup(props: React.ComponentProps<typeof Menu.RadioGroup>) {
|
|
90
|
+
return <Menu.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function DropdownMenuRadioItem({
|
|
94
|
+
className,
|
|
95
|
+
children,
|
|
96
|
+
...props
|
|
97
|
+
}: React.ComponentProps<typeof Menu.RadioItem>) {
|
|
98
|
+
return (
|
|
99
|
+
<Menu.RadioItem
|
|
100
|
+
data-slot="dropdown-menu-radio-item"
|
|
101
|
+
className={cn(
|
|
102
|
+
"relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none transition-colors select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
103
|
+
className
|
|
104
|
+
)}
|
|
105
|
+
{...props}
|
|
106
|
+
>
|
|
107
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
108
|
+
<Menu.RadioItemIndicator>
|
|
109
|
+
<Circle className="h-2 w-2 fill-current" />
|
|
110
|
+
</Menu.RadioItemIndicator>
|
|
111
|
+
</span>
|
|
112
|
+
{children}
|
|
113
|
+
</Menu.RadioItem>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function DropdownMenuLabel({
|
|
118
|
+
className,
|
|
119
|
+
inset,
|
|
120
|
+
...props
|
|
121
|
+
}: React.HTMLAttributes<HTMLDivElement> & { inset?: boolean }) {
|
|
122
|
+
return (
|
|
123
|
+
<div
|
|
124
|
+
data-slot="dropdown-menu-label"
|
|
125
|
+
className={cn(
|
|
126
|
+
"px-2 py-1.5 text-sm font-semibold",
|
|
127
|
+
inset && "pl-8",
|
|
128
|
+
className
|
|
129
|
+
)}
|
|
130
|
+
{...props}
|
|
131
|
+
/>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function DropdownMenuSeparator({
|
|
136
|
+
className,
|
|
137
|
+
...props
|
|
138
|
+
}: React.ComponentProps<typeof Menu.Separator>) {
|
|
139
|
+
return (
|
|
140
|
+
<Menu.Separator
|
|
141
|
+
data-slot="dropdown-menu-separator"
|
|
142
|
+
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
143
|
+
{...props}
|
|
144
|
+
/>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function DropdownMenuShortcut({
|
|
149
|
+
className,
|
|
150
|
+
...props
|
|
151
|
+
}: React.HTMLAttributes<HTMLSpanElement>) {
|
|
152
|
+
return (
|
|
153
|
+
<span
|
|
154
|
+
data-slot="dropdown-menu-shortcut"
|
|
155
|
+
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
|
156
|
+
{...props}
|
|
157
|
+
/>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Sub-menu support
|
|
162
|
+
function DropdownMenuSub(props: React.ComponentProps<typeof Menu.Root>) {
|
|
163
|
+
return <Menu.Root {...props} />
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function DropdownMenuSubTrigger({
|
|
167
|
+
className,
|
|
168
|
+
inset,
|
|
169
|
+
children,
|
|
170
|
+
...props
|
|
171
|
+
}: React.ComponentProps<typeof Menu.SubmenuTrigger> & { inset?: boolean }) {
|
|
172
|
+
return (
|
|
173
|
+
<Menu.SubmenuTrigger
|
|
174
|
+
data-slot="dropdown-menu-sub-trigger"
|
|
175
|
+
className={cn(
|
|
176
|
+
"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[highlighted]:bg-accent data-[open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
177
|
+
inset && "pl-8",
|
|
178
|
+
className
|
|
179
|
+
)}
|
|
180
|
+
{...props}
|
|
181
|
+
>
|
|
182
|
+
{children}
|
|
183
|
+
<ChevronRight className="ml-auto" />
|
|
184
|
+
</Menu.SubmenuTrigger>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function DropdownMenuSubContent({
|
|
189
|
+
className,
|
|
190
|
+
...props
|
|
191
|
+
}: React.ComponentProps<typeof Menu.Popup>) {
|
|
192
|
+
return (
|
|
193
|
+
<Menu.Portal>
|
|
194
|
+
<Menu.Positioner>
|
|
195
|
+
<Menu.Popup
|
|
196
|
+
data-slot="dropdown-menu-sub-content"
|
|
197
|
+
className={cn(
|
|
198
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg transition-all data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0",
|
|
199
|
+
className
|
|
200
|
+
)}
|
|
201
|
+
{...props}
|
|
202
|
+
/>
|
|
203
|
+
</Menu.Positioner>
|
|
204
|
+
</Menu.Portal>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const DropdownMenuPortal = Menu.Portal
|
|
209
|
+
|
|
210
|
+
export {
|
|
211
|
+
DropdownMenu,
|
|
212
|
+
DropdownMenuTrigger,
|
|
213
|
+
DropdownMenuContent,
|
|
214
|
+
DropdownMenuItem,
|
|
215
|
+
DropdownMenuCheckboxItem,
|
|
216
|
+
DropdownMenuRadioItem,
|
|
217
|
+
DropdownMenuLabel,
|
|
218
|
+
DropdownMenuSeparator,
|
|
219
|
+
DropdownMenuShortcut,
|
|
220
|
+
DropdownMenuGroup,
|
|
221
|
+
DropdownMenuPortal,
|
|
222
|
+
DropdownMenuSub,
|
|
223
|
+
DropdownMenuSubContent,
|
|
224
|
+
DropdownMenuSubTrigger,
|
|
225
|
+
DropdownMenuRadioGroup,
|
|
226
|
+
}
|
package/src/ui/index.ts
CHANGED
|
@@ -14,3 +14,35 @@ export {
|
|
|
14
14
|
TableCaption,
|
|
15
15
|
} from './table'
|
|
16
16
|
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants } from './tabs'
|
|
17
|
+
export {
|
|
18
|
+
Dialog,
|
|
19
|
+
DialogPortal,
|
|
20
|
+
DialogOverlay,
|
|
21
|
+
DialogTrigger,
|
|
22
|
+
DialogClose,
|
|
23
|
+
DialogContent,
|
|
24
|
+
DialogHeader,
|
|
25
|
+
DialogFooter,
|
|
26
|
+
DialogTitle,
|
|
27
|
+
DialogDescription,
|
|
28
|
+
} from './dialog'
|
|
29
|
+
export {
|
|
30
|
+
DropdownMenu,
|
|
31
|
+
DropdownMenuTrigger,
|
|
32
|
+
DropdownMenuContent,
|
|
33
|
+
DropdownMenuItem,
|
|
34
|
+
DropdownMenuCheckboxItem,
|
|
35
|
+
DropdownMenuRadioItem,
|
|
36
|
+
DropdownMenuLabel,
|
|
37
|
+
DropdownMenuSeparator,
|
|
38
|
+
DropdownMenuShortcut,
|
|
39
|
+
DropdownMenuGroup,
|
|
40
|
+
DropdownMenuPortal,
|
|
41
|
+
DropdownMenuSub,
|
|
42
|
+
DropdownMenuSubContent,
|
|
43
|
+
DropdownMenuSubTrigger,
|
|
44
|
+
DropdownMenuRadioGroup,
|
|
45
|
+
} from './dropdown-menu'
|
|
46
|
+
export { Tooltip, TooltipProvider } from './tooltip'
|
|
47
|
+
export { Toaster } from './sonner'
|
|
48
|
+
export { AutoResizeTextarea } from './AutoResizeTextarea'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Toaster as SonnerToaster } from "sonner"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/cn"
|
|
4
|
+
|
|
5
|
+
type ToasterProps = React.ComponentProps<typeof SonnerToaster>
|
|
6
|
+
|
|
7
|
+
export function Toaster({ className, ...props }: ToasterProps) {
|
|
8
|
+
return (
|
|
9
|
+
<SonnerToaster
|
|
10
|
+
className={cn(className)}
|
|
11
|
+
toastOptions={{
|
|
12
|
+
classNames: {
|
|
13
|
+
toast:
|
|
14
|
+
"group toast bg-card text-card-foreground border-border shadow-lg",
|
|
15
|
+
description: "text-muted-foreground",
|
|
16
|
+
actionButton: "bg-primary text-primary-foreground",
|
|
17
|
+
cancelButton: "bg-muted text-muted-foreground",
|
|
18
|
+
},
|
|
19
|
+
}}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../lib/cn";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Thin wrapper around @base-ui/react's Tooltip primitive that matches
|
|
8
|
+
* the rest of the shadcn-ish ui/ collection. Use <Tooltip> for any
|
|
9
|
+
* icon-only button so sighted users get hover feedback that mirrors
|
|
10
|
+
* the aria-label exposed to AT.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* <Tooltip label="Delete this opp">
|
|
14
|
+
* <button aria-label="Delete foo">...</button>
|
|
15
|
+
* </Tooltip>
|
|
16
|
+
*
|
|
17
|
+
* The child is rendered via base-ui's `render` prop so the existing
|
|
18
|
+
* button element (with its aria-label, classes, handlers) is preserved.
|
|
19
|
+
*/
|
|
20
|
+
const TooltipProvider = TooltipPrimitive.Provider;
|
|
21
|
+
|
|
22
|
+
interface TooltipProps {
|
|
23
|
+
/** Tooltip text. Should mirror the trigger's aria-label. */
|
|
24
|
+
label: React.ReactNode;
|
|
25
|
+
/** The interactive element to attach the tooltip to. */
|
|
26
|
+
children: React.ReactElement;
|
|
27
|
+
/** Open delay in ms. */
|
|
28
|
+
delay?: number;
|
|
29
|
+
/** Side of the trigger to place the popup. */
|
|
30
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
31
|
+
/** Offset (px) from the trigger. */
|
|
32
|
+
sideOffset?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function Tooltip({ label, children, delay = 300, side = "top", sideOffset = 6 }: TooltipProps) {
|
|
36
|
+
return (
|
|
37
|
+
<TooltipPrimitive.Root>
|
|
38
|
+
<TooltipPrimitive.Trigger delay={delay} render={children} />
|
|
39
|
+
<TooltipPrimitive.Portal>
|
|
40
|
+
<TooltipPrimitive.Positioner side={side} sideOffset={sideOffset}>
|
|
41
|
+
<TooltipPrimitive.Popup
|
|
42
|
+
className={cn(
|
|
43
|
+
"z-50 max-w-xs rounded-md border border-border bg-popover px-2 py-1 text-xs text-popover-foreground shadow-md",
|
|
44
|
+
"data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 transition-opacity",
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
{label}
|
|
48
|
+
</TooltipPrimitive.Popup>
|
|
49
|
+
</TooltipPrimitive.Positioner>
|
|
50
|
+
</TooltipPrimitive.Portal>
|
|
51
|
+
</TooltipPrimitive.Root>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { Tooltip, TooltipProvider };
|