blixify-ui-web-mcp 0.1.26 → 0.1.28
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/chat/chatAnchor/README.md +24 -22
- package/lib/components/chat/chatBase/README.md +47 -29
- package/lib/components/chat/chatBase/chatBase.stories.tsx +37 -10
- package/lib/components/chat/chatDashboard/README.md +5 -1
- package/lib/components/chat/chatDashboard/chatDashboard.stories.tsx +102 -38
- package/lib/components/data/dataTemplate/README.md +23 -3
- package/lib/components/data/dataWrapper/dataWrapper.edit.stories.tsx +33 -28
- package/lib/components/data/dataWrapper/dataWrapper.utils.stories.tsx +852 -0
- package/lib/components/data/dataWrapper/dataWrapper.view.stories.tsx +107 -81
- package/lib/components/data/flexFilter/flexFilter.stories.tsx +223 -0
- package/lib/components/display/modal/modal.stories.tsx +8 -5
- package/lib/components/display/newList/README.md +25 -13
- package/package.json +1 -1
|
@@ -22,11 +22,17 @@ import {
|
|
|
22
22
|
} from "../dataTemplate/spaceModel";
|
|
23
23
|
import {
|
|
24
24
|
DataWrapperTestSchema,
|
|
25
|
-
SCROLL_TEST_API,
|
|
26
25
|
SCROLL_TEST_COLLECTION,
|
|
27
26
|
ServiceChecklistSchema,
|
|
28
27
|
} from "./dataWrapper.fixtures";
|
|
29
28
|
import { DataTemplateWrapper } from "./index";
|
|
29
|
+
import {
|
|
30
|
+
STORY_API_HOST,
|
|
31
|
+
STORY_NAMES,
|
|
32
|
+
handleBuildStoryRecords,
|
|
33
|
+
storyWorkspace,
|
|
34
|
+
useStoryBackend,
|
|
35
|
+
} from "./storyBackend.fixtures";
|
|
30
36
|
import { StarIcon } from "@heroicons/react/24/outline";
|
|
31
37
|
import {
|
|
32
38
|
CheckCircleIcon,
|
|
@@ -46,18 +52,48 @@ const meta = {
|
|
|
46
52
|
|
|
47
53
|
export default meta;
|
|
48
54
|
|
|
55
|
+
// INFO: served from /api/mongoData rather than /api/data, the endpoint shape
|
|
56
|
+
// this story has always used
|
|
57
|
+
const CHECKLIST_ENDPOINT = `${STORY_API_HOST}/api/mongoData`;
|
|
58
|
+
|
|
59
|
+
const handleBuildChecklistRecord = (index: number) => ({
|
|
60
|
+
_id: `checklist-${index}`,
|
|
61
|
+
clientName: STORY_NAMES[index % STORY_NAMES.length],
|
|
62
|
+
clientPhone: `01${2000000 + index * 1379}`,
|
|
63
|
+
address: `${index} Jalan Contoh, 50450 Kuala Lumpur`,
|
|
64
|
+
date: dayjs()
|
|
65
|
+
.add(index - 15, "day")
|
|
66
|
+
.toISOString(),
|
|
67
|
+
installerName: STORY_NAMES[(index + 4) % STORY_NAMES.length],
|
|
68
|
+
numberOfModules: 8 + (index % 12),
|
|
69
|
+
typeOfInverter: index % 2 === 0 ? "String Inverter" : "Microinverter",
|
|
70
|
+
numberOfInverters: 1 + (index % 3),
|
|
71
|
+
status: index % 3 === 0 ? "Approved" : "Pending",
|
|
72
|
+
panelsCleaned: index % 2 === 0,
|
|
73
|
+
cablingInspected: index % 4 !== 0,
|
|
74
|
+
// INFO: left empty — an image value is a file name resolved against the asset
|
|
75
|
+
// endpoint, and the story asset endpoint serves nothing
|
|
76
|
+
flowDiagramAppendix: "",
|
|
77
|
+
notes: index % 5 === 0 ? "Access via the rear gate." : "",
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// INFO: the e2e CRUD spec answers this story's requests with page.route
|
|
49
81
|
export const DataWrapper = () => {
|
|
50
|
-
const
|
|
82
|
+
const mount = useStoryBackend({
|
|
83
|
+
endpoint: CHECKLIST_ENDPOINT,
|
|
84
|
+
collections: {
|
|
85
|
+
"dtw-test-collection": handleBuildStoryRecords(
|
|
86
|
+
30,
|
|
87
|
+
handleBuildChecklistRecord,
|
|
88
|
+
),
|
|
89
|
+
},
|
|
90
|
+
yieldToE2E: true,
|
|
91
|
+
});
|
|
51
92
|
const dataRefreshRef: any = useRef(null);
|
|
52
93
|
const [statusFilter] = useState<string>("all");
|
|
53
94
|
const [viewType, setViewType] = useState<"list" | "read" | "update">("list");
|
|
54
95
|
const [selectedId, setSelectedId] = useState<string | undefined>(undefined);
|
|
55
96
|
const [listLayout, setListLayout] = useState("table");
|
|
56
|
-
const dtwBaseUrl = "http://localhost:3000";
|
|
57
|
-
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
setMount(true);
|
|
60
|
-
}, []);
|
|
61
97
|
|
|
62
98
|
useEffect(() => {
|
|
63
99
|
if (mount && dataRefreshRef.current) {
|
|
@@ -158,15 +194,7 @@ export const DataWrapper = () => {
|
|
|
158
194
|
id={selectedId}
|
|
159
195
|
collectionId={"dtw-test-collection"}
|
|
160
196
|
schema={ServiceChecklistSchema}
|
|
161
|
-
workspace={
|
|
162
|
-
apiEndpointType: "mongoDB",
|
|
163
|
-
apiEndpoint: `${dtwBaseUrl}/api/mongoData`,
|
|
164
|
-
apiEndpointToken: "",
|
|
165
|
-
apiUserToken: "",
|
|
166
|
-
assetEndpoint:
|
|
167
|
-
"https://firebasestorage.googleapis.com/v0/b/demo-bucket.appspot.com/o/",
|
|
168
|
-
assetUploadEndpoint: `${dtwBaseUrl}/api/asset`,
|
|
169
|
-
}}
|
|
197
|
+
workspace={storyWorkspace(CHECKLIST_ENDPOINT)}
|
|
170
198
|
edit={{
|
|
171
199
|
allEditable: true,
|
|
172
200
|
disabledUpdate: false,
|
|
@@ -608,22 +636,42 @@ const DTWGroupTestSchema = [
|
|
|
608
636
|
},
|
|
609
637
|
];
|
|
610
638
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
639
|
+
const WORK_ORDER_STATUSES = [
|
|
640
|
+
"Not Completed",
|
|
641
|
+
"Completed",
|
|
642
|
+
"In Progress",
|
|
643
|
+
"Cancelled",
|
|
644
|
+
];
|
|
645
|
+
|
|
646
|
+
const handleBuildWorkOrderRecord = (index: number) => {
|
|
647
|
+
const start = dayjs().subtract(index, "day");
|
|
648
|
+
return {
|
|
649
|
+
_id: `work-order-${index}`,
|
|
650
|
+
title: `Work Order ${String(index).padStart(3, "0")}`,
|
|
651
|
+
status: WORK_ORDER_STATUSES[index % WORK_ORDER_STATUSES.length],
|
|
652
|
+
operationDay: (index % 4) + 1,
|
|
653
|
+
startDate: start.toISOString(),
|
|
654
|
+
endDate: start.add(6, "hour").toISOString(),
|
|
655
|
+
totalFlight: 2 + (index % 6),
|
|
656
|
+
totalHours: 3 + (index % 5),
|
|
657
|
+
};
|
|
658
|
+
};
|
|
616
659
|
|
|
660
|
+
// INFO: the e2e CRUD spec answers this story's requests with page.route
|
|
617
661
|
export const DTWGroupTest = () => {
|
|
618
|
-
const
|
|
662
|
+
const mount = useStoryBackend({
|
|
663
|
+
collections: {
|
|
664
|
+
[SCROLL_TEST_COLLECTION]: handleBuildStoryRecords(
|
|
665
|
+
24,
|
|
666
|
+
handleBuildWorkOrderRecord,
|
|
667
|
+
),
|
|
668
|
+
},
|
|
669
|
+
yieldToE2E: true,
|
|
670
|
+
});
|
|
619
671
|
const dataRefreshRef: any = useRef(null);
|
|
620
672
|
const [viewType, setViewType] = useState<"list" | "read" | "update">("list");
|
|
621
673
|
const [selectedId, setSelectedId] = useState<string | undefined>(undefined);
|
|
622
674
|
|
|
623
|
-
useEffect(() => {
|
|
624
|
-
setMount(true);
|
|
625
|
-
}, []);
|
|
626
|
-
|
|
627
675
|
if (!mount) return <Loading />;
|
|
628
676
|
|
|
629
677
|
return (
|
|
@@ -631,8 +679,8 @@ export const DTWGroupTest = () => {
|
|
|
631
679
|
<div className="font-semibold mb-1 text-gray-900">DTW Group Test</div>
|
|
632
680
|
<div className="text-sm font-light mb-4 text-gray-500">
|
|
633
681
|
Multi-tab + multi-group schema demo. List + read view renders sections
|
|
634
|
-
by tabId / groupId; Create / Update / Delete write to the
|
|
635
|
-
backend.
|
|
682
|
+
by tabId / groupId; Create / Update / Delete write to the in-memory
|
|
683
|
+
story backend.
|
|
636
684
|
</div>
|
|
637
685
|
<DataTemplateWrapper
|
|
638
686
|
ref={dataRefreshRef}
|
|
@@ -641,14 +689,7 @@ export const DTWGroupTest = () => {
|
|
|
641
689
|
id={selectedId}
|
|
642
690
|
collectionId={SCROLL_TEST_COLLECTION}
|
|
643
691
|
schema={DTWGroupTestSchema}
|
|
644
|
-
workspace={
|
|
645
|
-
apiEndpointType: "mongoDB",
|
|
646
|
-
apiEndpoint: SCROLL_TEST_API,
|
|
647
|
-
apiEndpointToken: "",
|
|
648
|
-
apiUserToken: "",
|
|
649
|
-
assetEndpoint: "",
|
|
650
|
-
assetUploadEndpoint: SCROLL_TEST_API.replace("/data", "/asset"),
|
|
651
|
-
}}
|
|
692
|
+
workspace={storyWorkspace()}
|
|
652
693
|
edit={{
|
|
653
694
|
allEditable: true,
|
|
654
695
|
disabledUpdate: false,
|
|
@@ -702,13 +743,9 @@ DTWGroupTest.storyName = "DTW Group Test";
|
|
|
702
743
|
// SECTION: Offline demo — DTW with no backend required
|
|
703
744
|
// ===================================================================================
|
|
704
745
|
|
|
705
|
-
// INFO:
|
|
706
|
-
//
|
|
707
|
-
//
|
|
708
|
-
// DataTemplateWrapper passes down as lib.axios. Nothing here reaches the network, so the
|
|
709
|
-
// story is the reference for reviewing DTW behaviour (including viewSettings.readDrawer) without
|
|
710
|
-
// any setup. The .invalid TLD is reserved by RFC 2606 and can never resolve, so a missed
|
|
711
|
-
// mock fails loudly instead of silently hitting a real host.
|
|
746
|
+
// INFO: serves DTW from an in-memory array via axios-mock-adapter, installed on the shared
|
|
747
|
+
// axios instance that DataTemplateWrapper passes down as lib.axios. Its own mock rather than
|
|
748
|
+
// the story backend, because it numbers new records (refNo) the way this demo shows them.
|
|
712
749
|
const DRAWER_DEMO_COLLECTION = "dtw-drawer-demo";
|
|
713
750
|
|
|
714
751
|
const DRAWER_DEMO_ENDPOINT = "https://demo.blixify.invalid/api/data";
|
|
@@ -937,15 +974,20 @@ const handleInstallDrawerDemoMock = (store: any[]) => {
|
|
|
937
974
|
// state wiring is identical — handleNavigate still just sets type + id, and the wrapper
|
|
938
975
|
// decides whether a read means "swap the page" or "open the panel".
|
|
939
976
|
export const DataWrapperDrawerTest = () => {
|
|
940
|
-
const
|
|
977
|
+
const mount = useStoryBackend({
|
|
978
|
+
collections: {
|
|
979
|
+
"dtw-test-collection": handleBuildStoryRecords(12, (index) => ({
|
|
980
|
+
_id: `checklist-${index}`,
|
|
981
|
+
clientName: STORY_NAMES[index % STORY_NAMES.length],
|
|
982
|
+
clientPhone: `01${2000000 + index * 1379}`,
|
|
983
|
+
address: `${index} Jalan Contoh, 50450 Kuala Lumpur`,
|
|
984
|
+
})),
|
|
985
|
+
},
|
|
986
|
+
yieldToE2E: true,
|
|
987
|
+
});
|
|
941
988
|
const dataRefreshRef: any = useRef(null);
|
|
942
989
|
const [viewType, setViewType] = useState<"list" | "read" | "update">("list");
|
|
943
990
|
const [selectedId, setSelectedId] = useState<string | undefined>(undefined);
|
|
944
|
-
const dtwBaseUrl = "http://localhost:3000";
|
|
945
|
-
|
|
946
|
-
useEffect(() => {
|
|
947
|
-
setMount(true);
|
|
948
|
-
}, []);
|
|
949
991
|
|
|
950
992
|
const handleNavigate = (type: any, id: any) => {
|
|
951
993
|
setViewType(type);
|
|
@@ -973,15 +1015,7 @@ export const DataWrapperDrawerTest = () => {
|
|
|
973
1015
|
collectionId={"dtw-test-collection"}
|
|
974
1016
|
schema={DataWrapperTestSchema}
|
|
975
1017
|
viewSettings={{ readDrawer: {} }}
|
|
976
|
-
workspace={
|
|
977
|
-
apiEndpointType: "mongoDB",
|
|
978
|
-
apiEndpoint: `${dtwBaseUrl}/api/data`,
|
|
979
|
-
apiEndpointToken: "",
|
|
980
|
-
apiUserToken: "",
|
|
981
|
-
assetEndpoint:
|
|
982
|
-
"https://firebasestorage.googleapis.com/v0/b/demo-bucket.firebasestorage.app/o/",
|
|
983
|
-
assetUploadEndpoint: `${dtwBaseUrl}/api/asset`,
|
|
984
|
-
}}
|
|
1018
|
+
workspace={storyWorkspace()}
|
|
985
1019
|
edit={{
|
|
986
1020
|
allEditable: true,
|
|
987
1021
|
disabledUpdate: false,
|
|
@@ -1009,13 +1043,9 @@ DataWrapperDrawerTest.storyName = "DTW Drawer Test";
|
|
|
1009
1043
|
// SECTION: Offline demo — DTW with no backend required
|
|
1010
1044
|
// ===================================================================================
|
|
1011
1045
|
|
|
1012
|
-
// INFO:
|
|
1013
|
-
//
|
|
1014
|
-
//
|
|
1015
|
-
// DataTemplateWrapper passes down as lib.axios. Nothing here reaches the network, so the
|
|
1016
|
-
// story is the reference for reviewing DTW behaviour (including viewSettings.readDrawer) without
|
|
1017
|
-
// any setup. The .invalid TLD is reserved by RFC 2606 and can never resolve, so a missed
|
|
1018
|
-
// mock fails loudly instead of silently hitting a real host.
|
|
1046
|
+
// INFO: serves DTW from an in-memory array via axios-mock-adapter, installed on the shared
|
|
1047
|
+
// axios instance that DataTemplateWrapper passes down as lib.axios. Its own mock rather than
|
|
1048
|
+
// the story backend, because it numbers new records (refNo) the way this demo shows them.
|
|
1019
1049
|
|
|
1020
1050
|
export const DTWDrawerDemo = () => {
|
|
1021
1051
|
const [mount, setMount] = useState<boolean>(false);
|
|
@@ -1167,16 +1197,20 @@ const DTWScrollTestSchema = [
|
|
|
1167
1197
|
];
|
|
1168
1198
|
|
|
1169
1199
|
export const DTWScrollTest = () => {
|
|
1170
|
-
|
|
1200
|
+
// INFO: enough rows for several scroll pages in every view type
|
|
1201
|
+
const mount = useStoryBackend({
|
|
1202
|
+
collections: {
|
|
1203
|
+
[SCROLL_TEST_COLLECTION]: handleBuildStoryRecords(
|
|
1204
|
+
120,
|
|
1205
|
+
handleBuildWorkOrderRecord,
|
|
1206
|
+
),
|
|
1207
|
+
},
|
|
1208
|
+
});
|
|
1171
1209
|
const dataRefreshRef: any = useRef(null);
|
|
1172
1210
|
const [viewType, setViewType] = useState<"list" | "read" | "update">("list");
|
|
1173
1211
|
const [selectedId, setSelectedId] = useState<string | undefined>(undefined);
|
|
1174
1212
|
const [viewId, setViewId] = useState<string>("scroll_list");
|
|
1175
1213
|
|
|
1176
|
-
useEffect(() => {
|
|
1177
|
-
setMount(true);
|
|
1178
|
-
}, []);
|
|
1179
|
-
|
|
1180
1214
|
// INFO: no second handleRefresh effect — ReadQueryHoc auto-fetches on mount,
|
|
1181
1215
|
// a manual refresh here would only cause a visible double-loading flash.
|
|
1182
1216
|
|
|
@@ -1205,9 +1239,8 @@ export const DTWScrollTest = () => {
|
|
|
1205
1239
|
<div className="w-full">
|
|
1206
1240
|
<div className="font-semibold mb-1 text-gray-900">DTW Scroll Test</div>
|
|
1207
1241
|
<div className="text-sm font-light mb-4 text-gray-500">
|
|
1208
|
-
Scroll pagination
|
|
1209
|
-
|
|
1210
|
-
feeds, and bare view types.
|
|
1242
|
+
Scroll pagination across list, feeds and bare view types, over 120 rows
|
|
1243
|
+
from the in-memory story backend.
|
|
1211
1244
|
</div>
|
|
1212
1245
|
<DataTemplateWrapper
|
|
1213
1246
|
ref={dataRefreshRef}
|
|
@@ -1216,14 +1249,7 @@ export const DTWScrollTest = () => {
|
|
|
1216
1249
|
id={selectedId}
|
|
1217
1250
|
collectionId={SCROLL_TEST_COLLECTION}
|
|
1218
1251
|
schema={DTWScrollTestSchema}
|
|
1219
|
-
workspace={
|
|
1220
|
-
apiEndpointType: "mongoDB",
|
|
1221
|
-
apiEndpoint: SCROLL_TEST_API,
|
|
1222
|
-
apiEndpointToken: "",
|
|
1223
|
-
apiUserToken: "",
|
|
1224
|
-
assetEndpoint: "",
|
|
1225
|
-
assetUploadEndpoint: SCROLL_TEST_API.replace("/data", "/asset"),
|
|
1226
|
-
}}
|
|
1252
|
+
workspace={storyWorkspace()}
|
|
1227
1253
|
edit={{ allEditable: false }}
|
|
1228
1254
|
bareSettings={{
|
|
1229
1255
|
bareCompactMode: true,
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { Transition } from "@headlessui/react";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import { Button } from "../../action/button";
|
|
5
|
+
import { StorySection } from "../../storyUtils";
|
|
6
|
+
import { DataStructure } from "../dataTemplate/model";
|
|
7
|
+
import {
|
|
8
|
+
FlexFilter as FlexFilterComponent,
|
|
9
|
+
renderFlexFilterConditions,
|
|
10
|
+
} from ".";
|
|
11
|
+
import { FlexFilterData } from "./model";
|
|
12
|
+
import { Toggle } from "../../action/toggle";
|
|
13
|
+
import { DevSettings } from "../dataTemplate/model";
|
|
14
|
+
import { Loading } from "../../display/loading";
|
|
15
|
+
import {
|
|
16
|
+
STORY_API_ENDPOINT,
|
|
17
|
+
STORY_NAMES,
|
|
18
|
+
useStoryBackend,
|
|
19
|
+
} from "../dataWrapper/storyBackend.fixtures";
|
|
20
|
+
|
|
21
|
+
const meta = { title: "Data/Flex Filter" };
|
|
22
|
+
export default meta;
|
|
23
|
+
|
|
24
|
+
const CREW_COLLECTION = "demo-crew";
|
|
25
|
+
|
|
26
|
+
// INFO: one column per filter type, including a reference column whose options
|
|
27
|
+
// load from the story backend
|
|
28
|
+
const sampleSchema: DataStructure[] = [
|
|
29
|
+
{ id: "title", name: "Title", type: "string" },
|
|
30
|
+
{ id: "verified", name: "Verified", type: "boolean" },
|
|
31
|
+
{
|
|
32
|
+
id: "status",
|
|
33
|
+
name: "Status",
|
|
34
|
+
type: "list",
|
|
35
|
+
listSelection: ["Not Completed", "In Progress", "Completed", "Cancelled"],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "tags",
|
|
39
|
+
name: "Tags",
|
|
40
|
+
type: "multipleList",
|
|
41
|
+
listSelection: ["Urgent", "Follow Up", "Warranty", "Inspection"],
|
|
42
|
+
},
|
|
43
|
+
{ id: "startDate", name: "Clock In", type: "date" },
|
|
44
|
+
{ id: "operationDay", name: "Operation Day", type: "number" },
|
|
45
|
+
{
|
|
46
|
+
id: "crewId",
|
|
47
|
+
name: "Crew Lead",
|
|
48
|
+
type: "reference",
|
|
49
|
+
referenceLabel: "name",
|
|
50
|
+
referenceSpaceId: CREW_COLLECTION,
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const devSettings: DevSettings = {
|
|
55
|
+
server: "mongoServer",
|
|
56
|
+
dbEndpoint: STORY_API_ENDPOINT,
|
|
57
|
+
imageEndpoint: "",
|
|
58
|
+
uploadEndpoint: "",
|
|
59
|
+
dbAPIToken: "",
|
|
60
|
+
dbUserToken: "",
|
|
61
|
+
schemaEndpoint: "",
|
|
62
|
+
bucketName: "",
|
|
63
|
+
organisationMembers: [],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const FlexFilter = () => {
|
|
67
|
+
const mount = useStoryBackend({
|
|
68
|
+
collections: {
|
|
69
|
+
[CREW_COLLECTION]: STORY_NAMES.map((name, index) => ({
|
|
70
|
+
_id: `crew-${index + 1}`,
|
|
71
|
+
name,
|
|
72
|
+
})),
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
const [bareFilters, setBareFilters] = useState<FlexFilterData[]>([
|
|
76
|
+
{ id: "bare-init", columnId: "", operator: "=", value: undefined },
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const [autoFilters, setAutoFilters] = useState<FlexFilterData[]>([
|
|
80
|
+
{ id: "auto-init", columnId: "", operator: "=", value: undefined },
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
const [modalFilters, setModalFilters] = useState<FlexFilterData[]>([
|
|
84
|
+
{ id: "modal-init", columnId: "", operator: "=", value: undefined },
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
const [addCompFilters, setAddCompFilters] = useState<FlexFilterData[]>([
|
|
88
|
+
{ id: "addcomp-init", columnId: "", operator: "=", value: undefined },
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
const bareQueryConditions = renderFlexFilterConditions(
|
|
92
|
+
bareFilters,
|
|
93
|
+
sampleSchema,
|
|
94
|
+
);
|
|
95
|
+
const autoQueryConditions = renderFlexFilterConditions(
|
|
96
|
+
autoFilters,
|
|
97
|
+
sampleSchema,
|
|
98
|
+
);
|
|
99
|
+
const modalQueryConditions = renderFlexFilterConditions(
|
|
100
|
+
modalFilters,
|
|
101
|
+
sampleSchema,
|
|
102
|
+
);
|
|
103
|
+
const addCompQueryConditions = renderFlexFilterConditions(
|
|
104
|
+
addCompFilters,
|
|
105
|
+
sampleSchema,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (!mount) return <Loading />;
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div className="flex flex-col space-y-12">
|
|
112
|
+
<StorySection
|
|
113
|
+
title="Variant: bare"
|
|
114
|
+
description="Raw filter panel with no wrapper. Consumer renders their own container."
|
|
115
|
+
>
|
|
116
|
+
<div className="flex flex-col">
|
|
117
|
+
<div className="max-w-[700px]">
|
|
118
|
+
<FlexFilterComponent
|
|
119
|
+
structure={sampleSchema}
|
|
120
|
+
filters={bareFilters}
|
|
121
|
+
onChange={setBareFilters}
|
|
122
|
+
devSettings={devSettings}
|
|
123
|
+
dbModule={axios}
|
|
124
|
+
variant="bare"
|
|
125
|
+
lib={{ Transition }}
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
<div className="mt-4 p-4 bg-gray-50 rounded-lg border">
|
|
129
|
+
<p className="text-sm font-semibold mb-2">
|
|
130
|
+
Generated Query Conditions ({bareQueryConditions.length})
|
|
131
|
+
</p>
|
|
132
|
+
<pre className="text-xs whitespace-pre-wrap">
|
|
133
|
+
{JSON.stringify(bareQueryConditions, null, 2)}
|
|
134
|
+
</pre>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</StorySection>
|
|
138
|
+
|
|
139
|
+
<StorySection
|
|
140
|
+
title="Variant: auto (PopUp on desktop, Modal on mobile)"
|
|
141
|
+
description="Uses PopUp on md+ screens and Modal on mobile. Click the funnel icon to open."
|
|
142
|
+
>
|
|
143
|
+
<div className="flex flex-col w-96 max-w-96">
|
|
144
|
+
<div className="flex gap-x-3 w-ful">
|
|
145
|
+
<div className="h-12 bg-gray-100 rounded-lg flex-1"></div>
|
|
146
|
+
<FlexFilterComponent
|
|
147
|
+
structure={sampleSchema}
|
|
148
|
+
filters={autoFilters}
|
|
149
|
+
onChange={setAutoFilters}
|
|
150
|
+
devSettings={devSettings}
|
|
151
|
+
dbModule={axios}
|
|
152
|
+
variant="auto"
|
|
153
|
+
lib={{ Transition }}
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
<div className="mt-4 p-4 bg-gray-50 rounded-lg border">
|
|
157
|
+
<p className="text-sm font-semibold mb-2">
|
|
158
|
+
Generated Query Conditions ({autoQueryConditions.length})
|
|
159
|
+
</p>
|
|
160
|
+
<pre className="text-xs whitespace-pre-wrap">
|
|
161
|
+
{JSON.stringify(autoQueryConditions, null, 2)}
|
|
162
|
+
</pre>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</StorySection>
|
|
166
|
+
<StorySection
|
|
167
|
+
title="Variant: modal with custom CTA"
|
|
168
|
+
description="Modal variant triggered by a custom button instead of the default funnel icon."
|
|
169
|
+
>
|
|
170
|
+
<div className="flex flex-col">
|
|
171
|
+
<FlexFilterComponent
|
|
172
|
+
structure={sampleSchema}
|
|
173
|
+
filters={modalFilters}
|
|
174
|
+
onChange={setModalFilters}
|
|
175
|
+
devSettings={devSettings}
|
|
176
|
+
dbModule={axios}
|
|
177
|
+
variant="modal"
|
|
178
|
+
lib={{ Transition }}
|
|
179
|
+
ctaComp={<Button text="Filters" type="normal" size="small" />}
|
|
180
|
+
/>
|
|
181
|
+
<div className="mt-4 p-4 bg-gray-50 rounded-lg border">
|
|
182
|
+
<p className="text-sm font-semibold mb-2">
|
|
183
|
+
Generated Query Conditions ({modalQueryConditions.length})
|
|
184
|
+
</p>
|
|
185
|
+
<pre className="text-xs whitespace-pre-wrap">
|
|
186
|
+
{JSON.stringify(modalQueryConditions, null, 2)}
|
|
187
|
+
</pre>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</StorySection>
|
|
191
|
+
|
|
192
|
+
<StorySection
|
|
193
|
+
title="Variant: bare with additional filter components"
|
|
194
|
+
description="bareListFilterAddComponent renders extra filter inputs above the flex filter rows."
|
|
195
|
+
>
|
|
196
|
+
<div className="flex flex-col">
|
|
197
|
+
<div className="max-w-[700px]">
|
|
198
|
+
<FlexFilterComponent
|
|
199
|
+
structure={sampleSchema}
|
|
200
|
+
filters={addCompFilters}
|
|
201
|
+
onChange={setAddCompFilters}
|
|
202
|
+
devSettings={devSettings}
|
|
203
|
+
dbModule={axios}
|
|
204
|
+
variant="bare"
|
|
205
|
+
lib={{ Transition }}
|
|
206
|
+
bareListFilterAddComponent={[
|
|
207
|
+
<Toggle text="Show all events" value={true} direction="left" />,
|
|
208
|
+
]}
|
|
209
|
+
/>
|
|
210
|
+
</div>
|
|
211
|
+
<div className="mt-4 p-4 bg-gray-50 rounded-lg border">
|
|
212
|
+
<p className="text-sm font-semibold mb-2">
|
|
213
|
+
Generated Query Conditions ({addCompQueryConditions.length})
|
|
214
|
+
</p>
|
|
215
|
+
<pre className="text-xs whitespace-pre-wrap">
|
|
216
|
+
{JSON.stringify(addCompQueryConditions, null, 2)}
|
|
217
|
+
</pre>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</StorySection>
|
|
221
|
+
</div>
|
|
222
|
+
);
|
|
223
|
+
};
|
|
@@ -3,6 +3,10 @@ import { CheckCircleIcon } from "@heroicons/react/24/outline";
|
|
|
3
3
|
import React, { useState } from "react";
|
|
4
4
|
import { Modal as ModalImpl } from ".";
|
|
5
5
|
import { DataTemplateWrapper } from "../../data/dataWrapper";
|
|
6
|
+
import {
|
|
7
|
+
storyWorkspace,
|
|
8
|
+
useStoryBackend,
|
|
9
|
+
} from "../../data/dataWrapper/storyBackend.fixtures";
|
|
6
10
|
import { Notification } from "../notification";
|
|
7
11
|
|
|
8
12
|
const meta = { title: "Display/Modal" };
|
|
@@ -10,6 +14,7 @@ export default meta;
|
|
|
10
14
|
|
|
11
15
|
export const Modal = () => {
|
|
12
16
|
const [open, setOpen] = useState(true);
|
|
17
|
+
const mount = useStoryBackend({ collections: { "modal-updates": [] } });
|
|
13
18
|
const notification = (
|
|
14
19
|
<Notification
|
|
15
20
|
icon={<CheckCircleIcon className="w-10 h-10 text-primary-600" />}
|
|
@@ -24,6 +29,8 @@ export const Modal = () => {
|
|
|
24
29
|
/>
|
|
25
30
|
);
|
|
26
31
|
|
|
32
|
+
if (!mount) return null;
|
|
33
|
+
|
|
27
34
|
return (
|
|
28
35
|
<ModalImpl
|
|
29
36
|
title="Modal Title"
|
|
@@ -46,11 +53,7 @@ export const Modal = () => {
|
|
|
46
53
|
},
|
|
47
54
|
{ id: "startDate", type: "date", name: "Clock In" },
|
|
48
55
|
]}
|
|
49
|
-
workspace={
|
|
50
|
-
apiEndpoint: "http://localhost:3000/api/data",
|
|
51
|
-
assetEndpoint:
|
|
52
|
-
"https://firebasestorage.googleapis.com/v0/b/demo-bucket.appspot.com/o/",
|
|
53
|
-
}}
|
|
56
|
+
workspace={storyWorkspace()}
|
|
54
57
|
additionalQuery={[]}
|
|
55
58
|
organise={{
|
|
56
59
|
breadcrumbType: "button",
|
|
@@ -17,6 +17,7 @@ Mobile-native list foundation. Other components (`NewFeeds`, `NewTable` mobile b
|
|
|
17
17
|
## Foundation status
|
|
18
18
|
|
|
19
19
|
This is the **base primitive** for the Batch 3 work. `NewListRow` is exported intentionally so:
|
|
20
|
+
|
|
20
21
|
- `NewFeeds` can use it with `renderExtra` to attach quoted content + reactions
|
|
21
22
|
- `NewTable` mobile breakpoint can use it instead of inventing a card-per-row layout
|
|
22
23
|
|
|
@@ -24,15 +25,15 @@ Do not duplicate the row layout in those components — extend `NewListRow` via
|
|
|
24
25
|
|
|
25
26
|
## Item content
|
|
26
27
|
|
|
27
|
-
| Item field
|
|
28
|
-
|
|
|
29
|
-
| `title`
|
|
30
|
-
| `description`
|
|
31
|
-
| `tag` / `tags`
|
|
32
|
-
| `date`
|
|
33
|
-
| `icon` / `img`
|
|
34
|
-
| `action`
|
|
35
|
-
| any key in `extraKeys` | Block content under the description, in order
|
|
28
|
+
| Item field | Rendered as |
|
|
29
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
30
|
+
| `title` | Row title |
|
|
31
|
+
| `description` | String / number: a `<p>`. Element: a `<div>` (block content). Both are truncated to one line per line of content unless `noTruncate` |
|
|
32
|
+
| `tag` / `tags` | Pill(s) next to the title |
|
|
33
|
+
| `date` | Right-aligned date text (see below) |
|
|
34
|
+
| `icon` / `img` | Avatar |
|
|
35
|
+
| `action` | Right-side element |
|
|
36
|
+
| any key in `extraKeys` | Block content under the description, in order |
|
|
36
37
|
|
|
37
38
|
The legacy `List` renders **every** extra key on an item. `NewList` renders only the keys listed in `extraKeys`:
|
|
38
39
|
|
|
@@ -42,10 +43,10 @@ The legacy `List` renders **every** extra key on an item. `NewList` renders only
|
|
|
42
43
|
|
|
43
44
|
## Date
|
|
44
45
|
|
|
45
|
-
| `date` value
|
|
46
|
-
|
|
|
47
|
-
| `Date`, or a string / number `new Date()` parses
|
|
48
|
-
| A string `new Date()` cannot parse (e.g. `"18/09/2026 10:00"`) | The string as given
|
|
46
|
+
| `date` value | Output |
|
|
47
|
+
| -------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
48
|
+
| `Date`, or a string / number `new Date()` parses | `dateFormat(date)` when given, else `date.toLocaleDateString()` (browser locale, no time) |
|
|
49
|
+
| A string `new Date()` cannot parse (e.g. `"18/09/2026 10:00"`) | The string as given |
|
|
49
50
|
|
|
50
51
|
```tsx
|
|
51
52
|
<NewList
|
|
@@ -54,6 +55,17 @@ The legacy `List` renders **every** extra key on an item. `NewList` renders only
|
|
|
54
55
|
/>
|
|
55
56
|
```
|
|
56
57
|
|
|
58
|
+
## Active row, row ids, empty text, bare
|
|
59
|
+
|
|
60
|
+
| Prop | Effect |
|
|
61
|
+
| ------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| `activeId` | The item with this `id` gets the selected tint and a left accent bar. Separate from checkbox `selectedIds` |
|
|
63
|
+
| `rowIdPrefix` | Each row gets `id` and `data-testid` of `${rowIdPrefix}${item.id}` |
|
|
64
|
+
| `emptyText` | Replaces "There is no data available at the moment" when the list is empty |
|
|
65
|
+
| `bare` | No card border, rounding, shadow or background, and no row dividers — for a list inside an already framed panel |
|
|
66
|
+
|
|
67
|
+
ChatDashboard's conversation list uses all four.
|
|
68
|
+
|
|
57
69
|
## Status
|
|
58
70
|
|
|
59
71
|
🚧 **Scaffold only.** Implementation lands in follow-up commits per #1714.
|