blixify-ui-web-mcp 0.1.3 → 0.1.18

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 (126) hide show
  1. package/lib/components/action/button/README.md +12 -11
  2. package/lib/components/action/button/button.stories.tsx +4 -10
  3. package/lib/components/action/buttonGroup/buttonGroup.stories.tsx +4 -10
  4. package/lib/components/action/iconButton/iconButton.stories.tsx +4 -10
  5. package/lib/components/action/radioList/radioList.stories.tsx +4 -10
  6. package/lib/components/action/slider/slider.stories.tsx +4 -10
  7. package/lib/components/action/textButton/textButton.stories.tsx +4 -10
  8. package/lib/components/action/toggle/toggle.stories.tsx +4 -10
  9. package/lib/components/advanced/collapse/collapse.stories.tsx +4 -10
  10. package/lib/components/advanced/infoWindow/infoWindow.stories.tsx +4 -10
  11. package/lib/components/advanced/popUp/README.md +21 -7
  12. package/lib/components/advanced/popUp/popUp.stories.tsx +4 -10
  13. package/lib/components/auth/bareSignIn/bareSignIn.stories.tsx +30 -10
  14. package/lib/components/auth/bareSignUp/bareSignUp.stories.tsx +4 -10
  15. package/lib/components/auth/signIn/signIn.stories.tsx +4 -10
  16. package/lib/components/auth/signUp/signUp.stories.tsx +4 -10
  17. package/lib/components/chart/areaChart/areaChart.stories.tsx +7 -13
  18. package/lib/components/chart/barChart/barChart.stories.tsx +7 -13
  19. package/lib/components/chart/boxPlotChart/boxPlotChart.stories.tsx +8 -14
  20. package/lib/components/chart/bubbleChart/bubbleChart.stories.tsx +8 -14
  21. package/lib/components/chart/card/card.stories.tsx +4 -10
  22. package/lib/components/chart/ganttChart/ganttChart.stories.tsx +4 -10
  23. package/lib/components/chart/heatmapChart/heatmapChart.stories.tsx +4 -10
  24. package/lib/components/chart/lineChart/lineChart.stories.tsx +4 -10
  25. package/lib/components/chart/pieChart/pieChart.stories.tsx +4 -10
  26. package/lib/components/chart/polarAreaChart/polarAreaChart.stories.tsx +8 -14
  27. package/lib/components/chart/radarChart/radarChart.stories.tsx +4 -10
  28. package/lib/components/chart/radialBar/radialChart.stories.tsx +4 -10
  29. package/lib/components/chart/rangeBarChart/rangeBarChart.stories.tsx +4 -10
  30. package/lib/components/chart/statsCard/statsCard.stories.tsx +4 -10
  31. package/lib/components/chart/treemapChart/treemapChart.stories.tsx +8 -14
  32. package/lib/components/chat/chatAnchor/chatAnchor.stories.tsx +13 -19
  33. package/lib/components/chat/chatBase/README.md +103 -22
  34. package/lib/components/chat/chatBase/chatBase.stories.tsx +652 -52
  35. package/lib/components/chat/chatDashboard/README.md +17 -15
  36. package/lib/components/chat/chatDashboard/chatDashboard.stories.tsx +356 -44
  37. package/lib/components/data/dataGraph/dataGraph.stories.tsx +4 -10
  38. package/lib/components/data/dataReference/dataReference.stories.tsx +4 -10
  39. package/lib/components/data/dataTemplate/README.md +88 -61
  40. package/lib/components/data/dataTemplate/dataModule.stories.tsx +4 -10
  41. package/lib/components/data/dataTemplate/dataTemplate.stories.tsx +4 -10
  42. package/lib/components/data/dataWrapper/README.md +46 -45
  43. package/lib/components/data/dataWrapper/dataWrapper.stories.tsx +258 -489
  44. package/lib/components/data/queryForm/queryForm.stories.tsx +4 -10
  45. package/lib/components/data/readQuery/readQuery.stories.tsx +4 -9
  46. package/lib/components/data/readQueryHoc/readQueryHoc.stories.tsx +4 -10
  47. package/lib/components/data/writeQuery/writeQuery.stories.tsx +4 -8
  48. package/lib/components/design/actionSection/actionSection.stories.tsx +6 -10
  49. package/lib/components/design/banner/banner.stories.tsx +4 -10
  50. package/lib/components/design/bannerAction/bannerAction.stories.tsx +4 -10
  51. package/lib/components/design/blogSection/blogSection.stories.tsx +4 -10
  52. package/lib/components/design/carouselSection/carouselSection.stories.tsx +4 -10
  53. package/lib/components/design/designList/designList.stories.tsx +4 -10
  54. package/lib/components/design/faq/faq.stories.tsx +4 -10
  55. package/lib/components/design/fileGrid/fileGrid.stories.tsx +4 -10
  56. package/lib/components/design/fileViewer/fileViewer.stories.tsx +4 -10
  57. package/lib/components/design/footerSection/footerSection.stories.tsx +4 -10
  58. package/lib/components/design/googleReview/googleReview.stories.tsx +4 -10
  59. package/lib/components/design/heroSection/heroSection.stories.tsx +4 -10
  60. package/lib/components/design/iconList/iconList.stories.tsx +4 -10
  61. package/lib/components/design/imageGallery/imageGallery.stories.tsx +4 -10
  62. package/lib/components/design/imageZoom/imageZoom.stories.tsx +4 -10
  63. package/lib/components/design/leftContent/leftContent.stories.tsx +4 -10
  64. package/lib/components/design/logoCloud/logoCloud.stories.tsx +4 -10
  65. package/lib/components/design/onboarding/onboarding.stories.tsx +4 -10
  66. package/lib/components/design/pricing/pricing.stories.tsx +4 -10
  67. package/lib/components/design/rightContent/rightContent.stories.tsx +4 -10
  68. package/lib/components/design/sectionList/sectionList.stories.tsx +4 -10
  69. package/lib/components/design/teams/teams.stories.tsx +4 -10
  70. package/lib/components/display/alert/alert.stories.tsx +4 -10
  71. package/lib/components/display/calendar/README.md +49 -0
  72. package/lib/components/display/calendar/calendar.stories.tsx +71 -28
  73. package/lib/components/display/clipboard/clipboard.stories.tsx +4 -10
  74. package/lib/components/display/detailList/detailList.stories.tsx +5 -10
  75. package/lib/components/display/drawer/drawer.stories.tsx +5 -10
  76. package/lib/components/display/emptyState/emptyState.stories.tsx +4 -10
  77. package/lib/components/display/feeds/feeds.stories.tsx +4 -10
  78. package/lib/components/display/flexTable/README.md +11 -7
  79. package/lib/components/display/flexTable/flexTable.stories.tsx +114 -11
  80. package/lib/components/display/grid/README.md +28 -27
  81. package/lib/components/display/grid/grid.stories.tsx +4 -10
  82. package/lib/components/display/kanban/kanban.stories.tsx +14 -20
  83. package/lib/components/display/list/README.md +30 -29
  84. package/lib/components/display/list/list.stories.tsx +4 -10
  85. package/lib/components/display/loading/loading.stories.tsx +5 -10
  86. package/lib/components/display/mapView/mapView.stories.tsx +4 -10
  87. package/lib/components/display/modal/modal.stories.tsx +4 -11
  88. package/lib/components/display/notification/notification.stories.tsx +4 -10
  89. package/lib/components/display/progress/progress.stories.tsx +5 -10
  90. package/lib/components/display/stepper/stepper.stories.tsx +5 -10
  91. package/lib/components/display/table/README.md +38 -37
  92. package/lib/components/display/table/table.stories.tsx +8 -14
  93. package/lib/components/display/tag/tag.stories.tsx +4 -10
  94. package/lib/components/input/address/address.stories.tsx +4 -10
  95. package/lib/components/input/canvas/canvas.stories.tsx +4 -10
  96. package/lib/components/input/checkbox/checkbox.stories.tsx +4 -10
  97. package/lib/components/input/datePicker/datePicker.stories.tsx +4 -10
  98. package/lib/components/input/form/form.stories.tsx +4 -10
  99. package/lib/components/input/markdown/markdown.stories.tsx +5 -10
  100. package/lib/components/input/monthYearPicker/monthYearPicker.stories.tsx +4 -10
  101. package/lib/components/input/netPromoterScore/netPromoterScore.stories.tsx +4 -10
  102. package/lib/components/input/rating/rating.stories.tsx +4 -10
  103. package/lib/components/input/select/README.md +30 -29
  104. package/lib/components/input/select/select.stories.tsx +4 -10
  105. package/lib/components/input/signature/signature.stories.tsx +4 -10
  106. package/lib/components/input/textArea/textArea.stories.tsx +4 -10
  107. package/lib/components/input/textInput/textinput.stories.tsx +4 -10
  108. package/lib/components/input/uploadInput/uploadInput.stories.tsx +135 -11
  109. package/lib/components/navigation/bars/bars.stories.tsx +4 -10
  110. package/lib/components/navigation/bottomTab/bottomTab.stories.tsx +5 -10
  111. package/lib/components/navigation/breadcrumb/breadcrumb.stories.tsx +4 -10
  112. package/lib/components/navigation/header/header.stories.tsx +24 -12
  113. package/lib/components/navigation/leftNav/leftNav.stories.tsx +4 -10
  114. package/lib/components/navigation/tabs/tabs.stories.tsx +4 -10
  115. package/lib/components/structure/column/column.stories.tsx +5 -10
  116. package/lib/components/structure/container/container.stories.tsx +5 -10
  117. package/lib/components/structure/link/link.stories.tsx +4 -10
  118. package/lib/components/structure/text/text.stories.tsx +5 -10
  119. package/lib/components/template/authPage/authPage.stories.tsx +6 -5
  120. package/lib/components/template/landingPage/landingPage.stories.tsx +8 -6
  121. package/lib/components/template/productListing/productListing.stories.tsx +7 -13
  122. package/lib/components/template/productOverview/productOverview.stories.tsx +5 -11
  123. package/lib/components/template/shoppingCart/shoppingCart.stories.tsx +6 -12
  124. package/lib/components/template/userHomePage/userHomePage.stories.tsx +7 -6
  125. package/lib/components/template/userRankingDashboard/userRankingDashboard.stories.tsx +5 -11
  126. package/package.json +1 -1
@@ -74,20 +74,22 @@ const headerElement = (
74
74
 
75
75
  ##### Optional props
76
76
 
77
- | Name | Type | Default | Description |
78
- | ----------------------- | -------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------- |
79
- | `mobileSelectedChat` | `boolean` | `false` | When `true`, hides the conversation list and shows the chat panel on mobile. Set to `false` to go back to the list. |
80
- | `searchMessage` | `string` | — | Current search input value. Pass a filtered conversation to `selectedConversation` to show results. |
81
- | `headerElement` | `ReactElement` | — | Replaces the default `ChatBase` header. Use this to show the other user's name and a mobile back button. |
82
- | `bodyElement` | `ReactElement` | — | Replaces the `ChatBase` message list. Useful for showing a loading spinner while fetching messages. |
83
- | `className` | `string` | — | Tailwind classes for the conversation list panel container |
84
- | `sizeClassName` | `string` | `h-[75vh]` | Tailwind classes controlling the height/width of the conversation list panel |
85
- | `chatBaseClassName` | `string` | — | Tailwind classes for the `ChatBase` container |
86
- | `chatBaseSizeClassName` | `string` | — | Tailwind classes controlling the height/width of the `ChatBase` panel |
87
- | `onSendMessage` | `(conversation: Conversation) => void` | — | Called with the updated conversation when a message is sent |
88
- | `onChangeMessage` | `(event: any) => void` | — | Called on every keystroke in the message input |
89
- | `onSearch` | `(searchMessage: string) => void` | — | Called when the search form is submitted inside `ChatBase` |
90
- | `onChangeSearch` | `(event: any) => void` | — | Called on every keystroke in the search input inside `ChatBase` |
77
+ | Name | Type | Default | Description |
78
+ | ----------------------- | -------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------- |
79
+ | `mobileSelectedChat` | `boolean` | `false` | When `true`, hides the conversation list and shows the chat panel on mobile. Set to `false` to go back to the list. |
80
+ | `disableMessageInput` | `boolean` | `false` | When `true`, message input is disabled. |
81
+ | `searchMessage` | `string` | — | Current search input value. Pass a filtered conversation to `selectedConversation` to show results. |
82
+ | `headerElement` | `ReactElement` | — | Replaces the default `ChatBase` header. Use this to show the other user's name and a mobile back button. |
83
+ | `bodyElement` | `ReactElement` | — | Replaces the `ChatBase` message list. Useful for showing a loading spinner while fetching messages. |
84
+ | `className` | `string` | — | Tailwind classes for the conversation list panel container |
85
+ | `sizeClassName` | `string` | `h-[75vh]` | Tailwind classes controlling the height/width of the conversation list panel |
86
+ | `chatBaseClassName` | `string` | — | Tailwind classes for the `ChatBase` container |
87
+ | `chatBaseSizeClassName` | `string` | — | Tailwind classes controlling the height/width of the `ChatBase` panel |
88
+ | `onSendMessage` | `(conversation: Conversation) => void` | — | Called with the updated conversation when a message is sent |
89
+ | `onChangeMessage` | `(event: any) => void` | — | Called on every keystroke in the message input |
90
+ | `onSearch` | `(searchMessage: string) => void` | — | Called when the search form is submitted inside `ChatBase` |
91
+ | `onChangeSearch` | `(event: any) => void` | — | Called on every keystroke in the search input inside `ChatBase` |
92
+ | `additionalAction` | `ChatBaseAction[]` | — | Extra items appended to the `⋮` actions menu in the chat panel. Each item requires `text` and `onClick`, with an optional `icon`. |
91
93
 
92
94
  ##### Conversation list behaviour
93
95
 
@@ -113,7 +115,7 @@ Each item in `conversationList` renders the other user's avatar and name (the me
113
115
 
114
116
  | Name | Type | Description |
115
117
  | -------------- | ------------------- | ----------------------------------------------------------------- |
116
- | `sender` | `User` | The user who sent the message |
118
+ | `sender` | `User` | The user who sent the message. |
117
119
  | `messageBody` | `string \| FileObj` | Text content or a file object for image/video messages |
118
120
  | `reactElement` | `ReactElement` | Optional — replaces `messageBody` rendering with a custom element |
119
121
  | `createdAt` | `Date` | Timestamp |
@@ -1,31 +1,41 @@
1
- import { ChevronLeftIcon } from "@heroicons/react/24/outline";
2
- import { storiesOf } from "@storybook/react";
3
- import moment from "moment";
1
+ import {
2
+ ChevronLeftIcon,
3
+ PencilSquareIcon,
4
+ TrashIcon,
5
+ } from "@heroicons/react/24/outline";
6
+ import dayjs from "../../dayjs";
4
7
  import React, { useEffect, useState } from "react";
8
+ import { v4 as uuidv4 } from "uuid";
5
9
  import { ChatDashboard } from ".";
6
10
  import { StorySection } from "../../storyUtils";
7
11
  import { IconButton } from "../../action/iconButton";
8
12
  import { Loading } from "../../display/loading";
9
- import { Conversation } from "../chatBase";
13
+ import { ChatBaseAction, Conversation } from "../chatBase";
10
14
  import { conversationList as seedConvoList, currentUser } from "../chatUtils";
11
- import README from "./README.md";
15
+ import { Button } from "../../action/button";
12
16
 
13
- const stories = storiesOf("Chat", module);
17
+ const meta = { title: "Chat/Chat Dashboard" };
18
+ export default meta;
14
19
 
15
- stories.addParameters({
16
- readme: {
17
- content: README,
18
- },
19
- });
20
20
 
21
21
  const sortByLatest = (list: Conversation[]) =>
22
22
  [...list].sort((a, b) =>
23
- moment(b.messageList[b.messageList.length - 1].createdAt).diff(
24
- moment(a.messageList[a.messageList.length - 1].createdAt)
25
- )
23
+ dayjs(b.messageList[b.messageList.length - 1].createdAt).diff(
24
+ dayjs(a.messageList[a.messageList.length - 1].createdAt),
25
+ ),
26
26
  );
27
27
 
28
- stories.add("Chat Dashboard", () => {
28
+ // INFO: demo statuses for renderConversationMeta / renderHeaderTags
29
+ const DEMO_STATUSES = ["active", "waiting", "closed", "assigned"];
30
+ const DEMO_BADGE_COLORS: Record<string, string> = {
31
+ active: "bg-green-100 text-green-700",
32
+ waiting: "bg-yellow-100 text-yellow-700",
33
+ closed: "bg-gray-100 text-gray-500",
34
+ assigned: "bg-blue-100 text-blue-700",
35
+ };
36
+
37
+ export const ChatDashboardStory = () => {
38
+ // INFO: Section 1 — Base chat state
29
39
  const [message, setMessage] = useState("");
30
40
  const [search, setSearch] = useState("");
31
41
  const [isLoading, setIsLoading] = useState(false);
@@ -38,11 +48,99 @@ stories.add("Chat Dashboard", () => {
38
48
  members: [],
39
49
  messageList: [],
40
50
  });
51
+ const [file, setFile] = useState<File | any>(undefined);
52
+
53
+ // INFO: Section 2 — Meta & Tags state
54
+ const [metaConvoList, setMetaConvoList] = useState<Conversation[]>([]);
55
+ const [metaConvo, setMetaConvo] = useState<Conversation>({
56
+ id: "",
57
+ members: [],
58
+ messageList: [],
59
+ });
60
+ const [metaMessage, setMetaMessage] = useState("");
61
+
62
+ // INFO: Section 3 — Pagination state
63
+ const [paginationConvoList, setPaginationConvoList] = useState<
64
+ Conversation[]
65
+ >([]);
66
+ const [paginationConvo, setPaginationConvo] = useState<Conversation>({
67
+ id: "",
68
+ members: [],
69
+ messageList: [],
70
+ });
71
+ const [paginationMessage, setPaginationMessage] = useState("");
72
+ const [page, setPage] = useState(1);
73
+ const [loadingConversations, setLoadingConversations] = useState(false);
74
+ const [loadingMessages, setLoadingMessages] = useState(false);
75
+ const [msgPage, setMsgPage] = useState(1);
76
+
77
+ // INFO: pageNum 0 generates 15 contacts to ensure the list overflows and is scrollable
78
+ const generateConversations = (pageNum: number): Conversation[] => {
79
+ const count = pageNum === 1 ? 15 : 5;
80
+ return Array.from({ length: count }, (_, i) => {
81
+ const idx = (pageNum - 1) * 5 + i;
82
+ const otherUser = {
83
+ id: `user-${idx}`,
84
+ name: `Contact ${idx + 1}`,
85
+ imageUrl: `https://ui-avatars.com/api/?name=C${idx + 1}&background=6366f1&color=fff`,
86
+ };
87
+ return {
88
+ id: uuidv4(),
89
+ members: [currentUser, otherUser],
90
+ messageList: Array.from({ length: 10 }, (_, j) => ({
91
+ sender: j % 2 === 0 ? otherUser : currentUser,
92
+ messageBody: `Message ${j + 1} from page ${pageNum}`,
93
+ createdAt: dayjs()
94
+ .subtract(10 - j, "minutes")
95
+ .toDate(),
96
+ })),
97
+ };
98
+ });
99
+ };
41
100
 
42
101
  useEffect(() => {
43
- const sorted = sortByLatest(seedConvoList);
102
+ // INFO: inject demo images of varying aspect ratios to exercise the bubble size cap
103
+ const demoImages = [
104
+ { id: 1015, w: 1600, h: 900, label: "Landscape (16:9)" },
105
+ { id: 1027, w: 900, h: 1600, label: "Portrait (9:16)" },
106
+ { id: 237, w: 1200, h: 1200, label: "Square (1:1)" },
107
+ ];
108
+ const withImages = seedConvoList.map((convo, idx) =>
109
+ idx === 0
110
+ ? {
111
+ ...convo,
112
+ messageList: [
113
+ ...convo.messageList,
114
+ ...demoImages.map((img) => ({
115
+ sender: convo.members.find((m) => m.id !== currentUser.id)!,
116
+ messageBody: {
117
+ fileName: `https://picsum.photos/id/${img.id}/${img.w}/${img.h}`,
118
+ token: img.label,
119
+ type: "image/jpeg",
120
+ },
121
+ createdAt: dayjs().subtract(demoImages.length - img.id, "minutes").toDate(),
122
+ })),
123
+ {
124
+ sender: convo.members.find((m) => m.id !== currentUser.id)!,
125
+ messageBody: {
126
+ fileName: "https://www.w3schools.com/html/mov_bbb.mp4",
127
+ token: "Sample video",
128
+ type: "video/mp4",
129
+ },
130
+ createdAt: dayjs().toDate(),
131
+ },
132
+ ],
133
+ }
134
+ : convo,
135
+ );
136
+ const sorted = sortByLatest(withImages);
44
137
  setConversationList(sorted);
45
138
  setConversation(sorted[0]);
139
+ setMetaConvoList(sorted);
140
+ setMetaConvo(sorted[0]);
141
+ const initial = generateConversations(1);
142
+ setPaginationConvoList(initial);
143
+ setPaginationConvo(initial[0]);
46
144
  }, []);
47
145
 
48
146
  const handleSelectConversation = (id: string) => {
@@ -58,10 +156,11 @@ stories.add("Chat Dashboard", () => {
58
156
  const handleSendMessage = (updated: Conversation) => {
59
157
  setConversation(updated);
60
158
  setMessage("");
61
- const updatedList = conversationList.map((c) =>
62
- c.id === updated.id ? updated : c
159
+ setConversationList(
160
+ sortByLatest(
161
+ conversationList.map((c) => (c.id === updated.id ? updated : c)),
162
+ ),
63
163
  );
64
- setConversationList(sortByLatest(updatedList));
65
164
  };
66
165
 
67
166
  const handleSearch = (searchMessage: string) => {
@@ -74,37 +173,46 @@ stories.add("Chat Dashboard", () => {
74
173
  messageList: conversation.messageList.filter(
75
174
  (m) =>
76
175
  typeof m.messageBody === "string" &&
77
- m.messageBody.toLowerCase().includes(searchMessage.toLowerCase())
176
+ m.messageBody.toLowerCase().includes(searchMessage.toLowerCase()),
78
177
  ),
79
178
  });
80
179
  }
81
180
  };
82
181
 
83
- const otherUser = conversation.members.find((m) => m.id !== currentUser.id);
84
-
85
- const headerElement = (
86
- <div className="w-full flex items-center h-12 text-white bg-primary-500">
87
- <IconButton
88
- icon={<ChevronLeftIcon className="w-6 h-6" />}
89
- background="bg-transparent"
90
- onClick={() => setMobileSelectedChat(false)}
91
- className={`${!mobileSelectedChat ? "hidden sm:flex" : ""} sm:hidden`}
92
- />
93
- <p className="ml-2 sm:ml-4 font-medium">{otherUser?.name ?? "Chat"}</p>
94
- </div>
95
- );
182
+ const handleSendUpload = (conv: Conversation, uploadedFile: File) => {
183
+ const fileUrl = URL.createObjectURL(uploadedFile);
184
+ const updated: Conversation = {
185
+ ...conv,
186
+ messageList: [
187
+ ...conv.messageList,
188
+ {
189
+ sender: currentUser,
190
+ messageBody: {
191
+ fileName: fileUrl,
192
+ token: uuidv4(),
193
+ type: uploadedFile.type,
194
+ },
195
+ createdAt: dayjs().toDate(),
196
+ },
197
+ ],
198
+ };
199
+ setConversation(updated);
200
+ setFile(undefined);
201
+ setConversationList(
202
+ sortByLatest(
203
+ conversationList.map((c) => (c.id === updated.id ? updated : c)),
204
+ ),
205
+ );
206
+ };
96
207
 
97
- const bodyElement = isLoading ? (
98
- <div className="w-full h-full flex items-center justify-center">
99
- <Loading />
100
- </div>
101
- ) : undefined;
208
+ const otherUser = conversation.members.find((m) => m.id !== currentUser.id);
102
209
 
103
210
  return (
104
211
  <>
212
+ {/* INFO: Section 1 — Base chat with file upload, search, actions */}
105
213
  <StorySection
106
- title="Chat Dashboard"
107
- description="A two-panel layout: a conversation list on the left and the active chat on the right. handleSelectedConversation is called with the conversation id when a list item is clicked. On mobile, the list and chat panels toggle via mobileSelectedChat — the header's back button sets it to false."
214
+ title="Base Chat"
215
+ description="Two-panel layout with conversation list, file upload, search, and actions. Mobile toggle via mobileSelectedChat."
108
216
  >
109
217
  <div className="w-full">
110
218
  <ChatDashboard
@@ -114,18 +222,222 @@ stories.add("Chat Dashboard", () => {
114
222
  selectedConversation={filteredConversation ?? conversation}
115
223
  conversationList={conversationList}
116
224
  mobileSelectedChat={mobileSelectedChat}
117
- sizeClassName="w-64 h-[60vh]"
225
+ sizeClassName="w-96 h-[60vh]"
118
226
  chatBaseSizeClassName="w-full h-[60vh]"
119
- headerElement={headerElement}
120
- bodyElement={bodyElement}
227
+ headerElement={
228
+ <div className="w-full flex items-center h-12 text-white bg-primary-500">
229
+ <IconButton
230
+ icon={<ChevronLeftIcon className="w-6 h-6" />}
231
+ background="bg-transparent"
232
+ onClick={() => setMobileSelectedChat(false)}
233
+ className={`${!mobileSelectedChat ? "hidden sm:flex" : ""} sm:hidden`}
234
+ />
235
+ <p className="ml-2 sm:ml-4 font-medium">
236
+ {otherUser?.name ?? "Chat"}
237
+ </p>
238
+ </div>
239
+ }
240
+ bodyElement={
241
+ isLoading ? (
242
+ <div className="w-full h-full flex items-center justify-center">
243
+ <Loading />
244
+ </div>
245
+ ) : undefined
246
+ }
247
+ fileUpload={{
248
+ allowFileUpload: true,
249
+ file,
250
+ acceptFileType: ".jpg,.jpeg,.png,.mp4,.webm",
251
+ onChangeUpload: (e) => {
252
+ if (e?.target?.files?.length > 0) setFile(e.target.files[0]);
253
+ else setFile(undefined);
254
+ },
255
+ onSendUpload: handleSendUpload,
256
+ }}
257
+ leftPanelHeaderElement={
258
+ <div className="p-4">
259
+ <Button
260
+ type="normal"
261
+ size="small"
262
+ text="New Chat"
263
+ icon={<PencilSquareIcon className="w-4 h-4" />}
264
+ onClick={() => alert("New Chat clicked")}
265
+ className="w-full"
266
+ />
267
+ </div>
268
+ }
121
269
  onChangeMessage={(e) => setMessage(e.target.value)}
122
270
  onSendMessage={handleSendMessage}
123
271
  onChangeSearch={(e) => setSearch(e.target.value)}
124
272
  onSearch={handleSearch}
125
273
  handleSelectedConversation={handleSelectConversation}
274
+ additionalAction={
275
+ [
276
+ {
277
+ text: "Clear Chat",
278
+ onClick: () =>
279
+ setConversation((prev) => ({ ...prev, messageList: [] })),
280
+ icon: <TrashIcon className="w-3 h-3" />,
281
+ },
282
+ ] as ChatBaseAction[]
283
+ }
284
+ />
285
+ </div>
286
+ </StorySection>
287
+
288
+ {/* INFO: Section 2 — renderConversationMeta + renderHeaderTags */}
289
+ <StorySection
290
+ title="Meta Badges & Header Tags"
291
+ description="renderConversationMeta shows status badges and 'Needs Reply' on left panel items. renderHeaderTags shows tags in the chat header."
292
+ >
293
+ <div className="w-full">
294
+ <ChatDashboard
295
+ value={metaMessage}
296
+ currentUser={currentUser}
297
+ selectedConversation={metaConvo}
298
+ conversationList={metaConvoList}
299
+ sizeClassName="w-96 h-[60vh]"
300
+ chatBaseSizeClassName="w-full h-[60vh]"
301
+ headerElement={
302
+ <div className="w-full flex items-center h-12 px-4 text-white bg-primary-500">
303
+ <p className="font-medium">
304
+ {metaConvo.members.find((m) => m.id !== currentUser.id)
305
+ ?.name ?? "Chat"}
306
+ </p>
307
+ </div>
308
+ }
309
+ renderConversationMeta={(convo) => {
310
+ const status =
311
+ DEMO_STATUSES[
312
+ Math.abs(convo.id.charCodeAt(0)) % DEMO_STATUSES.length
313
+ ];
314
+ const needsReply = convo.id.charCodeAt(0) % 2 === 0;
315
+ return (
316
+ <div className="flex gap-1 mt-1 flex-wrap">
317
+ <span
318
+ className={`text-[10px] px-1.5 py-0.5 rounded ${DEMO_BADGE_COLORS[status]}`}
319
+ >
320
+ {status}
321
+ </span>
322
+ {needsReply && (
323
+ <span className="text-[10px] px-1.5 py-0.5 rounded bg-orange-100 text-orange-700">
324
+ Needs Reply
325
+ </span>
326
+ )}
327
+ </div>
328
+ );
329
+ }}
330
+ renderHeaderTags={(convo) => {
331
+ const status =
332
+ DEMO_STATUSES[
333
+ Math.abs(convo.id.charCodeAt(0)) % DEMO_STATUSES.length
334
+ ];
335
+ return (
336
+ <div className="flex gap-1">
337
+ <span className="text-[10px] px-1.5 py-0.5 rounded bg-white/20 text-white">
338
+ {status}
339
+ </span>
340
+ <span className="text-[10px] px-1.5 py-0.5 rounded bg-white/20 text-white">
341
+ whatsapp
342
+ </span>
343
+ </div>
344
+ );
345
+ }}
346
+ onChangeMessage={(e) => setMetaMessage(e.target.value)}
347
+ onSendMessage={(updated) => {
348
+ setMetaConvo(updated);
349
+ setMetaMessage("");
350
+ setMetaConvoList((prev) =>
351
+ sortByLatest(
352
+ prev.map((c) => (c.id === updated.id ? updated : c)),
353
+ ),
354
+ );
355
+ }}
356
+ handleSelectedConversation={(id) => {
357
+ const found = metaConvoList.find((c) => c.id === id);
358
+ if (found) setMetaConvo(found);
359
+ }}
360
+ />
361
+ </div>
362
+ </StorySection>
363
+
364
+ {/* INFO: Section 3 — Pagination scroll callbacks */}
365
+ <StorySection
366
+ title="Pagination (Infinite Scroll)"
367
+ description="onConversationListScrollEnd loads more contacts with a spinner at the bottom. onMessageListScrollTop prepends older messages with a spinner at the top. Scroll to see both effects."
368
+ >
369
+ <div className="w-full">
370
+ <ChatDashboard
371
+ value={paginationMessage}
372
+ currentUser={currentUser}
373
+ selectedConversation={paginationConvo}
374
+ conversationList={paginationConvoList}
375
+ isLoadingConversations={loadingConversations}
376
+ isLoadingMessages={loadingMessages}
377
+ sizeClassName="w-96 h-[60vh]"
378
+ chatBaseSizeClassName="w-full h-[60vh]"
379
+ headerElement={
380
+ <div className="w-full flex items-center h-12 px-4 text-white bg-primary-500">
381
+ <p className="font-medium">
382
+ {paginationConvo.members.find(
383
+ (m) => m.id !== currentUser.id,
384
+ )?.name ?? "Chat"}
385
+ </p>
386
+ </div>
387
+ }
388
+ onConversationListScrollEnd={() => {
389
+ setLoadingConversations(true);
390
+ const nextPage = page + 1;
391
+ setTimeout(() => {
392
+ const more = generateConversations(nextPage);
393
+ setPaginationConvoList((prev) => [...prev, ...more]);
394
+ setPage(nextPage);
395
+ setLoadingConversations(false);
396
+ }, 1000);
397
+ }}
398
+ onMessageListScrollTop={() => {
399
+ setLoadingMessages(true);
400
+ const nextMsgPage = msgPage + 1;
401
+ setTimeout(() => {
402
+ setPaginationConvo((prev) => ({
403
+ ...prev,
404
+ messageList: [
405
+ ...Array.from({ length: 10 }, (_, i) => ({
406
+ sender:
407
+ i % 2 === 0
408
+ ? prev.members.find(
409
+ (m) => m.id !== currentUser.id,
410
+ )!
411
+ : currentUser,
412
+ messageBody: `Older message ${i + 1} from page ${nextMsgPage}`,
413
+ createdAt: dayjs()
414
+ .subtract(nextMsgPage * 10 + (10 - i), "minutes")
415
+ .toDate(),
416
+ })),
417
+ ...prev.messageList,
418
+ ],
419
+ }));
420
+ setMsgPage(nextMsgPage);
421
+ setLoadingMessages(false);
422
+ }, 1000);
423
+ }}
424
+ onChangeMessage={(e) => setPaginationMessage(e.target.value)}
425
+ onSendMessage={(updated) => {
426
+ setPaginationConvo(updated);
427
+ setPaginationMessage("");
428
+ setPaginationConvoList((prev) =>
429
+ sortByLatest(
430
+ prev.map((c) => (c.id === updated.id ? updated : c)),
431
+ ),
432
+ );
433
+ }}
434
+ handleSelectedConversation={(id) => {
435
+ const found = paginationConvoList.find((c) => c.id === id);
436
+ if (found) setPaginationConvo(found);
437
+ }}
126
438
  />
127
439
  </div>
128
440
  </StorySection>
129
441
  </>
130
442
  );
131
- });
443
+ }
@@ -1,23 +1,17 @@
1
- import { storiesOf } from "@storybook/react";
2
1
  import React from "react";
3
2
  import { StorySection } from "../../storyUtils";
4
- import README from "./README.md";
5
3
  import { DataGraph } from "./index";
6
4
 
7
- const stories = storiesOf("Data", module);
5
+ const meta = { title: "Data/Data Graph" };
6
+ export default meta;
8
7
 
9
- stories.addParameters({
10
- readme: {
11
- content: README,
12
- },
13
- });
14
8
 
15
9
  const workspace = {
16
10
  apiEndpoint: "http://localhost:3000/api",
17
11
  apiEndpointType: "mongoDB" as const,
18
12
  };
19
13
 
20
- stories.add("Data Graph", () => {
14
+ export const DataGraphStory = () => {
21
15
  return (
22
16
  <>
23
17
  <StorySection
@@ -100,4 +94,4 @@ stories.add("Data Graph", () => {
100
94
  </StorySection>
101
95
  </>
102
96
  );
103
- });
97
+ }
@@ -1,24 +1,18 @@
1
- import { storiesOf } from "@storybook/react";
2
1
  import axios from "axios";
3
2
  import React, { useState } from "react";
4
3
  import { DataReference } from "../dataReference/index";
5
4
  import { StorySection } from "../../storyUtils";
6
- import README from "./README.md";
7
5
 
8
- const stories = storiesOf("Data", module);
6
+ const meta = { title: "Data/Data Reference" };
7
+ export default meta;
9
8
 
10
- stories.addParameters({
11
- readme: {
12
- content: README,
13
- },
14
- });
15
9
 
16
10
  const devSettings = {
17
11
  apiEndpoint: "http://localhost:3000/api",
18
12
  apiEndpointType: "mondayServer" as const,
19
13
  };
20
14
 
21
- stories.add("Data Reference", () => {
15
+ export const DataReferenceStory = () => {
22
16
  const [singleValue, setSingleValue] = useState("");
23
17
  const [filteredValue, setFilteredValue] = useState("");
24
18
  const [multiValue, setMultiValue] = useState<string[]>([]);
@@ -122,4 +116,4 @@ stories.add("Data Reference", () => {
122
116
  </StorySection>
123
117
  </>
124
118
  );
125
- });
119
+ }