blixify-ui-web-mcp 0.1.2 → 0.1.3

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 (25) hide show
  1. package/lib/components/chart/ganttChart/README.md +96 -110
  2. package/lib/components/chart/ganttChart/ganttChart.stories.tsx +132 -116
  3. package/lib/components/chart/heatmapChart/README.md +93 -82
  4. package/lib/components/chart/heatmapChart/heatmapChart.stories.tsx +118 -74
  5. package/lib/components/chat/chatAnchor/README.md +61 -79
  6. package/lib/components/chat/chatBase/README.md +166 -199
  7. package/lib/components/chat/chatBase/chatBase.stories.tsx +243 -265
  8. package/lib/components/chat/chatDashboard/README.md +87 -129
  9. package/lib/components/chat/chatDashboard/chatDashboard.stories.tsx +84 -107
  10. package/lib/components/data/dataGraph/README.md +135 -1
  11. package/lib/components/data/dataGraph/dataGraph.stories.tsx +86 -67
  12. package/lib/components/data/dataReference/README.md +141 -43
  13. package/lib/components/data/dataReference/dataReference.stories.tsx +107 -35
  14. package/lib/components/data/dataWrapper/README.md +2 -1
  15. package/lib/components/display/table/README.md +1 -1
  16. package/lib/components/navigation/bars/README.md +90 -17
  17. package/lib/components/navigation/bars/bars.stories.tsx +125 -51
  18. package/lib/components/navigation/breadcrumb/README.md +40 -20
  19. package/lib/components/navigation/breadcrumb/breadcrumb.stories.tsx +30 -4
  20. package/lib/components/navigation/header/README.md +67 -108
  21. package/lib/components/navigation/header/header.stories.tsx +143 -39
  22. package/lib/components/navigation/leftNav/README.md +121 -66
  23. package/lib/components/navigation/leftNav/leftNav.stories.tsx +176 -83
  24. package/lib/components/navigation/tabs/tabs.stories.tsx +53 -29
  25. package/package.json +1 -1
@@ -4,7 +4,7 @@
4
4
 
5
5
  <hr>
6
6
 
7
- Chat Dashboard
7
+ A two-panel chat layout with a scrollable conversation list on the left and a `ChatBase` chat window on the right. On mobile, the two panels toggle — the list hides when a conversation is selected and reappears when the user navigates back. The conversation list is sorted and updated automatically as new messages are sent.
8
8
 
9
9
  ##### Import
10
10
 
@@ -16,146 +16,104 @@ import { ChatDashboard } from "blixify-ui-web/components/chat/chatDashboard";
16
16
 
17
17
  ```jsx
18
18
  const [message, setMessage] = useState("");
19
- const [search, setSearch] = useState("");
20
- const [isLoading, setIsLoading] = useState(false);
19
+ const [conversation, setConversation] = useState<Conversation>(conversationList[0]);
20
+ const [conversationList, setConversationList] = useState<Conversation[]>(seedList);
21
21
  const [mobileSelectedChat, setMobileSelectedChat] = useState(false);
22
- const [filteredConversation, setFilteredConversation] =
23
- useState<Conversation | null>();
24
- const [conversation, setConversation] = useState<Conversation>({
25
- id: "",
26
- members: [],
27
- messageList: [],
28
- });
29
- const [conversationList, setConversationList] = useState<Conversation[]>([]);
30
-
31
- const handleOnSendMessage = (conversation: Conversation) => {
32
- setConversation({
33
- id: conversation.id,
34
- members: [...conversation.members],
35
- messageList: [...conversation.messageList],
36
- });
37
- setMessage("");
38
- const dupConvoList = conversationList.map((eachConvo, i) => {
39
- if (eachConvo.id === conversation.id) {
40
- return conversation;
41
- }
42
- return eachConvo;
43
- });
44
-
45
- handleSortConvoList(dupConvoList);
46
- };
47
-
48
- const handleSortConvoList = (convoList: Conversation[]) => {
49
- const sortedConvoList = convoList.sort((a, b) => {
50
- return moment(b.messageList[b.messageList.length - 1].createdAt).diff(
51
- moment(a.messageList[a.messageList.length - 1].createdAt)
52
- );
53
- });
54
- setConversationList(sortedConvoList);
55
- setConversation(sortedConvoList[0]);
56
- };
57
22
 
58
- const handleMessageList = (id: string) => {
59
- setIsLoading(true);
23
+ const handleSelectConversation = (id: string) => {
60
24
  setMobileSelectedChat(true);
61
- setTimeout(() => {
62
- setIsLoading(false);
63
- conversationList.map((eachConversation) => {
64
- if (eachConversation.id === id) {
65
- setConversation(eachConversation);
66
- }
67
- return null;
68
- });
69
- }, 1000);
25
+ const found = conversationList.find((c) => c.id === id);
26
+ if (found) setConversation(found);
70
27
  };
71
28
 
72
- const handleOnSearch = (searchMessage: string) => {
73
- if (searchMessage.length === 0) {
74
- setSearch("");
75
- setFilteredConversation(null);
76
- } else {
77
- const dupMessageList = conversation.messageList.filter((eachMessage) => {
78
- return eachMessage.messageBody
79
- .toLowerCase()
80
- .includes(searchMessage.toLowerCase());
81
- });
82
- const dupConversation = {
83
- id: conversation.id,
84
- members: [...conversation.members],
85
- messageList: [...dupMessageList],
86
- };
87
- setFilteredConversation(dupConversation);
88
- }
29
+ const handleSendMessage = (updated: Conversation) => {
30
+ setConversation(updated);
31
+ setMessage("");
32
+ setConversationList((prev) =>
33
+ prev.map((c) => (c.id === updated.id ? updated : c))
34
+ );
89
35
  };
90
36
 
91
- return (
92
- <ChatDashboard
93
- value={message}
94
- searchMessage={search}
95
- currentUser={currentUser}
96
- selectedConversation={filteredConversation ?? conversation}
97
- conversationList={conversationList}
98
- mobileSelectedChat={mobileSelectedChat}
99
- sizeClassName="w-full h-[50vh] sm:h-[50vh]"
100
- chatBaseSizeClassName="w-full h-[50vh] sm:h-[50vh]"
101
- headerElement={renderHeaderElement()}
102
- bodyElement={renderBodyElement()}
103
- onChangeMessage={(e) => setMessage(e.target.value)}
104
- handleSelectedConversation={handleMessageList}
105
- onSendMessage={handleOnSendMessage}
106
- onChangeSearch={(e) => setSearch(e.target.value)}
107
- onSearch={handleOnSearch}
108
- />
37
+ // Custom header with a back button for mobile
38
+ const headerElement = (
39
+ <div className="flex items-center h-12 bg-primary-500 text-white px-4">
40
+ <button
41
+ className="sm:hidden mr-2"
42
+ onClick={() => setMobileSelectedChat(false)}
43
+ >
44
+ ←
45
+ </button>
46
+ <span>{otherUser?.name}</span>
47
+ </div>
109
48
  );
49
+
50
+ <ChatDashboard
51
+ value={message}
52
+ currentUser={currentUser}
53
+ selectedConversation={conversation}
54
+ conversationList={conversationList}
55
+ mobileSelectedChat={mobileSelectedChat}
56
+ sizeClassName="w-64 h-[60vh]"
57
+ chatBaseSizeClassName="w-full h-[60vh]"
58
+ headerElement={headerElement}
59
+ onChangeMessage={(e) => setMessage(e.target.value)}
60
+ onSendMessage={handleSendMessage}
61
+ handleSelectedConversation={handleSelectConversation}
62
+ />
110
63
  ```
111
64
 
112
65
  ##### Required props
113
66
 
114
- | Name | Type | Description |
115
- | -------------------- | ---------------- | --------------------------------------- |
116
- | value | `string` | Message input text |
117
- | currentUser | `User` | User Object |
118
- | selectedConversation | `Conversation` | Selected Conversation Object to display |
119
- | conversationList | `Conversation[]` | List of Conversation Objects |
67
+ | Name | Type | Description |
68
+ | ---------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------- |
69
+ | `value` | `string` | Current message input value |
70
+ | `currentUser` | `User` | The logged-in user — used to identify which side messages appear on and to find the other user in each conversation |
71
+ | `selectedConversation` | `Conversation` | The conversation currently displayed in the chat panel |
72
+ | `conversationList` | `Conversation[]` | All conversations shown in the left panel list |
73
+ | `handleSelectedConversation` | `(id: string) => void` | Called with the conversation `id` when a list item is clicked |
120
74
 
121
75
  ##### Optional props
122
76
 
123
- | Name | Type | Default | Description |
124
- | -------------------------- | -------------------- | ------------------------------- | --------------------------------- |
125
- | mobileSelectedChat | `boolean` | false | Mobile state for selected chat |
126
- | searchMessage | `string` | "" | Search input text |
127
- | headerElement | `React Element` | null | Replaces default component Header |
128
- | bodyElement | `React Element` | null | Replaces default component Body |
129
- | className | `tailwind classname` | "" | For component container |
130
- | sizeClassName | `tailwind classname` | "" | For component size |
131
- | chatBaseClassName | `tailwind classname` | "" | For Chat Base component container |
132
- | chatBaseSizeClassName | `tailwind classname` | "" | For Chat Base component size |
133
- | onSendMessage | `function` | `(event: any) => {}` | To send message |
134
- | onChangeMessage | `function` | `(event: any) => {}` | To type in message input |
135
- | onChangeSearch | `function` | `(event: any) => {}` | To type in search input |
136
- | onSearch | `function` | `(searchMessage: string) => {}` | To search in messages |
137
- | handleSelectedConversation | `function` | `(id: string) => {}` | Display selected conversation |
138
-
139
- ##### Interface User
140
-
141
- | Name | Type | Description |
142
- | ---------- | -------- | ----------------- |
143
- | `id` | `string` | Required id |
144
- | `name` | `string` | Required name |
145
- | `imageUrl` | `string` | Optional imageURL |
146
-
147
- ##### Interface Conversation
148
-
149
- | Name | Type | Description |
150
- | ------------- | ----------- | ------------------------- |
151
- | `id` | `string` | Required id |
152
- | `members` | `User[]` | Required list of Users |
153
- | `messageList` | `Message[]` | Required list of Messages |
154
-
155
- ##### Interface Message
156
-
157
- | Name | Type | Description |
158
- | ------------- | -------- | -------------------- |
159
- | `sender` | `User` | Required User |
160
- | `messageBody` | `string` | Required messageBody |
161
- | `createdAt` | `Date` | Required date |
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` |
91
+
92
+ ##### Conversation list behaviour
93
+
94
+ Each item in `conversationList` renders the other user's avatar and name (the member whose `id` differs from `currentUser.id`) and the last message body as a preview. Items with a `FileObj` message body show an empty preview string.
95
+
96
+ ##### User
97
+
98
+ | Name | Type | Description |
99
+ | ---------- | -------- | ------------------------------------------------- |
100
+ | `id` | `string` | Unique identifier |
101
+ | `name` | `string` | Display name |
102
+ | `imageUrl` | `string` | Avatar URL — shown in the conversation list items |
103
+
104
+ ##### Conversation
105
+
106
+ | Name | Type | Description |
107
+ | ------------- | ----------- | ------------------------------------------------------------ |
108
+ | `id` | `string` | Unique conversation identifier |
109
+ | `members` | `User[]` | Participants — must include `currentUser` and one other user |
110
+ | `messageList` | `Message[]` | Ordered list of messages |
111
+
112
+ ##### Message
113
+
114
+ | Name | Type | Description |
115
+ | -------------- | ------------------- | ----------------------------------------------------------------- |
116
+ | `sender` | `User` | The user who sent the message |
117
+ | `messageBody` | `string \| FileObj` | Text content or a file object for image/video messages |
118
+ | `reactElement` | `ReactElement` | Optional — replaces `messageBody` rendering with a custom element |
119
+ | `createdAt` | `Date` | Timestamp |
@@ -3,11 +3,11 @@ import { storiesOf } from "@storybook/react";
3
3
  import moment from "moment";
4
4
  import React, { useEffect, useState } from "react";
5
5
  import { ChatDashboard } from ".";
6
- import Logo from "../../../assets/logo.png";
6
+ import { StorySection } from "../../storyUtils";
7
7
  import { IconButton } from "../../action/iconButton";
8
8
  import { Loading } from "../../display/loading";
9
9
  import { Conversation } from "../chatBase";
10
- import { conversationList as convoList, currentUser } from "../chatUtils";
10
+ import { conversationList as seedConvoList, currentUser } from "../chatUtils";
11
11
  import README from "./README.md";
12
12
 
13
13
  const stories = storiesOf("Chat", module);
@@ -18,137 +18,114 @@ stories.addParameters({
18
18
  },
19
19
  });
20
20
 
21
+ const sortByLatest = (list: Conversation[]) =>
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
+ )
26
+ );
27
+
21
28
  stories.add("Chat Dashboard", () => {
22
29
  const [message, setMessage] = useState("");
23
30
  const [search, setSearch] = useState("");
24
31
  const [isLoading, setIsLoading] = useState(false);
25
32
  const [mobileSelectedChat, setMobileSelectedChat] = useState(false);
26
33
  const [filteredConversation, setFilteredConversation] =
27
- useState<Conversation | null>();
34
+ useState<Conversation | null>(null);
35
+ const [conversationList, setConversationList] = useState<Conversation[]>([]);
28
36
  const [conversation, setConversation] = useState<Conversation>({
29
37
  id: "",
30
38
  members: [],
31
39
  messageList: [],
32
40
  });
33
- const [conversationList, setConversationList] = useState<Conversation[]>([]);
34
41
 
35
42
  useEffect(() => {
36
- handleSortConvoList(convoList);
43
+ const sorted = sortByLatest(seedConvoList);
44
+ setConversationList(sorted);
45
+ setConversation(sorted[0]);
37
46
  }, []);
38
47
 
39
- const handleOnSearch = (searchMessage: string) => {
40
- if (searchMessage.length === 0) {
41
- setSearch("");
42
- setFilteredConversation(null);
43
- } else {
44
- const dupMessageList = conversation.messageList.filter((eachMessage) => {
45
- if (typeof eachMessage.messageBody === "string") {
46
- return eachMessage.messageBody
47
- .toLowerCase()
48
- .includes(searchMessage.toLowerCase());
49
- }
50
- return null;
51
- });
52
- const dupConversation = {
53
- id: conversation.id,
54
- members: [...conversation.members],
55
- messageList: [...dupMessageList],
56
- };
57
- setFilteredConversation(dupConversation);
58
- }
59
- };
60
-
61
- const handleOnSendMessage = (conversation: Conversation) => {
62
- setConversation({
63
- id: conversation.id,
64
- members: [...conversation.members],
65
- messageList: [...conversation.messageList],
66
- });
67
- setMessage("");
68
- const dupConvoList = conversationList.map((eachConvo, i) => {
69
- if (eachConvo.id === conversation.id) {
70
- return conversation;
71
- }
72
- return eachConvo;
73
- });
74
-
75
- handleSortConvoList(dupConvoList);
76
- };
77
-
78
- const handleSortConvoList = (convoList: Conversation[]) => {
79
- const sortedConvoList = convoList.sort((a, b) => {
80
- return moment(b.messageList[b.messageList.length - 1].createdAt).diff(
81
- moment(a.messageList[a.messageList.length - 1].createdAt)
82
- );
83
- });
84
- setConversationList(sortedConvoList);
85
- setConversation(sortedConvoList[0]);
86
- };
87
-
88
- const handleMessageList = (id: string) => {
48
+ const handleSelectConversation = (id: string) => {
89
49
  setIsLoading(true);
90
50
  setMobileSelectedChat(true);
91
51
  setTimeout(() => {
92
52
  setIsLoading(false);
93
- conversationList.map((eachConversation) => {
94
- if (eachConversation.id === id) {
95
- setConversation(eachConversation);
96
- }
97
- return null;
98
- });
99
- }, 1000);
53
+ const found = conversationList.find((c) => c.id === id);
54
+ if (found) setConversation(found);
55
+ }, 800);
100
56
  };
101
57
 
102
- const renderBodyElement = () => {
103
- return isLoading ? (
104
- <div className="w-full">
105
- <Loading />
106
- </div>
107
- ) : undefined;
58
+ const handleSendMessage = (updated: Conversation) => {
59
+ setConversation(updated);
60
+ setMessage("");
61
+ const updatedList = conversationList.map((c) =>
62
+ c.id === updated.id ? updated : c
63
+ );
64
+ setConversationList(sortByLatest(updatedList));
108
65
  };
109
66
 
110
- const renderHeaderElement = () => {
111
- const otherUser = conversation.members.find((eachMember) => {
112
- return eachMember.id !== currentUser.id;
113
- });
114
- const userName = otherUser ? otherUser.name : "";
115
-
116
- return (
117
- <div className="w-full flex items-center h-12 text-white bg-primary-500 text-center">
118
- <IconButton
119
- icon={<ChevronLeftIcon className="w-6 h-6" />}
120
- background="bg-transparent"
121
- onClick={() => setMobileSelectedChat(false)}
122
- className={`${!mobileSelectedChat ? "hidden sm:flex" : ""} sm:hidden`}
123
- />
124
- <p className="ml-2 sm:ml-12">{userName}</p>
125
- </div>
126
- );
67
+ const handleSearch = (searchMessage: string) => {
68
+ if (!searchMessage) {
69
+ setSearch("");
70
+ setFilteredConversation(null);
71
+ } else {
72
+ setFilteredConversation({
73
+ ...conversation,
74
+ messageList: conversation.messageList.filter(
75
+ (m) =>
76
+ typeof m.messageBody === "string" &&
77
+ m.messageBody.toLowerCase().includes(searchMessage.toLowerCase())
78
+ ),
79
+ });
80
+ }
127
81
  };
128
82
 
129
- return (
130
- <div>
131
- <div className="flex justify-center items-center py-2 space-x-4">
132
- <img src={Logo} alt="" className="h-14" />
133
- <h1 className="text-2xl text-primary-400">Chat App</h1>
134
- </div>
135
- <ChatDashboard
136
- value={message}
137
- searchMessage={search}
138
- currentUser={currentUser}
139
- selectedConversation={filteredConversation ?? conversation}
140
- conversationList={conversationList}
141
- mobileSelectedChat={mobileSelectedChat}
142
- sizeClassName="w-full h-[50vh] sm:h-[50vh]"
143
- chatBaseSizeClassName="w-full h-[50vh] sm:h-[50vh]"
144
- headerElement={renderHeaderElement()}
145
- bodyElement={renderBodyElement()}
146
- onChangeMessage={(e) => setMessage(e.target.value)}
147
- handleSelectedConversation={handleMessageList}
148
- onSendMessage={handleOnSendMessage}
149
- onChangeSearch={(e) => setSearch(e.target.value)}
150
- onSearch={handleOnSearch}
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`}
151
92
  />
93
+ <p className="ml-2 sm:ml-4 font-medium">{otherUser?.name ?? "Chat"}</p>
94
+ </div>
95
+ );
96
+
97
+ const bodyElement = isLoading ? (
98
+ <div className="w-full h-full flex items-center justify-center">
99
+ <Loading />
152
100
  </div>
101
+ ) : undefined;
102
+
103
+ return (
104
+ <>
105
+ <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."
108
+ >
109
+ <div className="w-full">
110
+ <ChatDashboard
111
+ value={message}
112
+ searchMessage={search}
113
+ currentUser={currentUser}
114
+ selectedConversation={filteredConversation ?? conversation}
115
+ conversationList={conversationList}
116
+ mobileSelectedChat={mobileSelectedChat}
117
+ sizeClassName="w-64 h-[60vh]"
118
+ chatBaseSizeClassName="w-full h-[60vh]"
119
+ headerElement={headerElement}
120
+ bodyElement={bodyElement}
121
+ onChangeMessage={(e) => setMessage(e.target.value)}
122
+ onSendMessage={handleSendMessage}
123
+ onChangeSearch={(e) => setSearch(e.target.value)}
124
+ onSearch={handleSearch}
125
+ handleSelectedConversation={handleSelectConversation}
126
+ />
127
+ </div>
128
+ </StorySection>
129
+ </>
153
130
  );
154
131
  });
@@ -4,10 +4,144 @@
4
4
 
5
5
  <hr>
6
6
 
7
- Data Graph for Dashboard
7
+ A dashboard chart grid that fetches and renders analytics data from a remote API. Supports card, bar, line, pie, and radar chart types in a responsive grid layout. Each chart handles its own data fetching, date range filtering, and attribute filtering.
8
8
 
9
9
  ##### Import
10
10
 
11
11
  ```js
12
12
  import { DataGraph } from "blixify-ui-web/components/data/dataGraph";
13
13
  ```
14
+
15
+ ##### Usage
16
+
17
+ Basic dashboard with card and bar charts:
18
+
19
+ ```jsx
20
+ <DataGraph
21
+ charts={[
22
+ {
23
+ gridSize: 1,
24
+ type: "card",
25
+ title: "Total Products",
26
+ spaceId: "products",
27
+ query: [],
28
+ queryBaseType: "count",
29
+ },
30
+ {
31
+ gridSize: 1,
32
+ type: "card",
33
+ title: "Total Revenue",
34
+ spaceId: "orders",
35
+ query: [],
36
+ queryBaseType: "sum",
37
+ queryAttributeId: ["price"],
38
+ queryBaseDateId: "createdAt",
39
+ },
40
+ {
41
+ gridSize: 2,
42
+ type: "bar",
43
+ title: "Sales by Product",
44
+ spaceId: "orders",
45
+ query: [
46
+ { type: "=", queryId: "code", value: "P001" },
47
+ { type: "=", queryId: "code", value: "P002" },
48
+ ],
49
+ queryBaseType: "sum",
50
+ queryAttributeId: ["price"],
51
+ },
52
+ ]}
53
+ workspace={{
54
+ apiEndpoint: "http://localhost:3000/api",
55
+ apiEndpointType: "mongoDB",
56
+ }}
57
+ />
58
+ ```
59
+
60
+ Line chart with date range:
61
+
62
+ ```jsx
63
+ {
64
+ gridSize: 2,
65
+ type: "line",
66
+ title: "Orders Over Time",
67
+ spaceId: "orders",
68
+ query: [],
69
+ queryBaseType: "count",
70
+ queryBaseDateId: "createdAt",
71
+ }
72
+ ```
73
+
74
+ Radar chart comparing multiple attributes across query groups:
75
+
76
+ ```jsx
77
+ {
78
+ gridSize: 2,
79
+ type: "radar",
80
+ title: "Performance",
81
+ spaceId: "reports",
82
+ query: [
83
+ { type: "=", queryId: "status", value: "Completed" },
84
+ { type: "=", queryId: "status", value: "Pending" },
85
+ ],
86
+ queryBaseType: "sum",
87
+ queryAttributeId: ["revenue", "units", "returns"],
88
+ queryBaseDateId: "createdAt",
89
+ }
90
+ ```
91
+
92
+ Custom layout via renderSettings:
93
+
94
+ ```jsx
95
+ <DataGraph
96
+ charts={charts}
97
+ workspace={workspace}
98
+ renderSettings={(chartsElement, data) => (
99
+ <div className="my-custom-wrapper">{chartsElement}</div>
100
+ )}
101
+ />
102
+ ```
103
+
104
+ ##### Required props
105
+
106
+ | Name | Type | Description |
107
+ | ----------- | ---------------------- | -------------------------------------------------------- |
108
+ | `charts` | `Charts[]` | List of chart definitions — see Charts below |
109
+ | `workspace` | `WorkspaceDevSettings` | API connection settings — see WorkspaceDevSettings below |
110
+
111
+ ##### Optional props
112
+
113
+ | Name | Type | Default | Description |
114
+ | ---------------- | ------------------------------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
115
+ | `gridSize` | `2 \| 3` | — | Override the grid column layout. Default is a responsive `1 → 2 → 4` column grid. `2` forces 2 cols, `3` forces 3 cols. |
116
+ | `renderSettings` | `(charts: ReactElement, data: any) => any` | — | Custom render function. Receives the rendered charts element and the raw analytics data object. Use to wrap charts in a custom layout. |
117
+
118
+ ##### Charts
119
+
120
+ | Name | Type | Description |
121
+ | ------------------ | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
122
+ | `type` | `"card" \| "bar" \| "line" \| "pie" \| "radar"` | Chart type to render |
123
+ | `title` | `string` | Chart title — also used as the key for internal state |
124
+ | `spaceId` | `string` | The collection/space ID to query data from |
125
+ | `gridSize` | `number` | Number of grid columns this chart spans |
126
+ | `query` | `Query[]` | Data series definitions. Each query entry produces one data point or series segment. Pass `[]` for a single aggregate. |
127
+ | `queryBaseType` | `"count" \| "sum" \| "avg" \| "value"` | Aggregate function. Defaults to `"count"`. |
128
+ | `queryAttributeId` | `string[]` | Required when `queryBaseType` is `"sum"`, `"avg"`, or `"value"`. The field(s) to aggregate. For `radar`, each entry becomes a radar axis. |
129
+ | `queryBaseDateId` | `string` | Field used for date range filtering. When set, a date range picker is shown on the chart. |
130
+ | `queryReferenceId` | `string` | Field used for reference-based filtering when a reference filter is active. |
131
+ | `baseQuery` | `Query[]` | Static filter queries always applied alongside any dynamic filters. |
132
+ | `description` | `string` | Subtitle shown on `card` type charts |
133
+ | `cardIcon` | `string` | Heroicon name (solid) for the card icon, e.g. `"UserIcon"`. Defaults to `FolderIcon`. |
134
+ | `cardIconBgColor` | `string` | Tailwind bg class for the card icon background |
135
+ | `colors` | `string[]` | Custom color array for chart segments/bars/lines |
136
+ | `orientation` | `"col" \| "row"` | Bar chart orientation. Defaults to `"row"`. |
137
+ | `stackedVertical` | `boolean` | Enables stacked bars on bar charts |
138
+
139
+ ##### WorkspaceDevSettings
140
+
141
+ | Name | Type | Description |
142
+ | ------------------ | -------------------------------------------------------------------------- | ------------------------------- |
143
+ | `apiEndpoint` | `string` | Base URL for the data API |
144
+ | `apiEndpointType` | `"mongoDB" \| "firebase" \| "mongoServer" \| "fbServer" \| "mondayServer"` | Server/DB type driving the API |
145
+ | `apiEndpointToken` | `string` | Auth token for the API endpoint |
146
+ | `apiUserToken` | `string` | Per-user auth token |
147
+ | `assetEndpoint` | `string` | Base URL for asset/file storage |