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.
@@ -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
- | `model` | `Model Data Structure` | Only used in Data Template |
52
- | `hideModal` | `boolean` | Only used in Data Template |
53
- | `referenceModel` | `Model Data Structure` | Only used in Data Template |
54
- | `splitView` | `boolean` | Able to split into two |
55
- | `handleReferenceLink` | `(dataId: string, collectionId: string) => void` | Only used in Data Template, when reference being clicked |
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: any }` | `null` | External libraries (e.g., axios for API calls) |
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blixify-ui-web-mcp",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "license": "MIT",
5
5
  "main": "lib/mcp/server.js",
6
6
  "bin": {