blixify-ui-web-mcp 0.1.25 → 0.1.26
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 +24 -21
- package/lib/components/data/dataTemplate/dataTemplate.savedViews.stories.tsx +142 -0
- package/lib/components/data/dataTemplate/dataTemplate.scrollRestore.stories.tsx +246 -0
- package/lib/components/data/dataTemplate/dataTemplate.stories.tsx +196 -38
- package/lib/components/data/dataWrapper/README.md +92 -49
- package/lib/components/data/dataWrapper/dataWrapper.savedViews.stories.tsx +149 -0
- package/lib/components/data/writeQuery/README.md +22 -0
- package/lib/components/display/newGrid/newGrid.stories.tsx +256 -0
- package/lib/components/display/newKanban/newKanban.stories.tsx +109 -51
- package/lib/components/input/datePicker/README.md +1 -0
- package/lib/components/input/select/README.md +1 -0
- package/lib/components/input/textInput/README.md +3 -1
- package/lib/components/input/textInput/textinput.stories.tsx +27 -0
- package/lib/components/navigation/leftNav/README.md +5 -0
- package/lib/components/navigation/leftNav/leftNav.stories.tsx +38 -0
- package/lib/mcp/tools/getBackendComponent.d.ts.map +1 -1
- package/lib/mcp/tools/getBackendComponent.js +4 -0
- package/lib/mcp/tools/getBackendComponent.js.map +1 -1
- package/lib/mcp/tools/getComponent.d.ts.map +1 -1
- package/lib/mcp/tools/getComponent.js +6 -1
- package/lib/mcp/tools/getComponent.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { NewGrid as NewGridImpl, NewGridItem } from ".";
|
|
3
|
+
import { StorySection } from "../../storyUtils";
|
|
4
|
+
|
|
5
|
+
const meta = { title: "Display/New Grid" };
|
|
6
|
+
export default meta;
|
|
7
|
+
|
|
8
|
+
// INFO: identical sample data to legacy Display/Grid for side-by-side comparison
|
|
9
|
+
const data: NewGridItem[] = [
|
|
10
|
+
{
|
|
11
|
+
id: "1",
|
|
12
|
+
title: "Nike Court Vision",
|
|
13
|
+
description: "Nike Men's Court Vision Low Next Nature Shoes - Black",
|
|
14
|
+
tags: ["Sports"],
|
|
15
|
+
imageUrl: "https://cf.shopee.com.my/file/fef57a9b0d40a0626def6fdc993c442a",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "2",
|
|
19
|
+
title: "Nike Air Max 2X",
|
|
20
|
+
description: "Nike Men's Air Max 2X Shoes - White",
|
|
21
|
+
tags: ["Sports", "Promotions"],
|
|
22
|
+
imageUrl: "https://cf.shopee.com.my/file/6df4da8ff019bfa08c1bcba4bbe3c983",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "3",
|
|
26
|
+
title: "Nike Air Max 2X",
|
|
27
|
+
description: "Nike Men's Air Max 2X Shoes - White",
|
|
28
|
+
tags: ["Sports", "Promotions"],
|
|
29
|
+
imageUrl: "https://cf.shopee.com.my/file/6df4da8ff019bfa08c1bcba4bbe3c983",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "4",
|
|
33
|
+
title: "Nike Air Max 2X",
|
|
34
|
+
description: "Nike Men's Air Max 2X Shoes - White",
|
|
35
|
+
tags: ["Sports", "Promotions"],
|
|
36
|
+
ribbonText: "SALE",
|
|
37
|
+
imageUrl: "https://cf.shopee.com.my/file/6df4da8ff019bfa08c1bcba4bbe3c983",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "5",
|
|
41
|
+
title: "Nike Air Max 2X",
|
|
42
|
+
description: "Nike Men's Air Max 2X Shoes - White",
|
|
43
|
+
tags: ["Sports"],
|
|
44
|
+
imageUrl: "https://cf.shopee.com.my/file/6df4da8ff019bfa08c1bcba4bbe3c983",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "6",
|
|
48
|
+
title: "Nike Air Max 2X",
|
|
49
|
+
description: "Nike Men's Air Max 2X Shoes - White",
|
|
50
|
+
tags: ["Sports", "Promotions"],
|
|
51
|
+
imageUrl: "https://cf.shopee.com.my/file/6df4da8ff019bfa08c1bcba4bbe3c983",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "7",
|
|
55
|
+
title: "Nike Air Max 2X",
|
|
56
|
+
description: "Nike Men's Air Max 2X Shoes - White",
|
|
57
|
+
tags: ["Sports"],
|
|
58
|
+
imageUrl: "https://cf.shopee.com.my/file/6df4da8ff019bfa08c1bcba4bbe3c983",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "8",
|
|
62
|
+
title: "Nike Air Max 2X",
|
|
63
|
+
description: "Nike Men's Air Max 2X Shoes - White",
|
|
64
|
+
tags: ["Sports", "Promotions"],
|
|
65
|
+
imageUrl: "https://cf.shopee.com.my/file/6df4da8ff019bfa08c1bcba4bbe3c983",
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const renderImgRibbon = (id: string) => {
|
|
70
|
+
if (id === "1" || id === "2") {
|
|
71
|
+
return (
|
|
72
|
+
<div className="absolute top-5 -left-9 w-[50%] bg-yellow-500 text-white text-xs font-bold p-2 text-center -rotate-45">
|
|
73
|
+
<span>WINNER</span>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const NewGrid = () => {
|
|
81
|
+
const [pageIndex, setPageIndex] = useState(0);
|
|
82
|
+
const limit = 5;
|
|
83
|
+
const cursor = true;
|
|
84
|
+
|
|
85
|
+
const handlePagination = (value: string | number) => {
|
|
86
|
+
if (typeof value === "number") setPageIndex(value);
|
|
87
|
+
else setPageIndex((prev) => (value === "next" ? prev + 1 : prev - 1));
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="font-sans p-6">
|
|
92
|
+
<StorySection
|
|
93
|
+
title="NewGrid — same data as legacy Display/Grid"
|
|
94
|
+
description="NewGridItem = GridClass. Drop-in swap from <Grid data={d}> to <NewGridImpl data={d}> — modernised card chrome (rounded-2xl, tag chips, shared NewPagination footer)."
|
|
95
|
+
>
|
|
96
|
+
<NewGridImpl
|
|
97
|
+
data={data}
|
|
98
|
+
limit={limit}
|
|
99
|
+
loading={false}
|
|
100
|
+
paginationType="page"
|
|
101
|
+
size={data.length}
|
|
102
|
+
pageIndex={pageIndex}
|
|
103
|
+
handleUpdatePageIndex={handlePagination}
|
|
104
|
+
renderImgRibbon={renderImgRibbon}
|
|
105
|
+
/>
|
|
106
|
+
</StorySection>
|
|
107
|
+
|
|
108
|
+
<StorySection
|
|
109
|
+
title="NewGrid — Horizontal scroll pagination"
|
|
110
|
+
description="Use paginationType='scroll' with horizontalScroll for an infinite grid that auto-loads the next page when the user scrolls to the right edge. Grab-to-pan drag is enabled."
|
|
111
|
+
>
|
|
112
|
+
<NewGridImpl
|
|
113
|
+
data={data}
|
|
114
|
+
limit={limit}
|
|
115
|
+
loading={false}
|
|
116
|
+
paginationType="scroll"
|
|
117
|
+
cursor={cursor}
|
|
118
|
+
pageIndex={pageIndex}
|
|
119
|
+
size={data.length}
|
|
120
|
+
horizontalScroll
|
|
121
|
+
handleUpdatePageIndex={handlePagination}
|
|
122
|
+
renderImgRibbon={renderImgRibbon}
|
|
123
|
+
/>
|
|
124
|
+
</StorySection>
|
|
125
|
+
|
|
126
|
+
<StorySection
|
|
127
|
+
title="NewGrid — Cursor pagination"
|
|
128
|
+
description="Use paginationType='cursor' for prev/next navigation with a truthy cursor while more data exists."
|
|
129
|
+
>
|
|
130
|
+
<NewGridImpl
|
|
131
|
+
data={data}
|
|
132
|
+
limit={limit}
|
|
133
|
+
loading={false}
|
|
134
|
+
paginationType="cursor"
|
|
135
|
+
cursor={cursor}
|
|
136
|
+
pageIndex={pageIndex}
|
|
137
|
+
size={data.length}
|
|
138
|
+
handleUpdatePageIndex={handlePagination}
|
|
139
|
+
onClickData={(id) => console.log("clicked", id)}
|
|
140
|
+
/>
|
|
141
|
+
</StorySection>
|
|
142
|
+
|
|
143
|
+
<StorySection
|
|
144
|
+
title="NewGrid — Custom grid item"
|
|
145
|
+
description="Pass customGridItems with a map of id → ReactElement to replace a specific card with fully custom content. The key must match the item's id in data."
|
|
146
|
+
>
|
|
147
|
+
<NewGridImpl
|
|
148
|
+
data={data.slice(0, 4)}
|
|
149
|
+
limit={limit}
|
|
150
|
+
loading={false}
|
|
151
|
+
customGridItems={{
|
|
152
|
+
"2": (
|
|
153
|
+
<div className="flex h-full flex-col items-center justify-center bg-primary-700 p-4 font-thin text-white">
|
|
154
|
+
<h1 className="text-3xl">Custom item</h1>
|
|
155
|
+
<p className="mt-6 text-sm">
|
|
156
|
+
This card replaces the default grid item for id="2".
|
|
157
|
+
</p>
|
|
158
|
+
</div>
|
|
159
|
+
),
|
|
160
|
+
}}
|
|
161
|
+
/>
|
|
162
|
+
</StorySection>
|
|
163
|
+
|
|
164
|
+
<StorySection
|
|
165
|
+
title="NewGrid — Selected items"
|
|
166
|
+
description="Pass selectedItemsId and selectedItemClassName to highlight specific cards."
|
|
167
|
+
>
|
|
168
|
+
<NewGridImpl
|
|
169
|
+
data={data.slice(0, 4)}
|
|
170
|
+
limit={limit}
|
|
171
|
+
loading={false}
|
|
172
|
+
selectedItemsId={["1", "3"]}
|
|
173
|
+
selectedItemClassName="ring-2 ring-primary-500"
|
|
174
|
+
onClickData={(id) => console.log("clicked", id)}
|
|
175
|
+
/>
|
|
176
|
+
</StorySection>
|
|
177
|
+
|
|
178
|
+
<StorySection
|
|
179
|
+
title="NewGrid — Dark mode"
|
|
180
|
+
description="darkMode uses the solid bg-gray-900 surface (same as NewList/NewKanban). Pass darkModeBg to override the default surface."
|
|
181
|
+
dark
|
|
182
|
+
>
|
|
183
|
+
<div className="w-full min-w-0 rounded-lg bg-gray-950 p-6">
|
|
184
|
+
<NewGridImpl
|
|
185
|
+
data={data}
|
|
186
|
+
limit={limit}
|
|
187
|
+
loading={false}
|
|
188
|
+
paginationType="page"
|
|
189
|
+
size={data.length}
|
|
190
|
+
pageIndex={pageIndex}
|
|
191
|
+
darkMode
|
|
192
|
+
handleUpdatePageIndex={handlePagination}
|
|
193
|
+
renderImgRibbon={renderImgRibbon}
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
</StorySection>
|
|
197
|
+
|
|
198
|
+
<StorySection
|
|
199
|
+
title="NewGrid — Loading state"
|
|
200
|
+
description="Pass loading=true to show a loading indicator while data is being fetched."
|
|
201
|
+
>
|
|
202
|
+
<NewGridImpl data={[]} loading={true} />
|
|
203
|
+
</StorySection>
|
|
204
|
+
|
|
205
|
+
<StorySection
|
|
206
|
+
title="NewGrid — Card links open in a new tab"
|
|
207
|
+
description="Pass newTabUrl alongside onClickData: a plain click navigates in place via onClickData, while cmd/ctrl-click falls through to the underlying link and opens a new tab."
|
|
208
|
+
>
|
|
209
|
+
<NewGridImpl
|
|
210
|
+
data={data.slice(0, 3)}
|
|
211
|
+
limit={limit}
|
|
212
|
+
loading={false}
|
|
213
|
+
newTabUrl="?record="
|
|
214
|
+
onClickData={(id) => console.log("clicked", id)}
|
|
215
|
+
/>
|
|
216
|
+
</StorySection>
|
|
217
|
+
|
|
218
|
+
<StorySection
|
|
219
|
+
title="NewGrid — Empty and loading keep the pagination footer"
|
|
220
|
+
description="Loading and empty states render inside the wrapper, so the NewPagination footer stays mounted. Without it an empty page on a non-zero pageIndex would leave no control to navigate back."
|
|
221
|
+
>
|
|
222
|
+
<div className="w-full space-y-6">
|
|
223
|
+
<NewGridImpl
|
|
224
|
+
data={[]}
|
|
225
|
+
loading={false}
|
|
226
|
+
paginationType="page"
|
|
227
|
+
limit={limit}
|
|
228
|
+
size={0}
|
|
229
|
+
pageIndex={2}
|
|
230
|
+
emptyTitle="No records match the current filter"
|
|
231
|
+
handleUpdatePageIndex={handlePagination}
|
|
232
|
+
/>
|
|
233
|
+
<NewGridImpl
|
|
234
|
+
data={[]}
|
|
235
|
+
loading={true}
|
|
236
|
+
paginationType="page"
|
|
237
|
+
limit={limit}
|
|
238
|
+
size={0}
|
|
239
|
+
pageIndex={2}
|
|
240
|
+
handleUpdatePageIndex={handlePagination}
|
|
241
|
+
/>
|
|
242
|
+
</div>
|
|
243
|
+
</StorySection>
|
|
244
|
+
|
|
245
|
+
<StorySection
|
|
246
|
+
title="Comparing with the legacy Grid"
|
|
247
|
+
description="Open Display/Grid in another tab — same sample data renders. NewGridItem is exactly GridClass — drop-in data swap."
|
|
248
|
+
>
|
|
249
|
+
<div className="rounded-lg bg-gray-50 p-6 text-center text-sm text-gray-500">
|
|
250
|
+
→ See <code>Display/Grid</code> in the sidebar for the legacy
|
|
251
|
+
component.
|
|
252
|
+
</div>
|
|
253
|
+
</StorySection>
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
};
|
|
@@ -6,14 +6,13 @@ import { StorySection } from "../../storyUtils";
|
|
|
6
6
|
const meta = { title: "Display/New Kanban" };
|
|
7
7
|
export default meta;
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
// INFO: identical sample data to legacy Display/Kanban for side-by-side comparison
|
|
11
10
|
const initialData: NewKanbanCard[] = [
|
|
12
11
|
{
|
|
13
12
|
id: "1",
|
|
14
13
|
title: "DC-1",
|
|
15
14
|
description: "Desc",
|
|
16
|
-
|
|
15
|
+
status: "Open",
|
|
17
16
|
tags: ["Sports", "Promotions"],
|
|
18
17
|
createdAt: dayjs("12/24/2020").toDate(),
|
|
19
18
|
} as any,
|
|
@@ -22,7 +21,7 @@ const initialData: NewKanbanCard[] = [
|
|
|
22
21
|
title: "DC-2",
|
|
23
22
|
description:
|
|
24
23
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore.",
|
|
25
|
-
|
|
24
|
+
status: "In progress",
|
|
26
25
|
tags: ["Sports", "Promotions"],
|
|
27
26
|
createdAt: dayjs("12/24/2020").toDate(),
|
|
28
27
|
} as any,
|
|
@@ -30,7 +29,7 @@ const initialData: NewKanbanCard[] = [
|
|
|
30
29
|
id: "3",
|
|
31
30
|
title: "DC-3",
|
|
32
31
|
description: "test",
|
|
33
|
-
|
|
32
|
+
status: "To be tested",
|
|
34
33
|
tags: ["Sports", "Promotions"],
|
|
35
34
|
createdAt: dayjs("12/24/2020").toDate(),
|
|
36
35
|
} as any,
|
|
@@ -38,39 +37,47 @@ const initialData: NewKanbanCard[] = [
|
|
|
38
37
|
id: "4",
|
|
39
38
|
title: "DC-4",
|
|
40
39
|
description: "test",
|
|
41
|
-
|
|
40
|
+
status: "Reopen",
|
|
42
41
|
createdAt: dayjs("12/24/2020").toDate(),
|
|
43
42
|
} as any,
|
|
44
43
|
{
|
|
45
44
|
id: "5",
|
|
46
45
|
title: "DC-5",
|
|
47
46
|
description: "test",
|
|
48
|
-
|
|
47
|
+
status: "Reopen",
|
|
49
48
|
createdAt: dayjs("12/24/2020").toDate(),
|
|
50
49
|
} as any,
|
|
51
50
|
{
|
|
52
51
|
id: "6",
|
|
53
52
|
title: "DC-6",
|
|
54
53
|
description: "test",
|
|
55
|
-
|
|
54
|
+
status: "Open",
|
|
56
55
|
createdAt: dayjs("12/24/2020").toDate(),
|
|
57
56
|
} as any,
|
|
58
57
|
{
|
|
59
58
|
id: "7",
|
|
60
59
|
title: "DC-7",
|
|
61
60
|
description: "test",
|
|
62
|
-
|
|
61
|
+
status: "Open",
|
|
63
62
|
createdAt: dayjs("12/24/2020").toDate(),
|
|
64
63
|
} as any,
|
|
65
64
|
{
|
|
66
65
|
id: "8",
|
|
67
66
|
title: "DC-8",
|
|
68
67
|
description: "test",
|
|
69
|
-
|
|
68
|
+
status: "Open",
|
|
69
|
+
createdAt: dayjs("12/24/2020").toDate(),
|
|
70
|
+
} as any, // INFO: no status, so it lands in Unassigned
|
|
71
|
+
{
|
|
72
|
+
id: "9",
|
|
73
|
+
title: "DC-9",
|
|
74
|
+
description: "No status yet",
|
|
70
75
|
createdAt: dayjs("12/24/2020").toDate(),
|
|
71
76
|
} as any,
|
|
72
77
|
];
|
|
73
78
|
|
|
79
|
+
const STATUSES = ["Open", "In progress", "To be tested", "Reopen"];
|
|
80
|
+
|
|
74
81
|
export const NewKanban = () => {
|
|
75
82
|
// INFO: stateful demo for drag-and-drop — onUpdateData mutates the local copy
|
|
76
83
|
// so dropping a card visually moves it. Matches legacy <Kanban onUpdateData>.
|
|
@@ -78,54 +85,105 @@ export const NewKanban = () => {
|
|
|
78
85
|
const handleUpdate = (cardId: string, _from: string, toCol: string) => {
|
|
79
86
|
setData((prev) =>
|
|
80
87
|
prev.map((c) =>
|
|
81
|
-
c.id === cardId
|
|
88
|
+
c.id === cardId
|
|
89
|
+
? ({
|
|
90
|
+
...c,
|
|
91
|
+
status: toCol || undefined,
|
|
92
|
+
} as any)
|
|
93
|
+
: c,
|
|
82
94
|
),
|
|
83
95
|
);
|
|
84
96
|
};
|
|
85
97
|
|
|
86
98
|
return (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
99
|
+
<div className="font-sans p-6">
|
|
100
|
+
<StorySection
|
|
101
|
+
title="NewKanban — same data as legacy Display/Kanban"
|
|
102
|
+
description="Same props as legacy <Kanban>: groupId is the field to group by, groupBy lists the column values in order, and nullColumn always shows Unassigned for cards with no value."
|
|
103
|
+
>
|
|
104
|
+
<NewKanbanImpl
|
|
105
|
+
data={data}
|
|
106
|
+
groupId="status"
|
|
107
|
+
groupBy={STATUSES}
|
|
108
|
+
nullColumn
|
|
109
|
+
/>
|
|
110
|
+
</StorySection>
|
|
111
|
+
|
|
112
|
+
<StorySection
|
|
113
|
+
title="NewKanban — Drag and drop (desktop)"
|
|
114
|
+
description="draggable=true + onUpdateData enables HTML5 drag between columns. Drop a card on another column to move it. Column highlights when a draggable card hovers over it."
|
|
115
|
+
>
|
|
116
|
+
<NewKanbanImpl
|
|
117
|
+
data={data}
|
|
118
|
+
groupId="status"
|
|
119
|
+
groupBy={STATUSES}
|
|
120
|
+
nullColumn
|
|
121
|
+
draggable
|
|
122
|
+
onUpdateData={handleUpdate}
|
|
123
|
+
/>
|
|
124
|
+
</StorySection>
|
|
125
|
+
|
|
126
|
+
<StorySection
|
|
127
|
+
title="NewKanban — Mobile (tab switcher) + touch drag"
|
|
128
|
+
description="One column at a time. Tabs replace horizontal scroll. With draggable=true, long-press a card and drag onto a tab button to move it to that column. The active tab also switches automatically."
|
|
129
|
+
>
|
|
130
|
+
<NewKanbanImpl
|
|
131
|
+
data={data}
|
|
132
|
+
groupId="status"
|
|
133
|
+
groupBy={STATUSES}
|
|
134
|
+
nullColumn
|
|
135
|
+
mobileView
|
|
136
|
+
draggable
|
|
137
|
+
onUpdateData={handleUpdate}
|
|
138
|
+
onAddCard={() => {}}
|
|
139
|
+
/>
|
|
140
|
+
</StorySection>
|
|
94
141
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
142
|
+
<StorySection
|
|
143
|
+
title="NewKanban — Dark mode"
|
|
144
|
+
description="darkMode uses the solid bg-gray-900 surface (same as Grid/List/DetailList) instead of a translucent white overlay. Columns show a subtle border only, so there is no washed-out overlay behind the cards. Pass darkModeBg to override the default surface."
|
|
145
|
+
>
|
|
146
|
+
<div className="w-full min-w-0 rounded-lg bg-gray-950 p-6">
|
|
147
|
+
<NewKanbanImpl
|
|
148
|
+
data={data}
|
|
149
|
+
groupId="status"
|
|
150
|
+
groupBy={STATUSES}
|
|
151
|
+
nullColumn
|
|
152
|
+
draggable
|
|
153
|
+
onUpdateData={handleUpdate}
|
|
154
|
+
darkMode
|
|
155
|
+
/>
|
|
156
|
+
</div>
|
|
157
|
+
</StorySection>
|
|
106
158
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
159
|
+
<StorySection
|
|
160
|
+
title="NewKanban — Dark mode (mobile)"
|
|
161
|
+
description="Mobile tab switcher in dark mode. Tab bar and count badges use the gray-800/700 palette, cards use bg-gray-900."
|
|
162
|
+
>
|
|
163
|
+
<div className="w-full min-w-0 rounded-lg bg-gray-950 p-6">
|
|
164
|
+
<NewKanbanImpl
|
|
165
|
+
data={data}
|
|
166
|
+
groupId="status"
|
|
167
|
+
groupBy={STATUSES}
|
|
168
|
+
nullColumn
|
|
169
|
+
mobileView
|
|
170
|
+
draggable
|
|
171
|
+
onUpdateData={handleUpdate}
|
|
172
|
+
onAddCard={() => {}}
|
|
173
|
+
darkMode
|
|
174
|
+
/>
|
|
175
|
+
</div>
|
|
176
|
+
</StorySection>
|
|
120
177
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
178
|
+
<StorySection
|
|
179
|
+
title="Comparing with the legacy Kanban"
|
|
180
|
+
description="Open Display/Kanban in another tab — same sample data renders. NewKanbanCard is exactly GridClass — drop-in data swap. NewKanbanImpl renders the same data with modernised card styling (image strip, tag chips, rounded corners) and a mobile tab switcher instead of horizontal scroll."
|
|
181
|
+
>
|
|
182
|
+
<div className="rounded-lg bg-gray-50 p-6 text-center text-sm text-gray-500">
|
|
183
|
+
→ See <code>Display/Kanban</code> in the sidebar for the legacy
|
|
184
|
+
component.
|
|
185
|
+
</div>
|
|
186
|
+
</StorySection>
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
131
189
|
};
|
|
@@ -82,6 +82,7 @@ const ref = useRef(null);
|
|
|
82
82
|
| `mobileView` | `boolean` | `false` | Renders a compact inline layout for mobile (label left, value right) |
|
|
83
83
|
| `darkMode` | `boolean` | `false` | Applies dark colour scheme to labels |
|
|
84
84
|
| `className` | `tailwind class` | — | Class applied to the outer container |
|
|
85
|
+
| `size` | `"xs"` | — | Compact input that looks 12px everywhere: on phones it keeps 16px text drawn at 75%, so iOS Safari does not zoom; from `sm` up it is a plain 12px input. Uses important utilities so an app's global date-picker input rule does not override it |
|
|
85
86
|
| `onChange` | `(id: string, value: Date \| undefined, type?: string) => void` | — | Called on date selection. `type` is `"start"` or `"end"` in range mode |
|
|
86
87
|
| `cellInput` | `CellInput` | — | Refer to Input Utils — enables cell/table editing mode |
|
|
87
88
|
|
|
@@ -92,6 +92,7 @@ Async / database search:
|
|
|
92
92
|
| `infoElement` | `ReactElement` | — | Info element rendered in the label area |
|
|
93
93
|
| `containerClassName` | `string` | — | Tailwind classes for the outer container |
|
|
94
94
|
| `selectClassName` | `string` | — | Tailwind classes for the select trigger element |
|
|
95
|
+
| `size` | `"xs"` | — | Compact field that looks 12px everywhere: on phones it keeps 16px text and draws the trigger at 75%, so iOS Safari does not zoom on focus; from `sm` up it is a plain 12px field |
|
|
95
96
|
| `dropdownClassName` | `string` | — | Tailwind classes for the dropdown list |
|
|
96
97
|
| `imageClassName` | `string` | — | Tailwind classes for option images (used with `type="button"`) |
|
|
97
98
|
| `onChange` | `(value: string \| string[]) => void` | — | Called with the updated value when selection changes |
|
|
@@ -54,7 +54,9 @@ const ref = useRef(null);
|
|
|
54
54
|
| ----------------------- | ------------------ | ---------- | ---------------------------------------------------------------------------- |
|
|
55
55
|
| `id` | `string` | — | HTML id attribute |
|
|
56
56
|
| `subLabel` | `string` | — | Secondary label shown below the main label |
|
|
57
|
-
| `size` | `string` | — | `"small"` (px-3 py-2)
|
|
57
|
+
| `size` | `string` | — | `"small"` (px-3 py-2), `"mini"` (p-2) or `"xs"`; default is standard (px-5 py-3). `"xs"` looks 12px everywhere: on phones it keeps 16px text and draws the field at 75%, so iOS Safari does not zoom on focus; from `sm` up it is a plain 12px field. Give it a width through `containerClassName` when it sits in a row |
|
|
58
|
+
| `enterKeyHint` | `string` | — | Label for a phone keyboard's enter key, e.g. `"done"`, `"search"`, `"go"` |
|
|
59
|
+
| `onKeyDown` | `(e) => void` | — | Passed to the `<input>` — e.g. Enter to commit, Escape to cancel |
|
|
58
60
|
| `labelExtra` | `ReactElement` | — | Extra element rendered inline with the label |
|
|
59
61
|
| `infoElement` | `ReactElement` | — | Content shown in an InfoWindow tooltip next to the label |
|
|
60
62
|
| `customElement` | `ReactElement` | — | Custom element rendered to the right of the label row |
|
|
@@ -18,6 +18,7 @@ export const TextInput = () => {
|
|
|
18
18
|
|
|
19
19
|
const [email, setEmail] = useState("");
|
|
20
20
|
const [password, setPassword] = useState("");
|
|
21
|
+
const [xsValue, setXsValue] = useState("");
|
|
21
22
|
const [number, setNumber] = useState("");
|
|
22
23
|
const [mobile, setMobile] = useState("");
|
|
23
24
|
const [capText, setCapText] = useState("");
|
|
@@ -51,6 +52,32 @@ export const TextInput = () => {
|
|
|
51
52
|
</div>
|
|
52
53
|
</StorySection>
|
|
53
54
|
|
|
55
|
+
<StorySection
|
|
56
|
+
title="Extra small (xs)"
|
|
57
|
+
description="size='xs' looks 12px everywhere. On a phone it keeps 16px text and draws the field at 75%, so iOS Safari does not zoom when it is focused; from sm up it is a plain 12px field. enterKeyHint='done' labels the phone keyboard's enter key, and onKeyDown handles Enter here."
|
|
58
|
+
>
|
|
59
|
+
<form
|
|
60
|
+
className="flex items-center gap-2"
|
|
61
|
+
onSubmit={(event) => {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
event.stopPropagation();
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
<TextInputImpl
|
|
67
|
+
id="xs-input"
|
|
68
|
+
value={xsValue}
|
|
69
|
+
type="text"
|
|
70
|
+
label=""
|
|
71
|
+
size="xs"
|
|
72
|
+
placeholder="View name"
|
|
73
|
+
enterKeyHint="done"
|
|
74
|
+
containerClassName="w-32 sm:w-44"
|
|
75
|
+
onChange={(e) => setXsValue(e.target.value)}
|
|
76
|
+
/>
|
|
77
|
+
<span className="text-xs text-gray-500">Compact, no iOS zoom</span>
|
|
78
|
+
</form>
|
|
79
|
+
</StorySection>
|
|
80
|
+
|
|
54
81
|
<StorySection
|
|
55
82
|
title="Password (show/hide toggle)"
|
|
56
83
|
description="type='password' renders an eye icon to toggle visibility. Validates 6–30 characters, no spaces."
|
|
@@ -127,6 +127,10 @@ const navigation = [
|
|
|
127
127
|
| `dashboardContentClassName` | `string` | — | Tailwind classes applied to the main content `<main>` element |
|
|
128
128
|
| `linkType` | `"react" \| "gatsby" \| "next" \| "external" \| "default"` | — | Router adapter passed through to nav links |
|
|
129
129
|
| `custom` | `any` | — | Custom link component used when `linkType` is `"react"`, `"gatsby"`, or `"next"` |
|
|
130
|
+
| `compactMode` | `boolean` | `false` | Tighter rows, smaller icons and a smaller expand chevron, keeping every label |
|
|
131
|
+
| `searchable` | `boolean` | `false` | Shows a filter box. Matches are listed flat, each with the path it sits under (e.g. `Sales › Reports`) |
|
|
132
|
+
| `persistKey` | `string` | — | localStorage key that remembers open groups and pages. A new key loads that key's stored state. |
|
|
133
|
+
| `defaultExpandedIds` | `string[]` | — | Groups (by `groupId`) and pages (by `id`) open when nothing is stored for them |
|
|
130
134
|
|
|
131
135
|
##### Navigation
|
|
132
136
|
|
|
@@ -137,6 +141,7 @@ const navigation = [
|
|
|
137
141
|
| `href` | `string` | URL used when `handleBarsOnClick` is not provided |
|
|
138
142
|
| `icon` | `any` | Heroicon or SVG component rendered beside the label |
|
|
139
143
|
| `groupId` | `string` | Optional — items sharing the same `groupId` are nested under a collapsible group labelled with that string |
|
|
144
|
+
| `parentId` | `string` | Optional — `id` of the item this one nests under. A row with children shows a chevron and, with nothing stored, opens when it holds `selectedId`. Rows are keyed by `id`, so names may repeat. |
|
|
140
145
|
|
|
141
146
|
##### Transition
|
|
142
147
|
|
|
@@ -77,6 +77,21 @@ const twoGroupNavigation = [
|
|
|
77
77
|
{ id: "vol3", name: "Volume 3", href: "/j/3", icon: FolderIcon, groupId: "Journals" },
|
|
78
78
|
];
|
|
79
79
|
|
|
80
|
+
// INFO: the shape Space builds — workspace rows, group rows under them, then
|
|
81
|
+
// spaces and sub-pages, all through parentId. "Reports" and "Charts" repeat
|
|
82
|
+
// across workspaces on purpose.
|
|
83
|
+
const workspaceNavigation = [
|
|
84
|
+
{ id: "ws-sales", name: "Sales", href: "/ws/sales", icon: UsersIcon },
|
|
85
|
+
{ id: "sales-reports", name: "Reports", href: "/g/sales-reports", icon: FolderIcon, parentId: "ws-sales" },
|
|
86
|
+
{ id: "sales-charts", name: "Charts", href: "/s/sales-charts", icon: FolderIcon, parentId: "sales-reports" },
|
|
87
|
+
{ id: "sales-q3", name: "Q3 Review", href: "/s/sales-q3", icon: FolderIcon, parentId: "sales-charts" },
|
|
88
|
+
{ id: "ws-marketing", name: "Marketing", href: "/ws/marketing", icon: UsersIcon },
|
|
89
|
+
{ id: "marketing-reports", name: "Reports", href: "/g/marketing-reports", icon: FolderIcon, parentId: "ws-marketing" },
|
|
90
|
+
{ id: "marketing-charts", name: "Charts", href: "/s/marketing-charts", icon: FolderIcon, parentId: "marketing-reports" },
|
|
91
|
+
{ id: "ws-ops", name: "Operations", href: "/ws/ops", icon: UsersIcon },
|
|
92
|
+
{ id: "ops-charts", name: "Charts", href: "/s/ops-charts", icon: FolderIcon, parentId: "ws-ops" },
|
|
93
|
+
];
|
|
94
|
+
|
|
80
95
|
const renderLogo = (collapse?: boolean) => (
|
|
81
96
|
<div className="flex items-center flex-shrink-0 px-6 cursor-pointer">
|
|
82
97
|
<img
|
|
@@ -108,6 +123,8 @@ export const LeftNav = () => {
|
|
|
108
123
|
// INFO: starts on an item inside a group, so the story shows the group already
|
|
109
124
|
// open on mount — the case a route change reproduces in a real app
|
|
110
125
|
const [groupedSelectedId, setGroupedSelectedId] = useState("engineering");
|
|
126
|
+
// INFO: starts three levels down, so the story shows its branch open on mount
|
|
127
|
+
const [workspaceSelectedId, setWorkspaceSelectedId] = useState("sales-q3");
|
|
111
128
|
|
|
112
129
|
return (
|
|
113
130
|
<>
|
|
@@ -298,6 +315,27 @@ export const LeftNav = () => {
|
|
|
298
315
|
</div>
|
|
299
316
|
</StorySection>
|
|
300
317
|
|
|
318
|
+
<StorySection
|
|
319
|
+
title="Workspace Tree"
|
|
320
|
+
description="Workspaces, groups and pages nested through parentId, in compact mode with search — the shape Blixify Space builds. The selected page (Sales › Reports › Charts › Q3 Review) starts with its whole branch open; other workspaces start closed, and every row with children shows a chevron. 'Reports' and 'Charts' repeat across workspaces: rows are keyed by id, so they never collide. Type 'charts' — each match shows the path it came from — then clear the box and the tree comes back exactly as it was."
|
|
321
|
+
>
|
|
322
|
+
<div className="w-full h-96 relative overflow-hidden border rounded-lg">
|
|
323
|
+
<LeftNavImpl
|
|
324
|
+
selectedId={workspaceSelectedId}
|
|
325
|
+
navigation={workspaceNavigation}
|
|
326
|
+
leftNavMobileMenu={false}
|
|
327
|
+
compactMode={true}
|
|
328
|
+
searchable={true}
|
|
329
|
+
searchPlaceholder="Filter spaces"
|
|
330
|
+
handleBarsOnClick={(id) => setWorkspaceSelectedId(id)}
|
|
331
|
+
handleToggleLeftNavMobileMenu={() => {}}
|
|
332
|
+
renderDashboardLogo={() => renderLogo()}
|
|
333
|
+
renderDashboardContent={renderContent}
|
|
334
|
+
renderDashboardHeaderContent={renderHeader}
|
|
335
|
+
/>
|
|
336
|
+
</div>
|
|
337
|
+
</StorySection>
|
|
338
|
+
|
|
301
339
|
<StorySection
|
|
302
340
|
title="Persistent Expand State"
|
|
303
341
|
description="persistKey names a localStorage entry that remembers which groups and pages are open. Expand a group, reload the page, and it is still open — without it expansion lasts one mount only, so anything opened is shut again by the next navigation. Groups are keyed by groupId and pages by id. Omit persistKey and no storage is touched at all."
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getBackendComponent.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/getBackendComponent.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"getBackendComponent.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/getBackendComponent.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAkExB,eAAO,MAAM,uBAAuB;;;;;;;;;;CA6DnC,CAAC"}
|