@wealthx/shadcn 1.5.37 → 1.5.38

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 (34) hide show
  1. package/.turbo/turbo-build.log +99 -93
  2. package/CHANGELOG.md +6 -0
  3. package/dist/{chunk-734FOOJC.mjs → chunk-B5PSUONN.mjs} +25 -58
  4. package/dist/chunk-EFHPSKVF.mjs +192 -0
  5. package/dist/{chunk-NB3ZL36B.mjs → chunk-MZI77ZMX.mjs} +17 -2
  6. package/dist/chunk-R7M657QL.mjs +587 -0
  7. package/dist/{chunk-DIH2NZZ3.mjs → chunk-RRROLESJ.mjs} +33 -23
  8. package/dist/components/ui/ai-assistant-drawer.js +269 -121
  9. package/dist/components/ui/ai-assistant-drawer.mjs +2 -1
  10. package/dist/components/ui/ai-conversations/index.js +474 -286
  11. package/dist/components/ui/ai-conversations/index.mjs +2 -1
  12. package/dist/components/ui/chat-input-area.js +429 -0
  13. package/dist/components/ui/chat-input-area.mjs +11 -0
  14. package/dist/components/ui/page-top-bar.js +182 -5
  15. package/dist/components/ui/page-top-bar.mjs +3 -1
  16. package/dist/components/ui/support-agent/index.js +1131 -0
  17. package/dist/components/ui/support-agent/index.mjs +27 -0
  18. package/dist/index.js +4760 -4027
  19. package/dist/index.mjs +54 -36
  20. package/dist/styles.css +1 -1
  21. package/package.json +11 -1
  22. package/src/components/index.tsx +24 -0
  23. package/src/components/ui/ai-assistant-drawer.tsx +24 -51
  24. package/src/components/ui/ai-conversations/index.tsx +16 -8
  25. package/src/components/ui/ai-conversations/thread.tsx +38 -27
  26. package/src/components/ui/chat-input-area.tsx +244 -0
  27. package/src/components/ui/page-top-bar.tsx +31 -5
  28. package/src/components/ui/support-agent/index.tsx +25 -0
  29. package/src/components/ui/support-agent/support-agent-fab.tsx +116 -0
  30. package/src/components/ui/support-agent/support-agent-panel.tsx +498 -0
  31. package/src/components/ui/support-agent/support-agent-primitives.tsx +354 -0
  32. package/src/styles/globals.css +1 -0
  33. package/src/styles/styles-css.ts +1 -1
  34. package/tsup.config.ts +2 -0
@@ -0,0 +1,354 @@
1
+ import * as React from "react";
2
+ import {
3
+ Check,
4
+ ChevronDown,
5
+ ChevronUp,
6
+ ExternalLink,
7
+ MapPin,
8
+ MousePointerClick,
9
+ } from "lucide-react";
10
+ import { cn } from "@/lib/utils";
11
+ import { Button } from "@/components/ui/button";
12
+ import { Skeleton } from "@/components/ui/skeleton";
13
+
14
+ /**
15
+ * Support Agent Primitives — WealthX DS
16
+ *
17
+ * Atomic building blocks for the Support Agent panel.
18
+ * Inspired by Jira Rovo's sidebar chat pattern:
19
+ * - SupportContextChip — shows current backoffice page + entity (Rovo: page context header)
20
+ * - SupportSuggestedQuestion — clickable question cards (Rovo: conversation starters)
21
+ * - SupportStepGuideCard — numbered walkthrough with "Show me" DOM highlight (Rovo: message cards)
22
+ * - SupportArticleCard — help article reference link (Rovo: linked content cards)
23
+ *
24
+ * All components are pure display — no state or API calls.
25
+ * State and event handling are managed by SupportAgentPanel (the parent organism).
26
+ */
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Types
30
+ // ---------------------------------------------------------------------------
31
+
32
+ /** Context injected from the current backoffice route. */
33
+ export interface SupportAgentContext {
34
+ /** Page label from the current route, e.g. "Loan Applications". */
35
+ pageLabel: string;
36
+ /** Entity currently being viewed, e.g. "John Smith". Optional. */
37
+ entityLabel?: string;
38
+ /** Entity type for semantic use, e.g. "client" | "application" | "contact". */
39
+ entityType?: string;
40
+ }
41
+
42
+ /** A single step inside a SupportStepGuideCard. */
43
+ export interface SupportAgentStep {
44
+ id: string;
45
+ /** Human-readable step instruction. */
46
+ label: string;
47
+ /**
48
+ * If set, the "Show me" button fires onHighlight(elementId).
49
+ * The consuming page listens for this and highlights the DOM element
50
+ * that has data-support-id matching this value.
51
+ */
52
+ elementId?: string;
53
+ /** Whether the user has marked this step as done. */
54
+ completed?: boolean;
55
+ }
56
+
57
+ /** Rich content embedded inside an assistant message bubble. */
58
+ export type SupportAgentRichContent =
59
+ | {
60
+ type: "step-guide";
61
+ title: string;
62
+ steps: SupportAgentStep[];
63
+ }
64
+ | {
65
+ type: "article";
66
+ title: string;
67
+ excerpt: string;
68
+ href: string;
69
+ /** If true, opens in a new tab and shows an external link icon. */
70
+ isExternal?: boolean;
71
+ };
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // SupportContextChip
75
+ // ---------------------------------------------------------------------------
76
+
77
+ export interface SupportContextChipProps {
78
+ context?: SupportAgentContext;
79
+ /** Show a skeleton loading placeholder. */
80
+ isLoading?: boolean;
81
+ className?: string;
82
+ }
83
+
84
+ /**
85
+ * Pill that shows the current backoffice page and optional entity.
86
+ * Appears below the panel header to ground the AI conversation in context.
87
+ *
88
+ * Variants:
89
+ * - page-only → "📍 Loan Applications"
90
+ * - page+entity → "📍 Loan Applications › John Smith"
91
+ * - loading → skeleton placeholder
92
+ */
93
+ export function SupportContextChip({
94
+ context,
95
+ isLoading = false,
96
+ className,
97
+ }: SupportContextChipProps) {
98
+ if (isLoading) {
99
+ return (
100
+ <Skeleton
101
+ className={cn("h-6 w-48", className)}
102
+ data-slot="support-context-chip"
103
+ />
104
+ );
105
+ }
106
+
107
+ if (!context) return null;
108
+
109
+ return (
110
+ <div
111
+ className={cn(
112
+ "flex w-fit items-center gap-1.5 bg-muted px-2.5 py-1 text-xs text-muted-foreground",
113
+ className
114
+ )}
115
+ data-slot="support-context-chip"
116
+ >
117
+ <MapPin className="size-3 shrink-0" aria-hidden="true" />
118
+ <span className="font-medium text-foreground">{context.pageLabel}</span>
119
+ {context.entityLabel && (
120
+ <>
121
+ <span aria-hidden="true">›</span>
122
+ <span className="truncate max-w-[160px]">{context.entityLabel}</span>
123
+ </>
124
+ )}
125
+ </div>
126
+ );
127
+ }
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // SupportSuggestedQuestion
131
+ // ---------------------------------------------------------------------------
132
+
133
+ export interface SupportSuggestedQuestionProps {
134
+ question: string;
135
+ onSelect: (question: string) => void;
136
+ className?: string;
137
+ }
138
+
139
+ /**
140
+ * Single clickable question card — the Jira Rovo "conversation starter" pattern.
141
+ * Clicking pre-fills the chat input with the question text.
142
+ */
143
+ export function SupportSuggestedQuestion({
144
+ question,
145
+ onSelect,
146
+ className,
147
+ }: SupportSuggestedQuestionProps) {
148
+ return (
149
+ <Button
150
+ type="button"
151
+ variant="ghost"
152
+ onClick={() => onSelect(question)}
153
+ className={cn(
154
+ "h-auto w-full justify-start border border-border bg-background px-3 py-2.5 text-left text-sm font-normal text-foreground hover:bg-muted/50",
155
+ className
156
+ )}
157
+ data-slot="support-suggested-question"
158
+ >
159
+ {question}
160
+ </Button>
161
+ );
162
+ }
163
+
164
+ // ---------------------------------------------------------------------------
165
+ // SupportStepGuideCard
166
+ // ---------------------------------------------------------------------------
167
+
168
+ export interface SupportStepGuideCardProps {
169
+ title: string;
170
+ steps: SupportAgentStep[];
171
+ /** Called when the user clicks "Show me" on a step that has an elementId. */
172
+ onHighlight?: (elementId: string) => void;
173
+ /** Called when the user marks a step as complete. */
174
+ onStepComplete?: (stepId: string) => void;
175
+ /** Default collapsed if more than 5 steps; otherwise expanded. */
176
+ defaultExpanded?: boolean;
177
+ className?: string;
178
+ }
179
+
180
+ /**
181
+ * A numbered walkthrough card embedded inside an AI assistant message bubble.
182
+ * Inspired by Jira Rovo message cards — bordered container with structured content.
183
+ *
184
+ * - Completed steps show a checkmark icon instead of a number.
185
+ * - Steps with an `elementId` show a "→ Show me" button that fires onHighlight.
186
+ * - Collapsible when more than 5 steps (default collapsed).
187
+ */
188
+ export function SupportStepGuideCard({
189
+ title,
190
+ steps,
191
+ onHighlight,
192
+ onStepComplete,
193
+ defaultExpanded,
194
+ className,
195
+ }: SupportStepGuideCardProps) {
196
+ const isLong = steps.length > 5;
197
+ const [expanded, setExpanded] = React.useState(defaultExpanded ?? !isLong);
198
+
199
+ const completedCount = steps.filter((s) => s.completed).length;
200
+ const allDone = completedCount === steps.length;
201
+
202
+ return (
203
+ <div
204
+ className={cn("mt-2 border border-border bg-background", className)}
205
+ data-slot="support-step-guide-card"
206
+ >
207
+ {/* Card header */}
208
+ <button
209
+ type="button"
210
+ onClick={() => setExpanded((v) => !v)}
211
+ className="flex w-full items-center justify-between gap-2 px-3 py-2.5 text-left hover:bg-muted/30"
212
+ aria-expanded={expanded}
213
+ >
214
+ <div className="flex flex-col gap-0.5">
215
+ <span className="text-sm font-semibold text-foreground">{title}</span>
216
+ <span className="text-xs text-muted-foreground">
217
+ {allDone
218
+ ? "All steps complete"
219
+ : `${completedCount} of ${steps.length} complete`}
220
+ </span>
221
+ </div>
222
+ {expanded ? (
223
+ <ChevronUp
224
+ className="size-4 shrink-0 text-muted-foreground"
225
+ aria-hidden="true"
226
+ />
227
+ ) : (
228
+ <ChevronDown
229
+ className="size-4 shrink-0 text-muted-foreground"
230
+ aria-hidden="true"
231
+ />
232
+ )}
233
+ </button>
234
+
235
+ {/* Steps list */}
236
+ {expanded && (
237
+ <ol className="flex flex-col divide-y divide-border border-t border-border">
238
+ {steps.map((step, index) => (
239
+ <li
240
+ key={step.id}
241
+ className={cn(
242
+ "flex items-start gap-3 px-3 py-2.5",
243
+ step.completed && "opacity-60"
244
+ )}
245
+ >
246
+ {/* Step number or checkmark */}
247
+ <button
248
+ type="button"
249
+ onClick={() => !step.completed && onStepComplete?.(step.id)}
250
+ disabled={step.completed}
251
+ title={step.completed ? "Step complete" : "Mark as done"}
252
+ className={cn(
253
+ "mt-0.5 flex size-5 shrink-0 items-center justify-center border text-xs font-semibold",
254
+ step.completed
255
+ ? "border-primary bg-primary text-primary-foreground"
256
+ : "border-border text-muted-foreground hover:bg-muted"
257
+ )}
258
+ aria-label={
259
+ step.completed
260
+ ? `Step ${index + 1} complete`
261
+ : `Mark step ${index + 1} as done`
262
+ }
263
+ >
264
+ {step.completed ? (
265
+ <Check className="size-3" aria-hidden="true" />
266
+ ) : (
267
+ index + 1
268
+ )}
269
+ </button>
270
+
271
+ {/* Step label + show me button */}
272
+ <div className="flex flex-1 flex-col gap-1">
273
+ <span className="text-sm text-foreground leading-snug">
274
+ {step.label}
275
+ </span>
276
+ {step.elementId && onHighlight && !step.completed && (
277
+ <Button
278
+ type="button"
279
+ variant="ghost"
280
+ size="sm"
281
+ onClick={() => onHighlight(step.elementId!)}
282
+ className="h-auto w-fit gap-1 px-0 py-0 text-xs text-primary hover:bg-transparent hover:underline"
283
+ >
284
+ <MousePointerClick className="size-3" aria-hidden="true" />
285
+ Show me
286
+ </Button>
287
+ )}
288
+ </div>
289
+ </li>
290
+ ))}
291
+ </ol>
292
+ )}
293
+ </div>
294
+ );
295
+ }
296
+
297
+ // ---------------------------------------------------------------------------
298
+ // SupportArticleCard
299
+ // ---------------------------------------------------------------------------
300
+
301
+ export interface SupportArticleCardProps {
302
+ title: string;
303
+ excerpt: string;
304
+ href: string;
305
+ /** If true, opens in a new tab with an external link icon. */
306
+ isExternal?: boolean;
307
+ className?: string;
308
+ }
309
+
310
+ /**
311
+ * A compact help article reference card embedded inside an AI message bubble.
312
+ * Inspired by Jira Rovo's linked content cards.
313
+ *
314
+ * - Internal links: navigates in the same tab (relative URL).
315
+ * - External links: opens new tab + shows ExternalLink icon.
316
+ */
317
+ export function SupportArticleCard({
318
+ title,
319
+ excerpt,
320
+ href,
321
+ isExternal = false,
322
+ className,
323
+ }: SupportArticleCardProps) {
324
+ return (
325
+ <a
326
+ href={href}
327
+ target={isExternal ? "_blank" : undefined}
328
+ rel={isExternal ? "noopener noreferrer" : undefined}
329
+ className={cn(
330
+ "mt-2 flex flex-col gap-1.5 border border-border bg-background px-3 py-2.5 text-left no-underline hover:bg-muted/30 text-foreground",
331
+ className
332
+ )}
333
+ data-slot="support-article-card"
334
+ >
335
+ <div className="flex items-center justify-between gap-2">
336
+ <span className="min-w-0 flex-1 truncate text-sm font-semibold text-foreground">
337
+ {title}
338
+ </span>
339
+ {isExternal && (
340
+ <ExternalLink
341
+ className="size-3.5 shrink-0 text-muted-foreground"
342
+ aria-hidden="true"
343
+ />
344
+ )}
345
+ </div>
346
+ <p className="text-xs text-muted-foreground line-clamp-2 leading-relaxed">
347
+ {excerpt}
348
+ </p>
349
+ <span className="text-xs font-medium text-primary">
350
+ {isExternal ? "Open article →" : "Read more →"}
351
+ </span>
352
+ </a>
353
+ );
354
+ }
@@ -32,6 +32,7 @@
32
32
  @source inline("animate-in animate-out animate-pulse animate-caret-blink fade-in-0 fade-out-0 zoom-in-95 data-open:slide-in-from-bottom data-open:slide-in-from-left data-open:slide-in-from-right data-open:slide-in-from-top data-ending-style:slide-out-to-bottom data-ending-style:slide-out-to-left data-ending-style:slide-out-to-right data-ending-style:slide-out-to-top data-open:duration-500 data-ending-style:duration-300 data-open:bg-accent data-open:text-accent-foreground");
33
33
 
34
34
  @source inline("focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 dark:bg-input/30 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground data-indeterminate:border-primary data-indeterminate:bg-primary data-indeterminate:text-primary-foreground dark:data-checked:bg-primary aria-invalid:data-checked:border-destructive aria-invalid:data-checked:bg-destructive aria-invalid:data-checked:text-destructive-foreground aria-invalid:data-indeterminate:border-destructive aria-invalid:data-indeterminate:bg-destructive aria-invalid:data-indeterminate:text-destructive-foreground group-data-indeterminate:hidden group-data-indeterminate:block bg-primary/5 bg-destructive/5 border-primary border-destructive peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-destructive text-muted-foreground disabled:pointer-events-none data-open:animate-in data-ending-style:animate-out data-open:fade-in-0 data-ending-style:fade-out-0 data-open:zoom-in-95 data-ending-style:zoom-out-95 animate-spin data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-unchecked:bg-input dark:data-unchecked:bg-input/80 group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-checked:translate-x-[calc(100%-2px)] data-unchecked:translate-x-0 dark:data-checked:bg-primary-foreground dark:data-unchecked:bg-foreground group/switch");
35
+ @source inline("bg-success/20 bg-warning/20 bg-destructive/20 bg-success bg-warning");
35
36
 
36
37
  @custom-variant dark (&:is(.dark *));
37
38