blixify-ui-web-mcp 0.1.23 → 0.1.25
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/display/detailList/README.md +17 -11
- package/lib/components/display/detailList/detailList.stories.tsx +22 -0
- package/lib/components/display/newList/README.md +32 -0
- package/lib/components/display/newList/newList.stories.tsx +40 -0
- package/lib/components/input/form/README.md +14 -0
- package/lib/components/input/form/form.stories.tsx +15 -3
- package/lib/components/input/markdown/README.md +52 -1
- package/lib/components/input/markdown/markdown.stories.tsx +39 -1
- package/package.json +1 -1
|
@@ -42,17 +42,23 @@ import { DetailList } from "blixify-ui-web/components/display/detailList";
|
|
|
42
42
|
|
|
43
43
|
##### Optional props
|
|
44
44
|
|
|
45
|
-
| Name | Type | Description
|
|
46
|
-
| --------------------- | ------------------------------------------------ |
|
|
47
|
-
| `addTitleElement` | `ReactElement` | Additional react elemnt to be displayed next to title
|
|
48
|
-
| `headerRightElement` | `ReactElement` | Element anchored to the top-right of the header, opposite the title/description
|
|
49
|
-
| `description` | `string` |
|
|
50
|
-
| `className` | `tailwind class` |
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
45
|
+
| Name | Type | Description |
|
|
46
|
+
| --------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
47
|
+
| `addTitleElement` | `ReactElement` | Additional react elemnt to be displayed next to title |
|
|
48
|
+
| `headerRightElement` | `ReactElement` | Element anchored to the top-right of the header, opposite the title/description |
|
|
49
|
+
| `description` | `string` | |
|
|
50
|
+
| `className` | `tailwind class` | |
|
|
51
|
+
| `textClassName` | `tailwind class` | Applied to both the label (`dt`) and value (`dd`) text; overrides the default `text-sm` |
|
|
52
|
+
| `darkMode` | `boolean` | Renders with dark-mode colors |
|
|
53
|
+
| `darkModeBg` | `tailwind class` | Overrides the dark-mode background (defaults to `bg-gray-900`); only used when `darkMode` is set |
|
|
54
|
+
| `model` | `Model Data Structure` | Only used in Data Template |
|
|
55
|
+
| `hideModal` | `boolean` | Only used in Data Template |
|
|
56
|
+
| `referenceModel` | `Model Data Structure` | Only used in Data Template |
|
|
57
|
+
| `splitView` | `boolean` | Able to split into two |
|
|
58
|
+
| `size` | `"default" \| "compact"` | `compact` renders label:value on the same row, much denser than the default stacked layout |
|
|
59
|
+
| `valueTextAlign` | `"left" \| "right"` | Compact only. Forces the value alignment at all widths. When omitted, compact keeps its responsive default (right on mobile, left from `sm` up) |
|
|
60
|
+
| `maxHeight` | `string` | When set, the body scrolls with this max height (e.g. `"60vh"`, `"500px"`) |
|
|
61
|
+
| `handleReferenceLink` | `(dataId: string, collectionId: string) => void` | Only used in Data Template, when reference being clicked |
|
|
56
62
|
|
|
57
63
|
##### Detail List class
|
|
58
64
|
|
|
@@ -107,6 +107,28 @@ export const DetailList = () => (
|
|
|
107
107
|
/>
|
|
108
108
|
</StorySection>
|
|
109
109
|
|
|
110
|
+
<StorySection
|
|
111
|
+
title='Order Summary — valueTextAlign="right"'
|
|
112
|
+
description="Compact list with values pinned to the right at all widths. The final row uses a styled node as its text to render the total bigger and bold."
|
|
113
|
+
>
|
|
114
|
+
<DetailListImpl
|
|
115
|
+
title="Order Summary"
|
|
116
|
+
size="compact"
|
|
117
|
+
valueTextAlign="right"
|
|
118
|
+
list={[
|
|
119
|
+
{ title: "Subtotal", text: "RM 1,200.00" },
|
|
120
|
+
{ title: "Shipping", text: "RM 15.00" },
|
|
121
|
+
{ title: "Tax (6%)", text: "RM 72.90" },
|
|
122
|
+
{ title: "Discount", text: "- RM 27.90" },
|
|
123
|
+
{
|
|
124
|
+
title: "Total",
|
|
125
|
+
customTitle: <span className="text-lg font-bold">Total</span>,
|
|
126
|
+
text: <span className="text-lg font-bold">RM 1,260.00</span>,
|
|
127
|
+
},
|
|
128
|
+
]}
|
|
129
|
+
/>
|
|
130
|
+
</StorySection>
|
|
131
|
+
|
|
110
132
|
<StorySection
|
|
111
133
|
title="splitView (legacy) — Two-Column Grid"
|
|
112
134
|
description='Two-up grid layout. Kept for backward compat; new code should prefer size="compact" or groups instead.'
|
|
@@ -22,6 +22,38 @@ This is the **base primitive** for the Batch 3 work. `NewListRow` is exported in
|
|
|
22
22
|
|
|
23
23
|
Do not duplicate the row layout in those components — extend `NewListRow` via `renderExtra` or compose.
|
|
24
24
|
|
|
25
|
+
## Item content
|
|
26
|
+
|
|
27
|
+
| Item field | Rendered as |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `title` | Row title |
|
|
30
|
+
| `description` | String / number: a `<p>`. Element: a `<div>` (block content). Both are truncated to one line per line of content unless `noTruncate` |
|
|
31
|
+
| `tag` / `tags` | Pill(s) next to the title |
|
|
32
|
+
| `date` | Right-aligned date text (see below) |
|
|
33
|
+
| `icon` / `img` | Avatar |
|
|
34
|
+
| `action` | Right-side element |
|
|
35
|
+
| any key in `extraKeys` | Block content under the description, in order |
|
|
36
|
+
|
|
37
|
+
The legacy `List` renders **every** extra key on an item. `NewList` renders only the keys listed in `extraKeys`:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<NewList list={tasks} extraKeys={["details", "activity"]} />
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Date
|
|
44
|
+
|
|
45
|
+
| `date` value | Output |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `Date`, or a string / number `new Date()` parses | `dateFormat(date)` when given, else `date.toLocaleDateString()` (browser locale, no time) |
|
|
48
|
+
| A string `new Date()` cannot parse (e.g. `"18/09/2026 10:00"`) | The string as given |
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
<NewList
|
|
52
|
+
list={tasks}
|
|
53
|
+
dateFormat={(date) => moment(date).format("DD/MM/YYYY HH:mm")}
|
|
54
|
+
/>
|
|
55
|
+
```
|
|
56
|
+
|
|
25
57
|
## Status
|
|
26
58
|
|
|
27
59
|
🚧 **Scaffold only.** Implementation lands in follow-up commits per #1714.
|
|
@@ -219,6 +219,46 @@ export const NewList = () => (
|
|
|
219
219
|
<NewListImpl list={baseLists} darkMode />
|
|
220
220
|
</StorySection>
|
|
221
221
|
|
|
222
|
+
<StorySection
|
|
223
|
+
title="NewList — Extra Keys, Date Format and Rich Description"
|
|
224
|
+
description="extraKeys renders the named item keys (string or element) as block content under the description — the legacy List rendered every extra key, NewList only the ones named. dateFormat customises the date text (here with time); a date string Date cannot parse is shown as given. A non-string description renders as block content in a div, truncated like a string unless noTruncate."
|
|
225
|
+
>
|
|
226
|
+
<NewListImpl
|
|
227
|
+
hidePagination
|
|
228
|
+
extraKeys={["details", "activity"]}
|
|
229
|
+
dateFormat={(date) =>
|
|
230
|
+
`${date.toLocaleDateString()} ${date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`
|
|
231
|
+
}
|
|
232
|
+
list={[
|
|
233
|
+
{
|
|
234
|
+
id: "task-1",
|
|
235
|
+
title: "3D Model — Site A",
|
|
236
|
+
date: new Date(),
|
|
237
|
+
details: "⏱ 42 min · 🖼 318 images",
|
|
238
|
+
activity: (
|
|
239
|
+
<div className="h-1.5 w-40 overflow-hidden rounded-full bg-gray-100">
|
|
240
|
+
<div className="h-full w-2/3 bg-primary-500" />
|
|
241
|
+
</div>
|
|
242
|
+
),
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: "task-2",
|
|
246
|
+
title: "2D Map — Site B",
|
|
247
|
+
date: "18/09/2026 10:00",
|
|
248
|
+
description: (
|
|
249
|
+
<div className="flex gap-2">
|
|
250
|
+
<span className="rounded bg-green-100 px-2 text-xs text-green-800">
|
|
251
|
+
Completed
|
|
252
|
+
</span>
|
|
253
|
+
<span className="text-xs text-gray-500">Ready to download</span>
|
|
254
|
+
</div>
|
|
255
|
+
),
|
|
256
|
+
details: "⏱ 12 min · 🖼 96 images",
|
|
257
|
+
},
|
|
258
|
+
]}
|
|
259
|
+
/>
|
|
260
|
+
</StorySection>
|
|
261
|
+
|
|
222
262
|
<StorySection
|
|
223
263
|
title="Comparing with the legacy List"
|
|
224
264
|
description="Open Display/List in another tab — same sample data renders. NewListItem is exactly ListClass — drop-in data swap. NewListImpl renders the same shape with modernised card styling, optional sticky section headers, and sticky search slot."
|
|
@@ -53,3 +53,17 @@ const passwordRef = useRef(null);
|
|
|
53
53
|
| `handleComplete` | `(isDraft?: boolean) => void` | — | Called when all inputs pass validation. `isDraft=true` when triggered via `handleSave()` |
|
|
54
54
|
| `handleOnFailValidation` | `(ids: string[]) => void` | — | Called when one or more inputs fail. Receives an array of the failed input `id` values |
|
|
55
55
|
| `disabledOnEnter` | `boolean` | `false` | When `true`, pressing Enter does not trigger form submission |
|
|
56
|
+
|
|
57
|
+
### Enter to submit
|
|
58
|
+
|
|
59
|
+
Pressing Enter calls `handleSubmit()` when the key event comes from inside this `<form>` — including inputs a child renders through a portal. Enter pressed anywhere else on the page, or inside another `Form`, does not submit this one.
|
|
60
|
+
|
|
61
|
+
Enter does not submit when:
|
|
62
|
+
|
|
63
|
+
| Condition | |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `disabledOnEnter` is `true` | set it for forms with a textarea or rich text field that needs Enter for a newline |
|
|
66
|
+
| Shift is held | |
|
|
67
|
+
| an IME composition is in progress (`isComposing`, or `keyCode` 229) | |
|
|
68
|
+
| the key is auto-repeating (`repeat`) | |
|
|
69
|
+
| the focused element is a `button` | |
|
|
@@ -6,19 +6,21 @@ import { RadioList } from "../../action/radioList";
|
|
|
6
6
|
import { StorySection } from "../../storyUtils";
|
|
7
7
|
import { TextInput } from "../textInput";
|
|
8
8
|
import { UploadInput } from "../uploadInput";
|
|
9
|
+
import { TextArea } from "../textArea";
|
|
9
10
|
|
|
10
11
|
const meta = { title: "Input/Form" };
|
|
11
12
|
export default meta;
|
|
12
13
|
|
|
13
|
-
|
|
14
14
|
export const Form = () => {
|
|
15
15
|
const formRef = useRef<any>(null);
|
|
16
16
|
const input1 = useRef<any>(null);
|
|
17
17
|
const input2 = useRef<any>(null);
|
|
18
|
+
const input3 = useRef<any>(null);
|
|
18
19
|
const uploadRef = useRef<any>(null);
|
|
19
20
|
|
|
20
21
|
const [radioValue, setRadioValue] = useState("");
|
|
21
22
|
const [textValue, setTextValue] = useState("");
|
|
23
|
+
const [textAreaValue, setTextAreaValue] = useState("");
|
|
22
24
|
const [file, setFile] = useState<any>(undefined);
|
|
23
25
|
const [result, setResult] = useState<string | null>(null);
|
|
24
26
|
|
|
@@ -38,7 +40,7 @@ export const Form = () => {
|
|
|
38
40
|
<div className="w-full max-w-sm">
|
|
39
41
|
<FormImpl
|
|
40
42
|
ref={formRef}
|
|
41
|
-
refList={[input1, input2, uploadRef]}
|
|
43
|
+
refList={[input1, input2, input3, uploadRef]}
|
|
42
44
|
handleComplete={() => setResult("All valid — form submitted!")}
|
|
43
45
|
>
|
|
44
46
|
<RadioList
|
|
@@ -61,6 +63,16 @@ export const Form = () => {
|
|
|
61
63
|
containerClassName="mt-3"
|
|
62
64
|
onChange={(e) => setTextValue(e.target.value)}
|
|
63
65
|
/>
|
|
66
|
+
<TextArea
|
|
67
|
+
ref={input3}
|
|
68
|
+
id="remarks"
|
|
69
|
+
value={textAreaValue}
|
|
70
|
+
rows={3}
|
|
71
|
+
label="Remarks"
|
|
72
|
+
placeholder="Remarks"
|
|
73
|
+
containerClassName="mt-3"
|
|
74
|
+
onChange={(e) => setTextAreaValue(e.target.value)}
|
|
75
|
+
/>
|
|
64
76
|
<UploadInput
|
|
65
77
|
ref={uploadRef}
|
|
66
78
|
id="file-upload"
|
|
@@ -134,4 +146,4 @@ export const Form = () => {
|
|
|
134
146
|
</StorySection>
|
|
135
147
|
</>
|
|
136
148
|
);
|
|
137
|
-
}
|
|
149
|
+
};
|
|
@@ -160,7 +160,7 @@ On error the editor draws a red outline (`border border-red-500 rounded`) — th
|
|
|
160
160
|
| `enableImageUpload` | `boolean` | `false` | Enable image upload functionality (drag-and-drop, paste, click to upload) |
|
|
161
161
|
| `enableVideoUpload` | `boolean` | `false` | Enable video upload functionality (supports MP4, MOV, WebM, AVI, MKV, etc.) |
|
|
162
162
|
| `compress` | `{ maxFileSize?: number, fileQuality?: number}` | `null` | Image compression settings. `maxFileSize` in MB (default: 30), `fileQuality` 0.0-1.0 (default: 0.8) |
|
|
163
|
-
| `lib` | `{ axios
|
|
163
|
+
| `lib` | `{ axios?: any, mermaid?: () => Promise<any> }` | `null` | External libraries — axios for API calls; `mermaid` loader (e.g. `() => import("mermaid")`) to draw diagrams |
|
|
164
164
|
| `uploadEndpoint` | `string` | `null` | API endpoint for uploading media files |
|
|
165
165
|
|
|
166
166
|
##### Ref Methods
|
|
@@ -209,6 +209,57 @@ commands. `onSelect` receives `insertEmbed`, and may await first — the host
|
|
|
209
209
|
usually has to create the space before it has an id. Embeds survive the round
|
|
210
210
|
trip back to markdown as the same `div`, the way an inline `<video>` does.
|
|
211
211
|
|
|
212
|
+
##### Embed view — grid or list
|
|
213
|
+
|
|
214
|
+
Each embed carries a `view` of `grid` (default) or `list`. It is stored on the
|
|
215
|
+
marker only when it is `list`:
|
|
216
|
+
|
|
217
|
+
```html
|
|
218
|
+
<div data-space-id="guides"></div> <!-- grid -->
|
|
219
|
+
<div data-space-id="guides" data-view="list"></div> <!-- list -->
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Grid writes no attribute at all, so documents saved before views existed
|
|
223
|
+
serialise byte-for-byte as they did. Anything other than exactly `list` reads as
|
|
224
|
+
grid. A host that parses these markers itself must accept the optional
|
|
225
|
+
`data-view`.
|
|
226
|
+
|
|
227
|
+
`renderEmbed` receives `view` and `onChangeView(view)` alongside `onRemove`, so
|
|
228
|
+
the host decides what a list row looks like and where its toggle sits. The node
|
|
229
|
+
view wrapper also carries `data-view` for styling. A new embed copies the view
|
|
230
|
+
of the embed directly above it, so a list stays a list as items are added.
|
|
231
|
+
|
|
232
|
+
##### Mermaid diagrams
|
|
233
|
+
|
|
234
|
+
Pass a loader and fenced ```` ```mermaid ```` blocks draw as diagrams — in the
|
|
235
|
+
editor as a live preview under the source, and in `MarkdownStatic` in place of
|
|
236
|
+
the block. The `/` menu gains **Diagram**, which inserts a starter flowchart.
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
<MarkdownInput lib={{ axios, mermaid: () => import("mermaid") }} />
|
|
240
|
+
<MarkdownStatic label="" value={body} lib={{ mermaid: () => import("mermaid") }} />
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
- **The host supplies mermaid.** This library compiles to CommonJS, so importing
|
|
244
|
+
mermaid here would bundle it into every app with a markdown field. Passed in,
|
|
245
|
+
the host's bundler splits it into a chunk fetched only by pages with a diagram.
|
|
246
|
+
Without a loader the block shows as ordinary code and Diagram is not offered.
|
|
247
|
+
- **`securityLevel: "strict"` is always set.** Documents can be published to
|
|
248
|
+
anyone with a link; strict keeps mermaid sanitising labels and refusing click
|
|
249
|
+
handlers.
|
|
250
|
+
- **Html labels keep inline formatting; `<img>` is stripped.** `<b>`, `<i>`,
|
|
251
|
+
`<u>`, `<sup>` and `<br>` draw as formatting. Strict still keeps sanitised
|
|
252
|
+
tags, so `dompurifyConfig.FORBID_TAGS: ["img"]` removes images, which would
|
|
253
|
+
otherwise load a remote file for every reader.
|
|
254
|
+
- **Entity codes such as `#quot;` render garbled** in mermaid 11.17.2 with
|
|
255
|
+
either api (`render` or `run`). Write the character directly instead.
|
|
256
|
+
- **`suppressErrorRendering: true`.** Without it mermaid appends its own error
|
|
257
|
+
graphic to `document.body` on a syntax error, left behind below the page.
|
|
258
|
+
- **A broken diagram never breaks the page.** It shows a message; in
|
|
259
|
+
`MarkdownStatic` the source stays visible under it.
|
|
260
|
+
- **The source is what is stored**, as a normal fenced block, so search and
|
|
261
|
+
anything reading the raw markdown still see the diagram's text.
|
|
262
|
+
|
|
212
263
|
##### The slash menu
|
|
213
264
|
|
|
214
265
|
Only a real edit opens it. The detection also runs on selection changes so that
|
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
|
-
import { MarkdownInput as MarkdownInputImpl } from ".";
|
|
2
|
+
import { MarkdownInput as MarkdownInputImpl, MarkdownStatic } from ".";
|
|
3
3
|
import { StorySection } from "../../storyUtils";
|
|
4
4
|
|
|
5
5
|
const meta = { title: "Input/Markdown Input" };
|
|
6
6
|
export default meta;
|
|
7
7
|
|
|
8
|
+
// INFO: the host passes mermaid in; the library never imports it
|
|
9
|
+
const handleLoadMermaid = () => import("mermaid");
|
|
10
|
+
|
|
11
|
+
const DIAGRAM_SAMPLE = [
|
|
12
|
+
"Pick Diagram from the / menu to add another.",
|
|
13
|
+
"",
|
|
14
|
+
"```mermaid",
|
|
15
|
+
"flowchart LR",
|
|
16
|
+
" A[<b>Draft</b>] --> B[Review<br/>by <i>owner</i>]",
|
|
17
|
+
" B --> C[Published]",
|
|
18
|
+
"```",
|
|
19
|
+
].join("\n");
|
|
20
|
+
|
|
8
21
|
export const MarkdownInput = () => {
|
|
9
22
|
const [value, setValue] = useState<string | undefined>("");
|
|
10
23
|
const [noteValue, setNoteValue] = useState<string | undefined>(
|
|
11
24
|
'A page can hold a live database, not just a link to one.\n\n<div data-space-id="orders"></div>\n\nType / on any line and pick Database to add another.',
|
|
12
25
|
);
|
|
26
|
+
const [diagramValue, setDiagramValue] = useState<string | undefined>(
|
|
27
|
+
DIAGRAM_SAMPLE,
|
|
28
|
+
);
|
|
13
29
|
const handleOnChange = (id: string, value?: string) => setValue(value);
|
|
30
|
+
const handleOnDiagramChange = (id: string, value?: string) =>
|
|
31
|
+
setDiagramValue(value);
|
|
14
32
|
const handleOnNoteChange = (id: string, value?: string) =>
|
|
15
33
|
setNoteValue(value);
|
|
16
34
|
|
|
@@ -88,6 +106,26 @@ export const MarkdownInput = () => {
|
|
|
88
106
|
{noteValue}
|
|
89
107
|
</pre>
|
|
90
108
|
</StorySection>
|
|
109
|
+
|
|
110
|
+
<StorySection
|
|
111
|
+
title="Mermaid diagrams"
|
|
112
|
+
description="lib.mermaid given, so ```mermaid blocks draw as diagrams — under the source while editing, in place of it in MarkdownStatic. Inline tags such as <b>, <i> and <br> format the label; an <img> is stripped."
|
|
113
|
+
>
|
|
114
|
+
<div className="grid w-full gap-6 md:grid-cols-2">
|
|
115
|
+
<MarkdownInputImpl
|
|
116
|
+
id="diagram"
|
|
117
|
+
value={diagramValue ?? ""}
|
|
118
|
+
onChange={handleOnDiagramChange}
|
|
119
|
+
borderless={true}
|
|
120
|
+
lib={{ mermaid: handleLoadMermaid }}
|
|
121
|
+
/>
|
|
122
|
+
<MarkdownStatic
|
|
123
|
+
label="MarkdownStatic"
|
|
124
|
+
value={diagramValue ?? ""}
|
|
125
|
+
lib={{ mermaid: handleLoadMermaid }}
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
</StorySection>
|
|
91
129
|
</>
|
|
92
130
|
);
|
|
93
131
|
};
|