blixify-ui-web-mcp 0.1.38 → 0.1.40
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/data/dataTemplate/README.md +42 -3
- package/lib/components/data/dataWrapper/README.md +38 -0
- package/lib/components/data/dataWrapper/dataWrapper.edit.stories.tsx +41 -1
- package/lib/components/data/dataWrapper/dataWrapper.view.stories.tsx +207 -2
- package/lib/components/display/newKanban/README.md +6 -0
- package/lib/components/input/markdown/README.md +4 -1
- package/package.json +1 -1
|
@@ -47,9 +47,9 @@ Three schema-level settings shape how a record is laid out. They nest in one fix
|
|
|
47
47
|
```
|
|
48
48
|
Tab (DataStructure.tabId) ← tab bar across the top of the read + update views
|
|
49
49
|
└─ Section (DataStructure.groupId) ← titled card inside the tab; collapsible in the form
|
|
50
|
-
└─ fields ← an object
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
└─ fields ← an object, or a listObject with
|
|
51
|
+
listObjectApplyGroup: true, renders inside
|
|
52
|
+
its section as a block or table
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
- Columns with a `tabId` but no `groupId` sit directly under the tab; columns with
|
|
@@ -60,6 +60,45 @@ Tab (DataStructure.tabId) ← tab bar across the top of the read
|
|
|
60
60
|
- `organise.groupBy` is different again: it splits the **list** into one table per value
|
|
61
61
|
of a column ("Group By X"), and has nothing to do with `groupId`.
|
|
62
62
|
|
|
63
|
+
**List-object section grouping** is opt-in on each schema field:
|
|
64
|
+
|
|
65
|
+
| Attribute | Type | Behavior |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `listObjectApplyGroup` | `boolean` (optional) | `true` includes the list object in its `groupId` section in read and update/create views. False or omitted keeps it outside named groups. |
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
{
|
|
71
|
+
id: "items",
|
|
72
|
+
name: "Items",
|
|
73
|
+
type: "listObject",
|
|
74
|
+
tabId: "Details",
|
|
75
|
+
groupId: "Shipping",
|
|
76
|
+
listObjectApplyGroup: true,
|
|
77
|
+
listObjectTableView: true,
|
|
78
|
+
objectData: [
|
|
79
|
+
{ id: "name", name: "Name", type: "string" },
|
|
80
|
+
{ id: "quantity", name: "Quantity", type: "number" },
|
|
81
|
+
],
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Grouped list objects share the read section's card with ordinary fields. A
|
|
86
|
+
section containing only a list object still shows its group heading. In update
|
|
87
|
+
forms, list objects span all 12 grid columns regardless of list/table mode or
|
|
88
|
+
`rowColumns`; their existing add/edit modal and save behavior are retained.
|
|
89
|
+
|
|
90
|
+
In grouped read views, list objects with `listObjectApplyGroup` false or omitted
|
|
91
|
+
render once after **all tab sections**, even if they have a `tabId` and `groupId`.
|
|
92
|
+
In update forms, regular ungrouped lists render after the form; `listObjOrder`
|
|
93
|
+
and `listFlexTable` lists remain in the form's default area outside named groups.
|
|
94
|
+
To keep an existing list object inside a named section, explicitly set
|
|
95
|
+
`listObjectApplyGroup: true`. `parseSpaceToDataStructure` preserves the attribute
|
|
96
|
+
when deriving a model from a Space schema.
|
|
97
|
+
|
|
98
|
+
The **DTW Group Test** story in `dataWrapper.view.stories.tsx` includes a grouped
|
|
99
|
+
Operation Crew table, a grouped Sprayed Plots list, and an ungrouped Operation
|
|
100
|
+
Equipment table for comparison.
|
|
101
|
+
|
|
63
102
|
**How a tabbed form is dressed** (`tabAlign: "left"`):
|
|
64
103
|
|
|
65
104
|
- The rail is a numbered `Stepper` (`variant="checklist"`) — the active step is a filled primary block, and a
|
|
@@ -400,6 +400,44 @@ organise={{ tabAlign: "stepper" }} // renders tabs as a step-by-step wizard
|
|
|
400
400
|
|
|
401
401
|
`tabId` and `groupId` can be combined — fields are first split by tab, then grouped within each tab.
|
|
402
402
|
|
|
403
|
+
#### List objects inside sections
|
|
404
|
+
|
|
405
|
+
Set `listObjectApplyGroup: true` on a `listObject` schema entry to include it in
|
|
406
|
+
its `groupId` section in read and update/create views:
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
{
|
|
410
|
+
id: "items",
|
|
411
|
+
name: "Items",
|
|
412
|
+
type: "listObject",
|
|
413
|
+
tabId: "Details",
|
|
414
|
+
groupId: "Shipping",
|
|
415
|
+
listObjectApplyGroup: true,
|
|
416
|
+
listObjectTableView: true,
|
|
417
|
+
objectData: [
|
|
418
|
+
{ id: "name", name: "Name", type: "string" },
|
|
419
|
+
{ id: "quantity", name: "Quantity", type: "number" },
|
|
420
|
+
],
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
`listObjectApplyGroup?: boolean` defaults to ungrouped when false or omitted.
|
|
425
|
+
It is independent of `listObjectTableView`: both lists and tables can join a
|
|
426
|
+
section. Grouped list objects share the read section's card and span all columns
|
|
427
|
+
of the update section, regardless of `rowColumns`. Adding and editing rows uses
|
|
428
|
+
the existing modal.
|
|
429
|
+
|
|
430
|
+
Ungrouped list objects appear after all tab sections in grouped read views,
|
|
431
|
+
even when they have a `tabId` and `groupId`. In update forms, regular ungrouped
|
|
432
|
+
lists appear after the form; ordered (`listObjOrder`) and FlexTable
|
|
433
|
+
(`listFlexTable`) lists remain in the default form area outside named groups.
|
|
434
|
+
Existing schemas must explicitly set `listObjectApplyGroup: true` to place a
|
|
435
|
+
list object inside a named section.
|
|
436
|
+
|
|
437
|
+
Open **Data / Data Wrapper / View / DTW Group Test** in Storybook to compare
|
|
438
|
+
Operation Crew (grouped table), Sprayed Plots (grouped list), and Operation
|
|
439
|
+
Equipment (ungrouped table), with sample rows provided in the story backend.
|
|
440
|
+
|
|
403
441
|
---
|
|
404
442
|
|
|
405
443
|
## Usage
|
|
@@ -7,7 +7,10 @@ import axios from "axios";
|
|
|
7
7
|
import dayjs from "../../dayjs";
|
|
8
8
|
import { Button } from "../../action/button";
|
|
9
9
|
import { Loading } from "../../display/loading";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
DataWrapperTestSchema,
|
|
12
|
+
RowAlignmentSchema,
|
|
13
|
+
} from "./dataWrapper.fixtures";
|
|
11
14
|
import { DataTemplateWrapper } from "./index";
|
|
12
15
|
import {
|
|
13
16
|
STORY_API_ENDPOINT,
|
|
@@ -114,6 +117,43 @@ export const DataWrapperTest = () => {
|
|
|
114
117
|
};
|
|
115
118
|
DataWrapperTest.storyName = "DTW Test";
|
|
116
119
|
|
|
120
|
+
// INFO: a create form whose two-column rows each mix a described field with an
|
|
121
|
+
// undescribed one. Every row's controls must sit on one baseline while the
|
|
122
|
+
// labels sit on another — the gap between them absorbs the difference.
|
|
123
|
+
export const RowAlignment = () => {
|
|
124
|
+
const mount = useStoryBackend({
|
|
125
|
+
collections: { "row-alignment": [] },
|
|
126
|
+
yieldToE2E: true,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (!mount) {
|
|
130
|
+
return <Loading />;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div className="w-full">
|
|
135
|
+
<div className="font-semibold mb-1 text-gray-900">Row Alignment</div>
|
|
136
|
+
<div className="text-sm font-light mb-4 text-gray-500">
|
|
137
|
+
Two-column create form where some fields carry a description and their
|
|
138
|
+
row neighbours do not. Controls align per row; labels align per row.
|
|
139
|
+
</div>
|
|
140
|
+
<DataTemplateWrapper
|
|
141
|
+
title="Row Alignment"
|
|
142
|
+
type="update"
|
|
143
|
+
id="new"
|
|
144
|
+
collectionId={"row-alignment"}
|
|
145
|
+
schema={RowAlignmentSchema}
|
|
146
|
+
workspace={storyWorkspace()}
|
|
147
|
+
edit={{ allEditable: true }}
|
|
148
|
+
bareSettings={{ bareUpdateActionBottom: true }}
|
|
149
|
+
organise={{ breadcrumbType: "hidden" }}
|
|
150
|
+
limit={1}
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
RowAlignment.storyName = "Row Alignment";
|
|
156
|
+
|
|
117
157
|
// INFO: Story used exclusively by the hide-columns e2e block. Shares the
|
|
118
158
|
// dtw-test-collection alias so the spec's existing API mocks apply, and adds
|
|
119
159
|
// one developer-hidden column to prove users can never toggle it back on.
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
CalendarSettings,
|
|
20
20
|
FeedsSettings,
|
|
21
21
|
KanbanSettings,
|
|
22
|
+
TimelineSettings,
|
|
22
23
|
} from "../dataTemplate/spaceModel";
|
|
23
24
|
import {
|
|
24
25
|
DataWrapperTestSchema,
|
|
@@ -64,6 +65,10 @@ const handleBuildChecklistRecord = (index: number) => ({
|
|
|
64
65
|
date: dayjs()
|
|
65
66
|
.add(index - 15, "day")
|
|
66
67
|
.toISOString(),
|
|
68
|
+
// INFO: a span a few days after the start so the timeline draws a readable bar
|
|
69
|
+
endDate: dayjs()
|
|
70
|
+
.add(index - 15 + (3 + (index % 5)), "day")
|
|
71
|
+
.toISOString(),
|
|
67
72
|
installerName: STORY_NAMES[(index + 4) % STORY_NAMES.length],
|
|
68
73
|
numberOfModules: 8 + (index % 12),
|
|
69
74
|
typeOfInverter: index % 2 === 0 ? "String Inverter" : "Microinverter",
|
|
@@ -119,6 +124,7 @@ export const DataWrapper = () => {
|
|
|
119
124
|
calendarSettings?: CalendarSettings;
|
|
120
125
|
feedsSettings?: FeedsSettings;
|
|
121
126
|
kanbanSettings?: KanbanSettings;
|
|
127
|
+
timelineSettings?: TimelineSettings;
|
|
122
128
|
} = {};
|
|
123
129
|
|
|
124
130
|
if (listLayout === "grid") {
|
|
@@ -146,6 +152,20 @@ export const DataWrapper = () => {
|
|
|
146
152
|
viewSwitch: ["month", "week", "agenda"],
|
|
147
153
|
};
|
|
148
154
|
}
|
|
155
|
+
if (listLayout === "timeline") {
|
|
156
|
+
viewSettings.timelineSettings = {
|
|
157
|
+
title: "clientName",
|
|
158
|
+
startDate: "date",
|
|
159
|
+
endDate: "endDate",
|
|
160
|
+
// INFO: colour bars by inverter type, same grouping the calendar story
|
|
161
|
+
// uses so the two views read the same data the same way
|
|
162
|
+
groupId: "typeOfInverter",
|
|
163
|
+
groupColorMap: {
|
|
164
|
+
"String Inverter": "blue",
|
|
165
|
+
Microinverter: "indigo",
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
149
169
|
return viewSettings;
|
|
150
170
|
};
|
|
151
171
|
|
|
@@ -179,6 +199,7 @@ export const DataWrapper = () => {
|
|
|
179
199
|
{ id: "scrollList", name: "Scroll Pagination List", href: "#" },
|
|
180
200
|
{ id: "grid", name: "Grid View", href: "#" },
|
|
181
201
|
{ id: "calendar", name: "Calendar View", href: "#" },
|
|
202
|
+
{ id: "timeline", name: "Timeline View", href: "#" },
|
|
182
203
|
{ id: "customList", name: "Custom List View", href: "#" },
|
|
183
204
|
]}
|
|
184
205
|
onClick={(id) => {
|
|
@@ -458,6 +479,34 @@ const DTWGroupTestSchema = [
|
|
|
458
479
|
groupId: "1. Operation",
|
|
459
480
|
optional: true,
|
|
460
481
|
},
|
|
482
|
+
{
|
|
483
|
+
id: "operationCrew",
|
|
484
|
+
name: "Operation Crew",
|
|
485
|
+
type: "listObject" as const,
|
|
486
|
+
tabId: "A. Basic Info",
|
|
487
|
+
groupId: "1. Operation",
|
|
488
|
+
listObjectApplyGroup: true,
|
|
489
|
+
listObjectTableView: true,
|
|
490
|
+
optional: true,
|
|
491
|
+
objectData: [
|
|
492
|
+
{ id: "name", name: "Name", type: "string" as const },
|
|
493
|
+
{ id: "role", name: "Role", type: "string" as const },
|
|
494
|
+
],
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
id: "operationEquipment",
|
|
498
|
+
name: "Operation Equipment (Ungrouped)",
|
|
499
|
+
type: "listObject" as const,
|
|
500
|
+
tabId: "A. Basic Info",
|
|
501
|
+
groupId: "1. Operation",
|
|
502
|
+
listObjectApplyGroup: false,
|
|
503
|
+
listObjectTableView: true,
|
|
504
|
+
optional: true,
|
|
505
|
+
objectData: [
|
|
506
|
+
{ id: "name", name: "Equipment", type: "string" as const },
|
|
507
|
+
{ id: "quantity", name: "Quantity", type: "number" as const },
|
|
508
|
+
],
|
|
509
|
+
},
|
|
461
510
|
{
|
|
462
511
|
id: "status",
|
|
463
512
|
name: "Status",
|
|
@@ -523,6 +572,19 @@ const DTWGroupTestSchema = [
|
|
|
523
572
|
groupId: "2. Coverage",
|
|
524
573
|
optional: true,
|
|
525
574
|
},
|
|
575
|
+
{
|
|
576
|
+
id: "sprayedPlots",
|
|
577
|
+
name: "Sprayed Plots",
|
|
578
|
+
type: "listObject" as const,
|
|
579
|
+
tabId: "B. Pesticide",
|
|
580
|
+
groupId: "2. Coverage",
|
|
581
|
+
listObjectApplyGroup: true,
|
|
582
|
+
optional: true,
|
|
583
|
+
objectData: [
|
|
584
|
+
{ id: "plot", name: "Plot", type: "string" as const },
|
|
585
|
+
{ id: "hectares", name: "Hectares", type: "number" as const },
|
|
586
|
+
],
|
|
587
|
+
},
|
|
526
588
|
// Tab C: Flight Metrics
|
|
527
589
|
{
|
|
528
590
|
id: "totalFlight",
|
|
@@ -654,6 +716,18 @@ const handleBuildWorkOrderRecord = (index: number) => {
|
|
|
654
716
|
endDate: start.add(6, "hour").toISOString(),
|
|
655
717
|
totalFlight: 2 + (index % 6),
|
|
656
718
|
totalHours: 3 + (index % 5),
|
|
719
|
+
operationCrew: [
|
|
720
|
+
{ id: `crew-${index}-1`, name: "Ali", role: "Pilot" },
|
|
721
|
+
{ id: `crew-${index}-2`, name: "Siti", role: "Ground Crew" },
|
|
722
|
+
],
|
|
723
|
+
operationEquipment: [
|
|
724
|
+
{ id: `equipment-${index}-1`, name: "Sprayer Drone", quantity: 1 },
|
|
725
|
+
{ id: `equipment-${index}-2`, name: "Spare Battery", quantity: 3 },
|
|
726
|
+
],
|
|
727
|
+
sprayedPlots: [
|
|
728
|
+
{ id: `plot-${index}-1`, plot: "North Field", hectares: 2.5 },
|
|
729
|
+
{ id: `plot-${index}-2`, plot: "South Field", hectares: 1.8 },
|
|
730
|
+
],
|
|
657
731
|
};
|
|
658
732
|
};
|
|
659
733
|
|
|
@@ -680,7 +754,9 @@ export const DTWGroupTest = () => {
|
|
|
680
754
|
<div className="text-sm font-light mb-4 text-gray-500">
|
|
681
755
|
Multi-tab + multi-group schema demo. List + read view renders sections
|
|
682
756
|
by tabId / groupId; Create / Update / Delete write to the in-memory
|
|
683
|
-
story backend.
|
|
757
|
+
story backend. Operation Crew (table) and Sprayed Plots (list) use
|
|
758
|
+
listObjectApplyGroup to appear inside their sections. Operation
|
|
759
|
+
Equipment has grouping disabled for comparison.
|
|
684
760
|
</div>
|
|
685
761
|
<DataTemplateWrapper
|
|
686
762
|
ref={dataRefreshRef}
|
|
@@ -706,7 +782,7 @@ export const DTWGroupTest = () => {
|
|
|
706
782
|
sort: { sortId: "startDate", type: "desc" as const },
|
|
707
783
|
breadcrumbType: "button",
|
|
708
784
|
groupRead: "groupId",
|
|
709
|
-
tabAlign: "left",
|
|
785
|
+
// tabAlign: "left",
|
|
710
786
|
filterComponentNewRow: true,
|
|
711
787
|
// INFO: removed showExport to hide the ellipsis (⋮) menu
|
|
712
788
|
}}
|
|
@@ -1039,6 +1115,135 @@ export const DataWrapperDrawerTest = () => {
|
|
|
1039
1115
|
};
|
|
1040
1116
|
DataWrapperDrawerTest.storyName = "DTW Drawer Test";
|
|
1041
1117
|
|
|
1118
|
+
// ===================================================================================
|
|
1119
|
+
// SECTION: Timeline view — GanttChart-backed date-range view
|
|
1120
|
+
// ===================================================================================
|
|
1121
|
+
|
|
1122
|
+
// INFO: seeds records that each carry a start (projectStart) and end (projectEnd)
|
|
1123
|
+
// spanning a few days, so the timeline draws a readable bar per row. One row is
|
|
1124
|
+
// intentionally left without an end date to show that incomplete rows are skipped
|
|
1125
|
+
// rather than dropping every bar around them.
|
|
1126
|
+
const TimelineStorySchema = [
|
|
1127
|
+
{
|
|
1128
|
+
id: "projectName",
|
|
1129
|
+
name: "Project",
|
|
1130
|
+
type: "string" as const,
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
id: "team",
|
|
1134
|
+
name: "Team",
|
|
1135
|
+
type: "list" as const,
|
|
1136
|
+
listSelection: ["Design", "Engineering", "QA"],
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
id: "projectStart",
|
|
1140
|
+
name: "Start Date",
|
|
1141
|
+
type: "date" as const,
|
|
1142
|
+
},
|
|
1143
|
+
{
|
|
1144
|
+
id: "projectEnd",
|
|
1145
|
+
name: "End Date",
|
|
1146
|
+
type: "date" as const,
|
|
1147
|
+
optional: true,
|
|
1148
|
+
},
|
|
1149
|
+
];
|
|
1150
|
+
|
|
1151
|
+
const TIMELINE_TEAMS = ["Design", "Engineering", "QA"];
|
|
1152
|
+
|
|
1153
|
+
const handleBuildTimelineRecord = (index: number) => {
|
|
1154
|
+
// INFO: stagger each project's start so the bars cascade down the axis the way
|
|
1155
|
+
// the Velocity-style timeline in the brief does
|
|
1156
|
+
const start = dayjs()
|
|
1157
|
+
.startOf("month")
|
|
1158
|
+
.add(index * 3, "day");
|
|
1159
|
+
// INFO: every 5th row is left open-ended (no projectEnd) to demonstrate the
|
|
1160
|
+
// skip — it must not render a bar, and must not hide the rows after it
|
|
1161
|
+
const isOpenEnded = index % 5 === 4;
|
|
1162
|
+
return {
|
|
1163
|
+
_id: `timeline-${index}`,
|
|
1164
|
+
projectName: `Project ${String.fromCharCode(65 + (index % 26))}${index}`,
|
|
1165
|
+
team: TIMELINE_TEAMS[index % TIMELINE_TEAMS.length],
|
|
1166
|
+
projectStart: start.toISOString(),
|
|
1167
|
+
projectEnd: isOpenEnded
|
|
1168
|
+
? undefined
|
|
1169
|
+
: start.add(4 + (index % 6), "day").toISOString(),
|
|
1170
|
+
};
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1173
|
+
export const Timeline = () => {
|
|
1174
|
+
const mount = useStoryBackend({
|
|
1175
|
+
collections: {
|
|
1176
|
+
"dtw-timeline-collection": handleBuildStoryRecords(
|
|
1177
|
+
12,
|
|
1178
|
+
handleBuildTimelineRecord,
|
|
1179
|
+
),
|
|
1180
|
+
},
|
|
1181
|
+
yieldToE2E: true,
|
|
1182
|
+
});
|
|
1183
|
+
const dataRefreshRef: any = useRef(null);
|
|
1184
|
+
const [viewType, setViewType] = useState<"list" | "read" | "update">("list");
|
|
1185
|
+
const [selectedId, setSelectedId] = useState<string | undefined>(undefined);
|
|
1186
|
+
|
|
1187
|
+
const handleNavigate = (type: any, id: any) => {
|
|
1188
|
+
setViewType(type);
|
|
1189
|
+
setSelectedId(id);
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
if (!mount) {
|
|
1193
|
+
return <Loading />;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
return (
|
|
1197
|
+
<div className="w-full">
|
|
1198
|
+
<div className="font-semibold mb-1 text-gray-900">DTW Timeline</div>
|
|
1199
|
+
<div className="text-sm font-light mb-4 text-gray-500">
|
|
1200
|
+
The timeline view renders each record as a horizontal bar spanning its
|
|
1201
|
+
start and end date, powered by GanttChart. Bars are coloured by team via{" "}
|
|
1202
|
+
<code>timelineSettings.groupColorMap</code>. Rows missing an end date are
|
|
1203
|
+
skipped (every 5th project here is open-ended), and clicking a bar
|
|
1204
|
+
navigates to that record. Backed by 12 in-memory records — no dev server
|
|
1205
|
+
needed.
|
|
1206
|
+
</div>
|
|
1207
|
+
<DataTemplateWrapper
|
|
1208
|
+
ref={dataRefreshRef}
|
|
1209
|
+
title="Projects"
|
|
1210
|
+
type={viewType}
|
|
1211
|
+
id={selectedId}
|
|
1212
|
+
collectionId={"dtw-timeline-collection"}
|
|
1213
|
+
schema={TimelineStorySchema}
|
|
1214
|
+
workspace={storyWorkspace()}
|
|
1215
|
+
edit={{ allEditable: true }}
|
|
1216
|
+
bareSettings={{
|
|
1217
|
+
bareCompactMode: true,
|
|
1218
|
+
bareOffline: false,
|
|
1219
|
+
}}
|
|
1220
|
+
organise={{
|
|
1221
|
+
sort: { sortId: "projectStart", type: "asc" as const },
|
|
1222
|
+
breadcrumbType: "button",
|
|
1223
|
+
}}
|
|
1224
|
+
handleNavigate={handleNavigate}
|
|
1225
|
+
viewSettings={{
|
|
1226
|
+
timelineSettings: {
|
|
1227
|
+
title: "projectName",
|
|
1228
|
+
startDate: "projectStart",
|
|
1229
|
+
endDate: "projectEnd",
|
|
1230
|
+
// INFO: fixed colour per team so the same team is always the same
|
|
1231
|
+
// colour across refetches, matching the calendar's grouping behaviour
|
|
1232
|
+
groupId: "team",
|
|
1233
|
+
groupColorMap: {
|
|
1234
|
+
Design: "violet",
|
|
1235
|
+
Engineering: "blue",
|
|
1236
|
+
QA: "green",
|
|
1237
|
+
},
|
|
1238
|
+
},
|
|
1239
|
+
}}
|
|
1240
|
+
limit={50}
|
|
1241
|
+
/>
|
|
1242
|
+
</div>
|
|
1243
|
+
);
|
|
1244
|
+
};
|
|
1245
|
+
Timeline.storyName = "DTW Timeline";
|
|
1246
|
+
|
|
1042
1247
|
// ===================================================================================
|
|
1043
1248
|
// SECTION: Offline demo — DTW with no backend required
|
|
1044
1249
|
// ===================================================================================
|
|
@@ -16,3 +16,9 @@ Sectioned card board. Desktop: horizontal columns. Mobile: tab switcher between
|
|
|
16
16
|
## Phone view
|
|
17
17
|
|
|
18
18
|
`mobileView` swaps the column row for a tab rail with one column visible. The first column is selected as soon as columns exist (data usually arrives after the first render). `className` styles the desktop column row only — the stacked phone layout ignores it, so a `flex` / `inline-flex` passed for the desktop row cannot turn the phone view into a row. Rail, count badges, hint text and the default card follow `darkMode`.
|
|
19
|
+
|
|
20
|
+
## Lane colours
|
|
21
|
+
|
|
22
|
+
Each lane carries an accent colour: a coloured top border and a matching dot beside its title (and on its tab in the phone view). Colours come from a fixed palette in lane order. `Unassigned` is always grey and is left out of the order, so switching `nullColumn` on does not change any other lane's colour. When `columns` is passed, a column's own `color` (any CSS colour) is used as it is.
|
|
23
|
+
|
|
24
|
+
The lane heading shows the title and the number of cards in a pill; the count pill carries `data-testid="new-kanban-count-<column id>"`.
|
|
@@ -109,7 +109,7 @@ What changes when `borderless` is on:
|
|
|
109
109
|
| | Default | `borderless` |
|
|
110
110
|
| --------- | -------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
111
111
|
| Container | `rounded`, `bg-white`, `border border-gray-300` | none |
|
|
112
|
-
| Toolbar | Always shown when not `readOnly` | Hidden —
|
|
112
|
+
| Toolbar | Always shown when not `readOnly` | Hidden — floating toolbar on text selection, `/` and keyboard shortcuts |
|
|
113
113
|
| Height | `heightClassName ?? "h-72"` with inner `overflow-y-auto` | `heightClassName ?? "min-h-[12rem]"`, grows with content, page scrolls |
|
|
114
114
|
| Padding | `p-4` | `p-0` |
|
|
115
115
|
| Label row | Always rendered | Rendered only if `label`, `subLabel`, `customElement`, or `infoElement` is given |
|
|
@@ -118,6 +118,8 @@ On error the editor draws a red outline (`border border-red-500 rounded`) — th
|
|
|
118
118
|
|
|
119
119
|
> **`heightClassName` in borderless mode must be a `min-h-*` class**, not a fixed `h-*`. Borderless has no inner `overflow-y-auto`, so a fixed height clips content instead of scrolling it. Leave it unset unless you need a taller empty state.
|
|
120
120
|
|
|
121
|
+
Selecting text in borderless mode shows a floating toolbar above the selection — Bold, Italic, Underline, Strikethrough, Inline code, Link and Clear formatting. It works inside table cells, and does not show in read-only mode, in a code block, or when an image or embed is selected. The boxed editor keeps its static toolbar instead.
|
|
122
|
+
|
|
121
123
|
> **Note:** hiding the toolbar also hides its media-upload button. In borderless mode insert images and videos by **dragging or pasting** into the editor, or supply your own control via `customElement`.
|
|
122
124
|
|
|
123
125
|
##### Read-Only Mode (Preview)
|
|
@@ -163,6 +165,7 @@ On error the editor draws a red outline (`border border-red-500 rounded`) — th
|
|
|
163
165
|
| `lib` | `{ axios?: any, mermaid?: () => Promise<any> }` | `null` | External libraries — axios for API calls; `mermaid` loader (e.g. `() => import("mermaid")`) to draw diagrams |
|
|
164
166
|
| `uploadEndpoint` | `string` | `null` | API endpoint for uploading media files |
|
|
165
167
|
| `onHeadingsChange` | `(headings: MarkdownHeading[]) => void` | `null` | Reports the document's `h1`–`h3` outline whenever it changes — feed it to `MarkdownOutline` |
|
|
168
|
+
| `textSize` | `"sm"` | `null` | 14px text on every screen. On phones the editor keeps a real 16px font and is drawn at 87.5% with `transform: scale`, the same trick as `TextInput` xs, so iOS Safari does not zoom on focus. `MarkdownStatic` takes the same prop |
|
|
166
169
|
|
|
167
170
|
##### Ref Methods
|
|
168
171
|
|