@wealthx/shadcn 1.5.36 → 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 +125 -119
  2. package/CHANGELOG.md +12 -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wealthx/shadcn",
3
- "version": "1.5.36",
3
+ "version": "1.5.38",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -559,11 +559,21 @@
559
559
  "import": "./dist/components/ui/ai-assistant-drawer.mjs",
560
560
  "require": "./dist/components/ui/ai-assistant-drawer.js"
561
561
  },
562
+ "./support-agent": {
563
+ "types": "./src/components/ui/support-agent/index.tsx",
564
+ "import": "./dist/components/ui/support-agent/index.mjs",
565
+ "require": "./dist/components/ui/support-agent/index.js"
566
+ },
562
567
  "./ai-conversations": {
563
568
  "types": "./src/components/ui/ai-conversations/index.tsx",
564
569
  "import": "./dist/components/ui/ai-conversations/index.mjs",
565
570
  "require": "./dist/components/ui/ai-conversations/index.js"
566
571
  },
572
+ "./chat-input-area": {
573
+ "types": "./src/components/ui/chat-input-area.tsx",
574
+ "import": "./dist/components/ui/chat-input-area.mjs",
575
+ "require": "./dist/components/ui/chat-input-area.js"
576
+ },
567
577
  "./chat-widget-primitives": {
568
578
  "types": "./src/components/ui/chat-widget-primitives.tsx",
569
579
  "import": "./dist/components/ui/chat-widget-primitives.mjs",
@@ -115,6 +115,30 @@ export type {
115
115
  AiTaskSuggestion,
116
116
  } from "./ui/ai-assistant-drawer";
117
117
 
118
+ export {
119
+ SupportContextChip,
120
+ SupportSuggestedQuestion,
121
+ SupportStepGuideCard,
122
+ SupportArticleCard,
123
+ SupportAgentFAB,
124
+ SupportAgentPanel,
125
+ } from "./ui/support-agent";
126
+ export type {
127
+ SupportAgentContext,
128
+ SupportAgentStep,
129
+ SupportAgentRichContent,
130
+ SupportContextChipProps,
131
+ SupportSuggestedQuestionProps,
132
+ SupportStepGuideCardProps,
133
+ SupportArticleCardProps,
134
+ SupportAgentFABProps,
135
+ SupportAgentMessage,
136
+ SupportAgentPanelProps,
137
+ } from "./ui/support-agent";
138
+
139
+ export { ChatInputArea } from "./ui/chat-input-area";
140
+ export type { ChatInputAreaProps } from "./ui/chat-input-area";
141
+
118
142
  export {
119
143
  ChatWidgetLauncher,
120
144
  ChatWidgetHeader,
@@ -1,11 +1,11 @@
1
1
  import * as React from "react";
2
- import { Bot, RotateCcw, Send, X } from "lucide-react";
2
+ import { Bot, RotateCcw, X } from "lucide-react";
3
3
  import { cn } from "@/lib/utils";
4
4
  import { Sheet, SheetContent } from "@/components/ui/sheet";
5
5
  import { Button } from "@/components/ui/button";
6
6
  import { Badge } from "@/components/ui/badge";
7
+ import { ChatInputArea } from "@/components/ui/chat-input-area";
7
8
  import { Spinner } from "@/components/ui/spinner";
8
- import { Textarea } from "@/components/ui/textarea";
9
9
 
10
10
  /**
11
11
  * AiAssistantDrawer — WealthX DS (L5 Drawer)
@@ -66,6 +66,10 @@ export interface AiAssistantDrawerProps {
66
66
  isLoading?: boolean;
67
67
  /** Called when the user submits a message. Input is cleared after this fires. */
68
68
  onSendMessage?: (text: string) => void;
69
+ /** Called when the user selects files via the attachment button. */
70
+ onAttachFile?: (files: FileList) => void;
71
+ /** Called when the user selects images via the image upload button. */
72
+ onAttachImage?: (files: FileList) => void;
69
73
  /** Called when the user clicks the reload/reset button. */
70
74
  onReset?: () => void;
71
75
  className?: string;
@@ -193,16 +197,16 @@ export function AiAssistantDrawer({
193
197
  isStreaming = false,
194
198
  isLoading = false,
195
199
  onSendMessage,
200
+ onAttachFile,
201
+ onAttachImage,
196
202
  onReset,
197
203
  className,
198
204
  }: AiAssistantDrawerProps) {
199
205
  const [inputValue, setInputValue] = React.useState("");
200
206
  const messagesEndRef = React.useRef<HTMLDivElement>(null);
201
- const textareaRef = React.useRef<HTMLTextAreaElement>(null);
202
207
 
203
208
  const suggestions = taskSuggestions ?? DEFAULT_SUGGESTIONS;
204
209
  const hasMessages = messages.length > 0;
205
- const canSend = inputValue.trim().length > 0 && !isStreaming && !isLoading;
206
210
 
207
211
  // Auto-scroll to latest message
208
212
  React.useEffect(() => {
@@ -213,31 +217,16 @@ export function AiAssistantDrawer({
213
217
  });
214
218
  }, [messages.length]);
215
219
 
216
- // Auto-resize textarea on value change
217
- React.useEffect(() => {
218
- const el = textareaRef.current;
219
- if (!el) return;
220
- el.style.height = "auto";
221
- el.style.height = `${Math.min(el.scrollHeight, 120)}px`;
222
- }, [inputValue]);
223
-
224
- const handleSend = React.useCallback(() => {
225
- const text = inputValue.trim();
226
- if (!text || !canSend) return;
227
- onSendMessage?.(text);
228
- setInputValue("");
229
- }, [inputValue, canSend, onSendMessage]);
230
-
231
- const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
232
- if (e.key === "Enter" && !e.shiftKey) {
233
- e.preventDefault();
234
- handleSend();
235
- }
236
- };
220
+ const handleSend = React.useCallback(
221
+ (text: string) => {
222
+ onSendMessage?.(text);
223
+ setInputValue("");
224
+ },
225
+ [onSendMessage],
226
+ );
237
227
 
238
228
  const handleSuggestionSelect = (text: string) => {
239
229
  setInputValue(text);
240
- textareaRef.current?.focus();
241
230
  };
242
231
 
243
232
  return (
@@ -377,31 +366,15 @@ export function AiAssistantDrawer({
377
366
 
378
367
  {/* Footer — input bar */}
379
368
  <div className="border-t border-border p-3">
380
- <div className="flex items-end gap-2">
381
- <Textarea
382
- ref={textareaRef}
383
- value={inputValue}
384
- onChange={(e) => setInputValue(e.target.value)}
385
- onKeyDown={handleKeyDown}
386
- placeholder="Ask me anything… (Enter to send, Shift+Enter for new line)"
387
- rows={1}
388
- disabled={isLoading}
389
- className="min-h-0 flex-1 resize-none overflow-hidden py-2 text-sm"
390
- />
391
- <Button
392
- size="icon"
393
- onClick={handleSend}
394
- disabled={!canSend}
395
- className="shrink-0"
396
- title="Send"
397
- >
398
- <Send className="size-4" />
399
- <span className="sr-only">Send message</span>
400
- </Button>
401
- </div>
402
- <p className="mt-1.5 text-[10px] text-muted-foreground">
403
- Enter to send · Shift+Enter for new line
404
- </p>
369
+ <ChatInputArea
370
+ value={inputValue}
371
+ onChange={setInputValue}
372
+ onSend={handleSend}
373
+ onAttachFile={onAttachFile}
374
+ onAttachImage={onAttachImage}
375
+ disabled={isLoading || isStreaming}
376
+ placeholder="Ask me anything…"
377
+ />
405
378
  </div>
406
379
  </SheetContent>
407
380
  </Sheet>
@@ -107,6 +107,10 @@ export interface ConversationsPageProps {
107
107
  onSendEmail?: (payload: AiConvEmailPayload) => void;
108
108
  onTakeOver?: () => void;
109
109
  onLetAiHandle?: () => void;
110
+ /** Called when the user selects files via the attachment button in the composer. */
111
+ onAttachFile?: (files: FileList) => void;
112
+ /** Called when the user selects images via the image upload button in the composer. */
113
+ onAttachImage?: (files: FileList) => void;
110
114
  /** Pre-fills the email Subject field with "Re: [emailReplySubject]" for reply threads. */
111
115
  emailReplySubject?: string;
112
116
  onReopen?: () => void;
@@ -168,6 +172,8 @@ export function ConversationsPage({
168
172
  onSendEmail,
169
173
  onTakeOver,
170
174
  onLetAiHandle,
175
+ onAttachFile,
176
+ onAttachImage,
171
177
  emailReplySubject,
172
178
  onReopen,
173
179
  onMarkUrgent,
@@ -187,7 +193,7 @@ export function ConversationsPage({
187
193
  className,
188
194
  }: ConversationsPageProps) {
189
195
  const [mobilePanel, setMobilePanel] = useState<"list" | "chat" | "lead">(
190
- "list"
196
+ "list",
191
197
  );
192
198
 
193
199
  const handleSelectConversation = (id: string) => {
@@ -222,7 +228,7 @@ export function ConversationsPage({
222
228
  onLoadMore={onLoadMore}
223
229
  className={cn(
224
230
  "shrink-0 md:w-[320px]",
225
- mobilePanel === "list" ? "flex w-full md:flex" : "hidden md:flex"
231
+ mobilePanel === "list" ? "flex w-full md:flex" : "hidden md:flex",
226
232
  )}
227
233
  />
228
234
 
@@ -244,6 +250,8 @@ export function ConversationsPage({
244
250
  onSendEmail={onSendEmail}
245
251
  onTakeOver={onTakeOver}
246
252
  onLetAiHandle={onLetAiHandle}
253
+ onAttachFile={onAttachFile}
254
+ onAttachImage={onAttachImage}
247
255
  emailReplySubject={emailReplySubject}
248
256
  onReopen={onReopen}
249
257
  onMarkUrgent={onMarkUrgent}
@@ -259,14 +267,14 @@ export function ConversationsPage({
259
267
  "min-w-0 flex-1 border-r border-border",
260
268
  mobilePanel === "chat"
261
269
  ? "flex flex-col md:flex"
262
- : "hidden md:flex"
270
+ : "hidden md:flex",
263
271
  )}
264
272
  />
265
273
  ) : (
266
274
  <div
267
275
  className={cn(
268
276
  "min-w-0 flex-1 items-center justify-center border-r border-border bg-muted/10",
269
- mobilePanel === "chat" ? "flex md:flex" : "hidden md:flex"
277
+ mobilePanel === "chat" ? "flex md:flex" : "hidden md:flex",
270
278
  )}
271
279
  >
272
280
  <div className="flex flex-col items-center gap-2 text-muted-foreground">
@@ -285,7 +293,7 @@ export function ConversationsPage({
285
293
  ? "flex w-full shrink-0 flex-col"
286
294
  : "hidden",
287
295
  "md:block md:shrink-0 md:overflow-hidden md:transition-[width] md:duration-200 md:ease-in-out",
288
- showLeadPanel ? "md:w-[320px]" : "md:w-0"
296
+ showLeadPanel ? "md:w-[320px]" : "md:w-0",
289
297
  )}
290
298
  >
291
299
  <LeadInfoPanel
@@ -364,13 +372,13 @@ export function AiConvAssignAdvisorDialog({
364
372
  const [roleFilter, setRoleFilter] = useState("");
365
373
 
366
374
  const roles = Array.from(
367
- new Set(advisors.map((a) => a.role).filter(Boolean))
375
+ new Set(advisors.map((a) => a.role).filter(Boolean)),
368
376
  ) as string[];
369
377
 
370
378
  const filtered = advisors.filter(
371
379
  (a) =>
372
380
  a.name.toLowerCase().includes(search.toLowerCase()) &&
373
- (!roleFilter || a.role === roleFilter)
381
+ (!roleFilter || a.role === roleFilter),
374
382
  );
375
383
 
376
384
  const handleOpenChange = (v: boolean) => {
@@ -436,7 +444,7 @@ export function AiConvAssignAdvisorDialog({
436
444
  onClick={() => onValueChange(advisor.id)}
437
445
  className={cn(
438
446
  "flex w-full items-center gap-3 px-3 py-2.5 text-left text-sm transition-colors hover:bg-muted",
439
- value === advisor.id && "bg-muted font-medium"
447
+ value === advisor.id && "bg-muted font-medium",
440
448
  )}
441
449
  >
442
450
  <Avatar size="sm">
@@ -39,7 +39,7 @@ import {
39
39
  import { Separator } from "@/components/ui/separator";
40
40
  import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
41
41
  import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
42
- import { Textarea } from "@/components/ui/textarea";
42
+ import { ChatInputArea } from "@/components/ui/chat-input-area";
43
43
  import type {
44
44
  AiConvChannel,
45
45
  AiConvContact,
@@ -88,6 +88,10 @@ export interface ChatComposerProps {
88
88
  onSendEmail?: (payload: AiConvEmailPayload) => void;
89
89
  onTakeOver?: () => void;
90
90
  onLetAiHandle?: () => void;
91
+ /** Called when the user selects files via the attachment button. */
92
+ onAttachFile?: (files: FileList) => void;
93
+ /** Called when the user selects images via the image upload button. */
94
+ onAttachImage?: (files: FileList) => void;
91
95
  /** Pre-fills the Subject field with "Re: [emailReplySubject]" for email threads. */
92
96
  emailReplySubject?: string;
93
97
  className?: string;
@@ -114,7 +118,7 @@ function ComposerToolbarButton({
114
118
  "flex size-7 items-center justify-center transition-colors",
115
119
  pressed
116
120
  ? "bg-foreground text-background"
117
- : "text-muted-foreground hover:bg-muted hover:text-foreground"
121
+ : "text-muted-foreground hover:bg-muted hover:text-foreground",
118
122
  )}
119
123
  >
120
124
  <Icon className="size-3.5" />
@@ -156,7 +160,7 @@ function ComposerLinkPopover({
156
160
  "flex size-7 items-center justify-center transition-colors",
157
161
  editor?.isActive("link")
158
162
  ? "bg-foreground text-background"
159
- : "text-muted-foreground hover:bg-muted hover:text-foreground"
163
+ : "text-muted-foreground hover:bg-muted hover:text-foreground",
160
164
  )}
161
165
  >
162
166
  <Link2 className="size-3.5" />
@@ -210,6 +214,8 @@ export function ChatComposer({
210
214
  onSendEmail,
211
215
  onTakeOver,
212
216
  onLetAiHandle,
217
+ onAttachFile,
218
+ onAttachImage,
213
219
  emailReplySubject,
214
220
  className,
215
221
  }: ChatComposerProps) {
@@ -217,16 +223,16 @@ export function ChatComposer({
217
223
  // Force chat when email isn't integrated so the panel never lands on a hidden tab.
218
224
  const initialChannelRef = React.useRef(channelProp);
219
225
  const [channel, setChannel] = React.useState<AiConvChannel>(
220
- isEmailIntegrated ? channelProp : "chat"
226
+ isEmailIntegrated ? channelProp : "chat",
221
227
  );
222
228
  const [emailTo, setEmailTo] = React.useState(contactEmail);
223
229
  const [emailCc, setEmailCc] = React.useState("");
224
230
  const [showCc, setShowCc] = React.useState(false);
225
231
  const [emailSubject, setEmailSubject] = React.useState(
226
- emailReplySubject ? `Re: ${emailReplySubject}` : ""
232
+ emailReplySubject ? `Re: ${emailReplySubject}` : "",
227
233
  );
228
234
  const [emailMode, setEmailMode] = React.useState<"reply" | "new">(
229
- emailReplySubject ? "reply" : "new"
235
+ emailReplySubject ? "reply" : "new",
230
236
  );
231
237
 
232
238
  const [, forceUpdate] = React.useReducer((x: number) => x + 1, 0);
@@ -272,7 +278,7 @@ export function ChatComposer({
272
278
  <div
273
279
  className={cn(
274
280
  "flex flex-col border-t border-border bg-background",
275
- className
281
+ className,
276
282
  )}
277
283
  >
278
284
  {isEmailIntegrated && (
@@ -316,7 +322,7 @@ export function ChatComposer({
316
322
  <div
317
323
  className={cn(
318
324
  "flex flex-col",
319
- channel !== "email" && "invisible pointer-events-none"
325
+ channel !== "email" && "invisible pointer-events-none",
320
326
  )}
321
327
  aria-hidden={channel !== "email"}
322
328
  >
@@ -432,29 +438,26 @@ export function ChatComposer({
432
438
  {/* Chat compose — absolute overlay, fills exact same height as email panel */}
433
439
  {channel === "chat" && (
434
440
  <div className="absolute inset-0 flex flex-col gap-3 p-4">
435
- <Textarea
441
+ <ChatInputArea
436
442
  value={inputValue}
437
- onChange={(e) => onInputChange?.(e.target.value)}
438
- placeholder="Reply to lead..."
439
- className="min-h-0 flex-1 resize-none text-base"
443
+ onChange={(v) => onInputChange?.(v)}
444
+ onSend={(text) => onSend?.(text)}
445
+ onAttachFile={onAttachFile}
446
+ onAttachImage={onAttachImage}
447
+ placeholder="Reply to lead…"
448
+ hint={false}
440
449
  />
441
- <div className="flex items-center justify-between">
442
- {initialChannelRef.current !== "email" && (
443
- <Button variant="outline" size="sm" onClick={onLetAiHandle}>
444
- <Bot className="mr-1.5 size-3.5" />
445
- Let AI Handle
446
- </Button>
447
- )}
450
+ {initialChannelRef.current !== "email" && (
448
451
  <Button
452
+ variant="outline"
449
453
  size="sm"
450
- className="ml-auto"
451
- onClick={() => onSend?.(inputValue)}
452
- disabled={!inputValue.trim()}
454
+ className="self-start"
455
+ onClick={onLetAiHandle}
453
456
  >
454
- <Send className="mr-1.5 size-3.5" />
455
- Send
457
+ <Bot className="mr-1.5 size-3.5" />
458
+ Let AI Handle
456
459
  </Button>
457
- </div>
460
+ )}
458
461
  </div>
459
462
  )}
460
463
  </div>
@@ -486,6 +489,10 @@ export interface ChatThreadProps {
486
489
  onSendEmail?: (payload: AiConvEmailPayload) => void;
487
490
  onTakeOver?: () => void;
488
491
  onLetAiHandle?: () => void;
492
+ /** Called when the user selects files via the attachment button in the composer. */
493
+ onAttachFile?: (files: FileList) => void;
494
+ /** Called when the user selects images via the image upload button in the composer. */
495
+ onAttachImage?: (files: FileList) => void;
489
496
  /** Pre-fills the email Subject field with "Re: [emailReplySubject]" for reply threads. */
490
497
  emailReplySubject?: string;
491
498
  onReopen?: () => void;
@@ -521,6 +528,8 @@ export function ChatThread({
521
528
  onSendEmail,
522
529
  onTakeOver,
523
530
  onLetAiHandle,
531
+ onAttachFile,
532
+ onAttachImage,
524
533
  emailReplySubject,
525
534
  onReopen,
526
535
  onMarkUrgent,
@@ -598,7 +607,7 @@ export function ChatThread({
598
607
  <div
599
608
  className={cn(
600
609
  PANEL_HEADER_HEIGHT,
601
- "flex items-center gap-3 border-b border-border px-4"
610
+ "flex items-center gap-3 border-b border-border px-4",
602
611
  )}
603
612
  >
604
613
  {onBack && (
@@ -651,7 +660,7 @@ export function ChatThread({
651
660
  <DropdownMenuTrigger
652
661
  className={cn(
653
662
  buttonVariants({ variant: "ghost", size: "icon" }),
654
- "size-8"
663
+ "size-8",
655
664
  )}
656
665
  aria-label="More actions"
657
666
  >
@@ -762,6 +771,8 @@ export function ChatThread({
762
771
  onSendEmail={onSendEmail}
763
772
  onTakeOver={onTakeOver}
764
773
  onLetAiHandle={onLetAiHandle}
774
+ onAttachFile={onAttachFile}
775
+ onAttachImage={onAttachImage}
765
776
  emailReplySubject={emailReplySubject}
766
777
  />
767
778
  )}