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.
- package/lib/components/action/iconButton/iconButton.stories.tsx +11 -2
- package/lib/components/auth/signUp/signUp.stories.tsx +39 -1
- package/lib/components/chart/statsCard/README.md +24 -0
- package/lib/components/chart/statsCard/statsCard.stories.tsx +68 -0
- package/lib/components/chat/chatBase/chatBase.stories.tsx +1 -1
- package/lib/components/data/dataGraph/dataGraph.stories.tsx +5 -5
- package/lib/components/data/dataTemplate/README.md +204 -85
- package/lib/components/data/dataTemplate/dataTemplate.stories.tsx +307 -295
- package/lib/components/data/dataTemplate/drive.stories.tsx +197 -0
- package/lib/components/data/dataWrapper/README.md +109 -22
- package/lib/components/data/dataWrapper/dataWrapper.bareHooks.stories.tsx +951 -0
- package/lib/components/data/dataWrapper/dataWrapper.edit.stories.tsx +356 -5
- package/lib/components/data/dataWrapper/dataWrapper.view.stories.tsx +79 -5
- package/lib/components/display/calendar/calendar.stories.tsx +1 -1
- package/lib/components/display/detailList/README.md +11 -10
- package/lib/components/display/detailList/detailList.stories.tsx +16 -3
- package/lib/components/display/feeds/README.md +3 -3
- package/lib/components/display/feeds/feeds.stories.tsx +3 -3
- package/lib/components/display/filePreview/filePreview.stories.tsx +111 -0
- package/lib/components/display/modal/README.md +3 -0
- package/lib/components/display/modal/modal.stories.tsx +50 -1
- package/lib/components/display/newCalendar/README.md +0 -6
- package/lib/components/display/newCalendar/newCalendar.stories.tsx +1 -1
- package/lib/components/display/newFeeds/README.md +1 -5
- package/lib/components/display/newFeeds/newFeeds.stories.tsx +3 -3
- package/lib/components/display/newKanban/README.md +0 -7
- package/lib/components/display/newList/README.md +1 -4
- package/lib/components/display/newTable/README.md +29 -9
- package/lib/components/display/newTable/newTable.stories.tsx +120 -9
- package/lib/components/display/tag/README.md +1 -1
- package/lib/components/display/tag/tag.stories.tsx +7 -0
- package/lib/components/input/markdown/markdown.stories.tsx +3 -1
- package/lib/components/input/uploadInput/uploadInput.stories.tsx +41 -0
- package/lib/components/navigation/leftNav/leftNav.stories.tsx +175 -0
- package/lib/components/navigation/tabs/tabs.stories.tsx +23 -0
- package/lib/components/template/calendarView/calendarView.stories.tsx +4 -4
- package/lib/components/template/chatDashboard/chatDashboard.stories.tsx +2 -2
- package/lib/mcp/tools/getBackendComponent.d.ts.map +1 -1
- package/lib/mcp/tools/getBackendComponent.js +7 -3
- package/lib/mcp/tools/getBackendComponent.js.map +1 -1
- package/lib/mcp/tools/suggestTemplate.js +3 -3
- package/lib/mcp/tools/suggestTemplate.js.map +1 -1
- package/package.json +1 -1
- package/lib/components/data/dataTemplate/dataModule.stories.tsx +0 -191
- package/lib/components/data/dataWrapper/dataWrapper.consumer.stories.tsx +0 -2125
- package/lib/components/data/dataWrapper/dataWrapper.utils.stories.tsx +0 -576
- 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
|
-
|
|
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 {
|
|
476
|
-
<p className="text-xs text-gray-500">Last updated: {
|
|
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
|
-
|
|
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.
|
|
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
|
|
1023
|
-
|
|
|
1024
|
-
| `handleRefresh()`
|
|
1025
|
-
| `handleRefreshCurrentPage()`
|
|
1026
|
-
| `handleFormSubmit()`
|
|
1027
|
-
| `handleFormCheck()`
|
|
1028
|
-
| `handleUpdateSelectedData(fields)`
|
|
1029
|
-
| `handleDeleteModal()`
|
|
1030
|
-
| `handleExportReadPDF()`
|
|
1031
|
-
| `handleUploadOfflineModal()`
|
|
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`
|
|
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`
|
|
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 |
|