blixify-ui-web-mcp 0.1.54 → 0.1.56

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.
@@ -4,13 +4,13 @@
4
4
 
5
5
  <hr>
6
6
 
7
- A floating chat button fixed to the viewport. Clicking it toggles the `ChatBase` panel open/closed with an animated transition. On mobile (< 640px) the chat panel expands to full screen when open.
7
+ A floating chat button fixed to the viewport. Clicking it toggles the `ChatBase` panel open/closed with an animated transition. On mobile (< 640px) the chat panel expands to full screen by default. Enable `resizableHeight` to open a full-width, bottom-aligned panel with a touch-friendly height handle instead. Desktop keeps the floating panel.
8
8
 
9
- The button can be dragged out of the way on desktop and touch, and where it is dropped is kept in `localStorage` so it stays there on the next visit. A short drag is still treated as a click, so tapping always opens the chat. A saved position is clamped back into view when it is restored on a smaller screen or the window is resized under it, and dragging is disabled while the panel is full screen on mobile.
9
+ The button can be dragged out of the way on desktop and touch, and where it is dropped is kept in `localStorage` so it stays there on the next visit. A short drag is still treated as a click, so tapping always opens the chat. A saved position is clamped back into view when it is restored on a smaller screen or the window is resized under it, and button dragging is disabled while the chat is open on mobile.
10
10
 
11
11
  With `resizableWidth`, an open panel gains a grab handle on its left edge — dragging it left or right resizes the panel to any width. The only bounds are physical: it cannot be dragged wider than the viewport, nor narrower than 280px, below which the header, messages and input stop being usable. The chosen width is kept in `localStorage`, re-clamped to the viewport on window resize, and the handle is hidden while the panel is full screen on mobile.
12
12
 
13
- `resizableHeight` works the same way on the vertical axis: an open panel gains a grab handle on its top edge, and dragging it up or down resizes the panel to any height. Because the panel grows upward from the bottom-right anchor, the cap is the distance from its bottom edge to the top of the viewport, and the floor keeps the header and input row usable. The default height is three quarters of the viewport (matching the panel's `sm:h-[75vh]`). Width and height are independent — turn on either or both. The chosen height is kept in `localStorage` and re-clamped on window resize.
13
+ `resizableHeight` works the same way on the vertical axis: an open panel gains a grab handle on its top edge, and dragging it up or down resizes the panel to any height. Because the panel grows upward from the bottom-right anchor, the cap is the distance from its bottom edge to the top of the viewport, and the floor keeps the header and input row usable. The default height is three quarters of the viewport (matching the panel's `sm:h-[75vh]`). Width and height are independent — turn on either or both. The chosen height is kept in `localStorage` and re-clamped on window resize. On mobile, this enables a bottom-aligned panel with a larger top handle; see Mobile height resizing below.
14
14
 
15
15
  | Prop | Type | Default | Description |
16
16
  | -------------------- | --------- | -------------------------------- | ------------------------------------------------------------------------------------------- |
@@ -66,6 +66,36 @@ return (
66
66
  );
67
67
  ```
68
68
 
69
+ ##### Mobile height resizing
70
+
71
+ ```tsx
72
+ <ChatAnchor
73
+ {...chatProps}
74
+ resizableHeight
75
+ open={open}
76
+ onClick={() => setOpen(!open)}
77
+ onClose={() => setOpen(false)}
78
+ />
79
+ ```
80
+
81
+ On mobile, the chat is pinned to the bottom at full width. Drag the top handle up to extend it to the viewport height, or down to shrink it to 50% of the current viewport height. The initial height is 75% of the viewport unless `defaultHeight` or a saved height overrides it. The same `heightStorageKey` stores height on desktop and mobile, and restored heights are clamped to the available screen. Arrow Up/Down on the focused handle also adjusts height.
82
+
83
+ Your `headerElement` renders beneath the handle. Messages scroll above the composer. The chat close button calls `onClose`, falling back to `onClick`; if `hideAdditionalAction` hides that button, a close button appears beside the handle. The page remains accessible above the panel. Mobile sizing ignores `positionOffset`, saved button positions, `sizeClassName`, and `disableMobileResize` while open; the closed button keeps its saved position. Width resizing stays desktop-only. Without `resizableHeight`, mobile keeps its fullscreen layout. No `mobileView` prop or Drawer is needed.
84
+
85
+ ##### Keyboard and safe areas
86
+
87
+ On mobile, opening the keyboard temporarily fits the chat to the visible viewport and disables the outer height resize handle. The preferred height is preserved and restored when the keyboard closes; keyboard adjustments are never saved to `heightStorageKey`. If a keyboard interrupts an unfinished height drag, that drag is cancelled. Message scrolling and the composer remain available.
88
+
89
+ Browsers detect the keyboard from input focus within the chat and a substantial viewport height reduction. Pinch zoom and small browser toolbar changes do not activate this detection. `visualViewport` resize/scroll events position the panel above an overlay keyboard; window resize events also support WebViews that resize their layout viewport. Native hosts can override the detection:
90
+
91
+ ```tsx
92
+ <ChatAnchor {...chatProps} resizableHeight keyboardOpen={keyboardVisible} />
93
+ ```
94
+
95
+ Omit `keyboardOpen` for automatic detection. Supply `true`/`false` from the app's Capacitor keyboard events when available. This prop supplies visibility, not keyboard dimensions: the host must still configure its WebView to resize or expose a reduced visual viewport when the keyboard opens. No Capacitor plugin is required by ui-web.
96
+
97
+ The mobile panel respects `--safe-area-inset-*` CSS variables, falling back to `env(safe-area-inset-*, 0px)`. Bottom padding is omitted while the keyboard is open; top spacing protects the drag handle and header when the panel reaches the top of the visible viewport. Provide `viewport-fit=cover` and the relevant safe-area values in the host app. The 50–100% range applies to the keyboard-closed viewport; fitting the visible space takes priority while typing.
98
+
69
99
  ##### Required props
70
100
 
71
101
  | Name | Type | Description |
@@ -80,6 +110,7 @@ return (
80
110
 
81
111
  | Name | Type | Default | Description |
82
112
  | ---------------- | ----------------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------- |
113
+ | `keyboardOpen` | `boolean` | Automatic detection | Optional native keyboard visibility override. Temporarily fits mobile chat and disables outer height resizing while open. |
83
114
  | `positionOffset` | `string` | `"bottom-[88px] right-4 md:bottom-6 md:right-6"` | Tailwind classes that control the fixed position of the anchor. On phones the default rests on the top edge of a bottom bar such as DataTemplate's Quick find, clear of its filter button. Ignored on mobile when open. |
84
115
  | `greetingCard` | `boolean` | `false` | When `true`, shows a greeting bubble next to the anchor button while the chat is closed. |
85
116
  | `greetingText` | `string` | `"Welcome! How can I help you today?"` | Text displayed inside the greeting card bubble. |
@@ -198,3 +198,73 @@ export const Resizable = () => {
198
198
  </div>
199
199
  );
200
200
  };
201
+
202
+ export const CustomHeader = () => {
203
+ const [open, setOpen] = useState(false);
204
+ const [message, setMessage] = useState("");
205
+ const currentUser = { id: "1", name: "Guest" };
206
+ const botUser = { id: "2", name: "Support" };
207
+ const [conversation, setConversation] = useState<Conversation>({
208
+ id: "custom-header",
209
+ members: [currentUser, botUser],
210
+ messageList: [
211
+ "Hi! Welcome to Blixify support. How can we help today?",
212
+ "Can I customize the chat to match my brand?",
213
+ "Absolutely. Your header can include your logo, team details, and availability.",
214
+ "Will the same header work on mobile?",
215
+ "Yes! Open this example at a mobile width to see how it adapts.",
216
+ ].map((messageBody, index) => ({
217
+ sender: index % 2 ? currentUser : botUser,
218
+ messageBody,
219
+ createdAt: new Date("2026-10-16T00:00:00Z"),
220
+ })),
221
+ });
222
+ const handleToggleChat = () => setOpen((previous) => !previous);
223
+ const handleSendMessage = (nextConversation: Conversation) => {
224
+ setConversation(nextConversation);
225
+ setMessage("");
226
+ };
227
+
228
+ return (
229
+ <div className="min-h-screen p-6 bg-gray-50">
230
+ <p>
231
+ Open chat to see a custom branded header with a team avatar, availability,
232
+ and response time. The same header adapts to desktop and mobile.
233
+ </p>
234
+ <ChatAnchorImpl
235
+ resizableHeight
236
+ headerElement={
237
+ <div className="bg-gradient-to-br from-indigo-900 to-indigo-700 text-white">
238
+ <div className="flex items-center gap-3 py-5 pl-4 pr-24">
239
+ <div className="relative flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-white/15 text-lg font-semibold ring-1 ring-white/20">
240
+ B
241
+ <span className="absolute -bottom-0.5 -right-0.5 h-3 w-3 rounded-full bg-indigo-400 ring-2 ring-indigo-900" />
242
+ </div>
243
+ <div className="min-w-0">
244
+ <p className="text-xs font-medium uppercase tracking-wider text-indigo-200">
245
+ Customer care
246
+ </p>
247
+ <h2 className="text-base font-semibold">Blixify support</h2>
248
+ </div>
249
+ </div>
250
+ <div className="flex flex-wrap items-center gap-x-3 gap-y-1 border-t border-white/15 bg-black/10 px-4 py-3 text-xs">
251
+ <span className="flex items-center gap-1.5 font-medium">
252
+ <span className="h-1.5 w-1.5 rounded-full bg-indigo-300" />
253
+ Online now
254
+ </span>
255
+ <span className="text-indigo-100">Usually replies in a few minutes</span>
256
+ </div>
257
+ </div>
258
+ }
259
+ open={open}
260
+ value={message}
261
+ currentUser={currentUser}
262
+ conversation={conversation}
263
+ onClick={handleToggleChat}
264
+ onClose={() => setOpen(false)}
265
+ onChangeMessage={(event) => setMessage(event.target.value)}
266
+ onSendMessage={handleSendMessage}
267
+ />
268
+ </div>
269
+ );
270
+ };
@@ -1173,7 +1173,7 @@ Which field types can be edited from the list table, and how. Types not listed u
1173
1173
  | `onChangeSort` | `(value: Sort) => void` | Called when the user changes the sort |
1174
1174
  | `onChangeHide` | `(value: DataStructure[], change: HideChange) => void` | Called when a column is hidden or shown from the view settings panel. Providing it switches the panel to schema mode — the hide is written to the model for everyone instead of being this user's own. `change` is `{ columnId, hide }` for one column or `{ showAll: true }`; persist that rather than `value` |
1175
1175
  | `onChangeDrag` | `(dragId: string, dropId: string, newHeader: TableHeader[]) => void` | Called after a column is reordered in the view settings panel, so the consumer can persist the order |
1176
- | `onChangeTabs` | `(value: ExtendedTabs[]) => void` | Called when tabs are added/removed/reordered |
1176
+ | `onChangeTabs` | `(value: ExtendedTabs[]) => void` | Called when tabs are added, edited (the pencil on a tab), removed or reordered |
1177
1177
  | `onChangePagination` | `(page: number) => void` | Called when the page changes |
1178
1178
  | `customPaginationCount` | `(pageDataCount: number) => void` | Called after each data fetch with the actual record count. Use this to override the count shown in the pagination bar — store the value in your own state and pass it back via `customPaginationSize` on `Table`, `Grid`, `List`, or `Pagination` if needed, or use it to display a custom count elsewhere in your UI |
1179
1179
  | `onAddColumns` | `(model: DataStructure[]) => void` | Called when a new column is added |
@@ -1271,7 +1271,7 @@ Injects custom React components and callbacks into the list, read, and update vi
1271
1271
  | `bareSaveOnTabChange` | `boolean` | Auto-save: every Next / Previous on a tabbed update form writes a draft first, silently (no toast). Pair with `edit.enableUpdateDraft` |
1272
1272
  | `bareUpsertPartialUpdate` | `boolean` | Upsert by `props.id` and send only the fields that changed since load; a save with no changes skips the write. Makes auto-save safe across devices |
1273
1273
  | `bareUpdateActionComponent` | `(data: any, formRef?: any, rawData?: any) => any` | Custom action buttons in the form footer. `rawData` is the stored doc as loaded (undefined for a new record) |
1274
- | `bareFlexFilters` | `boolean` | Funnel icon opens the Filters builder (column · condition · value, values from the schema) — replaces hand-rolled page filters; new table only |
1274
+ | `bareFlexFilters` | `boolean` | Funnel icon opens the Filters builder (column · condition · value, values from the schema) — replaces hand-rolled page filters; date columns also take a Relative Date (today, this week, this month, last 7 or 30 days, next 7, 30 or 90 days, before today; any `last:N` / `next:N` set through the API also works), stored as a word and worked out in the viewer's time zone on each load; new table only |
1275
1275
  | `bareOnFlexFiltersChange` | `(flexFilters: FlexFilterData[]) => void` | Fires when the applied (debounced) flex filters change — from user panel edits or programmatic `handleApplyFlexFilters` / `handleClearFlexFilters`. Lets a parent observe live filter state. Needs `bareFlexFilters` |
1276
1276
  | `bareListFilterAsPopup` | `boolean` | Shows the list's filter inputs in a popup instead of the header row |
1277
1277
  | `bareListTablePinColumns` | `{ left?: string[]; right?: string[] }` | Columns kept sticky at the edges while the table scrolls sideways (`"update"` is the actions column); new table only |
@@ -751,3 +751,85 @@ export const FlatTable = () => {
751
751
  </StorySection>
752
752
  );
753
753
  };
754
+
755
+ // INFO: a row that opens on click, with a link, a checkbox and a button placed
756
+ // in its cells — the e2e checks which of them open the row
757
+ export const RowLinks = () => {
758
+ const [opened, setOpened] = useState<string[]>([]);
759
+ const [ticked, setTicked] = useState(false);
760
+ const rows = [
761
+ {
762
+ id: "row-1",
763
+ name: "Alarm log",
764
+ download: (
765
+ <a href="#downloaded" data-testid="row-download-link">
766
+ Download
767
+ </a>
768
+ ),
769
+ ack: (
770
+ <input
771
+ type="checkbox"
772
+ data-testid="row-checkbox"
773
+ checked={ticked}
774
+ onChange={(e) => setTicked(e.target.checked)}
775
+ />
776
+ ),
777
+ action: (
778
+ <IconButton
779
+ testId="row-icon-button"
780
+ size="small"
781
+ icon={<span className="text-xs">Go</span>}
782
+ onClick={() => {}}
783
+ />
784
+ ),
785
+ },
786
+ ];
787
+ return (
788
+ <StorySection
789
+ title="Links and fields in a row"
790
+ description="A link or form field in a cell does its own action and does not open the row. A button still opens the row unless it stops the click."
791
+ >
792
+ <NewTableImpl
793
+ columns={[
794
+ { key: "name", title: "Name" },
795
+ { key: "download", title: "" },
796
+ { key: "ack", title: "" },
797
+ { key: "action", title: "" },
798
+ ]}
799
+ data={rows}
800
+ size={rows.length}
801
+ limit={10}
802
+ paginationType="page"
803
+ handleUpdatePageIndex={() => {}}
804
+ onClickRow={(id) => setOpened((previous) => [...previous, id])}
805
+ />
806
+ <pre data-testid="row-opened" className="mt-3 text-xs">
807
+ {JSON.stringify(opened)}
808
+ </pre>
809
+ <NewTableImpl
810
+ columns={[
811
+ { key: "name", title: "Name" },
812
+ { key: "preview", title: "" },
813
+ ]}
814
+ data={[
815
+ {
816
+ id: "row-2",
817
+ name: "Open in place",
818
+ preview: (
819
+ <button type="button" data-testid="row-link-button">
820
+ Preview
821
+ </button>
822
+ ),
823
+ },
824
+ ]}
825
+ size={1}
826
+ limit={10}
827
+ paginationType="page"
828
+ handleUpdatePageIndex={() => {}}
829
+ newTabUrl="#record-"
830
+ linkType="default"
831
+ onClickRow={(id) => setOpened((previous) => [...previous, id])}
832
+ />
833
+ </StorySection>
834
+ );
835
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blixify-ui-web-mcp",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
4
4
  "license": "MIT",
5
5
  "main": "lib/mcp/server.js",
6
6
  "bin": {