@lotics/ui 11.4.0 → 11.5.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/AGENTS.md +41 -1100
- package/docs/ai_patterns.md +374 -0
- package/docs/catalog.md +904 -0
- package/docs/composition.md +439 -0
- package/docs/data_entry.md +398 -0
- package/docs/templates.md +357 -0
- package/package.json +3 -2
package/docs/catalog.md
ADDED
|
@@ -0,0 +1,904 @@
|
|
|
1
|
+
# @lotics/ui — component catalog
|
|
2
|
+
|
|
3
|
+
The complete inventory of the kit, in two passes: **Reach by role** maps each data role /
|
|
4
|
+
job (actions, members, selects, dates, files, money, status, …) to the ONE canonical
|
|
5
|
+
component, and the **Full import inventory** lists every `@lotics/ui/<module>` entry point
|
|
6
|
+
with its purpose. Read this before building any screen — reuse first; a component absent
|
|
7
|
+
from this catalog does not exist, and one present here must never be hand-rolled. The
|
|
8
|
+
composition patterns (data entry, AI review, templates) live in the sibling area docs
|
|
9
|
+
indexed in [AGENTS.md](../AGENTS.md).
|
|
10
|
+
|
|
11
|
+
## How to read a component's API
|
|
12
|
+
|
|
13
|
+
- **Compose with React Native primitives**: `View` / `ScrollView` from `react-native` (never
|
|
14
|
+
`div`/`span`), style with RN style objects (not CSS), and render every string through the `Text`
|
|
15
|
+
primitive — the kit renders on web **and** native.
|
|
16
|
+
- **Import per module**: `import { Combobox, ComboboxInput, ComboboxContent } from "@lotics/ui/combobox"`.
|
|
17
|
+
There is no barrel — every module below is its own entry point.
|
|
18
|
+
- **The exact API is the source, which ships with the package**: read
|
|
19
|
+
`node_modules/@lotics/ui/src/<module>.tsx`. **Never guess a prop — open the file.**
|
|
20
|
+
- **If the closest component lacks a capability, extend it** (a platform change every app
|
|
21
|
+
inherits), never inline a one-off `View`/`Text` rebuild — that forfeits the typeahead,
|
|
22
|
+
async search, virtualization, and a11y the primitive already ships.
|
|
23
|
+
- Floating content (`Dialog` · `Popover` · `Tooltip` · `Alert` · `OptionList`) needs a
|
|
24
|
+
`PortalHost` (`@lotics/ui/portal`) at the app root.
|
|
25
|
+
- Worked examples referenced below (`tpl_record`, `tpl_item_list`, …) ship in
|
|
26
|
+
[`../examples/`](../examples/).
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Reach by role — what exists
|
|
31
|
+
|
|
32
|
+
Pick by capability, not by name. (→ the source file for the API.)
|
|
33
|
+
|
|
34
|
+
### Actions
|
|
35
|
+
|
|
36
|
+
`Button` (labelled; `title` is MANDATORY — the type blocks a title-less Button; `color` =
|
|
37
|
+
emphasis/risk), `IconButton` (icon-ONLY — the circular affordance; an icon-only Button is a
|
|
38
|
+
type error, so reach here. `size` `lg` 40px / `md` 28px / `sm` 24px — md/sm keep a 40px touch
|
|
39
|
+
target via hitSlop — the SAME `color` palette as Button, plus `loading` and `elevated` (white
|
|
40
|
+
fill + border + shadow, for a button sitting ON imagery — a tile's remove ✕, an overlay's
|
|
41
|
+
retry); needs `accessibilityLabel`/`tooltip`), `TextLink` (underlined text that's OPTIONALLY
|
|
42
|
+
an action (`onPress`) or a link (`href` — a real web anchor) — or, with neither, plain
|
|
43
|
+
underlined text you drop in your own pressable like a table cell; colour via `color`; the
|
|
44
|
+
go-to for Clear / Select all / inline links), `Chip` (dismissible facet chip). A button is
|
|
45
|
+
never a raw `Pressable`. For a link OUT (a URL / record / document) use `Link` — fixed
|
|
46
|
+
underline+blue + `role="link"`, the destination signal (`onPress` only — the consumer wires
|
|
47
|
+
the opener, e.g. the app SDK's `openExternal`); `TextLink` is the neutral,
|
|
48
|
+
colour-configurable underlined link/action.
|
|
49
|
+
|
|
50
|
+
### Pick from a list
|
|
51
|
+
|
|
52
|
+
`Picker` (native `<select>`, plain label-only single, native typeahead) or `Select` (the
|
|
53
|
+
rich one — custom-rendered options, single/multi, select-all). Search-as-you-type / async /
|
|
54
|
+
create-new → `Combobox`. A selectable card row → `CardSelectItem`. For a SELECT-FIELD
|
|
55
|
+
picker, render each option as its colored chip on `Select`
|
|
56
|
+
(`renderOptionContent={(o) => <OptionBadge value={o} />}`). `Select` opens the shared
|
|
57
|
+
`OptionList` body; `Combobox` is COMPOUND (`ComboboxInput` + `ComboboxContent`) over the
|
|
58
|
+
same option-list engine; `Picker` is the only one that's a native dropdown.
|
|
59
|
+
|
|
60
|
+
### Pick member(s)
|
|
61
|
+
|
|
62
|
+
`MemberSelect` (a `Select` that renders each option as a `MemberChip`, single or multi) —
|
|
63
|
+
the ready member picker; pass it the roster (`members={useMembers().members}`). Don't
|
|
64
|
+
re-wire `Select` + `renderOptionContent` + a directory by hand. For an assignee FILTER that
|
|
65
|
+
also matches unassigned records, pass `unassignedLabel` → it prepends a muted "unassigned"
|
|
66
|
+
option (value `MEMBER_UNASSIGNED`), so you never hand-roll a mixed member+none `Select`. To
|
|
67
|
+
edit a `select_member` field IN PLACE (chip at rest → picker on click) use its inline-edit
|
|
68
|
+
twin `InlineMemberSelect`.
|
|
69
|
+
|
|
70
|
+
### A person / member (display)
|
|
71
|
+
|
|
72
|
+
`MemberChip` (avatar + name + optional secondary line) — the ONE way to show a member
|
|
73
|
+
inline: a picker option, an assignee, a `select_member` value. Pure: resolve the member from
|
|
74
|
+
your directory and pass `name` / `image`; never hand-roll `Avatar` + `Text`. (`MemberSelect`
|
|
75
|
+
renders these per option.)
|
|
76
|
+
|
|
77
|
+
### A select-field value
|
|
78
|
+
|
|
79
|
+
`OptionBadge` (a stored `select` value as its CONFIGURED colored badge) — never hand-map
|
|
80
|
+
option-key → color. Feed it a resolved option; multi-select wraps to one badge each; a
|
|
81
|
+
missing/unknown color token degrades to neutral.
|
|
82
|
+
|
|
83
|
+
### Tags / multi-value chip box
|
|
84
|
+
|
|
85
|
+
`Select multi` whose `renderSelected` returns a removable `<Chip onDismiss={remove}>` (with
|
|
86
|
+
`searchable` + `allowCustom`). The chip box is COMPOSED, not a separate control — there's no
|
|
87
|
+
`display` mode; `renderSelected` is the seam (see the data-entry patterns doc indexed in
|
|
88
|
+
[AGENTS.md](../AGENTS.md)).
|
|
89
|
+
|
|
90
|
+
### Text & form
|
|
91
|
+
|
|
92
|
+
`TextInputField`, `NumberInput`, `SearchInput`; wrap with `FormField`; `Checkbox`, `Switch`,
|
|
93
|
+
`RadioPicker`; dates via `DatePicker` / `DateRangeFilterField`, times via `TimePicker`.
|
|
94
|
+
Batch draft-form state → `useForm`.
|
|
95
|
+
|
|
96
|
+
### Edit a record's fields in place
|
|
97
|
+
|
|
98
|
+
The `Inline*` family: `InlineTextInput` · `InlineNumberInput` · `InlineSelect` ·
|
|
99
|
+
`InlineMemberSelect` · `InlineTagSelect` · `InlineDatePicker` · `InlineTimePicker`; a
|
|
100
|
+
READ-ONLY field in that same column uses `InlineStatic` (matches the editor box exactly, no
|
|
101
|
+
input chrome, so it aligns pixel-for-pixel). A stack of labelled field rows lives in
|
|
102
|
+
`DetailTable` + `DetailRow`; the record's identity band is `RecordSummary`; its money
|
|
103
|
+
summary is `Ledger`.
|
|
104
|
+
|
|
105
|
+
### Tasks / to-dos — pick by ALTITUDE
|
|
106
|
+
|
|
107
|
+
A task-management PAGE (many tasks, grouping, filters, expandable rows) is COMPOSITION —
|
|
108
|
+
there is NO Task component (a Reminders row and a Linear column-grid share almost nothing):
|
|
109
|
+
`CheckCircle` + a struck `InlineTextInput` + your meta cells; see
|
|
110
|
+
[`tpl_tasks`](../examples/tpl_tasks.tsx) (quick list) and
|
|
111
|
+
[`tpl_task_board`](../examples/tpl_task_board.tsx) (columns). A record's own small CHECKLIST
|
|
112
|
+
(the 5–8 tasks living in a drawer/section) is the `Checklist`/`ChecklistRow` compound +
|
|
113
|
+
`SuggestionChip` commons + `CaptureRow` — see [`tpl_record`](../examples/tpl_record.tsx) /
|
|
114
|
+
[`tpl_item_list`](../examples/tpl_item_list.tsx).
|
|
115
|
+
|
|
116
|
+
### Tabular data — pick by SCALE + intent
|
|
117
|
+
|
|
118
|
+
Two columnar shapes, and the choice is about data size:
|
|
119
|
+
|
|
120
|
+
- **High-volume register** (thousands+ you BROWSE) — `Table` (columns defined once; sortable
|
|
121
|
+
headers via `SortHeader`; paired with `Pagination`) + read-only rows that open a `Drawer`
|
|
122
|
+
to edit. It scales by PAGING — renders one page, never the whole set — so you FILTER +
|
|
123
|
+
search, you don't group. Worked example: [`tpl_item_list`](../examples/tpl_item_list.tsx).
|
|
124
|
+
Never an HTML `<table>` or a `.map` of rows.
|
|
125
|
+
- **Inline-managed grouped table** (MODERATE — hundreds, low-thousands — you MANAGE in view)
|
|
126
|
+
— the `DataGrid` primitive: a grouped, sortable grid whose cells are LIVE inline editors
|
|
127
|
+
(ANY field — a column is `{ key, label, width?, sortable?, cell: (item) => ReactNode }`,
|
|
128
|
+
so `InlineMemberSelect` / `InlineDatePicker` / `InlineNumberInput` / `InlineSelect` / a
|
|
129
|
+
borderless `Select multi` for tags / colour-dot `OptionBadge`). `DataGrid` owns the
|
|
130
|
+
sortable header + collapsible grouped sections + aligned rows + an optional per-row
|
|
131
|
+
`leading` slot (a `CheckCircle`); YOU own the data, the sort/group/filter/collapse STATE
|
|
132
|
+
(`cycleSort` + `sortBy` from `@lotics/ui/sort_header`, a `collapsed` Set), the toolbar
|
|
133
|
+
(`SearchInput` + `FilterChip`s), and the per-group add row (`renderGroupFooter`, aligned
|
|
134
|
+
with the exported `gridRowStyle` + the column widths). It renders ALL rows (no
|
|
135
|
+
virtualization), so it's only for sets small enough to hold in view — at 10k+ it lags, and
|
|
136
|
+
grouping + pagination/infinite don't compose; use the register instead. Worked example:
|
|
137
|
+
[`tpl_task_board`](../examples/tpl_task_board.tsx) (a `CheckCircle` leading).
|
|
138
|
+
|
|
139
|
+
### Numbers & charts
|
|
140
|
+
|
|
141
|
+
`KPIStrip` (the dashboard stat band) · `SummaryLine` (the light inline register/list summary
|
|
142
|
+
— below the toolbar, from the filtered rows) · `KPICard` / `Metric` (headline figures),
|
|
143
|
+
`TrendChip` (delta), `Sparkline`, `BarChart` / `LineChart` / `PieChart` (the canonical SVG
|
|
144
|
+
set — no recharts), `RingGauge`, `ProgressBar` (its `compact` prop = ONE row, track + a
|
|
145
|
+
plain sm tabular count beside it — the cell/heading/peek-trigger meter; a caption floating
|
|
146
|
+
above a tiny bar reads misaligned) / `StackedProgressBar` / `StepProgress`, `Breakdown` (a
|
|
147
|
+
stacked bar + ranked share rows, pressable to drill; `maxRows` folds the long tail behind a
|
|
148
|
+
"Show N more" toggle — `labels` to localize — so several facet cards align to one height in
|
|
149
|
+
a row), `Funnel` (a CONVERSION funnel — ordered stages as bars that NARROW; the step
|
|
150
|
+
conversion rate is the HEADLINE (a bold aligned row across the top, the first stage = the
|
|
151
|
+
100% baseline), the count is the supporting figure below — the Amplitude/Mixpanel
|
|
152
|
+
convention, never pin the rate to the fill height. `orientation` vertical columns |
|
|
153
|
+
horizontal bars; pass `onSelect`+`selectedKey` to make the bars press-to-drill (the selected
|
|
154
|
+
stays solid, others dim — the caller renders the records). The subset/drop-off sibling of
|
|
155
|
+
`StackedProgressBar` — nested cohorts that shrink "calls → connected → won", NOT a whole
|
|
156
|
+
split across stages — that's `StackedProgressBar`), `StatusGrid` + `StatusLegend`, `Heatmap`
|
|
157
|
+
(density: colour-only, "where does it cluster"), `Matrix` (the PIVOT cross-tab: the NUMBER
|
|
158
|
+
in each cell — optionally a heat wash behind it — plus row/column/grand totals; press a cell
|
|
159
|
+
to drill).
|
|
160
|
+
|
|
161
|
+
### Surfaces & layout
|
|
162
|
+
|
|
163
|
+
`Card` (+ `CardHeader`/`CardHeaderTitle`/`CardHeaderMeta`/`CardBody`/`CardFooter`),
|
|
164
|
+
`Section` (from `@lotics/ui/section_heading` — the card-less twin of `Card`: bare
|
|
165
|
+
gap-spaced region + `SectionHeading`/`SectionHeadingTitle`/`SectionHeadingMeta`; no body
|
|
166
|
+
component — children are the body), `Subsection` (+ `SubsectionHeading`/
|
|
167
|
+
`SubsectionHeadingTitle` — the named group INSIDE a section, `###` lg-semibold title),
|
|
168
|
+
`SectionStack` (the flat page's content column — owns the fixed 56px beat + hairline between
|
|
169
|
+
top-level blocks), `SubsectionStack` (the same law one step tighter — fixed 24px beat +
|
|
170
|
+
hairline between a section's `Subsection` groups), `SectionCard`, `PageHeader` /
|
|
171
|
+
`PageContent`, `Stack`, `Spacer`, `Divider`, `Accordion`, `Tabs`, `SegmentedControl`,
|
|
172
|
+
`Stepper`, `DangerZone` (the destructive section — delete/archive — set apart at the bottom
|
|
173
|
+
of a record/settings surface).
|
|
174
|
+
|
|
175
|
+
### Rows & registers
|
|
176
|
+
|
|
177
|
+
`PressableRow` (THE register row; forwards its `ref`, so wrapping it in a `PopoverTrigger`
|
|
178
|
+
anchors a row-triggered peek Popover — without the ref the trigger is unmeasurable and the
|
|
179
|
+
popover renders off-screen), `ListItem`, `MenuButton`, `MenuListItem`, `DetailRow`
|
|
180
|
+
(label+value for drawers/peeks; optional `trailing` slot for a right-side
|
|
181
|
+
action/badge/unit), `ActionMenu` (⋯), `FloatingActionBar` (bulk-select bar).
|
|
182
|
+
|
|
183
|
+
### Filters & view controls
|
|
184
|
+
|
|
185
|
+
`SearchInput`, `ChipGroup`, `FilterChip` (+ `RangeSlider`, `Counter`), `Chip`,
|
|
186
|
+
`ColumnFilter` (the typed per-column filter pill + `columnFilterToConditions`).
|
|
187
|
+
|
|
188
|
+
### Overlays
|
|
189
|
+
|
|
190
|
+
`Dialog` (centered card over a scrim), `Modal` (+ `ModalHeader`/`ModalBody`/`ModalFooter` —
|
|
191
|
+
a full-bleed, edge-to-edge takeover with NO scrim), `Drawer` (+ `DrawerFooter`), `Popover`,
|
|
192
|
+
`Tooltip`, `OptionList` (the searchable list body — host it in a `Popover`/`Dialog` for a
|
|
193
|
+
command palette), `Alert` (the blocking confirm), `Peek` (drill-down popover on an inline
|
|
194
|
+
reference), `InfoPopover` (the ⓘ explainer).
|
|
195
|
+
|
|
196
|
+
### Status / feedback
|
|
197
|
+
|
|
198
|
+
`Badge` / `StatusBadge`, `Callout` (inline status), `EmptyState`, `CompletionState`,
|
|
199
|
+
`ActivityIndicator` / `Loading`, `Skeleton`.
|
|
200
|
+
|
|
201
|
+
### Files
|
|
202
|
+
|
|
203
|
+
`FilesEditor` (THE all-in-one attachment field: an upload-aware grid + a toolbar below it
|
|
204
|
+
that swaps into a batch SELECT mode, full-screen preview, download/share, and
|
|
205
|
+
Alert-confirmed remove — the host only owns `files` + wires `onAdd`/`onRemove`. Reach for
|
|
206
|
+
this first for "manage a record's attachments"), `FileDropzone`, `FileRows`
|
|
207
|
+
(batteries-included file LIST: tap a row → built-in full-screen gallery, with a per-row
|
|
208
|
+
trailing ⋯ menu = Download · Open-external · Remove; the default "here are some files"
|
|
209
|
+
surface), `FileGrid` (the upload-aware grid: completed files + a live upload queue in one
|
|
210
|
+
surface — `FilesEditor` is this + the toolbar; reach for `FileGrid` bare when you own the
|
|
211
|
+
chrome), `FileThumbnail` / `FileThumbnailGrid` (square tiles, display-only),
|
|
212
|
+
`UploadingThumbnail` (the single in-flight tile FileGrid renders — reach for it only when
|
|
213
|
+
hand-rolling a non-grid upload layout), `FileRow` (a horizontal file/document LINE —
|
|
214
|
+
badge-or-placeholder + name + meta + a composable `trailing` slot for a status badge /
|
|
215
|
+
action / remove; `onPress` makes the whole row a pressable door, `trailing` stays an
|
|
216
|
+
independently-pressable sibling; for checklists & readable lists), `FileBadge` (the two-tone
|
|
217
|
+
type mark), `FilePreview` / `FileGalleryModal`, `ImageGallery`; picking is `pickFiles`
|
|
218
|
+
(`@lotics/ui/file_picker` — opens the browser picker and resolves the chosen `File[]`, the
|
|
219
|
+
imperative half behind every Add-file CTA); for gated CRUD compose locally with
|
|
220
|
+
`useSelectionMode` + `shareOrDownloadFiles` + `rotateImageToBlob` (see the data-entry
|
|
221
|
+
patterns doc indexed in [AGENTS.md](../AGENTS.md)).
|
|
222
|
+
|
|
223
|
+
### Specialized work surfaces
|
|
224
|
+
|
|
225
|
+
`ScanField` (scan/verify), `Stepper` (a guided run / progress sequence — done · current ·
|
|
226
|
+
upcoming, horizontal OR vertical), `RemainderMeter` + `AllocationRow` (allocation),
|
|
227
|
+
`Timeline` (a heterogeneous event LOG — icons + expandable details, not progress),
|
|
228
|
+
`Calendar` (the `calendar` module's views), `Gantt`, `comments_thread`.
|
|
229
|
+
|
|
230
|
+
### AI surfaces
|
|
231
|
+
|
|
232
|
+
`Composer` (the adaptive command/chat composer — a compact pill when empty that expands for
|
|
233
|
+
long text + attachments; the surface that triggers agent work), `AgentRun` (the live
|
|
234
|
+
streaming work feed) + `AgentProgress` (its compact, floating, expandable form — a
|
|
235
|
+
composer's "working" state) + `Confidence`; **`ChangeReview` — THE one review-before-apply
|
|
236
|
+
surface, a COMPOUND family** (frame: `ChangeReview` · `ChangeReviewHeader` ·
|
|
237
|
+
`ChangeReviewActions`; sections: `Change` · `ChangeLabel` · `ChangeSummary` ·
|
|
238
|
+
`ChangeReasoning`; the grammar: `ChangeFields` + `ChangeField` · `ChangeRecord` ·
|
|
239
|
+
`ChangeBand` + `ChangeValueInput`): adds, updates, removals, conflicts, whole records,
|
|
240
|
+
display-only findings are all compositions — see the AI-patterns doc indexed in
|
|
241
|
+
[AGENTS.md](../AGENTS.md) for the laws; `Clarify` (the agent asks back — selectable
|
|
242
|
+
`ChoiceList` options), `Sources` (provenance chips for AI output — at review scale,
|
|
243
|
+
`label={null}` slots the chips at a section's bottom), `Finding` (one ranked insight from an
|
|
244
|
+
AI check — localized severity word · title · detail · `Sources` chips · a `children` slot;
|
|
245
|
+
**`FindingComparison`** is the expected-vs-actual body: each disagreeing side a labeled row,
|
|
246
|
+
the DELTA emphasized under a hairline (localized "Difference") — quantities, totals, dates;
|
|
247
|
+
a plain `metric` prop remains for one-number findings. The children slot composes ANY visual
|
|
248
|
+
result — a compact `Table` for per-line detail (danger color on the offending cells),
|
|
249
|
+
`ProgressBar` for consumption-toward-a-cliff (free time, credit), dot `Badge`s for a
|
|
250
|
+
present/missing checklist, `Confidence` for judgment calls. Display-only — it informs the
|
|
251
|
+
verdict the host records; `finding` locale slice).
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Full import inventory
|
|
256
|
+
|
|
257
|
+
Every published entry point, grouped by area. Import as `@lotics/ui/<module>`; the module's
|
|
258
|
+
source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
259
|
+
|
|
260
|
+
### Text & formatting
|
|
261
|
+
|
|
262
|
+
- **`text`** — the `Text` primitive (never raw `div`/`span`/`fontSize`): `size` `xs`–`xxxl`
|
|
263
|
+
(the heading ramp: `xl` = `##` section title, `xxl` = `#` page/record title, `xxxl` = hero
|
|
264
|
+
numbers), `weight`, `color`, alignment, tabular numerals.
|
|
265
|
+
- **`markdown`** — `Markdown`: the single canonical markdown renderer for chat, apps, and
|
|
266
|
+
`AgentRun`; rich on web via react-markdown/remark-gfm with copyable tables, plain-text on
|
|
267
|
+
native; takes a markdown `children` string.
|
|
268
|
+
- **`markdown.css`** — import once for the web markdown styling.
|
|
269
|
+
- **`format_date`** — `formatDate` / `parseDate` / `toISODate` + `DateFormatStyle`.
|
|
270
|
+
- **`format_money`** — `formatMoney` / `formatCompactNumber`.
|
|
271
|
+
- **`text_utils`** — text/typography plumbing: `getTextColor` (the TextColor→hex map incl.
|
|
272
|
+
the AA-cleared valence set), the Inter `fontFamily*` stacks, and `getInputTextStyle` /
|
|
273
|
+
`getInputLineHeight` — the 16px-mobile/14px-desktop input contract that stops Safari iOS
|
|
274
|
+
auto-zoom; only for hand-rolled raw inputs.
|
|
275
|
+
|
|
276
|
+
### Theming, locale & tokens
|
|
277
|
+
|
|
278
|
+
- **`theme`** — `LoticsThemeProvider` / `useLoticsTheme` + `DEFAULT_ACCENT`: the single
|
|
279
|
+
brand accent (OKLCH blue by default) that chart fills, hero CTAs, and focus rings read.
|
|
280
|
+
- **`locale`** — `LoticsLocaleProvider` / `useLoticsLocale` + the shipped `en` (default) and
|
|
281
|
+
`vi` packs. Set the language ONCE at the root; wired components resolve **prop → provider
|
|
282
|
+
locale → English default**. **Limitation:** the calendar/gantt views and the comment
|
|
283
|
+
labels are not yet provider-wired — pass their `labels` props directly.
|
|
284
|
+
- **`colors`** — the palette + `withAlpha` · `solid` · `tint` · `ramp` · `ColorName` ·
|
|
285
|
+
`isColorName` · `asColorName` (coerce a stored option/status token to a `ColorName`,
|
|
286
|
+
neutral fallback).
|
|
287
|
+
- **`tokens`** — design tokens: re-exports `colors` plus `space`/`type`/`weight`/`radius`
|
|
288
|
+
scales and `getCssVariables()` — an OPT-IN serializer to `--lotics-*` CSS variables for
|
|
289
|
+
hand-rolled plain DOM/CSS (nothing injects them automatically; `@lotics/ui` components
|
|
290
|
+
don't need them).
|
|
291
|
+
- **`spacing`** — the `SPACE` scale + `SpaceToken`.
|
|
292
|
+
- **`control_surface`** — `CONTROL_HEIGHT` (40) · `CONTROL_RADIUS` (10) · `FOCUS_RING` ·
|
|
293
|
+
`HOVER_BORDER` · `CONTROL_TRANSITION` · `chipSurfaceStyle` — the shared control-surface
|
|
294
|
+
tokens.
|
|
295
|
+
- **`fonts.css`** — the Inter sheet (400/500/600, served by absolute URL so it resolves on
|
|
296
|
+
every origin an app runs from); the app entry imports it ONCE or every `Text` falls back
|
|
297
|
+
to system fonts.
|
|
298
|
+
- **`index.css`** — CSS custom properties (`--font-size-*`, `--input-font-size`, …) for
|
|
299
|
+
hand-rolled DOM surfaces; components don't require it.
|
|
300
|
+
|
|
301
|
+
### Icons & identity
|
|
302
|
+
|
|
303
|
+
- **`icon`** — `Icon` + `IconName`: the curated lucide set for a fixed, code-chosen glyph.
|
|
304
|
+
The set is deliberately partial — a missing glyph is added to the kit (deep import + map
|
|
305
|
+
entry), never assumed to exist.
|
|
306
|
+
- **`dynamic_icon`** — `DynamicIcon`: any Lucide icon by RUNTIME name (kebab-case string),
|
|
307
|
+
for user/config-chosen icons a compile-time `IconName` can't express; web lazy-loads per
|
|
308
|
+
icon, unknown name → blank. Decorative — the enclosing control carries the accessible
|
|
309
|
+
name. A fixed, code-chosen glyph → `Icon`.
|
|
310
|
+
- **`app_icon`** — `AppIcon`: the app's launcher tile — a brand-gradient square from a
|
|
311
|
+
`themeColor` palette token (unknown → neutral zinc) holding any Lucide icon by runtime
|
|
312
|
+
name via `DynamicIcon`; `size` sm|md. One render wherever an app shows — launcher, list,
|
|
313
|
+
picker, settings.
|
|
314
|
+
- **`avatar`** — `Avatar`: image-or-initial person avatar (`source`/`name`/`size`;
|
|
315
|
+
`announce` when standalone — decorative by default because the name text usually sits
|
|
316
|
+
beside it).
|
|
317
|
+
- **`group_avatar`** — `GroupAvatar`: the first letter of `name` in a zinc rounded square
|
|
318
|
+
(`size`, default 40); the avatar for image-less entities — groups, organizations. A person
|
|
319
|
+
→ `Avatar`/`MemberChip`.
|
|
320
|
+
- **`wave_avatar`** — `WaveAvatar`: decorative animated waveform avatar (voice/audit
|
|
321
|
+
history); animates on web, renders a static fallback on native.
|
|
322
|
+
- **`member_chip`** — `MemberChip`: avatar + name; the universal person render.
|
|
323
|
+
|
|
324
|
+
### Layout & surfaces
|
|
325
|
+
|
|
326
|
+
- **`card`** — `Card` · `CardHeader` · `CardHeaderTitle` (+ `info` ⓘ popover) ·
|
|
327
|
+
`CardHeaderMeta` · `CardBody` · `CardFooter`.
|
|
328
|
+
- **`section_heading`** — `Section` · `SectionHeading` · `SectionHeadingTitle` ·
|
|
329
|
+
`SectionHeadingMeta` · `Subsection` · `SubsectionHeading` · `SubsectionHeadingTitle` — the
|
|
330
|
+
card-less twin of the Card family, compound, owns no margin; spacing via the Section gap
|
|
331
|
+
(12, fixed), no body component. `SectionHeadingTitle` is ALWAYS `##` (xl semibold;
|
|
332
|
+
`weight="medium"` opt-down only) + `info` for an ⓘ provenance popover after the title,
|
|
333
|
+
same as `CardHeaderTitle.info`. `SubsectionHeadingTitle` is the `###` lg-semibold level-3
|
|
334
|
+
title of a named group inside a section — heading-row siblings ride its right edge; the
|
|
335
|
+
heading ramp is FIXED: `#` xxl / `##` xl / `###` lg, no size props.
|
|
336
|
+
- **`section`** — a SECOND, standalone `Section`: a self-contained titled block
|
|
337
|
+
(`title`/`description`/`collapsible`/`icon`/`titleRight`, imperative
|
|
338
|
+
`SectionHandlers.expand/collapse`). **Warning:** two modules export a `Section` — the
|
|
339
|
+
layout grammar's card-less region is the one in `section_heading`; import from
|
|
340
|
+
`@lotics/ui/section` only when you specifically want this collapsible titled block.
|
|
341
|
+
- **`section_stack`** — `SectionStack` · `SubsectionStack` — divided stacks that own the
|
|
342
|
+
between-block law: a fixed beat + hairline `Divider` BETWEEN blocks, skipping null
|
|
343
|
+
children (56 for the flat page's Sections, 24 for a section's Subsections); stop
|
|
344
|
+
hand-rolling gap + `<Divider />` pairs.
|
|
345
|
+
- **`section_card`** — `SectionCard`: a titled card with a one-line description and an
|
|
346
|
+
optional hairline-set `footer` (where `TrendFooter` and source notes go).
|
|
347
|
+
- **`stack`** — `Stack`: gap-spaced row/column; `useSeparator` inserts `Separator`s between
|
|
348
|
+
children.
|
|
349
|
+
- **`spacer`** — `Spacer` + `SpacerSize` (the fixed size scale).
|
|
350
|
+
- **`divider`** — `Divider`: the bare hairline.
|
|
351
|
+
- **`separator`** — `Separator`: a `Divider` pre-wrapped in vertical padding (`padding:
|
|
352
|
+
SpacerSize`, default 8) — the between-groups rule WITH breathing room, what `Stack
|
|
353
|
+
useSeparator` inserts and what menus/popovers put between option groups.
|
|
354
|
+
- **`container`** — `Container`: centers content at a max width (`ContainerSize` sm|md|lg,
|
|
355
|
+
`CONTAINER_SIZES`).
|
|
356
|
+
- **`page_header`** — `PageHeader`: the page's title band.
|
|
357
|
+
- **`page_content`** — `PageContent` + `PAGE_SIZES`: the page's padded, width-capped content
|
|
358
|
+
region.
|
|
359
|
+
- **`accordion`** — `Accordion` + `AccordionHeader`/`AccordionTitle`/`AccordionMeta`:
|
|
360
|
+
expandable section rows.
|
|
361
|
+
- **`tabs`** — `Tabs`: switch between content sections; WAI-ARIA tablist + roving tabindex;
|
|
362
|
+
each `TabOption` takes an optional `status` `ColorName` → a small attention dot before its
|
|
363
|
+
label, for a tab whose area needs work.
|
|
364
|
+
- **`segmented_control`** — `SegmentedControl`: the exclusive small-set switch.
|
|
365
|
+
- **`danger_zone`** — `DangerZone`: the destructive section — a soft danger-tinted frame
|
|
366
|
+
(the kit's `tint`/`solid`, never raw hex) + a danger heading + a description + a
|
|
367
|
+
destructive action slot (children, e.g. a `danger` Button); sits APART at the bottom of a
|
|
368
|
+
record/settings surface.
|
|
369
|
+
- **`landmark`** — `Landmark`: the semantic region wrapper — `kind`
|
|
370
|
+
banner|navigation|main|complementary|contentinfo|region maps to the matching HTML element
|
|
371
|
+
on web for screen-reader landmark navigation, `accessibilityRole` on native;
|
|
372
|
+
`accessibilityLabel` required for `region`.
|
|
373
|
+
- **`skip_link`** — `SkipLink`: the a11y bypass link — parked off-screen until keyboard
|
|
374
|
+
focus slides it in; `href="#targetId"` jumps past repeated nav into the main region. One
|
|
375
|
+
per app shell, FIRST in the tree; web-only.
|
|
376
|
+
- **`auto_sizer`** — `AutoSizer`: measures its own box via onLayout and renders the
|
|
377
|
+
render-prop child only once `{width,height}` exist; `autoSizeWidthOnly` /
|
|
378
|
+
`autoSizeHeightOnly`, `onResize`. For content needing pixel dimensions before first paint
|
|
379
|
+
— a canvas, a virtualized grid.
|
|
380
|
+
|
|
381
|
+
### Actions, links & menus
|
|
382
|
+
|
|
383
|
+
- **`button`** — `Button`: the labelled action; `title` MANDATORY (it is the accessible
|
|
384
|
+
name), `color` = emphasis/risk.
|
|
385
|
+
- **`icon_button`** — `IconButton`: the icon-only circular action (see
|
|
386
|
+
[Actions](#actions)).
|
|
387
|
+
- **`back_button`** — `BackButton`: the chevron-left `IconButton` (lg, secondary) heading a
|
|
388
|
+
screen/panel: `onPress` + translated `accessibilityLabel` (default "Back"); the one
|
|
389
|
+
go-back glyph — don't hand-roll it.
|
|
390
|
+
- **`link`** — `Link`: the EXTERNAL hyperlink — fixed underline+blue + `role="link"`;
|
|
391
|
+
`onPress` only (the consumer wires the opener).
|
|
392
|
+
- **`text_link`** — `TextLink`: underlined text that's optionally an `onPress` action or an
|
|
393
|
+
`href` link (a real `<a>` on web — middle-click / open-in-new-tab work), or plain
|
|
394
|
+
underlined text to wrap in your own pressable; inherits every `Text` prop; the neutral
|
|
395
|
+
counterpart to the fixed-blue `Link`.
|
|
396
|
+
- **`chip`** — `Chip`: the generic pill — pressable when `onPress` (announces as a button;
|
|
397
|
+
pass `accessibilityLabel` when children aren't self-describing text) + an
|
|
398
|
+
absolutely-positioned dismiss ✕ sibling when `onDismiss` (its name = `dismissTooltip` ??
|
|
399
|
+
the `chip.remove` locale slice). Suggestion pills → `SuggestionChip`.
|
|
400
|
+
- **`action_menu`** — `ActionMenu`: the ⋯ overflow menu (`ActionMenuItem[]`; danger items
|
|
401
|
+
last).
|
|
402
|
+
- **`menu_button`** — `MenuButton`: the menu/rail row (icon · title · `right` slot;
|
|
403
|
+
`selected`/`focused`/`danger`; `role` menuitem|button|option) — popover menus, outline
|
|
404
|
+
rails, section pickers.
|
|
405
|
+
- **`menu_list_item`** — `MenuListItem`: the richer listbox/menu row (title + description +
|
|
406
|
+
`right`; `selected` vs roving `focused` via `aria-activedescendant`).
|
|
407
|
+
- **`floating_action_bar`** — `FloatingActionBar`: the floating bulk-select action bar.
|
|
408
|
+
- **`pressable_row`** — `PressableRow`: THE register row — full-width hover/open wash,
|
|
409
|
+
`selected` (the open record) vs `marked` (ticked in bulk-select), forwards `ref` for
|
|
410
|
+
popover anchoring.
|
|
411
|
+
- **`pressable_highlight`** — `PressableHighlight`: the hover-wash + keyboard-focus-ring
|
|
412
|
+
`Pressable` under `MenuButton`/`Switcher`/custom pressable surfaces; its style-fn/children
|
|
413
|
+
receive `hovered` + `focusVisible`.
|
|
414
|
+
- **`focus_ring_pressable`** — `FocusRingPressable`: a Pressable that rings on keyboard
|
|
415
|
+
focus; the raw-control default.
|
|
416
|
+
- **`card_select_item`** — `CardSelectItem`: a bordered, card-shaped button; `selected` =
|
|
417
|
+
persistent ring (reads as `aria-pressed`) — the org-picker / entity-switcher item.
|
|
418
|
+
- **`switcher`** — `Switcher`: current-item trigger opening a popover of `MenuButton` items
|
|
419
|
+
(`SwitcherItem { id, label }`, `currentId`, `onSelect`) — the compact entity/workspace
|
|
420
|
+
switcher.
|
|
421
|
+
- **`count`** — `Count`: a small circular count bubble (`color` highlight|muted|red).
|
|
422
|
+
- **`shortcut_badge`** — `ShortcutBadge`: the keycap hint pill — a zinc-50 badge rendering a
|
|
423
|
+
shortcut from a raw string or `ShortcutDescriptor` (⌘B on Mac, Ctrl+B elsewhere); null on
|
|
424
|
+
small screens. `TextInputField shortcut` renders it built-in.
|
|
425
|
+
- **`keyboard`** — `isMac` · `ShortcutDescriptor` · `formatShortcut` (platform-aware
|
|
426
|
+
shortcut formatting behind `ShortcutBadge`).
|
|
427
|
+
|
|
428
|
+
### Badges, status & feedback
|
|
429
|
+
|
|
430
|
+
- **`badge`** — `Badge`: the tonal pill; `variant="dot"` = pill-less colored dot + label,
|
|
431
|
+
the LIGHT state indicator.
|
|
432
|
+
- **`status_badge`** — `StatusBadge`: an enabled/disabled pulse badge (`enabled` + `label`).
|
|
433
|
+
- **`option_badge`** — `OptionBadge`: a select value as its configured colored badge.
|
|
434
|
+
- **`callout`** — `Callout` · `CalloutTitle` · `CalloutText` · `CalloutActions` (`tone`
|
|
435
|
+
info|success|warning|error|neutral): inline status band.
|
|
436
|
+
- **`empty_state`** — `EmptyState`: centered placeholder for an empty list/filter result —
|
|
437
|
+
`message` + `hint`, optional `icon` anchor and `action` CTA.
|
|
438
|
+
- **`completion_state`** — `CompletionState`: the "all done" terminal state.
|
|
439
|
+
- **`skeleton`** — `Skeleton`: loading placeholder blocks.
|
|
440
|
+
- **`loading`** — `Loading`: the centered indeterminate loading state (composes
|
|
441
|
+
`DotsIndicator`).
|
|
442
|
+
- **`activity_indicator`** — `ActivityIndicator`: the bare spinner.
|
|
443
|
+
- **`dots_indicator`** — `DotsIndicator`: three looping bouncing dots (`size`/`color`); the
|
|
444
|
+
indeterminate "working/typing" pulse `Loading` composes; use bare beside a caption while
|
|
445
|
+
an agent thinks. Determinate work → `ProgressBar`.
|
|
446
|
+
- **`animation_fade_in`** — `AnimationFadeIn`: the mount transition — children fade (+
|
|
447
|
+
optional `translateY` rise) into place on first render, once; the entrance polish
|
|
448
|
+
Accordion/Timeline/Stepper/AgentRun rows use.
|
|
449
|
+
|
|
450
|
+
### Pickers & selection controls
|
|
451
|
+
|
|
452
|
+
- **`picker`** — `Picker`: native `<select>`, plain label-only single, native typeahead;
|
|
453
|
+
also home of the shared `PickerOption` type.
|
|
454
|
+
- **`select`** — `Select`: rich/custom-rendered, single/multi, select-all, chips via
|
|
455
|
+
`renderSelected(item, { remove })` + `searchable` + `allowCustom` — the tag field is just
|
|
456
|
+
a multi Select; opens `OptionList`.
|
|
457
|
+
- **`option_list`** — `OptionList`: the ONE shared searchable listbox body every selector
|
|
458
|
+
opens — single/multi, optional internal search, create row, keyboard + native-`<select>`
|
|
459
|
+
typeahead; host it directly in a `Popover`/`Dialog` for a command palette.
|
|
460
|
+
- **`combobox`** — COMPOUND single-select editable search: `Combobox` root +
|
|
461
|
+
`ComboboxInput` + `ComboboxContent`, optional `ComboboxEmpty`/`ComboboxFooter`,
|
|
462
|
+
`useCombobox()`; over the shared option-list engine; browses on focus; no `multi` —
|
|
463
|
+
multi-value chips → `Select multi`.
|
|
464
|
+
- **`member_select`** — `MemberSelect` + `MEMBER_UNASSIGNED`: the member picker (a `Select`
|
|
465
|
+
of `MemberChip` options; `unassignedLabel` prepends the unassigned option).
|
|
466
|
+
- **`radio_picker`** — `RadioPicker`: labelled radio group for a small visible single
|
|
467
|
+
choice.
|
|
468
|
+
- **`chip_group`** — `ChipGroup`: pill single-select over a small visible set.
|
|
469
|
+
- **`checkbox`** — `Checkbox`: the bare square check control.
|
|
470
|
+
- **`checkbox_input`** — `CheckboxInput`: checkbox + label as one labelled control.
|
|
471
|
+
- **`switch`** — `Switch`: the bare toggle (a cell/toolbar boolean).
|
|
472
|
+
- **`counter`** — `Counter`: increment/decrement numeric stepper (filter facet).
|
|
473
|
+
- **`range_slider`** — `RangeSlider` + `rangeSummary`: min–max range control (filter facet).
|
|
474
|
+
|
|
475
|
+
### Form & text inputs
|
|
476
|
+
|
|
477
|
+
- **`text_input_field`** — `TextInputField`: the standard text input (multiline grows via
|
|
478
|
+
the auto-grow engine; `shortcut` renders a `ShortcutBadge`).
|
|
479
|
+
- **`number_input`** — `NumberInput`: numeric input; `format` for currency/units.
|
|
480
|
+
- **`search_input`** — `SearchInput`: the search box for toolbars/filters.
|
|
481
|
+
- **`form_field`** — `FormField` + `useFormField`: label / description / error / `optional`
|
|
482
|
+
marker wrapper; the fieldset grid cell (`half`/`full` widths — see the data-entry patterns
|
|
483
|
+
doc indexed in [AGENTS.md](../AGENTS.md)).
|
|
484
|
+
- **`form_text_input`** — `FormTextInput`: `FormField` wrapping a `TextInputField`; the
|
|
485
|
+
one-line labeled text field. Controls without a Form\* twin just wrap in `FormField`.
|
|
486
|
+
- **`form_switch`** — `FormSwitch`: the FormField-labeled twin of `Switch` — toggle +
|
|
487
|
+
clickable `label` (pressing it flips the value) + optional `description`/`error`; the
|
|
488
|
+
settings-form boolean row. A bare toggle in a cell/toolbar → `Switch`; a full-row menu
|
|
489
|
+
toggle → `SwitchButton`.
|
|
490
|
+
- **`form_picker`** — `FormPicker`: `FormField` wrapping a `Picker` — one labeled native
|
|
491
|
+
select.
|
|
492
|
+
- **`form_date_picker`** — `FormDatePicker`: `FormField` wrapping a `DatePicker` — one
|
|
493
|
+
labeled date field. (Omits `style`; for a grid cell use a bare `FormField style={half}`
|
|
494
|
+
around `DatePicker`.)
|
|
495
|
+
- **`switch_button`** — `SwitchButton`: the full-ROW toggle — a `PressableHighlight` row
|
|
496
|
+
(optional icon + medium title left, `Switch` pinned right) where the whole row IS the
|
|
497
|
+
switch (`accessibilityRole="switch"`, the inner Switch read-only). The settings-panel/menu
|
|
498
|
+
row toggle.
|
|
499
|
+
- **`use_form`** — `useForm`: THE batch draft-form state hook — `values` = `initialValues` +
|
|
500
|
+
an edits overlay (a revalidation refreshes untouched fields, no sync effect), `validate`
|
|
501
|
+
(sync/async, gates submit, editing clears the field's error), `onSubmit(values, helpers)`
|
|
502
|
+
with a re-entry-guarded `submitting`, and `changes`/`changed` — the touched-fields diff
|
|
503
|
+
that feeds DIFF-writes (send only edited fields); `setFieldValue` (curried or direct) +
|
|
504
|
+
`setValues`/`setFieldError`/`reset`. Pairs with the Form\* family for dialog/settings
|
|
505
|
+
forms — the draft-validate-COMMIT-together twin of the self-persisting Inline\* editors.
|
|
506
|
+
- **`scan_field`** — `ScanField`: the scan/verify input (`ScanStatus` idle|match|mismatch).
|
|
507
|
+
|
|
508
|
+
### Dates & times
|
|
509
|
+
|
|
510
|
+
- **`date_picker`** — `DatePicker` (+ `DatePickerPanel`, `DatePickerLabels`): the field-form
|
|
511
|
+
date (and datetime) picker.
|
|
512
|
+
- **`date_calendar`** — `Calendar`: the bare month grid — `mode="single"` or `"range"`
|
|
513
|
+
(`{start,end}` — two months side by side on desktop), month/year pickers + arrows,
|
|
514
|
+
localized weekday/month names via BCP-47 `locale`, `firstDayOfWeek` (default Monday),
|
|
515
|
+
`ref.navigateToMonth`. The engine `DatePicker`/`DateFilter` wrap in field chrome — reach
|
|
516
|
+
for it bare only when the calendar lives permanently on the surface, not behind a field.
|
|
517
|
+
- **`date_filter`** — `DateFilter`: the single/range date+time filter panel — presets
|
|
518
|
+
(`PresetId`), calendar, optional time segments; the body `DateRangeFilterField` opens.
|
|
519
|
+
- **`date_range_filter_field`** — `DateRangeFilterField`: the register's period filter field
|
|
520
|
+
(presets + footer + time-segment a11y; localized via the `dateRange` locale slice).
|
|
521
|
+
- **`time_picker`** — `TimePicker`: the time-of-day input.
|
|
522
|
+
|
|
523
|
+
### Inline editing & record surfaces
|
|
524
|
+
|
|
525
|
+
- **`inline_edit`** — `useInlineEdit` + `InlineEditView` / `InlineEditFrame` +
|
|
526
|
+
`INLINE_CONTROL_HEIGHT` (40) + `inlineValueTextStyle`: the engine custom inline editors
|
|
527
|
+
join through — a view ⇄ edit toggle, a draft buffer, async `onSave` with the spinner
|
|
528
|
+
INSIDE the control and inline error, commit on blur (Enter saves, Escape reverts) or
|
|
529
|
+
`controls="buttons"`; `background="tint" | "transparent"` (the zinc-50 editability chip vs
|
|
530
|
+
flat for dense uniformly-editable surfaces).
|
|
531
|
+
- **`inline_text_input`**, **`inline_number_input`** (`format` for currency/units),
|
|
532
|
+
**`inline_select`**, **`inline_member_select`**, **`inline_date_picker`**
|
|
533
|
+
(`format="datetime"`, `optionalTime`), **`inline_time_picker`** — the Inline\* per-field
|
|
534
|
+
editors; `InlineSelect`/`InlineMemberSelect` render the resting value like its option —
|
|
535
|
+
`renderOptionContent` by default, `renderSelected` to override — a chip/badge at rest, not
|
|
536
|
+
just text.
|
|
537
|
+
- **`inline_tag_select`** — `InlineTagSelect`: the MULTI inline editor — a tag SET in the
|
|
538
|
+
inline vocabulary: selected tags render as badges inside the standard chip (`renderTag`),
|
|
539
|
+
clicking floats a multi `OptionList` (checkbox rows), CLOSING commits the new set in one
|
|
540
|
+
`onSave` — never a borderless `Select` posing as an inline field.
|
|
541
|
+
- **`inline_static`** — `InlineStatic`: a READ-ONLY value matching the Inline\* box metrics
|
|
542
|
+
EXACTLY (height, padding, 1px transparent border) so a non-editable field — a computed
|
|
543
|
+
total, a system ID, a synced/locked value — aligns pixel-for-pixel in the same column;
|
|
544
|
+
non-interactive, NOT a disabled input; `muted`/`tabular`/`align="right"` for a number
|
|
545
|
+
column, `weight="medium"` to emphasise a total among plain rows.
|
|
546
|
+
- **`detail_row`** — `DetailTable` + `DetailRow` — the record field grid. `DetailRow`:
|
|
547
|
+
label+value row for drawer/peek detail; in FORM mode (`labelWidth` set) the value column
|
|
548
|
+
FILLS the row so a stack of inline editors all span the same width + none jumps wider on
|
|
549
|
+
edit; optional `trailing` slot renders a right-side action/badge after the value (units
|
|
550
|
+
belong IN the value via `InlineNumberInput format`). `DetailTable`: the compound parent of
|
|
551
|
+
a row STACK — `labelWidth` (default 130) / `trailingWidth` / `minHeight` (default 40, the
|
|
552
|
+
inline-control grid) declared ONCE + the 6px row gap; with `trailingWidth` every row
|
|
553
|
+
reserves the trailing column so value cells share one width and trailing items align at
|
|
554
|
+
one x, like a table. RESPONSIVE with no prop: it measures its own container (onLayout, not
|
|
555
|
+
the viewport — works inside a Drawer; the unmeasured first frame renders opacity-0 so the
|
|
556
|
+
first PAINT is already in the right mode) and when the columns would crush the value cell
|
|
557
|
+
it STACKS every row (the label above a full-width value row in the FormField label
|
|
558
|
+
grammar; trailing beside the value); raise `minValueWidth` (default 160) when a cell holds
|
|
559
|
+
MORE than one editor so the table stacks earlier. Two tables on one page share one grid by
|
|
560
|
+
repeating the same labelWidth/trailingWidth. Worked example:
|
|
561
|
+
[`tpl_record`](../examples/tpl_record.tsx).
|
|
562
|
+
- **`record_summary`** — `RecordSummary`: the identity band of a record detail/drawer — ONE
|
|
563
|
+
row: `title` xl semibold tabular · `subtitle` sm muted · `status` Badge slot · optional
|
|
564
|
+
`metric` {label,value,tone,note} pinned right, the band's ONE accent. The record's FIELDS
|
|
565
|
+
never live in the header: compose them as `DetailTable`s in the sections below. Replaces
|
|
566
|
+
hand-rolled record headers (mixed scales, several competing figures, color noise).
|
|
567
|
+
- **`ledger`** — `Ledger` + `LedgerGroup` + `LedgerRow` + `LedgerTotal` — the record-level
|
|
568
|
+
money list: charge/receipt GROUPS with sums in their headers, every figure on ONE
|
|
569
|
+
right-aligned tabular column, `peek` turns a row into a pressable door floating its
|
|
570
|
+
particulars in an anchored popover (put links INSIDE the peek — never a button in a
|
|
571
|
+
button; `reference` is the trailing-link alternative for static rows), `LedgerTotal` = the
|
|
572
|
+
divider-set emphasized close with `zeroLabel` for settled. The financial-statement grammar
|
|
573
|
+
at record density; worked example: [`tpl_item_list`](../examples/tpl_item_list.tsx)
|
|
574
|
+
drawer.
|
|
575
|
+
- **`peek`** — `Peek`: drill-down for inline references — press a name/id where it appears
|
|
576
|
+
and get its details in an anchored popover, without leaving the screen; keep the content a
|
|
577
|
+
summary with ONE action to the full record.
|
|
578
|
+
|
|
579
|
+
### Lists, tables & registers
|
|
580
|
+
|
|
581
|
+
- **`list`** — `List`: children separated by hairline `Divider`s.
|
|
582
|
+
- **`list_item`** — `ListItem`: the plain list row.
|
|
583
|
+
- **`table`** — `Table` + `TableRow` + `TableCell`: the paginated high-volume register
|
|
584
|
+
(columns defined once, `sortLabels` localizable; rows are `PressableRow`-based).
|
|
585
|
+
- **`sort_header`** — `SortHeader` + `SortState`/`SortDir` + `cycleSort` + `sortBy` +
|
|
586
|
+
`SortHeaderLabels`: the sortable column header and the sort-state helpers `Table`/
|
|
587
|
+
`DataGrid` consumers drive.
|
|
588
|
+
- **`data_grid`** — `DataGrid` + `gridRowStyle`: the inline-managed grouped table (see
|
|
589
|
+
[Tabular data](#tabular-data--pick-by-scale--intent)); `labels` localizes the sort-header
|
|
590
|
+
a11y via `SortHeaderLabels`.
|
|
591
|
+
- **`pagination`** — `Pagination` (+ `PaginationLabels`): the register's pager.
|
|
592
|
+
- **`column_filter`** — `ColumnFilter` + `columnFilterToConditions` +
|
|
593
|
+
`isColumnFilterActive` + `columnFilterSummary`: the typed per-column filter pill; for a
|
|
594
|
+
register filtering on several columns.
|
|
595
|
+
- **`filter_chip`** — `FilterChip` + `selectSummary`: the toolbar filter pill hosting a
|
|
596
|
+
facet (options, a `RangeSlider`, a `Counter`).
|
|
597
|
+
- **`summary_line`** — `SummaryLine`: the light inline summary of a register/list's FILTERED
|
|
598
|
+
view, sits below the toolbar; NOT the boxed dashboard `kpi_strip` band.
|
|
599
|
+
- **`use_selection`** — `useSelection`: always-on multi-select state for a register/list —
|
|
600
|
+
the `selected` Set + `toggle`/`setAll`/`allSelected`/`indeterminate`/`count`/`clear`;
|
|
601
|
+
selectability gating stays with the caller. The checkbox-always-visible counterpart to
|
|
602
|
+
`use_selection_mode`.
|
|
603
|
+
- **`use_selection_mode`** — `useSelectionMode`: `{ active, selected, enter, exit, toggle,
|
|
604
|
+
toggleAll }` — an agnostic enter/exit multi-select state machine (string ids; pairs with
|
|
605
|
+
`FileThumbnailGrid selectedIds` and gated-CRUD flows).
|
|
606
|
+
|
|
607
|
+
### Tasks & checklists
|
|
608
|
+
|
|
609
|
+
- **`check_circle`** — `CheckCircle`: the completion ring — an empty ring that springs to a
|
|
610
|
+
filled check when done, distinct from the square checkbox; the task/to-do/checklist
|
|
611
|
+
toggle.
|
|
612
|
+
- **`checklist`** — `Checklist` + `ChecklistRow` — the record-scoped checklist COMPOUND: it
|
|
613
|
+
owns GEOMETRY only (row minHeight 32, gap 12, ring/title alignment, ONE `trailingWidth` so
|
|
614
|
+
assignee cells column-align) while content stays composed — `control` takes the
|
|
615
|
+
`CheckCircle` (omit onChange = read-only ring), children the struck transparent
|
|
616
|
+
`InlineTextInput`, `trailing` an `InlineMemberSelect`, `menu` the row's ⋯ options
|
|
617
|
+
(`{items: ActionMenuItem[], accessibilityLabel}`) — Delete lives BEHIND the menu,
|
|
618
|
+
danger-styled and last, never a bare ✕ (omit on read-only rows). NARROW surfaces (a
|
|
619
|
+
drawer/peek checklist) put the editors on the `meta` line instead of `trailing` — the
|
|
620
|
+
second line indents past the ring so the TITLE keeps the full width; wide surfaces use
|
|
621
|
+
`trailing`; never both. SUGGESTIONS are never rows: offer the commons as `SuggestionChip`s
|
|
622
|
+
under the list. Close the list with `CaptureRow`. There is deliberately NO monolithic Task
|
|
623
|
+
component — richer task-management rows compose their own anatomy directly.
|
|
624
|
+
- **`suggestion_chip`** — `SuggestionChip`: the dismissible SUGGESTION pill — an item the
|
|
625
|
+
record could have but doesn't yet (a common task, an expected line) as a `Chip` whose
|
|
626
|
+
press MATERIALIZES it (plus glyph + label, one tuned anatomy) and whose ✕ refuses it;
|
|
627
|
+
filter out labels already present; suggestions never count in totals. Chrome via the
|
|
628
|
+
`suggestionChip` locale slice.
|
|
629
|
+
- **`capture_row`** — `CaptureRow`: the add-an-item row closing an editable list — dashed
|
|
630
|
+
empty ring + borderless input on the item-title inset + a primary Save that appears on
|
|
631
|
+
type; Enter commits too. Controlled: `value`/`onChangeText`/`onSubmit`. One shape wherever
|
|
632
|
+
a list grows in place — task checklists, simple item lists.
|
|
633
|
+
|
|
634
|
+
### Overlays & navigation
|
|
635
|
+
|
|
636
|
+
- **`portal`** — `PortalHost` + `Portal`: the floating-content mount point; the app root
|
|
637
|
+
needs ONE `PortalHost` or `Dialog`/`Popover`/`Tooltip`/`Alert`/`OptionList` cannot render.
|
|
638
|
+
- **`dialog`** — `Dialog` (+ `DialogHeader`/`DialogHeaderTitle`/`DialogFooter`, `useDialog`,
|
|
639
|
+
`useDialogNavigation`): the centered card over a scrim; BAKES a screen router in
|
|
640
|
+
(`<Dialog><Screen route="">…` — see `screen_router`).
|
|
641
|
+
- **`drawer`** — `Drawer` + `DrawerFooter`: the docked side panel with scrim; the register
|
|
642
|
+
row's edit surface.
|
|
643
|
+
- **`modal`** — `Modal` + `ModalHeader` + `ModalBody` + `ModalFooter` — the full-bleed,
|
|
644
|
+
edge-to-edge takeover: an OPAQUE surface that COVERS THE WHOLE SCREEN, so unlike Dialog
|
|
645
|
+
(centered card WITH scrim) and Drawer (docked panel WITH scrim) there is nothing behind it
|
|
646
|
+
to dim — NO scrim, NO backdrop. Lays children as a flex column: a pinned ModalHeader
|
|
647
|
+
(eyebrow/title + an actions slot + close), a flex:1 scrolling ModalBody, a pinned
|
|
648
|
+
ModalFooter (the commit bar, same chrome as DialogFooter/DrawerFooter). Reach for it for a
|
|
649
|
+
focused capture / multi-step wizard / a console the user steps INTO; pick Dialog when the
|
|
650
|
+
surface is a card the user can see context around.
|
|
651
|
+
- **`popover`** — `Popover` + `PopoverTrigger` + `PopoverContent` (+ `PopoverHeader`,
|
|
652
|
+
`PopoverFooter`; `side`/`align`; `disableBodyScroll` ONLY for children that manage their
|
|
653
|
+
own scroll, like `OptionList`): the anchored floating surface. **NON-MODAL**: the anchored
|
|
654
|
+
popover has NO blocking overlay — the rest of the page stays interactive, and clicking another
|
|
655
|
+
control both dismisses this popover AND activates that control in one click; clicking outside,
|
|
656
|
+
scrolling an ancestor, or Escape dismisses. Only the `small` (bottom-sheet) presentation is
|
|
657
|
+
modal (scrim). `PopoverContent` already insets its body 12px — put content directly in it,
|
|
658
|
+
NEVER add your own padding `View` (that double-pads); title/actions go in `PopoverHeader` /
|
|
659
|
+
`PopoverFooter`.
|
|
660
|
+
- **`popover_nav`** — `usePopoverNav` + `PopoverScreen` + `PopoverNavHeader` — the popover's
|
|
661
|
+
built-in mini-router: EVERY `Popover` provides the nav context (`navigate(route)` pushes,
|
|
662
|
+
`goBack`, `currentRoute`, `canGoBack`; resets on close), `PopoverScreen route=""` is the
|
|
663
|
+
root and screens render conditionally (unmounted when inactive — no scroll preservation),
|
|
664
|
+
`PopoverNavHeader` is the title row whose back chevron auto-appears while `canGoBack`
|
|
665
|
+
(`right` slot, `backLabel`). For a multi-screen menu inside ONE popover; route PATTERNS,
|
|
666
|
+
`params`, and stacked-alive screens are `screen_router`'s job.
|
|
667
|
+
- **`screen_router`** — `ScreenRouter` + `Screen` + `useScreenRouter` — the SCREENS
|
|
668
|
+
compound: a flat navigation stack (`navigate("/case/:id")` pushes, `goBack` pops,
|
|
669
|
+
`canGoBack`, route `params`; stacked screens stay mounted `display:none` so scroll
|
|
670
|
+
survives the round trip). Dialog bakes a router in; ANY other container hosts the
|
|
671
|
+
standalone `<ScreenRouter>` — and it wraps AROUND the container so the CHROME can read the
|
|
672
|
+
stack (a Drawer drilling into a LINKED record swaps its header to a BACK `IconButton`
|
|
673
|
+
while `canGoBack`). Key the router by record id so stepping between records resets the
|
|
674
|
+
stack. Worked example: [`tpl_item_list`](../examples/tpl_item_list.tsx) drawer.
|
|
675
|
+
- **`route_matching`** — pure `:param` route-pattern utilities — `routeMatches` /
|
|
676
|
+
`parseRouteParams` / `findBestPattern` (exact beats parameterized) / `shouldRouteMatch`;
|
|
677
|
+
the matching core under `ScreenRouter`/`Dialog`.
|
|
678
|
+
- **`tooltip`** — `Tooltip` (+ `TooltipTrigger`, `TooltipProvider`, `useTooltip`): the short
|
|
679
|
+
hover label.
|
|
680
|
+
- **`info_popover`** — `InfoPopover`: the ⓘ button opening a 280px popover of explanatory
|
|
681
|
+
`text`; the middle ground between Tooltip (short hover label) and composing Popover (rich
|
|
682
|
+
content) — it is what `SectionHeadingTitle.info`/`CardHeaderTitle.info` render.
|
|
683
|
+
- **`alert`** — `Alert` (+ `AlertButton`/`AlertOptions`): the blocking confirm —
|
|
684
|
+
`Alert.alert(title, message, [{cancel}, {destructive}])`.
|
|
685
|
+
- **`overlay_scope`** — `isOverlayScopeActive` / `pushOverlayScope` / `useOverlayScope`: the
|
|
686
|
+
module-level open-overlay counter every overlay primitive reports into; the host's
|
|
687
|
+
shortcut registry reads it synchronously to floor page-level shortcuts while any overlay
|
|
688
|
+
is open. Lives in the primitives — never call it from a screen.
|
|
689
|
+
- **`use_section_nav`** — `useSectionNav`: scroll-spy for a LONG record surface with a left
|
|
690
|
+
outline rail — keys in page order → `{scrollRef, onScroll, register(key)→onLayout,
|
|
691
|
+
jumpTo(key), activeKey}`; rail items are `MenuButton`s (`selected={activeKey===key}`);
|
|
692
|
+
section wrappers must be DIRECT children of the ScrollView content. On NARROW containers
|
|
693
|
+
the rail becomes a PINNED bar naming the CURRENT section that opens a full-page
|
|
694
|
+
section-picker `Modal` — never a horizontal tab strip. Worked example:
|
|
695
|
+
[`tpl_record`](../examples/tpl_record.tsx).
|
|
696
|
+
- **`scroll_to_bottom`** — `ScrollToBottom`: the floating jump-to-latest circle button for a
|
|
697
|
+
chat/feed; ONLY the affordance — the caller owns positioning, visibility, and the actual
|
|
698
|
+
scroll.
|
|
699
|
+
|
|
700
|
+
### Numbers & charts
|
|
701
|
+
|
|
702
|
+
- **`kpi_card`** — `KPICard`: one headline metric in a card (trend chip slot, sub-caption,
|
|
703
|
+
ⓘ definition).
|
|
704
|
+
- **`kpi_strip`** — `KPIStrip`: the boxed dashboard stat band.
|
|
705
|
+
- **`metric`** — `Metric`: a bare headline figure (`format`
|
|
706
|
+
currency|number|percentage|none, `tone`, `size` sm|md|lg|hero).
|
|
707
|
+
- **`trend_chip`** — `TrendChip`: the signed ±% delta chip.
|
|
708
|
+
- **`trend_footer`** — `TrendFooter`: the "Up X% vs last period" caption under a chart card
|
|
709
|
+
— signed `value` (0 = flat), `periodLabel`, optional `detail`; `goodDirection="down"`
|
|
710
|
+
flips green/red for metrics where down is good; SKIP it when there's no comparator.
|
|
711
|
+
Direction words via the `trendFooter` locale slice; goes in `SectionCard footer`.
|
|
712
|
+
- **`sparkline`** — `Sparkline`: the inline mini trend line.
|
|
713
|
+
- **`bar_chart`** / **`line_chart`** / **`pie_chart`** — `BarChart` / `LineChart` /
|
|
714
|
+
`PieChart`: the canonical SVG chart set (no recharts).
|
|
715
|
+
- **`ring_gauge`** — `RingGauge`: single-fraction circular gauge.
|
|
716
|
+
- **`progress_bar`** — `ProgressBar`: the determinate meter; `compact` = ONE row, track + a
|
|
717
|
+
plain sm tabular count beside it.
|
|
718
|
+
- **`stacked_progress_bar`** — `StackedProgressBar`: one whole split across segments (a
|
|
719
|
+
status mix on one bar).
|
|
720
|
+
- **`step_progress`** — `StepProgress`: N-of-M dots/segments progress.
|
|
721
|
+
- **`breakdown`** — `Breakdown`: a stacked bar + ranked share rows, pressable to drill;
|
|
722
|
+
`maxRows` folds the long tail behind a localized "Show N more" toggle
|
|
723
|
+
(`BreakdownLabels`).
|
|
724
|
+
- **`funnel`** — `Funnel`: conversion funnel — narrowing bars + the step rate as a bold
|
|
725
|
+
aligned headline row, count below; `orientation` vertical|horizontal,
|
|
726
|
+
`onSelect`/`selectedKey` press-to-drill; the subset/drop-off sibling of
|
|
727
|
+
`stacked_progress_bar`'s whole-split.
|
|
728
|
+
- **`status_grid`** — `StatusGrid` + `StatusLegend`: the per-slot state grid (occupancy,
|
|
729
|
+
availability) and its legend.
|
|
730
|
+
- **`heatmap`** — `Heatmap`: density cross-tab — colour-only, no numbers; "where does it
|
|
731
|
+
cluster".
|
|
732
|
+
- **`matrix`** — `Matrix`: the PIVOT cross-tab — band-compound `Matrix` root +
|
|
733
|
+
`Matrix.Header` (corner + axis labels) + `Matrix.Grid` (`display` number|heat|both — the
|
|
734
|
+
cells, pressable, the value IN the cell) + `Matrix.Totals` (row + column + grand) +
|
|
735
|
+
`Matrix.Legend`. Pick over `Heatmap` when the NUMBER and totals matter.
|
|
736
|
+
- **`matrix_totals`** — the data layer for `Matrix`: `matrixTotals` (the cross-tab
|
|
737
|
+
aggregation) + `MatrixAxisItem` / `MatrixCellRef` / `MatrixTotalsResult`; React-free, so a
|
|
738
|
+
KPI can be driven off the same numbers the grid shows.
|
|
739
|
+
- **`legend_item`** — `LegendItem`: one swatch + label of a chart legend.
|
|
740
|
+
- **`remainder_meter`** — `RemainderMeter`: allocated-vs-remaining meter
|
|
741
|
+
(`RemainderMeterLabels` localized via the provider).
|
|
742
|
+
- **`allocation_row`** — `AllocationRow`: one target's row in an allocation surface (pairs
|
|
743
|
+
with `RemainderMeter`).
|
|
744
|
+
|
|
745
|
+
### Scheduling & time
|
|
746
|
+
|
|
747
|
+
- **`calendar`** — the calendar views: `CalendarView` (the composed month/week surface),
|
|
748
|
+
`TimeGridView`, `MonthView`, plus the layout/date helpers (`layoutDayColumns`,
|
|
749
|
+
`packEventLanes`, `addDays`, `startOfWeek`, …) and `CalendarEvent`/`CalendarLabels` types.
|
|
750
|
+
**Limitation:** labels are not provider-wired — pass `labels`
|
|
751
|
+
(`DEFAULT_CALENDAR_LABELS` is English).
|
|
752
|
+
- **`gantt`** — `GanttView` + scale helpers (`barGeometry`, `axisRange`, `buildTicks`,
|
|
753
|
+
`pxPerDay`) + `GanttTask`/`GanttLabels` types. **Limitation:** labels are not
|
|
754
|
+
provider-wired — pass `labels` (`DEFAULT_GANTT_LABELS` is English).
|
|
755
|
+
- **`timeline`** — `Timeline`: a heterogeneous event LOG — per-row icon + expandable
|
|
756
|
+
details; models the past, NOT progress.
|
|
757
|
+
- **`stepper`** — `Stepper` + `Step` — done/current/upcoming/warning/complete progress on a
|
|
758
|
+
track (horizontal) or spine (vertical); compound `<Step status>children` OR data
|
|
759
|
+
`steps[]`+`current`; **navigable** via `Step.onPress` (both orientations — the whole step
|
|
760
|
+
is the tap target) + `active` to wash the selected one, so it doubles as a section/phase
|
|
761
|
+
switcher; the guided-run / agent-feed primitive.
|
|
762
|
+
|
|
763
|
+
### Files
|
|
764
|
+
|
|
765
|
+
- **`mime`** — pure MIME predicates: `isImageMimeType` / `isVideoMimeType` /
|
|
766
|
+
`isAudioMimeType` / `isPdfMimeType` / `isExcelMimeType` / `isCsvMimeType` /
|
|
767
|
+
`isDocxMimeType` / `isPreviewableMimeType`.
|
|
768
|
+
- **`download`** — `downloadFileFromUrl(url, filename, { credentials? })`: fetch+blob+anchor
|
|
769
|
+
download that works inside sandboxed iframes (where `window.open` is silently dropped);
|
|
770
|
+
`credentials` defaults to `same-origin` — pass `"include"` only for auth-gated same-site
|
|
771
|
+
proxy URLs, never for presigned URLs.
|
|
772
|
+
- **`file_picker`** — `pickFiles({ accept?, multiple? }) → Promise<File[]>`: opens the
|
|
773
|
+
browser file dialog imperatively — the trigger half behind every Add-file CTA; cancel
|
|
774
|
+
resolves `[]` on modern engines.
|
|
775
|
+
- **`file_dropzone`** — `FileDropzone`: the drag-drop capture well (`onFiles`, `accept`,
|
|
776
|
+
`label`/`hint`/`dropLabel`, `height`); click falls back to a picker.
|
|
777
|
+
- **`files_editor`** — `FilesEditor` — THE all-in-one attachment field: `FileGrid` + a
|
|
778
|
+
toolbar (Upload primary · Select · Download all) that swaps into a batch SELECT mode
|
|
779
|
+
(Select all · a Menu of Download/Share/Delete · Done; the per-tile ✕ is select-mode-only,
|
|
780
|
+
never in the default view; `selectTileRemove={false}` drops even that so delete is
|
|
781
|
+
menu-only) + built-in gallery (Download + inline preview; no "open in new tab") +
|
|
782
|
+
Alert-confirmed remove; host wires `files` + `onAdd`/`onRemove` (+ optional `uploads`,
|
|
783
|
+
`onShareSelected`, `readOnly`, `selectTileRemove`, `labels`, `galleryLabels`,
|
|
784
|
+
`gridMaxHeight` — cap the grid height so it scrolls and the toolbar pins, for a
|
|
785
|
+
popover/drawer). Use `FileGrid`/`FileRows` bare only when you own the chrome.
|
|
786
|
+
- **`file_grid`** — `FileGrid` + the `FileUpload`/`PendingUpload` types: the upload-aware
|
|
787
|
+
grid — `files` are the saved/completed `DisplayFile`s, `uploads` is the LIVE add-queue; it
|
|
788
|
+
interleaves both and renders each in-flight tile itself with a labeled status overlay.
|
|
789
|
+
Tiles FILL the container width at a uniform size (≥ `minItemWidth`, default 96); pass
|
|
790
|
+
`columns` for a fixed count, `itemSize` for exact tiles, or `singleRow` to fit one row and
|
|
791
|
+
collapse the rest into a clickable "+N" overflow tile (`onOverflowPress(hiddenCount)`).
|
|
792
|
+
CRUD via `onFilePress` / `onDisplayRemove` / `onUploadRemove` / `onRetry` / `onRetryAll`;
|
|
793
|
+
localize with `labels.upload` (an `UploadStatusLabels`).
|
|
794
|
+
- **`uploading_thumbnail`** — `UploadingThumbnail` + `UploadStatus`/`UploadStatusLabels`:
|
|
795
|
+
the single in-flight upload tile `FileGrid` renders; reach for it only when hand-rolling a
|
|
796
|
+
non-grid upload layout.
|
|
797
|
+
- **`file_thumbnail`** — `FileThumbnail` + `DisplayFile` + `THUMBNAIL_SIZE` /
|
|
798
|
+
`COMPACT_THUMBNAIL_SIZE` + `getMediaIcon`: the completed tile — the right surface per
|
|
799
|
+
MIME: image thumbnail · a doc tile with the `FileBadge` centered + a single-line filename
|
|
800
|
+
· media card; `isTemplate` overlays a TMPL marker.
|
|
801
|
+
- **`file_thumbnail_grid`** — `FileThumbnailGrid` (+ the generic `ThumbnailGrid`):
|
|
802
|
+
display-only square-tile grid; `selectedIds` + `onFilePress` pair with
|
|
803
|
+
`useSelectionMode()` for gated select-mode CRUD.
|
|
804
|
+
- **`file_row`** — `FileRow`: a horizontal file/document LINE — `FileBadge` or a
|
|
805
|
+
`placeholder` + the FULL name + a meta line + a composable `trailing` slot; `onPress`
|
|
806
|
+
makes the whole row a pressable door, `trailing` stays an independently-pressable sibling;
|
|
807
|
+
for attachment lists / message files / document checklists where a square tile truncates
|
|
808
|
+
the name.
|
|
809
|
+
- **`file_rows`** — `FileRows`: batteries-included file list — row press → built-in gallery
|
|
810
|
+
+ a ⋯ Download/Open-external/Remove menu; composes `FileRow` + `ActionMenu` +
|
|
811
|
+
`FileGalleryModal`; localized via the `gallery` locale slice.
|
|
812
|
+
- **`file_badge`** — `FileBadge`: the two-tone file-type mark (PDF / XLSX / DOCX / video /
|
|
813
|
+
audio …) driven by MIME.
|
|
814
|
+
- **`file_preview`** — `FilePreview`: the universal inline preview — image/PDF/video/audio +
|
|
815
|
+
Word via `@lotics/docx` + Excel/CSV via `@lotics/xlsx`; the heavy engines (pdf.js ·
|
|
816
|
+
`@lotics/docx` · `@lotics/xlsx`) are LAZY (dynamic-imported, ~free until a doc of that
|
|
817
|
+
type is opened) and SHIP AS `@lotics/ui` deps — custom-code apps get PDF/Word/Excel
|
|
818
|
+
preview with ZERO extra install. Renders to canvas/DOM, never a nested iframe — works in
|
|
819
|
+
the sandboxed app iframe.
|
|
820
|
+
- **`file_preview_types`** — `PreviewLabels` / `FilePreviewProps` / `GalleryLabels` — the
|
|
821
|
+
shared label + prop contracts of the file-preview family; types only.
|
|
822
|
+
- **`file_gallery_modal`** — `FileGalleryModal`: the FULL-SCREEN viewer — toolbar (filename
|
|
823
|
+
· counter · download, optional `onOpenExternal`/`onRemove` · close-✕), prev/next, ESC,
|
|
824
|
+
rotate (the 90° controls FLOAT as a pill on the image); on a phone the actions collapse
|
|
825
|
+
into a ⋯ `ActionMenu`. `onPersistRotation`/`persisting` wire a rotation save. Driven by
|
|
826
|
+
`files` + `activeIndex` + `onIndexChange`.
|
|
827
|
+
- **`image_gallery`** — `ImageGallery`: main image + thumbnail strip (`thumbnailPosition`
|
|
828
|
+
bottom|right, `mainAspectRatio`, `rotatable`) with a built-in zoom modal.
|
|
829
|
+
- **`rotatable_image`** — `RotatableImage`: an image rotating in 90° steps that REFITS — at
|
|
830
|
+
a quarter turn the box lays out in SWAPPED dimensions then rotates into place, so a
|
|
831
|
+
rotated landscape fills the frame; the VIEW half of image rotation
|
|
832
|
+
(`FilePreview`/`ImageGallery` compose it).
|
|
833
|
+
- **`rotate_image`** — `rotateImageToBlob(url, degrees)`: canvas-bake a 90° rotation into a
|
|
834
|
+
NEW blob for re-upload (`useImageRotation` is view-only; this is how a rotation is
|
|
835
|
+
persisted). Pair with `FileGalleryModal`'s `onPersistRotation`/`persisting`.
|
|
836
|
+
- **`use_image_rotation`** — `useImageRotation`: per-file VIEW rotation state (degrees keyed
|
|
837
|
+
by file id, in memory) shared between an inline gallery and its fullscreen modal;
|
|
838
|
+
view-only — a render transform, never a mutation of the stored file.
|
|
839
|
+
- **`share_or_download`** — `shareOrDownloadFiles(files, { title, credentials })`:
|
|
840
|
+
`navigator.share({ files })` (mobile → Save to gallery / send to an app), else individual
|
|
841
|
+
`downloadFileFromUrl` — **never a ZIP**. It shares the BYTES (fetches each URL → `File`),
|
|
842
|
+
so URL expiry afterward is moot; the share path needs the host iframe to grant
|
|
843
|
+
`allow="web-share"` (else it falls back to download). Best for FAST urls (presigned).
|
|
844
|
+
**For SLOW urls (auth-gated proxy with a server round-trip), split it:**
|
|
845
|
+
`prepareShareFiles(files, {credentials})` fetches the `File[]` BEFORE the gesture (on
|
|
846
|
+
menu-open), then `shareFiles(File[])` runs `navigator.share` synchronously on the tap —
|
|
847
|
+
**iOS Safari rejects `share()` if a slow fetch burns the tap's transient activation**,
|
|
848
|
+
which silently lands in the download fallback. `prepareShareFiles` throws on a fetch
|
|
849
|
+
failure (log it, don't swallow); `shareFiles` returns `"shared"|"cancelled"|"unsupported"`.
|
|
850
|
+
|
|
851
|
+
### AI & collaboration
|
|
852
|
+
|
|
853
|
+
- **`composer`** — `Composer`: the adaptive command/chat composer — a compact pill that
|
|
854
|
+
expands for long text + attachments (`pills` + attachment slots, controlled or
|
|
855
|
+
uncontrolled `value`, `onSend`/`onStop`, `sendDisabled` override).
|
|
856
|
+
- **`agent_run`** — `AgentRun` + `AgentRunStep`/`AgentRunItem` + `resolveToolMeta`: the live
|
|
857
|
+
streaming work feed.
|
|
858
|
+
- **`agent_progress`** — `AgentProgress`: its compact, floating, expandable form — a
|
|
859
|
+
composer's working state.
|
|
860
|
+
- **`confidence`** — `Confidence` + `ConfidenceLevel` + `levelFromScore`: calibrated
|
|
861
|
+
high/med/low; localized via the provider.
|
|
862
|
+
- **`change_review`** — the COMPOUND review family — frame: `ChangeReview` provider/stack ·
|
|
863
|
+
`ChangeReviewHeader` (auto kept-counter over decidable entries) · `ChangeReviewActions`
|
|
864
|
+
(the commit bar in the DialogFooter/DrawerFooter: Keep-all bottom-left (`onAcceptAll` for
|
|
865
|
+
host-held field state) + Apply gating); sections: `Change` (host-owned status, labeled
|
|
866
|
+
verbs, collapses to its `ChangeSummary` + Undo; no callbacks = display-only) ·
|
|
867
|
+
`ChangeLabel` · `ChangeSummary` · `ChangeReasoning` (the quiet why); grammar:
|
|
868
|
+
`ChangeFields` (the open record form) + `ChangeField` (THE field: − band · value ·
|
|
869
|
+
candidates + type-another-value · reasoning · per-field Keep/Drop · collapse) ·
|
|
870
|
+
`ChangeRecord` (THE item card: registers like a Change; tone wash + localized op word;
|
|
871
|
+
verb level follows the decision level) · `ChangeBand` (the raw ± band) ·
|
|
872
|
+
`ChangeValueInput` (the diff-at-rest editor); the `changeReview` locale slice.
|
|
873
|
+
- **`clarify`** — `Clarify`: the agent asks back, via `ChoiceList`.
|
|
874
|
+
- **`choice_list`** — `ChoiceList` + `ChoiceOption`: selectable answer options, the agent's
|
|
875
|
+
quick-reply surface.
|
|
876
|
+
- **`sources`** — `Sources` + `SourceRef`/`SourceKind` (record | document | table | web |
|
|
877
|
+
knowledge): provenance chips, per-kind glyphs.
|
|
878
|
+
- **`finding`** — `Finding` + `FindingComparison` + `FindingSeverity`/`FindingLabels`: one
|
|
879
|
+
ranked AI-check insight — severity word · title · detail · `Sources` · children slot; the
|
|
880
|
+
expected-vs-actual body with the emphasized delta; the `finding` locale slice.
|
|
881
|
+
- **`comments_thread`** — `CommentList` + `CommentComposer` + the
|
|
882
|
+
`ThreadComment`/`ThreadMember`/`ThreadFile` types: the record comments thread.
|
|
883
|
+
**Limitation:** labels are not provider-wired — pass `CommentListLabels`.
|
|
884
|
+
|
|
885
|
+
### Utility hooks & plumbing
|
|
886
|
+
|
|
887
|
+
- **`use_screen_size`** — `useScreenSize` / `getScreenSize` / `calculateScreenSize`:
|
|
888
|
+
`{ small, medium, large }` width-breakpoint booleans (small < 768 ≤ medium < 1728 ≤
|
|
889
|
+
large); tracks width changes only, so the mobile soft keyboard (a height-only change)
|
|
890
|
+
never re-renders.
|
|
891
|
+
- **`use_async_fn`** — `useAsyncFn`: wrap an async function into a manual-trigger mutation —
|
|
892
|
+
`[run, {loading, data, error}]`, unmount-safe, the error lands in state AND rethrows; the
|
|
893
|
+
pending-state engine for a submit/download/upload action.
|
|
894
|
+
- **`use_hover`** — `useHover`: pointer-hover state `{hovered, hoverProps}` for raw
|
|
895
|
+
inputs/DOM controls that lack a hovered style state; native stays false. Pressable-based
|
|
896
|
+
controls use the built-in `hovered` state instead.
|
|
897
|
+
- **`use_auto_grow_height`** — `useAutoGrowHeight`: the grows-with-content textarea engine
|
|
898
|
+
(`{minLines, maxLines}` → container height + `scrollEnabled` at the cap); powers
|
|
899
|
+
`Composer` and multiline `TextInputField` — reach for it only when hand-rolling a growing
|
|
900
|
+
input.
|
|
901
|
+
- **`use_focus_ring`** — `useFocusRing`: keyboard-aware focus state for painting a control's
|
|
902
|
+
own ring.
|
|
903
|
+
- **`json_panel`** — `JsonPanel` (`{title, value}`) + `stringifyData`: a labeled monospace
|
|
904
|
+
panel for raw/JSON payloads (debug & developer surfaces).
|