@lotics/ui 16.1.0 → 17.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/MIGRATION.md +47 -0
- package/docs/catalog.md +51 -30
- package/docs/composition.md +3 -1
- package/docs/data_entry.md +41 -15
- package/docs/templates.md +13 -9
- package/examples/tpl_item_list.tsx +47 -44
- package/examples/tpl_record.tsx +87 -50
- package/examples/tpl_task_board.tsx +7 -1
- package/package.json +2 -2
- package/src/capture_row.tsx +7 -2
- package/src/check_circle.tsx +62 -18
- package/src/date_picker.tsx +21 -0
- package/src/detail_row.tsx +14 -4
- package/src/form_date_picker.tsx +3 -1
- package/src/form_field.tsx +4 -1
- package/src/icon.tsx +2 -0
- package/src/inline_edit.tsx +35 -25
- package/src/locale.tsx +2 -2
- package/src/task.tsx +285 -0
- package/src/checklist.tsx +0 -119
package/MIGRATION.md
CHANGED
|
@@ -4,6 +4,53 @@ Breaking changes, newest first — normally per major, plus the rare minor that
|
|
|
4
4
|
anyway (recorded under its exact version). The current contract lives in `AGENTS.md` + `docs/`;
|
|
5
5
|
this file exists only to move an app from one release to the next.
|
|
6
6
|
|
|
7
|
+
## v17 from 16.x
|
|
8
|
+
|
|
9
|
+
**`Checklist` / `ChecklistRow` are DELETED.** They are replaced by the `Task` compound
|
|
10
|
+
(`@lotics/ui/task`), which composes the anatomy instead of configuring it:
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
<TaskList> {/* was <Checklist trailingWidth={152}> */}
|
|
14
|
+
<TaskItem> {/* was <ChecklistRow …> */}
|
|
15
|
+
<TaskStatus><CheckCircle …/></TaskStatus> {/* was control= */}
|
|
16
|
+
<TaskTitle><InlineTextInput …/></TaskTitle> {/* was children */}
|
|
17
|
+
<TaskFields><InlineDatePicker …/></TaskFields> {/* was trailing= OR meta= */}
|
|
18
|
+
<TaskActions><ActionMenu …/></TaskActions> {/* was menu= */}
|
|
19
|
+
<TaskDetail>…</TaskDetail> {/* was expansion= */}
|
|
20
|
+
<TaskList>…</TaskList> {/* was subtasks= */}
|
|
21
|
+
</TaskItem>
|
|
22
|
+
</TaskList>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Three of those are behaviour changes, not renames:
|
|
26
|
+
|
|
27
|
+
- **`trailing` and `meta` collapse into `TaskFields`.** They were mutually exclusive by type,
|
|
28
|
+
so an author had to decide "wide surface" or "narrow" while writing the JSX and was wrong on
|
|
29
|
+
the other one. `TaskFields` sits inline while the row has room and takes its own line when it
|
|
30
|
+
does not — intrinsically, with no viewport query, so it is also right inside a drawer.
|
|
31
|
+
- **`subtasks` becomes a NESTED `TaskList`.** The old `{key, label, checked, onToggle}[]` meant
|
|
32
|
+
a child could never carry a due date, a menu or children of its own. A subtask is now a task.
|
|
33
|
+
What you lose is the built-in chevron and remaining-count: collapsing is the app's call now —
|
|
34
|
+
hold a boolean and render the nested list or don't.
|
|
35
|
+
- **`note` is GONE, with no replacement.** A task's free text is its title, a descriptor
|
|
36
|
+
(`TaskFields`) or detail (`TaskDetail`); a fourth place to write invited writing it twice.
|
|
37
|
+
Move the value into whichever of those it actually is, or drop it.
|
|
38
|
+
|
|
39
|
+
Rows are now **44px minimum** (`density="dense"` opts down to 32 for a pointer-driven
|
|
40
|
+
register, set once on the list). The old row was 32 and the old subtask row 24 — under the
|
|
41
|
+
minimum tap target. `controlWidth` moves from `Checklist` to `TaskList` unchanged; the
|
|
42
|
+
`checklist` locale slice is deleted (its strings were all note/subtask chrome).
|
|
43
|
+
|
|
44
|
+
**`CheckCircle`'s `checked: boolean` is replaced by `state: "none" | "partial" | "done"`.**
|
|
45
|
+
Migrate with `checked={x}` → `state={x ? "done" : "none"}`; `onChange` is untouched (still
|
|
46
|
+
`(done: boolean) => void`, since the ring's GESTURE stays binary — finish or reopen). The new
|
|
47
|
+
middle position exists because a binary ring has to lie about it: a task at "doing", or a step
|
|
48
|
+
whose vocabulary reads "No portal account → Account created → Filed online", is neither finished
|
|
49
|
+
nor untouched in the middle, and an empty ring reports untouched — in the gutter column that is
|
|
50
|
+
the fastest scan on a task surface. Wherever a row already carries a three-position status, feed
|
|
51
|
+
it through (`state={s.status === "done" ? "done" : s.status === "doing" ? "partial" : "none"}`)
|
|
52
|
+
rather than leaving the ring binary.
|
|
53
|
+
|
|
7
54
|
## v16 from 15.x
|
|
8
55
|
|
|
9
56
|
**`Confidence` IS now a callout with a REQUIRED basis** — `children` (what was checked and
|
package/docs/catalog.md
CHANGED
|
@@ -105,12 +105,13 @@ summary is `Ledger`.
|
|
|
105
105
|
### Tasks / to-dos — pick by ALTITUDE
|
|
106
106
|
|
|
107
107
|
A task-management PAGE (many tasks, grouping, filters, expandable rows) is COMPOSITION —
|
|
108
|
-
|
|
109
|
-
(`InlineDatePicker`/`InlineMemberSelect`/`InlineSelect
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
`SuggestionChip` commons + `CaptureRow`
|
|
113
|
-
[`tpl_item_list`](../examples/tpl_item_list.tsx)
|
|
108
|
+
the `Task` compound owns the row — `TaskStatus` (a `CheckCircle`), `TaskTitle` (a struck
|
|
109
|
+
`InlineTextInput`), `TaskFields` (your `InlineDatePicker`/`InlineMemberSelect`/`InlineSelect`
|
|
110
|
+
cells, all `variant="cell"`), `TaskActions`. The SAME composition serves a record's 5–8-task
|
|
111
|
+
drawer checklist and a grouped desk board, because `TaskFields` reflows instead of being
|
|
112
|
+
authored per surface; add `SuggestionChip` commons + `CaptureRow` for the checklist case. See
|
|
113
|
+
[`tpl_record`](../examples/tpl_record.tsx) / [`tpl_item_list`](../examples/tpl_item_list.tsx),
|
|
114
|
+
and [`tpl_task_board`](../examples/tpl_task_board.tsx) for columns.
|
|
114
115
|
|
|
115
116
|
### Tabular data — pick by SCALE + intent
|
|
116
117
|
|
|
@@ -571,7 +572,10 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
571
572
|
select.
|
|
572
573
|
- **`form_date_picker`** — `FormDatePicker`: `FormField` wrapping a `DatePicker` — one
|
|
573
574
|
labeled date field. (Omits `style`; for a grid cell use a bare `FormField style={half}`
|
|
574
|
-
around `DatePicker`.)
|
|
575
|
+
around `DatePicker`.) Defaults the picker's `accessibilityLabel` to its own visible label
|
|
576
|
+
— the FormField label text is not otherwise associated with the segment inputs. A bare
|
|
577
|
+
`DatePicker` outside a FormField must be given `accessibilityLabel` explicitly; range
|
|
578
|
+
formats suffix each half with the locale's start/end-date names.
|
|
575
579
|
- **`switch_button`** — `SwitchButton`: the full-ROW toggle — a `PressableHighlight` row
|
|
576
580
|
(optional icon + medium title left, `Switch` pinned right) where the whole row IS the
|
|
577
581
|
switch (`accessibilityRole="switch"`, the inner Switch read-only). The settings-panel/menu
|
|
@@ -605,7 +609,10 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
605
609
|
### Inline editing & record surfaces
|
|
606
610
|
|
|
607
611
|
- **`inline_edit`** — `useInlineEdit` + `InlineEditView` / `InlineEditFrame` +
|
|
608
|
-
`
|
|
612
|
+
`useInlineEditFocusRestore` (returns focus to the RESTING control when a KEYBOARD close drops
|
|
613
|
+
it to `<body>` — what an editor with its own resting shape, e.g. the checklist `note` line,
|
|
614
|
+
reaches for instead of the frame) + `INLINE_CONTROL_HEIGHT` (40) +
|
|
615
|
+
`inlineValueTextStyle`: the engine custom inline editors
|
|
609
616
|
join through — a view ⇄ edit toggle, a draft buffer, async `onSave` with the spinner
|
|
610
617
|
INSIDE the control and inline error, commit on blur (Enter saves, Escape reverts) or
|
|
611
618
|
`controls="buttons"`; KEYBOARD focus on the closed view opens edit mode with the input
|
|
@@ -657,7 +664,11 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
657
664
|
label+value row for drawer/peek detail; in FORM mode (`labelWidth` set) the value column
|
|
658
665
|
FILLS the row so a stack of inline editors all span the same width + none jumps wider on
|
|
659
666
|
edit; optional `trailing` slot renders a right-side action/badge after the value (units
|
|
660
|
-
belong IN the value via `InlineNumberInput format`). The
|
|
667
|
+
belong IN the value via `InlineNumberInput format`). The LABEL WRAPS inside its column and
|
|
668
|
+
is never clipped — a fixed `labelWidth` would otherwise ellipsize every long field name
|
|
669
|
+
("Registered business address"), and a name the reader can't finish is worse than a taller
|
|
670
|
+
row; a wrapped label's FIRST line stays level with the value's first control line while a
|
|
671
|
+
one-line label still centers on it (both modes, no prop). The FIELD-ANNOTATION vocabulary (same
|
|
661
672
|
names + meanings as `FormField`), always EXPLICIT — a row never hides guidance behind an ⓘ:
|
|
662
673
|
**`description`** = a fact / persistent guidance (muted), under the VALUE (stacked mode
|
|
663
674
|
mirrors the form order label · description · control); **`warning`** = a consequence to weigh
|
|
@@ -737,27 +748,37 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
737
748
|
|
|
738
749
|
### Tasks & checklists
|
|
739
750
|
|
|
740
|
-
- **`check_circle`** — `CheckCircle`: the completion ring —
|
|
741
|
-
filled check when done, distinct from the square checkbox; the
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
`
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
`
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
751
|
+
- **`check_circle`** — `CheckCircle`: the completion ring — `state="none" | "partial" | "done"`,
|
|
752
|
+
springing to a filled check when done, distinct from the square checkbox; the
|
|
753
|
+
task/to-do/checklist control. `partial` half-fills it, for the row that is underway but not
|
|
754
|
+
finished (a task at "doing", a step whose children are half ticked) — a binary ring reports
|
|
755
|
+
that row as untouched, in the column readers scan fastest. The CLICK stays binary
|
|
756
|
+
(`onChange(done)`) so the ring means one thing everywhere; `partial` is reached through
|
|
757
|
+
whatever NAMES it — a status cell, or children ticking off — never by cycling the ring. Keep
|
|
758
|
+
it monochrome and let colour live in the status cell.
|
|
759
|
+
- **`task`** — `TaskList` + `TaskItem` + `TaskStatus` / `TaskTitle` / `TaskFields` /
|
|
760
|
+
`TaskActions` / `TaskDetail` — the task COMPOUND, for anything from a 5-item drawer
|
|
761
|
+
checklist to a grouped desk board. `TaskList` owns geometry only (the `controlWidth`
|
|
762
|
+
column every title aligns on, the indent, and `density`: `comfortable` = a 44px minimum
|
|
763
|
+
tap target, `dense` = 32 for a pointer-driven register). Everything else is composed, and
|
|
764
|
+
JSX order is screen order — nothing inspects child types.
|
|
765
|
+
|
|
766
|
+
`TaskStatus` takes the `CheckCircle` (omit `onChange` for a read-only ring; a PICKER list
|
|
767
|
+
puts a `CheckboxInput` here and sets `controlWidth={24}`). `TaskTitle` takes the struck
|
|
768
|
+
transparent `InlineTextInput` or plain `Text`, and claims a readable minimum width — which
|
|
769
|
+
is what makes the rest reflow. `TaskFields` is the descriptor cluster (due, assignee,
|
|
770
|
+
chips): it sits inline after the title while the row has room and takes its own line when
|
|
771
|
+
it does not. That is intrinsic, so the SAME JSX is right on a record page and in a drawer —
|
|
772
|
+
never author two variants, and never reach for a viewport query. `TaskActions` carries the
|
|
773
|
+
row's ⋯ `ActionMenu`; Delete lives BEHIND it, danger-styled and last, never a bare ✕.
|
|
774
|
+
`TaskDetail` is a block under the row on the title's text edge — an inline fill editor, a
|
|
775
|
+
drill-down — rendered only while open.
|
|
776
|
+
|
|
777
|
+
**Subtasks are a nested `TaskList`**, so a step IS a task: give one a due date, a menu or
|
|
778
|
+
children of its own and it works. Collapsing belongs to the app — hold a boolean and render
|
|
779
|
+
the nested list or don't. There is deliberately **no note slot**: a task's free text is its
|
|
780
|
+
title, a descriptor, or detail, and a fourth place to write invited writing it twice.
|
|
781
|
+
|
|
761
782
|
- **`suggestion_chip`** — `SuggestionChip`: the dismissible SUGGESTION pill — an item the
|
|
762
783
|
record could have but doesn't yet (a common task, an expected line) as a `Chip` whose
|
|
763
784
|
press MATERIALIZES it (plus glyph + label, one tuned anatomy) and whose ✕ refuses it;
|
package/docs/composition.md
CHANGED
|
@@ -440,7 +440,9 @@ gate's SCOPE, once** — never prose beside the button, never revealed only on p
|
|
|
440
440
|
fact or consequence, never widget mechanics, never a second sentence; `warning` marks a
|
|
441
441
|
consequence, not decoration. Rows share ONE
|
|
442
442
|
alignment law: the row top-aligns and label · control · trailing each center within the
|
|
443
|
-
first control line — a tall value block never drags the label
|
|
443
|
+
first control line — a tall value block never drags the label, and a LABEL too long for its
|
|
444
|
+
column WRAPS (never clipped, never abbreviated to fit) with its first line still level with
|
|
445
|
+
the value. A FLAT value row
|
|
444
446
|
(`InlineStatic`, plain `Text`) sets **`DetailRow flat`**: the annotation tucks up by the
|
|
445
447
|
control band's slack so the perceived gap under the text equals the gap under a chip —
|
|
446
448
|
the band's invisible bottom half must never read as a hole above the description.
|
package/docs/data_entry.md
CHANGED
|
@@ -157,7 +157,9 @@ narrower than its neighbours'. Rows share ONE alignment law: the row top-aligns
|
|
|
157
157
|
control · trailing each center within the first control line — annotations (the three tones
|
|
158
158
|
under the value: `description` muted · `warning` amber · `error` danger, each indented to the
|
|
159
159
|
chip's 8px text inset) grow the row downward without dragging the label. The full annotation
|
|
160
|
-
vocabulary lives in composition.md §Field annotations.
|
|
160
|
+
vocabulary lives in composition.md §Field annotations. A long LABEL wraps inside its column
|
|
161
|
+
instead of clipping — never shorten a field name to fit the label width, and never reach for a
|
|
162
|
+
tooltip to recover the tail; the wrapped label's first line stays level with the value.
|
|
161
163
|
|
|
162
164
|
### The editability affordance
|
|
163
165
|
|
|
@@ -253,22 +255,46 @@ When work crosses departments (sales → operations → accounting), the HANDOFF
|
|
|
253
255
|
changing desks — never an inbox, a notification, or a copied task. Two types:
|
|
254
256
|
|
|
255
257
|
**(A) Same entity** → a STAGE field on the shared record: sections carry OWNER dot tags, and the
|
|
256
|
-
handoff is MANAGED AS TASKS — each desk's checklist on the record. The **`
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
258
|
+
handoff is MANAGED AS TASKS — each desk's checklist on the record. The **`Task`** compound owns
|
|
259
|
+
the list geometry (the control column, the indent, and `density` — 44px targets by default) —
|
|
260
|
+
compose it, never hand-roll the row. A **`TaskItem`** carries a `TaskStatus` (a `CheckCircle` — a
|
|
261
|
+
read-only ring when it has no `onChange`), a `TaskTitle` (a struck transparent `InlineTextInput`,
|
|
262
|
+
or plain `Text`), a `TaskFields` cluster (the per-task assignee `InlineMemberSelect`, the due
|
|
263
|
+
date) and a `TaskActions` ⋯ menu — never a decorative progress strip. `TaskFields` sits inline
|
|
264
|
+
while the row has room and takes its own line when it does not, so the drawer and the record page
|
|
265
|
+
run the SAME composition. `CaptureRow` closes the list as its add-affordance.
|
|
266
|
+
|
|
267
|
+
A task's own free text is its TITLE, a **`TaskCaption`** or a **`TaskDetail`** — there is no
|
|
268
|
+
fourth place to write, because a fourth place gets written in twice. `TaskCaption` is the row's
|
|
269
|
+
state IN WORDS ("3 of 5 papers received", "waiting on the yard") on its own line under the title;
|
|
270
|
+
it is not a `TaskFields` cell, because a field is a VALUE the reader scans down a column and a
|
|
271
|
+
caption is a SENTENCE about one row — merging them pushes prose into the value column, where it
|
|
272
|
+
aligns with nothing.
|
|
273
|
+
|
|
274
|
+
**Subtasks are TASKS** — nest a `TaskList` inside the `TaskItem`, and the child carries a due
|
|
275
|
+
date, a menu and children of its own. There is no separate subtask shape to outgrow, and no
|
|
276
|
+
built-in chevron: collapsing is the app's call (hold a boolean, render the nested list or don't).
|
|
277
|
+
|
|
278
|
+
**State flows from the LEAVES.** Where a parent's position is computable from its children, DERIVE
|
|
279
|
+
it and render the parent's status read-only — an editable copy lets a row disagree with its own
|
|
280
|
+
children, and makes the operator enter one fact twice. Give the parent's ring the bulk gesture
|
|
281
|
+
instead (tick it, every child ticks). A parent with nothing to derive from is set by hand.
|
|
282
|
+
|
|
283
|
+
`CheckCircle` has THREE positions (`state="none" | "partial" | "done"`), and `partial` is not
|
|
284
|
+
optional dressing: a step whose vocabulary reads "No portal account → Account created → Filed
|
|
285
|
+
online" is neither finished nor untouched in the middle, and a binary ring calls it untouched — in
|
|
286
|
+
the gutter column, which is the fastest scan on the surface. Its CLICK stays binary (finish /
|
|
287
|
+
reopen) so the ring means one thing everywhere; `partial` is reached through whatever NAMES it —
|
|
288
|
+
an `InlineSelect` status cell in that step's own words, or children ticking off — never by cycling
|
|
289
|
+
the ring. Keep the ring MONOCHROME and let colour live in the status cell, or the row double-codes
|
|
290
|
+
one fact. A NEW record starts with an EMPTY checklist — tasks truly
|
|
265
291
|
vary. The commons split in two: MANDATORY tasks are seeded by the app (a workflow on create, per
|
|
266
292
|
record type) — no human types them; common-but-OPTIONAL tasks appear as `SuggestionChip`s under
|
|
267
293
|
the list — a PILL, never a row, so a suggestion can't be mistaken for a task (tap = materialize,
|
|
268
294
|
✕ = dismiss for this record; already-present labels filter out; suggestions never count in
|
|
269
295
|
done/total and pause while a filter narrows the view) — the same suggestion grammar as the billing
|
|
270
|
-
Standard pill.
|
|
271
|
-
never a bare ✕ a stray tap can hit) completes the
|
|
296
|
+
Standard pill. `TaskActions` holds an `ActionMenu` (Delete lives BEHIND it, danger-styled and last
|
|
297
|
+
— never a bare ✕ a stray tap can hit), which completes the list's CRUD.
|
|
272
298
|
|
|
273
299
|
Open tasks INFORM the handoff, they NEVER block it: the CTA stays enabled, the count warns, and
|
|
274
300
|
open tasks carry over. The handoff CTA opens a DIALOG for the receiving desk (assignee
|
|
@@ -276,9 +302,9 @@ open tasks carry over. The handoff CTA opens a DIALOG for the receiving desk (as
|
|
|
276
302
|
the record left this register.
|
|
277
303
|
|
|
278
304
|
Tasks PEEK from the register: the done/total column is a pressable compact-`ProgressBar` trigger
|
|
279
|
-
whose popover holds the same `
|
|
280
|
-
`
|
|
281
|
-
|
|
305
|
+
whose popover holds the same `Task` list — every `TaskItem`'s ring `TaskStatus` · struck
|
|
306
|
+
`InlineTextInput` `TaskTitle` (flex) · quick-reassign `InlineMemberSelect` in `TaskFields`, which
|
|
307
|
+
takes its own line at popover width — no expandable rows (tags/files depth is
|
|
282
308
|
the Task list template's lesson, not the peek's); the popover body is `PopoverContent`'s own
|
|
283
309
|
ScrollView (`disableBodyScroll` is ONLY for children that manage their own scroll, like
|
|
284
310
|
`OptionList`). The DRAWER carries a real Tasks SECTION in the Record template's shape (heading +
|
package/docs/templates.md
CHANGED
|
@@ -284,15 +284,20 @@ billing, and quick-capture templates. Top → bottom:
|
|
|
284
284
|
file-capable edit form injected via `renderEditForm`) + THE kit `Composer` (attach via
|
|
285
285
|
`actionsButton`, staged files in its `files` slot) — never a bespoke comment box.
|
|
286
286
|
- **Tasks** — the full task-list grammar: clearable Group-by `FilterChip` (+ assignee/status
|
|
287
|
-
chips), the `CaptureRow` on top, `
|
|
287
|
+
chips), the `CaptureRow` on top, `TaskList`/`TaskItem` rows and per-row `TaskActions` menus,
|
|
288
288
|
suggestions as `SuggestionChip`s; groups divide via the `SubsectionStack` beat with plain
|
|
289
|
-
text heads (no dot badges). Each row's
|
|
289
|
+
text heads (no dot badges). Each row's `TaskFields` carries **due + assignee as COMPACT grid CELLS** —
|
|
290
290
|
the SAME `InlineDatePicker` / `InlineMemberSelect` used in the General section, in **`variant="cell"`**
|
|
291
291
|
(transparent rest + wash on hover, no glyph/chevron): the due a urgency-coloured date (`tone`
|
|
292
292
|
= `dueTone` — red past due, amber ≤3d, else muted), the assignee an **`avatarOnly`** bare avatar
|
|
293
293
|
(dashed add-ghost when unset), sized to the list's `trailingWidth` so the title never overlaps.
|
|
294
294
|
(Contrast the General section's dates/assignee, the same editors in the default `variant="form"`
|
|
295
|
-
— a zinc-50 chip + hover-border + the full member chip.)
|
|
295
|
+
— a zinc-50 chip + hover-border + the full member chip.) Two rows carry a **`note`** — the kit's
|
|
296
|
+
per-row muted second line (press to edit, save empty to remove); the rest show its add button.
|
|
297
|
+
ONE row ("Book the carrier") carries **`subtasks`** — four child ticks behind the kit's chevron +
|
|
298
|
+
remaining-count expander, open because work is left; every other row passes an EMPTY list, which
|
|
299
|
+
renders nothing at all. It is the line to copy: a booking's steps carry only a tick, so they stay
|
|
300
|
+
child rows — anything needing its own note/due/assignee would be a task instead.
|
|
296
301
|
- **Documents** — the Agents "Document desk" pattern (this template is its worked example —
|
|
297
302
|
see the Agents chapter below): the register `Table` (search · Add files) whose selection
|
|
298
303
|
feeds the `FloatingActionBar` → ONE "Use AI" fork (extract / cross-check / edit-with-AI).
|
|
@@ -348,13 +353,12 @@ billing, and quick-capture templates. Top → bottom:
|
|
|
348
353
|
REPLACES the set — derived paperwork, never intake; the desk register above keeps what
|
|
349
354
|
arrived. **READINESS**: a form declares the RECORD FIELDS it reads (`needs`); the fields'
|
|
350
355
|
HOME stays their DATA section (the colocation law) with descriptions naming their
|
|
351
|
-
consumers ("printed on the delivery note"). The picker rows ride `
|
|
352
|
-
|
|
353
|
-
geometry and the
|
|
354
|
-
|
|
355
|
-
`meta` line: 13px `circle-alert` + "Needs: …" the actual field labels — muted at rest so
|
|
356
|
+
consumers ("printed on the delivery note"). The picker rows ride `TaskList`/`TaskItem`
|
|
357
|
+
(`controlWidth={24}` for the `CheckboxInput` in `TaskStatus`) — the compound owns the row
|
|
358
|
+
geometry and the indent; hand-rolling the anatomy is how alignment drifts. Warning and fix are CO-LOCATED on the row. The mark NAMES what's missing (the
|
|
359
|
+
`TaskFields` line: 13px `circle-alert` + "Needs: …" the actual field labels — muted at rest so
|
|
356
360
|
gaps show WITHOUT checking, warning once checked because it now blocks). The FIX sits
|
|
357
|
-
right under it in the row's `
|
|
361
|
+
right under it in the row's `TaskDetail` — an `Inset` (a FORM surface, NEVER a `Callout`:
|
|
358
362
|
a warning Callout is an ARIA `alert`, wrong around a form) with a `FormTextInput` per gap
|
|
359
363
|
(label + the consumer hint as `description`) and a "Save fields" (secondary — the section
|
|
360
364
|
keeps ONE primary, disabled while every draft is empty). Save writes the record fields
|
|
@@ -21,7 +21,7 @@ import { IconButton } from "@lotics/ui/icon_button";
|
|
|
21
21
|
import { ListItem } from "@lotics/ui/list_item";
|
|
22
22
|
import { PressableHighlight } from "@lotics/ui/pressable_highlight";
|
|
23
23
|
import { CaptureRow } from "@lotics/ui/capture_row";
|
|
24
|
-
import {
|
|
24
|
+
import { TaskActions, TaskFields, TaskItem, TaskList, TaskStatus, TaskTitle } from "@lotics/ui/task";
|
|
25
25
|
import { SuggestionChip } from "@lotics/ui/suggestion_chip";
|
|
26
26
|
import { Screen, ScreenRouter, useScreenRouter } from "@lotics/ui/screen_router";
|
|
27
27
|
import { SummaryLine } from "@lotics/ui/summary_line";
|
|
@@ -203,49 +203,52 @@ function TasksChecklist({ tasks, onChange, suggestions, onDismissSuggestion }: {
|
|
|
203
203
|
const addSuggested = (label: string) => onChange([...tasks, { id: newTaskId(), label, done: false, assignee: null, due: "" }]);
|
|
204
204
|
const pending = suggestions.filter((l) => !tasks.some((t) => t.label === l));
|
|
205
205
|
return (
|
|
206
|
-
<
|
|
207
|
-
{/*
|
|
208
|
-
|
|
206
|
+
<TaskList>
|
|
207
|
+
{/* ONE anatomy for every surface. The assignee and due editors sit inline
|
|
208
|
+
after the title when the row has room, and take their own line when it
|
|
209
|
+
does not — a drawer, a peek, a phone. The template no longer picks. */}
|
|
209
210
|
{tasks.map((t) => (
|
|
210
|
-
<
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
211
|
+
<TaskItem key={t.id}>
|
|
212
|
+
<TaskStatus>
|
|
213
|
+
<CheckCircle state={t.done ? "done" : "none"} onChange={(on) => patch(t.id, { done: on })} accessibilityLabel={t.label} />
|
|
214
|
+
</TaskStatus>
|
|
215
|
+
<TaskTitle>
|
|
216
|
+
<InlineTextInput
|
|
217
|
+
variant="cell"
|
|
218
|
+
value={t.label}
|
|
219
|
+
onSave={(v) => patch(t.id, { label: v })}
|
|
220
|
+
struck={t.done}
|
|
221
|
+
accessibilityLabel="Task title"
|
|
222
|
+
/>
|
|
223
|
+
</TaskTitle>
|
|
224
|
+
<TaskFields>
|
|
225
|
+
<View style={{ width: 150 }}>
|
|
226
|
+
<InlineMemberSelect
|
|
227
|
+
variant="cell"
|
|
228
|
+
members={MEMBERS}
|
|
229
|
+
value={t.assignee}
|
|
230
|
+
onSave={(m) => patch(t.id, { assignee: m })}
|
|
231
|
+
placeholder="Assign…"
|
|
232
|
+
accessibilityLabel={`Assignee · ${t.label}`}
|
|
233
|
+
/>
|
|
234
|
+
</View>
|
|
235
|
+
<View style={{ width: 130 }}>
|
|
236
|
+
<InlineDatePicker
|
|
237
|
+
variant="cell"
|
|
238
|
+
value={t.due}
|
|
239
|
+
onSave={(v) => patch(t.id, { due: v })}
|
|
240
|
+
placeholder="Set due…"
|
|
241
|
+
accessibilityLabel={`Due · ${t.label}`}
|
|
242
|
+
/>
|
|
243
|
+
</View>
|
|
244
|
+
</TaskFields>
|
|
245
|
+
<TaskActions>
|
|
246
|
+
<ActionMenu
|
|
247
|
+
items={[{ key: "xoa", label: "Delete task", icon: "trash", danger: true, onPress: () => onChange(tasks.filter((x) => x.id !== t.id)) }]}
|
|
248
|
+
accessibilityLabel={`Task options: ${t.label}`}
|
|
249
|
+
/>
|
|
250
|
+
</TaskActions>
|
|
251
|
+
</TaskItem>
|
|
249
252
|
))}
|
|
250
253
|
{/* SUGGESTIONS are pills, not rows — a chip can't be mistaken for a
|
|
251
254
|
task. Tap = materialize; ✕ = dismiss it for this record. */}
|
|
@@ -257,7 +260,7 @@ function TasksChecklist({ tasks, onChange, suggestions, onDismissSuggestion }: {
|
|
|
257
260
|
</View>
|
|
258
261
|
) : null}
|
|
259
262
|
<CaptureRow value={draft} onChangeText={setDraft} onSubmit={add} placeholder="Add a task…" accessibilityLabel="Add a task" />
|
|
260
|
-
</
|
|
263
|
+
</TaskList>
|
|
261
264
|
);
|
|
262
265
|
}
|
|
263
266
|
|