@momentumworld/ui-react 0.17.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/chunk-2HQPCV2L.js +895 -0
- package/dist/chunk-2HQPCV2L.js.map +1 -0
- package/dist/form.d.ts +175 -0
- package/dist/form.js +356 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.ts +2162 -0
- package/dist/index.js +11640 -0
- package/dist/index.js.map +1 -0
- package/dist/input-JCZs61MO.d.ts +24 -0
- package/package.json +90 -0
- package/src/components/form/checkbox-group-field.tsx +101 -0
- package/src/components/form/date-field.tsx +79 -0
- package/src/components/form/date-range-field.tsx +106 -0
- package/src/components/form/form-context.ts +10 -0
- package/src/components/form/form.tsx +54 -0
- package/src/components/form/number-field.tsx +69 -0
- package/src/components/form/select-field.tsx +76 -0
- package/src/components/form/submit-button.tsx +28 -0
- package/src/components/form/text-field.tsx +107 -0
- package/src/components/layout/dashboard-header.tsx +54 -0
- package/src/components/layout/dashboard-panel.tsx +34 -0
- package/src/components/layout/dashboard-topbar.tsx +221 -0
- package/src/components/theme-provider.tsx +403 -0
- package/src/components/ui/accordion.tsx +69 -0
- package/src/components/ui/alert-dialog.tsx +169 -0
- package/src/components/ui/alert.tsx +80 -0
- package/src/components/ui/animated-counter.tsx +109 -0
- package/src/components/ui/animated-theme-toggler.tsx +265 -0
- package/src/components/ui/app-store-buttons.tsx +182 -0
- package/src/components/ui/aspect-ratio.tsx +23 -0
- package/src/components/ui/aurora-orb.tsx +163 -0
- package/src/components/ui/aurora-text.tsx +76 -0
- package/src/components/ui/autocomplete.tsx +296 -0
- package/src/components/ui/avatar-group.tsx +150 -0
- package/src/components/ui/avatar.tsx +285 -0
- package/src/components/ui/badge-group.tsx +160 -0
- package/src/components/ui/badge.tsx +172 -0
- package/src/components/ui/breadcrumb.tsx +112 -0
- package/src/components/ui/button.tsx +77 -0
- package/src/components/ui/calendar.tsx +137 -0
- package/src/components/ui/card.tsx +244 -0
- package/src/components/ui/carousel.tsx +258 -0
- package/src/components/ui/chart.tsx +379 -0
- package/src/components/ui/chat-divider.tsx +67 -0
- package/src/components/ui/chat.tsx +494 -0
- package/src/components/ui/checkbox-group.tsx +16 -0
- package/src/components/ui/checkbox.tsx +82 -0
- package/src/components/ui/collapsible.tsx +45 -0
- package/src/components/ui/color-picker.tsx +389 -0
- package/src/components/ui/combobox.tsx +411 -0
- package/src/components/ui/command.tsx +264 -0
- package/src/components/ui/confetti.tsx +196 -0
- package/src/components/ui/context-menu.tsx +271 -0
- package/src/components/ui/context-meter.tsx +134 -0
- package/src/components/ui/credit-card.tsx +214 -0
- package/src/components/ui/data-table-paged.tsx +163 -0
- package/src/components/ui/date-picker.tsx +154 -0
- package/src/components/ui/dialog.tsx +196 -0
- package/src/components/ui/drawer.tsx +135 -0
- package/src/components/ui/empty.tsx +127 -0
- package/src/components/ui/fab.tsx +45 -0
- package/src/components/ui/featured-icon.tsx +149 -0
- package/src/components/ui/field.tsx +88 -0
- package/src/components/ui/fieldset.tsx +29 -0
- package/src/components/ui/form.tsx +17 -0
- package/src/components/ui/frame.tsx +82 -0
- package/src/components/ui/generic-empty.tsx +142 -0
- package/src/components/ui/group.tsx +97 -0
- package/src/components/ui/help-tip.tsx +94 -0
- package/src/components/ui/horizontal-scroll-fader.tsx +228 -0
- package/src/components/ui/input-group.tsx +102 -0
- package/src/components/ui/input-otp.tsx +96 -0
- package/src/components/ui/input.tsx +66 -0
- package/src/components/ui/item.tsx +198 -0
- package/src/components/ui/kbd.tsx +30 -0
- package/src/components/ui/label.tsx +28 -0
- package/src/components/ui/markdown.tsx +188 -0
- package/src/components/ui/menu.tsx +312 -0
- package/src/components/ui/menubar.tsx +93 -0
- package/src/components/ui/meter.tsx +67 -0
- package/src/components/ui/multi-select.tsx +308 -0
- package/src/components/ui/navigation-menu.tsx +143 -0
- package/src/components/ui/number-field.tsx +160 -0
- package/src/components/ui/pagination-controls.tsx +74 -0
- package/src/components/ui/pagination.tsx +149 -0
- package/src/components/ui/pipeline.tsx +102 -0
- package/src/components/ui/popover.tsx +119 -0
- package/src/components/ui/preview-card.tsx +55 -0
- package/src/components/ui/progress.tsx +289 -0
- package/src/components/ui/qr-code.tsx +150 -0
- package/src/components/ui/radio-group.tsx +103 -0
- package/src/components/ui/resizable.tsx +56 -0
- package/src/components/ui/scroll-area.tsx +90 -0
- package/src/components/ui/scroller.tsx +38 -0
- package/src/components/ui/section-header.tsx +118 -0
- package/src/components/ui/segmented-control.tsx +83 -0
- package/src/components/ui/select.tsx +181 -0
- package/src/components/ui/separator.tsx +23 -0
- package/src/components/ui/sheet.tsx +224 -0
- package/src/components/ui/sidebar.tsx +774 -0
- package/src/components/ui/skeleton.tsx +16 -0
- package/src/components/ui/slider.tsx +108 -0
- package/src/components/ui/smooth-scroll.tsx +206 -0
- package/src/components/ui/social-button.tsx +247 -0
- package/src/components/ui/spinner-on-demand.tsx +32 -0
- package/src/components/ui/spinner.tsx +18 -0
- package/src/components/ui/stat.tsx +187 -0
- package/src/components/ui/stepper.tsx +167 -0
- package/src/components/ui/switch.tsx +56 -0
- package/src/components/ui/table.tsx +126 -0
- package/src/components/ui/tabs.tsx +90 -0
- package/src/components/ui/tag.tsx +242 -0
- package/src/components/ui/target-countdown.tsx +46 -0
- package/src/components/ui/text-editor.tsx +313 -0
- package/src/components/ui/textarea.tsx +51 -0
- package/src/components/ui/thinking-orb.tsx +6 -0
- package/src/components/ui/time-picker.tsx +308 -0
- package/src/components/ui/timeline.tsx +116 -0
- package/src/components/ui/toast.tsx +268 -0
- package/src/components/ui/toggle-group.tsx +101 -0
- package/src/components/ui/toggle.tsx +45 -0
- package/src/components/ui/toolbar.tsx +89 -0
- package/src/components/ui/tooltip.tsx +102 -0
- package/src/components/ui/vertical-scroll-fader.tsx +250 -0
- package/src/components/ui/video-player.tsx +275 -0
- package/src/components/upload/avatar-upload-base.tsx +131 -0
- package/src/components/upload/image-upload-base.tsx +112 -0
- package/src/form.ts +17 -0
- package/src/index.ts +143 -0
- package/src/lib/hooks/use-callback-ref.ts +15 -0
- package/src/lib/hooks/use-first-render.ts +11 -0
- package/src/lib/hooks/use-hover.ts +53 -0
- package/src/lib/hooks/use-is-tab-active.ts +17 -0
- package/src/lib/hooks/use-media-query.ts +164 -0
- package/src/lib/utils/css.ts +6 -0
- package/src/styles.css +329 -0
- package/src/types/helpers.ts +24 -0
- package/src/types/react.d.ts +7 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { ArrowRightIcon, ArrowUpIcon, SparklesIcon } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils/css";
|
|
8
|
+
import { Button } from "./button";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Chat primitives for AI-assistant experiences: a column layout, an
|
|
12
|
+
* auto-scrolling message viewport, role-styled message bubbles, header chrome,
|
|
13
|
+
* an empty-state hero with suggestion prompts, tool activity chips/cards,
|
|
14
|
+
* a typing indicator, and a submit-on-Enter composer. Purely presentational —
|
|
15
|
+
* bring your own state.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
function Chat({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
className={cn("flex min-h-0 flex-1 flex-col", className)}
|
|
22
|
+
data-slot="chat"
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Header chrome for a chat panel: avatar + title on the left, icon actions on
|
|
30
|
+
* the right. Compose `ChatHeaderAvatar`, `ChatHeaderTitle` and
|
|
31
|
+
* `ChatHeaderActions` inside.
|
|
32
|
+
*/
|
|
33
|
+
function ChatHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
className={cn(
|
|
37
|
+
"flex shrink-0 items-center gap-1 border-border/72 border-b bg-background/95 px-3 py-2.5 backdrop-blur supports-backdrop-filter:bg-background/80",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
data-slot="chat-header"
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ChatHeaderAvatar({
|
|
47
|
+
className,
|
|
48
|
+
children,
|
|
49
|
+
...props
|
|
50
|
+
}: React.ComponentProps<"span">) {
|
|
51
|
+
return (
|
|
52
|
+
<span
|
|
53
|
+
className={cn(
|
|
54
|
+
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-linear-to-br from-primary to-primary/78 text-primary-foreground shadow-primary/24 shadow-xs after:absolute after:right-0 after:bottom-0 after:size-2 after:rounded-full after:bg-success after:ring-2 after:ring-background [&_svg:not([class*='size-'])]:size-4",
|
|
55
|
+
className,
|
|
56
|
+
)}
|
|
57
|
+
data-slot="chat-header-avatar"
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
{children ?? <SparklesIcon />}
|
|
61
|
+
</span>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function ChatHeaderTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
className={cn("min-w-0 flex-1 leading-tight", className)}
|
|
69
|
+
data-slot="chat-header-title"
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function ChatHeaderActions({ className, ...props }: React.ComponentProps<"div">) {
|
|
76
|
+
return (
|
|
77
|
+
<div
|
|
78
|
+
className={cn("flex shrink-0 items-center gap-0.5", className)}
|
|
79
|
+
data-slot="chat-header-actions"
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function ChatMessages({
|
|
86
|
+
autoScroll = true,
|
|
87
|
+
className,
|
|
88
|
+
children,
|
|
89
|
+
...props
|
|
90
|
+
}: React.ComponentProps<"div"> & {
|
|
91
|
+
/** Keep the viewport pinned to the bottom while new messages stream in. */
|
|
92
|
+
autoScroll?: boolean;
|
|
93
|
+
}) {
|
|
94
|
+
const viewportRef = React.useRef<HTMLDivElement>(null);
|
|
95
|
+
const pinnedRef = React.useRef(true);
|
|
96
|
+
|
|
97
|
+
const handleScroll = React.useCallback(() => {
|
|
98
|
+
const viewport = viewportRef.current;
|
|
99
|
+
if (!viewport) return;
|
|
100
|
+
const distanceFromBottom =
|
|
101
|
+
viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
|
|
102
|
+
pinnedRef.current = distanceFromBottom < 48;
|
|
103
|
+
}, []);
|
|
104
|
+
|
|
105
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: children is the scroll trigger
|
|
106
|
+
React.useEffect(() => {
|
|
107
|
+
const viewport = viewportRef.current;
|
|
108
|
+
if (!viewport || !autoScroll || !pinnedRef.current) return;
|
|
109
|
+
viewport.scrollTop = viewport.scrollHeight;
|
|
110
|
+
}, [children, autoScroll]);
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div
|
|
114
|
+
className={cn(
|
|
115
|
+
"flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto scroll-smooth p-4",
|
|
116
|
+
className,
|
|
117
|
+
)}
|
|
118
|
+
data-slot="chat-messages"
|
|
119
|
+
onScroll={handleScroll}
|
|
120
|
+
ref={viewportRef}
|
|
121
|
+
{...props}
|
|
122
|
+
>
|
|
123
|
+
{children}
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Centered hero shown when the conversation is empty. Compose
|
|
130
|
+
* `ChatEmptyStateIcon`, `ChatEmptyStateTitle`, `ChatEmptyStateDescription`
|
|
131
|
+
* and `ChatSuggestions` inside.
|
|
132
|
+
*/
|
|
133
|
+
function ChatEmptyState({ className, ...props }: React.ComponentProps<"div">) {
|
|
134
|
+
return (
|
|
135
|
+
<div
|
|
136
|
+
className={cn(
|
|
137
|
+
"flex flex-1 flex-col items-center justify-center gap-3 p-6 text-center",
|
|
138
|
+
className,
|
|
139
|
+
)}
|
|
140
|
+
data-slot="chat-empty-state"
|
|
141
|
+
{...props}
|
|
142
|
+
/>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function ChatEmptyStateIcon({
|
|
147
|
+
className,
|
|
148
|
+
children,
|
|
149
|
+
...props
|
|
150
|
+
}: React.ComponentProps<"span">) {
|
|
151
|
+
return (
|
|
152
|
+
<span
|
|
153
|
+
className={cn(
|
|
154
|
+
"fade-in-0 zoom-in-90 relative flex size-13 shrink-0 animate-in items-center justify-center rounded-2xl bg-linear-to-br from-primary to-primary/78 text-primary-foreground shadow-lg shadow-primary/24 duration-500 before:absolute before:-inset-2.5 before:-z-10 before:rounded-3xl before:bg-primary/8 before:blur-lg [&_svg:not([class*='size-'])]:size-6",
|
|
155
|
+
className,
|
|
156
|
+
)}
|
|
157
|
+
data-slot="chat-empty-state-icon"
|
|
158
|
+
{...props}
|
|
159
|
+
>
|
|
160
|
+
{children ?? <SparklesIcon />}
|
|
161
|
+
</span>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function ChatEmptyStateTitle({ className, ...props }: React.ComponentProps<"p">) {
|
|
166
|
+
return (
|
|
167
|
+
<p
|
|
168
|
+
className={cn("mt-1 font-semibold text-base text-foreground", className)}
|
|
169
|
+
data-slot="chat-empty-state-title"
|
|
170
|
+
{...props}
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function ChatEmptyStateDescription({
|
|
176
|
+
className,
|
|
177
|
+
...props
|
|
178
|
+
}: React.ComponentProps<"p">) {
|
|
179
|
+
return (
|
|
180
|
+
<p
|
|
181
|
+
className={cn(
|
|
182
|
+
"max-w-72 text-muted-foreground text-sm leading-relaxed",
|
|
183
|
+
className,
|
|
184
|
+
)}
|
|
185
|
+
data-slot="chat-empty-state-description"
|
|
186
|
+
{...props}
|
|
187
|
+
/>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Vertical stack of `ChatSuggestion` prompts. */
|
|
192
|
+
function ChatSuggestions({ className, ...props }: React.ComponentProps<"div">) {
|
|
193
|
+
return (
|
|
194
|
+
<div
|
|
195
|
+
className={cn("mt-2 flex w-full max-w-sm flex-col gap-2", className)}
|
|
196
|
+
data-slot="chat-suggestions"
|
|
197
|
+
{...props}
|
|
198
|
+
/>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface ChatSuggestionProps extends React.ComponentProps<"button"> {
|
|
203
|
+
/** Leading icon; defaults to a sparkle. */
|
|
204
|
+
icon?: React.ReactNode;
|
|
205
|
+
/** Position in the list — staggers the entrance animation. */
|
|
206
|
+
index?: number;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* One tappable suggested prompt: icon chip + label, with an arrow that slides
|
|
211
|
+
* in on hover and a staggered fade-in entrance (via `index`).
|
|
212
|
+
*/
|
|
213
|
+
function ChatSuggestion({
|
|
214
|
+
className,
|
|
215
|
+
children,
|
|
216
|
+
icon,
|
|
217
|
+
index = 0,
|
|
218
|
+
style,
|
|
219
|
+
...props
|
|
220
|
+
}: ChatSuggestionProps) {
|
|
221
|
+
return (
|
|
222
|
+
<button
|
|
223
|
+
className={cn(
|
|
224
|
+
"group fade-in-0 slide-in-from-bottom-2 flex w-full animate-in cursor-pointer items-center gap-2.5 fill-mode-both rounded-xl border border-border/72 bg-card px-3 py-2.5 text-left font-medium text-card-foreground text-sm shadow-xs outline-none transition-[border-color,background-color,box-shadow,transform] duration-200 hover:border-primary/24 hover:bg-accent/56 hover:shadow-sm focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background active:scale-[0.985] disabled:pointer-events-none disabled:opacity-64",
|
|
225
|
+
className,
|
|
226
|
+
)}
|
|
227
|
+
data-slot="chat-suggestion"
|
|
228
|
+
style={{ animationDelay: `${index * 80}ms`, ...style }}
|
|
229
|
+
type="button"
|
|
230
|
+
{...props}
|
|
231
|
+
>
|
|
232
|
+
<span className="flex size-6.5 shrink-0 items-center justify-center rounded-lg bg-primary/8 text-primary transition-colors duration-200 group-hover:bg-primary group-hover:text-primary-foreground [&_svg:not([class*='size-'])]:size-3.5">
|
|
233
|
+
{icon ?? <SparklesIcon />}
|
|
234
|
+
</span>
|
|
235
|
+
<span className="min-w-0 flex-1">{children}</span>
|
|
236
|
+
<ArrowRightIcon className="-translate-x-1 size-3.5 shrink-0 text-muted-foreground opacity-0 transition-[opacity,translate] duration-200 group-hover:translate-x-0 group-hover:opacity-100" />
|
|
237
|
+
</button>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const chatMessageVariants = cva(
|
|
242
|
+
"fade-in-0 slide-in-from-bottom-1 flex w-full animate-in flex-col gap-1 duration-200",
|
|
243
|
+
{
|
|
244
|
+
defaultVariants: {
|
|
245
|
+
from: "assistant",
|
|
246
|
+
},
|
|
247
|
+
variants: {
|
|
248
|
+
from: {
|
|
249
|
+
assistant: "items-start",
|
|
250
|
+
user: "items-end",
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
export interface ChatMessageProps
|
|
257
|
+
extends React.ComponentProps<"div">,
|
|
258
|
+
VariantProps<typeof chatMessageVariants> {}
|
|
259
|
+
|
|
260
|
+
function ChatMessage({ className, from, ...props }: ChatMessageProps) {
|
|
261
|
+
return (
|
|
262
|
+
<div
|
|
263
|
+
className={cn(chatMessageVariants({ className, from }))}
|
|
264
|
+
data-from={from ?? "assistant"}
|
|
265
|
+
data-slot="chat-message"
|
|
266
|
+
{...props}
|
|
267
|
+
/>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const chatMessageBubbleVariants = cva(
|
|
272
|
+
"max-w-[85%] rounded-2xl px-3.5 py-2 text-sm leading-relaxed break-words",
|
|
273
|
+
{
|
|
274
|
+
defaultVariants: {
|
|
275
|
+
from: "assistant",
|
|
276
|
+
},
|
|
277
|
+
variants: {
|
|
278
|
+
from: {
|
|
279
|
+
assistant: "rounded-bl-md bg-muted text-foreground",
|
|
280
|
+
user: "rounded-br-md bg-primary text-primary-foreground shadow-primary/16 shadow-xs",
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
export interface ChatMessageBubbleProps
|
|
287
|
+
extends React.ComponentProps<"div">,
|
|
288
|
+
VariantProps<typeof chatMessageBubbleVariants> {}
|
|
289
|
+
|
|
290
|
+
function ChatMessageBubble({ className, from, ...props }: ChatMessageBubbleProps) {
|
|
291
|
+
return (
|
|
292
|
+
<div
|
|
293
|
+
className={cn(chatMessageBubbleVariants({ className, from }))}
|
|
294
|
+
data-from={from ?? "assistant"}
|
|
295
|
+
data-slot="chat-message-bubble"
|
|
296
|
+
{...props}
|
|
297
|
+
/>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Small inline pill for tool activity ("Using X…", "X finished"). Pass the
|
|
303
|
+
* status icon (wrench, spinner, check) as `icon`.
|
|
304
|
+
*/
|
|
305
|
+
function ChatToolChip({
|
|
306
|
+
className,
|
|
307
|
+
children,
|
|
308
|
+
icon,
|
|
309
|
+
...props
|
|
310
|
+
}: React.ComponentProps<"div"> & { icon?: React.ReactNode }) {
|
|
311
|
+
return (
|
|
312
|
+
<div
|
|
313
|
+
className={cn(
|
|
314
|
+
"fade-in-0 flex w-fit max-w-full animate-in items-center gap-1.5 rounded-full border border-border/72 bg-muted/48 py-1 pr-3 pl-1.5 text-muted-foreground text-xs duration-200",
|
|
315
|
+
className,
|
|
316
|
+
)}
|
|
317
|
+
data-slot="chat-tool-chip"
|
|
318
|
+
{...props}
|
|
319
|
+
>
|
|
320
|
+
{icon ? (
|
|
321
|
+
<span className="flex size-4.5 shrink-0 items-center justify-center rounded-full bg-background text-foreground/72 shadow-xs [&_svg:not([class*='size-'])]:size-2.5">
|
|
322
|
+
{icon}
|
|
323
|
+
</span>
|
|
324
|
+
) : null}
|
|
325
|
+
<span className="min-w-0 truncate">{children}</span>
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Framed card for tool activity inside a conversation: running/finished tool
|
|
332
|
+
* calls, results, and write-action approval prompts. Compose with `Button`s
|
|
333
|
+
* in the `actions` area for approve/decline flows.
|
|
334
|
+
*/
|
|
335
|
+
function ChatToolCard({ className, ...props }: React.ComponentProps<"div">) {
|
|
336
|
+
return (
|
|
337
|
+
<div
|
|
338
|
+
className={cn(
|
|
339
|
+
"fade-in-0 slide-in-from-bottom-1 w-full max-w-[95%] animate-in rounded-xl border border-border bg-card p-3 text-card-foreground text-sm shadow-xs duration-200",
|
|
340
|
+
className,
|
|
341
|
+
)}
|
|
342
|
+
data-slot="chat-tool-card"
|
|
343
|
+
{...props}
|
|
344
|
+
/>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function ChatToolCardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
349
|
+
return (
|
|
350
|
+
<div
|
|
351
|
+
className={cn("flex items-center gap-2 font-medium", className)}
|
|
352
|
+
data-slot="chat-tool-card-header"
|
|
353
|
+
{...props}
|
|
354
|
+
/>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function ChatToolCardActions({ className, ...props }: React.ComponentProps<"div">) {
|
|
359
|
+
return (
|
|
360
|
+
<div
|
|
361
|
+
className={cn("mt-3 flex items-center justify-end gap-2", className)}
|
|
362
|
+
data-slot="chat-tool-card-actions"
|
|
363
|
+
{...props}
|
|
364
|
+
/>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function ChatTypingIndicator({ className, ...props }: React.ComponentProps<"div">) {
|
|
369
|
+
return (
|
|
370
|
+
<div
|
|
371
|
+
aria-label="Assistant is typing"
|
|
372
|
+
className={cn(
|
|
373
|
+
"fade-in-0 flex w-fit animate-in items-center gap-1 rounded-2xl rounded-bl-md bg-muted px-3.5 py-2.5 duration-200",
|
|
374
|
+
className,
|
|
375
|
+
)}
|
|
376
|
+
data-slot="chat-typing-indicator"
|
|
377
|
+
role="status"
|
|
378
|
+
{...props}
|
|
379
|
+
>
|
|
380
|
+
<span className="size-1.5 animate-bounce rounded-full bg-muted-foreground/60 [animation-delay:0ms]" />
|
|
381
|
+
<span className="size-1.5 animate-bounce rounded-full bg-muted-foreground/60 [animation-delay:150ms]" />
|
|
382
|
+
<span className="size-1.5 animate-bounce rounded-full bg-muted-foreground/60 [animation-delay:300ms]" />
|
|
383
|
+
</div>
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export interface ChatInputProps
|
|
388
|
+
extends Omit<React.ComponentProps<"textarea">, "onSubmit" | "value" | "onChange"> {
|
|
389
|
+
value: string;
|
|
390
|
+
onValueChange: (value: string) => void;
|
|
391
|
+
/** Called with the trimmed message when the user submits (Enter or button). */
|
|
392
|
+
onSubmit: (value: string) => void;
|
|
393
|
+
/** Disables both the textarea and the send button (e.g. while sending). */
|
|
394
|
+
disabled?: boolean;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Composer: a rounded card wrapping an auto-growing textarea and a round send
|
|
399
|
+
* button. Enter submits, Shift+Enter inserts a newline.
|
|
400
|
+
*/
|
|
401
|
+
function ChatInput({
|
|
402
|
+
className,
|
|
403
|
+
disabled,
|
|
404
|
+
onSubmit,
|
|
405
|
+
onValueChange,
|
|
406
|
+
placeholder = "Ask anything…",
|
|
407
|
+
value,
|
|
408
|
+
...props
|
|
409
|
+
}: ChatInputProps) {
|
|
410
|
+
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
|
|
411
|
+
|
|
412
|
+
const submit = React.useCallback(() => {
|
|
413
|
+
const trimmed = value.trim();
|
|
414
|
+
if (!trimmed || disabled) return;
|
|
415
|
+
onSubmit(trimmed);
|
|
416
|
+
}, [disabled, onSubmit, value]);
|
|
417
|
+
|
|
418
|
+
// Auto-grow up to ~6 lines.
|
|
419
|
+
React.useEffect(() => {
|
|
420
|
+
const textarea = textareaRef.current;
|
|
421
|
+
if (!textarea) return;
|
|
422
|
+
textarea.style.height = "auto";
|
|
423
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, 144)}px`;
|
|
424
|
+
}, [value]);
|
|
425
|
+
|
|
426
|
+
return (
|
|
427
|
+
<form
|
|
428
|
+
className={cn("shrink-0 bg-background p-3 pt-1.5", className)}
|
|
429
|
+
data-slot="chat-input"
|
|
430
|
+
onSubmit={(event) => {
|
|
431
|
+
event.preventDefault();
|
|
432
|
+
submit();
|
|
433
|
+
}}
|
|
434
|
+
>
|
|
435
|
+
<div
|
|
436
|
+
className={cn(
|
|
437
|
+
"flex items-end gap-1.5 rounded-2xl border border-input bg-popover p-1.5 pl-3.5 shadow-xs transition-[border-color,box-shadow] duration-150 focus-within:border-ring/64 focus-within:ring-2 focus-within:ring-ring/24 dark:bg-input/32",
|
|
438
|
+
disabled && "opacity-64",
|
|
439
|
+
)}
|
|
440
|
+
>
|
|
441
|
+
<textarea
|
|
442
|
+
className="max-h-36 min-h-8 flex-1 resize-none self-center bg-transparent py-1.5 text-sm outline-none placeholder:text-muted-foreground"
|
|
443
|
+
disabled={disabled}
|
|
444
|
+
onChange={(event) => onValueChange(event.target.value)}
|
|
445
|
+
onKeyDown={(event) => {
|
|
446
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
447
|
+
event.preventDefault();
|
|
448
|
+
submit();
|
|
449
|
+
}
|
|
450
|
+
}}
|
|
451
|
+
placeholder={placeholder}
|
|
452
|
+
ref={textareaRef}
|
|
453
|
+
rows={1}
|
|
454
|
+
value={value}
|
|
455
|
+
{...props}
|
|
456
|
+
/>
|
|
457
|
+
<Button
|
|
458
|
+
aria-label="Send message"
|
|
459
|
+
className="rounded-full before:rounded-full"
|
|
460
|
+
disabled={disabled || !value.trim()}
|
|
461
|
+
size="icon-sm"
|
|
462
|
+
type="submit"
|
|
463
|
+
>
|
|
464
|
+
<ArrowUpIcon />
|
|
465
|
+
</Button>
|
|
466
|
+
</div>
|
|
467
|
+
</form>
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export {
|
|
472
|
+
Chat,
|
|
473
|
+
ChatEmptyState,
|
|
474
|
+
ChatEmptyStateDescription,
|
|
475
|
+
ChatEmptyStateIcon,
|
|
476
|
+
ChatEmptyStateTitle,
|
|
477
|
+
ChatHeader,
|
|
478
|
+
ChatHeaderActions,
|
|
479
|
+
ChatHeaderAvatar,
|
|
480
|
+
ChatHeaderTitle,
|
|
481
|
+
ChatInput,
|
|
482
|
+
ChatMessage,
|
|
483
|
+
ChatMessageBubble,
|
|
484
|
+
chatMessageBubbleVariants,
|
|
485
|
+
chatMessageVariants,
|
|
486
|
+
ChatMessages,
|
|
487
|
+
ChatSuggestion,
|
|
488
|
+
ChatSuggestions,
|
|
489
|
+
ChatToolCard,
|
|
490
|
+
ChatToolCardActions,
|
|
491
|
+
ChatToolCardHeader,
|
|
492
|
+
ChatToolChip,
|
|
493
|
+
ChatTypingIndicator,
|
|
494
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { CheckboxGroup as CheckboxGroupPrimitive } from "@base-ui/react/checkbox-group";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils/css";
|
|
6
|
+
|
|
7
|
+
function CheckboxGroup({ className, ...props }: CheckboxGroupPrimitive.Props) {
|
|
8
|
+
return (
|
|
9
|
+
<CheckboxGroupPrimitive
|
|
10
|
+
className={cn("flex flex-col items-start gap-3", className)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { CheckboxGroup };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils/css";
|
|
6
|
+
|
|
7
|
+
const checkboxSizes = {
|
|
8
|
+
sm: { box: "size-4.5 sm:size-4", icon: "size-3.5 sm:size-3" },
|
|
9
|
+
md: { box: "size-5", icon: "size-4" },
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
interface CheckboxProps extends CheckboxPrimitive.Root.Props {
|
|
13
|
+
/** Visual size of the checkbox. Defaults to "sm". */
|
|
14
|
+
size?: keyof typeof checkboxSizes;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function Checkbox({ className, size = "sm", ...props }: CheckboxProps) {
|
|
18
|
+
const sizes = checkboxSizes[size];
|
|
19
|
+
return (
|
|
20
|
+
<CheckboxPrimitive.Root
|
|
21
|
+
className={cn(
|
|
22
|
+
"relative inline-flex shrink-0 items-center justify-center rounded-[4px] border border-input bg-background not-dark:bg-clip-padding shadow-xs/5 outline-none ring-ring transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[3px] not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/6%)] focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-offset-background aria-invalid:border-destructive/36 focus-visible:aria-invalid:border-destructive/64 focus-visible:aria-invalid:ring-destructive/48 data-disabled:opacity-64 dark:not-data-checked:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:not-data-disabled:not-data-checked:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] [[data-disabled],[data-checked],[aria-invalid]]:shadow-none",
|
|
23
|
+
sizes.box,
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
data-slot="checkbox"
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
<CheckboxPrimitive.Indicator
|
|
30
|
+
className="-inset-px absolute flex items-center justify-center rounded-[4px] text-primary-foreground transition-opacity duration-150 ease-out data-checked:bg-primary data-checked:opacity-100 data-indeterminate:text-foreground data-indeterminate:opacity-100 data-unchecked:opacity-0"
|
|
31
|
+
data-slot="checkbox-indicator"
|
|
32
|
+
keepMounted
|
|
33
|
+
render={(props, state) => (
|
|
34
|
+
<span {...props}>
|
|
35
|
+
{state.indeterminate ? (
|
|
36
|
+
// biome-ignore lint/a11y/noSvgWithoutTitle: Imported from library
|
|
37
|
+
<svg
|
|
38
|
+
className={sizes.icon}
|
|
39
|
+
fill="none"
|
|
40
|
+
height="24"
|
|
41
|
+
stroke="currentColor"
|
|
42
|
+
strokeLinecap="round"
|
|
43
|
+
strokeLinejoin="round"
|
|
44
|
+
strokeWidth="3"
|
|
45
|
+
viewBox="0 0 24 24"
|
|
46
|
+
width="24"
|
|
47
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
48
|
+
>
|
|
49
|
+
<path d="M5.252 12h13.496" />
|
|
50
|
+
</svg>
|
|
51
|
+
) : (
|
|
52
|
+
// biome-ignore lint/a11y/noSvgWithoutTitle: Imported from library
|
|
53
|
+
<svg
|
|
54
|
+
className={sizes.icon}
|
|
55
|
+
fill="none"
|
|
56
|
+
height="24"
|
|
57
|
+
stroke="currentColor"
|
|
58
|
+
strokeLinecap="round"
|
|
59
|
+
strokeLinejoin="round"
|
|
60
|
+
strokeWidth="3"
|
|
61
|
+
viewBox="0 0 24 24"
|
|
62
|
+
width="24"
|
|
63
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
64
|
+
>
|
|
65
|
+
{/* The checkmark draws itself in: pathLength normalises the
|
|
66
|
+
stroke to 1 unit, and the dashoffset animates to 0 once the
|
|
67
|
+
box is checked (a beat after the fill fades in). */}
|
|
68
|
+
<path
|
|
69
|
+
className="[stroke-dasharray:1] [stroke-dashoffset:1] [transition:stroke-dashoffset_250ms_cubic-bezier(0.65,0,0.35,1)_110ms] in-[[data-checked]]:[stroke-dashoffset:0]"
|
|
70
|
+
d="M5.252 12.7 10.2 18.63 18.748 5.37"
|
|
71
|
+
pathLength={1}
|
|
72
|
+
/>
|
|
73
|
+
</svg>
|
|
74
|
+
)}
|
|
75
|
+
</span>
|
|
76
|
+
)}
|
|
77
|
+
/>
|
|
78
|
+
</CheckboxPrimitive.Root>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { Checkbox };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils/css";
|
|
6
|
+
|
|
7
|
+
function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
|
|
8
|
+
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function CollapsibleTrigger({
|
|
12
|
+
className,
|
|
13
|
+
...props
|
|
14
|
+
}: CollapsiblePrimitive.Trigger.Props) {
|
|
15
|
+
return (
|
|
16
|
+
<CollapsiblePrimitive.Trigger
|
|
17
|
+
className={cn("cursor-pointer", className)}
|
|
18
|
+
data-slot="collapsible-trigger"
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function CollapsiblePanel({
|
|
25
|
+
className,
|
|
26
|
+
...props
|
|
27
|
+
}: CollapsiblePrimitive.Panel.Props) {
|
|
28
|
+
return (
|
|
29
|
+
<CollapsiblePrimitive.Panel
|
|
30
|
+
className={cn(
|
|
31
|
+
"h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-200 data-ending-style:h-0 data-starting-style:h-0",
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
data-slot="collapsible-panel"
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
Collapsible,
|
|
42
|
+
CollapsibleTrigger,
|
|
43
|
+
CollapsiblePanel,
|
|
44
|
+
CollapsiblePanel as CollapsibleContent,
|
|
45
|
+
};
|