torch-glare 2.3.0 → 2.4.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.
@@ -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 searchable combobox whose dropdown renders a real Table, with single-select, client- or server-side search, and infinite-scroll pagination
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, combobox, table, search, async, infinite-scroll, pagination, select]
5
+ keywords: [searchable-table, dialog, table, search, async, infinite-scroll, pagination, select, picker]
6
6
  ---
7
7
 
8
8
  # SearchableTable
9
9
 
10
- > A searchable combobox that renders its options as a real, multi-column `Table` inside a `Popover`. Type to filter rows (client-side) or refetch them from a backend (server-side), click a row to single-select it, and scroll to the bottom to lazy-load more pages. Generic over your row type `T`.
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 [Popover](./popover.md) (built on Radix Popover) and the [Table](./table.md) component internally, so both must be available — they ship with the library.
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-popover
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
- placeholder="Search users…"
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
- The input opens its dropdown on focus. As you type, rows are filtered locally by every column key. Clicking a row selects it (single-select), closes the dropdown, and shows `getLabel(row)` in the input.
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
- placeholder="Search users…"
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
- ### Capping the visible height
173
+ ### Dialog labels (trigger, title, search)
169
174
 
170
- `maxVisibleRows` (default `6`) caps how many rows are visible before the dropdown scrolls vertically. The remaining rows stay reachable via scroll, which is also what drives infinite-scroll loading.
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
- maxVisibleRows={4}
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&lt;T&gt;
@@ -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 dropdown closes and the input shows the row's label. |
192
- | `getLabel` | `(row: T) => string` | First column's value | Text shown in the input after selection. |
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` | `'Search…'` | Input placeholder text. |
196
- | `size` | `'XS' \| 'S' \| 'M'` | `'M'` | Input size. (`XS` maps the underlying Group to `S` with a tighter input height.) |
197
- | `variant` | `'PresentationStyle'` | `'PresentationStyle'` | Visual style of the input and dropdown surface. |
198
- | `icon` | `ReactNode` | - | Optional leading icon rendered inside the input. |
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 input group and dropdown. |
201
- | `className` | `string` | - | Additional classes merged onto the input group. |
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&lt;T&gt;
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 standard text `<input>`, so it is focusable and typeable by keyboard. Focusing it opens the dropdown and starts a fresh search.
399
- - The chevron is a boxed icon button with `aria-label` toggling between `"Open"` and `"Close"`; it is `tabIndex={-1}` so keyboard focus stays on the input, and it uses `onMouseDown` with `preventDefault` to avoid the input blur/focus race.
400
- - Clicking outside the input group or the dropdown closes it (handled via `useClickOutside`).
401
- - The dropdown surface does not steal focus on open (`onOpenAutoFocus` is prevented), keeping the caret in the input while results update.
402
- - Provide a descriptive `placeholder` and a meaningful `getLabel` so screen-reader users hear a clear value after selection. Prefer human-readable headers in `columns`.
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 input shows the first column's raw value, which may not be the most descriptive field.
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 dropdown, while `maxVisibleRows` caps the vertical height and the rest scrolls vertically (which is what drives infinite scroll). Keep column count and content reasonable so horizontal scroll stays usable.
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 dropdown
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.
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "torch-glare",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "files": [