@wealthx/shadcn 1.5.11 → 1.5.13

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 (36) hide show
  1. package/.turbo/turbo-build.log +88 -88
  2. package/CHANGELOG.md +12 -0
  3. package/dist/{chunk-O5CP6VP6.mjs → chunk-BF5FKUF6.mjs} +104 -63
  4. package/dist/{chunk-ZMTCMP2G.mjs → chunk-EB626HVW.mjs} +70 -3
  5. package/dist/chunk-KICT4VQL.mjs +508 -0
  6. package/dist/chunk-V23CBULF.mjs +432 -0
  7. package/dist/components/ui/ai-conversations.js +70 -3
  8. package/dist/components/ui/ai-conversations.mjs +1 -1
  9. package/dist/components/ui/appointment-calendar-view.js +177 -176
  10. package/dist/components/ui/appointment-calendar-view.mjs +1 -1
  11. package/dist/components/ui/bank-statement-generate-dialog.js +209 -107
  12. package/dist/components/ui/bank-statement-generate-dialog.mjs +2 -1
  13. package/dist/components/ui/resource-center/index.js +1030 -0
  14. package/dist/components/ui/resource-center/index.mjs +29 -0
  15. package/dist/index.js +661 -403
  16. package/dist/index.mjs +16 -14
  17. package/dist/styles.css +1 -1
  18. package/package.json +4 -4
  19. package/src/components/index.tsx +2 -0
  20. package/src/components/ui/ai-conversations.tsx +157 -23
  21. package/src/components/ui/appointment-calendar-view.tsx +211 -199
  22. package/src/components/ui/bank-statement-generate-dialog.tsx +147 -96
  23. package/src/components/ui/resource-center/index.tsx +35 -0
  24. package/src/components/ui/resource-center/resource-cards.tsx +218 -0
  25. package/src/components/ui/resource-center/resource-carousel.tsx +122 -0
  26. package/src/components/ui/resource-center/resource-center-header.tsx +95 -0
  27. package/src/components/ui/resource-center/resource-email-editor-dialog.tsx +131 -0
  28. package/src/components/ui/resource-center/resource-modal.tsx +76 -0
  29. package/src/components/ui/resource-center/types.ts +81 -0
  30. package/src/styles/styles-css.ts +1 -1
  31. package/tsup.config.ts +1 -1
  32. package/dist/chunk-IODGRCQG.mjs +0 -438
  33. package/dist/chunk-XYWEGBAA.mjs +0 -348
  34. package/dist/components/ui/resource-center.js +0 -748
  35. package/dist/components/ui/resource-center.mjs +0 -24
  36. package/src/components/ui/resource-center.tsx +0 -539
@@ -1,24 +0,0 @@
1
- import {
2
- ResourceCarousel,
3
- ResourceCenterHeader,
4
- ResourceDocumentCard,
5
- ResourceEmailTemplateCard,
6
- ResourceModal,
7
- ResourceVideoCard
8
- } from "../../chunk-XYWEGBAA.mjs";
9
- import "../../chunk-LI2CTS5O.mjs";
10
- import "../../chunk-X6RC5UWB.mjs";
11
- import "../../chunk-XYSRRDBH.mjs";
12
- import "../../chunk-FRCTOAKZ.mjs";
13
- import "../../chunk-NOOEKOWY.mjs";
14
- import "../../chunk-R4HCRDU5.mjs";
15
- import "../../chunk-AFML43VJ.mjs";
16
- import "../../chunk-WNQUEZJF.mjs";
17
- export {
18
- ResourceCarousel,
19
- ResourceCenterHeader,
20
- ResourceDocumentCard,
21
- ResourceEmailTemplateCard,
22
- ResourceModal,
23
- ResourceVideoCard
24
- };
@@ -1,539 +0,0 @@
1
- import React, { useRef, useState } from "react";
2
- import { ChevronLeft, ChevronRight, Download, Play } from "lucide-react";
3
- import { cn } from "@/lib/utils";
4
- import { Badge } from "@/components/ui/badge";
5
- import { Button } from "@/components/ui/button";
6
- import {
7
- Dialog,
8
- DialogContent,
9
- DialogHeader,
10
- DialogTitle,
11
- } from "@/components/ui/dialog";
12
-
13
- /**
14
- * Resource Center — WealthX Backoffice
15
- *
16
- * Full-page resource hub with video cards, email template cards, document cards,
17
- * a hero header, and a generic carousel container.
18
- *
19
- * Component hierarchy:
20
- * Atom → (no separate atoms — primitives inlined per co-located pattern)
21
- * Molecule → ResourceVideoCard, ResourceEmailTemplateCard, ResourceDocumentCard
22
- * Organism → ResourceModal, ResourceCarousel, ResourceCenterHeader
23
- * Template → (composed by consumers)
24
- */
25
-
26
- // ---------------------------------------------------------------------------
27
- // ResourceModal
28
- // ---------------------------------------------------------------------------
29
-
30
- export interface ResourceModalAttachment {
31
- name: string;
32
- url: string;
33
- type?: string;
34
- }
35
-
36
- export interface ResourceModalProps {
37
- open: boolean;
38
- onClose: () => void;
39
- title: string;
40
- videoUrl?: string;
41
- tags?: string[];
42
- attachments?: ResourceModalAttachment[];
43
- }
44
-
45
- export function ResourceModal({
46
- open,
47
- onClose,
48
- title,
49
- videoUrl,
50
- tags,
51
- attachments,
52
- }: ResourceModalProps) {
53
- return (
54
- <Dialog open={open} onOpenChange={(v) => !v && onClose()}>
55
- <DialogContent
56
- size="2xl"
57
- className="flex flex-col gap-4 p-0 overflow-hidden"
58
- >
59
- <DialogHeader className="px-6 pt-6 pb-0">
60
- <DialogTitle className="text-lg font-semibold">{title}</DialogTitle>
61
- {tags && tags.length > 0 && (
62
- <div className="flex flex-wrap gap-1.5 pt-1">
63
- {tags.map((tag) => (
64
- <Badge key={tag} variant="secondary" className="text-xs">
65
- {tag}
66
- </Badge>
67
- ))}
68
- </div>
69
- )}
70
- </DialogHeader>
71
-
72
- {videoUrl && (
73
- <div className="aspect-video w-full bg-foreground">
74
- <iframe
75
- src={videoUrl}
76
- title={title}
77
- className="h-full w-full"
78
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
79
- allowFullScreen
80
- />
81
- </div>
82
- )}
83
-
84
- {attachments && attachments.length > 0 && (
85
- <div className="flex flex-col gap-2 px-6 pb-6">
86
- <p className="text-sm font-medium text-foreground">Attachments</p>
87
- <div className="flex flex-col gap-1">
88
- {attachments.map((attachment, i) => (
89
- <a
90
- key={i}
91
- href={attachment.url}
92
- download
93
- className="flex items-center gap-2 text-sm text-primary hover:underline"
94
- >
95
- <Download className="size-3.5 shrink-0" />
96
- {attachment.name}
97
- </a>
98
- ))}
99
- </div>
100
- </div>
101
- )}
102
-
103
- {(!attachments || attachments.length === 0) && <div className="pb-2" />}
104
- </DialogContent>
105
- </Dialog>
106
- );
107
- }
108
-
109
- // ---------------------------------------------------------------------------
110
- // ResourceVideoCard
111
- // ---------------------------------------------------------------------------
112
-
113
- export interface ResourceVideoItem {
114
- id: string;
115
- title: string;
116
- thumbnailUrl?: string;
117
- videoUrl?: string;
118
- duration?: string;
119
- tags?: string[];
120
- attachments?: ResourceModalAttachment[];
121
- }
122
-
123
- export interface ResourceVideoCardProps {
124
- video: ResourceVideoItem;
125
- }
126
-
127
- export function ResourceVideoCard({ video }: ResourceVideoCardProps) {
128
- const [modalOpen, setModalOpen] = useState(false);
129
-
130
- return (
131
- <>
132
- <button
133
- type="button"
134
- onClick={() => setModalOpen(true)}
135
- className="group relative flex w-full flex-col gap-2 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
136
- >
137
- {/* Thumbnail */}
138
- <div className="relative w-full overflow-hidden bg-muted aspect-video">
139
- {video.thumbnailUrl ? (
140
- <img
141
- src={video.thumbnailUrl}
142
- alt={video.title}
143
- className="h-full w-full object-cover transition-transform duration-200 group-hover:scale-105"
144
- />
145
- ) : (
146
- <div className="flex h-full w-full items-center justify-center">
147
- <Play className="size-10 text-muted-foreground opacity-40" />
148
- </div>
149
- )}
150
-
151
- {/* Play overlay on hover */}
152
- <div className="absolute inset-0 flex items-center justify-center bg-black/30 opacity-0 transition-opacity duration-200 group-hover:opacity-100">
153
- <div className="flex size-12 items-center justify-center bg-background/90">
154
- <Play className="size-5 text-foreground" />
155
- </div>
156
- </div>
157
-
158
- {/* Duration badge */}
159
- {video.duration && (
160
- <span className="absolute bottom-2 right-2 bg-black/70 px-1.5 py-0.5 text-xs font-medium text-white">
161
- {video.duration}
162
- </span>
163
- )}
164
- </div>
165
-
166
- {/* Title */}
167
- <p className="text-sm font-medium text-foreground leading-snug line-clamp-2 group-hover:text-primary">
168
- {video.title}
169
- </p>
170
- </button>
171
-
172
- <ResourceModal
173
- open={modalOpen}
174
- onClose={() => setModalOpen(false)}
175
- title={video.title}
176
- videoUrl={video.videoUrl}
177
- tags={video.tags}
178
- attachments={video.attachments}
179
- />
180
- </>
181
- );
182
- }
183
-
184
- // ---------------------------------------------------------------------------
185
- // ResourceEmailTemplateCard
186
- // ---------------------------------------------------------------------------
187
-
188
- export interface ResourceEmailTemplateItem {
189
- id: string;
190
- title: string;
191
- htmlContent?: string;
192
- type?: "system" | "company";
193
- isAddTemplate?: boolean;
194
- }
195
-
196
- export interface ResourceEmailTemplateCardProps {
197
- template: ResourceEmailTemplateItem;
198
- onClick?: () => void;
199
- }
200
-
201
- export function ResourceEmailTemplateCard({
202
- template,
203
- onClick,
204
- }: ResourceEmailTemplateCardProps) {
205
- if (template.isAddTemplate) {
206
- return (
207
- <button
208
- type="button"
209
- onClick={onClick}
210
- className={cn(
211
- "flex w-full flex-col items-center justify-center gap-3",
212
- "border-2 border-dashed border-border bg-muted/40",
213
- "aspect-[3/4] hover:border-primary hover:bg-muted/70 transition-colors",
214
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
215
- )}
216
- >
217
- <div className="flex size-10 items-center justify-center border-2 border-dashed border-muted-foreground/40">
218
- <span className="text-2xl text-muted-foreground/60">+</span>
219
- </div>
220
- <p className="text-sm font-medium text-muted-foreground">
221
- Add Template
222
- </p>
223
- </button>
224
- );
225
- }
226
-
227
- return (
228
- <button
229
- type="button"
230
- onClick={onClick}
231
- className="group relative flex w-full flex-col gap-2 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
232
- >
233
- {/* Preview area */}
234
- <div className="relative w-full overflow-hidden border border-border bg-background aspect-[3/4]">
235
- {template.htmlContent ? (
236
- <iframe
237
- srcDoc={template.htmlContent}
238
- title={template.title}
239
- className="h-full w-full pointer-events-none"
240
- style={{
241
- transform: "scale(0.5)",
242
- transformOrigin: "top left",
243
- width: "200%",
244
- height: "200%",
245
- }}
246
- sandbox="allow-same-origin"
247
- />
248
- ) : (
249
- <div className="flex h-full w-full items-center justify-center bg-muted">
250
- <p className="text-xs text-muted-foreground">
251
- No preview available
252
- </p>
253
- </div>
254
- )}
255
-
256
- {/* Hover overlay */}
257
- <div className="absolute inset-0 flex items-center justify-center bg-foreground/60 opacity-0 transition-opacity duration-200 group-hover:opacity-100">
258
- <span className="text-sm font-medium text-white">Use Template</span>
259
- </div>
260
- </div>
261
-
262
- {/* Meta */}
263
- <div className="flex items-center justify-between gap-2">
264
- <p className="text-sm font-medium text-foreground leading-snug line-clamp-1">
265
- {template.title}
266
- </p>
267
- {template.type && (
268
- <Badge variant="secondary" className="shrink-0 text-xs">
269
- {template.type === "system" ? "System" : "Company"}
270
- </Badge>
271
- )}
272
- </div>
273
- </button>
274
- );
275
- }
276
-
277
- // ---------------------------------------------------------------------------
278
- // ResourceDocumentCard
279
- // ---------------------------------------------------------------------------
280
-
281
- export interface ResourceDocumentItem {
282
- id: string;
283
- title: string;
284
- thumbnailUrl?: string;
285
- tags?: string[];
286
- downloadUrl?: string;
287
- pdfUrl?: string;
288
- }
289
-
290
- export interface ResourceDocumentCardProps {
291
- document: ResourceDocumentItem;
292
- onClick?: () => void;
293
- }
294
-
295
- export function ResourceDocumentCard({
296
- document,
297
- onClick,
298
- }: ResourceDocumentCardProps) {
299
- return (
300
- <div className="flex w-full flex-col gap-2">
301
- {/* PDF preview */}
302
- <button
303
- type="button"
304
- onClick={onClick}
305
- className="group relative w-full overflow-hidden border border-border bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring aspect-[3/4]"
306
- >
307
- {document.pdfUrl ? (
308
- <iframe
309
- src={document.pdfUrl}
310
- title={document.title}
311
- className="h-full w-full pointer-events-none"
312
- />
313
- ) : document.thumbnailUrl ? (
314
- <img
315
- src={document.thumbnailUrl}
316
- alt={document.title}
317
- className="h-full w-full object-cover"
318
- />
319
- ) : (
320
- <div className="flex h-full w-full flex-col items-center justify-center gap-2">
321
- <div className="flex items-center justify-center bg-muted-foreground/10 p-4">
322
- <Download className="size-8 text-muted-foreground opacity-50" />
323
- </div>
324
- <span className="text-xs text-muted-foreground">
325
- {document.title}
326
- </span>
327
- </div>
328
- )}
329
-
330
- <div className="absolute inset-0 bg-foreground/40 opacity-0 transition-opacity duration-200 group-hover:opacity-100" />
331
- </button>
332
-
333
- {/* Footer: tags + download */}
334
- <div className="flex items-center justify-between gap-2">
335
- <div className="flex flex-col gap-1 min-w-0">
336
- <p className="text-sm font-medium text-foreground leading-snug line-clamp-1">
337
- {document.title}
338
- </p>
339
- {document.tags && document.tags.length > 0 && (
340
- <div className="flex flex-wrap gap-1">
341
- {document.tags.map((tag) => (
342
- <Badge key={tag} variant="secondary" className="text-xs">
343
- {tag}
344
- </Badge>
345
- ))}
346
- </div>
347
- )}
348
- </div>
349
- {document.downloadUrl && (
350
- <a
351
- href={document.downloadUrl}
352
- download
353
- onClick={(e) => e.stopPropagation()}
354
- className="shrink-0"
355
- aria-label={`Download ${document.title}`}
356
- >
357
- <Button variant="outline" size="icon" className="size-8">
358
- <Download className="size-4" />
359
- </Button>
360
- </a>
361
- )}
362
- </div>
363
- </div>
364
- );
365
- }
366
-
367
- // ---------------------------------------------------------------------------
368
- // ResourceCarousel
369
- // ---------------------------------------------------------------------------
370
-
371
- export interface ResourceCarouselProps {
372
- title?: React.ReactNode;
373
- items: React.ReactNode[];
374
- headerAction?: React.ReactNode;
375
- className?: string;
376
- }
377
-
378
- export function ResourceCarousel({
379
- title,
380
- items,
381
- headerAction,
382
- className,
383
- }: ResourceCarouselProps) {
384
- const scrollRef = useRef<HTMLDivElement>(null);
385
-
386
- function scrollLeft() {
387
- scrollRef.current?.scrollBy({ left: -300, behavior: "smooth" });
388
- }
389
-
390
- function scrollRight() {
391
- scrollRef.current?.scrollBy({ left: 300, behavior: "smooth" });
392
- }
393
-
394
- return (
395
- <div className={cn("flex flex-col gap-4", className)}>
396
- {/* Header row */}
397
- {(title || headerAction) && (
398
- <div className="flex items-center justify-between gap-4">
399
- {title && (
400
- <h2 className="text-lg font-semibold text-foreground">{title}</h2>
401
- )}
402
- {headerAction && <div className="shrink-0">{headerAction}</div>}
403
- </div>
404
- )}
405
-
406
- {/* Carousel track with hover-reveal arrows */}
407
- {items.length === 0 ? (
408
- <p className="py-8 text-center text-sm text-muted-foreground">
409
- No items available
410
- </p>
411
- ) : (
412
- <div className="group relative">
413
- {/* Prev arrow */}
414
- <button
415
- type="button"
416
- onClick={scrollLeft}
417
- aria-label="Scroll left"
418
- className={cn(
419
- "absolute left-0 top-1/2 z-10 -translate-x-1/2 -translate-y-1/2",
420
- "flex size-9 items-center justify-center",
421
- "border border-border bg-background shadow-sm",
422
- "opacity-0 transition-opacity duration-200 group-hover:opacity-100",
423
- "hover:bg-muted focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
424
- )}
425
- >
426
- <ChevronLeft className="size-4 text-foreground" />
427
- </button>
428
-
429
- {/* Scroll container */}
430
- <div
431
- ref={scrollRef}
432
- className="flex gap-4 overflow-x-auto scroll-smooth pb-2"
433
- style={{ scrollbarWidth: "none" }}
434
- >
435
- {items.map((item, i) => (
436
- <div key={i} className="w-[220px] shrink-0">
437
- {item}
438
- </div>
439
- ))}
440
- </div>
441
-
442
- {/* Next arrow */}
443
- <button
444
- type="button"
445
- onClick={scrollRight}
446
- aria-label="Scroll right"
447
- className={cn(
448
- "absolute right-0 top-1/2 z-10 translate-x-1/2 -translate-y-1/2",
449
- "flex size-9 items-center justify-center",
450
- "border border-border bg-background shadow-sm",
451
- "opacity-0 transition-opacity duration-200 group-hover:opacity-100",
452
- "hover:bg-muted focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
453
- )}
454
- >
455
- <ChevronRight className="size-4 text-foreground" />
456
- </button>
457
- </div>
458
- )}
459
- </div>
460
- );
461
- }
462
-
463
- // ---------------------------------------------------------------------------
464
- // ResourceCenterHeader
465
- // ---------------------------------------------------------------------------
466
-
467
- export interface ResourceCenterHeaderProps {
468
- title: string;
469
- description: string;
470
- backgroundVideoUrl?: string;
471
- watchVideoUrl?: string;
472
- }
473
-
474
- export function ResourceCenterHeader({
475
- title,
476
- description,
477
- backgroundVideoUrl,
478
- watchVideoUrl,
479
- }: ResourceCenterHeaderProps) {
480
- const [watchModalOpen, setWatchModalOpen] = useState(false);
481
-
482
- return (
483
- <>
484
- <div
485
- className={cn(
486
- "relative flex min-h-[400px] w-full items-center overflow-hidden bg-foreground",
487
- )}
488
- >
489
- {/* Background video */}
490
- {backgroundVideoUrl ? (
491
- <video
492
- className="absolute inset-0 h-full w-full object-cover opacity-60 z-[1]"
493
- src={backgroundVideoUrl}
494
- autoPlay
495
- muted
496
- loop
497
- playsInline
498
- />
499
- ) : (
500
- <div className="absolute inset-0 bg-foreground z-[1]" />
501
- )}
502
-
503
- {/* Overlay tint for legibility */}
504
- <div className="absolute inset-0 bg-black/40 z-[1]" />
505
-
506
- {/* Content */}
507
- <div className="relative z-[2] flex flex-col gap-4 px-8 py-12 md:px-16 max-w-3xl">
508
- <h1 className="text-3xl font-bold text-background md:text-4xl">
509
- {title}
510
- </h1>
511
- <p className="text-base text-background/80 leading-relaxed">
512
- {description}
513
- </p>
514
- {watchVideoUrl && (
515
- <div className="pt-2">
516
- <Button
517
- variant="outline"
518
- className="border-background text-background bg-transparent hover:bg-background/10 hover:text-background"
519
- onClick={() => setWatchModalOpen(true)}
520
- >
521
- <Play className="mr-2 size-4" />
522
- Watch Now
523
- </Button>
524
- </div>
525
- )}
526
- </div>
527
- </div>
528
-
529
- {watchVideoUrl && (
530
- <ResourceModal
531
- open={watchModalOpen}
532
- onClose={() => setWatchModalOpen(false)}
533
- title={title}
534
- videoUrl={watchVideoUrl}
535
- />
536
- )}
537
- </>
538
- );
539
- }