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
@@ -74,7 +74,7 @@ export const DataWrapperTest = () => {
74
74
  apiEndpointToken: "",
75
75
  apiUserToken: "",
76
76
  assetEndpoint:
77
- "https://firebasestorage.googleapis.com/v0/b/verdant-dev.firebasestorage.app/o/",
77
+ "https://firebasestorage.googleapis.com/v0/b/demo-bucket.firebasestorage.app/o/",
78
78
  assetUploadEndpoint: `${dtwBaseUrl}/api/asset`,
79
79
  }}
80
80
  edit={{
@@ -160,6 +160,11 @@ const InlineEditMatrixSchema = [
160
160
  optional: true,
161
161
  },
162
162
  { id: "due", name: "Due", type: "date" as const, optional: true },
163
+ // INFO: a composite editor — country picker beside a number field
164
+ { id: "phone", name: "Phone", type: "contact" as const, optional: true },
165
+ // INFO: a string column some rows fill with an object, which is what a
166
+ // schema change or an upstream sync leaves behind
167
+ { id: "place", name: "Place", type: "string" as const, optional: true },
163
168
  {
164
169
  id: "owner",
165
170
  name: "Owner",
@@ -245,6 +250,11 @@ const handleBuildMatrixRecords = () => {
245
250
  status: ["Draft", "Active", "Done"][index % 3],
246
251
  tags: index % 2 === 0 ? ["North"] : [],
247
252
  due: dayjs().add(index, "day").toISOString(),
253
+ phone: `+6012${String(index).padStart(7, "0")}`,
254
+ place:
255
+ index % 2 === 0
256
+ ? { name: `${index} Jalan Object`, lat: 3.1, lng: 101.6 }
257
+ : `${index} Jalan Plain`,
248
258
  owner: index % 2 === 0 ? "owner-01" : "owner-02",
249
259
  helpers: [],
250
260
  photo: "",
@@ -333,10 +343,10 @@ const handleInstallMatrixMock = (
333
343
  .reply((config) => {
334
344
  const body = handleParseBody(config);
335
345
  const id = `matrix-${String(store.length + 1).padStart(2, "0")}`;
336
- // INFO: baseDraft on purpose. listModule wraps a draft row's first field
337
- // in a <Tag>, so the row's cell holds a React element — which is exactly
338
- // the shape that turned into the literal text "div" when the list update
339
- // path deep cloned through JSON.
346
+ // INFO: baseDraft on purpose — it is what the create path stamps on a new
347
+ // inline row. The React-element-in-a-cell shape that once turned into the
348
+ // literal text "div" through the list update path's JSON clone now comes
349
+ // from the coloured status column, which renders its value as a Tag.
340
350
  store.unshift({ _id: id, baseDraft: true, ...(body.data ?? {}) });
341
351
  return [200, { success: id }];
342
352
  });
@@ -605,3 +615,344 @@ export const DTWEmbeddedInMarkdown = () => {
605
615
  </div>
606
616
  );
607
617
  };
618
+
619
+ // ===================================================================================
620
+
621
+ // INFO: the only story that exercises a listObject through NewTable. Every other
622
+ // listObject fixture in this repo sets listFlexTable, which takes the FlexTable
623
+ // branch instead — so without this one the NewTable path has no visual coverage.
624
+ // listObjectTableView is what selects the table; leave it off and the field
625
+ // renders as a List.
626
+
627
+ const LIST_OBJECT_ENDPOINT = "http://localhost:3000/api/data";
628
+ const LIST_OBJECT_COLLECTION = "dtw-list-object-table";
629
+
630
+ // INFO: tabIds on purpose — the action pill only renders its Previous/Next
631
+ // navigation when the form is tabbed, so a flat schema cannot exercise them
632
+ const ListObjectTableSchema: any = [
633
+ { id: "siteName", name: "Site Name", type: "string", tabId: "A. Site" },
634
+ {
635
+ id: "tasks",
636
+ name: "Tasks",
637
+ type: "listObject",
638
+ // INFO: this flag, not listFlexTable, is what routes the field to NewTable
639
+ listObjectTableView: true,
640
+ tabId: "B. Tasks",
641
+ optional: true,
642
+ objectData: [
643
+ { id: "title", name: "Task", type: "string" },
644
+ { id: "owner", name: "Owner", type: "string" },
645
+ { id: "hours", name: "Hours", type: "number", optional: true },
646
+ ],
647
+ },
648
+ {
649
+ id: "emptyTasks",
650
+ name: "Tasks With No Rows",
651
+ type: "listObject",
652
+ listObjectTableView: true,
653
+ tabId: "B. Tasks",
654
+ optional: true,
655
+ objectData: [
656
+ { id: "title", name: "Task", type: "string" },
657
+ { id: "owner", name: "Owner", type: "string" },
658
+ ],
659
+ },
660
+ ];
661
+
662
+ const handleBuildListObjectRecord = () => ({
663
+ _id: "site-01",
664
+ siteName: "Bangi Rooftop",
665
+ tasks: [
666
+ { id: "t1", title: "Wire the pump", owner: "Ali", hours: 4 },
667
+ { id: "t2", title: "Seal the tank", owner: "Siti", hours: 2 },
668
+ { id: "t3", title: "Commission inverter", owner: "Raj", hours: 6 },
669
+ ],
670
+ emptyTasks: [],
671
+ createdAt: dayjs().toISOString(),
672
+ });
673
+
674
+ export const DTWListObjectTable = () => {
675
+ const [mount, setMount] = useState<boolean>(false);
676
+ const [viewType, setViewType] = useState<"list" | "read" | "update">(
677
+ "update",
678
+ );
679
+ const [selectedId, setSelectedId] = useState<string | undefined>("site-01");
680
+ const storeRef = useRef<any[]>([handleBuildListObjectRecord()]);
681
+
682
+ useEffect(() => {
683
+ const store = storeRef.current;
684
+ const mock = new MockAdapter(axios, {
685
+ onNoMatch: "passthrough",
686
+ delayResponse: 80,
687
+ });
688
+ const handleParseBody = (config: any) => {
689
+ try {
690
+ return JSON.parse(config?.data ?? "{}");
691
+ } catch (err) {
692
+ return {};
693
+ }
694
+ };
695
+
696
+ mock
697
+ .onPost(`${LIST_OBJECT_ENDPOINT}/${LIST_OBJECT_COLLECTION}/list`)
698
+ .reply(() => [200, { data: store, count: store.length }]);
699
+
700
+ mock
701
+ .onPost(`${LIST_OBJECT_ENDPOINT}/${LIST_OBJECT_COLLECTION}/get`)
702
+ .reply((config) => {
703
+ const body = handleParseBody(config);
704
+ return [200, { data: store.find((row) => row._id === body.id) ?? null }];
705
+ });
706
+
707
+ mock
708
+ .onPost(`${LIST_OBJECT_ENDPOINT}/${LIST_OBJECT_COLLECTION}/update`)
709
+ .reply((config) => {
710
+ const body = handleParseBody(config);
711
+ const index = store.findIndex((row) => row._id === body.id);
712
+ if (index >= 0) store[index] = { ...store[index], ...(body.data ?? {}) };
713
+ return [200, { success: body.id ?? true }];
714
+ });
715
+
716
+ setMount(true);
717
+ return () => mock.restore();
718
+ }, []);
719
+
720
+ if (!mount) return <Loading />;
721
+
722
+ return (
723
+ <div className="w-full">
724
+ <div className="font-semibold mb-1 text-gray-900">
725
+ DTW List Object Table
726
+ </div>
727
+ <div className="text-sm font-light mb-4 text-gray-500">
728
+ A listObject rendered through NewTable — the branch every other fixture
729
+ misses because they set listFlexTable. Click a row in Tasks and the edit
730
+ modal should open. Tasks With No Rows should read &quot;No Data
731
+ Available&quot;. The form is tabbed so the bottom bar shows the full
732
+ pill — Previous / counter / Next as icon buttons at every width, plus
733
+ Delete and Submit on a solid grey surface.
734
+ </div>
735
+ <DataTemplateWrapper
736
+ title="Site"
737
+ type={viewType}
738
+ id={selectedId}
739
+ collectionId={LIST_OBJECT_COLLECTION}
740
+ schema={ListObjectTableSchema}
741
+ workspace={{
742
+ apiEndpointType: "mongoDB",
743
+ apiEndpoint: LIST_OBJECT_ENDPOINT,
744
+ apiEndpointToken: "",
745
+ apiUserToken: "",
746
+ }}
747
+ edit={{ allEditable: true, disabledUpdate: false, deleteBtn: true }}
748
+ organise={{ breadcrumbType: "button", tabAlign: true }}
749
+ handleNavigate={(type: any, id: any) => {
750
+ setViewType(type);
751
+ setSelectedId(id);
752
+ }}
753
+ limit={10}
754
+ />
755
+ </div>
756
+ );
757
+ };
758
+ DTWListObjectTable.storyName = "DTW List Object Table";
759
+
760
+ // ---------------------------------------------------------------------------
761
+ // DTW Callback Counts — used by e2e/dtw-callbacks.spec.ts
762
+ // ---------------------------------------------------------------------------
763
+
764
+ const CALLBACK_ENDPOINT = "https://callback-counts.blixify.invalid/api/data";
765
+ const CALLBACK_COLLECTION = "callback-records";
766
+
767
+ const CallbackCountsSchema = [
768
+ { id: "name", name: "Name", type: "string" as const },
769
+ { id: "notes", name: "Notes", type: "string" as const, optional: true },
770
+ ];
771
+
772
+ type CallbackCounterKey =
773
+ | "list"
774
+ | "get"
775
+ | "create"
776
+ | "update"
777
+ | "delete"
778
+ | "preComplete"
779
+ | "postComplete"
780
+ | "navigate";
781
+
782
+ const CALLBACK_COUNTER_KEYS: CallbackCounterKey[] = [
783
+ "list",
784
+ "get",
785
+ "create",
786
+ "update",
787
+ "delete",
788
+ "preComplete",
789
+ "postComplete",
790
+ "navigate",
791
+ ];
792
+
793
+ const handleInstallCallbackMock = (
794
+ store: any[],
795
+ onCount: (key: CallbackCounterKey) => void,
796
+ ) => {
797
+ const mock = new MockAdapter(axios, {
798
+ onNoMatch: "passthrough",
799
+ delayResponse: 100,
800
+ });
801
+ const handleParseBody = (config: any) => {
802
+ try {
803
+ return JSON.parse(config?.data ?? "{}");
804
+ } catch (err) {
805
+ return {};
806
+ }
807
+ };
808
+ const renderUrl = (type: string) =>
809
+ `${CALLBACK_ENDPOINT}/${CALLBACK_COLLECTION}/${type}`;
810
+ mock.onPost(renderUrl("list")).reply(() => {
811
+ onCount("list");
812
+ return [
813
+ 200,
814
+ { data: store.map((row) => ({ ...row })), count: store.length },
815
+ ];
816
+ });
817
+ mock
818
+ .onPost(renderUrl("count"))
819
+ .reply(() => [200, { data: store.length, count: store.length }]);
820
+ mock.onPost(renderUrl("get")).reply((config) => {
821
+ onCount("get");
822
+ const body = handleParseBody(config);
823
+ const row = store.find((eachRow) => eachRow._id === body.id);
824
+ return [200, { data: row ? { ...row } : null }];
825
+ });
826
+ mock.onPost(renderUrl("create")).reply((config) => {
827
+ onCount("create");
828
+ store.push({ ...(handleParseBody(config).data ?? {}) });
829
+ return [200, { success: true }];
830
+ });
831
+ mock.onPost(renderUrl("update")).reply((config) => {
832
+ onCount("update");
833
+ const body = handleParseBody(config);
834
+ const index = store.findIndex((eachRow) => eachRow._id === body.id);
835
+ if (index >= 0) store[index] = { ...store[index], ...(body.data ?? {}) };
836
+ return [200, { success: true }];
837
+ });
838
+ mock.onPost(renderUrl("delete")).reply((config) => {
839
+ onCount("delete");
840
+ const body = handleParseBody(config);
841
+ const index = store.findIndex((eachRow) => eachRow._id === body.id);
842
+ if (index >= 0) store.splice(index, 1);
843
+ return [200, { success: true }];
844
+ });
845
+ return mock;
846
+ };
847
+
848
+ /**
849
+ * INFO: a full CRUD page that counts every request and every save callback on
850
+ * screen, so e2e can assert that one user action fires each of them exactly
851
+ * once. A doubled callback reaches consumers as a doubled email or upload,
852
+ * while the page itself looks fine — the counters are the only visible signal.
853
+ */
854
+ export const DTWCallbackCounts = () => {
855
+ // 1. State
856
+ const [mount, setMount] = useState<boolean>(false);
857
+ const [, setTick] = useState<number>(0);
858
+ const [viewType, setViewType] = useState<"list" | "read" | "update">("list");
859
+ const [selectedId, setSelectedId] = useState<string>("");
860
+ const [lastNavigate, setLastNavigate] = useState<string>("");
861
+
862
+ // 2. Refs
863
+ const storeRef = useRef<any[]>([
864
+ { _id: "r1", name: "Record One", notes: "first" },
865
+ { _id: "r2", name: "Record Two", notes: "second" },
866
+ ]);
867
+ const countersRef = useRef<Record<CallbackCounterKey, number>>({
868
+ list: 0,
869
+ get: 0,
870
+ create: 0,
871
+ update: 0,
872
+ delete: 0,
873
+ preComplete: 0,
874
+ postComplete: 0,
875
+ navigate: 0,
876
+ });
877
+ const mockRef = useRef<any>(null);
878
+
879
+ // 4. Effects
880
+ useEffect(() => {
881
+ mockRef.current = handleInstallCallbackMock(storeRef.current, handleCount);
882
+ setMount(true);
883
+ return () => {
884
+ mockRef.current?.restore();
885
+ mockRef.current = null;
886
+ };
887
+ }, []);
888
+
889
+ // 5. Handlers
890
+ function handleCount(key: CallbackCounterKey) {
891
+ countersRef.current[key] += 1;
892
+ setTick((previous) => previous + 1);
893
+ }
894
+
895
+ const handleNavigate = (type: any, id: any) => {
896
+ handleCount("navigate");
897
+ setLastNavigate(`${type}:${id ?? ""}`);
898
+ setViewType(type);
899
+ if (id !== undefined) setSelectedId(id);
900
+ };
901
+
902
+ // 7. Return
903
+ if (!mount) return <Loading />;
904
+ return (
905
+ <div className="w-full">
906
+ <div className="font-semibold mb-1 text-gray-900">
907
+ DTW Callback Counts
908
+ </div>
909
+ <div className="text-sm font-light mb-4 text-gray-500">
910
+ Every request and save callback is counted below. One create, update or
911
+ delete should move each of its counters by exactly one.
912
+ </div>
913
+ <div className="flex flex-wrap gap-4 mb-4 text-xs text-gray-600 font-mono">
914
+ {CALLBACK_COUNTER_KEYS.map((eachKey) => (
915
+ <span key={eachKey}>
916
+ {eachKey}:{" "}
917
+ <span data-testid={`callback-count-${eachKey}`}>
918
+ {countersRef.current[eachKey]}
919
+ </span>
920
+ </span>
921
+ ))}
922
+ <span>
923
+ last navigate:{" "}
924
+ <span data-testid="callback-last-navigate">{lastNavigate}</span>
925
+ </span>
926
+ </div>
927
+ <DataTemplateWrapper
928
+ title="Records"
929
+ type={viewType}
930
+ id={selectedId}
931
+ collectionId={CALLBACK_COLLECTION}
932
+ schema={CallbackCountsSchema}
933
+ workspace={{
934
+ apiEndpointType: "mongoDB",
935
+ apiEndpoint: CALLBACK_ENDPOINT,
936
+ apiEndpointToken: "",
937
+ apiUserToken: "",
938
+ assetEndpoint: "",
939
+ assetUploadEndpoint: "",
940
+ }}
941
+ edit={{ allEditable: true, disabledUpdate: false, deleteBtn: true }}
942
+ organise={{ breadcrumbType: "button" }}
943
+ bareSettings={{
944
+ bareNewDTW: true,
945
+ bareUpdateHandlePreComplete: async () => {
946
+ handleCount("preComplete");
947
+ return true;
948
+ },
949
+ bareUpdateHandlePostComplete: async () => {
950
+ handleCount("postComplete");
951
+ },
952
+ }}
953
+ handleNavigate={handleNavigate}
954
+ />
955
+ </div>
956
+ );
957
+ };
958
+ DTWCallbackCounts.storyName = "DTW Callback Counts";
@@ -3,6 +3,9 @@
3
3
 
4
4
  import React, { useEffect, useRef, useState } from "react";
5
5
  import MockAdapter from "axios-mock-adapter";
6
+ import Papa from "papaparse";
7
+ import { Menu, Transition } from "@headlessui/react";
8
+ import { DataTemplate } from "../dataTemplate";
6
9
  import axios from "axios";
7
10
  import dayjs from "../../dayjs";
8
11
  import { Button } from "../../action/button";
@@ -17,11 +20,11 @@ import {
17
20
  FeedsSettings,
18
21
  KanbanSettings,
19
22
  } from "../dataTemplate/spaceModel";
20
- import { DTWChecklistSchema } from "../testSchema";
21
23
  import {
22
24
  DataWrapperTestSchema,
23
25
  SCROLL_TEST_API,
24
26
  SCROLL_TEST_COLLECTION,
27
+ ServiceChecklistSchema,
25
28
  } from "./dataWrapper.fixtures";
26
29
  import { DataTemplateWrapper } from "./index";
27
30
  import { StarIcon } from "@heroicons/react/24/outline";
@@ -154,14 +157,14 @@ export const DataWrapper = () => {
154
157
  type={viewType}
155
158
  id={selectedId}
156
159
  collectionId={"dtw-test-collection"}
157
- schema={DTWChecklistSchema}
160
+ schema={ServiceChecklistSchema}
158
161
  workspace={{
159
162
  apiEndpointType: "mongoDB",
160
163
  apiEndpoint: `${dtwBaseUrl}/api/mongoData`,
161
164
  apiEndpointToken: "",
162
165
  apiUserToken: "",
163
166
  assetEndpoint:
164
- "https://firebasestorage.googleapis.com/v0/b/verdant-solar-dev.appspot.com/o/",
167
+ "https://firebasestorage.googleapis.com/v0/b/demo-bucket.appspot.com/o/",
165
168
  assetUploadEndpoint: `${dtwBaseUrl}/api/asset`,
166
169
  }}
167
170
  edit={{
@@ -390,7 +393,7 @@ export const DataWrapper = () => {
390
393
  );
391
394
  };
392
395
 
393
- // INFO: Live-data demo for a multi-tab, multi-group schema (KPJ-style).
396
+ // INFO: Live-data demo for a multi-tab, multi-group schema (long grant-application style).
394
397
  // Schema uses tabId to split fields into top-level tabs and groupId to split
395
398
  // fields within a tab into named sections. organise.groupRead = "groupId"
396
399
  // also renders the read view as grouped sections instead of a flat list.
@@ -976,7 +979,7 @@ export const DataWrapperDrawerTest = () => {
976
979
  apiEndpointToken: "",
977
980
  apiUserToken: "",
978
981
  assetEndpoint:
979
- "https://firebasestorage.googleapis.com/v0/b/verdant-dev.firebasestorage.app/o/",
982
+ "https://firebasestorage.googleapis.com/v0/b/demo-bucket.firebasestorage.app/o/",
980
983
  assetUploadEndpoint: `${dtwBaseUrl}/api/asset`,
981
984
  }}
982
985
  edit={{
@@ -1271,3 +1274,74 @@ export const DTWScrollTest = () => {
1271
1274
  );
1272
1275
  };
1273
1276
  DTWScrollTest.storyName = "DTW Scroll Test";
1277
+
1278
+ /**
1279
+ * INFO: the same drawer, hosted by DataTemplate itself rather than by the
1280
+ * wrapper. An app that renders DataTemplate directly — because it owns its own
1281
+ * tabs, roles and devSettings, which the wrapper derives and would replace —
1282
+ * can now opt in with one prop instead of changing component.
1283
+ *
1284
+ * The mechanism is self-recursion: with readDrawer set and type "read", the
1285
+ * outer instance keeps rendering its LIST and nests a second DataTemplate with
1286
+ * the flag stripped inside the panel. One level, then it terminates.
1287
+ */
1288
+ export const DTReadDrawer = () => {
1289
+ const [mount, setMount] = useState<boolean>(false);
1290
+ const [viewType, setViewType] = useState<"list" | "read" | "update">("list");
1291
+ const [selectedId, setSelectedId] = useState<string | undefined>(undefined);
1292
+ const storeRef = useRef<any[]>(handleBuildDrawerDemoRecords());
1293
+ const mockRef = useRef<any>(null);
1294
+
1295
+ useEffect(() => {
1296
+ mockRef.current = handleInstallDrawerDemoMock(storeRef.current);
1297
+ setMount(true);
1298
+ return () => {
1299
+ mockRef.current?.restore();
1300
+ mockRef.current = null;
1301
+ };
1302
+ }, []);
1303
+
1304
+ const handleNavigate = (type: any, id: any) => {
1305
+ setViewType(type);
1306
+ setSelectedId(id);
1307
+ };
1308
+
1309
+ if (!mount) return <Loading />;
1310
+
1311
+ return (
1312
+ <div className="w-full">
1313
+ <div className="font-semibold mb-1 text-gray-900">
1314
+ DataTemplate Read Drawer
1315
+ </div>
1316
+ <div className="text-sm font-light mb-4 text-gray-500">
1317
+ `viewSettings.readDrawer` on DataTemplate directly. Click a row: the
1318
+ record opens in a panel and the table stays mounted behind it, keeping
1319
+ its page index and filters. Nothing else about the props changes — no
1320
+ schema/model rename, no workspace mapping, and the caller keeps its own
1321
+ tabs.
1322
+ </div>
1323
+ <DataTemplate
1324
+ title="Drawer Demo"
1325
+ description=""
1326
+ type={viewType}
1327
+ id={selectedId}
1328
+ collectionId={DRAWER_DEMO_COLLECTION}
1329
+ model={DrawerDemoSchema as any}
1330
+ localURLEndpoint=""
1331
+ viewSettings={{ readDrawer: {} }}
1332
+ devSettings={{
1333
+ server: "mongoDB",
1334
+ dbEndpoint: DRAWER_DEMO_ENDPOINT,
1335
+ dbAPIToken: "",
1336
+ dbUserToken: "",
1337
+ schemaEndpoint: "",
1338
+ imageEndpoint: "",
1339
+ }}
1340
+ lib={{ axios, Menu, Transition, papaParser: Papa }}
1341
+ edit={{ allEditable: true, deleteBtn: false }}
1342
+ bareSettings={{ bareNewDTW: true, bareCompactMode: true }}
1343
+ organise={{ handleNavigate }}
1344
+ />
1345
+ </div>
1346
+ );
1347
+ };
@@ -18,7 +18,7 @@ const sampleEvents = () => [
18
18
  },
19
19
  {
20
20
  id: "2",
21
- title: "Verdant Summarize Updates",
21
+ title: "Weekly Summary Updates",
22
22
  start: dayjs("2024/05/08 13:10", "YYYY/MM/DD HH:mm").toDate(),
23
23
  status: "Pending",
24
24
  },
@@ -42,16 +42,17 @@ import { DetailList } from "blixify-ui-web/components/display/detailList";
42
42
 
43
43
  ##### Optional props
44
44
 
45
- | Name | Type | Description |
46
- | --------------------- | ------------------------------------------------ | -------------------------------------------------------- |
47
- | `addTitleElement` | `ReactElement` | Additional react elemnt to be displayed next to title |
48
- | `description` | `string` | |
49
- | `className` | `tailwind class` | |
50
- | `model` | `Model Data Structure` | Only used in Data Template |
51
- | `hideModal` | `boolean` | Only used in Data Template |
52
- | `referenceModel` | `Model Data Structure` | Only used in Data Template |
53
- | `splitView` | `boolean` | Able to split into two |
54
- | `handleReferenceLink` | `(dataId: string, collectionId: string) => void` | Only used in Data Template, when reference being clicked |
45
+ | Name | Type | Description |
46
+ | --------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------- |
47
+ | `addTitleElement` | `ReactElement` | Additional react elemnt to be displayed next to title |
48
+ | `headerRightElement` | `ReactElement` | Element anchored to the top-right of the header, opposite the title/description |
49
+ | `description` | `string` | |
50
+ | `className` | `tailwind class` | |
51
+ | `model` | `Model Data Structure` | Only used in Data Template |
52
+ | `hideModal` | `boolean` | Only used in Data Template |
53
+ | `referenceModel` | `Model Data Structure` | Only used in Data Template |
54
+ | `splitView` | `boolean` | Able to split into two |
55
+ | `handleReferenceLink` | `(dataId: string, collectionId: string) => void` | Only used in Data Template, when reference being clicked |
55
56
 
56
57
  ##### Detail List class
57
58
 
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import { DetailList as DetailListImpl } from ".";
3
3
  import { Tag } from "../tag";
4
4
  import { StorySection } from "../../storyUtils";
5
+ import { Button } from "../../action/button";
5
6
 
6
7
  const meta = { title: "Display/Detail List" };
7
8
  export default meta;
@@ -49,7 +50,7 @@ For more details visit [the official docs](https://example.com).
49
50
 
50
51
  ## Media
51
52
 
52
- <img src="https://arusoil.com/static/oil_collection_drum-ddf6f26e6fbb1bc058bffb0faa722f51.webp" alt="Oil drum" width="160" style="border-radius:8px">
53
+ <img src="https://placehold.co/160x120/e2e8f0/475569.png?text=Oil+drum" alt="Oil drum" width="160" style="border-radius:8px">
53
54
 
54
55
  <video controls width="320">
55
56
  <source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4">
@@ -93,6 +94,18 @@ export const DetailList = () => (
93
94
  maxHeight="180px"
94
95
  />
95
96
  </StorySection>
97
+ <StorySection
98
+ title="Header Right Element"
99
+ description="With custom element on the top right of header"
100
+ >
101
+ <DetailListImpl
102
+ title="Order #4812"
103
+ description="With custom element on the top right of header"
104
+ list={sampleList}
105
+ size="compact"
106
+ headerRightElement={<Button size="small" type="normal" text="Action" />}
107
+ />
108
+ </StorySection>
96
109
 
97
110
  <StorySection
98
111
  title="splitView (legacy) — Two-Column Grid"
@@ -145,7 +158,7 @@ export const DetailList = () => (
145
158
  title: "Image",
146
159
  text: (
147
160
  <img
148
- src="https://arusoil.com/static/oil_collection_drum-ddf6f26e6fbb1bc058bffb0faa722f51.webp"
161
+ src="https://placehold.co/160x120/e2e8f0/475569.png?text=Oil+drum"
149
162
  alt=""
150
163
  />
151
164
  ),
@@ -187,7 +200,7 @@ export const DetailList = () => (
187
200
  title: "Image",
188
201
  text: (
189
202
  <img
190
- src="https://arusoil.com/static/oil_collection_drum-ddf6f26e6fbb1bc058bffb0faa722f51.webp"
203
+ src="https://placehold.co/160x120/e2e8f0/475569.png?text=Oil+drum"
191
204
  alt=""
192
205
  />
193
206
  ),
@@ -31,12 +31,12 @@ const feeds: Feed[] = [
31
31
  {
32
32
  iconClass: "bg-blue-500",
33
33
  iconType: <HandThumbUpIcon className="h-5 w-5 text-white" />,
34
- content: "Notify Arus Oil logistic team once you are ready to sell",
34
+ content: "Notify the logistics team once you are ready to sell",
35
35
  step: "Step 2",
36
36
  addContent: (
37
37
  <img
38
38
  className="mt-4 w-36 sm:w-56 h-auto"
39
- src="https://arusoil.com/static/oil_collection_drum-ddf6f26e6fbb1bc058bffb0faa722f51.webp"
39
+ src="https://placehold.co/160x120/e2e8f0/475569.png?text=Oil+drum"
40
40
  alt=""
41
41
  />
42
42
  ),
@@ -44,7 +44,7 @@ const feeds: Feed[] = [
44
44
  {
45
45
  iconClass: "bg-green-500",
46
46
  iconType: <CheckIcon className="h-5 w-5 text-white" />,
47
- content: "View payment made to you on Arus Oil App",
47
+ content: "View payments made to you in the app",
48
48
  step: "Step 3",
49
49
  },
50
50
  ];
@@ -38,12 +38,12 @@ export const Feeds = () => {
38
38
  id: "2",
39
39
  iconClass: "bg-blue-500",
40
40
  iconType: <HandThumbUpIcon className="h-5 w-5 text-white" />,
41
- content: "Notify Arus Oil logistic team once you are ready to sell",
41
+ content: "Notify the logistics team once you are ready to sell",
42
42
  step: "2025-12-11",
43
43
  addContent: (
44
44
  <img
45
45
  className="mt-4 w-36 sm:w-56 h-auto"
46
- src="https://arusoil.com/static/oil_collection_drum-ddf6f26e6fbb1bc058bffb0faa722f51.webp"
46
+ src="https://placehold.co/160x120/e2e8f0/475569.png?text=Oil+drum"
47
47
  alt=""
48
48
  />
49
49
  ),
@@ -52,7 +52,7 @@ export const Feeds = () => {
52
52
  id: "3",
53
53
  iconClass: "bg-green-500",
54
54
  iconType: <CheckIcon className="h-5 w-5 text-white" />,
55
- content: "View payment made to you on Arus Oil App",
55
+ content: "View payments made to you in the app",
56
56
  step: "2025-12-15",
57
57
  },
58
58
  ];