@lotics/ui 22.2.0 → 23.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 +9 -2
- package/MIGRATION.md +142 -2
- package/docs/ai_patterns.md +27 -25
- package/docs/catalog.md +119 -98
- 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 +3 -2
- package/src/agent_run.tsx +40 -25
- 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_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 +298 -59
- 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 +12 -4
- package/src/inline_time_picker.tsx +10 -4
- 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 +19 -1
- package/src/text_utils.ts +1 -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,8 @@ Batch draft-form state → `useForm`.
|
|
|
95
95
|
|
|
96
96
|
### Edit a record's fields in place
|
|
97
97
|
|
|
98
|
-
The `Inline*` family: `InlineTextInput
|
|
99
|
-
`multi`)
|
|
98
|
+
The `Inline*` family: `InlineTextInput`, `InlineNumberInput`, `InlineSelect` (single or
|
|
99
|
+
`multi`), `InlineMemberSelect`, `InlineDatePicker`, `InlineTimePicker`; a
|
|
100
100
|
READ-ONLY field in that same column uses `InlineStatic` (matches the editor box exactly, no
|
|
101
101
|
input chrome, so it aligns pixel-for-pixel). A stack of labelled field rows lives in
|
|
102
102
|
`DetailTable` + `DetailRow`; the record's identity band is `RecordSummary`; its money
|
|
@@ -151,13 +151,13 @@ Two columnar shapes, and the choice is about data size:
|
|
|
151
151
|
|
|
152
152
|
### Numbers & charts
|
|
153
153
|
|
|
154
|
-
`KPIStrip` (the dashboard stat band)
|
|
155
|
-
— below the toolbar, from the filtered rows)
|
|
154
|
+
`KPIStrip` (the dashboard stat band), `SummaryLine` (the light inline register/list summary
|
|
155
|
+
— below the toolbar, from the filtered rows), `KPICard` / `Metric` (headline figures),
|
|
156
156
|
`TrendChip` (delta), `Sparkline`, `BarChart` / `LineChart` / `PieChart` (the canonical SVG
|
|
157
157
|
set — no recharts), `RingGauge`, `ProgressBar` (its `compact` prop = ONE row, track + a
|
|
158
158
|
plain sm tabular count beside it — the cell/heading/peek-trigger meter; a caption floating
|
|
159
159
|
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
|
|
160
|
+
its max it reads `2,100 / 2,000 (105%)`, because a meter that says "100%" when you are over
|
|
161
161
|
tells the reader they are exactly at the limit. Numbers format in the reader's locale, so
|
|
162
162
|
never hand-format the value you pass in — when display precision differs from the true value
|
|
163
163
|
(whole credits off a fractional balance), `formatValue` reshapes the caption text and leaves
|
|
@@ -206,8 +206,8 @@ controls is `PressableRow` + **`PressDoor`** — never `PressableHighlight`, whi
|
|
|
206
206
|
button and wraps its children (a button must not contain interactive descendants). The
|
|
207
207
|
door is an empty absolutely-positioned SIBLING of the content carrying the tab stop,
|
|
208
208
|
accessible name, and focus ring; lift the content above it with `zIndex: 1`. `Table`/
|
|
209
|
-
`TableRow`
|
|
210
|
-
|
|
209
|
+
`TableRow` does exactly this internally, so a columnar register needs no assembly —
|
|
210
|
+
reach for `PressDoor` only outside it.
|
|
211
211
|
|
|
212
212
|
### Filters & view controls
|
|
213
213
|
|
|
@@ -255,7 +255,7 @@ patterns doc indexed in [AGENTS.md](../AGENTS.md)).
|
|
|
255
255
|
|
|
256
256
|
### Specialized work surfaces
|
|
257
257
|
|
|
258
|
-
`ScanField` (scan/verify), `Stepper` (a guided run / progress sequence — done
|
|
258
|
+
`ScanField` (scan/verify), `Stepper` (a guided run / progress sequence — done, current,
|
|
259
259
|
upcoming, horizontal OR vertical), `RemainderMeter` + `AllocationRow` (allocation),
|
|
260
260
|
`Timeline` (a heterogeneous event LOG — icons + expandable details, not progress),
|
|
261
261
|
`Calendar` (the `calendar` module's views), `Gantt`, `comments_thread`.
|
|
@@ -266,15 +266,15 @@ upcoming, horizontal OR vertical), `RemainderMeter` + `AllocationRow` (allocatio
|
|
|
266
266
|
long text + attachments; the surface that triggers agent work), `AgentRun` (the live
|
|
267
267
|
streaming work feed) + `AgentProgress` (its compact, floating, expandable form — a
|
|
268
268
|
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
|
|
269
|
+
surface, a COMPOUND family** (frame: `ChangeReview`, `ChangeReviewHeader`,
|
|
270
|
+
`ChangeReviewActions`; sections: `Change`, `ChangeLabel`, `ChangeSummary`,
|
|
271
|
+
`ChangeReasoning`; the grammar: `ChangeFields` + `ChangeField`, `ChangeRecord`,
|
|
272
272
|
`ChangeBand` + `ChangeValueInput`): adds, updates, removals, conflicts, whole records,
|
|
273
273
|
display-only findings are all compositions — see the AI-patterns doc indexed in
|
|
274
274
|
[AGENTS.md](../AGENTS.md) for the laws; `Clarify` (the agent asks back — selectable
|
|
275
275
|
`ChoiceList` options), `Sources` (provenance chips for AI output — at review scale,
|
|
276
276
|
`label={null}` slots the chips at a section's bottom), `Finding` (one ranked insight from an
|
|
277
|
-
AI check — localized severity word
|
|
277
|
+
AI check — localized severity word, title, detail, `Sources` chips, a `children` slot;
|
|
278
278
|
**`FindingComparison`** is the expected-vs-actual body: each disagreeing side a labeled row,
|
|
279
279
|
the DELTA emphasized under a hairline (localized "Difference") — quantities, totals, dates;
|
|
280
280
|
a plain `metric` prop remains for one-number findings. The children slot composes ANY visual
|
|
@@ -336,18 +336,18 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
336
336
|
weekday/month names, and `formatDate` display all follow the provider with no per-instance
|
|
337
337
|
`locale` prop — an explicit `locale` still overrides. **Limitation:** the calendar/gantt
|
|
338
338
|
views and the comment labels are not yet provider-wired — pass their `labels` props directly.
|
|
339
|
-
- **`colors`** — the palette + `withAlpha
|
|
340
|
-
`isColorName
|
|
339
|
+
- **`colors`** — the palette + `withAlpha`, `solid`, `tint`, `ramp`, `ColorName`,
|
|
340
|
+
`isColorName`, `asColorName` (coerce a stored option/status token to a `ColorName`,
|
|
341
341
|
neutral fallback).
|
|
342
342
|
- **`tokens`** — design tokens: re-exports `colors` plus `space`/`type`/`weight`/`radius`
|
|
343
343
|
scales and `getCssVariables()` — an OPT-IN serializer to `--lotics-*` CSS variables for
|
|
344
344
|
hand-rolled plain DOM/CSS (nothing injects them automatically; `@lotics/ui` components
|
|
345
345
|
don't need them).
|
|
346
346
|
- **`spacing`** — the `SPACE` scale + `SpaceToken`.
|
|
347
|
-
- **`control_surface`** — `CONTROL_HEIGHT` (40)
|
|
347
|
+
- **`control_surface`** — `CONTROL_HEIGHT` (40), `CONTROL_RADIUS` (10), `CONTROL_TEXT_INSET`
|
|
348
348
|
(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
|
|
349
|
+
up with a control's WORDS rather than its box carries it, and `TASK_TEXT_INSET` IS it),
|
|
350
|
+
`FOCUS_RING`, `HOVER_BORDER`, `CONTROL_TRANSITION`, `chipSurfaceStyle` — the shared
|
|
351
351
|
control-surface tokens.
|
|
352
352
|
- **`fonts.css`** — the Inter sheet (400/500/600, served by absolute URL so it resolves on
|
|
353
353
|
every origin an app runs from); the app entry imports it ONCE or every `Text` falls back
|
|
@@ -380,15 +380,15 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
380
380
|
|
|
381
381
|
### Layout & surfaces
|
|
382
382
|
|
|
383
|
-
- **`card`** — `Card
|
|
384
|
-
`CardHeaderMeta
|
|
385
|
-
- **`inset`** — `Inset`: a tinted, recessed content surface (zinc-50 fill
|
|
386
|
-
pad
|
|
383
|
+
- **`card`** — `Card`, `CardHeader`, `CardHeaderTitle` (+ `info` ⓘ popover),
|
|
384
|
+
`CardHeaderMeta`, `CardBody`, `CardFooter`.
|
|
385
|
+
- **`inset`** — `Inset`: a tinted, recessed content surface (zinc-50 fill, 10 radius, 14
|
|
386
|
+
pad, 12 gap) — the "well" INSIDE a Card/Section for a grouped sub-form, an inline fill
|
|
387
387
|
editor, or a nested block. **NOT a `Callout`:** a Callout is a status BAND (and
|
|
388
388
|
`warning`/`error` render as an ARIA `alert`) — wrapping form fields in an alert is wrong.
|
|
389
389
|
The law: **content + fields → `Inset`; a message → `Callout`.**
|
|
390
|
-
- **`section_heading`** — `Section
|
|
391
|
-
`SectionHeadingMeta
|
|
390
|
+
- **`section_heading`** — `Section`, `SectionHeading`, `SectionHeadingTitle`,
|
|
391
|
+
`SectionHeadingMeta`, `Subsection`, `SubsectionHeading`, `SubsectionHeadingTitle`,
|
|
392
392
|
`DialogSectionHeadingTitle` — the
|
|
393
393
|
card-less twin of the Card family, compound, owns no margin; spacing via the Section gap
|
|
394
394
|
(16, fixed), no body component. `SectionHeadingTitle` is ALWAYS `##` (xl semibold;
|
|
@@ -399,7 +399,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
399
399
|
md-semibold rung with the SAME `icon`/`description`/`info` slots as the section title, so a
|
|
400
400
|
dialog surface loses only the type size, never an affordance. The heading ramp is FIXED:
|
|
401
401
|
`#` xxl / `##` xl / `###` lg / `####` md, no size props.
|
|
402
|
-
- **`section_stack`** — `SectionStack
|
|
402
|
+
- **`section_stack`** — `SectionStack`, `SubsectionStack` — stacks that own the
|
|
403
403
|
between-block law, skipping null children: `SectionStack` = a fixed 56px beat + a hairline
|
|
404
404
|
`Divider` between top-level blocks; `SubsectionStack` = a fixed 32px beat, space-only while
|
|
405
405
|
the groups are SHORT (the titles carry the grouping, hairlines stay at the section level) and
|
|
@@ -467,7 +467,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
467
467
|
the `chip.remove` locale slice). Suggestion pills → `SuggestionChip`.
|
|
468
468
|
- **`action_menu`** — `ActionMenu`: the ⋯ overflow menu (`ActionMenuItem[]`; danger items
|
|
469
469
|
last).
|
|
470
|
-
- **`menu_button`** — `MenuButton`: the menu/rail row (icon
|
|
470
|
+
- **`menu_button`** — `MenuButton`: the menu/rail row (icon, title, `right` slot;
|
|
471
471
|
`focused`/`danger`; `role` menuitem|button|option) — popover menus, outline rails, section
|
|
472
472
|
pickers. Its resting highlight has TWO meanings and they are not interchangeable:
|
|
473
473
|
**`selected`** is listbox SELECTION (emitted as `aria-selected`, and only under
|
|
@@ -490,27 +490,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
490
490
|
own controls. Pair it with `PressableRow` (surface takes the mouse + the wash, door takes
|
|
491
491
|
the keyboard, nested controls keep their own presses); the parent owns the positioning
|
|
492
492
|
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.
|
|
493
|
+
matches the surface it spans. `TableRow` is the in-kit consumer — reach for it when
|
|
494
|
+
designing a pressable surface it doesn't cover.
|
|
514
495
|
- **`pressable_highlight`** — `PressableHighlight`: the hover-wash + keyboard-focus-ring
|
|
515
496
|
`Pressable` under `MenuButton`/`Switcher`/custom pressable surfaces; its style-fn/children
|
|
516
497
|
receive `hovered` + `focusVisible`.
|
|
@@ -526,7 +507,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
526
507
|
shortcut from a raw string or `ShortcutDescriptor` (⌘B on Mac, Ctrl+B elsewhere); null on
|
|
527
508
|
small screens. `TextInputField shortcut` renders it built-in, while the field is EMPTY —
|
|
528
509
|
it hints at reaching the field, so a value replaces it (with the clear ✕, when `clearable`).
|
|
529
|
-
- **`keyboard`** — `isMac
|
|
510
|
+
- **`keyboard`** — `isMac`, `ShortcutDescriptor`, `formatShortcut` (platform-aware
|
|
530
511
|
shortcut formatting behind `ShortcutBadge`).
|
|
531
512
|
|
|
532
513
|
### Badges, status & feedback
|
|
@@ -538,7 +519,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
538
519
|
(see composition.md "Badge is for status only"). A bare `<Badge>` is `dot`.
|
|
539
520
|
- **`status_badge`** — `StatusBadge`: an enabled/disabled pulse badge (`enabled` + `label`).
|
|
540
521
|
- **`option_badge`** — `OptionBadge`: a select value as its configured colored badge.
|
|
541
|
-
- **`callout`** — `Callout
|
|
522
|
+
- **`callout`** — `Callout`, `CalloutTitle`, `CalloutText`, `CalloutActions` (`tone`
|
|
542
523
|
info|success|warning|error|neutral): inline status band — a MESSAGE (`warning`/`error`
|
|
543
524
|
are ARIA `alert`s). For a form / nested content surface use **`Inset`**, never a Callout.
|
|
544
525
|
- **`empty_state`** — `EmptyState`: centered placeholder for an empty list/filter result —
|
|
@@ -664,17 +645,30 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
664
645
|
INSIDE the control and inline error, commit on blur (Enter saves, Escape reverts) or
|
|
665
646
|
`controls="buttons"`; KEYBOARD focus on the closed view opens edit mode with the input
|
|
666
647
|
focused (type → Tab → type — see the data-entry keyboard contract), pointer focus never
|
|
667
|
-
does; **`variant: "
|
|
668
|
-
|
|
648
|
+
does; **`variant: "framed" | "bare"`** sets how much of the field's frame shows AT REST
|
|
649
|
+
(see below). Every commit registers in **`pending_commits`**, which
|
|
669
650
|
`Button`/`IconButton` wait on via **`use_gated_press`** so an action pressed in the same
|
|
670
651
|
gesture as the blur cannot read the record before the edit lands (data_entry.md § Inline
|
|
671
652
|
edit) — automatic, nothing to pass.
|
|
653
|
+
**`actions` puts VERBS on the field's surface** (an `InlineButton` — Copy, Open, Today), and
|
|
654
|
+
EVERY `Inline*` editor takes it. `trailing` is decoration only (a chevron, a spinner): it
|
|
655
|
+
renders INSIDE the press target, so a button there would be a button in a button.
|
|
656
|
+
With `actions` the field renders through ONE shell that owns the surface in BOTH modes — so
|
|
657
|
+
the verbs never move when the editor swaps to its input, the control inside goes `seamless`
|
|
658
|
+
(drawing no second box), hover is tracked on the box (react-native-web hands a parent's hover
|
|
659
|
+
to the innermost pressable, so a control-tracked border drops out as the pointer crosses a
|
|
660
|
+
verb), and the focus ring paints on the whole FIELD via focus-within, not on the value region.
|
|
661
|
+
A popover-backed editor also anchors its overlay to the shell (`anchorRef`) rather than to the
|
|
662
|
+
control — with verbs the control is narrower than the field, and an inherited width would open
|
|
663
|
+
a list too small for its own row.
|
|
664
|
+
Pass `actions` UNCONDITIONALLY and `disabled` the verb when it has nothing to act on: a slot
|
|
665
|
+
that appears once the value is non-empty resizes the field as the user types.
|
|
672
666
|
- **`inline_text_input`**, **`inline_number_input`** (`format` for currency/units),
|
|
673
667
|
**`inline_select`**, **`inline_member_select`**, **`inline_date_picker`**
|
|
674
668
|
(`format="datetime"`, `optionalTime`; keyboard focus opens the TYPED segmented `DateField`
|
|
675
669
|
— locale field order, separator advances, Alt+ArrowDown floats the calendar; click still
|
|
676
670
|
opens the calendar popover), **`inline_time_picker`** — the inline field editors (a form field by
|
|
677
|
-
|
|
671
|
+
the same editor everywhere, `variant="bare"` on a dense grid — see the surface note below);
|
|
678
672
|
`InlineSelect`/`InlineMemberSelect` render the resting value like its option —
|
|
679
673
|
`renderOptionContent` by default, `renderSelected` to override — a chip/badge at rest, not
|
|
680
674
|
just text. **`InlineSelect` is single OR multi** — pass `multi` for a tag SET (`value: T[]`,
|
|
@@ -687,38 +681,64 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
687
681
|
(`TextColor`) that colours the resting date by
|
|
688
682
|
semantic state — overdue red, soon-due amber (the caller owns the rule, e.g. a task's `dueTone`);
|
|
689
683
|
the value text IS the signal, no separate dot.
|
|
690
|
-
**
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
684
|
+
**ONE surface, ONE hover language; `variant` sets its resting WEIGHT.** An editor wears THE pill
|
|
685
|
+
surface — white, 1px border, the same one `Chip`, `ChipGroup` and a secondary `Button` wear — so
|
|
686
|
+
every control a row can hold reads as one family. Open adds the 2px ring; disabled rests flat and
|
|
687
|
+
borderless, promising no press. HOVER differs by variant, because hover must be a visible CHANGE
|
|
688
|
+
rather than a deeper line: `framed` deepens its border AND tints (zinc-200 → zinc-400 alone is a
|
|
689
|
+
shade shift on 1px), `bare` has the border ARRIVE (loud by itself — and a tint there would make a
|
|
690
|
+
hovered cell lighter than the row washing beneath it).
|
|
691
|
+
- **`variant="framed"`** (default) — the frame is already showing. Required wherever editable and
|
|
692
|
+
static values MIX (a record's `DetailTable`): there it is the only thing saying what you can change.
|
|
693
|
+
- **`variant="bare"`** — nothing at rest, the SAME border on hover. For a surface where EVERY value
|
|
694
|
+
edits (a register/`DataGrid` column, a task row), where a per-field frame states what the whole
|
|
695
|
+
surface already promises and, down a column, draws the grid twice.
|
|
696
|
+
|
|
697
|
+
It is an axis of weight, not of use: both hover and open identically. There is no `*Cell` family —
|
|
698
|
+
a data-grid cell is `<InlineSelect variant="bare" …/>`, not a `SelectCell` (naming a component for a
|
|
699
|
+
USE, and duplicating the picker stack to flip two style properties, are both things the kit forbids).
|
|
700
|
+
Hand-roll a pressable cell to match `bare`, never a background wash.
|
|
705
701
|
- **`inline_static`** — `InlineStatic`: a READ-ONLY value matching the Inline\* box metrics
|
|
706
702
|
EXACTLY (height, padding, 1px transparent border) so a non-editable field — a computed
|
|
707
703
|
total, a system ID, a synced/locked value — aligns pixel-for-pixel in the same column;
|
|
708
704
|
non-interactive, NOT a disabled input; `muted`/`tabular`/`align="right"` for a number
|
|
709
705
|
column, `weight="medium"` to emphasise a total among plain rows.
|
|
706
|
+
- **`sequence`** — `Sequence` + `SequenceItem` (+ `SEQUENCE_INSET`): an ORDERED list whose ORDER
|
|
707
|
+
IS THE DATA — a route's stops, an approval chain, a set of legs — drawn as a connected rail so
|
|
708
|
+
the sequence reads without a label saying "first"/"then". Reach for it when a thing has a
|
|
709
|
+
VARIABLE number of positions: one field per position (`Origin` / `Transfer point` /
|
|
710
|
+
`Destination`) cannot hold a second middle entry and cannot say the entries are ordered.
|
|
711
|
+
`SequenceItem` takes the content, an optional `role` (DERIVE it from index — a stored role lies
|
|
712
|
+
the moment the list is reordered) and `onMoveUp`/`onMoveDown`/`onRemove`; the controls render
|
|
713
|
+
even where they cannot act so the right edge never shifts between items. Reorder is BUTTONS,
|
|
714
|
+
not a drag handle — dragging is invisible to the keyboard and fiddly on a phone, and a
|
|
715
|
+
three-to-six position list does not need it. Indent anything that follows the list (an "Add"
|
|
716
|
+
link) by `SEQUENCE_INSET` so it lands on the rail's column. Distinct from `Timeline` (an
|
|
717
|
+
activity FEED), `Stepper` (a wizard's fixed positions) and `Pipeline` (ONE record walking
|
|
718
|
+
stages that own their controls) — this is the list a user EDITS.
|
|
719
|
+
- **`inline_button`** — `InlineButton`: a verb that sits INSIDE an inline field's surface
|
|
720
|
+
(the `Open` on a reference, a `Copy` on a value worth copying) rather than in the row's
|
|
721
|
+
trailing column — put it inside when the act is ABOUT THE VALUE, so it travels with what it
|
|
722
|
+
acts on and the trailing column stays free for row-level verbs. FILLED (`Button secondary`'s own
|
|
723
|
+
zinc-100) on the field's white surface — the contrast is a RELATIONSHIP, not a colour: invert it
|
|
724
|
+
if the resting field surface ever changes, or the verb dissolves into the value. 28px, radius 8,
|
|
725
|
+
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
|
|
726
|
+
surface underneath is itself pressable, so without it one press fires both, silently. Always
|
|
727
|
+
a WORD, never icon-only.
|
|
710
728
|
- **`detail_row`** — `DetailTable` + `DetailRow` — the record field grid. `DetailRow`:
|
|
711
729
|
label+value row for drawer/peek detail; in FORM mode (`labelWidth` set) the value column
|
|
712
730
|
FILLS the row so a stack of inline editors all span the same width + none jumps wider on
|
|
713
|
-
edit
|
|
714
|
-
|
|
731
|
+
edit. There is NO trailing slot: a verb about the VALUE goes on the field (`InlineButton` in
|
|
732
|
+
the editor's `actions`), and anything else that belongs beside the value — a status `Badge`, a
|
|
733
|
+
unit (though "kg"/"$" belong IN the value via `InlineNumberInput format`) — composes into a
|
|
734
|
+
row inside the value cell, costing only the row that wants it. The LABEL WRAPS inside its column and
|
|
715
735
|
is never clipped — a fixed `labelWidth` would otherwise ellipsize every long field name
|
|
716
736
|
("Registered business address"), and a name the reader can't finish is worse than a taller
|
|
717
737
|
row; a wrapped label's FIRST line stays level with the value's first control line while a
|
|
718
738
|
one-line label still centers on it (both modes, no prop). The FIELD-ANNOTATION vocabulary (same
|
|
719
739
|
names + meanings as `FormField`), always EXPLICIT — a row never hides guidance behind an ⓘ:
|
|
720
740
|
**`description`** = a fact / persistent guidance (muted), under the VALUE (stacked mode
|
|
721
|
-
mirrors the form order label
|
|
741
|
+
mirrors the form order label, description, control); **`warning`** = a consequence to weigh
|
|
722
742
|
before acting (amber, announced); **`error`** = field-level failure under the value, danger +
|
|
723
743
|
alert semantics (the `Inline*` editors already render their own transient save errors — don't
|
|
724
744
|
wire both); invalid/cross-field STATE = a co-located `Callout`; **`flat`**
|
|
@@ -729,19 +749,20 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
729
749
|
on two surfaces.
|
|
730
750
|
`DetailTable`: the compound parent of
|
|
731
751
|
a row STACK — `labelWidth` (default `DETAIL_LABEL_WIDTH`, 130 — THE kit's label column, and
|
|
732
|
-
a `TaskList`'s default too) / `
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
752
|
+
a `TaskList`'s default too) / `minHeight` (default 40, the inline-control grid) declared
|
|
753
|
+
ONCE + the `SPACE.md` (16) row gap — 8 was right while a field was a tint, but a stack of
|
|
754
|
+
BORDERED rows that close together fuses into one block. TWO columns, no third: the value
|
|
755
|
+
FILLS what the label leaves, so nothing one row does can narrow its neighbours' editors. RESPONSIVE with no prop: it
|
|
756
|
+
measures its own container (onLayout, not
|
|
736
757
|
the viewport — works inside a Drawer; the unmeasured first frame renders opacity-0 so the
|
|
737
758
|
first PAINT is already in the right mode) and when the columns would crush the value cell
|
|
738
759
|
it STACKS every row (the label above a full-width value row in the FormField label
|
|
739
|
-
grammar
|
|
760
|
+
grammar); raise `minValueWidth` (default 160) when a cell holds
|
|
740
761
|
MORE than one editor so the table stacks earlier. Two tables on one page share one grid by
|
|
741
|
-
repeating the same labelWidth
|
|
762
|
+
repeating the same labelWidth. Worked example:
|
|
742
763
|
[`tpl_record`](../examples/tpl_record.tsx).
|
|
743
764
|
- **`record_summary`** — `RecordSummary`: the identity band of a record detail/drawer — ONE
|
|
744
|
-
row: `title` xxl semibold tabular
|
|
765
|
+
row: `title` xxl semibold tabular, `subtitle` sm muted, `status` Badge slot, optional
|
|
745
766
|
`metric` {label,value,tone,note} pinned right, the band's ONE accent. The record's FIELDS
|
|
746
767
|
never live in the header: compose them as `DetailTable`s in the sections below. Replaces
|
|
747
768
|
hand-rolled record headers (mixed scales, several competing figures, color noise).
|
|
@@ -819,7 +840,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
819
840
|
|
|
820
841
|
`TaskStatus` takes the `CheckCircle` (omit `onChange` for a read-only ring; a PICKER list
|
|
821
842
|
puts a `CheckboxInput` here and sets `controlWidth={24}`). `TaskTitle` takes the
|
|
822
|
-
`variant="
|
|
843
|
+
`InlineTextInput variant="bare"` — or the title TEXT itself as a string child
|
|
823
844
|
(`<TaskTitle struck={done}>{label}</TaskTitle>`), which is the READ-ONLY form: the compound
|
|
824
845
|
applies the cell inset and the row's band, so a plain `<Text>` + a hand-rolled
|
|
825
846
|
`TASK_TEXT_INSET` is never needed and a long title that wraps keeps its first line beside the
|
|
@@ -831,7 +852,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
831
852
|
gutter on the first line (Delete lives BEHIND it, danger-styled and last, never a bare ✕).
|
|
832
853
|
`TaskDetail` is a FREE-FORM block under the row on the title's text edge — a chart, a table, a
|
|
833
854
|
form with its own submit — rendered only while open. The module also exports
|
|
834
|
-
**`TASK_TEXT_INSET`** (9), the inset
|
|
855
|
+
**`TASK_TEXT_INSET`** (9), the inset an inline control puts on its own text: every
|
|
835
856
|
slot above already applies it, so reach for it ONLY when a custom title NODE (not a cell
|
|
836
857
|
control) has to land on the same text edge.
|
|
837
858
|
|
|
@@ -844,7 +865,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
844
865
|
step inward per level. Declare `actionWidth={0}` when NO row in the list carries actions.
|
|
845
866
|
|
|
846
867
|
**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
|
|
868
|
+
own fields hang under it as **`TaskSubRow`** (`label`, the control, `description` /
|
|
848
869
|
`warning` / `error`), indented ONE step — the same step a nested `TaskList` takes, because
|
|
849
870
|
belonging is expressed by indentation and there is only one device for it. Label and value sit
|
|
850
871
|
ADJACENT so the eye pairs them. **The label column is the LIST's, not the row's** — one
|
|
@@ -855,7 +876,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
855
876
|
vocabulary with longer field names staggered nearly every row). The value takes the slack from
|
|
856
877
|
a readable minimum — on a phone or in a narrow drawer it drops onto its own line under the
|
|
857
878
|
label rather than ellipsizing beside it. Pass a form-variant `Inline*` editor (the default),
|
|
858
|
-
|
|
879
|
+
`variant="bare"`: a dense grid drops the resting frame.
|
|
859
880
|
|
|
860
881
|
**A TASK'S FIELD ANNOTATES EXACTLY LIKE A RECORD'S FIELD.** `description` (persistent
|
|
861
882
|
guidance), `warning` (a consequence to weigh — amber, announced) and `error` (field-level
|
|
@@ -1096,8 +1117,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1096
1117
|
`FileGrid`; the host wires `files` + `onAdd`/`onRemove` (+ optional `uploads`,
|
|
1097
1118
|
`selectTileRemove`, `labels`, `galleryLabels`, `gridMaxHeight` — cap the grid height so it
|
|
1098
1119
|
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
|
|
1120
|
+
below it**, from `FilesEditorBar` + `FilesEditorUpload`, `FilesEditorSelect`,
|
|
1121
|
+
`FilesEditorSelectAll`, `FilesEditorDownload`, `FilesEditorRemove` (+
|
|
1101
1122
|
`FilesEditorBarSpacer` to push the rest right). Each renders nothing without the handler it
|
|
1102
1123
|
needs, so withholding `onAdd`/`onRemove` IS the read-only shape — there is no `readOnly`
|
|
1103
1124
|
mode. **A HOST verb is a plain `Button`** reading **`useFilesEditorSelection()`**
|
|
@@ -1124,8 +1145,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1124
1145
|
(`FileGrid` resolves them from `LoticsLocale.fileUpload` on your behalf).
|
|
1125
1146
|
- **`file_thumbnail`** — `FileThumbnail` + `DisplayFile` + `THUMBNAIL_SIZE` /
|
|
1126
1147
|
`COMPACT_THUMBNAIL_SIZE` + `getMediaIcon`: the completed tile — the right surface per
|
|
1127
|
-
MIME: image thumbnail
|
|
1128
|
-
|
|
1148
|
+
MIME: image thumbnail, a doc tile with the `FileBadge` centered + a single-line filename
|
|
1149
|
+
, media card; `isTemplate` overlays a TMPL marker. The tile's accessible name is the
|
|
1129
1150
|
filename; pass `accessibilityLabel` to say what pressing it DOES instead. The per-surface
|
|
1130
1151
|
pieces are exported for a hand-rolled layout: `DocumentBadge` (a bare pressable badge —
|
|
1131
1152
|
its `size` is the square SLOT side, like every other tile here, and the badge is fitted to
|
|
@@ -1146,15 +1167,15 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1146
1167
|
the mark is TALLER than it is wide (26 × 32 by default), so a square slot takes the
|
|
1147
1168
|
fitted width, not the slot side (that conversion is what `DocumentBadge` does).
|
|
1148
1169
|
- **`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
|
|
1170
|
+
Word via `@lotics/docx` + Excel/CSV via `@lotics/xlsx`; the heavy engines (pdf.js,
|
|
1171
|
+
`@lotics/docx`, `@lotics/xlsx`) are LAZY (dynamic-imported, ~free until a doc of that
|
|
1151
1172
|
type is opened) and SHIP AS `@lotics/ui` deps — custom-code apps get PDF/Word/Excel
|
|
1152
1173
|
preview with ZERO extra install. Renders to canvas/DOM, never a nested iframe — works in
|
|
1153
1174
|
the sandboxed app iframe.
|
|
1154
1175
|
- **`file_preview_types`** — `PreviewLabels` / `FilePreviewProps` / `GalleryLabels` — the
|
|
1155
1176
|
shared label + prop contracts of the file-preview family; types only.
|
|
1156
1177
|
- **`file_gallery_modal`** — `FileGalleryModal`: the FULL-SCREEN viewer — toolbar (filename
|
|
1157
|
-
|
|
1178
|
+
, counter, download, optional `onOpenExternal`/`onRemove`, close-✕), prev/next, ESC,
|
|
1158
1179
|
rotate (the 90° controls FLOAT as a pill on the image); on a phone the actions collapse
|
|
1159
1180
|
into a ⋯ `ActionMenu`. `onPersistRotation`/`persisting` wire a rotation save. Driven by
|
|
1160
1181
|
`files` + `activeIndex` + `onIndexChange`.
|
|
@@ -1236,16 +1257,16 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1236
1257
|
be rendered; where a compact meter used to decorate a ranked row or proposal card, rank /
|
|
1237
1258
|
badges / severity carry the standing instead. Also: calibrated
|
|
1238
1259
|
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)
|
|
1260
|
+
- **`change_review`** — the COMPOUND review family — frame: `ChangeReview` provider/stack,
|
|
1261
|
+
`ChangeReviewHeader` (auto kept-counter over decidable entries), `ChangeReviewActions`
|
|
1241
1262
|
(the commit bar in the DialogFooter/DrawerFooter: Keep-all bottom-left (`onAcceptAll` for
|
|
1242
1263
|
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
|
|
1264
|
+
verbs, collapses to its `ChangeSummary` + Undo; no callbacks = display-only),
|
|
1265
|
+
`ChangeLabel`, `ChangeSummary`, `ChangeReasoning` (the quiet why); grammar:
|
|
1266
|
+
`ChangeFields` (the open record form) + `ChangeField` (THE field: − band, value,
|
|
1267
|
+
candidates + type-another-value, reasoning, per-field Keep/Drop, collapse),
|
|
1247
1268
|
`ChangeRecord` (THE item card: registers like a Change; tone wash + localized op word;
|
|
1248
|
-
verb level follows the decision level)
|
|
1269
|
+
verb level follows the decision level), `ChangeBand` (the raw ± band),
|
|
1249
1270
|
`ChangeValueInput` (the diff-at-rest editor); the `changeReview` locale slice.
|
|
1250
1271
|
- **`clarify`** — `Clarify` + `ClarifyOption`: the agent asks back — a borderless block
|
|
1251
1272
|
(the question text + a `ChoiceList`, no card wrapper; an optional muted `eyebrow` sits tight above
|
|
@@ -1280,17 +1301,17 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1280
1301
|
- **`sources`** — `Sources` + `SourceRef`/`SourceKind` (record | document | table | web |
|
|
1281
1302
|
knowledge): provenance chips, per-kind glyphs.
|
|
1282
1303
|
- **`finding`** — `Finding` + `FindingComparison` + `FindingSeverity`/`FindingLabels`: one
|
|
1283
|
-
ranked AI-check insight — severity word
|
|
1304
|
+
ranked AI-check insight — severity word, title, detail, `Sources`, children slot; the
|
|
1284
1305
|
expected-vs-actual body with the emphasized delta; the `finding` locale slice.
|
|
1285
1306
|
- **`result_header`** — `ResultHeader` (+ `ResultTone`): the save-direct RECEIPT's outcome
|
|
1286
1307
|
strip, on the page grid — tone mark (`ok`/`attention`/`error`/`skipped`) inline on the
|
|
1287
|
-
title row
|
|
1308
|
+
title row, outcome-first title, right-slot `action` (the open-record jump / a retry),
|
|
1288
1309
|
ONE line under (the honest `Confidence`, or the failure reason). The receipt composes:
|
|
1289
1310
|
header → the receipt lines (a `DetailTable` of spread `DetailRow`s — label left, value at
|
|
1290
1311
|
the right edge) → a `Confidence` callout naming the exact values that failed their
|
|
1291
1312
|
deterministic checks (or stating what passed). NOTHING in a
|
|
1292
1313
|
receipt edits — the RECORD is the edit surface, one press away. SEVERAL records → an
|
|
1293
|
-
attention-first register of `ListItem` rows (tone mark
|
|
1314
|
+
attention-first register of `ListItem` rows (tone mark, title, figures, needs-checking
|
|
1294
1315
|
count) pressing straight through to the records. See ai_patterns §the one law.
|
|
1295
1316
|
- **`comments_thread`** — `CommentList` + the
|
|
1296
1317
|
`ThreadComment`/`ThreadMember`/`ThreadFile` types: the record comments thread.
|