@lotics/ui 11.8.10 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +13 -0
- package/docs/catalog.md +37 -19
- package/docs/composition.md +60 -3
- package/docs/data_entry.md +26 -7
- package/docs/templates.md +148 -50
- package/examples/tpl_documents.tsx +29 -18
- package/examples/tpl_item_list.tsx +27 -8
- package/examples/tpl_record.tsx +2214 -555
- package/package.json +1 -1
- package/src/back_button.tsx +26 -33
- package/src/checklist.tsx +30 -15
- package/src/comments_thread.tsx +3 -62
- package/src/detail_row.tsx +86 -40
- package/src/inline_edit.tsx +5 -0
- package/src/inline_select.tsx +5 -5
- package/src/radio_picker.tsx +11 -2
- package/src/section_heading.tsx +3 -3
- package/src/section_stack.tsx +6 -4
- package/src/text_input_field.tsx +6 -0
- package/src/timeline.tsx +9 -9
package/AGENTS.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @lotics/ui — the UI reference index
|
|
2
2
|
|
|
3
|
+
> **Upgrading to v12 from 11.x.** Two exports were removed: **`CommentComposer`** (compose
|
|
4
|
+
> comments with THE kit `Composer` — attach via `actionsButton`/`files`; `CommentList` is
|
|
5
|
+
> unchanged) and **`DetailRow.info`** (field annotations are explicit — use the new
|
|
6
|
+
> **`DetailRow.description`** under the value and **`DetailRow.error`**, the `FormField`
|
|
7
|
+
> vocabulary; heading/card `info` ⓘ props are untouched). Visual behavior changes:
|
|
8
|
+
> `SubsectionStack` is SPACE-ONLY (32px beat, no hairlines — titles carry the grouping),
|
|
9
|
+
> `Section` heading gap 12→16, `DetailTable` row gap 6→8, `DetailRow` rows top-align with
|
|
10
|
+
> label/control/trailing centered on the first control line, `Timeline` grew to 32px
|
|
11
|
+
> discs / 16px icons / 40px minimum rows, `TextInputField` paints a WHITE surface on any
|
|
12
|
+
> background (it was transparent — identical on the white canvas, visible on tinted
|
|
13
|
+
> panels), and the inline editors' resting chip gained 6px vertical padding (identical for
|
|
14
|
+
> one-line values; multi-line resting content now breathes).
|
|
15
|
+
|
|
3
16
|
> **Upgrading to v11 from ≤10.x — the type scale was re-slotted.** The display sizes gained the
|
|
4
17
|
> missing `##` step: `xl` is now 22/24 (section title), `xxl` is 28/32 (page/record title — the
|
|
5
18
|
> OLD `xl` look), and the new `xxxl` is 32/48 (hero numbers — the OLD `xxl` look). Migrate an
|
package/docs/catalog.md
CHANGED
|
@@ -170,8 +170,8 @@ gap-spaced region + `SectionHeading`/`SectionHeadingTitle`/`SectionHeadingMeta`;
|
|
|
170
170
|
component — children are the body), `Subsection` (+ `SubsectionHeading`/
|
|
171
171
|
`SubsectionHeadingTitle` — the named group INSIDE a section, `###` lg-semibold title),
|
|
172
172
|
`SectionStack` (the flat page's content column — owns the fixed 56px beat + hairline between
|
|
173
|
-
top-level blocks), `SubsectionStack` (
|
|
174
|
-
|
|
173
|
+
top-level blocks), `SubsectionStack` (a section's `Subsection` groups — fixed 32px beat,
|
|
174
|
+
SPACE-ONLY: titles carry the grouping, hairlines stay at the section level), `SectionCard`, `PageHeader` /
|
|
175
175
|
`PageContent`, `Stack`, `Spacer`, `Divider`, `Accordion`, `Tabs`, `SegmentedControl`,
|
|
176
176
|
`Stepper`, `DangerZone` (the destructive section — delete/archive — set apart at the bottom
|
|
177
177
|
of a record/settings surface).
|
|
@@ -396,8 +396,9 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
396
396
|
`arrow-left` `MenuButton` reads as another nav ITEM, not an exit). `onPress` +
|
|
397
397
|
`accessibilityLabel` (defaults to `label`, else the locale's `nav.back` — "Back" /
|
|
398
398
|
"Quay lại"). Bare, it's the chevron-left `IconButton` (lg, secondary) heading a
|
|
399
|
-
screen/panel; pass **`label`** to name where back GOES ("Danh sách")
|
|
400
|
-
|
|
399
|
+
screen/panel; pass **`label`** to name where back GOES ("Danh sách") — the SAME
|
|
400
|
+
circular glyph with the destination beside it, ONE pressable over disc + words —
|
|
401
|
+
never an icon button beside unclickable prose.
|
|
401
402
|
- **`link`** — `Link`: the EXTERNAL hyperlink — fixed underline+blue + `role="link"`;
|
|
402
403
|
`onPress` only (the consumer wires the opener).
|
|
403
404
|
- **`text_link`** — `TextLink`: underlined text that's optionally an `onPress` action or an
|
|
@@ -489,7 +490,9 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
489
490
|
### Form & text inputs
|
|
490
491
|
|
|
491
492
|
- **`text_input_field`** — `TextInputField`: the standard text input (multiline grows via
|
|
492
|
-
the auto-grow engine; `shortcut` renders a `ShortcutBadge`).
|
|
493
|
+
the auto-grow engine; `shortcut` renders a `ShortcutBadge`). Its surface is WHITE on any
|
|
494
|
+
background — on a tinted panel the input still reads as an open well, never blending into
|
|
495
|
+
the tint; override via `style` only when a surface genuinely wants otherwise.
|
|
493
496
|
- **`number_input`** — `NumberInput`: numeric input; `format` for currency/units.
|
|
494
497
|
- **`search_input`** — `SearchInput`: the search box for toolbars/filters.
|
|
495
498
|
- **`form_field`** — `FormField` + `useFormField`: label / description / error / `optional`
|
|
@@ -567,10 +570,15 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
567
570
|
label+value row for drawer/peek detail; in FORM mode (`labelWidth` set) the value column
|
|
568
571
|
FILLS the row so a stack of inline editors all span the same width + none jumps wider on
|
|
569
572
|
edit; optional `trailing` slot renders a right-side action/badge after the value (units
|
|
570
|
-
belong IN the value via `InlineNumberInput format`)
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
573
|
+
belong IN the value via `InlineNumberInput format`). The FIELD-ANNOTATION vocabulary (same
|
|
574
|
+
names + meanings as `FormField`), always EXPLICIT — a row never hides guidance behind an ⓘ:
|
|
575
|
+
**`description`** = persistent guidance, under the VALUE (stacked mode mirrors the form
|
|
576
|
+
order label · description · control); **`error`** = field-level failure under the value,
|
|
577
|
+
danger + alert semantics (the `Inline*` editors already render their own transient save
|
|
578
|
+
errors — don't wire both); invalid/cross-field STATE = a co-located `Callout`; **`flat`**
|
|
579
|
+
on a flat-text value row (`InlineStatic`, plain `Text`) tucks the annotation up by the
|
|
580
|
+
control band's slack so its gap matches a chip row's.
|
|
581
|
+
`DetailTable`: the compound parent of
|
|
574
582
|
a row STACK — `labelWidth` (default 130) / `trailingWidth` / `minHeight` (default 40, the
|
|
575
583
|
inline-control grid) declared ONCE + the 6px row gap; with `trailingWidth` every row
|
|
576
584
|
reserves the trailing column so value cells share one width and trailing items align at
|
|
@@ -645,17 +653,23 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
645
653
|
filled check when done, distinct from the square checkbox; the task/to-do/checklist
|
|
646
654
|
toggle.
|
|
647
655
|
- **`checklist`** — `Checklist` + `ChecklistRow` — the record-scoped checklist COMPOUND: it
|
|
648
|
-
owns GEOMETRY only (row minHeight 32, gap 12,
|
|
649
|
-
assignee cells column-align) while content
|
|
650
|
-
`CheckCircle` (omit onChange = read-only ring
|
|
651
|
-
|
|
656
|
+
owns GEOMETRY only (row minHeight 32, gap 12, control/title alignment, the `meta`/
|
|
657
|
+
`expansion` indent, ONE `trailingWidth` so assignee cells column-align) while content
|
|
658
|
+
stays composed — `control` takes the `CheckCircle` (omit onChange = read-only ring; a
|
|
659
|
+
PICKER list puts a `CheckboxInput` here and sets `controlWidth={24}` so the indent
|
|
660
|
+
tracks), children the struck transparent `InlineTextInput` or plain `Text`, `trailing` an
|
|
661
|
+
`InlineMemberSelect`, `menu` the row's ⋯ options
|
|
652
662
|
(`{items: ActionMenuItem[], accessibilityLabel}`) — Delete lives BEHIND the menu,
|
|
653
663
|
danger-styled and last, never a bare ✕ (omit on read-only rows). NARROW surfaces (a
|
|
654
664
|
drawer/peek checklist) put the editors on the `meta` line instead of `trailing` — the
|
|
655
|
-
second line indents
|
|
656
|
-
`trailing`; never both.
|
|
657
|
-
|
|
658
|
-
|
|
665
|
+
second line indents to the title's text edge so the TITLE keeps the full width; wide
|
|
666
|
+
surfaces use `trailing`; never both. `expansion` is a BLOCK slot under the row on the
|
|
667
|
+
same indent — a transient inline fill editor, a drill-down — rendered only while open
|
|
668
|
+
(`tpl_record`'s Document set rows are the worked example). SUGGESTIONS are never rows:
|
|
669
|
+
offer the commons as `SuggestionChip`s under the list. Close the list with `CaptureRow`.
|
|
670
|
+
There is deliberately NO monolithic Task component — richer task-management rows compose
|
|
671
|
+
their own anatomy directly. Hand-rolling this checkbox-row anatomy in a template is how
|
|
672
|
+
alignment drifts — the compound exists so it can't.
|
|
659
673
|
- **`suggestion_chip`** — `SuggestionChip`: the dismissible SUGGESTION pill — an item the
|
|
660
674
|
record could have but doesn't yet (a common task, an expected line) as a `Chip` whose
|
|
661
675
|
press MATERIALIZES it (plus glyph + label, one tuned anatomy) and whose ✕ refuses it;
|
|
@@ -892,7 +906,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
892
906
|
expands for long text + attachments (`pills` + attachment slots, controlled or
|
|
893
907
|
uncontrolled `value`, `onSend`/`onStop`, `sendDisabled` override).
|
|
894
908
|
- **`agent_run`** — `AgentRun` + `AgentRunStep`/`AgentRunItem` + `resolveToolMeta`: the live
|
|
895
|
-
streaming work feed.
|
|
909
|
+
streaming work feed. AI work ONLY — deterministic work (template fill, an export) shows a
|
|
910
|
+
`loading` state on its trigger and delivers at once; dressing it as an agent run is theater.
|
|
896
911
|
- **`agent_progress`** — `AgentProgress`: its compact, floating, expandable form — a
|
|
897
912
|
composer's working state.
|
|
898
913
|
- **`confidence`** — `Confidence` + `ConfidenceLevel` + `levelFromScore`: calibrated
|
|
@@ -916,8 +931,11 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
916
931
|
- **`finding`** — `Finding` + `FindingComparison` + `FindingSeverity`/`FindingLabels`: one
|
|
917
932
|
ranked AI-check insight — severity word · title · detail · `Sources` · children slot; the
|
|
918
933
|
expected-vs-actual body with the emphasized delta; the `finding` locale slice.
|
|
919
|
-
- **`comments_thread`** — `CommentList` +
|
|
934
|
+
- **`comments_thread`** — `CommentList` + the
|
|
920
935
|
`ThreadComment`/`ThreadMember`/`ThreadFile` types: the record comments thread.
|
|
936
|
+
Composing is THE kit `Composer` (never a bespoke comment box): attach via its
|
|
937
|
+
`actionsButton`, staged files in its `files` slot; render attachments with
|
|
938
|
+
`renderFiles` (a pressable `FileGrid` → gallery preview — see tpl_record).
|
|
921
939
|
**Limitation:** labels are not provider-wired — pass `CommentListLabels`.
|
|
922
940
|
|
|
923
941
|
### Utility hooks & plumbing
|
package/docs/composition.md
CHANGED
|
@@ -63,8 +63,9 @@ restyle a heading level per-page.
|
|
|
63
63
|
`SubsectionHeading` › `SubsectionHeadingTitle` (`###` — lg semibold, with the same `info` ⓘ
|
|
64
64
|
affordance as `SectionHeadingTitle`; siblings — a `Badge`, a
|
|
65
65
|
`SectionHeadingMeta`, an action — ride the heading row's right edge). Sibling subsections stack
|
|
66
|
-
in a **`SubsectionStack`** (
|
|
67
|
-
|
|
66
|
+
in a **`SubsectionStack`** (a fixed 32px beat, SPACE-ONLY — subsection titles carry the
|
|
67
|
+
grouping; hairlines belong to the SECTION level, one rule per altitude; no margins, no
|
|
68
|
+
hand-rolled dividers); a headingless `Subsection` is the
|
|
68
69
|
section's lead group. Group leads INSIDE a subsection's rows are `md` medium at most. Do NOT
|
|
69
70
|
hand-roll `Text weight="semibold"` group leads inside a section, and never promote a subsection
|
|
70
71
|
to its own section-level heading just to separate it.
|
|
@@ -377,6 +378,58 @@ All view controls are 40px tall (`CONTROL_HEIGHT`), `sm` labels, in ONE wrapping
|
|
|
377
378
|
`danger` (and its quieter `danger-secondary`) marks destructive — that's the whole axis. No
|
|
378
379
|
"success"/green button. Decision UIs put positive/negative color on the STATUS (dot) and verdict
|
|
379
380
|
(colored `Text`), not the buttons.
|
|
381
|
+
- **Create buttons carry NO `+` icon** — "Add fee", not "+ Add fee". Section-heading creates are
|
|
382
|
+
`secondary` (one primary per scope, reserved for the scope's main verb); a create promotes to
|
|
383
|
+
`primary` only where creating IS the surface's job — the register page's New, an empty state's
|
|
384
|
+
action. (The icon-only `+` `IconButton` — a composer's attach — is a different thing and keeps
|
|
385
|
+
its glyph.)
|
|
386
|
+
|
|
387
|
+
## The action-gating law — disabled + the reason as a co-located Callout
|
|
388
|
+
|
|
389
|
+
A not-ready CTA is **DISABLED**, and the reason is a **persistent co-located `Callout` at the
|
|
390
|
+
gate's SCOPE, once** — never prose beside the button, never revealed only on press:
|
|
391
|
+
|
|
392
|
+
- A **local inconsistency** (a charged line without a payment method) → a callout in ITS group,
|
|
393
|
+
beside its own disabled CTA.
|
|
394
|
+
- A **broken record-level premise** (no customer attached, invalid tax ID) → ONE callout at the
|
|
395
|
+
section top — not repeated beside every affected button.
|
|
396
|
+
- **Self-evident empties stay silent**: an empty band's disabled Issue, a composer's Send on
|
|
397
|
+
empty input, a dialog's confirm until its one required adjacent field is filled — nothing to
|
|
398
|
+
explain, so nothing is said.
|
|
399
|
+
- An **expected not-yet** (a stage that simply hasn't arrived) is not a problem — it gets no
|
|
400
|
+
callout; if that leaves a disabled button undiscoverable, remove the gate rather than narrate
|
|
401
|
+
it (a filled surface whose CTA stays silently dead is a trap).
|
|
402
|
+
- A **DEGRADED-but-valid action is not a gate.** When the action can still produce a legitimate
|
|
403
|
+
outcome (generating forms whose optional fields print BLANK and get filled by hand), the CTA
|
|
404
|
+
stays **ENABLED**: the co-located callout states the CONSEQUENCE (not a block), and the press
|
|
405
|
+
CONFIRMS via `Alert` — name what's missing, offer "Cancel" (back to the inline fixes) and the
|
|
406
|
+
proceed verb. Reserve DISABLING for actions that cannot produce their outcome at all.
|
|
407
|
+
|
|
408
|
+
## Field annotations & linked records
|
|
409
|
+
|
|
410
|
+
- **The field-annotation vocabulary** (same names + meanings on `FormField` and `DetailRow`,
|
|
411
|
+
always EXPLICIT — never a hidden ⓘ on rows): **`description`** = persistent guidance (under
|
|
412
|
+
the VALUE on a horizontal row — the label column has no room for prose; stacked mode wears
|
|
413
|
+
the form order label · description · control); **`error`** = field-level failure under the
|
|
414
|
+
value, danger + alert semantics (the `Inline*` editors already render their own transient
|
|
415
|
+
save errors); cross-field / record-level STATE = a co-located `Callout`. Rows share ONE
|
|
416
|
+
alignment law: the row top-aligns and label · control · trailing each center within the
|
|
417
|
+
first control line — a tall value block never drags the label. A FLAT value row
|
|
418
|
+
(`InlineStatic`, plain `Text`) sets **`DetailRow flat`**: the annotation tucks up by the
|
|
419
|
+
control band's slack so the perceived gap under the text equals the gap under a chip —
|
|
420
|
+
the band's invisible bottom half must never read as a hole above the description.
|
|
421
|
+
- **A LINKED record never renders as field rows** — another record's data must not read as
|
|
422
|
+
fields on THIS one. The reference shape (`tpl_record`'s Customer/Handoff, inline — no
|
|
423
|
+
wrapper component): a BORDERED, fully pressable box (glyph anchor · identity · facts
|
|
424
|
+
stacked vertically) whose press opens the record's detail drawer (read-only rows + the
|
|
425
|
+
navigate verb); the action row sits INSIDE the box — destructive LEFT in danger, go-to
|
|
426
|
+
RIGHT. Editing the linked record happens on ITS page, never here. The press ANATOMY:
|
|
427
|
+
the box is a `PressableRow` (role-less, non-focusable surface — hover wash spans the
|
|
428
|
+
nested verbs) with the keyboard DOOR — an EMPTY absolutely-positioned `role="button"`
|
|
429
|
+
sibling carrying the tab stop, the accessible name and the focus ring — beneath the
|
|
430
|
+
content, and the interior verbs lifted above it via `zIndex: 1`. NEVER `role="button"`
|
|
431
|
+
on a container with interactive descendants: a button must not contain a button
|
|
432
|
+
(invalid HTML, a hydration error, and AT double-announcement).
|
|
380
433
|
|
|
381
434
|
## Color discipline — solid / tint / ramp + ONE accent
|
|
382
435
|
|
|
@@ -454,11 +507,15 @@ that praises it is marketing.
|
|
|
454
507
|
- **Instructions live where action is needed** — empty states, callouts, form-field
|
|
455
508
|
descriptions: imperative, one concrete step ("Drop the CI + PL here."). A heading never
|
|
456
509
|
instructs.
|
|
510
|
+
- **NEVER prose beside a CTA.** No consequence narration next to a button ("Handing off moves
|
|
511
|
+
the record to Operations — fields stay editable after") and no label chips beside headings
|
|
512
|
+
("Collected separately") — the CTA's label names where it goes, and STATE speaks through
|
|
513
|
+
the gating law below, not through standing text.
|
|
457
514
|
- **Restatement is the second noise mode.** A description that repeats what the screen already
|
|
458
515
|
shows — the title, the subsection names below it, the table's columns — carries zero
|
|
459
516
|
information and fails even though it "states content". Useful copy states an **invisible
|
|
460
517
|
truth**: an invariant of the data, a boundary ("P&L lives in the accounting app, not here"),
|
|
461
|
-
a linkage ("the
|
|
518
|
+
a linkage ("the commodity code here feeds the customs declaration"), or a consequence ("past the
|
|
462
519
|
cut-off, the box misses the sailing").
|
|
463
520
|
- **The deletion test**: if removing the line changes nothing for the reader, remove the line —
|
|
464
521
|
`description` is optional for a reason; when no invisible truth is worth stating, omit it.
|
package/docs/data_entry.md
CHANGED
|
@@ -19,11 +19,27 @@ in [the templates](./templates.md) (`examples/tpl_*.tsx`).
|
|
|
19
19
|
| CHARGES that bill onto documents | [**billing**](#billing--the-invoice-document-is-the-unit) (`tpl_record` Billing section) | the invoice document is the unit |
|
|
20
20
|
| a record's FEE/charge SUMMARY | [**`Ledger`**](#fee-summary--ledger) (worked example: `tpl_item_list` drawer) | grouped money lines, one emphasized total — no bars/charts |
|
|
21
21
|
| a multi-value TAG field | [**`Select multi`**](#tag--multi-value-field--select-multi) (`renderSelected` → `Chip`) | chips composed, not a separate control |
|
|
22
|
-
| ONE choice from a small visible set | **`ChipGroup` pills** (or `RadioPicker`) | required single-select, one tap, every option visible |
|
|
22
|
+
| ONE choice from a small visible set | **`ChipGroup` pills** (or `RadioPicker` — column when options carry descriptions, row when short) | required single-select, one tap, every option visible |
|
|
23
|
+
| ONE pick from a BIG registry (ports, projects…) | on a RECORD surface: **`InlineSelect searchable`** (search lives in the popover); on a form/attach surface: **`Combobox`** with `value` | an inline row stays an inline editor — a persistent search input breaks the surface's grammar |
|
|
24
|
+
| a YES/NO on a record | **`CheckboxInput`** as the `DetailRow` value | a boolean needs no editor mode |
|
|
23
25
|
| a STATUS with terminal outcomes | [**disposition**](#disposition--lifecycle-status-is-asymmetric-by-phase) (open → resolve → revise) | guides the decision |
|
|
24
26
|
| FILES | [**attachment field**](#attachments--a-full-add--preview--delete-field) (dropzone + grid + gallery) | add / preview / delete |
|
|
25
27
|
| a state TRANSITION mid-flow | [**stage gate**](#stage-gates--tiered-by-weight) (popover / dialog by weight) | right-sized friction |
|
|
26
28
|
|
|
29
|
+
**The right-input-per-field law.** A field gets the control its SHAPE wants, never a default
|
|
30
|
+
text box — and on a record surface radio/checkbox render as PERSISTENT controls (the control
|
|
31
|
+
is the best display of the value; prose-shaped values and registry picks stay inline
|
|
32
|
+
editors). Rich `InlineSelect` options carry a description line (`renderOptionContent` — it
|
|
33
|
+
shows in the resting row too; the `data` generic types option payloads). A DEPENDENT field
|
|
34
|
+
renders only while its parent value makes it real — never a disabled ghost row. Worked rows:
|
|
35
|
+
`tpl_record`'s Classification group.
|
|
36
|
+
|
|
37
|
+
**The form-action alignment law.** On an open page, a label-left form's action row rides the
|
|
38
|
+
form's OWN GRID — an empty-label `DetailRow` puts the CTA (and what it produces) exactly on
|
|
39
|
+
the CONTROL COLUMN, the same left edge the user just filled, and inherits stacked mode on
|
|
40
|
+
narrow containers. A right-floated button aligns to nothing; only OVERLAY footers
|
|
41
|
+
(`DialogFooter`/`DrawerFooter`) right-align. Worked: `tpl_record`'s Delivery receipt.
|
|
42
|
+
|
|
27
43
|
## Inline edit — the preferred way to edit an existing record
|
|
28
44
|
|
|
29
45
|
When the whole record is editable (a detail/record screen, dense settings), don't wrap it in a
|
|
@@ -107,10 +123,14 @@ null). `InlineTagSelect` (multi) needs no `onClear` — an empty set is already
|
|
|
107
123
|
### The row stack — `DetailTable` + `DetailRow`
|
|
108
124
|
|
|
109
125
|
A STACK of rows lives in a `DetailTable` (label · value · trailing laid out like a TABLE:
|
|
110
|
-
`labelWidth` / `trailingWidth` / `minHeight` set ONCE on the parent, plus the
|
|
126
|
+
`labelWidth` / `trailingWidth` / `minHeight` set ONCE on the parent, plus the 8px row gap the
|
|
111
127
|
zinc-50 chips need) holding `DetailRow`s — set `trailingWidth` when ANY row carries a trailing
|
|
112
128
|
action/badge, so EVERY row reserves the column and one row's `Copy` button never makes its editor
|
|
113
|
-
narrower than its neighbours'.
|
|
129
|
+
narrower than its neighbours'. Rows share ONE alignment law: the row top-aligns and label ·
|
|
130
|
+
control · trailing each center within the first control line — annotations (`description` under
|
|
131
|
+
the value, `error` in danger with alert semantics; both indented to the chip's 8px text inset)
|
|
132
|
+
grow the row downward without dragging the label. The full annotation vocabulary lives in
|
|
133
|
+
composition.md §Field annotations.
|
|
114
134
|
|
|
115
135
|
### The editability affordance
|
|
116
136
|
|
|
@@ -127,10 +147,9 @@ border) but stays FLAT and non-interactive, so editable (chip) vs read-only (fla
|
|
|
127
147
|
glance and the static value never reads as a disabled input.
|
|
128
148
|
|
|
129
149
|
To hang a right-side action/badge off a row, use `DetailRow`'s `trailing` slot (NOT for units —
|
|
130
|
-
"kg"/"$" belong IN the value via `InlineNumberInput format`) — see the "
|
|
131
|
-
`tpl_record`, which also reads top→bottom as a full record surface
|
|
132
|
-
|
|
133
|
-
grid edit in place too (below).
|
|
150
|
+
"kg"/"$" belong IN the value via `InlineNumberInput format`) — see the "General" section of
|
|
151
|
+
`tpl_record`, which also reads top→bottom as a full record surface. Not every field is a
|
|
152
|
+
same-height swap — a tag field, a status, or an attachment grid edit in place too (below).
|
|
134
153
|
|
|
135
154
|
## Fieldset form — fields lay out on a RESPONSIVE two-column grid
|
|
136
155
|
|
package/docs/templates.md
CHANGED
|
@@ -20,6 +20,11 @@ the package index is [../AGENTS.md](../AGENTS.md).
|
|
|
20
20
|
- **Pick by the job, not the domain.** Templates are scenario-flavoured (a pick run, cash
|
|
21
21
|
application, a delivery week) but generic-purpose — they teach the shape. Your orders /
|
|
22
22
|
items / customers screen starts from the template whose *job* matches, whatever the domain.
|
|
23
|
+
**Flavor lives in VALUES, never in STRUCTURE**: section identities, teaching comments, and
|
|
24
|
+
registry keys name the generic PATTERN; mock nouns stay at the common-denominator level any
|
|
25
|
+
goods/services business uses (a service level, a destination, a delivery receipt) — never
|
|
26
|
+
one vertical's jargon (an FCL, an EIR, an HS code). Adapt a template by swapping the
|
|
27
|
+
values; the patterns are what you keep.
|
|
23
28
|
- The templates encode the composition grammar (band order, toolbar law, color discipline,
|
|
24
29
|
status weight) as working code — when a template and your instinct disagree, the template
|
|
25
30
|
wins.
|
|
@@ -60,12 +65,16 @@ Laws for any screen where a record's data is edited and a primary action produce
|
|
|
60
65
|
2. **Static shape.** The page never changes shape from output-selection state. Conditional
|
|
61
66
|
rendering is DATA-driven only (a control appears because two stored values differ), never
|
|
62
67
|
selection-driven. Relevance is an advisory `Callout` with a jump-to-section action — warn,
|
|
63
|
-
never hide, never hard-block.
|
|
68
|
+
never hide, never hard-block. One sanctioned disclosure: a TRANSIENT fill-what's-missing
|
|
69
|
+
editor expanding under a just-checked item is progressive disclosure of the gate, not
|
|
70
|
+
shape change — it edits the record field in place and collapses once satisfied.
|
|
64
71
|
3. **Colocation & ownership.** Every output option (selections, print/export switches) lives in
|
|
65
72
|
the output section beside its action; a per-entity selection carries its owner's name in its
|
|
66
73
|
heading. Persisted record DATA never lives there — data belongs in data sections. Misfiled
|
|
67
74
|
data announces itself as callout special-cases; the exception disappearing is the sign the
|
|
68
|
-
model is right.
|
|
75
|
+
model is right. (HOMING is what's forbidden, not repair: the output section may host a
|
|
76
|
+
transient fill editor that saves onto the record field — same storage, the field's home row
|
|
77
|
+
shows the value — rendered only while the field blocks a checked output.)
|
|
69
78
|
4. **Resolved state.** Controls show the EFFECTIVE state, never raw storage: a derived/suggested
|
|
70
79
|
selection renders checked; the first manual change persists the full explicit set; a
|
|
71
80
|
reset-to-auto affordance appears only in explicit mode. What's shown = what happens.
|
|
@@ -207,43 +216,133 @@ Oldest-first auto-allocates; each `AllocationRow` can be filled or typed.
|
|
|
207
216
|
|
|
208
217
|
### `tpl_record` — THE record surface
|
|
209
218
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
the
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
a
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
219
|
+
The record page — **the surface IS the editor**; every field refines in place. No breadcrumb
|
|
220
|
+
and no create CTA (creation belongs to the REGISTER — the list owns "new"; back lives in the
|
|
221
|
+
left panel as the bare circular `BackButton`). First paint is a `Skeleton` MIRRORING the
|
|
222
|
+
layout, never a spinner. It absorbed the old order-form, inline-record, intake, settings,
|
|
223
|
+
billing, and quick-capture templates. Top → bottom:
|
|
224
|
+
|
|
225
|
+
- A MINIMAL header: `RecordSummary` (title · subtitle ONLY — no stage chip, no metric; Tasks
|
|
226
|
+
carries progress, Handoff carries the desk, money lives in Billing), then the record's
|
|
227
|
+
ATTENTION state as a co-located `Callout` (e.g. overdue — record-scoped state reads here;
|
|
228
|
+
field-scoped state stays on its field), then a quiet `SummaryLine` (tasks done · documents
|
|
229
|
+
· to collect).
|
|
230
|
+
- **GENERAL — the MAIN details, FIRST in the rail**; every later section is supplementary.
|
|
231
|
+
A `SubsectionStack` (space-only beat) of a headingless LEAD group (the key facts) plus
|
|
232
|
+
named groups (e.g. Order / Fulfilment / System — system identifiers get a HOME, not a
|
|
233
|
+
per-row badge). Every group's `DetailTable` repeats the same `labelWidth`/`trailingWidth`
|
|
234
|
+
so the grid aligns through the groups. The FIELD-ANNOTATION vocabulary is the worked
|
|
235
|
+
example here: `description` (persistent, under the value, incl. a deliberately LONG one)
|
|
236
|
+
and `error` (derived, e.g. overdue Due) — always explicit, never an ⓘ; the inline editors
|
|
237
|
+
demo their own transient save error (a throwing `onSave`). The Classification group works
|
|
238
|
+
**the right-input-per-field law**: a field gets the control its SHAPE wants, never a
|
|
239
|
+
default text box — ≤5 exclusive options the user should see → `RadioPicker` (column when
|
|
240
|
+
options carry descriptions, row when short), a longer closed list → `InlineSelect` (rich
|
|
241
|
+
options carry a description line, shown in the resting row too), a pick from a big
|
|
242
|
+
REGISTRY → `Combobox` (search is the input), a boolean → `CheckboxInput`; a DEPENDENT
|
|
243
|
+
field renders only while its parent value makes it real (no disabled ghost rows).
|
|
244
|
+
Radio/checkbox/search are PERSISTENT controls — the control is the best display;
|
|
245
|
+
prose-shaped values stay inline editors.
|
|
246
|
+
- **Comments** — a coworker's note is the first thing read on a shared record: `CommentList`
|
|
247
|
+
(author-only edit/delete; attachments as a pressable `FileGrid` → gallery preview; a
|
|
248
|
+
file-capable edit form injected via `renderEditForm`) + THE kit `Composer` (attach via
|
|
249
|
+
`actionsButton`, staged files in its `files` slot) — never a bespoke comment box.
|
|
250
|
+
- **Tasks** — the full task-list grammar: clearable Group-by `FilterChip` (+ assignee/status
|
|
251
|
+
chips), the `CaptureRow` on top, `Checklist`/`ChecklistRow` rows with `InlineMemberSelect`
|
|
252
|
+
assignees and per-row menus, suggestions as `SuggestionChip`s; groups divide via the
|
|
253
|
+
`SubsectionStack` beat with plain text heads (no dot badges).
|
|
254
|
+
- **Documents** — the Agents "Document desk" pattern (shared with `tpl_documents` — change
|
|
255
|
+
it in BOTH): the register `Table` (search · Add files) whose selection feeds the
|
|
256
|
+
`FloatingActionBar` → ONE "Use AI" fork (extract / cross-check / edit-with-AI). Every
|
|
257
|
+
row's leading visual is a `FileThumbnail` in ONE square 32px slot — an image file fills
|
|
258
|
+
it as a real thumbnail, a document centers its badge in it — never a bare `FileBadge`
|
|
259
|
+
(mixed footprints misalign the identity column). ONE
|
|
260
|
+
sanctioned divergence: GENERATION — on the record page this desk holds what ARRIVES and
|
|
261
|
+
Create moves to the Document set OUTPUT section below; `tpl_documents` (no output
|
|
262
|
+
pipeline on its page) keeps the toolbar "Create documents" dialog.
|
|
263
|
+
- **Customer — the LINKED-RECORD reference implementation** (inline, no wrapper component):
|
|
264
|
+
another record's data NEVER renders as field rows on this one — a BORDERED, fully
|
|
265
|
+
pressable box (glyph anchor · identity · facts stacked VERTICALLY, legible at any width)
|
|
266
|
+
whose press opens the detail drawer (read-only rows + the navigate verb); the action row
|
|
267
|
+
sits INSIDE the box — destructive (Remove, `danger`) LEFT, go-to (Open record) RIGHT.
|
|
268
|
+
Built on `PressableRow` + the keyboard door, never `role="button"` on the box — the
|
|
269
|
+
press anatomy is [composition.md's linked-record law](./composition.md).
|
|
270
|
+
Empty state = the find-or-create search, custom row → the create `Dialog` (tax ID + Fetch;
|
|
271
|
+
its field errors ride `DetailRow error`, not a loose line).
|
|
272
|
+
- **Fees — the DETAILED money ledger** (both directions), distinct from Billing's invoice
|
|
273
|
+
documents: a `SummaryLine` (collected · to collect · to pay, warning-toned when overdue) →
|
|
274
|
+
a `priority`-annotated register `Table` (Fee · Type · Party · Amount · Status — status in
|
|
275
|
+
plain ink, danger only when overdue) → EVERY row opens a right-docked entity `Drawer`
|
|
276
|
+
(◀ ▶ + position stepping) with all fields inline-editable and a confirmed Remove in the
|
|
277
|
+
`DrawerFooter`. "Add fee" is create-then-refine: a blank fee opens straight in the drawer.
|
|
278
|
+
- **Billing** — every group a `Subsection` on ONE beat: per-invoice bands (charge chips with
|
|
279
|
+
ghost list prices + one-tap "Standard …" fill, "How paid…" selects), the collect band
|
|
280
|
+
(owns the total + the receipt), the deposit. THE ACTION-GATING LAW is the worked example:
|
|
281
|
+
a not-ready Issue is DISABLED and the reason is a co-located `Callout` at the gate's
|
|
282
|
+
scope, once — a band-local problem (charged line missing its method) in the band, a broken
|
|
283
|
+
record premise (no customer / invalid tax ID) at the section top; self-evident empties
|
|
284
|
+
stay silent, and NEVER prose beside a CTA. Issuing gates on record premises only — never
|
|
285
|
+
silently on stage.
|
|
286
|
+
- **Document set — the OUTPUT desk, the last WORK section** (the composition rules' output
|
|
287
|
+
law worked on the record surface: the top is intake, the bottom produces on demand).
|
|
288
|
+
Forms group PER PARTY on the `SubsectionStack` beat — each party a `SubsectionHeading` +
|
|
289
|
+
plain `CheckboxInput` rows, the common ones PRE-CHECKED, the long tail folded behind a
|
|
290
|
+
muted "Show all forms (N hidden)" toggle. Between the pickers and the CTA sit the OUTPUT
|
|
291
|
+
CONFIG fields (values printed on the forms — e.g. issuing office, prefill-signing-date
|
|
292
|
+
boolean), each with the input its shape wants. "Create document set" (primary — create IS
|
|
293
|
+
this scope's job; an empty pick self-evidently disables it, no callout) is DETERMINISTIC
|
|
294
|
+
template fill, NOT AI — the CTA carries a brief `loading` state and the whole set appears
|
|
295
|
+
at once (`AgentRun` is for AI work only; dressing deterministic work as an agent run is
|
|
296
|
+
theater). The produced files list under the action row as `FileRow`s (press → preview;
|
|
297
|
+
⋯ → Download / Remove) with "Download all" BELOW the files it acts on. Regenerating
|
|
298
|
+
REPLACES the set — derived paperwork, never intake; the desk register above keeps what
|
|
299
|
+
arrived. **READINESS**: a form declares the RECORD FIELDS it reads (`needs`); the fields'
|
|
300
|
+
HOME stays their DATA section (the colocation law) with descriptions naming their
|
|
301
|
+
consumers ("printed on the delivery note"). The picker rows ride `Checklist`/
|
|
302
|
+
`ChecklistRow` (`controlWidth={24}` for the `CheckboxInput`) — the compound owns the row
|
|
303
|
+
geometry and the `meta`/`expansion` indent; hand-rolling the anatomy is how alignment
|
|
304
|
+
drifts. Every not-ready row shows BOTH the mark and the explicit trigger — the mark says
|
|
305
|
+
WHAT (the `meta` line: 12px `circle-alert` + "Needs: …", muted ink until checked,
|
|
306
|
+
warning once it blocks), the trigger says HOW: a colored `TextLink` ("Add missing
|
|
307
|
+
fields", link-blue) BESIDE the label — an inline row action; a `Button` here outweighs
|
|
308
|
+
the row. It toggles the TRANSIENT fill editor in the row's `expansion` ("These values
|
|
309
|
+
save onto the order and unlock the form.") — `FormTextInput`s (label + the consumer
|
|
310
|
+
hint as `description`), Cancel / "Save fields" (secondary — the section keeps ONE
|
|
311
|
+
primary, disabled while a draft is empty). Checking NEVER expands anything by itself —
|
|
312
|
+
an expansion that only a side effect reveals is no affordance. Save writes the record
|
|
313
|
+
field (its home row shows the value) and the panel collapses; mark + trigger vanish =
|
|
314
|
+
ready, and a ready form stays silent. Drafts are keyed BY FIELD, so two forms needing
|
|
315
|
+
the same field share one draft and one Save resolves both. The trigger HIDES while its
|
|
316
|
+
panel is open — the panel's Cancel is the closer. Missing fields are the gating law's
|
|
317
|
+
DEGRADED-BUT-VALID case (those fields print blank, filled by hand later): Create stays
|
|
318
|
+
ENABLED, the co-located warning `Callout` states the consequence ("… print blank unless
|
|
319
|
+
filled above"), and the press CONFIRMS via `Alert` — per-form "form — fields" lines,
|
|
320
|
+
Cancel back to the inline fixes / "Generate anyway". Only an empty pick disables, and
|
|
321
|
+
silently. A form can also be DATA-conditional (the hazardous-goods note renders only
|
|
322
|
+
while the record's Hazardous goods boolean is set — record data may reshape the list,
|
|
323
|
+
output selection never does).
|
|
324
|
+
- **Delivery receipt — the QUICK-ISSUE form, its OWN section** (in the rail): one document
|
|
325
|
+
issued from a handful of facts at a known moment (a handover, an inspection, a visit) —
|
|
326
|
+
a label-left capture form (auto-stamped date/receipt-no, record-prefilled fields, the
|
|
327
|
+
right input each) whose CTA and produced `FileRow` ride an empty-label `DetailRow` — the
|
|
328
|
+
form-action alignment law (data_entry.md), worked.
|
|
329
|
+
- **Activity — the audit trail**: a `Timeline` (32px discs, 40px rows) of every change —
|
|
330
|
+
who and when — with DRILL-DOWN `details` (a field change carries its From → To diff, a
|
|
331
|
+
document entry a pressable `FileRow` → preview) and LIVE appends from the surface's real
|
|
332
|
+
actions (issue, fees, handoff).
|
|
333
|
+
- **Handoff — ONCE.** Handing off CREATES the next desk's record on ITS table and links it
|
|
334
|
+
here: the dialog collects the receiver (`MemberSelect`, confirm disabled until chosen) +
|
|
335
|
+
an optional note; confirm writes the trail mark and the SIBLING record, shown as the same
|
|
336
|
+
linked-record box (its drawer holds the reference info + Open record + **Recall**). After
|
|
337
|
+
the handoff there is NO further CTA. Recall is the undo: confirmed, it withdraws the
|
|
338
|
+
sibling and returns the desk — history is NEVER erased (the trail keeps both marks) and
|
|
339
|
+
the CTA returns, so redo is possible.
|
|
340
|
+
- **Danger zone** — with the full section anatomy (heading + description) like every other
|
|
341
|
+
section; the fenced red `DangerZone` card is the section's BODY.
|
|
342
|
+
- A LEFT OUTLINE RAIL (`MenuButton` + `useSectionNav`) — **the rail is the page's COMPLETE
|
|
343
|
+
map** (every section including Danger zone; subsections never join it) — headed by the
|
|
344
|
+
bare circular `BackButton`; it scroll-spies the active section, and on narrow containers
|
|
345
|
+
becomes the pinned current-section bar opening a section-picker `Modal`.
|
|
247
346
|
- **The page layout is the DOCS layout — the reading column is CENTRED IN THE VIEWPORT, and
|
|
248
347
|
the rail sits in its left gutter.** The `ScrollView` is FULL-WIDTH — **never cap it at
|
|
249
348
|
`CONTENT_MAX`, or the scroll container becomes the column and the gutters go wheel-dead**
|
|
@@ -253,17 +352,16 @@ Top → bottom:
|
|
|
253
352
|
three parts: a gutter RESERVED for the rail (`width: GUTTER` — the rail's own `RAIL_W`
|
|
254
353
|
PLUS `RAIL_GAP`, the docs' 3rem of breathing room, so the rail never butts against the
|
|
255
354
|
column), the reading column (`flex: 1, minWidth: 0`), and an EMPTY right gutter of the
|
|
256
|
-
same `GUTTER` that BALANCES the rail — without it the column sits half a rail right of
|
|
257
|
-
this balance for free: their right-hand TOC is the
|
|
258
|
-
TOC, so the gutter is simply reserved.) The rail is
|
|
259
|
-
border, not glued to the window edge — it FLOATS over
|
|
260
|
-
(`position: "absolute"`, `left` mirroring the row's centring maths),
|
|
261
|
-
the record scrolls **without** web-only `position: sticky` (which RN
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
second `maxWidth` + `alignSelf: "center"`.
|
|
355
|
+
same `GUTTER` that BALANCES the rail — without it the column sits half a rail right of
|
|
356
|
+
centre. (The public docs pages get this balance for free: their right-hand TOC is the
|
|
357
|
+
counterweight. A record surface has no TOC, so the gutter is simply reserved.) The rail is
|
|
358
|
+
NOT window chrome: no full-height border, not glued to the window edge — it FLOATS over
|
|
359
|
+
its reserved gutter (`position: "absolute"`, `left` mirroring the row's centring maths),
|
|
360
|
+
so it stays put while the record scrolls **without** web-only `position: sticky` (which RN
|
|
361
|
+
does not type) and without stealing the page's scrollable width. The rail earns its place
|
|
362
|
+
only while the centred column still has room beside it (`pageWidth >= GUTTER * 2 + 560`);
|
|
363
|
+
below that it collapses to the pinned bar. Sizing the column is the SHELL's job — inner
|
|
364
|
+
blocks are plain `width: "100%"`, never a second `maxWidth` + `alignSelf: "center"`.
|
|
267
365
|
|
|
268
366
|
### `tpl_tasks` — the quick list
|
|
269
367
|
|