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,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/
|
|
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 |
|
|
@@ -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
|
|
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
|
|
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://
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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: "
|
|
191
|
-
{ key: "
|
|
190
|
+
{ key: "projects", label: "Projects" },
|
|
191
|
+
{ key: "users", label: "Users" },
|
|
192
192
|
],
|
|
193
193
|
renderReferenceLabelOptions: (spaceId?: string) =>
|
|
194
|
-
spaceId === "
|
|
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
|
-
{
|
|
513
|
-
{
|
|
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="
|
|
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="
|
|
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.
|
|
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."
|