blixify-ui-web-mcp 0.1.19 → 0.1.21

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 (47) hide show
  1. package/lib/components/action/iconButton/iconButton.stories.tsx +11 -2
  2. package/lib/components/auth/signUp/signUp.stories.tsx +39 -1
  3. package/lib/components/chart/statsCard/README.md +24 -0
  4. package/lib/components/chart/statsCard/statsCard.stories.tsx +68 -0
  5. package/lib/components/chat/chatBase/chatBase.stories.tsx +1 -1
  6. package/lib/components/data/dataGraph/dataGraph.stories.tsx +5 -5
  7. package/lib/components/data/dataTemplate/README.md +204 -85
  8. package/lib/components/data/dataTemplate/dataTemplate.stories.tsx +307 -295
  9. package/lib/components/data/dataTemplate/drive.stories.tsx +197 -0
  10. package/lib/components/data/dataWrapper/README.md +109 -22
  11. package/lib/components/data/dataWrapper/dataWrapper.bareHooks.stories.tsx +951 -0
  12. package/lib/components/data/dataWrapper/dataWrapper.edit.stories.tsx +356 -5
  13. package/lib/components/data/dataWrapper/dataWrapper.view.stories.tsx +79 -5
  14. package/lib/components/display/calendar/calendar.stories.tsx +1 -1
  15. package/lib/components/display/detailList/README.md +11 -10
  16. package/lib/components/display/detailList/detailList.stories.tsx +16 -3
  17. package/lib/components/display/feeds/README.md +3 -3
  18. package/lib/components/display/feeds/feeds.stories.tsx +3 -3
  19. package/lib/components/display/filePreview/filePreview.stories.tsx +111 -0
  20. package/lib/components/display/modal/README.md +3 -0
  21. package/lib/components/display/modal/modal.stories.tsx +50 -1
  22. package/lib/components/display/newCalendar/README.md +0 -6
  23. package/lib/components/display/newCalendar/newCalendar.stories.tsx +1 -1
  24. package/lib/components/display/newFeeds/README.md +1 -5
  25. package/lib/components/display/newFeeds/newFeeds.stories.tsx +3 -3
  26. package/lib/components/display/newKanban/README.md +0 -7
  27. package/lib/components/display/newList/README.md +1 -4
  28. package/lib/components/display/newTable/README.md +29 -9
  29. package/lib/components/display/newTable/newTable.stories.tsx +120 -9
  30. package/lib/components/display/tag/README.md +1 -1
  31. package/lib/components/display/tag/tag.stories.tsx +7 -0
  32. package/lib/components/input/markdown/markdown.stories.tsx +3 -1
  33. package/lib/components/input/uploadInput/uploadInput.stories.tsx +41 -0
  34. package/lib/components/navigation/leftNav/leftNav.stories.tsx +175 -0
  35. package/lib/components/navigation/tabs/tabs.stories.tsx +23 -0
  36. package/lib/components/template/calendarView/calendarView.stories.tsx +4 -4
  37. package/lib/components/template/chatDashboard/chatDashboard.stories.tsx +2 -2
  38. package/lib/mcp/tools/getBackendComponent.d.ts.map +1 -1
  39. package/lib/mcp/tools/getBackendComponent.js +7 -3
  40. package/lib/mcp/tools/getBackendComponent.js.map +1 -1
  41. package/lib/mcp/tools/suggestTemplate.js +3 -3
  42. package/lib/mcp/tools/suggestTemplate.js.map +1 -1
  43. package/package.json +1 -1
  44. package/lib/components/data/dataTemplate/dataModule.stories.tsx +0 -191
  45. package/lib/components/data/dataWrapper/dataWrapper.consumer.stories.tsx +0 -2125
  46. package/lib/components/data/dataWrapper/dataWrapper.utils.stories.tsx +0 -576
  47. package/lib/components/data/flexFilter/flexFilter.stories.tsx +0 -171
@@ -0,0 +1,197 @@
1
+ // INFO: the drive view over an in-memory collection. The story mocks its own
2
+ // API, so it opens without a server and the e2e spec drives it as it is.
3
+
4
+ import React, { useEffect, useRef, useState } from "react";
5
+ import MockAdapter from "axios-mock-adapter";
6
+ import axios from "axios";
7
+ import { DataTemplateWrapper } from "../dataWrapper";
8
+ import { DataStructure } from "./model";
9
+ import { ExtendedTabs } from "./spaceModel";
10
+
11
+ const meta = {
12
+ title: "Data/Data Template/Drive",
13
+ decorators: [
14
+ (Story: () => React.ReactElement) => (
15
+ <div className="bg-gray-50 min-h-screen p-4">
16
+ <Story />
17
+ </div>
18
+ ),
19
+ ],
20
+ };
21
+ export default meta;
22
+
23
+ const DRIVE_ENDPOINT = "http://localhost:3000/api/data";
24
+ const DRIVE_UPLOAD = "http://localhost:3000/api/asset";
25
+ const DRIVE_COLLECTION = "demo-drive";
26
+
27
+ const DriveSchema: DataStructure[] = [
28
+ { id: "name", name: "Name", type: "string" },
29
+ { id: "kind", name: "Kind", type: "list", listSelection: ["folder", "file"] },
30
+ { id: "parentId", name: "Folder", type: "string" },
31
+ { id: "file", name: "File", type: "multipleFile" },
32
+ { id: "size", name: "Size", type: "number" },
33
+ ];
34
+
35
+ const DriveTabs: ExtendedTabs[] = [
36
+ {
37
+ name: "Drive",
38
+ type: "list",
39
+ viewType: "drive",
40
+ defaultTab: true,
41
+ driveSettings: {
42
+ nameColumnId: "name",
43
+ kindColumnId: "kind",
44
+ parentColumnId: "parentId",
45
+ fileColumnId: "file",
46
+ sizeColumnId: "size",
47
+ },
48
+ },
49
+ ];
50
+
51
+ const handleBuildDriveStore = () => [
52
+ { _id: "f-contracts", name: "Contracts", kind: "folder", parentId: "" },
53
+ { _id: "f-invoices", name: "Invoices", kind: "folder", parentId: "" },
54
+ { _id: "f-2025", name: "2025", kind: "folder", parentId: "f-invoices" },
55
+ {
56
+ _id: "d-handbook",
57
+ name: "Team handbook.pdf",
58
+ kind: "file",
59
+ parentId: "",
60
+ size: 482000,
61
+ file: [{ token: "demo", fileName: "demo-handbook.pdf" }],
62
+ },
63
+ {
64
+ _id: "d-inv-01",
65
+ name: "Invoice 01.pdf",
66
+ kind: "file",
67
+ parentId: "f-2025",
68
+ size: 91000,
69
+ file: [{ token: "demo", fileName: "demo-inv-01.pdf" }],
70
+ },
71
+ {
72
+ _id: "d-nda",
73
+ name: "Supplier NDA.docx",
74
+ kind: "file",
75
+ parentId: "f-contracts",
76
+ size: 34000,
77
+ file: [{ token: "demo", fileName: "demo-nda.docx" }],
78
+ },
79
+ ];
80
+
81
+ const handleParseBody = (config: any) => {
82
+ try {
83
+ return JSON.parse(config?.data ?? "{}");
84
+ } catch (err) {
85
+ return {};
86
+ }
87
+ };
88
+
89
+ // INFO: enough of the query language for a drive — equality, `in` with null
90
+ // matching a missing field the way Mongo does, and a name search
91
+ const handleMatchQuery = (row: any, query: any[]) =>
92
+ query.every((eachQuery) => {
93
+ const value = row[eachQuery.queryId];
94
+ if (eachQuery.type === "=") return value === eachQuery.value;
95
+ if (eachQuery.type === "in")
96
+ return (eachQuery.value ?? []).some((eachValue: any) =>
97
+ eachValue === null
98
+ ? value === undefined || value === null
99
+ : value === eachValue,
100
+ );
101
+ if (eachQuery.type === "search")
102
+ return String(row.name ?? "")
103
+ .toLowerCase()
104
+ .includes(String(eachQuery.value ?? "").toLowerCase());
105
+ return true;
106
+ });
107
+
108
+ export const Drive = () => {
109
+ const [folderId, setFolderId] = useState("");
110
+ const [mounted, setMounted] = useState(false);
111
+ const storeRef = useRef<any[]>(handleBuildDriveStore());
112
+
113
+ useEffect(() => {
114
+ const store = storeRef.current;
115
+ const mock = new MockAdapter(axios, {
116
+ onNoMatch: "passthrough",
117
+ delayResponse: 120,
118
+ });
119
+ const base = `${DRIVE_ENDPOINT}/${DRIVE_COLLECTION}`;
120
+
121
+ mock.onPost(`${base}/list`).reply((config) => {
122
+ const body = handleParseBody(config);
123
+ const rows = store.filter((eachRow) =>
124
+ handleMatchQuery(eachRow, body.query ?? []),
125
+ );
126
+ return [200, { data: rows, count: rows.length }];
127
+ });
128
+ mock.onPost(`${base}/get`).reply((config) => {
129
+ const body = handleParseBody(config);
130
+ return [200, { data: store.find((row) => row._id === body.id) ?? null }];
131
+ });
132
+ mock.onPost(`${base}/create`).reply((config) => {
133
+ store.push(handleParseBody(config).data);
134
+ return [200, { success: true }];
135
+ });
136
+ mock.onPost(`${base}/update`).reply((config) => {
137
+ const body = handleParseBody(config);
138
+ const row = store.find((eachRow) => eachRow._id === body.id);
139
+ if (row) Object.assign(row, body.data);
140
+ return [200, { success: true }];
141
+ });
142
+ mock.onPost(`${base}/batchDelete`).reply((config) => {
143
+ const ids: string[] = handleParseBody(config).id ?? [];
144
+ for (let index = store.length - 1; index >= 0; index -= 1)
145
+ if (ids.includes(store[index]._id)) store.splice(index, 1);
146
+ return [200, { success: true }];
147
+ });
148
+ mock.onPost(`${DRIVE_UPLOAD}/upload`).reply(200, { data: "demo-token" });
149
+ mock
150
+ .onPost(`${DRIVE_UPLOAD}/batchDeleteDirectory`)
151
+ .reply(200, { success: true });
152
+
153
+ setMounted(true);
154
+ return () => mock.restore();
155
+ }, []);
156
+
157
+ if (!mounted) return null;
158
+
159
+ return (
160
+ <div className="w-full">
161
+ <div className="font-semibold mb-1 text-gray-900">Drive</div>
162
+ <div className="text-sm font-light mb-4 text-gray-500">
163
+ A folder browser over a collection where every row is a file or a
164
+ folder. Open folders, use the path to go back, create, upload, rename,
165
+ move and delete. Deleting a folder takes everything inside it.
166
+ </div>
167
+ <DataTemplateWrapper
168
+ title="Documents"
169
+ type="list"
170
+ collectionId={DRIVE_COLLECTION}
171
+ schema={DriveSchema}
172
+ workspace={{
173
+ apiEndpointType: "mongoDB",
174
+ apiEndpoint: DRIVE_ENDPOINT,
175
+ apiEndpointToken: "",
176
+ apiUserToken: "",
177
+ assetEndpoint:
178
+ "https://firebasestorage.googleapis.com/v0/b/demo-bucket.firebasestorage.app/o/",
179
+ assetUploadEndpoint: DRIVE_UPLOAD,
180
+ }}
181
+ edit={{
182
+ allEditable: true,
183
+ listEditable: true,
184
+ disabledUpdate: false,
185
+ deleteBtn: true,
186
+ }}
187
+ bareSettings={{ bareNewDTW: true, bareCompactMode: true }}
188
+ organise={{
189
+ extendedTabs: DriveTabs,
190
+ folderId,
191
+ onChangeFolder: setFolderId,
192
+ }}
193
+ handleNavigate={() => {}}
194
+ />
195
+ </div>
196
+ );
197
+ };
@@ -14,7 +14,7 @@ For most custom integrations, **`DataTemplateWrapper` is the recommended startin
14
14
 
15
15
  These `DataTemplate` props are handled internally and cannot be passed:
16
16
 
17
- - `devSettings` — replaced by `workspace`
17
+ - `devSettings` — mostly replaced by `workspace`, but see the override below
18
18
  - `lib.axios`, `lib.Menu`, `lib.Transition`, `lib.papaParser` — always bundled
19
19
  - `model` — replaced by `schema`
20
20
  - `onComplete` — replaced by `handleComplete`
@@ -25,6 +25,34 @@ These `DataTemplate` props are handled internally and cannot be passed:
25
25
 
26
26
  You can still pass `organise` for everything else (`breadcrumbType`, `tabAlign`, `splitView`, `exportSettings`, etc.).
27
27
 
28
+ ### `devSettings` override
29
+
30
+ `workspace` is a `WorkspaceDevSettings` — deliberately narrower than
31
+ `DevSettings`, because it maps to what a workspace record actually stores. Some
32
+ app-level values have no place on a workspace and so cannot travel that way:
33
+
34
+ - `schemaEndpoint` / `schemaEndpointToken` — schema editing
35
+ - `roleOptions` — role pickers in forms
36
+ - `currentUser` — activity log attribution
37
+
38
+ Pass those as `devSettings`. It is **merged last**, so anything you set wins over
39
+ the value derived from `workspace`, and anything you omit keeps the derived one:
40
+
41
+ ```tsx
42
+ <DataTemplateWrapper
43
+ workspace={workspaceSettings}
44
+ devSettings={{
45
+ schemaEndpoint: getSchemaUrl,
46
+ schemaEndpointToken: securityToken,
47
+ roleOptions: renderRoleOptions(),
48
+ currentUser: { userEmail, userName },
49
+ }}
50
+ />
51
+ ```
52
+
53
+ This is an override for what `workspace` cannot express, not a replacement for
54
+ it — the endpoints still come from `workspace`.
55
+
28
56
  ### Props forwarded straight through
29
57
 
30
58
  The wrapper names every prop it hands to `DataTemplate` rather than spreading, so
@@ -163,6 +191,11 @@ When `edit` is not passed, the wrapper defaults to:
163
191
 
164
192
  Pass your own `edit` object to override any of these.
165
193
 
194
+ > `deleteBtn` is independent of `allEditable`. `{ allEditable: false, deleteBtn: true }`
195
+ > renders Delete in the read and update action bars — grant it per role rather than
196
+ > unconditionally. See DataTemplate's Write permission matrix for the full rules,
197
+ > including which controls the imperative API deliberately does not gate.
198
+
166
199
  ---
167
200
 
168
201
  **7. Apply a pre-filter (optional)**
@@ -181,6 +214,51 @@ bareSettings={{
181
214
 
182
215
  Call `dataRef.current?.handleRefresh()` after changing the filter to re-fetch.
183
216
 
217
+ > `bareListFilterQuery` is baked into every query and **cannot be cleared by the user**. When you want programmatic filters that the user can still edit or remove, use flex filters instead — see Flex Filters below.
218
+
219
+ **7b. Flex filters (optional)**
220
+
221
+ Flex filters are the Monday.com-style filter rows shown in the filter bar. Enable them with `bareSettings.bareFlexFilters`, then apply or clear them programmatically via the ref. Unlike `bareListFilterQuery`, filters applied this way land as normal flex filter rows the user can edit or clear themselves.
222
+
223
+ ```tsx
224
+ bareSettings={{ bareFlexFilters: true }}
225
+
226
+ // Apply — merged by columnId: an incoming filter REPLACES any existing row on
227
+ // the same column, so applying status="Closed" over an existing status="Open"
228
+ // swaps it rather than producing a contradictory AND (which returns zero rows).
229
+ // Filters on other columns are preserved.
230
+ dataRef.current?.handleApplyFlexFilters([
231
+ { id: "card-status", columnId: "status", operator: "=", value: "Closed" },
232
+ ]);
233
+
234
+ // Clear specific columns (others preserved)
235
+ dataRef.current?.handleClearFlexFilters(["status"]);
236
+
237
+ // Clear all flex filters
238
+ dataRef.current?.handleClearFlexFilters();
239
+ ```
240
+
241
+ A `FlexFilterData` row is `{ id: string; columnId: string; operator: FlexFilterOperator; value: any }`. Import the type from `blixify-ui-web/data/flexFilter/model`.
242
+
243
+ **Observing live filter state**
244
+
245
+ To keep external UI (e.g. stat cards) in sync with what's actually applied, pass `bareOnFlexFiltersChange`. It fires whenever the applied filters change — from user edits in the panel or your own apply/clear calls — using the debounced state so it emits on committed changes, not every keystroke.
246
+
247
+ ```tsx
248
+ const [liveFilters, setLiveFilters] = useState<FlexFilterData[]>([]);
249
+
250
+ bareSettings={{
251
+ bareFlexFilters: true,
252
+ bareOnFlexFiltersChange: setLiveFilters,
253
+ }}
254
+
255
+ // Derive "is this filter applied" from the real state rather than click history,
256
+ // so a card de-highlights on its own if the user edits or clears that row.
257
+ const isStatusClosed = liveFilters.some(
258
+ (f) => f.columnId === "status" && f.operator === "=" && String(f.value) === "Closed",
259
+ );
260
+ ```
261
+
184
262
  **8. newTabUrl (Optional)**
185
263
 
186
264
  Enables right-click → "Open in new tab" on list rows. The component builds the link as `default + recordId + addons`.
@@ -447,14 +525,19 @@ bareSettings={{
447
525
  <MyCustomDateRangePicker key="range" onChange={setRange} />,
448
526
  ],
449
527
 
450
- // MongoDB aggregation pipeline stages appended to list queries
528
+ // MongoDB aggregation pipeline stages appended to list queries.
529
+ // Each result lands on the row as `<spaceReferenceAs>_<field>` (or
530
+ // `<spaceReferenceAs>_count`); match a schema column `id` to that key to show it.
451
531
  bareListPipeline: [
452
532
  {
453
533
  spaceReferenceId: "users",
454
534
  spaceReferenceAs: "assigneeData",
455
535
  condition: [{ type: "=", fromColumnId: "assigneeId", toColumnId: "_id" }],
456
- referenceSpaceLabel: ["name", "email"],
536
+ referenceSpaceLabel: ["name", "email"], // → assigneeData_name, assigneeData_email
457
537
  referenceFirstItem: true,
538
+ // Optional, opt-in — existing pipelines are unaffected when omitted:
539
+ referenceQuery: [{ type: "=", queryId: "active", value: true }], // extra filter on the joined collection, on top of `condition`
540
+ referenceShowCount: true, // emits assigneeData_count = size of the joined set after condition + referenceQuery
458
541
  },
459
542
  ],
460
543
  }}
@@ -469,11 +552,12 @@ Use these to inject extra content into the read view, intercept data on load, or
469
552
  ```tsx
470
553
  bareSettings={{
471
554
  // Inject a custom component into the read view
472
- // By default it renders below the field list
473
- bareReadComponent: (data) => (
555
+ // By default it renders below the field list.
556
+ // `data` is display data (labels + elements); `rawData` is the untouched DB doc.
557
+ bareReadComponent: (data, rawData) => (
474
558
  <div className="mt-4 p-4 bg-blue-50 rounded-lg">
475
- <p className="text-sm font-medium">Custom summary for {data.name}</p>
476
- <p className="text-xs text-gray-500">Last updated: {data.updatedAt}</p>
559
+ <p className="text-sm font-medium">Custom summary for {rawData.name}</p>
560
+ <p className="text-xs text-gray-500">Last updated: {rawData.updatedAt}</p>
477
561
  </div>
478
562
  ),
479
563
 
@@ -489,8 +573,9 @@ bareSettings={{
489
573
  return null; // return null to use the default renderer
490
574
  },
491
575
 
492
- // Called when read view data loads — use to sync external state
493
- bareReadHandleData: (data) => setPageTitle(data.name),
576
+ // Called when read view data loads — use to sync external state.
577
+ // `data` is display data (labels + elements); `rawData` is the untouched DB doc.
578
+ bareReadHandleData: (data, rawData) => setPageTitle(rawData.name),
494
579
 
495
580
  // Control whether clicking a reference field opens its detail view
496
581
  bareReadReferenceView: (dataId, collectionId, attributeId) => {
@@ -618,7 +703,7 @@ Set `bareOffline: true` in `bareSettings` to enable offline data caching via Ind
618
703
 
619
704
  Fields of type `reference` and `listReference` that pull their options from the server will be empty when offline. You must fetch those options while online and store them in IndexedDB yourself so the form stays usable offline.
620
705
 
621
- Use the same database name the component uses and store each option set under a key with the format `[collectionId]-ref` (e.g. meraque-cycles-ref). Each value must be an array of `Option` objects — `{ key, label, secondaryLabel?, color?, image?, icon? }` — where `key` is the record's `_id`.
706
+ Use the same database name the component uses and store each option set under a key with the format `[collectionId]-ref` (e.g. work-cycles-ref). Each value must be an array of `Option` objects — `{ key, label, secondaryLabel?, color?, image?, icon? }` — where `key` is the record's `_id`.
622
707
 
623
708
  ```tsx
624
709
  // Call this once on app load (or when the user logs in) while online
@@ -1019,16 +1104,18 @@ needs to know about the other.
1019
1104
 
1020
1105
  ## Ref Methods
1021
1106
 
1022
- | Method | Description |
1023
- | ---------------------------------- | ----------------------------------------------------------------- |
1024
- | `handleRefresh()` | Re-fetches the current list data, resetting pagination to page 1 |
1025
- | `handleRefreshCurrentPage()` | Re-fetches in place, keeping page index, cursor, and filters |
1026
- | `handleFormSubmit()` | Programmatically submits the form |
1027
- | `handleFormCheck()` | Validates the form without submitting |
1028
- | `handleUpdateSelectedData(fields)` | Prefills form fields. `fields` is `{ key: string; value: any }[]` |
1029
- | `handleDeleteModal()` | Opens the delete confirmation modal |
1030
- | `handleExportReadPDF()` | Triggers PDF export of the current read view |
1031
- | `handleUploadOfflineModal()` | Opens the offline data upload modal |
1107
+ | Method | Description |
1108
+ | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
1109
+ | `handleRefresh()` | Re-fetches the current list data, resetting pagination to page 1 |
1110
+ | `handleRefreshCurrentPage()` | Re-fetches in place, keeping page index, cursor, and filters |
1111
+ | `handleFormSubmit()` | Programmatically submits the form |
1112
+ | `handleFormCheck()` | Validates the form without submitting |
1113
+ | `handleUpdateSelectedData(fields)` | Prefills form fields. `fields` is `{ key: string; value: any }[]` |
1114
+ | `handleDeleteModal()` | Opens the delete confirmation modal |
1115
+ | `handleExportReadPDF()` | Triggers PDF export of the current read view |
1116
+ | `handleUploadOfflineModal()` | Opens the offline data upload modal |
1117
+ | `handleApplyFlexFilters(filters)` | Applies flex filters to the list, merged by `columnId`. `filters` is `FlexFilterData[]`. Requires `bareSettings.bareFlexFilters` — see Flex Filters below |
1118
+ | `handleClearFlexFilters(columnIds?)` | Clears flex filters by `columnId` (others preserved), or all when called with no args. Requires `bareSettings.bareFlexFilters` |
1032
1119
 
1033
1120
  ---
1034
1121
 
@@ -1148,12 +1235,12 @@ A shorthand that builds the `extendedTabs` and `viewId` config for you. Only one
1148
1235
 
1149
1236
  | Name | Type | Description |
1150
1237
  | ---------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
1151
- | `bareReadComponent` | `(data: any) => ReactElement` | Custom component rendered in read view |
1238
+ | `bareReadComponent` | `(data: any, rawData?: any) => ReactElement` | Custom component rendered in read view. `data` is display data (labels + elements); `rawData` is the untouched DB doc |
1152
1239
  | `bareReadComponentPos` | `"top"` | Renders `bareReadComponent` above the default read content |
1153
1240
  | `bareReadComponentHide` | `boolean` | Hides the default read content |
1154
1241
  | `bareReadDescription` | `(key, value) => any` | Custom renderer for read view field values |
1155
1242
  | `bareReadReferenceView` | `(dataId, collectionId, attributeId) => boolean \| void` | Controls whether a reference field opens a view |
1156
- | `bareReadHandleData` | `(data: any) => void` | Called when read view data is loaded |
1243
+ | `bareReadHandleData` | `(data: any, rawData?: any) => void` | Called when read view data is loaded. `data` is display data (labels + elements); `rawData` is the untouched DB doc |
1157
1244
  | `bareReadListTabs` | `boolean` | Shows list-style tabs in read view |
1158
1245
  | `bareListComponent` | `(data, prettierData, loading, pageIndex) => ReactElement` | Fully replaces the list UI |
1159
1246
  | `bareListActionBtns` | `(data: any) => ReactElement[]` | Extra icon buttons on each list row |