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,111 @@
1
+ import React from "react";
2
+ import { FilePreview as FilePreviewImpl } from ".";
3
+ import { StorySection } from "../../storyUtils";
4
+
5
+ const meta = { title: "Display/File Preview" };
6
+ export default meta;
7
+
8
+ // INFO: a real firebase download url — percent-encoded path, timestamp-prefixed
9
+ // filename, token in the query. The label and the type are both read off this
10
+ // shape, so the story uses it rather than a tidy fake that would hide the work.
11
+ const FIREBASE_PDF =
12
+ "https://firebasestorage.googleapis.com/v0/b/demo-bucket.appspot.com/o/notes%2Fn1%2Fcontent%2F1757238400000_Annual%20Report.pdf?alt=media&token=abc123";
13
+
14
+ const IMAGE =
15
+ "https://images.pexels.com/photos/2338407/pexels-photo-2338407.jpeg?auto=compress&cs=tinysrgb&w=600";
16
+
17
+ const VIDEO =
18
+ "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4";
19
+
20
+ export const FilePreview = () => {
21
+ return (
22
+ <div className="font-sans p-6">
23
+ <StorySection
24
+ title="Card — one row per file"
25
+ description="The default. An icon chosen by type, the name with its upload timestamp stripped, and a human-readable size. This is what an attachment in a markdown document renders as."
26
+ >
27
+ <div className="flex w-full max-w-md flex-col gap-y-2">
28
+ <FilePreviewImpl
29
+ file={{ fileName: FIREBASE_PDF, fileSize: 2097152 }}
30
+ />
31
+ <FilePreviewImpl
32
+ file={{ fileName: IMAGE, label: "site-photo.jpg", fileSize: 348160 }}
33
+ />
34
+ <FilePreviewImpl
35
+ file={{ fileName: "s/ledger.xlsx", fileSize: 51200 }}
36
+ />
37
+ <FilePreviewImpl file={{ fileName: "s/archive.zip" }} />
38
+ </div>
39
+ </StorySection>
40
+
41
+ <StorySection
42
+ title="Tile — a grid of files"
43
+ description="Media gets a real thumbnail; everything else falls back to its icon. Pair with viewType 'grid' and a $unwind pipeline to paginate over files rather than records — the server counts through the pipeline, so the total is the file count with nothing extra to configure."
44
+ >
45
+ <div className="grid w-full grid-cols-2 gap-4 sm:grid-cols-4">
46
+ <FilePreviewImpl
47
+ variant="tile"
48
+ file={{ fileName: IMAGE, label: "site-photo.jpg", fileSize: 348160 }}
49
+ />
50
+ <FilePreviewImpl
51
+ variant="tile"
52
+ file={{ fileName: VIDEO, label: "walkthrough.mp4" }}
53
+ />
54
+ <FilePreviewImpl
55
+ variant="tile"
56
+ file={{ fileName: FIREBASE_PDF, fileSize: 2097152 }}
57
+ />
58
+ <FilePreviewImpl
59
+ variant="tile"
60
+ file={{ fileName: "s/ledger.xlsx", fileSize: 51200 }}
61
+ />
62
+ </div>
63
+ </StorySection>
64
+
65
+ <StorySection
66
+ title="Type resolution"
67
+ description="mimeType wins when the consumer stored one. Most files were saved as only a name and a token, so the extension is the fallback — not a shortcut, the only thing available. The query string is cut first, or every firebase url would look like a '.pdf?alt=media' file."
68
+ >
69
+ <div className="flex w-full max-w-md flex-col gap-y-2">
70
+ <FilePreviewImpl
71
+ file={{ fileName: "s/mystery.bin", mimeType: "image/png", label: "Stored type says image" }}
72
+ />
73
+ <FilePreviewImpl
74
+ file={{ fileName: "s/clip.MOV", label: "Extension, upper case" }}
75
+ />
76
+ <FilePreviewImpl
77
+ file={{ fileName: "s/no-extension", label: "Nothing to go on" }}
78
+ />
79
+ </div>
80
+ </StorySection>
81
+
82
+ <StorySection
83
+ title="Pending upload"
84
+ description="A blob URL belongs to the session that made it, so a stored one is a file whose upload never finished. It is named but deliberately NOT a link — following it would 404, and an anchor with no href is unreachable by keyboard while still looking clickable."
85
+ >
86
+ <div className="flex w-full max-w-md flex-col gap-y-2">
87
+ <FilePreviewImpl
88
+ file={{ fileName: "blob:http://localhost/abc", label: "draft.pdf" }}
89
+ />
90
+ <FilePreviewImpl
91
+ variant="tile"
92
+ file={{ fileName: "blob:http://localhost/abc", label: "draft.png" }}
93
+ className="max-w-[12rem]"
94
+ />
95
+ </div>
96
+ </StorySection>
97
+
98
+ <StorySection
99
+ title="onClick instead of a link"
100
+ description="A file view navigates to the record a file belongs to rather than downloading it — the record carries the status, the owner and everything else you came looking for. Passing onClick renders a button and leaves the destination to the consumer."
101
+ >
102
+ <div className="w-full max-w-md">
103
+ <FilePreviewImpl
104
+ file={{ fileName: FIREBASE_PDF, fileSize: 2097152 }}
105
+ onClick={() => window.alert("Navigate to the parent record")}
106
+ />
107
+ </div>
108
+ </StorySection>
109
+ </div>
110
+ );
111
+ };
@@ -41,3 +41,6 @@ import { Overlay } from "blixify-ui-web/components/display/overlay";
41
41
  | `renderContent` | `function` | `() => {}` | |
42
42
  | `onClose` | `function` | `() => {}` | Called upon modal overlay is clicked |
43
43
  | `overflowScroll` | `boolean` | `false` | If `true`, modal height is limited to 75vh on mobile, title stays sticky, and content is scrollable |
44
+ | `bgColor` | `string` | `bg-white` | Tailwind background class for the panel in light mode |
45
+ | `darkMode` | `boolean` | `false` | Switches the panel, text and icons to the dark palette |
46
+ | `darkModeBg` | `string` | `bg-gray-900` | Tailwind background class for the panel when `darkMode` is on |
@@ -49,7 +49,7 @@ export const Modal = () => {
49
49
  workspace={{
50
50
  apiEndpoint: "http://localhost:3000/api/data",
51
51
  assetEndpoint:
52
- "https://firebasestorage.googleapis.com/v0/b/esh-web-dev.appspot.com/o/",
52
+ "https://firebasestorage.googleapis.com/v0/b/demo-bucket.appspot.com/o/",
53
53
  }}
54
54
  additionalQuery={[]}
55
55
  organise={{
@@ -72,3 +72,52 @@ export const Modal = () => {
72
72
  />
73
73
  );
74
74
  };
75
+
76
+ // INFO: the light panel was hardcoded bg-white — darkModeBg only ever applied
77
+ // when darkMode was on, so there was no way to tint a light modal.
78
+
79
+ export const ModalBgColor = () => {
80
+ const [open, setOpen] = useState<string | null>("grey");
81
+
82
+ const renderSwatch = (id: string, label: string) => (
83
+ <button
84
+ type="button"
85
+ className="rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm hover:bg-gray-50"
86
+ onClick={() => setOpen(id)}
87
+ >
88
+ {label}
89
+ </button>
90
+ );
91
+
92
+ const renderModal = (id: string, label: string, bgColor?: string) => (
93
+ <ModalImpl
94
+ open={open === id}
95
+ title={label}
96
+ bgColor={bgColor}
97
+ renderContent={() => (
98
+ <div className="py-4 text-sm text-gray-700">
99
+ Panel background: <code>{bgColor ?? "bg-white (default)"}</code>
100
+ </div>
101
+ )}
102
+ onClose={() => setOpen(null)}
103
+ />
104
+ );
105
+
106
+ return (
107
+ <div className="w-full">
108
+ <div className="mb-3 text-sm text-gray-500">
109
+ bgColor takes any Tailwind background class and applies to the light
110
+ panel. darkModeBg still owns the panel when darkMode is on.
111
+ </div>
112
+ <div className="flex flex-wrap gap-2">
113
+ {renderSwatch("default", "Default (bg-white)")}
114
+ {renderSwatch("grey", "bg-gray-100")}
115
+ {renderSwatch("tint", "bg-primary-50")}
116
+ </div>
117
+ {renderModal("default", "Default")}
118
+ {renderModal("grey", "Grey Panel", "bg-gray-100")}
119
+ {renderModal("tint", "Tinted Panel", "bg-primary-50")}
120
+ </div>
121
+ );
122
+ };
123
+ ModalBgColor.storyName = "bgColor";
@@ -2,12 +2,6 @@
2
2
 
3
3
  Calendar surface with six variants. Mobile default should be **weekStrip + dayTimeline** (not month grid).
4
4
 
5
- ## Design reference
6
-
7
- PR #1666 — `uiRefresh.stories.tsx`, sections **Calendar — Variant 1 through Variant 5** (~line 979 `MiniMonthCalendar`, ~line 1091 `MobileWeekStrip`, ~line 1136 `DayTimeline`, etc.).
8
-
9
- Note: an earlier draft of `NewCalendar` lived at `src/components/display/newCalendar/index.tsx` (1,243 LOC) but was deleted by Sean's commit `bbd0117c` ("fix removed new stories and implemented redesign directly to components"). The full source is recoverable via `git show <sha>:src/components/display/newCalendar/index.tsx` and should be the starting point for this implementation — don't rewrite from scratch.
10
-
11
5
  ## Variants
12
6
 
13
7
  | Variant | Pattern | Best for |
@@ -17,7 +17,7 @@ const sampleEvents: NewCalendarEvent[] = [
17
17
  },
18
18
  {
19
19
  id: "2",
20
- title: "Verdant Summarize Updates",
20
+ title: "Weekly Summary Updates",
21
21
  start: dayjs("2024/05/08 13:10", "YYYY/MM/DD HH:mm").toDate(),
22
22
  status: "Pending",
23
23
  },
@@ -2,11 +2,7 @@
2
2
 
3
3
  Social-style activity feed. Composes `NewListRow` so the row geometry matches `NewList` — same avatar size, same trailing-time position, same border treatment. The Feed-specific bits (quoted content, inline media, reactions row) attach via `renderExtra`.
4
4
 
5
- ## Design reference
6
-
7
- PR #1666 — `uiRefresh.stories.tsx`, section **"Feeds View — Modern Mobile (Social-style Activity)"** (~line 1676, `ModernFeedsView`).
8
-
9
- Patterns to honour:
5
+ ## Design patterns
10
6
 
11
7
  - Avatar + actor name + relative time on the row header
12
8
  - Quoted comment blocks with left-border accent
@@ -33,12 +33,12 @@ const feeds: NewFeedItem[] = [
33
33
  id: "2",
34
34
  iconClass: "bg-blue-500",
35
35
  iconType: <HandThumbUpIcon className="h-5 w-5 text-white" />,
36
- content: "Notify Arus Oil logistic team once you are ready to sell",
36
+ content: "Notify the logistics team once you are ready to sell",
37
37
  step: "2025-12-11",
38
38
  addContent: (
39
39
  <img
40
40
  className="mt-4 h-auto w-36 sm:w-56"
41
- src="https://arusoil.com/static/oil_collection_drum-ddf6f26e6fbb1bc058bffb0faa722f51.webp"
41
+ src="https://placehold.co/160x120/e2e8f0/475569.png?text=Oil+drum"
42
42
  alt=""
43
43
  />
44
44
  ),
@@ -47,7 +47,7 @@ const feeds: NewFeedItem[] = [
47
47
  id: "3",
48
48
  iconClass: "bg-green-500",
49
49
  iconType: <CheckIcon className="h-5 w-5 text-white" />,
50
- content: "View payment made to you on Arus Oil App",
50
+ content: "View payments made to you in the app",
51
51
  step: "2025-12-15",
52
52
  },
53
53
  ];
@@ -2,13 +2,6 @@
2
2
 
3
3
  Sectioned card board. Desktop: horizontal columns. Mobile: tab switcher between columns + swipe-card-to-move-column + FAB for primary CTA.
4
4
 
5
- ## Design reference
6
-
7
- PR #1666 — `uiRefresh.stories.tsx`:
8
-
9
- - **"Kanban — Desktop Column View"** (~line 650, `DesktopKanban`)
10
- - **"Kanban — Mobile Tab View"** (~line 675, `MobileKanban`) — already includes the tab switcher, swipe hint, and FAB pattern. Lift those into the real component.
11
-
12
5
  ## Mobile interactions
13
6
 
14
7
  - Tap a column tab to switch (no horizontal scroll)
@@ -6,11 +6,8 @@ Mobile-native list foundation. Other components (`NewFeeds`, `NewTable` mobile b
6
6
 
7
7
  `Calendar` / `Table` / `Kanban` / `List` / `Feeds` already exist as legacy components. The `New*` prefix lets us ship the modernised foundation without breaking consumers. Migration plan TBD (likely v2.0.0 deprecation).
8
8
 
9
- ## Design reference
9
+ ## Design patterns
10
10
 
11
- PR #1666 — `uiRefresh.stories.tsx`, section **"List View — Modern Mobile (Sticky Section Headers)"** (~line 1561, `ModernListView`).
12
-
13
- Key patterns to honour:
14
11
  - Sticky section headers (TODAY / YESTERDAY / THIS WEEK) — stay pinned while scrolling
15
12
  - Avatar + 2-line preview row (`NewListRow`)
16
13
  - Unread state — bold sender + small blue dot
@@ -2,15 +2,6 @@
2
2
 
3
3
  Tabular display with a List-shaped mobile breakpoint. The architectural key: **`mobileView=true` delegates row rendering to `NewListRow`**, so the same tap-rhythm and avatar/title/trailing geometry shows up across NewList, NewFeeds, and NewTable mobile.
4
4
 
5
- ## Design reference
6
-
7
- PR #1666 — `uiRefresh.stories.tsx`:
8
-
9
- - **"Table — Desktop (clean borderless)"** (~line 491, `DesktopTable`)
10
- - **"Table — Mobile Card View"** (~line 552, `MobileCardTable`)
11
-
12
- Note: the Mobile Card View mock in #1666 invented a bespoke avatar/name/status layout. That pattern is being replaced — the mobile rendering should use `NewListRow` (composition), not duplicate it.
13
-
14
5
  ## Mobile column flags
15
6
 
16
7
  Columns expose three optional booleans that map them to the `NewListItem` shape:
@@ -23,6 +14,35 @@ Columns expose three optional booleans that map them to the `NewListItem` shape:
23
14
 
24
15
  Columns flagged `hideOnMobile` are dropped from the mobile rendering entirely.
25
16
 
17
+ ## Mobile tooltip (`mobileTooltip`)
18
+
19
+ With `mobileView`, `mobileTooltip` adds row data to the collapsed row and to a hover card. Through `DataTemplate` it is `bareSettings.bareMobileTooltip`, which applies when `bareNewDTW` and `bareListMobileView` are both on.
20
+
21
+ | Key | Type | Shows |
22
+ | --------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
23
+ | `titleAdjacent.tagKey` | `string` | That row field's value as a Tag next to the row title |
24
+ | `titleAdjacent.tagColor` | `Record<string, TagColor>` | The Tag colour for each value |
25
+ | `titleAdjacent.trailingKey` | `string` | That row field's value as trailing text, e.g. a price |
26
+ | `titleAdjacent.summaryKeys` | `string[]` | A second line under the title while the row is collapsed, as label + value pairs. Labels come from the column titles; a dotted key (`shippingAddress.name`) falls back to its parent column's title. Empty values are skipped. Tapping the line expands the row |
27
+ | `tooltip.imageKey` | `string` | The image URL field shown in the hover card |
28
+ | `tooltip.summaryKeys` | `string[]` | Label + value pairs in the hover card |
29
+ | `tooltip.tagKeys` | `string[]` | Tag pills in the hover card |
30
+ | `tooltip.tagColors` | `Record<string, TagColor>` | The Tag colour for each value |
31
+
32
+ ```tsx
33
+ <NewTable
34
+ mobileView
35
+ mobileTooltip={{
36
+ titleAdjacent: {
37
+ tagKey: "status",
38
+ tagColor: { Delivered: "green", Pending: "yellow" },
39
+ summaryKeys: ["shippingAddress.name"],
40
+ },
41
+ }}
42
+ {...tableProps}
43
+ />
44
+ ```
45
+
26
46
  ## Column width
27
47
 
28
48
  Drag the grip on a header's right border to resize that column. The line is
@@ -133,7 +133,7 @@ const fullColumns: NewTableColumn[] = [
133
133
  ];
134
134
 
135
135
  // INFO: a compact "config matrix" — the pattern used for KNX group addresses in
136
- // energeek-gateway-config. Cells are React elements (kit inputs in cellInput
136
+ // a gateway-config screen. Cells are React elements (kit inputs in cellInput
137
137
  // mode, icon buttons), typed columns power the inline add row, and the parent
138
138
  // owns the state.
139
139
  interface MatrixRow {
@@ -187,11 +187,11 @@ const EditableWithAddColumn = () => {
187
187
  advSettings={{
188
188
  addColumnEnabled: true,
189
189
  renderReferenceSpaceOptions: () => [
190
- { key: "kpj-projects", label: "Projects" },
191
- { key: "kpj-users", label: "Users" },
190
+ { key: "projects", label: "Projects" },
191
+ { key: "users", label: "Users" },
192
192
  ],
193
193
  renderReferenceLabelOptions: (spaceId?: string) =>
194
- spaceId === "kpj-users"
194
+ spaceId === "users"
195
195
  ? [{ key: "email", label: "Email" }]
196
196
  : [{ key: "name", label: "Name" }],
197
197
  onAddColumn: (column: any) => {
@@ -372,6 +372,85 @@ const ElementCellsMatrix = () => {
372
372
  );
373
373
  };
374
374
 
375
+ // INFO: an editable table with disabled schema columns beside editable ones —
376
+ // hovering Value or Notes opens an editor, while Unit, Source and the coloured
377
+ // Status column stay read-only, matching how the form treats disabled fields
378
+ const lockedColumns: NewTableColumn[] = [
379
+ {
380
+ key: "metric",
381
+ title: "Metric",
382
+ type: "list",
383
+ listSelection: ["Steps", "Heart Rate", "Weight"],
384
+ listSelectionColour: ["blue", "red", "yellow"],
385
+ },
386
+ { key: "value", title: "Value", type: "number" },
387
+ { key: "unit", title: "Unit (disabled)", type: "string", disabled: true },
388
+ { key: "notes", title: "Notes", type: "string" },
389
+ {
390
+ key: "source",
391
+ title: "Source (disabled)",
392
+ type: "string",
393
+ disabled: true,
394
+ },
395
+ {
396
+ key: "status",
397
+ title: "Status (disabled)",
398
+ type: "list",
399
+ listSelection: ["Synced", "Pending"],
400
+ listSelectionColour: ["green", "yellow"],
401
+ disabled: true,
402
+ },
403
+ ];
404
+
405
+ const lockedRows: NewTableRow[] = [
406
+ {
407
+ id: "1",
408
+ metric: "Heart Rate",
409
+ value: 72,
410
+ unit: "bpm",
411
+ notes: "After walk",
412
+ source: "Manual entry",
413
+ status: "Synced",
414
+ },
415
+ {
416
+ id: "2",
417
+ metric: "Weight",
418
+ value: 68.5,
419
+ unit: "kg",
420
+ notes: "Morning",
421
+ source: "Device sync",
422
+ status: "Pending",
423
+ },
424
+ ];
425
+
426
+ const EditableWithDisabledColumns = () => {
427
+ const [rows, setRows] = useState<NewTableRow[]>(lockedRows);
428
+
429
+ return (
430
+ <NewTableImpl
431
+ columns={lockedColumns}
432
+ data={rows}
433
+ paginationType="page"
434
+ size={rows.length}
435
+ limit={10}
436
+ pageIndex={0}
437
+ handleUpdatePageIndex={() => {}}
438
+ editable={{
439
+ active: true,
440
+ onUpdateData: async (rowId, columnId, value) => {
441
+ setRows((previous) =>
442
+ previous.map((eachRow) =>
443
+ eachRow.id === rowId
444
+ ? { ...eachRow, [columnId]: value }
445
+ : eachRow,
446
+ ),
447
+ );
448
+ },
449
+ }}
450
+ />
451
+ );
452
+ };
453
+
375
454
  export const NewTable = () => {
376
455
  const [selectedIds, setSelectedIds] = useState<string[]>([]);
377
456
 
@@ -452,7 +531,6 @@ export const NewTable = () => {
452
531
  Processing: "blue",
453
532
  Pending: "yellow",
454
533
  },
455
- trailingKey: "totalAmount",
456
534
  },
457
535
  tooltip: {
458
536
  imageKey: "shippingAddress.name",
@@ -468,6 +546,32 @@ export const NewTable = () => {
468
546
  />
469
547
  </div>
470
548
  </StorySection>
549
+ <StorySection
550
+ title="Mobile view with Summary"
551
+ description="titleAdjacent.tagKey shows a status tag next to the title; summaryKeys renders extra columns as label + value pairs on a second line below (visible while collapsed). Clicking the summary expands the row."
552
+ >
553
+ <div className="w-full max-w-96">
554
+ <NewTableImpl
555
+ columns={fullColumns}
556
+ data={data}
557
+ mobileView
558
+ checkbox
559
+ selectedIds={selectedIds}
560
+ handleSelectedIds={setSelectedIds}
561
+ mobileTooltip={{
562
+ titleAdjacent: {
563
+ tagKey: "status",
564
+ summaryKeys: ["shippingAddress.name"],
565
+ tagColor: {
566
+ Delivered: "green",
567
+ Processing: "blue",
568
+ Pending: "yellow",
569
+ },
570
+ },
571
+ }}
572
+ />
573
+ </div>
574
+ </StorySection>
471
575
 
472
576
  <StorySection
473
577
  title="Empty state"
@@ -487,6 +591,13 @@ export const NewTable = () => {
487
591
  <EditableWithAddColumn />
488
592
  </StorySection>
489
593
 
594
+ <StorySection
595
+ title="Editable with disabled columns"
596
+ description="A column marked disabled in the schema stays read-only in an editable table, the same as in the form. Hover Value or Notes to edit; Unit, Source and the coloured Status column never open an editor, and a row click still reaches the row."
597
+ >
598
+ <EditableWithDisabledColumns />
599
+ </StorySection>
600
+
490
601
  <StorySection
491
602
  title="Element cells with row actions (config matrix)"
492
603
  description="Cells can be React elements: kit inputs with cellInput={{ active: true }} (the same compact mode the DTW inline edit matrix uses) and IconButtons per row, while the parent owns the row state. Typed columns are what make the inline 'Create New Item' row render inputs — element-only columns render nothing there. Do NOT combine with editable.active: edit mode wraps element cells in its own editors (from 1.2.66 a React element cell passes through, but its inputs are ~286px wide). Wrap in overflow-x-auto when the columns outgrow the pane."
@@ -509,8 +620,8 @@ export const NewTable = () => {
509
620
 
510
621
  export const FlatTable = () => {
511
622
  const rows = [
512
- { _id: "1", name: "Acme", stage: "Won" },
513
- { _id: "2", name: "Globex", stage: "Lost" },
623
+ { id: "1", name: "Acme", stage: "Won" },
624
+ { id: "2", name: "Globex", stage: "Lost" },
514
625
  ];
515
626
  return (
516
627
  <StorySection
@@ -526,7 +637,7 @@ export const FlatTable = () => {
526
637
  limit={10}
527
638
  cursor=""
528
639
  loading={false}
529
- paginationType="hidden"
640
+ paginationType="page"
530
641
  advSettings={{ flat: true }}
531
642
  handleUpdatePageIndex={() => {}}
532
643
  />
@@ -541,7 +652,7 @@ export const FlatTable = () => {
541
652
  cursor=""
542
653
  loading={false}
543
654
  mobileView
544
- paginationType="hidden"
655
+ paginationType="page"
545
656
  advSettings={{ flat: true }}
546
657
  handleUpdatePageIndex={() => {}}
547
658
  />
@@ -68,7 +68,7 @@ return (
68
68
  | `disabled` | `boolean` | |
69
69
  | `rounded` | `none / md /lg` | Border with |
70
70
  | `variant` | `flat / outline /flatOutline` | |
71
- | `color` | `red / blue / green / yellow / indigo / dark` | Background color & text color |
71
+ | `color` | `TagColor \| string` — `primary / red / blue / green / yellow / indigo / purple / lime / sky / pink / orange / dark` | Background color & text color. `TagColor` is exported from this component |
72
72
  | `customFontSize` | `string` | Tailwind font class name |
73
73
  | `customPadding` | `string` | Tailwind padding class name |
74
74
  | `onClick` | `(value: string) => void` | Value is tagText |
@@ -67,6 +67,13 @@ export const Tag = () => {
67
67
  <TagImpl tagText="React" color="dark" variant="flatOutline" />
68
68
  <TagImpl tagText="Default" color="default" variant="flatOutline" />
69
69
  </div>
70
+ <h1 className="text-sm font-medium">Custom Hex</h1>
71
+ <div className="flex flex-wrap gap-4">
72
+ <TagImpl tagText="Joel Michael" color="#2563eb" />
73
+ <TagImpl tagText="cychanyi" color="#0d9488" />
74
+ <TagImpl tagText="Backlogs" color="#a16207" />
75
+ <TagImpl tagText="Small" size="small" color="#db2777" />
76
+ </div>
70
77
  </div>
71
78
  </div>
72
79
  );
@@ -18,7 +18,7 @@ export const MarkdownInput = () => {
18
18
  <>
19
19
  <StorySection
20
20
  title="Default (boxed form field)"
21
- description="Renders as a form field — bordered container, static toolbar, and a fixed-height scroll box. Press / to see the available commands, esc to close the menu. Images show blob URL previews in Storybook."
21
+ description="Renders as a form field — bordered container, static toolbar, and a fixed-height scroll box. Press / to see the available commands, esc to close the menu. Image, Video and File each appear only when the host enables them. Storybook wires no upload handler, so a file stays on Uploading — in an app the host flushes it on save."
22
22
  >
23
23
  <div className="w-full">
24
24
  <MarkdownInputImpl
@@ -31,6 +31,7 @@ export const MarkdownInput = () => {
31
31
  darkMode={false}
32
32
  enableImageUpload={true}
33
33
  enableVideoUpload={true}
34
+ enableFileUpload={true}
34
35
  />
35
36
  </div>
36
37
  </StorySection>
@@ -49,6 +50,7 @@ export const MarkdownInput = () => {
49
50
  darkMode={false}
50
51
  enableImageUpload={true}
51
52
  enableVideoUpload={true}
53
+ enableFileUpload={true}
52
54
  onRequestRemove={(spaceId, remove) => {
53
55
  // INFO: stands in for the host's confirmation modal
54
56
  if (window.confirm(`Remove ${spaceId} from this page?`)) remove();
@@ -34,6 +34,10 @@ export const UploadInput = () => {
34
34
  const [multiPdfs, setMultiPdfs] = useState<any>({ ...emptyFiles });
35
35
  const [noDupFiles, setNoDupFiles] = useState<any>({ ...emptyFiles });
36
36
  const [compactFiles, setCompactFiles] = useState<any>({ ...preloadedImages });
37
+ const [chooserFiles, setChooserFiles] = useState<any>({ ...emptyFiles });
38
+ const [chooserMobileFiles, setChooserMobileFiles] = useState<any>({
39
+ ...emptyFiles,
40
+ });
37
41
  const [tagFiles, setTagFiles] = useState<any>({
38
42
  data: [
39
43
  {
@@ -104,6 +108,43 @@ export const UploadInput = () => {
104
108
 
105
109
  return (
106
110
  <>
111
+ <StorySection
112
+ title="Camera / Gallery Chooser"
113
+ description="showCameraGalleryOptionModal anchors a Camera / Gallery panel to the upload trigger. Android needs it: a WebView only reaches the camera when the input carries `capture`, so without a chooser an image field drops straight into the file picker with no camera option. iOS presents its own sheet, so consumers should gate this on platform. Camera uses capture='environment'; both honour the field's accept."
114
+ >
115
+ <div className="w-full max-w-sm">
116
+ <UploadInputImpl
117
+ lib={{ axios }}
118
+ id="chooserImages"
119
+ label="Roof Aerial / Overview Image"
120
+ type="multi"
121
+ showModal
122
+ accept="image/*"
123
+ files={chooserFiles}
124
+ onChange={(value: any) => setChooserFiles(value)}
125
+ />
126
+ </div>
127
+ </StorySection>
128
+
129
+ <StorySection
130
+ title="Camera / Gallery Chooser — Mobile Row"
131
+ description="The same chooser in the mobileView row layout, which is what a phone renders below 768px. The panel is anchored to its trigger, so it travels with the field as the form scrolls."
132
+ >
133
+ <div className="w-full max-w-sm">
134
+ <UploadInputImpl
135
+ lib={{ axios }}
136
+ id="chooserMobileImages"
137
+ label="Ridgecapping Location Image"
138
+ type="multi"
139
+ showModal
140
+ mobileView
141
+ accept="image/*,video/*"
142
+ files={chooserMobileFiles}
143
+ onChange={(value: any) => setChooserMobileFiles(value)}
144
+ />
145
+ </div>
146
+ </StorySection>
147
+
107
148
  <StorySection
108
149
  title="Single Image Upload"
109
150
  description="Default single-file mode. Accepts PNG, JPG, JPEG, HEIC, HEIF. HEIC/HEIF files are auto-converted to JPEG. compress reduces file size before upload."