torch-glare-mcp 1.4.0 → 1.6.0
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/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/docs/components/context-menu.md +30 -0
- package/docs/components/data-views-config-panel.md +3 -3
- package/docs/components/data-views-layout.md +9 -2
- package/docs/components/dropdown-menu.md +28 -0
- package/docs/components/header-bar.md +181 -0
- package/docs/components/searchable-table.md +44 -30
- package/docs/components/section-block.md +118 -0
- package/docs/components/tab-switch.md +163 -0
- package/docs/how-to/data-views-from-backend-response.md +1 -0
- package/docs/llms-manifest.json +15 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,9 +8,13 @@
|
|
|
8
8
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
10
|
import { z } from "zod";
|
|
11
|
+
import { createRequire } from "node:module";
|
|
11
12
|
import { DocsLoader } from "./docs-loader.js";
|
|
12
13
|
import { ComponentRegistry } from "./component-registry.js";
|
|
13
14
|
import { extractSection, extractCodeExamples } from "./markdown-utils.js";
|
|
15
|
+
// Read the server version from package.json so the MCP handshake version never
|
|
16
|
+
// drifts from the published package version. dist/index.js → ../package.json.
|
|
17
|
+
const { version: SERVER_VERSION } = createRequire(import.meta.url)("../package.json");
|
|
14
18
|
/**
|
|
15
19
|
* Absolute project rules prepended to every code/docs response so any AI
|
|
16
20
|
* assistant using this MCP server sees them, even without a companion skill.
|
|
@@ -40,7 +44,7 @@ async function main() {
|
|
|
40
44
|
// 3. Create MCP server
|
|
41
45
|
const server = new McpServer({
|
|
42
46
|
name: "torch-glare-docs",
|
|
43
|
-
version:
|
|
47
|
+
version: SERVER_VERSION,
|
|
44
48
|
});
|
|
45
49
|
// ─── TOOLS ───────────────────────────────────────────────────────────
|
|
46
50
|
// Tool 1: List components
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1E;;;;GAIG;AACH,MAAM,YAAY,GAAG;;;;;;;;;;;;;CAapB,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,wCAAwC;IACxC,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;IAChC,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IAEvB,8BAA8B;IAC9B,MAAM,QAAQ,GAAG,IAAI,iBAAiB,EAAE,CAAC;IACzC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAElD,uBAAuB;IACvB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,kBAAkB;QACxB,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -233,6 +233,33 @@ function RtlMenu() {
|
|
|
233
233
|
}
|
|
234
234
|
```
|
|
235
235
|
|
|
236
|
+
### Long Menu (max height + scroll)
|
|
237
|
+
|
|
238
|
+
Tall menus scroll instead of overflowing off-screen. The surface caps at `maxHeight` (default `320`px) and never exceeds the space available after collision handling. Pass `maxHeight` to change the cap.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuLabel } from '@torch-ui/components'
|
|
242
|
+
|
|
243
|
+
function LongMenu() {
|
|
244
|
+
return (
|
|
245
|
+
<ContextMenu>
|
|
246
|
+
<ContextMenuTrigger asChild>
|
|
247
|
+
<div className="flex h-40 w-72 items-center justify-center rounded-md border border-dashed">
|
|
248
|
+
Right-click here
|
|
249
|
+
</div>
|
|
250
|
+
</ContextMenuTrigger>
|
|
251
|
+
{/* Cap the surface at 220px — the rest scrolls. */}
|
|
252
|
+
<ContextMenuContent maxHeight={220}>
|
|
253
|
+
<ContextMenuLabel>Jump to section</ContextMenuLabel>
|
|
254
|
+
{Array.from({ length: 20 }, (_, i) => (
|
|
255
|
+
<ContextMenuItem key={i}>Section {i + 1}</ContextMenuItem>
|
|
256
|
+
))}
|
|
257
|
+
</ContextMenuContent>
|
|
258
|
+
</ContextMenu>
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
236
263
|
## API Reference
|
|
237
264
|
|
|
238
265
|
### ContextMenu (Root)
|
|
@@ -264,6 +291,7 @@ The right-click zone. Wrap it around the element the menu should open from.
|
|
|
264
291
|
| `theme` | `'dark' \| 'light' \| 'default'` | - | Theme variant (applied as `data-theme`) |
|
|
265
292
|
| `className` | `string` | - | Additional CSS classes |
|
|
266
293
|
| `collisionPadding` | `number` | `8` | Min distance kept from the viewport edge |
|
|
294
|
+
| `maxHeight` | `number` | `320` | Max height (px) of the surface before it scrolls. Capped at `min(maxHeight, available-height)` so the menu never overflows off-screen |
|
|
267
295
|
| `autoGroup` | `boolean` | `true` | Auto-wrap loose items in a Boxed group (see Behavior notes) |
|
|
268
296
|
|
|
269
297
|
### ContextMenuItem
|
|
@@ -353,6 +381,7 @@ interface ContextMenuContentProps {
|
|
|
353
381
|
theme?: 'dark' | 'light' | 'default'
|
|
354
382
|
className?: string
|
|
355
383
|
collisionPadding?: number // default 8
|
|
384
|
+
maxHeight?: number // default 320 — surface scrolls past this
|
|
356
385
|
autoGroup?: boolean // default true
|
|
357
386
|
}
|
|
358
387
|
|
|
@@ -403,6 +432,7 @@ export const ContextMenuRadioItem: React.ForwardRefExoticComponent<ContextMenuRa
|
|
|
403
432
|
- **Opens at the pointer**: the menu opens on right-click (`contextmenu`) at the exact cursor position, not anchored to a fixed trigger button.
|
|
404
433
|
- **Second right-click closes it**: the Root is made controlled and tracks `open` in context. The Trigger listens in the capture phase, and when the menu is already open it `preventDefault()` / `stopPropagation()` and closes — so a second right-click dismisses instead of re-anchoring (which Radix handles unreliably).
|
|
405
434
|
- **Auto-grouping**: by default (`autoGroup` on `ContextMenuContent`, default `true`) consecutive loose items (`ContextMenuItem`, `ContextMenuCheckboxItem`, `ContextMenuRadioItem`, and `ContextMenuSub`) are automatically wrapped in a `Boxed` `ContextMenuGroup`, so they render inside a boxed container like DropdownMenu even when you do not write a group. Labels and explicit groups act as boundaries and pass through unchanged. Set `autoGroup={false}` to render children verbatim.
|
|
435
|
+
- **Max height & scrolling**: the surface caps its height at `min(maxHeight, available-height)` (where `maxHeight` defaults to `320`px and `available-height` is the space Radix has after collision handling). A taller menu scrolls vertically instead of overflowing off-screen — items and groups keep their full height rather than squishing. Pass `maxHeight={N}` to change the cap.
|
|
406
436
|
- **Checkbox / radio keep the menu open**: `ContextMenuCheckboxItem` and `ContextMenuRadioItem` call `event.preventDefault()` inside `onSelect`, stopping Radix's default auto-close so users can toggle multiple options in one pass.
|
|
407
437
|
- **Open-only animation**: only the open (enter) state animates (`fade-in`). There is intentionally no exit animation — holding the old DOM node during close breaks close/reposition on a second right-click, so it is omitted to keep repositioning reliable.
|
|
408
438
|
- **Submenus and RTL**: nested `ContextMenuSub` / `ContextMenuSubTrigger` / `ContextMenuSubContent` are supported, and `dir="rtl"` on the Root mirrors the layout (including the submenu chevron).
|
|
@@ -148,7 +148,7 @@ render `DataViewsConfigPanel` yourself (example above) or extend the layout
|
|
|
148
148
|
| Saved View | `savedViews` / `activeSavedView` | Radio list + "Save a New View" button. |
|
|
149
149
|
| Table Columns | `config.tableColumns` | Green `Switch` per column toggles `visible`; rows are drag-reorderable (HTML5 DnD) and patch `order`. |
|
|
150
150
|
| Default Sort | `config.sortBy` | Single-choice radio; selecting sets `sortBy`. Direction stays on `config.sortOrder`. |
|
|
151
|
-
| Filters tab | `filterState` + `fields` | Renders `FilterPanel` restyled full-width/transparent. |
|
|
151
|
+
| Filters tab | `filterState` + `fields` | Renders `FilterPanel` restyled full-width/transparent. Categorical fields render as checkbox/radio lists, or a `SearchableSelect` dropdown when a field sets `filterVariant: "searchable-select"`. |
|
|
152
152
|
|
|
153
153
|
## Internal: PanelControls
|
|
154
154
|
|
|
@@ -173,8 +173,8 @@ Common changes and where to make them:
|
|
|
173
173
|
|---|---|
|
|
174
174
|
| Make Saved View work through `DataViewsLayout` | Add `savedViews` / `activeSavedView` / `onSavedViewChange` / `onSaveNewView` to `DataViewsLayoutProps`, hold them in layout state (or accept from the host), and forward them to `<DataViewsConfigPanel>` at its render site in `DataViewsLayout.tsx`. |
|
|
175
175
|
| Add a new Config section | Add a new block inside the Config. tab in `DataViewsConfigPanel.tsx`, backed by a `config.*` field so `onConfigChange` persists it. |
|
|
176
|
-
| Restyle a radio/toggle | Edit `PanelControls.tsx`. Keep
|
|
177
|
-
| Change the dark chrome | The root forces `data-theme="dark"` and uses hardcoded hex (`#1C1D1F`, `#252729`, `#005ECC`, `#626467`, `#0AC713`). These are intentional Figma values, not tokens. |
|
|
176
|
+
| Restyle a radio/toggle | Edit the radio ring in `DataViewRadio.tsx` (the `RadioRow` in `PanelControls.tsx` just wraps it) or the switch in `PanelControls.tsx`. Keep the hardcoded hex matching the Figma spec; do not swap in the shared `Radio`/`Label` (they impose theming/layout that fights the dark panel — this was deliberate). |
|
|
177
|
+
| Change the dark chrome | The root forces `data-theme="dark"` and uses hardcoded hex (`#1C1D1F`, `#252729`, `#005ECC`, `#0075FF`, `#626467`, `#0AC713`). These are intentional Figma values, not tokens — the radio/checkbox rings hardcode them (`#626467` border, `rgba(255,255,255,0.05)` fill, `#0075FF` selected) so the panel always renders dark regardless of host `data-theme`. |
|
|
178
178
|
|
|
179
179
|
After any change to the panel docs or component, update this file and rebuild
|
|
180
180
|
the MCP server (`cd mcp && pnpm build`) so the docs the server serves stay in
|
|
@@ -176,7 +176,11 @@ Inbox auto-detects `isRead`, `isStarred`, `hasAttachment`, `priority`. Override
|
|
|
176
176
|
| `type` | `FieldType` | Renderer key (see below). Auto-inferred if omitted. |
|
|
177
177
|
| `visible` | `boolean` | Show in cells. Default `true`. |
|
|
178
178
|
| `order` | `number` | Display order. |
|
|
179
|
-
| `filterable` | `boolean` | Surface this field in the filter panel. |
|
|
179
|
+
| `filterable` | `boolean` | Surface this field in the filter panel. The control adapts to the field `type`: categorical fields render checkboxes/radios (or a searchable dropdown via `filterVariant`), numeric fields a range slider, and **date / date-format fields a From + To pair of Glare `DatePicker`s** (two single-date pickers bounding the range). Set `false` to explicitly exclude a field the panel would otherwise auto-detect (e.g. an `id` or `name` with few unique values). |
|
|
180
|
+
| `filterLabel` | `string` | Override the label shown above this field's filter (defaults to `label`). |
|
|
181
|
+
| `filterMode` | `"single" \| "multi"` | Categorical selection mode. `"multi"` (default) renders checkboxes; `"single"` renders radios. |
|
|
182
|
+
| `filterVariant` | `"checkbox" \| "searchable-select"` | Categorical control style. `"checkbox"` (default) is the inline checkbox/radio list; `"searchable-select"` renders a single-select `SearchableSelect` dropdown — useful when a field has many options. Implies single-select. |
|
|
183
|
+
| `filterOptions` | `string[] \| { label: string; value: string }[]` | Explicit option list for the categorical filter (otherwise options are collected from the data). |
|
|
180
184
|
| `variants` | `Record<string, BadgeVariant>` | For `enum-badge`: per-value color map. |
|
|
181
185
|
| `currency` | `string \| CurrencyOptions` | For `currency`: ISO code or `{ symbol, locale, decimals, code }`. |
|
|
182
186
|
| `thresholds` | `[number, number]` | For `progress-bar`: warning/ok thresholds. |
|
|
@@ -188,6 +192,8 @@ Inbox auto-detects `isRead`, `isStarred`, `hasAttachment`, `priority`. Override
|
|
|
188
192
|
|
|
189
193
|
`text` · `number` · `date` · `date-format` · `boolean` · `currency` · `number-format` · `enum-badge` · `badge-array` · `progress-bar` · `star-rating` · `icon-text` · `two-line` · `avatar` · `link` · `image` · `hidden`
|
|
190
194
|
|
|
195
|
+
> **`hidden` vs `filterable: false`** — use `type: "hidden"` to drop a field from the UI **entirely** (no column, no column-toggle in the config panel, no filter) while it stays in the data for row identity — e.g. an `id` you key rows by but never want shown. Use `filterable: false` to keep a field as a **column** but remove only its **filter**.
|
|
196
|
+
|
|
191
197
|
### `BadgeVariant`
|
|
192
198
|
|
|
193
199
|
`green` · `greenLight` · `cocktailGreen` · `yellow` · `redOrange` · `redLight` · `rose` · `purple` · `bluePurple` · `blue` · `navy` · `gray` · `highlight`
|
|
@@ -268,7 +274,7 @@ function CustomScreen({ data, fields }: Props) {
|
|
|
268
274
|
|
|
269
275
|
## Accessibility
|
|
270
276
|
|
|
271
|
-
- The view-switcher uses `
|
|
277
|
+
- The view-switcher uses [`TabSwitch`](./tab-switch.md) — a segmented `role="tablist"` control (each view a `role="tab"` button, full keyboard support via Tab/Enter/Space). Installing DataViews pulls in `TabSwitch` automatically.
|
|
272
278
|
- Tree rows expose `role="treeitem"` with `aria-expanded` and `aria-selected`.
|
|
273
279
|
- Filter checkboxes carry labels and `htmlFor` linkage.
|
|
274
280
|
- Settings panel buttons have `aria-pressed` for sort direction.
|
|
@@ -284,4 +290,5 @@ The component uses only `*-presentation-*` design tokens. Wrap with `ThemeProvid
|
|
|
284
290
|
- [`KanbanView`](./kanban-view.md) — standalone kanban
|
|
285
291
|
- [`InboxView`](./inbox-view.md) — standalone inbox
|
|
286
292
|
- [`TreeView`](./tree-view.md) — standalone tree
|
|
293
|
+
- [`TabSwitch`](./tab-switch.md) — the segmented view-switcher in the header (reusable on its own)
|
|
287
294
|
- [How-to: Render a backend response with DataViews](../how-to/data-views-from-backend-response.md) — recipes by data shape.
|
|
@@ -299,6 +299,32 @@ function Example() {
|
|
|
299
299
|
}
|
|
300
300
|
```
|
|
301
301
|
|
|
302
|
+
### Long Menu (max height + scroll)
|
|
303
|
+
|
|
304
|
+
Tall menus scroll instead of overflowing off-screen. The surface caps at `maxHeight` (default `320`px) and never exceeds the space available after collision handling. Pass `maxHeight` to change the cap.
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel } from '@torch-ui/components'
|
|
308
|
+
import { Button } from '@torch-ui/components'
|
|
309
|
+
|
|
310
|
+
function LongMenu() {
|
|
311
|
+
return (
|
|
312
|
+
<DropdownMenu>
|
|
313
|
+
<DropdownMenuTrigger asChild>
|
|
314
|
+
<Button variant="BorderStyle">Jump to section</Button>
|
|
315
|
+
</DropdownMenuTrigger>
|
|
316
|
+
{/* Cap the surface at 240px — the rest scrolls. */}
|
|
317
|
+
<DropdownMenuContent align="start" maxHeight={240}>
|
|
318
|
+
<DropdownMenuLabel>Sections</DropdownMenuLabel>
|
|
319
|
+
{Array.from({ length: 20 }, (_, i) => (
|
|
320
|
+
<DropdownMenuItem key={i}>Section {i + 1}</DropdownMenuItem>
|
|
321
|
+
))}
|
|
322
|
+
</DropdownMenuContent>
|
|
323
|
+
</DropdownMenu>
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
302
328
|
## API Reference
|
|
303
329
|
|
|
304
330
|
### DropdownMenu (Root)
|
|
@@ -326,6 +352,7 @@ function Example() {
|
|
|
326
352
|
| `sideOffset` | `number` | `4` | Distance from trigger |
|
|
327
353
|
| `collisionPadding` | `number` | `8` | Gap kept from viewport edges when flipping/shifting |
|
|
328
354
|
| `align` | `'start' \| 'center' \| 'end'` | `'center'` | Alignment (inherited from Radix) |
|
|
355
|
+
| `maxHeight` | `number` | `320` | Max height (px) of the surface before it scrolls. Capped at `min(maxHeight, available-height)` so the menu never overflows off-screen |
|
|
329
356
|
| `autoGroup` | `boolean` | `true` | Auto-wrap loose items in boxed groups |
|
|
330
357
|
|
|
331
358
|
### DropdownMenuItem
|
|
@@ -426,6 +453,7 @@ interface DropdownMenuContentProps {
|
|
|
426
453
|
sideOffset?: number
|
|
427
454
|
collisionPadding?: number
|
|
428
455
|
align?: 'start' | 'center' | 'end'
|
|
456
|
+
maxHeight?: number // default 320 — surface scrolls past this
|
|
429
457
|
autoGroup?: boolean
|
|
430
458
|
}
|
|
431
459
|
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: HeaderBar
|
|
3
|
+
description: Variant-driven page/form header chip pairing a colored emphasis pill with a plain title.
|
|
4
|
+
component: true
|
|
5
|
+
group: Layout
|
|
6
|
+
keywords: [header, headerbar, page-header, form-header, title, badge, label, layout]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# HeaderBar
|
|
10
|
+
|
|
11
|
+
A presentational header chip used at the top of a form, page, or drawer to communicate the current record context — for example "NEW sales invoice", "EDIT sales invoice", or "ORDER de-344". It pairs a colored emphasis pill (the `label`) with a plain `title`, and the `variant` controls both the pill color and which side the pill sits on. The surface is always a fixed dark container, and the component is non-interactive.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx torch-glare@latest add HeaderBar
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Imports
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import HeaderBar from '@/components/HeaderBar'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { HeaderBar } from '@/components/HeaderBar'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Both the default export and the named export resolve to the same component. The website convention is the default import.
|
|
30
|
+
|
|
31
|
+
## Basic Usage
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import HeaderBar from '@/components/HeaderBar'
|
|
35
|
+
|
|
36
|
+
export function BasicHeaderBar() {
|
|
37
|
+
return (
|
|
38
|
+
<HeaderBar
|
|
39
|
+
variant="new"
|
|
40
|
+
label="New"
|
|
41
|
+
title="sales iNVOICE"
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Examples
|
|
48
|
+
|
|
49
|
+
### New Invoice Header
|
|
50
|
+
|
|
51
|
+
The `new` variant renders a blue emphasis pill on the left followed by the plain title on the right. Use it on create screens.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import HeaderBar from '@/components/HeaderBar'
|
|
55
|
+
|
|
56
|
+
export function NewInvoiceHeader() {
|
|
57
|
+
return (
|
|
58
|
+
<HeaderBar
|
|
59
|
+
variant="new"
|
|
60
|
+
label="New"
|
|
61
|
+
title="sales iNVOICE"
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Edit Header
|
|
68
|
+
|
|
69
|
+
The `edit` variant shares the exact same layout as `new` (pill left, title right) but uses an orange emphasis pill. Use it on edit screens.
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import HeaderBar from '@/components/HeaderBar'
|
|
73
|
+
|
|
74
|
+
export function EditInvoiceHeader() {
|
|
75
|
+
return (
|
|
76
|
+
<HeaderBar
|
|
77
|
+
variant="edit"
|
|
78
|
+
label="edit"
|
|
79
|
+
title="sales iNVOICE"
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Detail Header
|
|
86
|
+
|
|
87
|
+
The `detail` variant swaps the positions: the plain title renders on the left and the colored (white-alpha) pill renders on the right. Use it for read-only record context such as an order reference.
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import HeaderBar from '@/components/HeaderBar'
|
|
91
|
+
|
|
92
|
+
export function OrderDetailHeader() {
|
|
93
|
+
// Renders as: SALES INVOICE [ de-344 ]
|
|
94
|
+
// plain title on the LEFT, badge on the RIGHT
|
|
95
|
+
return (
|
|
96
|
+
<HeaderBar
|
|
97
|
+
variant="detail"
|
|
98
|
+
label="de-344"
|
|
99
|
+
title="sales iNVOICE"
|
|
100
|
+
/>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Themed
|
|
106
|
+
|
|
107
|
+
HeaderBar accepts a `theme` prop applied via `data-theme`. The surface is always a dark chip, so theming is most useful for keeping the component consistent with the surrounding `data-theme` scope.
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import HeaderBar from '@/components/HeaderBar'
|
|
111
|
+
|
|
112
|
+
export function ThemedHeaderBars() {
|
|
113
|
+
return (
|
|
114
|
+
<div className="flex flex-col gap-4">
|
|
115
|
+
<HeaderBar variant="new" label="New" title="sales iNVOICE" theme="dark" />
|
|
116
|
+
<HeaderBar variant="edit" label="edit" title="sales iNVOICE" theme="light" />
|
|
117
|
+
<HeaderBar variant="detail" label="de-344" title="sales iNVOICE" theme="default" />
|
|
118
|
+
</div>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## API Reference
|
|
124
|
+
|
|
125
|
+
### HeaderBar Props
|
|
126
|
+
|
|
127
|
+
| Prop | Type | Default | Description |
|
|
128
|
+
|------|------|---------|-------------|
|
|
129
|
+
| variant | `'new' \| 'edit' \| 'detail'` | `'new'` | Controls both the pill color and which side the pill sits on |
|
|
130
|
+
| label | `string` | — (required) | Text rendered inside the colored emphasis pill |
|
|
131
|
+
| title | `string` | — (required) | Plain text rendered alongside the pill |
|
|
132
|
+
| theme | `Themes` | — | Theme variant applied via `data-theme` (`'dark' \| 'light' \| 'default'`) |
|
|
133
|
+
| className | `string` | — | Additional CSS classes merged onto the root element |
|
|
134
|
+
|
|
135
|
+
All standard `HTMLAttributes<HTMLDivElement>` (for example `id`, `aria-*`, `data-*`, `style`, `onClick`) pass through to the root `div`.
|
|
136
|
+
|
|
137
|
+
## Variants
|
|
138
|
+
|
|
139
|
+
| Variant | Pill color | Pill text | Layout (left → right) |
|
|
140
|
+
|---------|-----------|-----------|------------------------|
|
|
141
|
+
| `new` | `bg-blue-sparkle-alpha-50` | `text-blue-sparkle-200` | pill (`label`) → title |
|
|
142
|
+
| `edit` | `bg-orange-alpha-50` | `text-orange-200` | pill (`label`) → title |
|
|
143
|
+
| `detail` | `bg-white-alpha-30` | `text-white-00` | title → pill (`label`) — positions swapped |
|
|
144
|
+
|
|
145
|
+
## Styling
|
|
146
|
+
|
|
147
|
+
- **Fixed dark container**: `rounded-[14px]`, `border-black-600`, `bg-black-1000`, `p-1.5`, with a double soft shadow. The surface is always dark regardless of theme.
|
|
148
|
+
- **Layout**: the root is `inline-flex`, so the chip hugs its content rather than stretching to fill its parent.
|
|
149
|
+
- **Typography**: 28px, weight 510, uppercase, SF Pro with the `cv05` stylistic set. Both `label` and `title` render uppercase.
|
|
150
|
+
- **Emphasis pill**: the colored badge background and text color are driven entirely by `variant` (see the Variants table). For `detail`, the pill also moves to the right side.
|
|
151
|
+
|
|
152
|
+
## TypeScript Types
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { HTMLAttributes } from 'react'
|
|
156
|
+
import { Themes } from '@/utils/types'
|
|
157
|
+
|
|
158
|
+
interface HeaderBarProps extends HTMLAttributes<HTMLDivElement> {
|
|
159
|
+
variant?: 'new' | 'edit' | 'detail'
|
|
160
|
+
label: string
|
|
161
|
+
title: string
|
|
162
|
+
theme?: Themes
|
|
163
|
+
className?: string
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Accessibility
|
|
168
|
+
|
|
169
|
+
- HeaderBar is purely presentational — it has no interactive behavior and is intended to label the surrounding content visually.
|
|
170
|
+
- Because the text is rendered uppercase via styling (not the source string), screen readers receive the original casing of `label` and `title`.
|
|
171
|
+
- Pass any `aria-*` attributes through the standard prop spread when the chip needs an explicit accessible role or label in its context.
|
|
172
|
+
- HeaderBar does not render an HTML heading element. When this chip represents the page or section heading, ensure proper heading semantics exist in the surrounding layout (for example an `<h1>` / `<h2>` for the page) so document structure remains navigable.
|
|
173
|
+
|
|
174
|
+
## Best Practices
|
|
175
|
+
|
|
176
|
+
1. **Match the variant to the screen mode**: use `new` for create screens, `edit` for edit screens, and `detail` for read-only record context.
|
|
177
|
+
2. **Keep `label` short**: it is the emphasis pill, so a concise token (a mode word like "New" / "edit" or a record reference like "de-344") reads best — text renders UPPERCASE automatically.
|
|
178
|
+
3. **Use `title` for the record type**: the plain side should name the entity (for example "sales invoice", "Order"), not duplicate the label.
|
|
179
|
+
4. **Expect the detail swap**: for `variant="detail"` the pill moves to the right and the title to the left — author content with that ordering in mind.
|
|
180
|
+
5. **Don't rely on it for interactivity**: HeaderBar is a label, not a control; place buttons or actions in a separate toolbar.
|
|
181
|
+
6. **Let it hug its content**: the chip is `inline-flex`; avoid forcing it to full width and keep it at the top of the form, page, or drawer it describes.
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: SearchableTable
|
|
3
|
-
description: A
|
|
3
|
+
description: A field that opens a modal dialog to pick a row from a real Table, with single-select, client- or server-side search, and infinite-scroll pagination
|
|
4
4
|
group: Inputs
|
|
5
|
-
keywords: [searchable-table,
|
|
5
|
+
keywords: [searchable-table, dialog, table, search, async, infinite-scroll, pagination, select, picker]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# SearchableTable
|
|
9
9
|
|
|
10
|
-
> A
|
|
10
|
+
> A field that opens a modal `Dialog` to pick a row from a real, multi-column `Table`. The trigger shows a placeholder until something is selected, then the selected row's label. Click it to open the dialog (a search input + the data table); type to filter rows (client-side) or refetch them from a backend (server-side), click a row to single-select it and close the dialog, and scroll to the bottom to lazy-load more pages. Generic over your row type `T`.
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
|
-
`SearchableTable` is part of the TORCH Glare component library. It composes the [
|
|
14
|
+
`SearchableTable` is part of the TORCH Glare component library. It composes the [Dialog](./dialog.md) (built on Radix Dialog) and the [Table](./table.md) component internally, so both must be available — they ship with the library.
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npm install @torch-ui/components @radix-ui/react-
|
|
17
|
+
npm install @torch-ui/components @radix-ui/react-dialog
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
## Import
|
|
@@ -60,13 +60,15 @@ function Example() {
|
|
|
60
60
|
onSelect={setSelected}
|
|
61
61
|
getLabel={(row) => row.name}
|
|
62
62
|
getRowId={(row) => row.id}
|
|
63
|
-
|
|
63
|
+
icon={<i className="ri-user-line" />}
|
|
64
|
+
placeholder="Select a user…"
|
|
65
|
+
title="Select a user"
|
|
64
66
|
/>
|
|
65
67
|
)
|
|
66
68
|
}
|
|
67
69
|
```
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
Clicking the field opens a modal dialog with a search input and the data table. As you type, rows are filtered locally by every column key. Clicking a row selects it (single-select), closes the dialog, and shows `getLabel(row)` on the trigger. Use `placeholder` for the trigger text, `searchPlaceholder` for the in-dialog search input, and `title` for the dialog's search-field label (also the accessible dialog title).
|
|
70
72
|
|
|
71
73
|
### Custom cell rendering
|
|
72
74
|
|
|
@@ -159,26 +161,35 @@ function AsyncExample() {
|
|
|
159
161
|
hasMore={hasMore}
|
|
160
162
|
loading={loading}
|
|
161
163
|
onLoadMore={loadMore}
|
|
162
|
-
|
|
164
|
+
icon={<i className="ri-user-line" />}
|
|
165
|
+
placeholder="Select a user…"
|
|
166
|
+
title="Select a user"
|
|
167
|
+
searchPlaceholder="Search users… (scroll to load more)"
|
|
163
168
|
/>
|
|
164
169
|
)
|
|
165
170
|
}
|
|
166
171
|
```
|
|
167
172
|
|
|
168
|
-
###
|
|
173
|
+
### Dialog labels (trigger, title, search)
|
|
169
174
|
|
|
170
|
-
|
|
175
|
+
Three props control the dialog's text. `placeholder` is the trigger text shown until a row is selected; `title` labels the dialog's search field (and is the accessible dialog title); `searchPlaceholder` is the placeholder of the in-dialog search input.
|
|
171
176
|
|
|
172
177
|
```typescript
|
|
173
178
|
<SearchableTable<User>
|
|
174
179
|
columns={columns}
|
|
175
180
|
rows={users}
|
|
181
|
+
value={selected}
|
|
176
182
|
onSelect={setSelected}
|
|
177
183
|
getRowId={(row) => row.id}
|
|
178
|
-
|
|
184
|
+
icon={<i className="ri-user-line" />}
|
|
185
|
+
placeholder="Select a user…"
|
|
186
|
+
title="Select a user"
|
|
187
|
+
searchPlaceholder="Search by name or role…"
|
|
179
188
|
/>
|
|
180
189
|
```
|
|
181
190
|
|
|
191
|
+
The data table inside the dialog scrolls vertically once it exceeds `~55vh`; the remaining rows stay reachable via scroll, which is also what drives infinite-scroll loading.
|
|
192
|
+
|
|
182
193
|
## API Reference
|
|
183
194
|
|
|
184
195
|
### SearchableTable<T>
|
|
@@ -188,24 +199,25 @@ function AsyncExample() {
|
|
|
188
199
|
| `columns` | `SearchableTableColumn<T>[]` | Required | Column config — header, the row key to read, and an optional cell renderer. |
|
|
189
200
|
| `rows` | `T[]` | Required | The data rows. In server mode these are rendered as-is (already filtered upstream). |
|
|
190
201
|
| `value` | `T \| null` | - | Controlled selected row. The matching row is highlighted in the table. |
|
|
191
|
-
| `onSelect` | `(row: T) => void` | - | Called when a row is clicked. The
|
|
192
|
-
| `getLabel` | `(row: T) => string` | First column's value | Text shown
|
|
202
|
+
| `onSelect` | `(row: T) => void` | - | Called when a row is clicked. The dialog closes and the trigger shows the row's label. |
|
|
203
|
+
| `getLabel` | `(row: T) => string` | First column's value | Text shown on the trigger after selection. |
|
|
193
204
|
| `getRowId` | `(row: T) => string` | `JSON.stringify(row)` | Stable id/key per row, used for keys and selection matching. |
|
|
194
205
|
| `searchKeys` | `(keyof T & string)[]` | Every column `key` | Which fields client-side search matches against. |
|
|
195
|
-
| `placeholder` | `string` | `'
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
206
|
+
| `placeholder` | `string` | `'Select…'` | Trigger placeholder shown until a row is selected. |
|
|
207
|
+
| `searchPlaceholder` | `string` | `'Search…'` | Placeholder for the search input inside the dialog. |
|
|
208
|
+
| `title` | `string` | `'Select an item'` | Label shown on the dialog's search field (also used as the accessible dialog title). |
|
|
209
|
+
| `size` | `'XS' \| 'S' \| 'M'` | `'M'` | Trigger size. (`XS` maps the underlying Group to `S` with a tighter input height.) |
|
|
210
|
+
| `variant` | `'PresentationStyle'` | `'PresentationStyle'` | Visual style of the trigger field. |
|
|
211
|
+
| `icon` | `ReactNode` | - | Optional leading icon rendered inside the trigger. |
|
|
199
212
|
| `theme` | `'dark' \| 'light' \| 'default'` | - | Theme variant, applied via `data-theme`. |
|
|
200
|
-
| `dir` | `string` | - | Text direction (e.g. `'rtl'`), applied to the
|
|
201
|
-
| `className` | `string` | - | Additional classes merged onto the
|
|
213
|
+
| `dir` | `string` | - | Text direction (e.g. `'rtl'`), applied to the trigger and dialog. |
|
|
214
|
+
| `className` | `string` | - | Additional classes merged onto the trigger group. |
|
|
202
215
|
| `filterClientSide` | `boolean` | `true` | When `true`, filter `rows` locally by `searchKeys`. Set `false` for server-side search. |
|
|
203
216
|
| `onSearchChange` | `(query: string) => void` | - | Debounced query callback — refetch your data here in server mode. |
|
|
204
217
|
| `searchDebounceMs` | `number` | `300` | Debounce delay (ms) for `onSearchChange`. |
|
|
205
218
|
| `hasMore` | `boolean` | `false` | Whether more pages are available; gates the infinite-scroll loader. |
|
|
206
219
|
| `loading` | `boolean` | `false` | Whether a fetch is in flight; renders a loading row and blocks `onLoadMore`. |
|
|
207
220
|
| `onLoadMore` | `() => void` | - | Called when the list nears the bottom and `hasMore && !loading`. |
|
|
208
|
-
| `maxVisibleRows` | `number` | `6` | Max rows visible before the list scrolls vertically. |
|
|
209
221
|
|
|
210
222
|
### SearchableTableColumn<T>
|
|
211
223
|
|
|
@@ -242,7 +254,9 @@ interface SearchableTableProps<T> {
|
|
|
242
254
|
getLabel?: (row: T) => string
|
|
243
255
|
getRowId?: (row: T) => string
|
|
244
256
|
searchKeys?: (keyof T & string)[]
|
|
245
|
-
placeholder?: string
|
|
257
|
+
placeholder?: string // trigger text until selection (default 'Select…')
|
|
258
|
+
searchPlaceholder?: string // in-dialog search input (default 'Search…')
|
|
259
|
+
title?: string // dialog search-field label / a11y title (default 'Select an item')
|
|
246
260
|
size?: 'XS' | 'S' | 'M'
|
|
247
261
|
variant?: 'PresentationStyle'
|
|
248
262
|
icon?: React.ReactNode
|
|
@@ -256,7 +270,6 @@ interface SearchableTableProps<T> {
|
|
|
256
270
|
hasMore?: boolean
|
|
257
271
|
loading?: boolean
|
|
258
272
|
onLoadMore?: () => void
|
|
259
|
-
maxVisibleRows?: number
|
|
260
273
|
}
|
|
261
274
|
|
|
262
275
|
// Generic function component, constrained so rows are object-shaped:
|
|
@@ -395,25 +408,26 @@ function toUser(api: ApiUser): User {
|
|
|
395
408
|
|
|
396
409
|
## Accessibility
|
|
397
410
|
|
|
398
|
-
- The trigger is a
|
|
399
|
-
-
|
|
400
|
-
-
|
|
401
|
-
-
|
|
402
|
-
- Provide a descriptive `placeholder` and a
|
|
411
|
+
- The trigger is a focusable group with `role="button"` and `tabIndex={0}`; activating it opens a modal dialog (Radix Dialog), which traps focus and closes on `Esc` or outside click.
|
|
412
|
+
- On open, focus moves to the in-dialog search input (`onOpenAutoFocus` is intercepted to focus the search field rather than the first row), so users can type to filter immediately.
|
|
413
|
+
- The dialog always renders a `DialogTitle` (visually hidden, sourced from `title`) so the modal has an accessible name even though the heading is not shown.
|
|
414
|
+
- Clicking a row selects it and closes the dialog; the selected row is marked with the Table's `state="selected"`.
|
|
415
|
+
- Provide a descriptive `placeholder`, a meaningful `title`, and a `getLabel` so screen-reader users hear a clear value after selection. Prefer human-readable headers in `columns`.
|
|
403
416
|
|
|
404
417
|
## Best Practices
|
|
405
418
|
|
|
406
419
|
1. **Always supply `getRowId`.** The default key is `JSON.stringify(row)`, which is slow and brittle for large or nested rows. A stable id keeps keys and selection matching cheap.
|
|
407
|
-
2. **Set `getLabel` for the selected display.** Otherwise the
|
|
420
|
+
2. **Set `getLabel` for the selected display.** Otherwise the trigger shows the first column's raw value, which may not be the most descriptive field.
|
|
408
421
|
3. **Pick the right search mode.** Keep `filterClientSide` (default) for small, in-memory datasets. Switch to `filterClientSide={false}` + `onSearchChange` for backend-driven search so you never filter a partial page.
|
|
409
422
|
4. **Gate pagination correctly.** `onLoadMore` only fires while `hasMore && !loading` — keep `loading` accurate so a single scroll doesn't trigger duplicate fetches, and flip `hasMore` to `false` on the last page.
|
|
410
|
-
5. **The table scrolls in both directions.** Columns keep their natural width, so wide tables scroll horizontally inside the
|
|
423
|
+
5. **The table scrolls in both directions.** Columns keep their natural width, so wide tables scroll horizontally inside the dialog, while the table caps its vertical height (~`55vh`) and the rest scrolls vertically (which is what drives infinite scroll). Keep column count and content reasonable so horizontal scroll stays usable.
|
|
411
424
|
6. **Tune `searchDebounceMs` to your backend.** The 300ms default suits most APIs; raise it for slow or rate-limited endpoints.
|
|
412
425
|
7. **Match `searchKeys` to visible columns.** In client mode, search defaults to every column key — narrow `searchKeys` if some columns hold non-searchable data (ids, formatted dates).
|
|
413
426
|
|
|
414
427
|
## Related Components
|
|
415
428
|
|
|
416
429
|
- [SearchableSelect](./searchable-select.md) - Single-column searchable combobox
|
|
417
|
-
- [Table](./table.md) - The table primitive rendered inside the
|
|
430
|
+
- [Table](./table.md) - The table primitive rendered inside the dialog
|
|
431
|
+
- [Dialog](./dialog.md) - The modal surface the picker opens in
|
|
418
432
|
- [DataTable](./data-table.md) - Full-featured data grid for page-level tables
|
|
419
433
|
- [Select](./select.md) - Standard form select field
|
|
@@ -236,6 +236,124 @@ function RowDivider() {
|
|
|
236
236
|
}
|
|
237
237
|
```
|
|
238
238
|
|
|
239
|
+
### Bilingual Form with Language Switch (EN / AR)
|
|
240
|
+
|
|
241
|
+
A real-world pattern: a `SectionBlock` form with an EN/AR language switcher in the header. The colored title badge and the `TabSwitch` stay LTR; only the field rows flip to RTL when Arabic is selected, and all labels/placeholders are translated.
|
|
242
|
+
|
|
243
|
+
Key points:
|
|
244
|
+
|
|
245
|
+
- Put the `TabSwitch` in a `relative` wrapper and position it `absolute top-2 right-2` so it sits at the header's top-right **outside** the colored title badge (the badge wraps the entire `title` node).
|
|
246
|
+
- Apply `dir` to each field **row**, not to the `SectionBlock` root — otherwise the header (badge + switch) flips too.
|
|
247
|
+
- Drive copy through a tiny `t(en, ar)` helper keyed off the selected language.
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
import { type ReactNode, useState } from "react";
|
|
251
|
+
import { SectionBlock } from "@/components/SectionBlock";
|
|
252
|
+
import { InputField } from "@/components/InputField";
|
|
253
|
+
import { ActionButton } from "@/components/ActionButton";
|
|
254
|
+
import { TabSwitch } from "@/components/TabSwitch";
|
|
255
|
+
|
|
256
|
+
export function BilingualFieldsForm() {
|
|
257
|
+
const [language, setLanguage] = useState<"ar" | "en">("en");
|
|
258
|
+
const isAr = language === "ar";
|
|
259
|
+
const t = (en: string, ar: string) => (isAr ? ar : en);
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<div className="relative">
|
|
263
|
+
<SectionBlock
|
|
264
|
+
color="Blue"
|
|
265
|
+
title={
|
|
266
|
+
<span className="flex items-center gap-[6px]">
|
|
267
|
+
<i className="ri-edit-box-line" />
|
|
268
|
+
{t("Custom fields", "حقول مخصصة")}
|
|
269
|
+
</span>
|
|
270
|
+
}
|
|
271
|
+
>
|
|
272
|
+
<FieldRow
|
|
273
|
+
dir={isAr ? "rtl" : "ltr"}
|
|
274
|
+
label={t("Name", "الاسم")}
|
|
275
|
+
required
|
|
276
|
+
requiredLabel={t("(Required)", "(مطلوب)")}
|
|
277
|
+
right={
|
|
278
|
+
<div className="flex flex-1 items-center gap-[12px] min-w-0">
|
|
279
|
+
<InputField placeholder={t("First Name*", "الاسم الأول*")} className="flex-1 min-w-0" />
|
|
280
|
+
<InputField placeholder={t("Last Name*", "اسم العائلة*")} className="flex-1 min-w-0" />
|
|
281
|
+
</div>
|
|
282
|
+
}
|
|
283
|
+
/>
|
|
284
|
+
|
|
285
|
+
<RowDivider />
|
|
286
|
+
|
|
287
|
+
<FieldRow
|
|
288
|
+
dir={isAr ? "rtl" : "ltr"}
|
|
289
|
+
label={t("Department", "القسم")}
|
|
290
|
+
right={<InputField placeholder={t("Write Hint Here", "اكتب التلميح هنا")} className="flex-1" />}
|
|
291
|
+
/>
|
|
292
|
+
|
|
293
|
+
<RowDivider />
|
|
294
|
+
|
|
295
|
+
<FieldRow
|
|
296
|
+
dir={isAr ? "rtl" : "ltr"}
|
|
297
|
+
label={t("Alias names", "الأسماء المستعارة")}
|
|
298
|
+
right={
|
|
299
|
+
<InputField
|
|
300
|
+
placeholder={t("Write Hint Here", "اكتب التلميح هنا")}
|
|
301
|
+
className="flex-1"
|
|
302
|
+
childrenSide={
|
|
303
|
+
<ActionButton aria-label={t("Add alias name", "إضافة اسم مستعار")}>
|
|
304
|
+
<i className="ri-add-line" />
|
|
305
|
+
</ActionButton>
|
|
306
|
+
}
|
|
307
|
+
/>
|
|
308
|
+
}
|
|
309
|
+
/>
|
|
310
|
+
</SectionBlock>
|
|
311
|
+
|
|
312
|
+
<TabSwitch
|
|
313
|
+
className="absolute top-2 right-2 z-10"
|
|
314
|
+
size="S"
|
|
315
|
+
value={language}
|
|
316
|
+
onValueChange={setLanguage}
|
|
317
|
+
options={[
|
|
318
|
+
{ value: "en", label: "English" },
|
|
319
|
+
{ value: "ar", label: "العربية" },
|
|
320
|
+
]}
|
|
321
|
+
/>
|
|
322
|
+
</div>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
interface FieldRowProps {
|
|
327
|
+
label: string;
|
|
328
|
+
required?: boolean;
|
|
329
|
+
requiredLabel?: string;
|
|
330
|
+
right: ReactNode;
|
|
331
|
+
dir?: "ltr" | "rtl";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function FieldRow({ label, required, requiredLabel = "(Required)", right, dir }: FieldRowProps) {
|
|
335
|
+
return (
|
|
336
|
+
<div dir={dir} className="flex items-center gap-[24px] py-[18px]">
|
|
337
|
+
<div className="flex w-[140px] shrink-0 items-center gap-[6px]">
|
|
338
|
+
<span className="typography-body-medium-regular text-content-presentation-action-light-primary">
|
|
339
|
+
{label}
|
|
340
|
+
</span>
|
|
341
|
+
{required && (
|
|
342
|
+
<span className="typography-body-small-medium text-content-presentation-state-negative">
|
|
343
|
+
{requiredLabel}
|
|
344
|
+
</span>
|
|
345
|
+
)}
|
|
346
|
+
</div>
|
|
347
|
+
<div className="flex flex-1 items-center min-w-0">{right}</div>
|
|
348
|
+
</div>
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function RowDivider() {
|
|
353
|
+
return <div className="h-px w-full bg-border-presentation-global-primary" />;
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
239
357
|
### Custom Layout (override defaults)
|
|
240
358
|
|
|
241
359
|
Use `containerClassName`, `headerClassName`, and `bodyClassName` to override the built-in spacing and width without losing the title/body structure.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: TabSwitch
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
status: stable
|
|
5
|
+
category: components/navigation
|
|
6
|
+
tags: [tab-switch, segmented-control, view-switcher, toggle, list-cards, pills]
|
|
7
|
+
last-reviewed: 2026-06-15
|
|
8
|
+
bundle-size: 2.0kb
|
|
9
|
+
dependencies:
|
|
10
|
+
- "class-variance-authority": "^0.7.0"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# TabSwitch
|
|
14
|
+
|
|
15
|
+
> A segmented control for picking one option from a small set — the classic List / Cards style pill switcher. The active option renders as a solid raised white pill; thin dividers sit between adjacent inactive options (never flanking the active pill). Controlled, generic over the option value, supports optional per-option icons, three sizes, and theme-aware track/labels. This is the switcher used in the DataViews header to flip between Table / Kanban / Inbox / Tree.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install torch-glare
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Import
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { TabSwitch } from 'torch-glare/lib/components/TabSwitch'
|
|
27
|
+
import type { TabSwitchOption } from 'torch-glare/lib/components/TabSwitch'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Examples
|
|
31
|
+
|
|
32
|
+
### Basic Usage (List / Cards)
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { TabSwitch } from 'torch-glare/lib/components/TabSwitch'
|
|
36
|
+
import { useState } from 'react'
|
|
37
|
+
|
|
38
|
+
function Example() {
|
|
39
|
+
const [view, setView] = useState('list')
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<TabSwitch
|
|
43
|
+
value={view}
|
|
44
|
+
onValueChange={setView}
|
|
45
|
+
options={[
|
|
46
|
+
{ value: 'list', label: 'List', icon: <i className="ri-layout-grid-line" /> },
|
|
47
|
+
{ value: 'cards', label: 'Cards', icon: <i className="ri-grid-fill" /> },
|
|
48
|
+
]}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Sizes
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
<TabSwitch size="S" value={view} onValueChange={setView} options={options} />
|
|
58
|
+
<TabSwitch size="M" value={view} onValueChange={setView} options={options} /> {/* default */}
|
|
59
|
+
<TabSwitch size="L" value={view} onValueChange={setView} options={options} />
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Icons Only
|
|
63
|
+
|
|
64
|
+
Omit `label` to render an icon-only switcher.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
<TabSwitch
|
|
68
|
+
value={view}
|
|
69
|
+
onValueChange={setView}
|
|
70
|
+
options={[
|
|
71
|
+
{ value: 'list', icon: <i className="ri-layout-grid-line" /> },
|
|
72
|
+
{ value: 'cards', icon: <i className="ri-grid-fill" /> },
|
|
73
|
+
{ value: 'board', icon: <i className="ri-layout-column-line" /> },
|
|
74
|
+
]}
|
|
75
|
+
/>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### On a dark surface
|
|
79
|
+
|
|
80
|
+
The active pill is always a solid white pill with dark text, so it stays visible on dark bars. The track and inactive labels follow the theme — pass `theme="dark"` (or render inside a `data-theme="dark"` scope) so they resolve dark-theme tokens. This is how the DataViews header uses it.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
<div data-theme="dark" className="bg-black p-2">
|
|
84
|
+
<TabSwitch theme="dark" value={view} onValueChange={setView} options={options} />
|
|
85
|
+
</div>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Disabled
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
{/* whole control */}
|
|
92
|
+
<TabSwitch disabled value={view} onValueChange={setView} options={options} />
|
|
93
|
+
|
|
94
|
+
{/* a single option */}
|
|
95
|
+
<TabSwitch
|
|
96
|
+
value={view}
|
|
97
|
+
onValueChange={setView}
|
|
98
|
+
options={[
|
|
99
|
+
{ value: 'list', label: 'List' },
|
|
100
|
+
{ value: 'cards', label: 'Cards', disabled: true },
|
|
101
|
+
]}
|
|
102
|
+
/>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API Reference
|
|
106
|
+
|
|
107
|
+
### TabSwitch
|
|
108
|
+
|
|
109
|
+
| Prop | Type | Default | Description |
|
|
110
|
+
|------|------|---------|-------------|
|
|
111
|
+
| `options` | `TabSwitchOption[]` | — (required) | The selectable options rendered as segments. |
|
|
112
|
+
| `value` | `string` | — | The currently selected option value (controlled). |
|
|
113
|
+
| `onValueChange` | `(value: string) => void` | — | Called with the option value when a segment is selected. |
|
|
114
|
+
| `size` | `'S' \| 'M' \| 'L'` | `'M'` | Size of the control. |
|
|
115
|
+
| `disabled` | `boolean` | `false` | Disables the whole control. |
|
|
116
|
+
| `theme` | `'dark' \| 'light' \| 'default'` | — | Applies a fixed theme to the track and inactive labels (the active pill stays white). |
|
|
117
|
+
| `className` | `string` | — | Additional classes merged onto the track. |
|
|
118
|
+
|
|
119
|
+
`TabSwitch` is generic over the option value: `TabSwitch<T extends string>` infers `T` from `options`, so `value` and `onValueChange` are typed to your union (e.g. `'list' | 'cards'`).
|
|
120
|
+
|
|
121
|
+
### TabSwitchOption
|
|
122
|
+
|
|
123
|
+
| Prop | Type | Default | Description |
|
|
124
|
+
|------|------|---------|-------------|
|
|
125
|
+
| `value` | `string` | — | Unique value for the option. |
|
|
126
|
+
| `label` | `ReactNode` | `undefined` | Text or node shown for the option. Omit for an icon-only segment. |
|
|
127
|
+
| `icon` | `ReactNode` | `undefined` | Optional leading icon rendered before the label. |
|
|
128
|
+
| `disabled` | `boolean` | `false` | Disables this individual option. |
|
|
129
|
+
|
|
130
|
+
## Accessibility
|
|
131
|
+
|
|
132
|
+
- The track is a `role="tablist"`; each option is a `role="tab"` with `aria-selected` reflecting the active state.
|
|
133
|
+
- Options are real `<button>` elements, so they are keyboard-focusable and activate on Enter/Space.
|
|
134
|
+
|
|
135
|
+
## Notes
|
|
136
|
+
|
|
137
|
+
- The active pill is intentionally a solid white pill with dark text in every theme (not derived from the per-theme selected-tab tokens), so it reads correctly on the always-dark DataViews header as well as on light surfaces.
|
|
138
|
+
- TabSwitch is a controlled component — always pass both `value` and `onValueChange`.
|
|
139
|
+
|
|
140
|
+
## TypeScript
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
interface TabSwitchOption<T extends string = string> {
|
|
144
|
+
value: T
|
|
145
|
+
label?: React.ReactNode
|
|
146
|
+
icon?: React.ReactNode
|
|
147
|
+
disabled?: boolean
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
interface TabSwitchProps<T extends string = string>
|
|
151
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
152
|
+
options: TabSwitchOption<T>[]
|
|
153
|
+
value: T
|
|
154
|
+
onValueChange: (value: T) => void
|
|
155
|
+
size?: 'S' | 'M' | 'L'
|
|
156
|
+
theme?: 'dark' | 'light' | 'default'
|
|
157
|
+
disabled?: boolean
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
declare function TabSwitch<T extends string = string>(
|
|
161
|
+
props: TabSwitchProps<T> & { ref?: React.ForwardedRef<HTMLDivElement> }
|
|
162
|
+
): React.ReactElement
|
|
163
|
+
```
|
|
@@ -183,6 +183,7 @@ See each view's reference: [TableView](../components/table-view.md) ·
|
|
|
183
183
|
|
|
184
184
|
- **Empty `data`** → all views render their empty state; pass `isLoading` upstream if you fetch async.
|
|
185
185
|
- **Tree tab missing?** No hierarchy was detected. Supply `treeConfig` explicitly or check your `childrenField` / `parentField`.
|
|
186
|
+
- **Unwanted filters?** The panel auto-detects filterable text fields with few unique values (e.g. `id`, `name`). Set `filterable: false` on a field to exclude it. For a field with many options, set `filterVariant: "searchable-select"` to render a searchable dropdown instead of a long checkbox list.
|
|
186
187
|
- **Saved Views don't persist** in tab mode — that's a known limitation documented in [`DataViewsConfigPanel`](../components/data-views-config-panel.md). Use composable mode for real persistence.
|
|
187
188
|
|
|
188
189
|
## Related
|
package/docs/llms-manifest.json
CHANGED
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"statistics": {
|
|
22
|
-
"totalComponents":
|
|
23
|
-
"documentedComponents":
|
|
24
|
-
"coverage":
|
|
22
|
+
"totalComponents": 76,
|
|
23
|
+
"documentedComponents": 73,
|
|
24
|
+
"coverage": 96.1,
|
|
25
25
|
"totalHooks": 4,
|
|
26
26
|
"totalProviders": 1,
|
|
27
27
|
"totalPlugins": 4,
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
"forms": {
|
|
67
|
-
"count":
|
|
67
|
+
"count": 20,
|
|
68
68
|
"items": [
|
|
69
69
|
{
|
|
70
70
|
"name": "SearchableSelect",
|
|
@@ -151,6 +151,11 @@
|
|
|
151
151
|
"version": "1.1.15",
|
|
152
152
|
"documented": true
|
|
153
153
|
},
|
|
154
|
+
{
|
|
155
|
+
"name": "TabSwitch",
|
|
156
|
+
"version": "1.0.0",
|
|
157
|
+
"documented": true
|
|
158
|
+
},
|
|
154
159
|
{
|
|
155
160
|
"name": "FormStepper",
|
|
156
161
|
"version": "2.1.1",
|
|
@@ -164,13 +169,18 @@
|
|
|
164
169
|
]
|
|
165
170
|
},
|
|
166
171
|
"layout": {
|
|
167
|
-
"count":
|
|
172
|
+
"count": 8,
|
|
168
173
|
"items": [
|
|
169
174
|
{
|
|
170
175
|
"name": "Card",
|
|
171
176
|
"version": "1.1.15",
|
|
172
177
|
"documented": true
|
|
173
178
|
},
|
|
179
|
+
{
|
|
180
|
+
"name": "HeaderBar",
|
|
181
|
+
"version": "1.0.0",
|
|
182
|
+
"documented": true
|
|
183
|
+
},
|
|
174
184
|
{
|
|
175
185
|
"name": "CNLayout",
|
|
176
186
|
"version": "1.1.15",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "torch-glare-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "MCP server for TORCH Glare component library — gives AI assistants full access to component docs, API references, code examples, and design system info",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|