@lotics/ui 16.2.0 → 17.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +47 -0
- package/docs/catalog.md +38 -53
- package/docs/data_entry.md +54 -26
- package/docs/templates.md +7 -8
- package/examples/tpl_item_list.tsx +47 -44
- package/examples/tpl_record.tsx +70 -79
- package/examples/tpl_task_board.tsx +7 -1
- package/package.json +3 -3
- package/src/capture_row.tsx +7 -2
- package/src/check_circle.tsx +62 -18
- package/src/checkbox.tsx +7 -1
- package/src/locale.tsx +0 -24
- package/src/task.tsx +287 -0
- package/src/checklist.tsx +0 -377
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
|
|
|
@@ -747,53 +748,37 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
747
748
|
|
|
748
749
|
### Tasks & checklists
|
|
749
750
|
|
|
750
|
-
- **`check_circle`** — `CheckCircle`: the completion ring —
|
|
751
|
-
filled check when done, distinct from the square checkbox; the
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
`
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
`
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
`
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
render as compact `CheckboxInput` + label rows on the title's text edge, denser than the
|
|
782
|
-
parent (24 vs 32) and struck+muted when checked. `items: []` renders NOTHING, not even the
|
|
783
|
-
expander, so a row can pass the prop unconditionally. Expansion is controlled with
|
|
784
|
-
`expanded` + `onToggleExpanded`, or UNCONTROLLED (omit `expanded`): open on mount while any
|
|
785
|
-
step is unchecked, then the user's toggle wins — checking the last step must not yank the
|
|
786
|
-
list shut. A11y: the expander keeps ONE stable name (`countLabel(missing)` → the
|
|
787
|
-
`checklist` locale slice's `subtasks(missing)` → English) with `aria-expanded` carrying the
|
|
788
|
-
state, and the children sit in a `role="group"` named by the row's own title, so they are
|
|
789
|
-
announced UNDER their parent instead of as loose checkboxes. Never hand-roll indented child
|
|
790
|
-
rows with a bespoke chevron. `expansion` is a BLOCK slot under the row on
|
|
791
|
-
the same indent — a transient inline fill editor, a drill-down — rendered only while open
|
|
792
|
-
(`tpl_record`'s Document set rows are the worked example). SUGGESTIONS are never rows:
|
|
793
|
-
offer the commons as `SuggestionChip`s under the list. Close the list with `CaptureRow`.
|
|
794
|
-
There is deliberately NO monolithic Task component — richer task-management rows compose
|
|
795
|
-
their own anatomy directly. Hand-rolling this checkbox-row anatomy in a template is how
|
|
796
|
-
alignment drifts — the compound exists so it can't.
|
|
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
|
+
|
|
797
782
|
- **`suggestion_chip`** — `SuggestionChip`: the dismissible SUGGESTION pill — an item the
|
|
798
783
|
record could have but doesn't yet (a common task, an expected line) as a `Chip` whose
|
|
799
784
|
press MATERIALIZES it (plus glyph + label, one tuned anatomy) and whose ✕ refuses it;
|
package/docs/data_entry.md
CHANGED
|
@@ -255,34 +255,62 @@ When work crosses departments (sales → operations → accounting), the HANDOFF
|
|
|
255
255
|
changing desks — never an inbox, a notification, or a copied task. Two types:
|
|
256
256
|
|
|
257
257
|
**(A) Same entity** → a STAGE field on the shared record: sections carry OWNER dot tags, and the
|
|
258
|
-
handoff is MANAGED AS TASKS — each desk's checklist on the record. The **`
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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 row's position is computable from what it owns, DERIVE
|
|
279
|
+
it — a stored status is a second copy of what the fields already say, and two copies drift. Give
|
|
280
|
+
the parent's ring the bulk gesture where its children ARE the fact (tick it, every child ticks);
|
|
281
|
+
where the ring answers a DIFFERENT field the children do not determine — a date, say — coupling
|
|
282
|
+
them asserts something the operator never said.
|
|
283
|
+
|
|
284
|
+
**`TaskFields` holds CONTROLS.** Where exactly one field determines a row's status, that cell IS
|
|
285
|
+
the field's editor: a read-only copy above an editable control puts one value on the row twice,
|
|
286
|
+
under two labels. Everything else derived is prose in `TaskCaption`. A read-only pill parked in
|
|
287
|
+
the column sits `TASK_TEXT_INSET` left of its neighbours (a `variant="cell"` control insets its
|
|
288
|
+
text by that much; a bare `Badge` by nothing) and reads as pressable when it is not. A `Badge`
|
|
289
|
+
INSIDE a cell control is fine — it inherits that cell's inset, and the 12px its label sits in by
|
|
290
|
+
is the dot and its gap, not padding.
|
|
291
|
+
|
|
292
|
+
**A status reports what is OUTSTANDING, never what is settled.** A finished row already says so
|
|
293
|
+
twice — a full ring and its date — so naming the settled condition is a third copy of one fact.
|
|
294
|
+
The empty end is worse than redundant: "every document is in" and "nobody has looked yet" are the
|
|
295
|
+
same empty list, so a badge asserting the first states something no one established. Stay silent
|
|
296
|
+
at both ends, and separate "is it under way" (which drives the ring) from "has it anything to
|
|
297
|
+
say" (which drives the caption) — fusing them forces every pending state to invent a label.
|
|
298
|
+
|
|
299
|
+
`CheckCircle` has THREE positions (`state="none" | "partial" | "done"`), and `partial` is not
|
|
300
|
+
optional dressing: a step whose vocabulary reads "No portal account → Account created → Filed
|
|
301
|
+
online" is neither finished nor untouched in the middle, and a binary ring calls it untouched — in
|
|
302
|
+
the gutter column, which is the fastest scan on the surface. Its CLICK stays binary (finish /
|
|
303
|
+
reopen) so the ring means one thing everywhere; `partial` is reached through whatever NAMES it —
|
|
304
|
+
an `InlineSelect` status cell in that step's own words, or children ticking off — never by cycling
|
|
305
|
+
the ring. Keep the ring MONOCHROME and let colour live in the status cell, or the row double-codes
|
|
306
|
+
one fact. A NEW record starts with an EMPTY checklist — tasks truly
|
|
279
307
|
vary. The commons split in two: MANDATORY tasks are seeded by the app (a workflow on create, per
|
|
280
308
|
record type) — no human types them; common-but-OPTIONAL tasks appear as `SuggestionChip`s under
|
|
281
309
|
the list — a PILL, never a row, so a suggestion can't be mistaken for a task (tap = materialize,
|
|
282
310
|
✕ = dismiss for this record; already-present labels filter out; suggestions never count in
|
|
283
311
|
done/total and pause while a filter narrows the view) — the same suggestion grammar as the billing
|
|
284
|
-
Standard pill.
|
|
285
|
-
never a bare ✕ a stray tap can hit) completes the
|
|
312
|
+
Standard pill. `TaskActions` holds an `ActionMenu` (Delete lives BEHIND it, danger-styled and last
|
|
313
|
+
— never a bare ✕ a stray tap can hit), which completes the list's CRUD.
|
|
286
314
|
|
|
287
315
|
Open tasks INFORM the handoff, they NEVER block it: the CTA stays enabled, the count warns, and
|
|
288
316
|
open tasks carry over. The handoff CTA opens a DIALOG for the receiving desk (assignee
|
|
@@ -290,9 +318,9 @@ open tasks carry over. The handoff CTA opens a DIALOG for the receiving desk (as
|
|
|
290
318
|
the record left this register.
|
|
291
319
|
|
|
292
320
|
Tasks PEEK from the register: the done/total column is a pressable compact-`ProgressBar` trigger
|
|
293
|
-
whose popover holds the same `
|
|
294
|
-
`
|
|
295
|
-
|
|
321
|
+
whose popover holds the same `Task` list — every `TaskItem`'s ring `TaskStatus` · struck
|
|
322
|
+
`InlineTextInput` `TaskTitle` (flex) · quick-reassign `InlineMemberSelect` in `TaskFields`, which
|
|
323
|
+
takes its own line at popover width — no expandable rows (tags/files depth is
|
|
296
324
|
the Task list template's lesson, not the peek's); the popover body is `PopoverContent`'s own
|
|
297
325
|
ScrollView (`disableBodyScroll` is ONLY for children that manage their own scroll, like
|
|
298
326
|
`OptionList`). The DRAWER carries a real Tasks SECTION in the Record template's shape (heading +
|
package/docs/templates.md
CHANGED
|
@@ -284,9 +284,9 @@ 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
|
|
@@ -353,13 +353,12 @@ billing, and quick-capture templates. Top → bottom:
|
|
|
353
353
|
REPLACES the set — derived paperwork, never intake; the desk register above keeps what
|
|
354
354
|
arrived. **READINESS**: a form declares the RECORD FIELDS it reads (`needs`); the fields'
|
|
355
355
|
HOME stays their DATA section (the colocation law) with descriptions naming their
|
|
356
|
-
consumers ("printed on the delivery note"). The picker rows ride `
|
|
357
|
-
|
|
358
|
-
geometry and the
|
|
359
|
-
|
|
360
|
-
`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
|
|
361
360
|
gaps show WITHOUT checking, warning once checked because it now blocks). The FIX sits
|
|
362
|
-
right under it in the row's `
|
|
361
|
+
right under it in the row's `TaskDetail` — an `Inset` (a FORM surface, NEVER a `Callout`:
|
|
363
362
|
a warning Callout is an ARIA `alert`, wrong around a form) with a `FormTextInput` per gap
|
|
364
363
|
(label + the consumer hint as `description`) and a "Save fields" (secondary — the section
|
|
365
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
|
|