@cortexasystem/ui 0.1.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1326 -643
- package/dist/index.d.cts +171 -13
- package/dist/index.d.ts +171 -13
- package/dist/index.js +1307 -646
- package/package.json +3 -3
- package/src/assets/isotipo-cortexa-dark.png +0 -0
- package/src/assets/isotipo-cortexa-light.png +0 -0
- package/src/components/ai/ai-chat.tsx +597 -0
- package/src/components/branding/brand-logo.tsx +77 -0
- package/src/components/data-display/icons.tsx +81 -0
- package/src/components/data-display/profile-avatar.tsx +154 -0
- package/src/components/data-display/typography.tsx +46 -0
- package/src/components/feedback/empty-state.tsx +63 -0
- package/src/components/feedback/loading-state.tsx +93 -0
- package/src/components/feedback/module-skeleton.tsx +76 -0
- package/src/components/feedback/notification.tsx +111 -0
- package/src/components/feedback/skeleton.tsx +9 -0
- package/src/components/feedback/spinner.tsx +18 -0
- package/src/components/feedback/status-badge.tsx +44 -0
- package/src/components/feedback/sync-status-badge.tsx +54 -0
- package/src/components/feedback/sync-status-bar.tsx +92 -0
- package/src/components/feedback/toaster.tsx +36 -0
- package/src/components/forms/searchable-select.tsx +206 -0
- package/src/components/forms/select.tsx +142 -0
- package/src/components/layout/app-shell.tsx +44 -0
- package/src/components/layout/form-section.tsx +21 -0
- package/src/components/layout/page-header.tsx +21 -0
- package/src/components/layout/theme-toggle.tsx +33 -0
- package/src/components/navigation/breadcrumb.tsx +87 -0
- package/src/components/navigation/header-user-menu.tsx +108 -0
- package/src/components/navigation/navbar.tsx +30 -0
- package/src/components/navigation/page-breadcrumb.tsx +44 -0
- package/src/components/navigation/sidebar.tsx +104 -0
- package/src/components/navigation/steps.tsx +82 -0
- package/src/components/overlays/dialog.tsx +94 -0
- package/src/components/overlays/drawer.tsx +85 -0
- package/src/components/overlays/dropdown-menu.tsx +179 -0
- package/src/components/overlays/sheet.tsx +110 -0
- package/src/components/primitives/alert.tsx +43 -0
- package/src/components/primitives/avatar.tsx +41 -0
- package/src/components/primitives/badge.tsx +26 -0
- package/src/components/primitives/button.tsx +49 -0
- package/src/components/primitives/card.tsx +97 -0
- package/src/components/primitives/checkbox.tsx +52 -0
- package/src/components/primitives/input.tsx +23 -0
- package/src/components/primitives/label.tsx +18 -0
- package/src/components/primitives/radio-group.tsx +57 -0
- package/src/components/primitives/separator.tsx +23 -0
- package/src/components/primitives/switch.tsx +75 -0
- package/src/components/primitives/textarea.tsx +18 -0
- package/src/components/tables/data-table.tsx +214 -0
- package/src/components/tables/data-table.types.ts +9 -0
- package/src/components/tables/table-row-actions.tsx +61 -0
- package/src/components/tables/table.tsx +88 -0
- package/src/declarations.d.ts +14 -0
- package/src/index.ts +50 -0
- package/src/lib/cn.ts +6 -0
- package/src/providers/theme-provider.tsx +90 -0
- package/src/styles.css +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexasystem/ui",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
|
-
"src
|
|
18
|
+
"src"
|
|
19
19
|
],
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"sonner": "^2.0.7",
|
|
43
43
|
"tailwind-merge": "^3.5.0",
|
|
44
44
|
"vaul": "^1.1.2",
|
|
45
|
-
"@cortexasystem/tokens": "0.1.
|
|
45
|
+
"@cortexasystem/tokens": "0.1.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/react": "^19.2.14",
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Bot, Clock3, MessageSquareText, PanelRightOpen, Send, Sparkles, type LucideIcon } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../../lib/cn";
|
|
5
|
+
import { Sheet, SheetContent, SheetTrigger } from "../overlays/sheet";
|
|
6
|
+
import { Badge } from "../primitives/badge";
|
|
7
|
+
import { Button, type ButtonProps } from "../primitives/button";
|
|
8
|
+
import { Textarea } from "../primitives/textarea";
|
|
9
|
+
|
|
10
|
+
type AiChatLayoutVariant = "screen" | "sidebar";
|
|
11
|
+
type AiChatRole = "assistant" | "user" | "system";
|
|
12
|
+
type AiChatMessageStatus = "complete" | "streaming" | "error";
|
|
13
|
+
|
|
14
|
+
interface AiChatLayoutProps extends React.HTMLAttributes<HTMLElement> {
|
|
15
|
+
variant?: AiChatLayoutVariant;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function AiChatLayout({ className, variant = "screen", ...props }: AiChatLayoutProps) {
|
|
19
|
+
return (
|
|
20
|
+
<section
|
|
21
|
+
className={cn(
|
|
22
|
+
"overflow-hidden rounded-2xl border bg-card text-card-foreground shadow-sm",
|
|
23
|
+
variant === "screen"
|
|
24
|
+
? "grid min-h-[720px] w-full lg:grid-cols-[320px_minmax(0,1fr)]"
|
|
25
|
+
: "flex h-full min-h-0 w-full flex-col",
|
|
26
|
+
className
|
|
27
|
+
)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const AiChatSidebar = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
34
|
+
({ className, ...props }, ref) => (
|
|
35
|
+
<aside
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={cn(
|
|
38
|
+
"flex min-h-0 flex-col border-b bg-muted/20 lg:border-b-0 lg:border-r",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
AiChatSidebar.displayName = "AiChatSidebar";
|
|
47
|
+
|
|
48
|
+
interface AiChatSidebarHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
49
|
+
title: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
action?: React.ReactNode;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function AiChatSidebarHeader({ className, title, description, action, ...props }: AiChatSidebarHeaderProps) {
|
|
55
|
+
return (
|
|
56
|
+
<div className={cn("border-b px-4 py-4", className)} {...props}>
|
|
57
|
+
<div className="flex items-start justify-between gap-3">
|
|
58
|
+
<div className="grid gap-1">
|
|
59
|
+
<p className="text-sm font-semibold text-foreground">{title}</p>
|
|
60
|
+
{description ? <p className="text-xs text-muted-foreground">{description}</p> : null}
|
|
61
|
+
</div>
|
|
62
|
+
{action ? <div className="shrink-0">{action}</div> : null}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const AiChatThreadList = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
69
|
+
({ className, ...props }, ref) => (
|
|
70
|
+
<div ref={ref} className={cn("flex-1 space-y-2 overflow-y-auto p-3", className)} {...props} />
|
|
71
|
+
)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
AiChatThreadList.displayName = "AiChatThreadList";
|
|
75
|
+
|
|
76
|
+
interface AiChatThreadButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
77
|
+
title: string;
|
|
78
|
+
preview?: string;
|
|
79
|
+
timestamp?: string;
|
|
80
|
+
active?: boolean;
|
|
81
|
+
unreadCount?: number;
|
|
82
|
+
icon?: LucideIcon;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const AiChatThreadButton = React.forwardRef<HTMLButtonElement, AiChatThreadButtonProps>(
|
|
86
|
+
(
|
|
87
|
+
{
|
|
88
|
+
className,
|
|
89
|
+
title,
|
|
90
|
+
preview,
|
|
91
|
+
timestamp,
|
|
92
|
+
active = false,
|
|
93
|
+
unreadCount,
|
|
94
|
+
icon: Icon = MessageSquareText,
|
|
95
|
+
type = "button",
|
|
96
|
+
...props
|
|
97
|
+
},
|
|
98
|
+
ref
|
|
99
|
+
) => (
|
|
100
|
+
<button
|
|
101
|
+
ref={ref}
|
|
102
|
+
type={type}
|
|
103
|
+
className={cn(
|
|
104
|
+
"flex w-full items-start gap-3 rounded-xl border px-3 py-3 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
105
|
+
active
|
|
106
|
+
? "border-[var(--color-accent-blue)]/40 bg-[var(--color-accent-blue)]/8"
|
|
107
|
+
: "border-transparent bg-transparent hover:border-border hover:bg-accent/40",
|
|
108
|
+
className
|
|
109
|
+
)}
|
|
110
|
+
{...props}
|
|
111
|
+
>
|
|
112
|
+
<span
|
|
113
|
+
className={cn(
|
|
114
|
+
"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border",
|
|
115
|
+
active
|
|
116
|
+
? "border-[var(--color-accent-blue)]/30 bg-[var(--color-accent-blue)]/12 text-[var(--color-accent-blue)]"
|
|
117
|
+
: "border-border bg-background text-muted-foreground"
|
|
118
|
+
)}
|
|
119
|
+
>
|
|
120
|
+
<Icon className="h-4 w-4" />
|
|
121
|
+
</span>
|
|
122
|
+
|
|
123
|
+
<span className="grid min-w-0 flex-1 gap-1">
|
|
124
|
+
<span className="flex items-start justify-between gap-3">
|
|
125
|
+
<span className="truncate text-sm font-medium text-foreground">{title}</span>
|
|
126
|
+
{timestamp ? (
|
|
127
|
+
<span className="inline-flex shrink-0 items-center gap-1 text-[11px] text-muted-foreground">
|
|
128
|
+
<Clock3 className="h-3 w-3" />
|
|
129
|
+
{timestamp}
|
|
130
|
+
</span>
|
|
131
|
+
) : null}
|
|
132
|
+
</span>
|
|
133
|
+
{preview ? <span className="line-clamp-2 text-xs text-muted-foreground">{preview}</span> : null}
|
|
134
|
+
</span>
|
|
135
|
+
|
|
136
|
+
{typeof unreadCount === "number" && unreadCount > 0 ? (
|
|
137
|
+
<Badge className="shrink-0 rounded-full px-2 py-0.5 text-[11px]">{unreadCount > 9 ? "9+" : unreadCount}</Badge>
|
|
138
|
+
) : null}
|
|
139
|
+
</button>
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
AiChatThreadButton.displayName = "AiChatThreadButton";
|
|
144
|
+
|
|
145
|
+
const AiChatPanel = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
146
|
+
({ className, ...props }, ref) => (
|
|
147
|
+
<div ref={ref} className={cn("flex min-h-0 flex-1 flex-col bg-background/40", className)} {...props} />
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
AiChatPanel.displayName = "AiChatPanel";
|
|
152
|
+
|
|
153
|
+
interface AiChatHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
154
|
+
title: string;
|
|
155
|
+
description?: string;
|
|
156
|
+
meta?: React.ReactNode;
|
|
157
|
+
actions?: React.ReactNode;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function AiChatHeader({ className, title, description, meta, actions, ...props }: AiChatHeaderProps) {
|
|
161
|
+
return (
|
|
162
|
+
<header className={cn("border-b bg-card/95 px-5 py-4 backdrop-blur", className)} {...props}>
|
|
163
|
+
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
164
|
+
<div className="grid gap-1">
|
|
165
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
166
|
+
<p className="text-base font-semibold text-foreground">{title}</p>
|
|
167
|
+
{meta ? <div className="flex items-center gap-2 text-xs text-muted-foreground">{meta}</div> : null}
|
|
168
|
+
</div>
|
|
169
|
+
{description ? <p className="text-sm text-muted-foreground">{description}</p> : null}
|
|
170
|
+
</div>
|
|
171
|
+
{actions ? <div className="flex flex-wrap items-center gap-2">{actions}</div> : null}
|
|
172
|
+
</div>
|
|
173
|
+
</header>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const AiChatMessageList = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
178
|
+
({ className, ...props }, ref) => (
|
|
179
|
+
<div
|
|
180
|
+
ref={ref}
|
|
181
|
+
role="log"
|
|
182
|
+
aria-live="polite"
|
|
183
|
+
className={cn("flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto px-5 py-5", className)}
|
|
184
|
+
{...props}
|
|
185
|
+
/>
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
AiChatMessageList.displayName = "AiChatMessageList";
|
|
190
|
+
|
|
191
|
+
interface AiChatMessageSource {
|
|
192
|
+
label: string;
|
|
193
|
+
href?: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
interface AiChatMessageProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
197
|
+
role?: AiChatRole;
|
|
198
|
+
author?: string;
|
|
199
|
+
timestamp?: string;
|
|
200
|
+
status?: AiChatMessageStatus;
|
|
201
|
+
avatar?: React.ReactNode;
|
|
202
|
+
actions?: React.ReactNode;
|
|
203
|
+
sources?: AiChatMessageSource[];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getDefaultAvatar(role: AiChatRole) {
|
|
207
|
+
if (role === "assistant") {
|
|
208
|
+
return (
|
|
209
|
+
<span className="flex h-9 w-9 items-center justify-center rounded-full bg-[var(--color-brand)] text-white shadow-sm">
|
|
210
|
+
<Bot className="h-4 w-4" />
|
|
211
|
+
</span>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (role === "system") {
|
|
216
|
+
return (
|
|
217
|
+
<span className="flex h-9 w-9 items-center justify-center rounded-full bg-secondary text-secondary-foreground shadow-sm">
|
|
218
|
+
<Sparkles className="h-4 w-4" />
|
|
219
|
+
</span>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<span className="flex h-9 w-9 items-center justify-center rounded-full bg-foreground text-background shadow-sm">
|
|
225
|
+
<span className="text-xs font-semibold">TU</span>
|
|
226
|
+
</span>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function AiChatMessage({
|
|
231
|
+
className,
|
|
232
|
+
role = "assistant",
|
|
233
|
+
author,
|
|
234
|
+
timestamp,
|
|
235
|
+
status = "complete",
|
|
236
|
+
avatar,
|
|
237
|
+
actions,
|
|
238
|
+
sources,
|
|
239
|
+
children,
|
|
240
|
+
...props
|
|
241
|
+
}: AiChatMessageProps) {
|
|
242
|
+
const isUser = role === "user";
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<article
|
|
246
|
+
className={cn("flex gap-3", isUser ? "justify-end" : "justify-start", className)}
|
|
247
|
+
{...props}
|
|
248
|
+
>
|
|
249
|
+
{!isUser ? <div className="shrink-0">{avatar ?? getDefaultAvatar(role)}</div> : null}
|
|
250
|
+
|
|
251
|
+
<div className={cn("grid max-w-[90%] gap-2 md:max-w-[74%]", isUser && "justify-items-end")}>
|
|
252
|
+
<div className={cn("flex items-center gap-2", isUser && "justify-end")}>
|
|
253
|
+
<span className="text-xs font-medium text-foreground">{author ?? (isUser ? "Tu" : "Cortexa AI")}</span>
|
|
254
|
+
{timestamp ? <span className="text-xs text-muted-foreground">{timestamp}</span> : null}
|
|
255
|
+
{status !== "complete" ? (
|
|
256
|
+
<Badge variant={status === "error" ? "destructive" : "secondary"}>
|
|
257
|
+
{status === "streaming" ? "Generando" : "Error"}
|
|
258
|
+
</Badge>
|
|
259
|
+
) : null}
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<div
|
|
263
|
+
className={cn(
|
|
264
|
+
"rounded-2xl border px-4 py-3 text-sm leading-6 shadow-sm",
|
|
265
|
+
isUser
|
|
266
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
267
|
+
: role === "system"
|
|
268
|
+
? "border-secondary bg-secondary text-secondary-foreground"
|
|
269
|
+
: "bg-card text-foreground"
|
|
270
|
+
)}
|
|
271
|
+
>
|
|
272
|
+
<div className="whitespace-pre-wrap">{children}</div>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
{sources?.length ? (
|
|
276
|
+
<div className={cn("flex flex-wrap gap-2", isUser && "justify-end")}>
|
|
277
|
+
{sources.map((source) =>
|
|
278
|
+
source.href ? (
|
|
279
|
+
<a
|
|
280
|
+
key={`${source.label}-${source.href}`}
|
|
281
|
+
href={source.href}
|
|
282
|
+
className="inline-flex items-center gap-1 rounded-full border px-2.5 py-1 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
|
283
|
+
>
|
|
284
|
+
{source.label}
|
|
285
|
+
<PanelRightOpen className="h-3 w-3" />
|
|
286
|
+
</a>
|
|
287
|
+
) : (
|
|
288
|
+
<span
|
|
289
|
+
key={source.label}
|
|
290
|
+
className="inline-flex items-center rounded-full border px-2.5 py-1 text-xs text-muted-foreground"
|
|
291
|
+
>
|
|
292
|
+
{source.label}
|
|
293
|
+
</span>
|
|
294
|
+
)
|
|
295
|
+
)}
|
|
296
|
+
</div>
|
|
297
|
+
) : null}
|
|
298
|
+
|
|
299
|
+
{actions ? <div className={cn("flex flex-wrap gap-2", isUser && "justify-end")}>{actions}</div> : null}
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
{isUser ? <div className="shrink-0">{avatar ?? getDefaultAvatar(role)}</div> : null}
|
|
303
|
+
</article>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
type AiChatAssistantMessageProps = Omit<AiChatMessageProps, "role">;
|
|
308
|
+
|
|
309
|
+
function AiChatAssistantMessage(props: AiChatAssistantMessageProps) {
|
|
310
|
+
return <AiChatMessage role="assistant" {...props} />;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
type AiChatUserMessageProps = Omit<AiChatMessageProps, "role">;
|
|
314
|
+
|
|
315
|
+
function AiChatUserMessage(props: AiChatUserMessageProps) {
|
|
316
|
+
return <AiChatMessage role="user" {...props} />;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
interface AiChatSummaryActionsProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
320
|
+
title?: string;
|
|
321
|
+
description?: string;
|
|
322
|
+
actionsClassName?: string;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function AiChatSummaryActions({
|
|
326
|
+
className,
|
|
327
|
+
title = "Quick actions",
|
|
328
|
+
description,
|
|
329
|
+
actionsClassName,
|
|
330
|
+
children,
|
|
331
|
+
...props
|
|
332
|
+
}: AiChatSummaryActionsProps) {
|
|
333
|
+
return (
|
|
334
|
+
<section className={cn("grid gap-3 rounded-2xl border bg-card p-4 shadow-sm", className)} {...props}>
|
|
335
|
+
<div className="grid gap-1">
|
|
336
|
+
<p className="text-sm font-semibold text-foreground">{title}</p>
|
|
337
|
+
{description ? <p className="text-xs text-muted-foreground">{description}</p> : null}
|
|
338
|
+
</div>
|
|
339
|
+
<div className={cn("grid gap-3 md:grid-cols-3", actionsClassName)}>{children}</div>
|
|
340
|
+
</section>
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
interface AiChatSummaryActionProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
345
|
+
title: string;
|
|
346
|
+
description?: string;
|
|
347
|
+
icon?: LucideIcon;
|
|
348
|
+
active?: boolean;
|
|
349
|
+
badge?: string;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const AiChatSummaryAction = React.forwardRef<HTMLButtonElement, AiChatSummaryActionProps>(
|
|
353
|
+
(
|
|
354
|
+
{
|
|
355
|
+
className,
|
|
356
|
+
title,
|
|
357
|
+
description,
|
|
358
|
+
icon: Icon = Sparkles,
|
|
359
|
+
active = false,
|
|
360
|
+
badge,
|
|
361
|
+
type = "button",
|
|
362
|
+
...props
|
|
363
|
+
},
|
|
364
|
+
ref
|
|
365
|
+
) => (
|
|
366
|
+
<button
|
|
367
|
+
ref={ref}
|
|
368
|
+
type={type}
|
|
369
|
+
className={cn(
|
|
370
|
+
"flex w-full items-start gap-3 rounded-xl border px-4 py-4 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
371
|
+
active
|
|
372
|
+
? "border-[var(--color-accent-blue)]/40 bg-[var(--color-accent-blue)]/10"
|
|
373
|
+
: "hover:border-border hover:bg-accent/40",
|
|
374
|
+
className
|
|
375
|
+
)}
|
|
376
|
+
{...props}
|
|
377
|
+
>
|
|
378
|
+
<span className="mt-0.5 flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[var(--color-brand)]/10 text-[var(--color-brand)]">
|
|
379
|
+
<Icon className="h-5 w-5" />
|
|
380
|
+
</span>
|
|
381
|
+
<span className="grid flex-1 gap-1">
|
|
382
|
+
<span className="flex flex-wrap items-center gap-2">
|
|
383
|
+
<span className="text-sm font-medium text-foreground">{title}</span>
|
|
384
|
+
{badge ? <Badge variant="secondary">{badge}</Badge> : null}
|
|
385
|
+
</span>
|
|
386
|
+
{description ? <span className="text-xs leading-5 text-muted-foreground">{description}</span> : null}
|
|
387
|
+
</span>
|
|
388
|
+
</button>
|
|
389
|
+
)
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
AiChatSummaryAction.displayName = "AiChatSummaryAction";
|
|
393
|
+
|
|
394
|
+
interface AiChatComposerProps extends Omit<React.FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {
|
|
395
|
+
textareaProps?: React.ComponentPropsWithoutRef<typeof Textarea>;
|
|
396
|
+
onSubmit?: React.FormEventHandler<HTMLFormElement>;
|
|
397
|
+
submitLabel?: string;
|
|
398
|
+
submitIcon?: React.ReactNode;
|
|
399
|
+
leadingActions?: React.ReactNode;
|
|
400
|
+
trailingActions?: React.ReactNode;
|
|
401
|
+
helperText?: React.ReactNode;
|
|
402
|
+
disabled?: boolean;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function AiChatComposer({
|
|
406
|
+
className,
|
|
407
|
+
textareaProps,
|
|
408
|
+
onSubmit,
|
|
409
|
+
submitLabel = "Send",
|
|
410
|
+
submitIcon = <Send className="h-4 w-4" />,
|
|
411
|
+
leadingActions,
|
|
412
|
+
trailingActions,
|
|
413
|
+
helperText,
|
|
414
|
+
disabled = false,
|
|
415
|
+
...props
|
|
416
|
+
}: AiChatComposerProps) {
|
|
417
|
+
const { className: textareaClassName, placeholder, disabled: textareaDisabled, ...restTextareaProps } = textareaProps ?? {};
|
|
418
|
+
const isDisabled = disabled || Boolean(textareaDisabled);
|
|
419
|
+
|
|
420
|
+
return (
|
|
421
|
+
<form className={cn("border-t bg-card px-4 py-4", className)} onSubmit={onSubmit} {...props}>
|
|
422
|
+
<div className="rounded-2xl border bg-background p-3 shadow-sm">
|
|
423
|
+
<Textarea
|
|
424
|
+
className={cn(
|
|
425
|
+
"min-h-24 resize-none border-0 bg-transparent p-0 text-sm shadow-none focus-visible:ring-0",
|
|
426
|
+
textareaClassName
|
|
427
|
+
)}
|
|
428
|
+
placeholder={placeholder ?? "Describe what you need from the assistant..."}
|
|
429
|
+
disabled={isDisabled}
|
|
430
|
+
{...restTextareaProps}
|
|
431
|
+
/>
|
|
432
|
+
<div className="mt-3 flex flex-wrap items-center justify-between gap-3">
|
|
433
|
+
<div className="flex flex-wrap items-center gap-2">{leadingActions}</div>
|
|
434
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
435
|
+
{trailingActions}
|
|
436
|
+
<Button type="submit" disabled={isDisabled}>
|
|
437
|
+
{submitIcon}
|
|
438
|
+
{submitLabel}
|
|
439
|
+
</Button>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
</div>
|
|
443
|
+
{helperText ? <p className="mt-2 text-xs text-muted-foreground">{helperText}</p> : null}
|
|
444
|
+
</form>
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
interface AiChatEmptyStateProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
449
|
+
title: string;
|
|
450
|
+
description: string;
|
|
451
|
+
prompts?: string[];
|
|
452
|
+
onPromptSelect?: (prompt: string) => void;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function AiChatEmptyState({
|
|
456
|
+
className,
|
|
457
|
+
title,
|
|
458
|
+
description,
|
|
459
|
+
prompts,
|
|
460
|
+
onPromptSelect,
|
|
461
|
+
...props
|
|
462
|
+
}: AiChatEmptyStateProps) {
|
|
463
|
+
return (
|
|
464
|
+
<div
|
|
465
|
+
className={cn(
|
|
466
|
+
"flex flex-1 flex-col items-center justify-center gap-5 rounded-2xl border border-dashed bg-card/70 px-6 py-10 text-center",
|
|
467
|
+
className
|
|
468
|
+
)}
|
|
469
|
+
{...props}
|
|
470
|
+
>
|
|
471
|
+
<span className="flex h-14 w-14 items-center justify-center rounded-2xl bg-[var(--color-brand)]/10 text-[var(--color-brand)]">
|
|
472
|
+
<Sparkles className="h-6 w-6" />
|
|
473
|
+
</span>
|
|
474
|
+
<div className="grid max-w-xl gap-2">
|
|
475
|
+
<p className="text-lg font-semibold text-foreground">{title}</p>
|
|
476
|
+
<p className="text-sm leading-6 text-muted-foreground">{description}</p>
|
|
477
|
+
</div>
|
|
478
|
+
|
|
479
|
+
{prompts?.length ? (
|
|
480
|
+
<div className="flex max-w-2xl flex-wrap items-center justify-center gap-2">
|
|
481
|
+
{prompts.map((prompt) => (
|
|
482
|
+
<Button key={prompt} type="button" variant="outline" size="sm" onClick={() => onPromptSelect?.(prompt)}>
|
|
483
|
+
{prompt}
|
|
484
|
+
</Button>
|
|
485
|
+
))}
|
|
486
|
+
</div>
|
|
487
|
+
) : null}
|
|
488
|
+
</div>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
interface AiChatFloatingButtonProps extends ButtonProps {
|
|
493
|
+
label?: string;
|
|
494
|
+
unreadCount?: number;
|
|
495
|
+
icon?: LucideIcon;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function AiChatFloatingButton({
|
|
499
|
+
className,
|
|
500
|
+
label = "Asistente IA",
|
|
501
|
+
unreadCount,
|
|
502
|
+
icon: Icon = Sparkles,
|
|
503
|
+
variant = "default",
|
|
504
|
+
size = "lg",
|
|
505
|
+
children,
|
|
506
|
+
...props
|
|
507
|
+
}: AiChatFloatingButtonProps) {
|
|
508
|
+
return (
|
|
509
|
+
<Button
|
|
510
|
+
variant={variant}
|
|
511
|
+
size={size}
|
|
512
|
+
className={cn("fixed bottom-6 right-6 h-14 rounded-full px-5 shadow-lg", className)}
|
|
513
|
+
{...props}
|
|
514
|
+
>
|
|
515
|
+
<Icon className="h-4 w-4" />
|
|
516
|
+
<span>{children ?? label}</span>
|
|
517
|
+
{typeof unreadCount === "number" && unreadCount > 0 ? (
|
|
518
|
+
<span className="rounded-full bg-white/15 px-2 py-0.5 text-xs font-semibold text-inherit">
|
|
519
|
+
{unreadCount > 9 ? "9+" : unreadCount}
|
|
520
|
+
</span>
|
|
521
|
+
) : null}
|
|
522
|
+
</Button>
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
type AiChatFloatingSidebarSide = "left" | "right";
|
|
527
|
+
|
|
528
|
+
interface AiChatFloatingSidebarProps {
|
|
529
|
+
children: React.ReactNode;
|
|
530
|
+
trigger?: React.ReactNode;
|
|
531
|
+
side?: AiChatFloatingSidebarSide;
|
|
532
|
+
open?: boolean;
|
|
533
|
+
defaultOpen?: boolean;
|
|
534
|
+
onOpenChange?: (open: boolean) => void;
|
|
535
|
+
contentClassName?: string;
|
|
536
|
+
triggerAsChild?: boolean;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function AiChatFloatingSidebar({
|
|
540
|
+
children,
|
|
541
|
+
trigger,
|
|
542
|
+
side = "right",
|
|
543
|
+
open,
|
|
544
|
+
defaultOpen,
|
|
545
|
+
onOpenChange,
|
|
546
|
+
contentClassName,
|
|
547
|
+
triggerAsChild = true
|
|
548
|
+
}: AiChatFloatingSidebarProps) {
|
|
549
|
+
const resolvedTrigger = trigger ?? <AiChatFloatingButton />;
|
|
550
|
+
|
|
551
|
+
return (
|
|
552
|
+
<Sheet open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
|
|
553
|
+
<SheetTrigger asChild={triggerAsChild}>{resolvedTrigger}</SheetTrigger>
|
|
554
|
+
<SheetContent side={side} className={cn("w-full p-0 sm:max-w-[460px]", contentClassName)}>
|
|
555
|
+
<AiChatLayout variant="sidebar" className="h-full rounded-none border-0 shadow-none">
|
|
556
|
+
{children}
|
|
557
|
+
</AiChatLayout>
|
|
558
|
+
</SheetContent>
|
|
559
|
+
</Sheet>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export {
|
|
564
|
+
AiChatAssistantMessage,
|
|
565
|
+
AiChatComposer,
|
|
566
|
+
AiChatEmptyState,
|
|
567
|
+
AiChatFloatingButton,
|
|
568
|
+
AiChatFloatingSidebar,
|
|
569
|
+
AiChatHeader,
|
|
570
|
+
AiChatLayout,
|
|
571
|
+
AiChatMessage,
|
|
572
|
+
AiChatMessageList,
|
|
573
|
+
AiChatPanel,
|
|
574
|
+
AiChatSidebar,
|
|
575
|
+
AiChatSidebarHeader,
|
|
576
|
+
AiChatSummaryAction,
|
|
577
|
+
AiChatSummaryActions,
|
|
578
|
+
AiChatThreadButton,
|
|
579
|
+
AiChatThreadList,
|
|
580
|
+
AiChatUserMessage
|
|
581
|
+
};
|
|
582
|
+
export type {
|
|
583
|
+
AiChatAssistantMessageProps,
|
|
584
|
+
AiChatFloatingButtonProps,
|
|
585
|
+
AiChatFloatingSidebarProps,
|
|
586
|
+
AiChatFloatingSidebarSide,
|
|
587
|
+
AiChatLayoutProps,
|
|
588
|
+
AiChatLayoutVariant,
|
|
589
|
+
AiChatMessageProps,
|
|
590
|
+
AiChatMessageSource,
|
|
591
|
+
AiChatMessageStatus,
|
|
592
|
+
AiChatRole,
|
|
593
|
+
AiChatSummaryActionProps,
|
|
594
|
+
AiChatSummaryActionsProps,
|
|
595
|
+
AiChatThreadButtonProps,
|
|
596
|
+
AiChatUserMessageProps
|
|
597
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn";
|
|
2
|
+
import { useTheme } from "../../providers/theme-provider";
|
|
3
|
+
import LogoDark from "../../assets/isotipo-cortexa-dark.png";
|
|
4
|
+
import LogoLight from "../../assets/isotipo-cortexa-light.png";
|
|
5
|
+
|
|
6
|
+
interface BrandLogoProps {
|
|
7
|
+
className?: string;
|
|
8
|
+
size?: "sm" | "md" | "lg";
|
|
9
|
+
href?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const sizeMap = {
|
|
13
|
+
sm: { icon: "h-6 w-6", text: "text-sm", gap: "gap-2" },
|
|
14
|
+
md: { icon: "h-7 w-7", text: "text-lg", gap: "gap-3" },
|
|
15
|
+
lg: { icon: "h-9 w-9", text: "text-xl", gap: "gap-3" }
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export function BrandLogo({ className, size = "md", href }: BrandLogoProps) {
|
|
19
|
+
const { resolvedTheme } = useTheme();
|
|
20
|
+
const isDark = resolvedTheme === "dark";
|
|
21
|
+
const s = sizeMap[size];
|
|
22
|
+
|
|
23
|
+
const content = (
|
|
24
|
+
<>
|
|
25
|
+
<div
|
|
26
|
+
className={cn(
|
|
27
|
+
"flex shrink-0 items-center justify-center rounded-md",
|
|
28
|
+
s.icon,
|
|
29
|
+
isDark ? "bg-[var(--color-brand)] p-1" : "bg-transparent"
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
32
|
+
<img
|
|
33
|
+
src={isDark ? LogoDark : LogoLight}
|
|
34
|
+
alt="Cortexa"
|
|
35
|
+
className="max-h-full max-w-full object-contain"
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div className="flex items-baseline">
|
|
40
|
+
<span
|
|
41
|
+
className={cn(
|
|
42
|
+
"font-bold leading-none",
|
|
43
|
+
s.text,
|
|
44
|
+
isDark ? "text-white" : "text-[var(--color-brand)]"
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
Cortexa
|
|
48
|
+
</span>
|
|
49
|
+
<span
|
|
50
|
+
className={cn(
|
|
51
|
+
"ml-1 font-normal leading-none",
|
|
52
|
+
s.text,
|
|
53
|
+
isDark ? "text-white" : "text-[var(--color-accent-blue)]"
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
Fiscal
|
|
57
|
+
</span>
|
|
58
|
+
</div>
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const wrapperClass = cn("flex items-center", s.gap, className);
|
|
63
|
+
|
|
64
|
+
if (href) {
|
|
65
|
+
return (
|
|
66
|
+
<a href={href} className={wrapperClass} aria-label="Cortexa Fiscal — Inicio">
|
|
67
|
+
{content}
|
|
68
|
+
</a>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div className={wrapperClass} aria-label="Cortexa Fiscal">
|
|
74
|
+
{content}
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|