@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wealthx/shadcn",
3
- "version": "1.5.11",
3
+ "version": "1.5.13",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -949,9 +949,9 @@
949
949
  "require": "./dist/components/ui/transactions-summary-block.js"
950
950
  },
951
951
  "./resource-center": {
952
- "types": "./src/components/ui/resource-center.tsx",
953
- "import": "./dist/components/ui/resource-center.mjs",
954
- "require": "./dist/components/ui/resource-center.js"
952
+ "types": "./src/components/ui/resource-center/index.tsx",
953
+ "import": "./dist/components/ui/resource-center/index.mjs",
954
+ "require": "./dist/components/ui/resource-center/index.js"
955
955
  }
956
956
  }
957
957
  }
@@ -1262,6 +1262,7 @@ export {
1262
1262
  ResourceCarousel,
1263
1263
  ResourceCenterHeader,
1264
1264
  ResourceDocumentCard,
1265
+ ResourceEmailEditorDialog,
1265
1266
  ResourceEmailTemplateCard,
1266
1267
  ResourceModal,
1267
1268
  ResourceVideoCard,
@@ -1271,6 +1272,7 @@ export type {
1271
1272
  ResourceCenterHeaderProps,
1272
1273
  ResourceDocumentCardProps,
1273
1274
  ResourceDocumentItem,
1275
+ ResourceEmailEditorDialogProps,
1274
1276
  ResourceEmailTemplateCardProps,
1275
1277
  ResourceEmailTemplateItem,
1276
1278
  ResourceModalAttachment,
@@ -659,16 +659,31 @@ export function ChatBubble({ message, className }: ChatBubbleProps) {
659
659
  // ChatComposer
660
660
  // ---------------------------------------------------------------------------
661
661
 
662
+ export interface AiConvEmailPayload {
663
+ content: string;
664
+ to: string;
665
+ cc: string;
666
+ subject: string;
667
+ }
668
+
662
669
  export interface ChatComposerProps {
663
670
  mode: AiConvMode;
664
671
  /** Active reply channel. Defaults to "chat". */
665
672
  channel?: AiConvChannel;
666
673
  onChannelChange?: (channel: AiConvChannel) => void;
674
+ /**
675
+ * When true, the Email tab is shown in the composer. Defaults to false —
676
+ * consumers must opt in once their tenant's email integration is wired up.
677
+ */
678
+ isEmailIntegrated?: boolean;
667
679
  /** Lead's email address — pre-fills the To field in email compose. */
668
680
  contactEmail?: string;
669
681
  inputValue?: string;
670
682
  onInputChange?: (v: string) => void;
671
- onSend?: (v: string) => void;
683
+ /** Fired when the user sends a chat message. */
684
+ onSend?: (content: string) => void;
685
+ /** Fired when the user sends an email. */
686
+ onSendEmail?: (payload: AiConvEmailPayload) => void;
672
687
  onTakeOver?: () => void;
673
688
  onLetAiHandle?: () => void;
674
689
  className?: string;
@@ -678,16 +693,21 @@ export function ChatComposer({
678
693
  mode,
679
694
  channel: channelProp = "chat",
680
695
  onChannelChange,
696
+ isEmailIntegrated = false,
681
697
  contactEmail = "",
682
698
  inputValue = "",
683
699
  onInputChange,
684
700
  onSend,
701
+ onSendEmail,
685
702
  onTakeOver,
686
703
  onLetAiHandle,
687
704
  className,
688
705
  }: ChatComposerProps) {
689
- // Semi-controlled: owns channel state for uncontrolled usage, notifies parent on change
690
- const [channel, setChannel] = React.useState<AiConvChannel>(channelProp);
706
+ // Semi-controlled: owns channel state for uncontrolled usage, notifies parent on change.
707
+ // Force chat when email isn't integrated so the panel never lands on a hidden tab.
708
+ const [channel, setChannel] = React.useState<AiConvChannel>(
709
+ isEmailIntegrated ? channelProp : "chat",
710
+ );
691
711
  const [emailTo, setEmailTo] = React.useState(contactEmail);
692
712
  const [emailCc, setEmailCc] = React.useState("");
693
713
  const [showCc, setShowCc] = React.useState(false);
@@ -754,23 +774,25 @@ export function ChatComposer({
754
774
  className,
755
775
  )}
756
776
  >
757
- <div className="border-b border-border px-3 py-2">
758
- <Tabs
759
- value={channel}
760
- onValueChange={(v) => v && handleChannelChange(v as AiConvChannel)}
761
- >
762
- <TabsList variant="default" className="w-full">
763
- <TabsTrigger value="chat" className="flex-1 gap-1.5">
764
- <MessageSquare className="size-3.5" />
765
- Chat
766
- </TabsTrigger>
767
- <TabsTrigger value="email" className="flex-1 gap-1.5">
768
- <Mail className="size-3.5" />
769
- Email
770
- </TabsTrigger>
771
- </TabsList>
772
- </Tabs>
773
- </div>
777
+ {isEmailIntegrated && (
778
+ <div className="border-b border-border px-3 py-2">
779
+ <Tabs
780
+ value={channel}
781
+ onValueChange={(v) => v && handleChannelChange(v as AiConvChannel)}
782
+ >
783
+ <TabsList variant="default" className="w-full">
784
+ <TabsTrigger value="chat" className="flex-1 gap-1.5">
785
+ <MessageSquare className="size-3.5" />
786
+ Chat
787
+ </TabsTrigger>
788
+ <TabsTrigger value="email" className="flex-1 gap-1.5">
789
+ <Mail className="size-3.5" />
790
+ Email
791
+ </TabsTrigger>
792
+ </TabsList>
793
+ </Tabs>
794
+ </div>
795
+ )}
774
796
 
775
797
  {mode === "ai" ? (
776
798
  <div className="flex items-center gap-2 bg-muted/30 px-4 py-2.5 text-[12px] text-muted-foreground">
@@ -867,7 +889,14 @@ export function ChatComposer({
867
889
  </div>
868
890
  <Button
869
891
  size="sm"
870
- onClick={() => onSend?.(inputValue)}
892
+ onClick={() =>
893
+ onSendEmail?.({
894
+ content: inputValue,
895
+ to: emailTo,
896
+ cc: emailCc,
897
+ subject: emailSubject,
898
+ })
899
+ }
871
900
  disabled={!inputValue.trim() || !emailTo.trim()}
872
901
  >
873
902
  <Send className="mr-1.5 size-3.5" />
@@ -920,9 +949,16 @@ export interface ChatThreadProps {
920
949
  /** Active reply channel — "chat" (default) or "email". */
921
950
  channel?: AiConvChannel;
922
951
  onChannelChange?: (channel: AiConvChannel) => void;
952
+ /**
953
+ * When true, the Email tab is shown in the composer. Defaults to false.
954
+ */
955
+ isEmailIntegrated?: boolean;
923
956
  inputValue?: string;
924
957
  onInputChange?: (v: string) => void;
925
- onSend?: (v: string) => void;
958
+ /** Fired when the user sends a chat message. */
959
+ onSend?: (content: string) => void;
960
+ /** Fired when the user sends an email. */
961
+ onSendEmail?: (payload: AiConvEmailPayload) => void;
926
962
  onTakeOver?: () => void;
927
963
  onLetAiHandle?: () => void;
928
964
  onReopen?: () => void;
@@ -930,6 +966,12 @@ export interface ChatThreadProps {
930
966
  onUnmarkUrgent?: () => void;
931
967
  onArchive?: () => void;
932
968
  onAssignToAdvisor?: () => void;
969
+ /** True when older messages can be loaded (e.g. paginated history). */
970
+ hasMoreMessages?: boolean;
971
+ /** True while a `onLoadMoreMessages` request is in-flight. */
972
+ isLoadingMoreMessages?: boolean;
973
+ /** Fired when the consumer should fetch older messages. */
974
+ onLoadMoreMessages?: () => void;
933
975
  /** Mobile only — back to conversation list. */
934
976
  onBack?: () => void;
935
977
  /** Mobile only — show lead info panel. */
@@ -945,9 +987,11 @@ export function ChatThread({
945
987
  isAiTyping = false,
946
988
  channel,
947
989
  onChannelChange,
990
+ isEmailIntegrated,
948
991
  inputValue,
949
992
  onInputChange,
950
993
  onSend,
994
+ onSendEmail,
951
995
  onTakeOver,
952
996
  onLetAiHandle,
953
997
  onReopen,
@@ -955,6 +999,9 @@ export function ChatThread({
955
999
  onUnmarkUrgent,
956
1000
  onArchive,
957
1001
  onAssignToAdvisor,
1002
+ hasMoreMessages,
1003
+ isLoadingMoreMessages,
1004
+ onLoadMoreMessages,
958
1005
  onBack,
959
1006
  onShowLeadInfo,
960
1007
  className,
@@ -962,6 +1009,61 @@ export function ChatThread({
962
1009
  const aiIsHandling = mode === "ai";
963
1010
  const isClosed = status === "closed";
964
1011
 
1012
+ const scrollRef = React.useRef<HTMLDivElement>(null);
1013
+ // Captures scrollHeight just before older messages are prepended, so we can
1014
+ // restore the user's visible scroll offset once the new nodes render.
1015
+ const preLoadScrollHeightRef = React.useRef<number | null>(null);
1016
+
1017
+ const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
1018
+ if (!hasMoreMessages || isLoadingMoreMessages || !onLoadMoreMessages) {
1019
+ return;
1020
+ }
1021
+ if (e.currentTarget.scrollTop <= 80) {
1022
+ preLoadScrollHeightRef.current = e.currentTarget.scrollHeight;
1023
+ onLoadMoreMessages();
1024
+ }
1025
+ };
1026
+
1027
+ // Tracks the last "tail" message id so we can tell an append (new message,
1028
+ // tail changed) apart from a prepend (older history loaded, tail unchanged).
1029
+ const prevLastMessageIdRef = React.useRef<string | undefined>(undefined);
1030
+ const prevContactIdRef = React.useRef<string>(contact.id);
1031
+
1032
+ React.useLayoutEffect(() => {
1033
+ const el = scrollRef.current;
1034
+ if (!el) return;
1035
+
1036
+ // Prepend (older messages just loaded) — restore scroll so the user
1037
+ // stays anchored to the message they were reading.
1038
+ if (preLoadScrollHeightRef.current !== null) {
1039
+ el.scrollTop = el.scrollHeight - preLoadScrollHeightRef.current;
1040
+ preLoadScrollHeightRef.current = null;
1041
+ prevLastMessageIdRef.current = messages[messages.length - 1]?.id;
1042
+ prevContactIdRef.current = contact.id;
1043
+ return;
1044
+ }
1045
+
1046
+ const currentLastId = messages[messages.length - 1]?.id;
1047
+ const contactChanged = prevContactIdRef.current !== contact.id;
1048
+ const tailChanged = prevLastMessageIdRef.current !== currentLastId;
1049
+
1050
+ // Opening a conversation or appending a new message (sent, received,
1051
+ // or system) — pin to the bottom.
1052
+ if (contactChanged || tailChanged) {
1053
+ el.scrollTop = el.scrollHeight;
1054
+ }
1055
+
1056
+ prevLastMessageIdRef.current = currentLastId;
1057
+ prevContactIdRef.current = contact.id;
1058
+ }, [contact.id, messages]);
1059
+
1060
+ // Typing indicator adds DOM height — keep the view pinned to bottom.
1061
+ React.useLayoutEffect(() => {
1062
+ if (!isAiTyping) return;
1063
+ const el = scrollRef.current;
1064
+ if (el) el.scrollTop = el.scrollHeight;
1065
+ }, [isAiTyping]);
1066
+
965
1067
  return (
966
1068
  <div className={cn("flex flex-col bg-background", className)}>
967
1069
  {/* Header */}
@@ -1071,9 +1173,16 @@ export function ChatThread({
1071
1173
 
1072
1174
  {/* Messages */}
1073
1175
  <div
1176
+ ref={scrollRef}
1177
+ onScroll={handleScroll}
1074
1178
  className="flex flex-1 flex-col gap-4 overflow-y-auto p-4"
1075
1179
  tabIndex={0}
1076
1180
  >
1181
+ {isLoadingMoreMessages && (
1182
+ <div className="flex justify-center py-1 text-caption text-muted-foreground">
1183
+ Loading older messages...
1184
+ </div>
1185
+ )}
1077
1186
  {messages.length === 0 ? (
1078
1187
  <div className="flex flex-1 flex-col items-center justify-center gap-2 text-muted-foreground">
1079
1188
  <MessageSquare className="size-8 opacity-30" />
@@ -1120,10 +1229,12 @@ export function ChatThread({
1120
1229
  mode={mode}
1121
1230
  channel={channel}
1122
1231
  onChannelChange={onChannelChange}
1232
+ isEmailIntegrated={isEmailIntegrated}
1123
1233
  contactEmail={contact.email}
1124
1234
  inputValue={inputValue}
1125
1235
  onInputChange={onInputChange}
1126
1236
  onSend={onSend}
1237
+ onSendEmail={onSendEmail}
1127
1238
  onTakeOver={onTakeOver}
1128
1239
  onLetAiHandle={onLetAiHandle}
1129
1240
  />
@@ -1584,18 +1695,31 @@ export interface ConversationsPageProps {
1584
1695
  /** Active reply channel — "chat" (default) or "email". */
1585
1696
  channel?: AiConvChannel;
1586
1697
  onChannelChange?: (channel: AiConvChannel) => void;
1698
+ /**
1699
+ * When true, the Email tab is shown in the composer. Defaults to false.
1700
+ */
1701
+ isEmailIntegrated?: boolean;
1587
1702
  inputValue?: string;
1588
1703
  internalNotes?: string;
1589
1704
  showLeadPanel?: boolean;
1590
1705
  hasMore?: boolean;
1591
1706
  isLoadingMore?: boolean;
1707
+ /** True when older messages can be loaded for the active conversation. */
1708
+ hasMoreMessages?: boolean;
1709
+ /** True while a `onLoadMoreMessages` request is in-flight. */
1710
+ isLoadingMoreMessages?: boolean;
1711
+ /** Fired when the consumer should fetch older messages for the active conversation. */
1712
+ onLoadMoreMessages?: () => void;
1592
1713
  onSelectConversation?: (id: string) => void;
1593
1714
  onRead?: (id: string) => void;
1594
1715
  onSearchChange?: (v: string) => void;
1595
1716
  onFilterChange?: (f: AiConvFilterTab) => void;
1596
1717
  onChannelFilterChange?: (f: AiConvChannelFilter) => void;
1597
1718
  onInputChange?: (v: string) => void;
1598
- onSend?: (v: string) => void;
1719
+ /** Fired when the user sends a chat message. */
1720
+ onSend?: (content: string) => void;
1721
+ /** Fired when the user sends an email. */
1722
+ onSendEmail?: (payload: AiConvEmailPayload) => void;
1599
1723
  onTakeOver?: () => void;
1600
1724
  onLetAiHandle?: () => void;
1601
1725
  onReopen?: () => void;
@@ -1637,11 +1761,15 @@ export function ConversationsPage({
1637
1761
  onChannelFilterChange,
1638
1762
  channel,
1639
1763
  onChannelChange,
1764
+ isEmailIntegrated,
1640
1765
  inputValue,
1641
1766
  internalNotes,
1642
1767
  showLeadPanel = true,
1643
1768
  hasMore,
1644
1769
  isLoadingMore,
1770
+ hasMoreMessages,
1771
+ isLoadingMoreMessages,
1772
+ onLoadMoreMessages,
1645
1773
  isAiTyping,
1646
1774
  notesSaveStatus,
1647
1775
  onSelectConversation,
@@ -1650,6 +1778,7 @@ export function ConversationsPage({
1650
1778
  onFilterChange,
1651
1779
  onInputChange,
1652
1780
  onSend,
1781
+ onSendEmail,
1653
1782
  onTakeOver,
1654
1783
  onLetAiHandle,
1655
1784
  onReopen,
@@ -1723,9 +1852,11 @@ export function ConversationsPage({
1723
1852
  isAiTyping={isAiTyping}
1724
1853
  channel={channel}
1725
1854
  onChannelChange={onChannelChange}
1855
+ isEmailIntegrated={isEmailIntegrated}
1726
1856
  inputValue={inputValue}
1727
1857
  onInputChange={onInputChange}
1728
1858
  onSend={onSend}
1859
+ onSendEmail={onSendEmail}
1729
1860
  onTakeOver={onTakeOver}
1730
1861
  onLetAiHandle={onLetAiHandle}
1731
1862
  onReopen={onReopen}
@@ -1733,6 +1864,9 @@ export function ConversationsPage({
1733
1864
  onUnmarkUrgent={onUnmarkUrgent}
1734
1865
  onArchive={onArchive}
1735
1866
  onAssignToAdvisor={onAssignToAdvisor}
1867
+ hasMoreMessages={hasMoreMessages}
1868
+ isLoadingMoreMessages={isLoadingMoreMessages}
1869
+ onLoadMoreMessages={onLoadMoreMessages}
1736
1870
  onBack={() => setMobilePanel("list")}
1737
1871
  onShowLeadInfo={() => setMobilePanel("lead")}
1738
1872
  className={cn(