@lotics/ui 45.9.0 → 45.10.1
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 +70 -137
- package/docs/ai_patterns.md +88 -150
- package/docs/catalog.md +243 -285
- package/docs/composition.md +455 -465
- package/docs/data_entry.md +109 -155
- package/docs/reviewing.md +47 -55
- package/docs/templates.md +480 -371
- package/docs/testing.md +3 -7
- package/package.json +3 -1
- package/src/bar_chart.tsx +20 -2
- package/src/chip_group.tsx +5 -3
- package/src/finding.tsx +18 -3
- package/src/line_chart.tsx +5 -14
- package/src/line_chart_labels.ts +32 -0
- package/src/page_header.tsx +13 -1
- package/src/pressable_row.tsx +7 -5
- package/src/stacked_bar_chart.tsx +197 -0
- package/src/table.tsx +15 -14
- package/src/waterfall_chart.tsx +398 -0
package/docs/catalog.md
CHANGED
|
@@ -3,13 +3,23 @@
|
|
|
3
3
|
The complete inventory of the kit, in two passes: **Reach by role** maps each data role /
|
|
4
4
|
job (actions, members, selects, dates, files, money, status, …) to the ONE canonical
|
|
5
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
|
|
7
|
-
|
|
6
|
+
with its purpose. Read this before building any screen — reuse first, and never hand-roll
|
|
7
|
+
something listed here. Absence is the weaker signal: this file is a hand-written index of
|
|
8
|
+
`src/`, not `src/` itself, so an entry can be missing from a kit that ships the component —
|
|
9
|
+
the first bullet below is what to do about that. The
|
|
8
10
|
composition patterns (data entry, AI review, templates) live in the sibling area docs
|
|
9
11
|
indexed in [AGENTS.md](../AGENTS.md).
|
|
10
12
|
|
|
11
13
|
## How to read a component's API
|
|
12
14
|
|
|
15
|
+
- **Reuse first — and this catalog can LAG `src/`.** The kit's inventory is `src/`; this file
|
|
16
|
+
is a hand-written index of it, so an export lands in `src/` and in `package.json`'s `exports`
|
|
17
|
+
in one edit and gets its entry here in another. Before hand-rolling ANY capability, list and
|
|
18
|
+
grep the shipped sources — `ls node_modules/@lotics/ui/src/` — as well as reading this file.
|
|
19
|
+
A component in `src/` and missing here is a DOC BUG: from inside the kit it is fixed in the
|
|
20
|
+
same change, and from an app it is a reason to import the module anyway, never a licence to
|
|
21
|
+
hand-roll a second copy of what the kit already ships. Only the converse is absolute —
|
|
22
|
+
everything listed here exists, and what is listed here is never hand-rolled.
|
|
13
23
|
- **Compose with React Native primitives**: `View` / `ScrollView` from `react-native` (never
|
|
14
24
|
`div`/`span`), style with RN style objects (not CSS), and render every string through the `Text`
|
|
15
25
|
primitive — the kit renders on web **and** native.
|
|
@@ -47,9 +57,7 @@ in place), `Chip` (dismissible facet chip), `CopyButton`
|
|
|
47
57
|
A button is
|
|
48
58
|
never a raw `Pressable`. For a link OUT (a URL / record / document) use `Link` — fixed
|
|
49
59
|
underline+blue + `role="link"`, the destination signal (`onPress` only — the consumer wires
|
|
50
|
-
the opener, e.g. the app SDK's `openExternal`)
|
|
51
|
-
colour-configurable underlined marker. Underlined text GOES somewhere or REVEALS something and
|
|
52
|
-
the ink says which — anything that MUTATES carries a control surface instead.
|
|
60
|
+
the opener, e.g. the app SDK's `openExternal`).
|
|
53
61
|
|
|
54
62
|
### Pick from a list
|
|
55
63
|
|
|
@@ -80,28 +88,23 @@ renders these per option.)
|
|
|
80
88
|
|
|
81
89
|
**To answer "who IS this?" — `MemberPeek`**, the same chip made a door onto a
|
|
82
90
|
`MemberProfileCard`: a 72px avatar, what they sign in as, their role, their teams and when they
|
|
83
|
-
joined. The
|
|
84
|
-
|
|
91
|
+
joined. The trigger takes `Peek`'s `variant="control"` there, so the chip seats in the 40px
|
|
92
|
+
band instead of under-filling it — there is no `marker` prop. Feed it
|
|
85
93
|
a member (an app's `ResolvedMember` already carries `groups`); `role` arrives ALREADY
|
|
86
|
-
TRANSLATED, never a raw enum
|
|
94
|
+
TRANSLATED, never a raw enum. Use `MemberProfileCard`
|
|
87
95
|
alone wherever the card is not behind a press (a profile header, a drawer).
|
|
88
96
|
|
|
89
97
|
**Where a peek must NOT go**, both cases being one rule — *two destinations behind one
|
|
90
|
-
object*: a PICKER OPTION
|
|
91
|
-
|
|
92
|
-
PRESSES (the row opens the record; a chip inside it would open something else four pixels
|
|
93
|
-
away). Those keep the plain `MemberChip`. A peek belongs where nothing else on the line
|
|
98
|
+
object*: a PICKER OPTION, and a ROW THAT ALREADY PRESSES. Those keep the plain `MemberChip`. A
|
|
99
|
+
peek belongs where nothing else on the line
|
|
94
100
|
presses — a person named in prose, a static header, a detail row, a log whose rows do not
|
|
95
101
|
navigate. **Judge the container, not its look:** a `DataGrid` group band names a person and
|
|
96
102
|
reads inert, but it is a button that collapses the group, so a peek there nests a button in a
|
|
97
|
-
button. A fully interactive screen often has NO valid spot
|
|
98
|
-
names a person three times (collapsing band, inline editor, picker option) and correctly
|
|
99
|
-
peeks on none of them.
|
|
103
|
+
button. A fully interactive screen often has NO valid spot.
|
|
100
104
|
|
|
101
105
|
**`groups` distinguishes absent from empty, and the card renders the difference**: omitted
|
|
102
106
|
drops the row (this caller was never told — a public-app response, an older server), `[]`
|
|
103
|
-
keeps it and says "None".
|
|
104
|
-
on no team.
|
|
107
|
+
keeps it and says "None".
|
|
105
108
|
|
|
106
109
|
**Size is a RUNG, never a pixel count** — `sm | md | lg | xl` = 24 / 28 / 40 / 72
|
|
107
110
|
(`@lotics/ui/avatar_size`, shared by `Avatar`, `MemberChip` and `GroupAvatar`). Default `md`.
|
|
@@ -111,17 +114,14 @@ Pick by density:
|
|
|
111
114
|
|
|
112
115
|
**Both middle rungs quote `control_surface`, and which one you want depends on whether the
|
|
113
116
|
avatar is IN a control or IS one.** `md` is `CONTROL_CONTENT_HEIGHT` (28) — the largest rung a
|
|
114
|
-
40px control band seats without growing
|
|
115
|
-
already used to sit inside a field. An avatar is the tallest thing in whatever row holds it, so
|
|
116
|
-
a rung that overshoots doesn't look slightly big, it RESIZES the control. Put nothing taller
|
|
117
|
+
40px control band seats without growing. Put nothing taller
|
|
117
118
|
than `md` in a control. `lg` is `CONTROL_HEIGHT` (40) — the avatar that stands in a band on its
|
|
118
|
-
own
|
|
119
|
-
belongs in a row that sets its own height.
|
|
119
|
+
own. Anything bigger belongs in a row that sets its own height.
|
|
120
120
|
|
|
121
121
|
**`sm` shows ONE initial; every larger rung shows two** — two letters are not legible at 24px.
|
|
122
|
-
That is the practical reason not to reach for `sm` by reflex:
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
That is the practical reason not to reach for `sm` by reflex: a register that must identify
|
|
123
|
+
someone wants `md`. Initials are the first and **last** word of the name — "Vũ Thị Lan" → VL,
|
|
124
|
+
"Mary Jane
|
|
125
125
|
Watson" → MW — because a Vietnamese name's first two words are the family and middle names
|
|
126
126
|
that a whole office shares.
|
|
127
127
|
|
|
@@ -150,9 +150,7 @@ The `Inline*` family: `InlineTextInput` (**`numberOfLines`** — the line budget
|
|
|
150
150
|
reader must read WHOLE rather than recognise at a glance: a payment term, an address, a clause.
|
|
151
151
|
Above 1 the resting box RESERVES that many lines — the full editor height, not the height this
|
|
152
152
|
particular value needs — so clicking in moves neither the field nor anything below it; Enter
|
|
153
|
-
then inserts a newline and the value commits on blur.
|
|
154
|
-
goes multiline once focused hides the text from the reader who never clicks, and one that
|
|
155
|
-
reserves less than it opens to shoves the rest of the form down the page on every click.
|
|
153
|
+
then inserts a newline and the value commits on blur.
|
|
156
154
|
**`autoGrow`** makes that budget a MINIMUM and fits the value instead — see
|
|
157
155
|
[data_entry.md §Open prose grows](./data_entry.md). **`link`** marks a URL value with
|
|
158
156
|
the navigation ink as a TREATMENT on the same single element — never a swapped-in
|
|
@@ -167,20 +165,16 @@ is mounted at rest and merely gains a draft on focus, so focusing moves nothing
|
|
|
167
165
|
the prose stays SELECTABLE rather than sitting under a button. Its toolbar is off for
|
|
168
166
|
that reason — a band that appears with the edit pushes everything under it down — and it
|
|
169
167
|
passes its **`variant`** THROUGH to the editor, which is what actually draws the box:
|
|
170
|
-
the frame paints no surface here
|
|
171
|
-
every markdown field wore the document sheet, 4px right of the plain-text field above it.
|
|
168
|
+
the frame paints no surface here.
|
|
172
169
|
**`numberOfLines`** is the reserve, as everywhere else in the family); a
|
|
173
170
|
READ-ONLY field in that same column uses `InlineStatic` (matches the editor box exactly, no
|
|
174
171
|
input chrome, so it aligns pixel-for-pixel). **`MarkdownEditor`** is that editor standalone — a WYSIWYG whose value stays a plain
|
|
175
|
-
markdown string, with a formatting toolbar as a SIBLING of the field
|
|
176
|
-
encloses is the document; the controls that act on it sit outside it). Web is
|
|
172
|
+
markdown string, with a formatting toolbar as a SIBLING of the field. Web is
|
|
177
173
|
ProseMirror via `@lotics/markdown-editor`; native falls back to raw-text editing, and
|
|
178
174
|
the platform-neutral `MarkdownEditorProps` is what stops the two drifting. **`variant`**
|
|
179
175
|
is `"document"` (the default soft sheet) or the two FIELD states, `"framed"` / `"bare"`,
|
|
180
176
|
DERIVED from `InlineEditVariant` so they cannot drift from the rest of the family. A field
|
|
181
|
-
takes the control's corner and the control's text inset instead of the sheet's.
|
|
182
|
-
is NAMED rather than left as absence: an optional prop whose omission is a distinct third
|
|
183
|
-
mode reads exactly backwards to anyone who knows `TextInputField.variant`. Wrap it in a
|
|
177
|
+
takes the control's corner and the control's text inset instead of the sheet's. Wrap it in a
|
|
184
178
|
label with **`FormMarkdownEditor`**. Pass **`accessibilityLabel`** whenever the visible
|
|
185
179
|
label is a sibling rather than a wrapping `FormField` — a `DetailRow`, an inline
|
|
186
180
|
editor's frame — or the editable surface is an unnamed text box. A stack of labelled
|
|
@@ -198,13 +192,9 @@ axes carry every shape it takes, and BOTH are decisions about meaning, not looks
|
|
|
198
192
|
have.
|
|
199
193
|
- **`mark`** — does ticking mean DONE or CHOSEN? `check` is a ring, `select` is the square box.
|
|
200
194
|
Reach for the square whenever the row is PICKED rather than finished — which forms to produce,
|
|
201
|
-
which records to export.
|
|
202
|
-
prevent: a reader who has learned that a filled ring means "done" reads a ticked pick-list as
|
|
203
|
-
work already carried out, and nothing on screen corrects them. (The shapes are 24px either way
|
|
195
|
+
which records to export. (The shapes are 24px either way
|
|
204
196
|
and share one gutter, so a surface swaps between them without moving anything.)
|
|
205
197
|
|
|
206
|
-
A record's Progress and a desk's to-do list are the same component two booleans apart.
|
|
207
|
-
|
|
208
198
|
**A row can carry a DECIDED outcome instead of a tick — `ChecklistItem.verdict` (`"pass"` /
|
|
209
199
|
`"fail"`).** This is a per-ROW axis, not a list-level one: a QC gate, a reconciliation match,
|
|
210
200
|
any row whose mark means an inspection SETTLED rather than work getting done. `fail` renders in
|
|
@@ -216,21 +206,12 @@ no-op (withheld internally), because neither of the toggle-capable marks (`Check
|
|
|
216
206
|
pass/fail should drive that from its own control, then set `verdict` from the result, never
|
|
217
207
|
reach for `done`+`onToggle` on the same row a verdict already settled.
|
|
218
208
|
|
|
219
|
-
**A row carries its TITLE and its own stamp, and nothing else.** A checklist REPORTS: it does not
|
|
220
|
-
collect, and it does not PREVIEW. A field some section owns — an assignee, an address, a portal
|
|
221
|
-
login — is NAMED as a gap by a `ChecklistNote` whose `action` jumps to that section, never edited
|
|
222
|
-
in the row (two edit surfaces for one fact) and never rendered as a value (an avatar or badge is
|
|
223
|
-
a copy that has to be kept in step). The exceptions are narrow and stated: `ChecklistItem.trailing`
|
|
224
|
-
for the row's OWN stamp (a tick writes today, so a run filled in after the fact needs the
|
|
225
|
-
correction in reach), and `ChecklistField` for a value with NO other home.
|
|
226
|
-
|
|
227
209
|
**A list you scan DOWN columns — the same four values compared across twenty rows — is a
|
|
228
210
|
`Table`.** Grouping, filters and a per-row register belong there.
|
|
229
211
|
|
|
230
212
|
**A record's work state is a connected checklist** ([`tpl_record`](../examples/tpl_record.tsx)
|
|
231
|
-
§ Progress), where the desks are `ChecklistGroup`s and the milestones are rows. A
|
|
232
|
-
|
|
233
|
-
put the CTA a section away from the state that gated it. A register's workspace drawer renders the
|
|
213
|
+
§ Progress), where the desks are `ChecklistGroup`s and the milestones are rows. A register's
|
|
214
|
+
workspace drawer renders the
|
|
234
215
|
same record, so it follows automatically; for a surface whose SUBJECT is tasks see
|
|
235
216
|
[`tpl_task_board`](../examples/tpl_task_board.tsx).
|
|
236
217
|
|
|
@@ -259,16 +240,15 @@ Two columnar shapes, and the choice is about data size:
|
|
|
259
240
|
heading, so digits line up under their label; **`DataGridColumn.footer` gives that column its
|
|
260
241
|
cell in the group's footer row** (a subtotal, a resolved measure) — the grid lays it out in
|
|
261
242
|
the column's own box, so a total can never drift from what it sums and no consumer rebuilds
|
|
243
|
+
the row. `renderGroupFooter` remains for content that does NOT sit in a column (a per-group
|
|
244
|
+
add row). The grid takes exactly its container's width — no outdent to fight, so it needs no
|
|
245
|
+
scroller unless YOU add one.
|
|
262
246
|
**`DataGrid` SHEDS NOTHING at narrow widths, and that is deliberate** — every column is a
|
|
263
247
|
measure you came to compare, unlike `Table`'s register of objects where the row is an index
|
|
264
248
|
and the record holds the rest. So it has no `priority` and no stacked mode: fixed columns keep
|
|
265
249
|
their widths, the flexible column holds a `FLEX_MIN_WIDTH` floor, and past that the grid
|
|
266
|
-
OVERFLOWS. A narrow container must scroll it horizontally
|
|
267
|
-
|
|
268
|
-
rendered rows of unlabelled numbers, silently. If you want columns to drop, you want `Table`.
|
|
269
|
-
the row. `renderGroupFooter` remains for content that does NOT sit in a column (a per-group
|
|
270
|
-
add row). The grid takes exactly its container's width — no outdent to fight, so it needs no
|
|
271
|
-
scroller unless YOU add one. It renders ALL rows (no
|
|
250
|
+
OVERFLOWS. A narrow container must scroll it horizontally. If you want columns to drop, you
|
|
251
|
+
want `Table`. It renders ALL rows (no
|
|
272
252
|
virtualization), so it's only for sets small enough to hold in view — at 10k+ it lags, and
|
|
273
253
|
grouping + pagination/infinite don't compose; use the register instead. Worked example:
|
|
274
254
|
[`tpl_task_board`](../examples/tpl_task_board.tsx) (a `CheckCircle` leading).
|
|
@@ -279,23 +259,26 @@ Two columnar shapes, and the choice is about data size:
|
|
|
279
259
|
— below the toolbar, from the filtered rows), `KPICard` / `Metric` (headline figures),
|
|
280
260
|
`TrendChip` (delta), `Sparkline`, `BarChart` / `LineChart` / `PieChart` (the canonical SVG
|
|
281
261
|
set — no recharts), `ProgressRing`, `ProgressBar` (its `compact` prop = ONE row, track + a
|
|
282
|
-
plain sm tabular count beside it — the cell/heading/peek-trigger meter
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
tells the reader they are exactly at the limit. Numbers format in the reader's locale, so
|
|
262
|
+
plain sm tabular count beside it — the cell/heading/peek-trigger meter. **The track clamps at
|
|
263
|
+
100%, the caption does not** — over its max it reads `2,100 / 2,000 (105%)`. Numbers format in the
|
|
264
|
+
reader's locale, so
|
|
286
265
|
never hand-format the value you pass in — when display precision differs from the true value
|
|
287
266
|
(whole credits off a fractional balance), `formatValue` reshapes the caption text and leaves
|
|
288
|
-
the fill and the percentage exact) / `StackedProgressBar`
|
|
267
|
+
the fill and the percentage exact) / `StackedProgressBar` (ONE whole split across segments,
|
|
268
|
+
filling its own track whatever the total) / `StackedBarChart` (SEVERAL wholes compared — rows
|
|
269
|
+
of stacked bars on one shared scale, so length says how big and the segments say what of) /
|
|
270
|
+
`StepProgress`, `WaterfallChart` (the BRIDGE — an opening level, the signed steps that move
|
|
271
|
+
it, the level they close at; revenue walked down to net), `Breakdown` (a
|
|
289
272
|
stacked bar + ranked share rows, pressable to drill; `maxRows` folds the long tail behind a
|
|
290
273
|
"Show N more" toggle — `labels` to localize — so several facet cards align to one height in
|
|
291
274
|
a row), `Funnel` (a CONVERSION funnel — ordered stages as bars that NARROW; the step
|
|
292
275
|
conversion rate is the HEADLINE (a bold aligned row across the top, the first stage = the
|
|
293
|
-
100% baseline), the count is the supporting figure below — the
|
|
294
|
-
|
|
276
|
+
100% baseline), the count is the supporting figure below — never pin the rate to the fill height.
|
|
277
|
+
`orientation` vertical columns |
|
|
295
278
|
horizontal bars; pass `onSelect`+`selectedKey` to make the bars press-to-drill (the selected
|
|
296
279
|
stays solid, others dim — the caller renders the records). The subset/drop-off sibling of
|
|
297
280
|
`StackedProgressBar` — nested cohorts that shrink "calls → connected → won", NOT a whole
|
|
298
|
-
split across stages
|
|
281
|
+
split across stages), `StatusGrid` + `StatusLegend`, `Heatmap`
|
|
299
282
|
(density: colour-only, "where does it cluster"), `Matrix` (the PIVOT cross-tab: the NUMBER
|
|
300
283
|
in each cell — optionally a heat wash behind it — plus row/column/grand totals; press a cell
|
|
301
284
|
to drill).
|
|
@@ -325,11 +308,8 @@ action/badge/unit), `ActionMenu` (⋯), `FloatingActionBar` (bulk-select bar).
|
|
|
325
308
|
|
|
326
309
|
A DESIGNED row (your own pile, card, or panel) that presses open AND carries its own
|
|
327
310
|
controls is `PressableRow` + **`PressDoor`** — never `PressableHighlight`, which IS a
|
|
328
|
-
button and wraps its children (a button must not contain interactive descendants).
|
|
329
|
-
|
|
330
|
-
accessible name, and focus ring; lift the content above it with `zIndex: 1`. `Table`/
|
|
331
|
-
`TableRow` does exactly this internally, so a columnar register needs no assembly —
|
|
332
|
-
reach for `PressDoor` only outside it.
|
|
311
|
+
button and wraps its children (a button must not contain interactive descendants). `Table`/
|
|
312
|
+
`TableRow` does exactly this internally, so a columnar register needs no assembly.
|
|
333
313
|
|
|
334
314
|
### Filters & view controls
|
|
335
315
|
|
|
@@ -362,11 +342,9 @@ into a list *and* a grid is the mistake — it gives the reader two places to lo
|
|
|
362
342
|
thing. The minority is not stranded by this: a non-image tile in a grid renders a `DocumentCard`
|
|
363
343
|
printing its filename, and a photo in a list keeps its full name plus a meta line and is ONE tap
|
|
364
344
|
from the picture, because a `FileRows` row press opens the built-in gallery. (The badge is
|
|
365
|
-
MIME-driven, so that row shows a generic image mark, not the shot itself
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
that arrived on the record, and the hand-over photos taken against it — not because one set was
|
|
369
|
-
split by type.
|
|
345
|
+
MIME-driven, so that row shows a generic image mark, not the shot itself.) A screen showing
|
|
346
|
+
BOTH surfaces (`tpl_record`) does so because it holds two distinct SETS the reader asks
|
|
347
|
+
different questions of, not because one set was split by type.
|
|
370
348
|
|
|
371
349
|
`FilesEditor` (THE attachment surface: an upload-aware grid whose bar you COMPOSE — the root
|
|
372
350
|
owns selection + gallery + confirmed remove, the bar pieces and any HOST verb go below it via
|
|
@@ -388,8 +366,7 @@ action / remove; `onPress` makes the whole row a pressable door, `trailing` stay
|
|
|
388
366
|
independently-pressable sibling; for checklists & readable lists. **`size`** — `sm` (default)
|
|
389
367
|
is the compact attachment line for many files scanned as a list; **`md` is the document-desk
|
|
390
368
|
row** (taller badge, `ListItem` height) for the FEW rows that ARE the section's subject — an
|
|
391
|
-
expected-document checklist, a slot a button fills
|
|
392
|
-
appendix to the page rather than its point), `FileBadge` (the two-tone
|
|
369
|
+
expected-document checklist, a slot a button fills), `FileBadge` (the two-tone
|
|
393
370
|
type mark), `FilePreview` / `FileGalleryModal`, `ImageGallery`; picking is `pickFiles`
|
|
394
371
|
(`@lotics/ui/file_picker` — opens the browser picker and resolves the chosen `File[]`, the
|
|
395
372
|
imperative half behind every Add-file CTA); for gated CRUD compose locally with
|
|
@@ -422,20 +399,14 @@ that same line, over the `readings` stacked ONE PER LINE — each `source value`
|
|
|
422
399
|
joining them (a middot claims a relation it will not name, and is the one mark a screen reader
|
|
423
400
|
drops), both kept on
|
|
424
401
|
screen with NEITHER marked as the wrong one, since a finding reports that two sources disagree
|
|
425
|
-
and which is right is the reader's call. `detail` is the optional consequence.
|
|
426
|
-
a stack gets severity down the left edge and magnitude down the right. The readings ARE the
|
|
402
|
+
and which is right is the reader's call. `detail` is the optional consequence. The readings ARE the
|
|
427
403
|
provenance, so there are no `Sources` chips under it repeating the same names. The children slot
|
|
428
404
|
composes ANY visual result — a compact `Table` for per-line detail (danger color on the offending cells),
|
|
429
405
|
`ProgressBar` for consumption-toward-a-cliff (free time, credit), dot `Badge`s for a
|
|
430
406
|
present/missing checklist, `Confidence` for judgment calls. Display-only — it informs the
|
|
431
407
|
verdict the host records; `finding` locale slice). `ApprovalPrompt` (the surface that ANSWERS
|
|
432
|
-
an agent's approval-gated action
|
|
433
|
-
|
|
434
|
-
optional `children` input summary; slot it where the composer sits while the run is parked;
|
|
435
|
-
`approvalPrompt` locale slice), `MessageActions` (THE single shared per-message chrome bar for a
|
|
436
|
-
chat transcript — copy with owned copied-feedback, regenerate, edit, and the branch/version
|
|
437
|
-
pager as an internal part; each action renders only when its handler is given, so the assistant
|
|
438
|
-
bar and the user bar are one component with different handlers; `messageActions` locale slice).
|
|
408
|
+
an agent's approval-gated action), `MessageActions` (THE single shared per-message chrome bar
|
|
409
|
+
for a chat transcript).
|
|
439
410
|
|
|
440
411
|
---
|
|
441
412
|
|
|
@@ -448,15 +419,14 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
448
419
|
|
|
449
420
|
- **`vite`** — `loticsOptimizeDeps`: the canonical `optimizeDeps.include` list a custom-code
|
|
450
421
|
app's `vite.config.ts` must pre-bundle for `lotics app dev` to render (RN-ecosystem + markdown
|
|
451
|
-
CJS-interop; dev-only — the prod rollup build resolves the interop without it).
|
|
452
|
-
|
|
453
|
-
the scaffold's `vite.config.ts` imports it instead of hardcoding the list:
|
|
422
|
+
CJS-interop; dev-only — the prod rollup build resolves the interop without it). The
|
|
423
|
+
scaffold's `vite.config.ts` imports it instead of hardcoding the list:
|
|
454
424
|
`import { loticsOptimizeDeps } from "@lotics/ui/vite"` → `optimizeDeps: { include: loticsOptimizeDeps }`.
|
|
455
425
|
To add app-specific entries, spread: `include: [...loticsOptimizeDeps, "my-dep"]`. A Node-loadable
|
|
456
426
|
`.mjs` leaf (a Vite config can't import a `.ts` from `node_modules`), zero-import by contract.
|
|
457
427
|
- **`vite`** — `loticsResolve()`: the whole `resolve` block, for the same reason and on the same
|
|
458
|
-
terms — `resolve: loticsResolve()`.
|
|
459
|
-
|
|
428
|
+
terms — `resolve: loticsResolve()`. The entries: the `react-native` → `react-native-web` alias
|
|
429
|
+
(**load-bearing** — without it the RN
|
|
460
430
|
primitives have no web implementation and the app does not build), `.web.tsx`-first `extensions`
|
|
461
431
|
(per-target variants win; `.mjs`/`.mts` kept for parity with Vite's default resolver), `dedupe`
|
|
462
432
|
for React + RN-Web (the kit is consumed across many subpaths, and a second React copy in one
|
|
@@ -464,8 +434,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
464
434
|
read at call time so it lasts exactly one command. To add your own alias, spread:
|
|
465
435
|
`const base = loticsResolve(); resolve: { ...base, alias: [...base.alias, entry] }`.
|
|
466
436
|
`lotics app codegen` writes the matching `paths` into the app's `.lotics/tsconfig.link.json`,
|
|
467
|
-
so `tsc`, vitest, eslint and the editor resolve the same kit Vite does
|
|
468
|
-
Vite-only trick that leaves every other tool checking the published package.
|
|
437
|
+
so `tsc`, vitest, eslint and the editor resolve the same kit Vite does.
|
|
469
438
|
|
|
470
439
|
### Text & formatting
|
|
471
440
|
|
|
@@ -491,18 +460,18 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
491
460
|
`Icon` rather than rendering the wrong company's logo. Not for the app's
|
|
492
461
|
own glyphs; that is `icon`. `x` is the one mark whose brand colour is BLACK, so it is the one
|
|
493
462
|
that needs `muted` on a dark ground; the rest carry their own hue either way. **Not a product
|
|
494
|
-
logo**: a monochrome stand-in for one reads as a different thing
|
|
495
|
-
|
|
496
|
-
|
|
463
|
+
logo**: a monochrome stand-in for one reads as a different thing, the kit ships no vendor
|
|
464
|
+
artwork, and official artwork is not uniformly inlinable anyway — pass your own image to
|
|
465
|
+
`SwitchButton`'s `leading` slot instead.
|
|
497
466
|
- **`markdown`** — `Markdown`: the single canonical markdown renderer for chat, apps, and
|
|
498
467
|
`AgentRun`; rich GFM markdown on web with copyable tables, plain-text on
|
|
499
468
|
native; takes a markdown `children` string. **`variant`** decides whose type
|
|
500
469
|
ladder the headings belong to: `"document"` (default) where the markdown IS the
|
|
501
470
|
surface, `"embedded"` where it is a VALUE on a page someone else designed — a
|
|
502
471
|
field on a record, a summary in a feed row. Reach for `"embedded"` whenever the
|
|
503
|
-
text was not written by whoever built the screen
|
|
504
|
-
|
|
505
|
-
|
|
472
|
+
text was not written by whoever built the screen; at document scale an `h1` it
|
|
473
|
+
emits is the same rung as your `SectionHeadingTitle`. Embedded demotes the heading LEVEL as well
|
|
474
|
+
as
|
|
506
475
|
the scale (h1→h4, h2→h5, h3+→h6): sizing alone leaves a model's `##` announcing as a PEER of
|
|
507
476
|
your own sections in heading navigation, which no visual check can see.
|
|
508
477
|
- **`markdown_types`** — `MarkdownProps`; types only.
|
|
@@ -514,10 +483,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
514
483
|
`getInputLineHeight` — the 16px-mobile/14px-desktop input contract that stops Safari iOS
|
|
515
484
|
auto-zoom; only for hand-rolled raw inputs. It carries the **ink** as well as the size,
|
|
516
485
|
because omitting `color` on a native `<input>` does not inherit — it falls to the UA
|
|
517
|
-
default, pure black, against the `zinc-900` every resting value beside it uses.
|
|
518
|
-
|
|
519
|
-
change re-weights every stroke's antialiasing), which is a bug no box measurement can
|
|
520
|
-
see. Hand-rolling a raw input means taking this style, not just its font size.
|
|
486
|
+
default, pure black, against the `zinc-900` every resting value beside it uses.
|
|
487
|
+
Hand-rolling a raw input means taking this style, not just its font size.
|
|
521
488
|
- **`font_family`** — the three Inter stacks (`fontFamilyRegular` / `fontFamilyMedium` /
|
|
522
489
|
`fontFamilySemiBold`). Weight in this kit is a FAMILY, not a `font-weight` axis: each rung is
|
|
523
490
|
a separate file, which is why `Text`'s `weight` prop swaps the family rather than a number.
|
|
@@ -527,14 +494,13 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
527
494
|
the ONLY themeable typeface. There is deliberately no body-face role: body weight is three
|
|
528
495
|
coupled families, `text.css` hand-tunes tracking for Inter's glyphs and for Vietnamese
|
|
529
496
|
diacritics at 12px, and `font-feature-settings: "cv11","ss01","ss03"` are Inter's own
|
|
530
|
-
alternates.
|
|
531
|
-
subtly miscalibrated. Changing the body face is a kit change, done once for everybody.
|
|
497
|
+
alternates. Changing the body face is a kit change, done once for everybody.
|
|
532
498
|
- **`type_ramp`** — the type ramp as DATA: `TYPE_RAMP_MOBILE` / `TYPE_RAMP_DESKTOP` (size +
|
|
533
499
|
leading per rung, the 768px breakpoint between them), `TYPE_TRACKING` (em per rung, negative
|
|
534
500
|
throughout and steepening as the type grows), `TypeRung` / `TypeRungName` (`TextSize` IS this
|
|
535
501
|
set), and `trackingPx` for platforms with no em unit. `Text` renders through two mechanisms —
|
|
536
502
|
a `data-text-size` attribute matched by `text.css` on web, a `StyleSheet` on native — and this
|
|
537
|
-
is what both derive from, so the two cannot disagree.
|
|
503
|
+
is what both derive from, so the two cannot disagree. You
|
|
538
504
|
almost never need to import it, because `Text size=` is the way to ask for a rung. Reach for
|
|
539
505
|
it only when styling something `Text` cannot wrap (a raw `TextInput`, a canvas, a chart axis)
|
|
540
506
|
and the result has to sit at a rung exactly.
|
|
@@ -571,15 +537,13 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
571
537
|
- **`spacing`** — the `SPACE` scale + `SpaceToken`, and **`pagePad(small)`** — the page's
|
|
572
538
|
side gutter (`SPACE.md` on a phone, `SPACE.xl` wider). `PageContent` applies it; a screen
|
|
573
539
|
that cannot use that shell — one with a rail, a side panel or its own scroller — imports it
|
|
574
|
-
rather than picking a number
|
|
575
|
-
seen twice and must not shift sideways between them. It is also COUPLED to `ROW_WASH_BLEED`:
|
|
540
|
+
rather than picking a number. It is also COUPLED to `ROW_WASH_BLEED`:
|
|
576
541
|
a register row bleeds its wash outward to `pagePad − ROW_WASH_BLEED`, so the two are read
|
|
577
542
|
together and neither belongs to the app.
|
|
578
543
|
- **`control_surface`** — `CONTROL_HEIGHT` (40), `CONTROL_CONTENT_HEIGHT` (28 — the tallest
|
|
579
544
|
FIXED-height node a 40px band seats, 6 per side of air; what `InlineButton` and a `md`
|
|
580
545
|
`IconButton` already used, now named so an avatar or any other child lands on it too. Text
|
|
581
|
-
never needs it because `minHeight` governs a single line,
|
|
582
|
-
could each pick a different inset and only disagree once an avatar went in),
|
|
546
|
+
never needs it because `minHeight` governs a single line),
|
|
583
547
|
`CONTROL_PADDING_V` (5 — derived from the pair, BORDER INCLUDED in the 6 per side; every 40px
|
|
584
548
|
surface pads by it instead of a number of its own), `CONTROL_RADIUS` (10), `CONTROL_TEXT_INSET`
|
|
585
549
|
(9 — how far a control insets its OWN text: 1px border + 8px padding; anything that must line
|
|
@@ -627,8 +591,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
627
591
|
so a prop declared on one side typechecks everywhere and silently does nothing in the browser.
|
|
628
592
|
- **`label_tone`** — `labelTone(label)`: one stable `ColorName` per free-text label (a tag, a
|
|
629
593
|
topic, a category), for `Badge`. `avatarTone`'s sibling and the same discipline: pure and
|
|
630
|
-
NEVER STORED
|
|
631
|
-
colour has to be chosen; `red` and `amber` excluded, because a palette that can paint an
|
|
594
|
+
NEVER STORED; `red` and `amber` excluded, because a palette that can paint an
|
|
632
595
|
arbitrary noun red makes every red on the screen ambiguous; hue-interleaved so two labels in
|
|
633
596
|
adjacent buckets still look different. Case- and space-folded, matching whatever normalizes the
|
|
634
597
|
set — two spellings of one label must not be two colours. Returns a NAME (not a hex like
|
|
@@ -646,14 +609,14 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
646
609
|
one cell. Takes `{ id, name, kind: "person" | "group", image? }`, shows `max` of them (default
|
|
647
610
|
3) and makes the rest a `+N` that is the LAST MARK IN THE STACK, not loose text beside it.
|
|
648
611
|
`size` is the shared avatar scale with the same `md` default as `Avatar`/`MemberChip`, and the
|
|
649
|
-
marks, the overlap and the count's type all derive from it — no fixed pixels
|
|
650
|
-
|
|
612
|
+
marks, the overlap and the count's type all derive from it — no fixed pixels. Three rules it
|
|
613
|
+
exists to carry: the caller passes only principals it
|
|
651
614
|
can NAME, so the overflow never counts a mark the reader sees is missing; a group is ONE mark
|
|
652
615
|
bearing the group's name, never its members' faces (expanding it states a membership the reader
|
|
653
616
|
may not be entitled to); and the separating hairline is **translucent ink, never the surface
|
|
654
617
|
colour** — a register row washes to `zinc[50]` on hover, `accent_wash` when open and
|
|
655
618
|
`zinc[200]` while pressed, so a ring painted the background is a halo in three of the four
|
|
656
|
-
states, while a dark edge darkens any ground
|
|
619
|
+
states, while a dark edge darkens any ground. Marks
|
|
657
620
|
`announce` because they stand alone here. INERT — its home is a register row, and a row that
|
|
658
621
|
already presses must not hold a second destination (see `member_peek`). A single person →
|
|
659
622
|
`MemberChip`; a single group → `GroupAvatar`.
|
|
@@ -668,8 +631,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
668
631
|
- **`member_profile_card`** — `MemberProfileCard`: WHO IS THIS — `xl` avatar, name, sign-in
|
|
669
632
|
identity, role, join date and groups, plus one optional `action`. Every row is conditional, so a
|
|
670
633
|
card told nothing is a header and nothing else. `role` comes in pre-translated (vocabulary only
|
|
671
|
-
the product can name); `joined` comes in as an ISO DATE and the card formats it to month + year
|
|
672
|
-
(a date has a canonical form the kit owns, so two hosts formatting it themselves would drift);
|
|
634
|
+
the product can name); `joined` comes in as an ISO DATE and the card formats it to month + year;
|
|
673
635
|
`groups` distinguishes absent (no row) from `[]` ("None"). `inactive` draws the departed state as
|
|
674
636
|
a `Badge` — a lifecycle state, not another muted line.
|
|
675
637
|
- **`member_peek`** — `MemberPeek`: `MemberChip` + `Peek` + the card above, with the trigger's
|
|
@@ -696,8 +658,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
696
658
|
one opt-down). **Which rung it renders is the SURFACE's answer, not a prop:** `##` xl-semibold
|
|
697
659
|
on a page, `####` md-semibold inside an overlay's content region (`DrawerScrollArea` /
|
|
698
660
|
`DialogScrollArea` / `ModalBody` / `PopoverContent`), where the container's own title already
|
|
699
|
-
occupies lg.
|
|
700
|
-
per-surface variant to pick, so it cannot be picked wrong. A whole PAGE surface docked in an
|
|
661
|
+
occupies lg. A whole PAGE surface docked in an
|
|
701
662
|
overlay (a record screen in a master-detail drawer) carries its own gutters and so sits in the
|
|
702
663
|
container's BARE slot, keeping the page rungs; nesting overlays never compounds.
|
|
703
664
|
`SubsectionHeadingTitle` is the `###` lg-semibold level-3
|
|
@@ -709,8 +670,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
709
670
|
`primary` `Button` beside the title, rendered whether the collection is empty or full, never
|
|
710
671
|
under the rows it extends and never repeated in the `EmptyState`
|
|
711
672
|
(composition.md § The add-placement law). An overlay surface loses only the type size, never an
|
|
712
|
-
affordance — `icon`, `description` and `info` are the same slots at both altitudes.
|
|
713
|
-
is on every rung** — the ramp's slots do not vary by level; only the type size does. Inside an
|
|
673
|
+
affordance — `icon`, `description` and `info` are the same slots at both altitudes. Inside an
|
|
714
674
|
overlay `####` is the ramp's FLOOR: `SubsectionHeadingTitle` has one altitude and would out-rank
|
|
715
675
|
the section holding it, so a named group there is `Text size="sm" weight="semibold"`. The heading
|
|
716
676
|
ramp is FIXED: `#` xxl / `##` xl / `###` lg / `####` md, no size props.
|
|
@@ -719,9 +679,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
719
679
|
rung. The kit's content regions publish it, so an ordinary screen never imports this. Reach for
|
|
720
680
|
it when you build your OWN pane — a docked side panel, a split view, any self-contained region
|
|
721
681
|
whose own chrome caps at `lg` and which therefore uses none of those regions: wrap its content
|
|
722
|
-
in `<HeadingAltitudeContext.Provider value="panel">` and every heading inside is right.
|
|
723
|
-
|
|
724
|
-
build. Flat, never a depth counter — a popover inside a drawer is one rung down, not two.
|
|
682
|
+
in `<HeadingAltitudeContext.Provider value="panel">` and every heading inside is right.
|
|
683
|
+
Flat, never a depth counter — a popover inside a drawer is one rung down, not two.
|
|
725
684
|
- **`section_stack`** — `SectionStack`, `SubsectionStack` — stacks that own the
|
|
726
685
|
between-block law, skipping null children: `SectionStack` = a fixed 56px beat + a hairline
|
|
727
686
|
`Divider` between top-level blocks; `SubsectionStack` = a fixed 32px beat, space-only while
|
|
@@ -748,16 +707,13 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
748
707
|
`left`/`right` form a separate nav row above. Split `trailing` from `actions` by what the
|
|
749
708
|
control acts on: `actions` do something to the page's CONTENT (create, sort, export),
|
|
750
709
|
`trailing` changes what is AROUND it (a side-panel toggle, a view switch). A panel toggle
|
|
751
|
-
filed under `actions` reads as a peer of "create one of these".
|
|
752
|
-
|
|
753
|
-
and
|
|
754
|
-
and `actions` keep their width — a wrapped title is merely taller, a control squeezed
|
|
755
|
-
below its own icon is broken.
|
|
710
|
+
filed under `actions` reads as a peer of "create one of these". Under a title too long for
|
|
711
|
+
the row the TITLE gives way and wraps while `trailing`
|
|
712
|
+
and `actions` keep their width.
|
|
756
713
|
- **`page_content`** — `PageContent` + `PAGE_SIZES`: the page's padded, width-capped content
|
|
757
714
|
region — a centred column with optional `title`/`titleRight`/`description`, `header`/`footer`
|
|
758
|
-
slots and `fullscreen`. **Reach for it before hand-rolling a screen shell
|
|
759
|
-
|
|
760
|
-
up on different gutters. Its side padding is `pagePad` (`@lotics/ui/spacing`), so a screen that
|
|
715
|
+
slots and `fullscreen`. **Reach for it before hand-rolling a screen shell.** Its side
|
|
716
|
+
padding is `pagePad` (`@lotics/ui/spacing`), so a screen that
|
|
761
717
|
genuinely cannot use it — one with a rail, a side panel, or a scroller it must hold a ref to —
|
|
762
718
|
still lands on the same edge by importing that token. It OWNS its `ScrollView` and exposes no
|
|
763
719
|
scroll props, which is the real limit on adoption.
|
|
@@ -804,21 +760,20 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
804
760
|
- **`comments_button`** — `CommentsButton`: THE discussion affordance for a listed record — the
|
|
805
761
|
count and a bubble as ONE control, opening the thread. Reach for it wherever a record is listed
|
|
806
762
|
and its conversation would otherwise be invisible until opened (a register row, a card, a
|
|
807
|
-
compact header); it is `CopyButton`'s peer
|
|
763
|
+
compact header); it is `CopyButton`'s peer and
|
|
808
764
|
shares its surface, scale and focus ring, so a row carrying both reads as one grammar. The count
|
|
809
765
|
is INSIDE the target, never loose text beside an `IconButton`: the number is what the reader
|
|
810
766
|
aims at, and splitting them leaves half the affordance dead under the pointer. FULL INK, unlike
|
|
811
|
-
the muted values around it —
|
|
812
|
-
|
|
767
|
+
the muted values around it — the one thing on the row that can be UNREAD. `subject` reaches the
|
|
768
|
+
accessible
|
|
813
769
|
NAME only ("3 comments on Northwind Packaging" is a destination; "3 comments" on the fortieth row
|
|
814
770
|
is not) — never the visible label, which would spend row width restating whose record it is. The
|
|
815
771
|
pack's `commentsButton.withSubject` builds that phrase, so the word ORDER is the pack's to choose
|
|
816
|
-
and the join is a WORD
|
|
817
|
-
|
|
818
|
-
**Render it only when `count > 0`** — a zero on every quiet row is a column of noise
|
|
819
|
-
|
|
820
|
-
thread (`tpl_item_list` opens the record's drawer on its comments section)
|
|
821
|
-
reader cannot act on states that a conversation exists and leaves them to find it.
|
|
772
|
+
and the join is a WORD — a screen reader drops punctuation along with the relation it was
|
|
773
|
+
carrying.
|
|
774
|
+
**Render it only when `count > 0`** — a zero on every quiet row is a column of noise. Pair it
|
|
775
|
+
with a jump that LANDS on the
|
|
776
|
+
thread (`tpl_item_list` opens the record's drawer on its comments section).
|
|
822
777
|
- **`copy_button`** — `CopyButton`: THE copy control — `value` onto the clipboard, glyph
|
|
823
778
|
flips to a check and the tooltip to the pack's "Copied" for ~2s. The confirmation follows
|
|
824
779
|
a REAL write: an unreachable clipboard throws and the button stays at rest rather than
|
|
@@ -840,8 +795,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
840
795
|
`nav.back` — "Back" / "Quay lại"). Bare it is the 40px chevron-left disc; pass
|
|
841
796
|
**`label`** to name where back GOES ("Danh sách") — the SAME disc with the destination
|
|
842
797
|
beside it, ONE pressable over disc + words, never an icon button beside unclickable
|
|
843
|
-
prose. It takes no size or colour
|
|
844
|
-
one disc so they cannot drift.
|
|
798
|
+
prose. It takes no size or colour.
|
|
845
799
|
**A back affordance INSIDE a header row is not this** — beside a drawer's title a 40px
|
|
846
800
|
disc outweighs the name it belongs to, so that one is a small `IconButton` (see
|
|
847
801
|
`screen_router` below). The distinction is prominence: this HEADS a surface, that
|
|
@@ -889,8 +843,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
889
843
|
- **`floating_action_bar`** — `FloatingActionBar`: the floating bulk-select action bar.
|
|
890
844
|
- **`pressable_row`** — `PressableRow`: THE register row — full-width hover/open wash,
|
|
891
845
|
`selected` (the open record) vs `marked` (ticked in bulk-select), forwards `ref` for
|
|
892
|
-
popover anchoring. **Its text is SELECTABLE**:
|
|
893
|
-
|
|
846
|
+
popover anchoring. **Its text is SELECTABLE**: dragging across a name and copying it
|
|
847
|
+
works, and a release that selected text
|
|
894
848
|
does not open the record (see `press_selection`). Furniture that must not smear over —
|
|
895
849
|
`Badge`, `Avatar`, `Button` — opts out for itself.
|
|
896
850
|
The guard behind it is internal (`press_selection`): only a real, non-empty selection
|
|
@@ -948,21 +902,19 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
948
902
|
the section's add (that lives on the heading row, where it does not move — § The add-placement
|
|
949
903
|
law in composition.md), a no-results empty is HINT-only since the filters that emptied it carry
|
|
950
904
|
their own clear, and a FAILED read is `ErrorState`, not this. **`compact`** for one BAND of a
|
|
951
|
-
record — left-aligned, glyph-less, 56px against the default's 178
|
|
952
|
-
|
|
953
|
-
whole region, where nothing else is in view to say so. → composition.md § Empty states.
|
|
905
|
+
record — left-aligned, glyph-less, 56px against the default's 178; the centred default speaks
|
|
906
|
+
for a whole region. → composition.md § Empty states.
|
|
954
907
|
- **`error_state`** — `ErrorState`: the region-scale FAILED read — `message` + optional `detail`
|
|
955
908
|
+ `onRetry` (the kit renders the button and words it from the locale pack, so "try again" reads
|
|
956
909
|
the same everywhere). The fourth of the region states: `Skeleton`/`Loading` in flight → this on
|
|
957
|
-
failure → `EmptyState` on nothing → `CompletionState` on done.
|
|
958
|
-
asserts the read succeeded and found nothing, when nothing is known. For a failure INSIDE a
|
|
910
|
+
failure → `EmptyState` on nothing → `CompletionState` on done. For a failure INSIDE a
|
|
959
911
|
flow (a form that won't save) use `Callout tone="error"`; this is for a region with no content
|
|
960
912
|
to show, where a tinted strip leaves the area collapsed.
|
|
961
913
|
- **`completion_state`** — `CompletionState`: the "all done" terminal state.
|
|
962
914
|
- **`skeleton`** — `Skeleton`: loading placeholder blocks. **Mirror the layout that is
|
|
963
915
|
arriving** — same row count, same column widths, same heights. A skeleton of a different
|
|
964
|
-
shape makes the content jump when it lands
|
|
965
|
-
|
|
916
|
+
shape makes the content jump when it lands. First of the four region states; the other three are
|
|
917
|
+
under `error_state`.
|
|
966
918
|
- **`loading`** — `Loading`: the centered indeterminate loading state (composes
|
|
967
919
|
`DotsIndicator`).
|
|
968
920
|
- **`activity_indicator`** — `ActivityIndicator`: the bare spinner.
|
|
@@ -980,8 +932,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
980
932
|
also home of the shared `PickerOption` type. Its empty row does one of two jobs and they
|
|
981
933
|
are not interchangeable: `includeEmptyOption` makes empty a **choice** the reader picks to
|
|
982
934
|
clear the field, so it is NAMED ("None", from the locale pack; override per instance with
|
|
983
|
-
`emptyOptionLabel`) and keeps that name after a selection
|
|
984
|
-
something is chosen is missing exactly when it is the only route back to empty. Without
|
|
935
|
+
`emptyOptionLabel`) and keeps that name after a selection. Without
|
|
985
936
|
that flag the row is a **placeholder**: it carries `placeholder` and leaves on selection.
|
|
986
937
|
Don't reach for `placeholder` to name a choice — a hint and a value read differently.
|
|
987
938
|
Inside a `FormField` (so inside `FormPicker`) it takes its NAME from the visible label
|
|
@@ -992,8 +943,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
992
943
|
`renderSelected(item, { remove })` + `searchable` + `allowCustom` — the tag field is just
|
|
993
944
|
a multi Select; opens `OptionList`. **Set `searchPlaceholder` whenever `allowCustom` is on**:
|
|
994
945
|
the create row only exists once there is a query, so on an untyped menu the placeholder is the
|
|
995
|
-
only always-visible place that can say a new value is allowed
|
|
996
|
-
|
|
946
|
+
only always-visible place that can say a new value is allowed. `customOptionLabel` names what
|
|
947
|
+
the create row will
|
|
997
948
|
do, with the value in it. Its trigger is `role="combobox"` (a `<div>`), never
|
|
998
949
|
`role="button"`: the chip box legitimately puts a remove button INSIDE the trigger, and a
|
|
999
950
|
real `<button>` may not contain one. **Any trigger that renders caller-supplied content
|
|
@@ -1006,13 +957,12 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1006
957
|
opens — single/multi, optional internal search, create row, keyboard + native-`<select>`
|
|
1007
958
|
typeahead; host it directly in a `Popover`/`Dialog` for a command palette. A chosen row is
|
|
1008
959
|
`selected`: the kit's `zinc.100` resting highlight AND `aria-selected`, one weight above the
|
|
1009
|
-
`zinc.50` the keyboard/hover row takes
|
|
960
|
+
`zinc.50` the keyboard/hover row takes.
|
|
1010
961
|
It opens SCROLLED to its seat (the selected row in `search.mode:"none"`, else the first real
|
|
1011
|
-
option), which only shows on a list longer than its own box
|
|
1012
|
-
96-row list opening on your value and opening at the top.
|
|
962
|
+
option), which only shows on a list longer than its own box.
|
|
1013
963
|
`indeterminateValues` (multi) draws a MIXED tick on values only SOME of the things being
|
|
1014
|
-
edited carry — the shape for labelling a selection
|
|
1015
|
-
|
|
964
|
+
edited carry — the shape for labelling a selection. Presentational and one-directional: a mixed
|
|
965
|
+
row is not in `value`, so
|
|
1016
966
|
pressing it selects (the caller applies to all), and it announces itself in the row label
|
|
1017
967
|
because `role="option"` has no ARIA state for a third value.
|
|
1018
968
|
- **`combobox`** — COMPOUND single-select editable search: `Combobox` root +
|
|
@@ -1056,6 +1006,17 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1056
1006
|
field with an empty slot reserves nothing, so its text starts and ends where it always did.
|
|
1057
1007
|
- **`number_input`** — `NumberInput`: numeric input; `format` for currency/units.
|
|
1058
1008
|
- **`search_input`** — `SearchInput`: the search box for toolbars/filters.
|
|
1009
|
+
- **`markdown_editor`** — `MarkdownEditor`: the standalone WYSIWYG whose value stays a plain
|
|
1010
|
+
markdown string, with the formatting toolbar as a SIBLING of the field. `variant` is
|
|
1011
|
+
`"document"` (the default soft sheet) or the two FIELD states `"framed"` / `"bare"`, DERIVED
|
|
1012
|
+
from `InlineEditVariant`; the editor draws its own surface, so a host frame never paints one
|
|
1013
|
+
under it. Web is ProseMirror via `@lotics/markdown-editor`, native falls back to raw-text
|
|
1014
|
+
editing — see § Edit a record's fields in place for the whole family and its `numberOfLines`
|
|
1015
|
+
reserve. **`markdown_editor_props`** is the platform-neutral `MarkdownEditorProps` /
|
|
1016
|
+
`MarkdownEditorVariant` that keeps the two implementations from drifting; **`markdown_editor.css`**
|
|
1017
|
+
is the web sheet, imported once at the app entry.
|
|
1018
|
+
- **`form_markdown_editor`** — `FormMarkdownEditor`: `FormField` wrapping a `MarkdownEditor` —
|
|
1019
|
+
the labeled prose field, for a dialog or settings form.
|
|
1059
1020
|
- **`form_field`** — `FormField` + `useFormField`: label / description / warning / error /
|
|
1060
1021
|
`optional` marker wrapper (the three field-annotation tones — muted / amber / danger — all
|
|
1061
1022
|
wired into the input's describedby); the fieldset grid cell (`half`/`full` widths — see the
|
|
@@ -1079,7 +1040,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1079
1040
|
switch (`accessibilityRole="switch"`, the inner Switch read-only). The settings-panel/menu
|
|
1080
1041
|
row toggle. The leading slot takes EITHER `icon` (an `IconName`, the app's own glyph language)
|
|
1081
1042
|
or `leading` (any node); `icon` wins if both are passed. A row naming an outside product passes
|
|
1082
|
-
its logo through `leading
|
|
1043
|
+
its logo through `leading`.
|
|
1083
1044
|
- **`use_form`** — `useForm`: THE batch draft-form state hook — `values` = `initialValues` +
|
|
1084
1045
|
an edits overlay (a revalidation refreshes untouched fields, no sync effect), `validate`
|
|
1085
1046
|
(sync/async, gates submit, editing clears the field's error), `onSubmit(values, helpers)`
|
|
@@ -1110,7 +1071,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1110
1071
|
- **`date_filter`** — `DateFilter`: the date+time period filter panel — presets
|
|
1111
1072
|
(`PresetId`), calendar, optional time segments; the body `DateRangeFilterField` opens.
|
|
1112
1073
|
**Every preset SETS a range** — the list holds no mode and no null case, so a hand-picked
|
|
1113
|
-
range selects nothing
|
|
1074
|
+
range selects nothing. The
|
|
1114
1075
|
presets are a `listbox` of `MenuButton` options: selection is the kit's `zinc.100` row
|
|
1115
1076
|
highlight plus `aria-selected`, never a filled primary treatment, because the row reports
|
|
1116
1077
|
state rather than offering an action. Sidebar at viewports of 768 and up, strip below —
|
|
@@ -1146,13 +1107,11 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1146
1107
|
`DeadlineThresholds`, `Deadline`). Three contracts a screen must not invent for itself — how
|
|
1147
1108
|
far off a date is, what to CALL that number, and how loud it reads. `daysUntil` collapses both
|
|
1148
1109
|
ends to midnight because the vocabulary is day-granular: rounding the raw gap lands on the
|
|
1149
|
-
wrong day whenever now and the deadline straddle noon
|
|
1150
|
-
one at 02:00 today reads "1 day overdue" 12 hours in). Wording comes from the `deadline` locale
|
|
1110
|
+
wrong day whenever now and the deadline straddle noon. Wording comes from the `deadline` locale
|
|
1151
1111
|
slice; the 1-day/3-day tone lines are freight defaults and move per domain via
|
|
1152
1112
|
`DeadlineThresholds`. **`deadlineAnnotation` spreads onto the `DetailRow` holding the date** —
|
|
1153
1113
|
a deadline is a property OF that field, and the three annotation slots carry the three urgency
|
|
1154
|
-
levels exactly (`error` / `warning` / `description`)
|
|
1155
|
-
speaking a vocabulary the rest of the field grid does not. `nearestDeadline` returns an OVERDUE
|
|
1114
|
+
levels exactly (`error` / `warning` / `description`). `nearestDeadline` returns an OVERDUE
|
|
1156
1115
|
milestone FIRST and expects the caller to pass only OPEN ones — done-ness is not knowable from
|
|
1157
1116
|
a date, and skipping past a missed cut-off is how a record silently stops reporting the failure.
|
|
1158
1117
|
Worked example: `tpl_record`'s Due rows.
|
|
@@ -1200,8 +1159,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1200
1159
|
same shape `Select` gives: return `<Chip onDismiss={remove}>` for a removable tag, or a plain
|
|
1201
1160
|
badge that ignores it — a dense grid column is the case for the badge, since the ✕ costs more
|
|
1202
1161
|
width there than the reopen it saves. `remove` detaches that one tag whether the list is open or
|
|
1203
|
-
shut, and never opens the list
|
|
1204
|
-
trigger needs nothing to hold the press back. Single has no `remove`: unsetting one value is `onClear` and its
|
|
1162
|
+
shut, and never opens the list. Single has no `remove`: unsetting one value is `onClear` and its
|
|
1205
1163
|
Clear row. Both modes take
|
|
1206
1164
|
`allowCustom` (a create-a-tag/option row) + `searchable` + **`customOptionPlacement`** (default
|
|
1207
1165
|
`"bottom"`, right for a tag field; pass `"top"` for a find-or-create REFERENCE picker over a long
|
|
@@ -1230,9 +1188,14 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1230
1188
|
surface already promises and, down a column, draws the grid twice.
|
|
1231
1189
|
|
|
1232
1190
|
It is an axis of weight, not of use: both hover and open identically. There is no `*Cell` family —
|
|
1233
|
-
a data-grid cell is `<InlineSelect variant="bare" …/>`, not a `SelectCell
|
|
1234
|
-
USE, and duplicating the picker stack to flip two style properties, are both things the kit forbids).
|
|
1191
|
+
a data-grid cell is `<InlineSelect variant="bare" …/>`, not a `SelectCell`.
|
|
1235
1192
|
Hand-roll a pressable cell to match `bare`, never a background wash.
|
|
1193
|
+
- **`inline_markdown`** — `InlineMarkdown`: the PROSE member of the Inline\* family — a
|
|
1194
|
+
`MarkdownEditor` on the inline-field box metrics, `onSave` per field. It is the one member
|
|
1195
|
+
that does NOT swap: the editor is mounted at rest and merely gains a draft on focus, so
|
|
1196
|
+
focusing moves nothing and the prose stays SELECTABLE instead of sitting under a button. Its
|
|
1197
|
+
toolbar is off for the same reason, and `variant` passes THROUGH to the editor, which draws
|
|
1198
|
+
the box. Full note under § Edit a record's fields in place.
|
|
1236
1199
|
- **`inline_files`** — `InlineFiles`: the FILES member of the Inline\* family — a record's
|
|
1237
1200
|
attachments as a `DetailRow` value. A `FileRows` LIST (press a row to preview full-screen, ⋯
|
|
1238
1201
|
to remove) with ONE CTA under it; no selection mode, no bulk bar, no upload queue — reach for
|
|
@@ -1240,8 +1203,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1240
1203
|
is the FIELD's cardinality and it changes the verb**: many → the CTA reads `addMoreLabel` and
|
|
1241
1204
|
the host APPENDS in `onAdd`; one → it reads `replaceLabel` and the host SETS, because "add
|
|
1242
1205
|
another" is not an act a single-file field can perform and offering it is how a record ends up
|
|
1243
|
-
holding two of something the schema says there is one of
|
|
1244
|
-
|
|
1206
|
+
holding two of something the schema says there is one of. `blockedReason` renders a sentence
|
|
1207
|
+
instead of the CTA for a
|
|
1245
1208
|
field with nothing to attach TO yet — never a disabled button, which says only that you may
|
|
1246
1209
|
not. A LIST and not switchable: documents are identified by NAME, images by CONTENT, so a
|
|
1247
1210
|
photo field wants `FileGrid`/`FilesEditor` instead — that is a judgment about the data, so it
|
|
@@ -1291,8 +1254,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1291
1254
|
unit (though "kg"/"$" belong IN the value via `InlineNumberInput format`) — composes into a
|
|
1292
1255
|
row inside the value cell, costing only the row that wants it. The LABEL WRAPS inside its column and
|
|
1293
1256
|
is never clipped — a fixed `labelWidth` would otherwise ellipsize every long field name
|
|
1294
|
-
("Registered business address")
|
|
1295
|
-
|
|
1257
|
+
("Registered business address"); a wrapped label's FIRST line stays level with the value's first
|
|
1258
|
+
control line while a
|
|
1296
1259
|
one-line label still centers on it (both modes, no prop). The FIELD-ANNOTATION vocabulary (same
|
|
1297
1260
|
names + meanings as `FormField`), always EXPLICIT — a row never hides guidance behind an ⓘ:
|
|
1298
1261
|
**`description`** = a fact / persistent guidance (muted), under the VALUE (stacked mode
|
|
@@ -1335,10 +1298,9 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1335
1298
|
into a pressable door floating its particulars in an anchored popover (put links INSIDE the
|
|
1336
1299
|
peek — never a button in a button; `reference` is the trailing-link alternative for static
|
|
1337
1300
|
rows), `LedgerTotal` = the divider-set emphasized close with `zeroLabel` for settled. **`meta` is a
|
|
1338
|
-
neutral QUALIFIER** (a date, a method) at caption weight — never a problem or a state:
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
that can FIX it, and in a statement the arithmetic has usually said it already.
|
|
1301
|
+
neutral QUALIFIER** (a date, a method) at caption weight — never a problem or a state: a
|
|
1302
|
+
caption is the wrong weight for something wanting action. A problem goes on the row
|
|
1303
|
+
that can FIX it.
|
|
1342
1304
|
**Pick the shape by what the statement IS**, because there are two and they read differently:
|
|
1343
1305
|
- **`charges → total`** — `LedgerGroup`s, each **closed by its own sum** ("Charges" vs
|
|
1344
1306
|
"Received"), giving three ascending rungs: row `sm/regular`, subtotal `sm/medium`, total
|
|
@@ -1364,10 +1326,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1364
1326
|
whose lookup link sits INSIDE the popover, a flat `reference` on the row with nothing to
|
|
1365
1327
|
expand, `meta` + `success` tone on money coming back, and a one-row `Adjustments` group that
|
|
1366
1328
|
takes no `total`. `Received` is derived from the charges carrying a payment METHOD rather than
|
|
1367
|
-
a second flag to keep in step
|
|
1368
|
-
trust by summing exactly what its label claims. `LedgerBasis` is demoed on the gallery's
|
|
1369
|
-
Charts page instead — a delivery order has no base to compute from, and inventing one to
|
|
1370
|
-
place a component is how invented needs start.
|
|
1329
|
+
a second flag to keep in step. `LedgerBasis` is demoed on the gallery's Charts page instead.
|
|
1371
1330
|
- **`running_ledger`** — `RunningLedger`: a CHRONOLOGICAL, self-totalling ledger — a stock
|
|
1372
1331
|
kardex, a statement of account — where every row carries the balance AS OF that row. Not
|
|
1373
1332
|
`Ledger`: that compound closes a fixed set of charge/receipt groups into one total and has no
|
|
@@ -1375,23 +1334,17 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1375
1334
|
`rows` (date, label, a SIGNED `amount` — positive in/debit, negative out/credit, one field
|
|
1376
1335
|
rather than paired columns so the same shape serves a stock quantity and a money statement),
|
|
1377
1336
|
each rendered with its running balance **computed by the component**, never accepted as a
|
|
1378
|
-
caller-supplied number
|
|
1379
|
-
accumulates, and a caller-supplied one is the one kind of drift no reader can catch by looking.
|
|
1337
|
+
caller-supplied number.
|
|
1380
1338
|
`format` is ONE formatter for the whole ledger (`formatMoney`, or a quantity formatter with a
|
|
1381
1339
|
unit suffix) — every row shares one unit, which is what lets the column state it once.
|
|
1382
1340
|
`criticalAtOrBelow` reads a row's balance in danger tone once it reaches a caller-declared
|
|
1383
1341
|
floor (a stock-out, a negative balance) — **the ONLY tone this component owns**, evaluated
|
|
1384
1342
|
independently on EVERY balance in the column: each row, the opening anchor (so an already-critical
|
|
1385
1343
|
opening reads danger even with zero rows following it), and the closing line. A row's `amount`
|
|
1386
|
-
is never auto-toned by its arithmetic sign
|
|
1387
|
-
as an IN one
|
|
1344
|
+
is never auto-toned by its arithmetic sign — an OUT movement is routine business as often
|
|
1345
|
+
as an IN one. Closes with a `Divider`-set current-balance line at `Ledger`'s own
|
|
1388
1346
|
`LedgerTotal` weight, `closingLabel` overriding the locale default ("Current balance") for a
|
|
1389
|
-
domain the pack cannot own generically ("Tồn kho hiện tại").
|
|
1390
|
-
— a stock kardex or an AR statement of account is a genuinely different shape from every
|
|
1391
|
-
existing template (the 3-statement accrual grammar in `tpl_statements`, the fleet-register
|
|
1392
|
-
drawer in `tpl_stock`), so forcing it into either would be a mismatch rather than a worked
|
|
1393
|
-
example; proven against real, live deployed consumers instead (a warehouse and a receivables
|
|
1394
|
-
app) until a template of its own earns its place.
|
|
1347
|
+
domain the pack cannot own generically ("Tồn kho hiện tại").
|
|
1395
1348
|
- **`reference_field`** — `ReferenceField`: a reference to ANOTHER RECORD, rendered as a
|
|
1396
1349
|
FIELD VALUE — the kit's inline-editor surface (so a pointer sits in the value column
|
|
1397
1350
|
like the editors above and below it), whose press opens a PEEK of that record's facts.
|
|
@@ -1404,23 +1357,21 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1404
1357
|
VALUE floor (200), not the control floor, because that column holds names as well as editors.
|
|
1405
1358
|
The verbs live INSIDE the
|
|
1406
1359
|
peek, never on the field, and they are ALL `Button`s at one altitude — a peek is
|
|
1407
|
-
dialog-scale and a dialog's verbs are buttons
|
|
1408
|
-
|
|
1360
|
+
dialog-scale and a dialog's verbs are buttons. The row splits by WHAT EACH TOUCHES, and the
|
|
1361
|
+
spacer is that
|
|
1409
1362
|
seam: **left, the LINK** — `Change` (`onChange`, `secondary`) points the field at a different
|
|
1410
1363
|
record, named for the intent and deliberately not `danger` since a recoverable correction must
|
|
1411
1364
|
not wear the colour reserved for destruction; `Clear` (`onClear`, NO fill — the least-reached
|
|
1412
1365
|
verb of the four) leaves it empty. The draft's footer follows the same rule: `Cancel` carries
|
|
1413
1366
|
no fill beside a filled `Save`, so the only filled button in either mode is the one that acts. **Both are REQUIRED**, so the footer has one shape
|
|
1414
|
-
everywhere
|
|
1415
|
-
Change and then declining to pick reaches the same empty state. **Right, the RECORD it points at** — `Edit` (`primary`, the one filled-dark rung,
|
|
1367
|
+
everywhere. **Right, the RECORD it points at** — `Edit` (`primary`, the one filled-dark rung,
|
|
1416
1368
|
handed to `Save` when the draft opens) and `Open` (`secondary`, rightmost, OPTIONAL, the
|
|
1417
|
-
departure — not prioritized
|
|
1418
|
-
|
|
1369
|
+
departure — not prioritized). Every verb is optional and the footer renders only when it HAS
|
|
1370
|
+
one, so an
|
|
1419
1371
|
edit-only reference draws no rule under nothing.
|
|
1420
1372
|
**Both unset — they differ only in FOLLOW-THROUGH.** `Clear` is the plain detach (nothing
|
|
1421
|
-
focused); `Change` is the same write plus the caller's focus handoff.
|
|
1422
|
-
|
|
1423
|
-
it" from "unset it, I'm about to pick another". Whether empty is VALID is the row's business — its
|
|
1373
|
+
focused); `Change` is the same write plus the caller's focus handoff. Whether empty is VALID is
|
|
1374
|
+
the row's business — its
|
|
1424
1375
|
`warning`, its validation — never the peek's.
|
|
1425
1376
|
**Hand the picker focus after `Change`, never after `Clear`.** Both land on your empty state.
|
|
1426
1377
|
Change said "wrong one", so open the picker focused (`Combobox`'s `autoFocus` both focuses AND
|
|
@@ -1466,11 +1417,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1466
1417
|
Omitting `onOpen` renders a plain, non-interactive reference — never a press target wired to
|
|
1467
1418
|
nothing, which is worse than no affordance. Composes as a `DetailRow`'s value for a single
|
|
1468
1419
|
relationship, or repeats plainly (no per-row label) in a list for several — the surrounding
|
|
1469
|
-
label or heading is the caller's, this renders one reference.
|
|
1470
|
-
yet, for the same reason as `running_ledger` above: what this component is FOR — a reference
|
|
1471
|
-
into a genuinely separate deployed app — has no natural home inside any single-app template in
|
|
1472
|
-
this repo without inventing a fictional sibling app to point at; proven against real, live
|
|
1473
|
-
deployed consumers instead.
|
|
1420
|
+
label or heading is the caller's, this renders one reference.
|
|
1474
1421
|
- **`peek`** — `Peek`: drill-down for a reference — press a name/id where it appears and get its
|
|
1475
1422
|
details in an anchored popover, without leaving the screen; keep the content a summary with ONE
|
|
1476
1423
|
action to the full record. **`variant` picks the register the TRIGGER sits in, and getting it
|
|
@@ -1498,9 +1445,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1498
1445
|
**The row OWNS its `title`/`description` pair, so do not hand-roll one beside it.** Both
|
|
1499
1446
|
lines are `sm`, `leading="tight"`, no gap between them — the stacked-pair law in
|
|
1500
1447
|
[composition.md](./composition.md), applied by the component rather than restated at each
|
|
1501
|
-
call site.
|
|
1502
|
-
between the lines, against the 6px the rule prescribes), and the height was sized around
|
|
1503
|
-
that mistake at 64, which left a correctly-sized `md` mark adrift in it; the row is 56 now.
|
|
1448
|
+
call site. The row is 56.
|
|
1504
1449
|
A `description` passed as a NODE keeps its own leading — the component only sets it on the
|
|
1505
1450
|
string branch, because forcing a line box on somebody else's subtree re-spaces content that
|
|
1506
1451
|
may not be a line of text at all.
|
|
@@ -1524,14 +1469,10 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1524
1469
|
default.** A `SummaryLine`/`KPIStrip` figure above the register is a claim that ONE column is
|
|
1525
1470
|
the reason to scan this page; left unset, `priority` defaults to column order and sheds
|
|
1526
1471
|
right-to-left, so that exact column is the first thing table_fit drops the moment the
|
|
1527
|
-
container narrows
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
register answers "which one"; the moment it cannot answer "and what about it" the reader is
|
|
1532
|
-
sent to a drawer, loses the surrounding rows, and pays a navigation to come back — which is
|
|
1533
|
-
what makes scanning twenty records unaffordable. Reach for it when the detail is read or
|
|
1534
|
-
light-edit; a heavy form still deserves its own surface. `expanded` is CONTROLLED — how many
|
|
1472
|
+
container narrows.
|
|
1473
|
+
**`detail` + `expanded` reveal a row's detail BENEATH it instead of navigating away.**
|
|
1474
|
+
Reach for it when the detail is read or light-edit; a heavy form still deserves its own surface.
|
|
1475
|
+
`expanded` is CONTROLLED — how many
|
|
1535
1476
|
may be open at once is the caller's rule, so the component does not pick one. The detail
|
|
1536
1477
|
renders as a SIBLING of the press surface (a control inside it would otherwise be swallowed
|
|
1537
1478
|
by the row's own toggle), the row shows the open wash while it is out, and the door announces
|
|
@@ -1539,8 +1480,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1539
1480
|
to expand.
|
|
1540
1481
|
**`Table.count` + `TableRow.ordinal` make it a COUNTED register** — a narrow gutter left of
|
|
1541
1482
|
`leading` carrying each row's position, headed by the total. Reach for it on any register
|
|
1542
|
-
long enough to scroll
|
|
1543
|
-
without counting rows, and it is what a reader coming from a spreadsheet (STT) expects to
|
|
1483
|
+
long enough to scroll — it is what a reader coming from a spreadsheet (STT) expects to
|
|
1544
1484
|
find. `count` is the single switch — omit it and no gutter is reserved, so an uncounted
|
|
1545
1485
|
register keeps every pixel it had. It is CHROME, not a column: it never sorts, never drops,
|
|
1546
1486
|
and is excluded from the column band, but it IS reserved space the fit accounts for.
|
|
@@ -1560,8 +1500,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1560
1500
|
`SortHeaderLabels`: the sortable column header and the sort-state helpers `Table`/
|
|
1561
1501
|
`DataGrid` consumers drive. `sortBy` copies rather than mutates, and compares strings with
|
|
1562
1502
|
`localeCompare` (case- and accent-folded, `numeric`) rather than `<`/`>` — code-point order
|
|
1563
|
-
exiles every accented letter past the whole unaccented alphabet
|
|
1564
|
-
reads correctly until its first Đ and looks fine to a reviewer forever. `getValue` returns a
|
|
1503
|
+
exiles every accented letter past the whole unaccented alphabet. `getValue` returns a
|
|
1565
1504
|
NUMBER for anything ordered numerically (a timestamp, a count).
|
|
1566
1505
|
- **`data_grid`** — `DataGrid` + `gridRowStyle`: the inline-managed grouped table (see
|
|
1567
1506
|
[Tabular data](#tabular-data--pick-by-scale--intent)); `labels` localizes the sort-header
|
|
@@ -1593,9 +1532,9 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1593
1532
|
`Summary.Header` / `Summary.Total` / `Summary.Metric` / `Summary.Distribution` /
|
|
1594
1533
|
`Summary.Facts`, composed as children. **Declare the buckets ONCE on the root and every part
|
|
1595
1534
|
derives from them** — the headline is their sum (there is no `total` prop to go stale), the
|
|
1596
|
-
segments and the legend swatches are the buckets' own colours
|
|
1597
|
-
|
|
1598
|
-
|
|
1535
|
+
segments and the legend swatches are the buckets' own colours: a `segments` array passed
|
|
1536
|
+
beside an `items` array beside a `total` drifts three ways, invisibly. A `SummaryBucket`
|
|
1537
|
+
must PARTITION the set; an aggregate over the same
|
|
1599
1538
|
rows that does not (a condition like "overdue" beside "draft / sent / paid", counted again
|
|
1600
1539
|
from a different question) goes to `Summary.Facts` as `extra`, where it renders with NO swatch
|
|
1601
1540
|
— the visual difference says it is not a slice of the bar. Ordering is the caller's and is
|
|
@@ -1631,7 +1570,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1631
1570
|
springing to a filled check when done, distinct from the square checkbox; the
|
|
1632
1571
|
task/to-do/checklist control. `partial` half-fills it, for the row that is underway but not
|
|
1633
1572
|
finished (a task at "doing", a step whose children are half ticked) — a binary ring reports
|
|
1634
|
-
that row as untouched
|
|
1573
|
+
that row as untouched. The CLICK stays binary
|
|
1635
1574
|
(`onChange(done)`) so the ring means one thing everywhere; `partial` is reached through
|
|
1636
1575
|
whatever NAMES it — a status cell, or children ticking off — never by cycling the ring. Keep
|
|
1637
1576
|
it monochrome and let colour live in the status cell.
|
|
@@ -1680,9 +1619,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1680
1619
|
never dismiss it — clicks, scrolls, and Escape belong to that layer until it closes. In a
|
|
1681
1620
|
NARROW VIEWPORT (`small`, under 768) it presents instead as a modal bottom sheet (scrim, close
|
|
1682
1621
|
button, slide-up) — decided by `PopoverContent` itself off the screen size, with nothing to pass
|
|
1683
|
-
and no way to answer it differently:
|
|
1684
|
-
|
|
1685
|
-
the device — a few-hundred-pixel side panel on a desktop gets the sheet too.
|
|
1622
|
+
and no way to answer it differently: the VIEWPORT, never the container the trigger sits in,
|
|
1623
|
+
and never the device — a few-hundred-pixel side panel on a desktop gets the sheet too.
|
|
1686
1624
|
`PopoverContent` already insets its
|
|
1687
1625
|
body 12px — put content directly in it, NEVER add your own padding `View` (that double-pads);
|
|
1688
1626
|
title/actions go in `PopoverHeader` / `PopoverFooter`. **A fixed width is the `width` PROP,
|
|
@@ -1691,8 +1629,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1691
1629
|
then stretches the panel and leaves the content adrift in a box wider than itself. A popover
|
|
1692
1630
|
with no bands never sees this.
|
|
1693
1631
|
The built-in scroller is **full-bleed horizontally** and re-insets its content by the same
|
|
1694
|
-
amount, so a scrolling body reaches the panel's real edges
|
|
1695
|
-
panel edge rather than under an invisible margin 12px inside it — while the text stays on the
|
|
1632
|
+
amount, so a scrolling body reaches the panel's real edges while the text stays on the
|
|
1696
1633
|
same column the header and footer use. Nothing to opt into; `disableBodyScroll` consumers
|
|
1697
1634
|
(which own their scroll) are untouched.
|
|
1698
1635
|
- **`popover_nav`** — `usePopoverNav` + `PopoverScreen` + `PopoverNavHeader` — the popover's
|
|
@@ -1735,8 +1672,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1735
1672
|
section wrappers must be DIRECT children of the ScrollView content. The highlight and the
|
|
1736
1673
|
jump both re-measure the live DOM on web, so content loading in above a section never
|
|
1737
1674
|
strands them. Reaching the END of the scroll activates the LAST key regardless of offsets —
|
|
1738
|
-
a final section shorter than the viewport never scrolls its top past the trigger line
|
|
1739
|
-
|
|
1675
|
+
a final section shorter than the viewport never scrolls its top past the trigger line.
|
|
1676
|
+
On NARROW containers
|
|
1740
1677
|
the rail becomes a PINNED bar naming the CURRENT section that opens a full-page
|
|
1741
1678
|
section-picker `Modal` — never a horizontal tab strip. Worked example:
|
|
1742
1679
|
[`tpl_record`](../examples/tpl_record.tsx).
|
|
@@ -1747,11 +1684,10 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1747
1684
|
### Numbers & charts
|
|
1748
1685
|
|
|
1749
1686
|
**These are where a work screen gets its visual interest, and it is the only kind that survives
|
|
1750
|
-
contact with a real dataset.**
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
sentences. → [composition.md](./composition.md) §"Character comes from the DATA".
|
|
1687
|
+
contact with a real dataset.** Each of these is information first, so none of it is the
|
|
1688
|
+
decoration that gets cut in review. A screen reported as flat or boring is usually a screen with
|
|
1689
|
+
numbers in it rendered as sentences.
|
|
1690
|
+
→ [composition.md](./composition.md) §"Character comes from the DATA".
|
|
1755
1691
|
|
|
1756
1692
|
Two failure modes to hold against them. A figure nobody compares to anything is not a metric — it
|
|
1757
1693
|
is a number with a large font, and the size promises a significance it cannot deliver. And a
|
|
@@ -1763,7 +1699,7 @@ component rather than showing it at zero.
|
|
|
1763
1699
|
(ⓘ), so the reader can find out what "on-time %" counts without leaving the screen.
|
|
1764
1700
|
- **`kpi_strip`** — `KPIStrip`: the boxed dashboard stat band. Every item has to answer a
|
|
1765
1701
|
question the surface below it cannot — a strip restating totals the table already carries is
|
|
1766
|
-
the trim test failing
|
|
1702
|
+
the trim test failing.
|
|
1767
1703
|
- **`metric`** — `Metric`: a bare headline figure (`format`
|
|
1768
1704
|
currency|number|percentage|none, `tone`, `size` sm|md|lg|hero). Bare = no card, no label of its
|
|
1769
1705
|
own, so whatever contains it has to name it. Always `tabular` — it is set that way here, and it
|
|
@@ -1777,10 +1713,13 @@ component rather than showing it at zero.
|
|
|
1777
1713
|
Direction words via the `trendFooter` locale slice; goes in `SectionCard footer`.
|
|
1778
1714
|
- **`sparkline`** — `Sparkline`: the inline mini trend line. Sized for a ROW or a card corner:
|
|
1779
1715
|
it carries SHAPE (rising, spiky, flat) and no readable values, which is exactly what a
|
|
1780
|
-
register wants
|
|
1781
|
-
without stopping. Reach for a real chart the moment someone needs to read a value off it.
|
|
1716
|
+
register wants. Reach for a real chart the moment someone needs to read a value off it.
|
|
1782
1717
|
- **`bar_chart`** / **`line_chart`** / **`pie_chart`** — `BarChart` / `LineChart` /
|
|
1783
|
-
`PieChart`: the canonical SVG chart set (no recharts).
|
|
1718
|
+
`PieChart`: the canonical SVG chart set (no recharts). `LineChart` prints as many x labels
|
|
1719
|
+
as the track fits and thins the rest, anchored on the LAST point — the newest reading is the
|
|
1720
|
+
one a reader looks up, and anchoring there is what keeps the spacing uniform. The first
|
|
1721
|
+
point is labelled only when it clears the same distance, so a series whose length does not
|
|
1722
|
+
divide evenly drops its opening label rather than crowding the one beside it.
|
|
1784
1723
|
- **`progress_bar`** — `ProgressBar`: the determinate meter; `compact` = ONE row, track + a
|
|
1785
1724
|
plain sm tabular count beside it.
|
|
1786
1725
|
- **`progress_ring`** — `ProgressRing`: the same meter in a circle, and the ONLY circular one. Same API shape as the bar — a real `value`/`max` rather than a
|
|
@@ -1792,7 +1731,34 @@ component rather than showing it at zero.
|
|
|
1792
1731
|
bare ring renders no text, so it announces nothing — whatever owns it must say what it
|
|
1793
1732
|
measures.
|
|
1794
1733
|
- **`stacked_progress_bar`** — `StackedProgressBar`: one whole split across segments (a
|
|
1795
|
-
status mix on one bar).
|
|
1734
|
+
status mix on one bar). Its segments size against EACH OTHER, not against `total` — the
|
|
1735
|
+
track is always full — because one whole is the whole of itself. That is the right model for
|
|
1736
|
+
one bar and the wrong one for a column of them: ten unequal totals draw ten equal bars, which
|
|
1737
|
+
reads as a chart and states nothing. Several wholes → `stacked_bar_chart`.
|
|
1738
|
+
- **`stacked_bar_chart`** — `StackedBarChart`: rows of stacked bars on ONE SHARED SCALE
|
|
1739
|
+
(`max` defaults to the largest row total). Bar LENGTH answers "how big is this one", the
|
|
1740
|
+
segment run answers "what is it made of", in one pass — spend against return per campaign,
|
|
1741
|
+
cost make-up per product line. `series` (key/label/colour, one hue family per dimension)
|
|
1742
|
+
drives a legend that is on by default; each `StackedBarRow` takes `label` + optional `meta`,
|
|
1743
|
+
a pre-formatted headline `value` with its `valueTone`, and a `caption` sentence, so colour is
|
|
1744
|
+
never the only channel. Between `stacked_progress_bar` (one whole, own track) and `breakdown`
|
|
1745
|
+
(one whole + ranked share rows beneath it): reach here the moment there are SEVERAL wholes to
|
|
1746
|
+
compare.
|
|
1747
|
+
- **`waterfall_chart`** — `WaterfallChart`: the BRIDGE — an opening level, the signed steps
|
|
1748
|
+
that move it, and the level they close at. Revenue walked down to net, budget to actual,
|
|
1749
|
+
opening balance to closing. `items` carry a `kind`: `basis` and `total` are LEVELS standing on
|
|
1750
|
+
zero, `delta` (the default) is a signed STEP floating between the level before it and the
|
|
1751
|
+
level after; colour derives from that — neutral basis, emerald up, red down, and a close in
|
|
1752
|
+
the ink of its own sign — so a caller passes no colours. `orientation` vertical columns |
|
|
1753
|
+
horizontal rows (take rows for a narrow container or long step names; a 70px column and a
|
|
1754
|
+
rotated label is not an answer). The span always includes zero, so a bridge that closes at a
|
|
1755
|
+
loss hangs below the baseline instead of floating on a scale that exaggerates every step.
|
|
1756
|
+
`waterfallLayout` is the pure geometry, exported and tested on its own.
|
|
1757
|
+
Pick it over `ledger` when the reader is scanning for SHAPE (a dashboard), and `ledger` when
|
|
1758
|
+
the figures must be read exactly and reconciled line by line (a record). Over `bar_chart`
|
|
1759
|
+
whenever the bars are steps in ONE arithmetic — five columns all starting at zero state a
|
|
1760
|
+
comparison nobody meant. The steps must be all of the arithmetic and must close on the total
|
|
1761
|
+
drawn; derive them from one computation, never two that can drift.
|
|
1796
1762
|
- **`step_progress`** — `StepProgress`: N-of-M dots/segments progress. A real `progressbar`
|
|
1797
1763
|
(valuenow/min/max) because its segments are decoration over ONE quantity — the opposite
|
|
1798
1764
|
case from `stepper`, whose steps carry content a `progressbar` would hide. Pass the stage
|
|
@@ -1813,17 +1779,17 @@ component rather than showing it at zero.
|
|
|
1813
1779
|
`Matrix.Header` (corner + axis labels) + `Matrix.Grid` (`display` number|heat|both — the
|
|
1814
1780
|
cells, pressable, the value IN the cell) + `Matrix.Totals` (row + column + grand) +
|
|
1815
1781
|
`Matrix.Legend`. Pick over `Heatmap` when the NUMBER and totals matter. Figures are
|
|
1816
|
-
RIGHT-aligned — they are `tabular`, and centring them defeats the aligned column
|
|
1817
|
-
|
|
1818
|
-
|
|
1782
|
+
RIGHT-aligned — they are `tabular`, and centring them defeats the aligned column — and row
|
|
1783
|
+
labels carry default ink, because a row's label is its identity, not chrome. `MATRIX_COL_GAP` is
|
|
1784
|
+
exported for a caller budgeting
|
|
1819
1785
|
its own column widths.
|
|
1820
1786
|
- **`matrix_totals`** — the data layer for `Matrix`: `matrixTotals` (the cross-tab
|
|
1821
1787
|
aggregation) + `MatrixAxisItem` / `MatrixCellRef` / `MatrixTotalsResult`; React-free, so a
|
|
1822
1788
|
KPI can be driven off the same numbers the grid shows.
|
|
1823
1789
|
- **`highlight_segments`** — `splitHighlightSegments`: cuts a string into plain and
|
|
1824
|
-
highlighted runs for `Composer`'s `highlightRanges`. React-free
|
|
1825
|
-
|
|
1826
|
-
|
|
1790
|
+
highlighted runs for `Composer`'s `highlightRanges`. React-free: it feeds a mirror sitting
|
|
1791
|
+
behind a real text field, so a boundary off by one paints the tint adrift while everything
|
|
1792
|
+
still looks like a working composer. Overlapping,
|
|
1827
1793
|
unsorted and out-of-bounds ranges all normalize; no input drops a character.
|
|
1828
1794
|
- **`legend_item`** — `LegendItem`: one swatch + label of a chart legend.
|
|
1829
1795
|
- **`remainder_meter`** — `RemainderMeter`: allocated-vs-remaining meter
|
|
@@ -1833,7 +1799,7 @@ component rather than showing it at zero.
|
|
|
1833
1799
|
whole model is one source distributed one way, and a caller with a second relationship to show
|
|
1834
1800
|
reaches for a second instance before reaching for a wider one. Extract a true 3-column strip
|
|
1835
1801
|
only once a SECOND real caller needs the exact same 3-way shape (the kit's own
|
|
1836
|
-
extract-on-the-second-hand-roll rule)
|
|
1802
|
+
extract-on-the-second-hand-roll rule).
|
|
1837
1803
|
- **`allocation_row`** — `AllocationRow`: one target's row in an allocation surface (pairs
|
|
1838
1804
|
with `RemainderMeter`).
|
|
1839
1805
|
|
|
@@ -1863,19 +1829,16 @@ component rather than showing it at zero.
|
|
|
1863
1829
|
composition.md §registers); and **`right` renders beside the press target, not inside
|
|
1864
1830
|
it**, so a control there is valid HTML and keeps its own click — though on a row that
|
|
1865
1831
|
EXPANDS, verbs belong in the BODY and `right` is better kept for a fact (a duration, a
|
|
1866
|
-
badge).
|
|
1867
|
-
the first-line box and the chevron-pairing problems in the first place.
|
|
1832
|
+
badge).
|
|
1868
1833
|
- **`checklist`** — `Checklist` + `ChecklistGroup` + `ChecklistItem` + `ChecklistNote` /
|
|
1869
1834
|
`ChecklistActions` / `ChecklistField`: THE run of tickable rows, at any
|
|
1870
1835
|
altitude — a ring, a title, a value beside it, a muted note, a detail block, an optional
|
|
1871
1836
|
grouping level. **`connected` is the LINE joining the rings, and it is the whole
|
|
1872
|
-
difference
|
|
1873
|
-
sequence), off for a SET picked in any order, where a line claims an order the work does not
|
|
1874
|
-
have. Rows are ticked ONE AT A TIME; where the run is a sequence, derive position from the
|
|
1837
|
+
difference between a SEQUENCE and a set picked in any order.** Rows are ticked ONE AT A TIME; where the run is a sequence, derive position from the
|
|
1875
1838
|
highest stamp so one click marks everything below it — a parent that ticked its children wrote
|
|
1876
1839
|
what the reader never said (today's date onto milestones that happened on unrecorded days),
|
|
1877
|
-
and a rung passed without a recorded day shows a BLANK date
|
|
1878
|
-
|
|
1840
|
+
and a rung passed without a recorded day shows a BLANK date. **`ChecklistGroup` is a phase — a
|
|
1841
|
+
name, and at most a way to fold its rows away**: no
|
|
1879
1842
|
COMPLETION control, because a phase
|
|
1880
1843
|
owns no completion of its own and a ring there is a control that never responds (one identical
|
|
1881
1844
|
to the pressable rings beneath it invites the press it refuses); and no BODY, because a
|
|
@@ -1935,7 +1898,7 @@ component rather than showing it at zero.
|
|
|
1935
1898
|
`accept`, `label`/`hint`/`dropLabel`, `height`); click falls back to a picker, and while
|
|
1936
1899
|
mounted it ALSO takes a paste (Ctrl/Cmd+V) via its inner `FileDropTarget` `paste`, scoped to
|
|
1937
1900
|
the well's region. `label`/`dropLabel`/`hint` default from the locale's `fileDropzone` slice
|
|
1938
|
-
(the hint
|
|
1901
|
+
(the hint names the paste) — pass them only to override.
|
|
1939
1902
|
- **`file_drop_target`** — `FileDropTarget`: makes ANY region accept a file drag-drop —
|
|
1940
1903
|
`{ onFiles, accept?, multiple?, paste?, disabled?, children, style? }` wrapped around a
|
|
1941
1904
|
section, a card, a whole panel, so nobody hunts for a dropzone. While a drag hovers, the
|
|
@@ -1972,8 +1935,8 @@ component rather than showing it at zero.
|
|
|
1972
1935
|
needs, so withholding `onAdd`/`onRemove` IS the read-only shape — there is no `readOnly`
|
|
1973
1936
|
mode. **A HOST verb is a plain `Button`** reading **`useFilesEditorSelection()`**
|
|
1974
1937
|
(`{selected, selectedIds, files, selectMode, clear, exit}`) — an AI read over the picked
|
|
1975
|
-
papers, "send to the broker", a ZIP: acts the kit has never heard of
|
|
1976
|
-
|
|
1938
|
+
papers, "send to the broker", a ZIP: acts the kit has never heard of. **No children means no
|
|
1939
|
+
bar** (a grid that only
|
|
1977
1940
|
previews). Bar words resolve prop → `LoticsLocale.filesEditor`; in-flight tile words →
|
|
1978
1941
|
`LoticsLocale.fileUpload`. **No empty state:** with zero files it is a bare bar, so pair it
|
|
1979
1942
|
with a `FileDropzone` for the well a records screen opens on. Use `FileGrid`/`FileRows` bare
|
|
@@ -2002,8 +1965,8 @@ component rather than showing it at zero.
|
|
|
2002
1965
|
- **`file_thumbnail`** — **`diff`** puts a `DiffKind` corner mark on a tile (top-LEFT, the one
|
|
2003
1966
|
corner remove/selection/uploading do not claim) and fades a `removed` one. The mark rides a
|
|
2004
1967
|
white RING: a tile's ground is an arbitrary photo, and a pale tint over a pale scan is a
|
|
2005
|
-
smudge. A `removed` tile fades its BODY only — the mark stays at full strength
|
|
2006
|
-
|
|
1968
|
+
smudge. A `removed` tile fades its BODY only — the mark stays at full strength. Ignored at
|
|
1969
|
+
the ROW rung: a corner disc on a row-sized tile is the tile, and such a tile lives
|
|
2007
1970
|
inside a row that carries the change instead. A grid says MEMBERSHIP well (arriving, leaving) and PAIRING not at
|
|
2008
1971
|
all — nowhere on a 96px tile to name what a replacement supersedes — so a set where things
|
|
2009
1972
|
are being REPLACED wants `FileRow`. Also: `FileThumbnail` + `DisplayFile` + `THUMBNAIL_SIZE` /
|
|
@@ -2035,7 +1998,7 @@ component rather than showing it at zero.
|
|
|
2035
1998
|
+ a ⋯ Download/Open-external/Remove menu; composes `FileRow` + `ActionMenu` +
|
|
2036
1999
|
`FileGalleryModal`; localized via the `gallery` locale slice. **`meta` has NO default**: the
|
|
2037
2000
|
badge states the type as colour and as a label, and the filename ends in the extension, so a
|
|
2038
|
-
type caption
|
|
2001
|
+
type caption is a fourth statement of one fact costing a line of row height. Pass a second
|
|
2039
2002
|
line only when it carries something the NAME does not — what an invoice covers, who uploaded
|
|
2040
2003
|
it, when it landed.
|
|
2041
2004
|
- **`file_badge`** — `FileBadge`: the two-tone file-type mark (PDF / XLSX / DOCX / video /
|
|
@@ -2065,8 +2028,7 @@ component rather than showing it at zero.
|
|
|
2065
2028
|
absolutely inside a positioning wrapper the component owns) because a `<video>` is a
|
|
2066
2029
|
replaced element: once metadata loads it reports the source's intrinsic size, and any
|
|
2067
2030
|
ancestor sized by its content grows to match — with RN-web's `flexShrink: 0` default,
|
|
2068
|
-
nothing pushes back
|
|
2069
|
-
the moment it loaded. **Do not add your own fullscreen/expand control**: the native
|
|
2031
|
+
nothing pushes back. **Do not add your own fullscreen/expand control**: the native
|
|
2070
2032
|
transport already carries fullscreen, picture-in-picture and download, and a second
|
|
2071
2033
|
affordance beside them is both redundant and a claim about where it leads. A source it
|
|
2072
2034
|
cannot load is surfaced, never silent — a media element that fails paints an EMPTY BOX and
|
|
@@ -2165,10 +2127,9 @@ component rather than showing it at zero.
|
|
|
2165
2127
|
- **`confidence`** — `Confidence` + `ConfidenceLevel` + `levelFromScore`: confidence IS a
|
|
2166
2128
|
callout — the meter + level phrase head the block, the REQUIRED `children` carry the basis
|
|
2167
2129
|
(what was checked, what failed); the box stays white with a hairline border at every
|
|
2168
|
-
level — the meter is the only colored element. There is NO standalone meter — a level without
|
|
2169
|
-
be rendered;
|
|
2170
|
-
|
|
2171
|
-
high/med/low; localized via the provider.
|
|
2130
|
+
level — the meter is the only colored element. There is NO standalone meter — a level without
|
|
2131
|
+
its reasons cannot be rendered; rank / badges / severity carry the standing instead. Also:
|
|
2132
|
+
calibrated high/med/low; localized via the provider.
|
|
2172
2133
|
- **`diff_value`** — **`DiffValue`, THE atom of a review surface**: a value that changed,
|
|
2173
2134
|
rendering `before` struck above/before `after`. Owns no container, no context, no decision —
|
|
2174
2135
|
so it drops into a table cell, a `DetailRow`, a total, a chip or a sentence and inherits that
|
|
@@ -2176,16 +2137,15 @@ component rather than showing it at zero.
|
|
|
2176
2137
|
a chip for a link being repointed — and a node `before` is struck by a DRAWN rule rather than
|
|
2177
2138
|
by text decoration, because `line-through` on a `Text` does not cross a `View` child (the old
|
|
2178
2139
|
value would otherwise render at full strength beside its replacement with nothing saying it is
|
|
2179
|
-
the one going away
|
|
2180
|
-
genuinely is not text: an avatar carries identity a string cannot
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
the column's own size, not a miniature badge. Worked in `dev/pages/ai.tsx` § every shape.
|
|
2140
|
+
the one going away). Reach for a node when the value
|
|
2141
|
+
genuinely is not text: an avatar carries identity a string cannot. A node belongs where it
|
|
2142
|
+
fits at FULL size — a
|
|
2143
|
+
form row, a card. Shrinking a composite into a table cell takes its own type off the scale,
|
|
2144
|
+
so a file in a CELL is a filename at the column's own size, not a miniature badge. Worked in
|
|
2145
|
+
`dev/pages/ai.tsx` § every shape.
|
|
2186
2146
|
**`before === after` collapses to ONE plain value** —
|
|
2187
2147
|
agreement is not a change, and a document confirming what the record already says is the
|
|
2188
|
-
GOOD case
|
|
2148
|
+
GOOD case. **`delta`** (a host-formatted string, the
|
|
2189
2149
|
same contract as `Finding.delta`) prints HOW FAR it moved, because
|
|
2190
2150
|
`1.600.000 → 1.481.481` otherwise makes the reader subtract to find out whether a
|
|
2191
2151
|
correction is trivial or alarming. `layout` stacked (a column of figures) | inline (prose, dense
|
|
@@ -2193,9 +2153,9 @@ component rather than showing it at zero.
|
|
|
2193
2153
|
for an addition, `after` for a removal. BOTH values stay on screen: the old one is the only
|
|
2194
2154
|
evidence the reader has that the correction is the right SIZE.
|
|
2195
2155
|
**`placeholder` is YOUR string — translate it.** The component renders no word of its own
|
|
2196
|
-
(its only fallbacks are `—` and empty), so nothing here reaches the locale packs.
|
|
2197
|
-
|
|
2198
|
-
|
|
2156
|
+
(its only fallbacks are `—` and empty), so nothing here reaches the locale packs.
|
|
2157
|
+
`DiffMark` beside it DOES ship localized words, but the two are different sentences even when
|
|
2158
|
+
English collides them — the mark says what happened to
|
|
2199
2159
|
the ROW ("Removed" / "Xóa"), the placeholder says what is in the FIELD now, which reads "đã
|
|
2200
2160
|
xóa" or "bỏ trống", never the bare verb. The same prop also carries "Pick a candidate below"
|
|
2201
2161
|
and "Not on the document". Pull it from your own catalog.
|
|
@@ -2204,9 +2164,8 @@ component rather than showing it at zero.
|
|
|
2204
2164
|
accessible word (never colour alone — "added" vs "removed" by hue is invisible to a reader who
|
|
2205
2165
|
cannot separate them, and that is the distinction a review turns on). **ONE treatment
|
|
2206
2166
|
everywhere: a filled 22px DISC**, 14px glyph, 700 ink on a 100 ground. A form marked with bare
|
|
2207
|
-
glyphs beside a table marked with filled shapes reads as two systems saying the same thing
|
|
2208
|
-
|
|
2209
|
-
principled the reason. The disc also gives the glyph its own ground, which is what lets one
|
|
2167
|
+
glyphs beside a table marked with filled shapes reads as two systems saying the same thing.
|
|
2168
|
+
The disc also gives the glyph its own ground, which is what lets one
|
|
2210
2169
|
mark sit on a white row, a tinted cell, or a photograph. **`unchanged` renders as an empty
|
|
2211
2170
|
disc-sized HOLE** — silence is the honest rendering of "nothing happened", it keeps the
|
|
2212
2171
|
column's width so nothing shifts left, and it announces nothing (eleven "Unchanged"es is the
|
|
@@ -2217,8 +2176,7 @@ component rather than showing it at zero.
|
|
|
2217
2176
|
an `Inline*` editor draws (40px band, 8px horizontal padding inside a 1px transparent border),
|
|
2218
2177
|
minus the editor. A record's value column mixes editors with things that are not editors, and
|
|
2219
2178
|
anything rendered raw beside them starts at the cell edge, 20px tall, at the top — so the
|
|
2220
|
-
column quietly acquires TWO left edges and two baselines
|
|
2221
|
-
down to meet a control) agrees with one kind of row and not the other. `InlineStatic` is the
|
|
2179
|
+
column quietly acquires TWO left edges and two baselines. `InlineStatic` is the
|
|
2222
2180
|
STRING case and composes it; reach for the slot when the value is a node, which a review
|
|
2223
2181
|
surface always is. An ANNOTATED row needs the grid even with no editor in sight, because the
|
|
2224
2182
|
annotation stack is inset to a control's words — data_entry.md §The editability affordance
|