@lotics/ui 22.3.0 → 23.1.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 +9 -2
- package/MIGRATION.md +142 -2
- package/docs/ai_patterns.md +22 -22
- package/docs/catalog.md +131 -99
- package/docs/composition.md +134 -42
- package/docs/data_entry.md +67 -31
- package/docs/templates.md +86 -68
- package/examples/tpl_allocate.tsx +5 -5
- package/examples/tpl_attendance.tsx +2 -2
- package/examples/tpl_calendar.tsx +6 -6
- package/examples/tpl_dashboard.tsx +9 -9
- package/examples/tpl_dieline.tsx +2 -2
- package/examples/tpl_item_list.tsx +64 -37
- package/examples/tpl_lookup.tsx +5 -5
- package/examples/tpl_pick.tsx +6 -6
- package/examples/tpl_pivot.tsx +3 -3
- package/examples/tpl_record.tsx +919 -611
- package/examples/tpl_report.tsx +2 -2
- package/examples/tpl_rollup.tsx +5 -5
- package/examples/tpl_shifts.tsx +5 -5
- package/examples/tpl_statements.tsx +7 -7
- package/examples/tpl_stock.tsx +2 -2
- package/examples/tpl_task_board.tsx +42 -26
- package/examples/tpl_tower.tsx +6 -6
- package/package.json +4 -3
- package/src/agent_run.tsx +4 -4
- package/src/breakdown.tsx +1 -1
- package/src/calendar/calendar_view.tsx +1 -1
- package/src/change_review.tsx +9 -8
- package/src/chip_group.tsx +12 -2
- package/src/choice_list.tsx +2 -2
- package/src/confidence.tsx +2 -2
- package/src/data_grid.tsx +1 -1
- package/src/detail_row.tsx +50 -58
- package/src/file_dropzone.tsx +1 -1
- package/src/file_gallery_modal.tsx +3 -3
- package/src/file_row.tsx +29 -4
- package/src/file_rows.tsx +1 -1
- package/src/finding.tsx +4 -4
- package/src/form_field.tsx +1 -1
- package/src/format_date.ts +2 -2
- package/src/heatmap.tsx +1 -1
- package/src/inline_button.tsx +84 -0
- package/src/inline_date_picker.tsx +17 -10
- package/src/inline_edit.tsx +341 -60
- package/src/inline_member_select.tsx +8 -3
- package/src/inline_number_input.tsx +11 -4
- package/src/inline_select.tsx +26 -13
- package/src/inline_text_input.tsx +27 -6
- package/src/inline_time_picker.tsx +10 -4
- package/src/input_metrics.ts +17 -0
- package/src/ledger.tsx +2 -2
- package/src/locale.tsx +7 -3
- package/src/matrix.tsx +1 -1
- package/src/number_input.tsx +18 -7
- package/src/pipeline.tsx +1 -1
- package/src/popover.tsx +1 -1
- package/src/press_door.tsx +1 -1
- package/src/pressable_highlight.tsx +1 -1
- package/src/progress_bar.tsx +3 -3
- package/src/record_summary.tsx +2 -2
- package/src/result_header.tsx +2 -2
- package/src/sequence.tsx +170 -0
- package/src/share_or_download.ts +2 -2
- package/src/step_progress.tsx +7 -5
- package/src/stepper.tsx +1 -1
- package/src/task.tsx +6 -6
- package/src/text_input_field.tsx +21 -4
- package/src/text_utils.ts +7 -1
- package/src/linked_record_box.tsx +0 -157
package/docs/catalog.md
CHANGED
|
@@ -20,7 +20,7 @@ indexed in [AGENTS.md](../AGENTS.md).
|
|
|
20
20
|
- **If the closest component lacks a capability, extend it** (a platform change every app
|
|
21
21
|
inherits), never inline a one-off `View`/`Text` rebuild — that forfeits the typeahead,
|
|
22
22
|
async search, virtualization, and a11y the primitive already ships.
|
|
23
|
-
- Floating content (`Dialog
|
|
23
|
+
- Floating content (`Dialog`, `Popover`, `Tooltip`, `Alert`, `OptionList`) needs a
|
|
24
24
|
`PortalHost` (`@lotics/ui/portal`) at the app root.
|
|
25
25
|
- Worked examples referenced below (`tpl_record`, `tpl_item_list`, …) ship in
|
|
26
26
|
[`../examples/`](../examples/).
|
|
@@ -95,8 +95,15 @@ Batch draft-form state → `useForm`.
|
|
|
95
95
|
|
|
96
96
|
### Edit a record's fields in place
|
|
97
97
|
|
|
98
|
-
The `Inline*` family: `InlineTextInput`
|
|
99
|
-
|
|
98
|
+
The `Inline*` family: `InlineTextInput` (**`numberOfLines`** — the line budget for a value a
|
|
99
|
+
reader must read WHOLE rather than recognise at a glance: a payment term, an address, a clause.
|
|
100
|
+
Above 1 the resting box RESERVES that many lines — the full editor height, not the height this
|
|
101
|
+
particular value needs — so clicking in moves neither the field nor anything below it; Enter
|
|
102
|
+
then inserts a newline and the value commits on blur. Both halves matter: a field that only
|
|
103
|
+
goes multiline once focused hides the text from the reader who never clicks, and one that
|
|
104
|
+
reserves less than it opens to shoves the rest of the form down the page on every click),
|
|
105
|
+
`InlineNumberInput`, `InlineSelect` (single or
|
|
106
|
+
`multi`), `InlineMemberSelect`, `InlineDatePicker`, `InlineTimePicker`; a
|
|
100
107
|
READ-ONLY field in that same column uses `InlineStatic` (matches the editor box exactly, no
|
|
101
108
|
input chrome, so it aligns pixel-for-pixel). A stack of labelled field rows lives in
|
|
102
109
|
`DetailTable` + `DetailRow`; the record's identity band is `RecordSummary`; its money
|
|
@@ -151,13 +158,13 @@ Two columnar shapes, and the choice is about data size:
|
|
|
151
158
|
|
|
152
159
|
### Numbers & charts
|
|
153
160
|
|
|
154
|
-
`KPIStrip` (the dashboard stat band)
|
|
155
|
-
— below the toolbar, from the filtered rows)
|
|
161
|
+
`KPIStrip` (the dashboard stat band), `SummaryLine` (the light inline register/list summary
|
|
162
|
+
— below the toolbar, from the filtered rows), `KPICard` / `Metric` (headline figures),
|
|
156
163
|
`TrendChip` (delta), `Sparkline`, `BarChart` / `LineChart` / `PieChart` (the canonical SVG
|
|
157
164
|
set — no recharts), `RingGauge`, `ProgressBar` (its `compact` prop = ONE row, track + a
|
|
158
165
|
plain sm tabular count beside it — the cell/heading/peek-trigger meter; a caption floating
|
|
159
166
|
above a tiny bar reads misaligned. **The track clamps at 100%, the caption does not** — over
|
|
160
|
-
its max it reads `2,100 / 2,000
|
|
167
|
+
its max it reads `2,100 / 2,000 (105%)`, because a meter that says "100%" when you are over
|
|
161
168
|
tells the reader they are exactly at the limit. Numbers format in the reader's locale, so
|
|
162
169
|
never hand-format the value you pass in — when display precision differs from the true value
|
|
163
170
|
(whole credits off a fractional balance), `formatValue` reshapes the caption text and leaves
|
|
@@ -206,8 +213,8 @@ controls is `PressableRow` + **`PressDoor`** — never `PressableHighlight`, whi
|
|
|
206
213
|
button and wraps its children (a button must not contain interactive descendants). The
|
|
207
214
|
door is an empty absolutely-positioned SIBLING of the content carrying the tab stop,
|
|
208
215
|
accessible name, and focus ring; lift the content above it with `zIndex: 1`. `Table`/
|
|
209
|
-
`TableRow`
|
|
210
|
-
|
|
216
|
+
`TableRow` does exactly this internally, so a columnar register needs no assembly —
|
|
217
|
+
reach for `PressDoor` only outside it.
|
|
211
218
|
|
|
212
219
|
### Filters & view controls
|
|
213
220
|
|
|
@@ -246,7 +253,11 @@ scoped to that region's focus; the region-wrapper next to `FileDropzone`'s dedic
|
|
|
246
253
|
"files land without hunting for a dropzone", `FileRow` (a horizontal file/document LINE —
|
|
247
254
|
badge-or-placeholder + name + meta + a composable `trailing` slot for a status badge /
|
|
248
255
|
action / remove; `onPress` makes the whole row a pressable door, `trailing` stays an
|
|
249
|
-
independently-pressable sibling; for checklists & readable lists
|
|
256
|
+
independently-pressable sibling; for checklists & readable lists. **`size`** — `sm` (default)
|
|
257
|
+
is the compact attachment line for many files scanned as a list; **`md` is the document-desk
|
|
258
|
+
row** (taller badge, `ListItem` height) for the FEW rows that ARE the section's subject — an
|
|
259
|
+
expected-document checklist, a slot a button fills. A two-row section at `sm` reads as an
|
|
260
|
+
appendix to the page rather than its point), `FileBadge` (the two-tone
|
|
250
261
|
type mark), `FilePreview` / `FileGalleryModal`, `ImageGallery`; picking is `pickFiles`
|
|
251
262
|
(`@lotics/ui/file_picker` — opens the browser picker and resolves the chosen `File[]`, the
|
|
252
263
|
imperative half behind every Add-file CTA); for gated CRUD compose locally with
|
|
@@ -255,7 +266,7 @@ patterns doc indexed in [AGENTS.md](../AGENTS.md)).
|
|
|
255
266
|
|
|
256
267
|
### Specialized work surfaces
|
|
257
268
|
|
|
258
|
-
`ScanField` (scan/verify), `Stepper` (a guided run / progress sequence — done
|
|
269
|
+
`ScanField` (scan/verify), `Stepper` (a guided run / progress sequence — done, current,
|
|
259
270
|
upcoming, horizontal OR vertical), `RemainderMeter` + `AllocationRow` (allocation),
|
|
260
271
|
`Timeline` (a heterogeneous event LOG — icons + expandable details, not progress),
|
|
261
272
|
`Calendar` (the `calendar` module's views), `Gantt`, `comments_thread`.
|
|
@@ -266,15 +277,15 @@ upcoming, horizontal OR vertical), `RemainderMeter` + `AllocationRow` (allocatio
|
|
|
266
277
|
long text + attachments; the surface that triggers agent work), `AgentRun` (the live
|
|
267
278
|
streaming work feed) + `AgentProgress` (its compact, floating, expandable form — a
|
|
268
279
|
composer's "working" state) + `Confidence`; **`ChangeReview` — THE one review-before-apply
|
|
269
|
-
surface, a COMPOUND family** (frame: `ChangeReview
|
|
270
|
-
`ChangeReviewActions`; sections: `Change
|
|
271
|
-
`ChangeReasoning`; the grammar: `ChangeFields` + `ChangeField
|
|
280
|
+
surface, a COMPOUND family** (frame: `ChangeReview`, `ChangeReviewHeader`,
|
|
281
|
+
`ChangeReviewActions`; sections: `Change`, `ChangeLabel`, `ChangeSummary`,
|
|
282
|
+
`ChangeReasoning`; the grammar: `ChangeFields` + `ChangeField`, `ChangeRecord`,
|
|
272
283
|
`ChangeBand` + `ChangeValueInput`): adds, updates, removals, conflicts, whole records,
|
|
273
284
|
display-only findings are all compositions — see the AI-patterns doc indexed in
|
|
274
285
|
[AGENTS.md](../AGENTS.md) for the laws; `Clarify` (the agent asks back — selectable
|
|
275
286
|
`ChoiceList` options), `Sources` (provenance chips for AI output — at review scale,
|
|
276
287
|
`label={null}` slots the chips at a section's bottom), `Finding` (one ranked insight from an
|
|
277
|
-
AI check — localized severity word
|
|
288
|
+
AI check — localized severity word, title, detail, `Sources` chips, a `children` slot;
|
|
278
289
|
**`FindingComparison`** is the expected-vs-actual body: each disagreeing side a labeled row,
|
|
279
290
|
the DELTA emphasized under a hairline (localized "Difference") — quantities, totals, dates;
|
|
280
291
|
a plain `metric` prop remains for one-number findings. The children slot composes ANY visual
|
|
@@ -336,18 +347,18 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
336
347
|
weekday/month names, and `formatDate` display all follow the provider with no per-instance
|
|
337
348
|
`locale` prop — an explicit `locale` still overrides. **Limitation:** the calendar/gantt
|
|
338
349
|
views and the comment labels are not yet provider-wired — pass their `labels` props directly.
|
|
339
|
-
- **`colors`** — the palette + `withAlpha
|
|
340
|
-
`isColorName
|
|
350
|
+
- **`colors`** — the palette + `withAlpha`, `solid`, `tint`, `ramp`, `ColorName`,
|
|
351
|
+
`isColorName`, `asColorName` (coerce a stored option/status token to a `ColorName`,
|
|
341
352
|
neutral fallback).
|
|
342
353
|
- **`tokens`** — design tokens: re-exports `colors` plus `space`/`type`/`weight`/`radius`
|
|
343
354
|
scales and `getCssVariables()` — an OPT-IN serializer to `--lotics-*` CSS variables for
|
|
344
355
|
hand-rolled plain DOM/CSS (nothing injects them automatically; `@lotics/ui` components
|
|
345
356
|
don't need them).
|
|
346
357
|
- **`spacing`** — the `SPACE` scale + `SpaceToken`.
|
|
347
|
-
- **`control_surface`** — `CONTROL_HEIGHT` (40)
|
|
358
|
+
- **`control_surface`** — `CONTROL_HEIGHT` (40), `CONTROL_RADIUS` (10), `CONTROL_TEXT_INSET`
|
|
348
359
|
(9 — how far a control insets its OWN text: 1px border + 8px padding; anything that must line
|
|
349
|
-
up with a control's WORDS rather than its box carries it, and `TASK_TEXT_INSET` IS it)
|
|
350
|
-
`FOCUS_RING
|
|
360
|
+
up with a control's WORDS rather than its box carries it, and `TASK_TEXT_INSET` IS it),
|
|
361
|
+
`FOCUS_RING`, `HOVER_BORDER`, `CONTROL_TRANSITION`, `chipSurfaceStyle` — the shared
|
|
351
362
|
control-surface tokens.
|
|
352
363
|
- **`fonts.css`** — the Inter sheet (400/500/600, served by absolute URL so it resolves on
|
|
353
364
|
every origin an app runs from); the app entry imports it ONCE or every `Text` falls back
|
|
@@ -380,15 +391,15 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
380
391
|
|
|
381
392
|
### Layout & surfaces
|
|
382
393
|
|
|
383
|
-
- **`card`** — `Card
|
|
384
|
-
`CardHeaderMeta
|
|
385
|
-
- **`inset`** — `Inset`: a tinted, recessed content surface (zinc-50 fill
|
|
386
|
-
pad
|
|
394
|
+
- **`card`** — `Card`, `CardHeader`, `CardHeaderTitle` (+ `info` ⓘ popover),
|
|
395
|
+
`CardHeaderMeta`, `CardBody`, `CardFooter`.
|
|
396
|
+
- **`inset`** — `Inset`: a tinted, recessed content surface (zinc-50 fill, 10 radius, 14
|
|
397
|
+
pad, 12 gap) — the "well" INSIDE a Card/Section for a grouped sub-form, an inline fill
|
|
387
398
|
editor, or a nested block. **NOT a `Callout`:** a Callout is a status BAND (and
|
|
388
399
|
`warning`/`error` render as an ARIA `alert`) — wrapping form fields in an alert is wrong.
|
|
389
400
|
The law: **content + fields → `Inset`; a message → `Callout`.**
|
|
390
|
-
- **`section_heading`** — `Section
|
|
391
|
-
`SectionHeadingMeta
|
|
401
|
+
- **`section_heading`** — `Section`, `SectionHeading`, `SectionHeadingTitle`,
|
|
402
|
+
`SectionHeadingMeta`, `Subsection`, `SubsectionHeading`, `SubsectionHeadingTitle`,
|
|
392
403
|
`DialogSectionHeadingTitle` — the
|
|
393
404
|
card-less twin of the Card family, compound, owns no margin; spacing via the Section gap
|
|
394
405
|
(16, fixed), no body component. `SectionHeadingTitle` is ALWAYS `##` (xl semibold;
|
|
@@ -399,7 +410,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
399
410
|
md-semibold rung with the SAME `icon`/`description`/`info` slots as the section title, so a
|
|
400
411
|
dialog surface loses only the type size, never an affordance. The heading ramp is FIXED:
|
|
401
412
|
`#` xxl / `##` xl / `###` lg / `####` md, no size props.
|
|
402
|
-
- **`section_stack`** — `SectionStack
|
|
413
|
+
- **`section_stack`** — `SectionStack`, `SubsectionStack` — stacks that own the
|
|
403
414
|
between-block law, skipping null children: `SectionStack` = a fixed 56px beat + a hairline
|
|
404
415
|
`Divider` between top-level blocks; `SubsectionStack` = a fixed 32px beat, space-only while
|
|
405
416
|
the groups are SHORT (the titles carry the grouping, hairlines stay at the section level) and
|
|
@@ -467,7 +478,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
467
478
|
the `chip.remove` locale slice). Suggestion pills → `SuggestionChip`.
|
|
468
479
|
- **`action_menu`** — `ActionMenu`: the ⋯ overflow menu (`ActionMenuItem[]`; danger items
|
|
469
480
|
last).
|
|
470
|
-
- **`menu_button`** — `MenuButton`: the menu/rail row (icon
|
|
481
|
+
- **`menu_button`** — `MenuButton`: the menu/rail row (icon, title, `right` slot;
|
|
471
482
|
`focused`/`danger`; `role` menuitem|button|option) — popover menus, outline rails, section
|
|
472
483
|
pickers. Its resting highlight has TWO meanings and they are not interchangeable:
|
|
473
484
|
**`selected`** is listbox SELECTION (emitted as `aria-selected`, and only under
|
|
@@ -490,27 +501,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
490
501
|
own controls. Pair it with `PressableRow` (surface takes the mouse + the wash, door takes
|
|
491
502
|
the keyboard, nested controls keep their own presses); the parent owns the positioning
|
|
492
503
|
context and the content lifts above the door with `zIndex: 1`. `radius` (default 10)
|
|
493
|
-
matches the surface it spans. `TableRow`
|
|
494
|
-
|
|
495
|
-
- **`linked_record_box`** — `LinkedRecordBox`: a bordered box scoping ANOTHER record's data
|
|
496
|
-
(`icon` · `name` · `subtitle` · vertical `facts`); verbs ride an `actions` slot in a
|
|
497
|
-
hairline-fenced footer at the bottom (destructive LEFT, go-to RIGHT — the divider draws itself
|
|
498
|
-
when `actions` is present, so don't hand-add one). Use it wherever a record points at another (a
|
|
499
|
-
shipment's customer, an invoice's party, a case's sibling). **`onOpen` picks the variant, and
|
|
500
|
-
they are exclusive at the type level**: WITH it (+ the required `doorLabel`) the whole box is a
|
|
501
|
-
keyboard door into the record's detail and `facts` values are TEXT (`""` → "—"); WITHOUT it the
|
|
502
|
-
box is a STATIC card — no door, no tab stop, no pointer, nothing announced as a button — and a
|
|
503
|
-
`facts` value may be a NODE (an inline editor; a node prints as authored, no "—"). A node fact
|
|
504
|
-
gets the CONTROL BAND on both cells — its label centres on the control instead of printing at
|
|
505
|
-
the band's top — while a string fact keeps its bare text line, so a long value that wraps still
|
|
506
|
-
tops out level with its label. Reach for the
|
|
507
|
-
static one when the linked record has NO page of its own: nothing to open, so the box is where
|
|
508
|
-
its values are read and edited, and the only interactive parts are `actions` + the fact nodes.
|
|
509
|
-
Under a door, `actions` is the ONLY place a control may live (a fact editor there would compete
|
|
510
|
-
with the press). The door variant **enforces the a11y contract** copy-pasting got wrong: a
|
|
511
|
-
container with interactive descendants is never `role="button"` (invalid HTML) — an internal
|
|
512
|
-
empty door sibling carries the tab stop / name / focus ring, verbs lift above via `zIndex`.
|
|
513
|
-
The reassign picker / empty state that swaps in for the box is the consumer's.
|
|
504
|
+
matches the surface it spans. `TableRow` is the in-kit consumer — reach for it when
|
|
505
|
+
designing a pressable surface it doesn't cover.
|
|
514
506
|
- **`pressable_highlight`** — `PressableHighlight`: the hover-wash + keyboard-focus-ring
|
|
515
507
|
`Pressable` under `MenuButton`/`Switcher`/custom pressable surfaces; its style-fn/children
|
|
516
508
|
receive `hovered` + `focusVisible`.
|
|
@@ -526,7 +518,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
526
518
|
shortcut from a raw string or `ShortcutDescriptor` (⌘B on Mac, Ctrl+B elsewhere); null on
|
|
527
519
|
small screens. `TextInputField shortcut` renders it built-in, while the field is EMPTY —
|
|
528
520
|
it hints at reaching the field, so a value replaces it (with the clear ✕, when `clearable`).
|
|
529
|
-
- **`keyboard`** — `isMac
|
|
521
|
+
- **`keyboard`** — `isMac`, `ShortcutDescriptor`, `formatShortcut` (platform-aware
|
|
530
522
|
shortcut formatting behind `ShortcutBadge`).
|
|
531
523
|
|
|
532
524
|
### Badges, status & feedback
|
|
@@ -538,7 +530,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
538
530
|
(see composition.md "Badge is for status only"). A bare `<Badge>` is `dot`.
|
|
539
531
|
- **`status_badge`** — `StatusBadge`: an enabled/disabled pulse badge (`enabled` + `label`).
|
|
540
532
|
- **`option_badge`** — `OptionBadge`: a select value as its configured colored badge.
|
|
541
|
-
- **`callout`** — `Callout
|
|
533
|
+
- **`callout`** — `Callout`, `CalloutTitle`, `CalloutText`, `CalloutActions` (`tone`
|
|
542
534
|
info|success|warning|error|neutral): inline status band — a MESSAGE (`warning`/`error`
|
|
543
535
|
are ARIA `alert`s). For a form / nested content surface use **`Inset`**, never a Callout.
|
|
544
536
|
- **`empty_state`** — `EmptyState`: centered placeholder for an empty list/filter result —
|
|
@@ -664,17 +656,30 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
664
656
|
INSIDE the control and inline error, commit on blur (Enter saves, Escape reverts) or
|
|
665
657
|
`controls="buttons"`; KEYBOARD focus on the closed view opens edit mode with the input
|
|
666
658
|
focused (type → Tab → type — see the data-entry keyboard contract), pointer focus never
|
|
667
|
-
does; **`variant: "
|
|
668
|
-
|
|
659
|
+
does; **`variant: "framed" | "bare"`** sets how much of the field's frame shows AT REST
|
|
660
|
+
(see below). Every commit registers in **`pending_commits`**, which
|
|
669
661
|
`Button`/`IconButton` wait on via **`use_gated_press`** so an action pressed in the same
|
|
670
662
|
gesture as the blur cannot read the record before the edit lands (data_entry.md § Inline
|
|
671
663
|
edit) — automatic, nothing to pass.
|
|
664
|
+
**`actions` puts VERBS on the field's surface** (an `InlineButton` — Copy, Open, Today), and
|
|
665
|
+
EVERY `Inline*` editor takes it. `trailing` is decoration only (a chevron, a spinner): it
|
|
666
|
+
renders INSIDE the press target, so a button there would be a button in a button.
|
|
667
|
+
With `actions` the field renders through ONE shell that owns the surface in BOTH modes — so
|
|
668
|
+
the verbs never move when the editor swaps to its input, the control inside goes `seamless`
|
|
669
|
+
(drawing no second box), hover is tracked on the box (react-native-web hands a parent's hover
|
|
670
|
+
to the innermost pressable, so a control-tracked border drops out as the pointer crosses a
|
|
671
|
+
verb), and the focus ring paints on the whole FIELD via focus-within, not on the value region.
|
|
672
|
+
A popover-backed editor also anchors its overlay to the shell (`anchorRef`) rather than to the
|
|
673
|
+
control — with verbs the control is narrower than the field, and an inherited width would open
|
|
674
|
+
a list too small for its own row.
|
|
675
|
+
Pass `actions` UNCONDITIONALLY and `disabled` the verb when it has nothing to act on: a slot
|
|
676
|
+
that appears once the value is non-empty resizes the field as the user types.
|
|
672
677
|
- **`inline_text_input`**, **`inline_number_input`** (`format` for currency/units),
|
|
673
678
|
**`inline_select`**, **`inline_member_select`**, **`inline_date_picker`**
|
|
674
679
|
(`format="datetime"`, `optionalTime`; keyboard focus opens the TYPED segmented `DateField`
|
|
675
680
|
— locale field order, separator advances, Alt+ArrowDown floats the calendar; click still
|
|
676
681
|
opens the calendar popover), **`inline_time_picker`** — the inline field editors (a form field by
|
|
677
|
-
|
|
682
|
+
the same editor everywhere, `variant="bare"` on a dense grid — see the surface note below);
|
|
678
683
|
`InlineSelect`/`InlineMemberSelect` render the resting value like its option —
|
|
679
684
|
`renderOptionContent` by default, `renderSelected` to override — a chip/badge at rest, not
|
|
680
685
|
just text. **`InlineSelect` is single OR multi** — pass `multi` for a tag SET (`value: T[]`,
|
|
@@ -687,38 +692,64 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
687
692
|
(`TextColor`) that colours the resting date by
|
|
688
693
|
semantic state — overdue red, soon-due amber (the caller owns the rule, e.g. a task's `dueTone`);
|
|
689
694
|
the value text IS the signal, no separate dot.
|
|
690
|
-
**
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
695
|
+
**ONE surface, ONE hover language; `variant` sets its resting WEIGHT.** An editor wears THE pill
|
|
696
|
+
surface — white, 1px border, the same one `Chip`, `ChipGroup` and a secondary `Button` wear — so
|
|
697
|
+
every control a row can hold reads as one family. Open adds the 2px ring; disabled rests flat and
|
|
698
|
+
borderless, promising no press. HOVER differs by variant, because hover must be a visible CHANGE
|
|
699
|
+
rather than a deeper line: `framed` deepens its border AND tints (zinc-200 → zinc-400 alone is a
|
|
700
|
+
shade shift on 1px), `bare` has the border ARRIVE (loud by itself — and a tint there would make a
|
|
701
|
+
hovered cell lighter than the row washing beneath it).
|
|
702
|
+
- **`variant="framed"`** (default) — the frame is already showing. Required wherever editable and
|
|
703
|
+
static values MIX (a record's `DetailTable`): there it is the only thing saying what you can change.
|
|
704
|
+
- **`variant="bare"`** — nothing at rest, the SAME border on hover. For a surface where EVERY value
|
|
705
|
+
edits (a register/`DataGrid` column, a task row), where a per-field frame states what the whole
|
|
706
|
+
surface already promises and, down a column, draws the grid twice.
|
|
707
|
+
|
|
708
|
+
It is an axis of weight, not of use: both hover and open identically. There is no `*Cell` family —
|
|
709
|
+
a data-grid cell is `<InlineSelect variant="bare" …/>`, not a `SelectCell` (naming a component for a
|
|
710
|
+
USE, and duplicating the picker stack to flip two style properties, are both things the kit forbids).
|
|
711
|
+
Hand-roll a pressable cell to match `bare`, never a background wash.
|
|
705
712
|
- **`inline_static`** — `InlineStatic`: a READ-ONLY value matching the Inline\* box metrics
|
|
706
713
|
EXACTLY (height, padding, 1px transparent border) so a non-editable field — a computed
|
|
707
714
|
total, a system ID, a synced/locked value — aligns pixel-for-pixel in the same column;
|
|
708
715
|
non-interactive, NOT a disabled input; `muted`/`tabular`/`align="right"` for a number
|
|
709
716
|
column, `weight="medium"` to emphasise a total among plain rows.
|
|
717
|
+
- **`sequence`** — `Sequence` + `SequenceItem` (+ `SEQUENCE_INSET`): an ORDERED list whose ORDER
|
|
718
|
+
IS THE DATA — a route's stops, an approval chain, a set of legs — drawn as a connected rail so
|
|
719
|
+
the sequence reads without a label saying "first"/"then". Reach for it when a thing has a
|
|
720
|
+
VARIABLE number of positions: one field per position (`Origin` / `Transfer point` /
|
|
721
|
+
`Destination`) cannot hold a second middle entry and cannot say the entries are ordered.
|
|
722
|
+
`SequenceItem` takes the content, an optional `role` (DERIVE it from index — a stored role lies
|
|
723
|
+
the moment the list is reordered) and `onMoveUp`/`onMoveDown`/`onRemove`; the controls render
|
|
724
|
+
even where they cannot act so the right edge never shifts between items. Reorder is BUTTONS,
|
|
725
|
+
not a drag handle — dragging is invisible to the keyboard and fiddly on a phone, and a
|
|
726
|
+
three-to-six position list does not need it. Indent anything that follows the list (an "Add"
|
|
727
|
+
link) by `SEQUENCE_INSET` so it lands on the rail's column. Distinct from `Timeline` (an
|
|
728
|
+
activity FEED), `Stepper` (a wizard's fixed positions) and `Pipeline` (ONE record walking
|
|
729
|
+
stages that own their controls) — this is the list a user EDITS.
|
|
730
|
+
- **`inline_button`** — `InlineButton`: a verb that sits INSIDE an inline field's surface
|
|
731
|
+
(the `Open` on a reference, a `Copy` on a value worth copying) rather than in the row's
|
|
732
|
+
trailing column — put it inside when the act is ABOUT THE VALUE, so it travels with what it
|
|
733
|
+
acts on and the trailing column stays free for row-level verbs. FILLED (`Button secondary`'s own
|
|
734
|
+
zinc-100) on the field's white surface — the contrast is a RELATIONSHIP, not a colour: invert it
|
|
735
|
+
if the resting field surface ever changes, or the verb dissolves into the value. 28px, radius 8,
|
|
736
|
+
no border (the field already has one) and no shadow (depth belongs to the primary action alone). **It stops propagation** — that is the contract: the
|
|
737
|
+
surface underneath is itself pressable, so without it one press fires both, silently. Always
|
|
738
|
+
a WORD, never icon-only.
|
|
710
739
|
- **`detail_row`** — `DetailTable` + `DetailRow` — the record field grid. `DetailRow`:
|
|
711
740
|
label+value row for drawer/peek detail; in FORM mode (`labelWidth` set) the value column
|
|
712
741
|
FILLS the row so a stack of inline editors all span the same width + none jumps wider on
|
|
713
|
-
edit
|
|
714
|
-
|
|
742
|
+
edit. There is NO trailing slot: a verb about the VALUE goes on the field (`InlineButton` in
|
|
743
|
+
the editor's `actions`), and anything else that belongs beside the value — a status `Badge`, a
|
|
744
|
+
unit (though "kg"/"$" belong IN the value via `InlineNumberInput format`) — composes into a
|
|
745
|
+
row inside the value cell, costing only the row that wants it. The LABEL WRAPS inside its column and
|
|
715
746
|
is never clipped — a fixed `labelWidth` would otherwise ellipsize every long field name
|
|
716
747
|
("Registered business address"), and a name the reader can't finish is worse than a taller
|
|
717
748
|
row; a wrapped label's FIRST line stays level with the value's first control line while a
|
|
718
749
|
one-line label still centers on it (both modes, no prop). The FIELD-ANNOTATION vocabulary (same
|
|
719
750
|
names + meanings as `FormField`), always EXPLICIT — a row never hides guidance behind an ⓘ:
|
|
720
751
|
**`description`** = a fact / persistent guidance (muted), under the VALUE (stacked mode
|
|
721
|
-
mirrors the form order label
|
|
752
|
+
mirrors the form order label, description, control); **`warning`** = a consequence to weigh
|
|
722
753
|
before acting (amber, announced); **`error`** = field-level failure under the value, danger +
|
|
723
754
|
alert semantics (the `Inline*` editors already render their own transient save errors — don't
|
|
724
755
|
wire both); invalid/cross-field STATE = a co-located `Callout`; **`flat`**
|
|
@@ -729,19 +760,20 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
729
760
|
on two surfaces.
|
|
730
761
|
`DetailTable`: the compound parent of
|
|
731
762
|
a row STACK — `labelWidth` (default `DETAIL_LABEL_WIDTH`, 130 — THE kit's label column, and
|
|
732
|
-
a `TaskList`'s default too) / `
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
763
|
+
a `TaskList`'s default too) / `minHeight` (default 40, the inline-control grid) declared
|
|
764
|
+
ONCE + the `SPACE.md` (16) row gap — 8 was right while a field was a tint, but a stack of
|
|
765
|
+
BORDERED rows that close together fuses into one block. TWO columns, no third: the value
|
|
766
|
+
FILLS what the label leaves, so nothing one row does can narrow its neighbours' editors. RESPONSIVE with no prop: it
|
|
767
|
+
measures its own container (onLayout, not
|
|
736
768
|
the viewport — works inside a Drawer; the unmeasured first frame renders opacity-0 so the
|
|
737
769
|
first PAINT is already in the right mode) and when the columns would crush the value cell
|
|
738
770
|
it STACKS every row (the label above a full-width value row in the FormField label
|
|
739
|
-
grammar
|
|
771
|
+
grammar); raise `minValueWidth` (default 160) when a cell holds
|
|
740
772
|
MORE than one editor so the table stacks earlier. Two tables on one page share one grid by
|
|
741
|
-
repeating the same labelWidth
|
|
773
|
+
repeating the same labelWidth. Worked example:
|
|
742
774
|
[`tpl_record`](../examples/tpl_record.tsx).
|
|
743
775
|
- **`record_summary`** — `RecordSummary`: the identity band of a record detail/drawer — ONE
|
|
744
|
-
row: `title` xxl semibold tabular
|
|
776
|
+
row: `title` xxl semibold tabular, `subtitle` sm muted, `status` Badge slot, optional
|
|
745
777
|
`metric` {label,value,tone,note} pinned right, the band's ONE accent. The record's FIELDS
|
|
746
778
|
never live in the header: compose them as `DetailTable`s in the sections below. Replaces
|
|
747
779
|
hand-rolled record headers (mixed scales, several competing figures, color noise).
|
|
@@ -819,7 +851,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
819
851
|
|
|
820
852
|
`TaskStatus` takes the `CheckCircle` (omit `onChange` for a read-only ring; a PICKER list
|
|
821
853
|
puts a `CheckboxInput` here and sets `controlWidth={24}`). `TaskTitle` takes the
|
|
822
|
-
`variant="
|
|
854
|
+
`InlineTextInput variant="bare"` — or the title TEXT itself as a string child
|
|
823
855
|
(`<TaskTitle struck={done}>{label}</TaskTitle>`), which is the READ-ONLY form: the compound
|
|
824
856
|
applies the cell inset and the row's band, so a plain `<Text>` + a hand-rolled
|
|
825
857
|
`TASK_TEXT_INSET` is never needed and a long title that wraps keeps its first line beside the
|
|
@@ -831,7 +863,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
831
863
|
gutter on the first line (Delete lives BEHIND it, danger-styled and last, never a bare ✕).
|
|
832
864
|
`TaskDetail` is a FREE-FORM block under the row on the title's text edge — a chart, a table, a
|
|
833
865
|
form with its own submit — rendered only while open. The module also exports
|
|
834
|
-
**`TASK_TEXT_INSET`** (9), the inset
|
|
866
|
+
**`TASK_TEXT_INSET`** (9), the inset an inline control puts on its own text: every
|
|
835
867
|
slot above already applies it, so reach for it ONLY when a custom title NODE (not a cell
|
|
836
868
|
control) has to land on the same text edge.
|
|
837
869
|
|
|
@@ -844,7 +876,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
844
876
|
step inward per level. Declare `actionWidth={0}` when NO row in the list carries actions.
|
|
845
877
|
|
|
846
878
|
**THE ROW CARRIES THE TITLE; THE FIELDS THE USER CAN SET ARE SUB-ROWS BENEATH IT.** One task's
|
|
847
|
-
own fields hang under it as **`TaskSubRow`** (`label
|
|
879
|
+
own fields hang under it as **`TaskSubRow`** (`label`, the control, `description` /
|
|
848
880
|
`warning` / `error`), indented ONE step — the same step a nested `TaskList` takes, because
|
|
849
881
|
belonging is expressed by indentation and there is only one device for it. Label and value sit
|
|
850
882
|
ADJACENT so the eye pairs them. **The label column is the LIST's, not the row's** — one
|
|
@@ -855,7 +887,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
855
887
|
vocabulary with longer field names staggered nearly every row). The value takes the slack from
|
|
856
888
|
a readable minimum — on a phone or in a narrow drawer it drops onto its own line under the
|
|
857
889
|
label rather than ellipsizing beside it. Pass a form-variant `Inline*` editor (the default),
|
|
858
|
-
|
|
890
|
+
`variant="bare"`: a dense grid drops the resting frame.
|
|
859
891
|
|
|
860
892
|
**A TASK'S FIELD ANNOTATES EXACTLY LIKE A RECORD'S FIELD.** `description` (persistent
|
|
861
893
|
guidance), `warning` (a consequence to weigh — amber, announced) and `error` (field-level
|
|
@@ -1096,8 +1128,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1096
1128
|
`FileGrid`; the host wires `files` + `onAdd`/`onRemove` (+ optional `uploads`,
|
|
1097
1129
|
`selectTileRemove`, `labels`, `galleryLabels`, `gridMaxHeight` — cap the grid height so it
|
|
1098
1130
|
scrolls and the bar pins, for a popover/drawer). **What you can DO to the files is composed
|
|
1099
|
-
below it**, from `FilesEditorBar` + `FilesEditorUpload
|
|
1100
|
-
`FilesEditorSelectAll
|
|
1131
|
+
below it**, from `FilesEditorBar` + `FilesEditorUpload`, `FilesEditorSelect`,
|
|
1132
|
+
`FilesEditorSelectAll`, `FilesEditorDownload`, `FilesEditorRemove` (+
|
|
1101
1133
|
`FilesEditorBarSpacer` to push the rest right). Each renders nothing without the handler it
|
|
1102
1134
|
needs, so withholding `onAdd`/`onRemove` IS the read-only shape — there is no `readOnly`
|
|
1103
1135
|
mode. **A HOST verb is a plain `Button`** reading **`useFilesEditorSelection()`**
|
|
@@ -1124,8 +1156,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1124
1156
|
(`FileGrid` resolves them from `LoticsLocale.fileUpload` on your behalf).
|
|
1125
1157
|
- **`file_thumbnail`** — `FileThumbnail` + `DisplayFile` + `THUMBNAIL_SIZE` /
|
|
1126
1158
|
`COMPACT_THUMBNAIL_SIZE` + `getMediaIcon`: the completed tile — the right surface per
|
|
1127
|
-
MIME: image thumbnail
|
|
1128
|
-
|
|
1159
|
+
MIME: image thumbnail, a doc tile with the `FileBadge` centered + a single-line filename
|
|
1160
|
+
, media card; `isTemplate` overlays a TMPL marker. The tile's accessible name is the
|
|
1129
1161
|
filename; pass `accessibilityLabel` to say what pressing it DOES instead. The per-surface
|
|
1130
1162
|
pieces are exported for a hand-rolled layout: `DocumentBadge` (a bare pressable badge —
|
|
1131
1163
|
its `size` is the square SLOT side, like every other tile here, and the badge is fitted to
|
|
@@ -1146,15 +1178,15 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1146
1178
|
the mark is TALLER than it is wide (26 × 32 by default), so a square slot takes the
|
|
1147
1179
|
fitted width, not the slot side (that conversion is what `DocumentBadge` does).
|
|
1148
1180
|
- **`file_preview`** — `FilePreview`: the universal inline preview — image/PDF/video/audio +
|
|
1149
|
-
Word via `@lotics/docx` + Excel/CSV via `@lotics/xlsx`; the heavy engines (pdf.js
|
|
1150
|
-
`@lotics/docx
|
|
1181
|
+
Word via `@lotics/docx` + Excel/CSV via `@lotics/xlsx`; the heavy engines (pdf.js,
|
|
1182
|
+
`@lotics/docx`, `@lotics/xlsx`) are LAZY (dynamic-imported, ~free until a doc of that
|
|
1151
1183
|
type is opened) and SHIP AS `@lotics/ui` deps — custom-code apps get PDF/Word/Excel
|
|
1152
1184
|
preview with ZERO extra install. Renders to canvas/DOM, never a nested iframe — works in
|
|
1153
1185
|
the sandboxed app iframe.
|
|
1154
1186
|
- **`file_preview_types`** — `PreviewLabels` / `FilePreviewProps` / `GalleryLabels` — the
|
|
1155
1187
|
shared label + prop contracts of the file-preview family; types only.
|
|
1156
1188
|
- **`file_gallery_modal`** — `FileGalleryModal`: the FULL-SCREEN viewer — toolbar (filename
|
|
1157
|
-
|
|
1189
|
+
, counter, download, optional `onOpenExternal`/`onRemove`, close-✕), prev/next, ESC,
|
|
1158
1190
|
rotate (the 90° controls FLOAT as a pill on the image); on a phone the actions collapse
|
|
1159
1191
|
into a ⋯ `ActionMenu`. `onPersistRotation`/`persisting` wire a rotation save. Driven by
|
|
1160
1192
|
`files` + `activeIndex` + `onIndexChange`.
|
|
@@ -1236,16 +1268,16 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1236
1268
|
be rendered; where a compact meter used to decorate a ranked row or proposal card, rank /
|
|
1237
1269
|
badges / severity carry the standing instead. Also: calibrated
|
|
1238
1270
|
high/med/low; localized via the provider.
|
|
1239
|
-
- **`change_review`** — the COMPOUND review family — frame: `ChangeReview` provider/stack
|
|
1240
|
-
`ChangeReviewHeader` (auto kept-counter over decidable entries)
|
|
1271
|
+
- **`change_review`** — the COMPOUND review family — frame: `ChangeReview` provider/stack,
|
|
1272
|
+
`ChangeReviewHeader` (auto kept-counter over decidable entries), `ChangeReviewActions`
|
|
1241
1273
|
(the commit bar in the DialogFooter/DrawerFooter: Keep-all bottom-left (`onAcceptAll` for
|
|
1242
1274
|
host-held field state) + Apply gating); sections: `Change` (host-owned status, labeled
|
|
1243
|
-
verbs, collapses to its `ChangeSummary` + Undo; no callbacks = display-only)
|
|
1244
|
-
`ChangeLabel
|
|
1245
|
-
`ChangeFields` (the open record form) + `ChangeField` (THE field: − band
|
|
1246
|
-
candidates + type-another-value
|
|
1275
|
+
verbs, collapses to its `ChangeSummary` + Undo; no callbacks = display-only),
|
|
1276
|
+
`ChangeLabel`, `ChangeSummary`, `ChangeReasoning` (the quiet why); grammar:
|
|
1277
|
+
`ChangeFields` (the open record form) + `ChangeField` (THE field: − band, value,
|
|
1278
|
+
candidates + type-another-value, reasoning, per-field Keep/Drop, collapse),
|
|
1247
1279
|
`ChangeRecord` (THE item card: registers like a Change; tone wash + localized op word;
|
|
1248
|
-
verb level follows the decision level)
|
|
1280
|
+
verb level follows the decision level), `ChangeBand` (the raw ± band),
|
|
1249
1281
|
`ChangeValueInput` (the diff-at-rest editor); the `changeReview` locale slice.
|
|
1250
1282
|
- **`clarify`** — `Clarify` + `ClarifyOption`: the agent asks back — a borderless block
|
|
1251
1283
|
(the question text + a `ChoiceList`, no card wrapper; an optional muted `eyebrow` sits tight above
|
|
@@ -1280,17 +1312,17 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1280
1312
|
- **`sources`** — `Sources` + `SourceRef`/`SourceKind` (record | document | table | web |
|
|
1281
1313
|
knowledge): provenance chips, per-kind glyphs.
|
|
1282
1314
|
- **`finding`** — `Finding` + `FindingComparison` + `FindingSeverity`/`FindingLabels`: one
|
|
1283
|
-
ranked AI-check insight — severity word
|
|
1315
|
+
ranked AI-check insight — severity word, title, detail, `Sources`, children slot; the
|
|
1284
1316
|
expected-vs-actual body with the emphasized delta; the `finding` locale slice.
|
|
1285
1317
|
- **`result_header`** — `ResultHeader` (+ `ResultTone`): the save-direct RECEIPT's outcome
|
|
1286
1318
|
strip, on the page grid — tone mark (`ok`/`attention`/`error`/`skipped`) inline on the
|
|
1287
|
-
title row
|
|
1319
|
+
title row, outcome-first title, right-slot `action` (the open-record jump / a retry),
|
|
1288
1320
|
ONE line under (the honest `Confidence`, or the failure reason). The receipt composes:
|
|
1289
1321
|
header → the receipt lines (a `DetailTable` of spread `DetailRow`s — label left, value at
|
|
1290
1322
|
the right edge) → a `Confidence` callout naming the exact values that failed their
|
|
1291
1323
|
deterministic checks (or stating what passed). NOTHING in a
|
|
1292
1324
|
receipt edits — the RECORD is the edit surface, one press away. SEVERAL records → an
|
|
1293
|
-
attention-first register of `ListItem` rows (tone mark
|
|
1325
|
+
attention-first register of `ListItem` rows (tone mark, title, figures, needs-checking
|
|
1294
1326
|
count) pressing straight through to the records. See ai_patterns §the one law.
|
|
1295
1327
|
- **`comments_thread`** — `CommentList` + the
|
|
1296
1328
|
`ThreadComment`/`ThreadMember`/`ThreadFile` types: the record comments thread.
|