@lotics/ui 5.7.0 → 5.9.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 +102 -42
- package/examples/tpl_approvals.tsx +10 -10
- package/examples/tpl_convert.tsx +11 -23
- package/examples/tpl_crm_desk.tsx +3 -3
- package/examples/tpl_dieline.tsx +7 -6
- package/examples/tpl_directory.tsx +3 -3
- package/examples/tpl_dossier.tsx +7 -7
- package/examples/tpl_draft.tsx +4 -4
- package/examples/tpl_extract.tsx +7 -7
- package/examples/tpl_inventory.tsx +3 -3
- package/examples/tpl_lookup.tsx +4 -3
- package/examples/tpl_match.tsx +1 -1
- package/examples/tpl_order.tsx +1 -1
- package/examples/tpl_ratedesk.tsx +12 -12
- package/examples/tpl_record.tsx +1 -1
- package/examples/tpl_report.tsx +3 -3
- package/examples/tpl_stock.tsx +3 -3
- package/examples/tpl_triage.tsx +1 -1
- package/package.json +12 -4
- package/src/agent_progress.tsx +14 -12
- package/src/agent_run.tsx +253 -66
- package/src/back_button.tsx +3 -2
- package/src/badge.tsx +2 -1
- package/src/button.tsx +11 -32
- package/src/button_colors.ts +23 -0
- package/src/calendar/calendar_view.tsx +2 -2
- package/src/card_select_item.tsx +2 -1
- package/src/change_review.tsx +5 -6
- package/src/{pill_button.tsx → chip.tsx} +7 -7
- package/src/chip_group.tsx +2 -2
- package/src/column_filter.tsx +4 -4
- package/src/combobox.tsx +2 -9
- package/src/composer.tsx +4 -3
- package/src/control_surface.ts +15 -15
- package/src/date_field.tsx +2 -1
- package/src/date_filter.tsx +2 -1
- package/src/date_range_filter_field.tsx +2 -1
- package/src/dialog.tsx +2 -2
- package/src/discrepancy.tsx +1 -1
- package/src/drawer.tsx +1 -2
- package/src/file_gallery_modal.tsx +220 -133
- package/src/file_grid.tsx +184 -0
- package/src/file_preview.web.tsx +329 -6
- package/src/file_preview_types.ts +40 -0
- package/src/file_row.tsx +114 -0
- package/src/file_rows.tsx +105 -0
- package/src/file_thumbnail.tsx +121 -79
- package/src/file_thumbnail_grid.tsx +130 -55
- package/src/{filter_pill.tsx → filter_chip.tsx} +8 -8
- package/src/finding.tsx +1 -1
- package/src/gantt/gantt_view.tsx +2 -2
- package/src/icon_button.tsx +49 -14
- package/src/image_gallery.tsx +0 -4
- package/src/inline_edit.tsx +2 -2
- package/src/link_button.tsx +2 -1
- package/src/menu_button.tsx +2 -1
- package/src/menu_list_item.tsx +2 -1
- package/src/number_input.tsx +2 -1
- package/src/pdfjs_worker.d.ts +5 -0
- package/src/picker.tsx +3 -3
- package/src/popover_header.tsx +2 -2
- package/src/radio_picker.tsx +2 -1
- package/src/range_slider.tsx +1 -1
- package/src/review_card.tsx +2 -2
- package/src/scan_field.tsx +2 -1
- package/src/scored_option.tsx +0 -1
- package/src/search_input.tsx +3 -2
- package/src/segmented_control.tsx +2 -1
- package/src/status_badge.tsx +2 -1
- package/src/stepper.tsx +3 -2
- package/src/switch_button.tsx +2 -1
- package/src/switcher.tsx +3 -2
- package/src/tabs.tsx +2 -1
- package/src/tag_input.tsx +2 -1
- package/src/text_input_field.tsx +2 -1
- package/src/time_field.tsx +2 -1
- package/src/time_picker.tsx +2 -1
- package/src/triage_row.tsx +3 -3
- package/src/uploading_thumbnail.tsx +212 -0
- package/src/hover_action.tsx +0 -54
package/AGENTS.md
CHANGED
|
@@ -22,9 +22,12 @@ typeahead, async search, virtualization, and a11y the primitive already ships.
|
|
|
22
22
|
|
|
23
23
|
Pick by capability, not by name. (→ the source file for the API.)
|
|
24
24
|
|
|
25
|
-
- **Actions** — `Button` (labelled; `color` = emphasis/risk
|
|
26
|
-
`IconButton` (icon-only
|
|
27
|
-
|
|
25
|
+
- **Actions** — `Button` (labelled; `title` is MANDATORY; `color` = emphasis/risk),
|
|
26
|
+
`IconButton` (icon-ONLY — the circular affordance; an icon-only Button is a type error, so reach
|
|
27
|
+
here. `size` `lg` 40px / `md` 28px / `sm` 24px, the SAME `color` palette as Button, plus `loading`
|
|
28
|
+
and `elevated` (white fill + border + shadow, for a button sitting ON imagery — a tile's remove ✕,
|
|
29
|
+
an overlay's retry); needs `accessibilityLabel`/`tooltip`), `LinkButton` (quiet ghost
|
|
30
|
+
action — Clear / Select all), `Chip` (dismissible facet chip). A button is never a raw
|
|
28
31
|
`Pressable`. For a link OUT (a URL / record / document) use `Link` — underline+blue, the
|
|
29
32
|
destination signal; the opposite of `LinkButton` (a quiet in-app action, never underlined).
|
|
30
33
|
- **Pick from a list** — `Picker` (known options, native typeahead, single/multi/custom-render,
|
|
@@ -66,13 +69,21 @@ Pick by capability, not by name. (→ the source file for the API.)
|
|
|
66
69
|
- **Rows & registers** — `PressableRow` (THE register row), `ListItem`, `MenuButton`,
|
|
67
70
|
`MenuListItem`, `DetailRow` (label+value for drawers/peeks), `ActionMenu` (⋯), `FloatingActionBar`
|
|
68
71
|
(bulk-select bar).
|
|
69
|
-
- **Filters & view controls** — `SearchInput`, `ChipGroup`, `
|
|
70
|
-
`Counter`), `
|
|
72
|
+
- **Filters & view controls** — `SearchInput`, `ChipGroup`, `FilterChip` (+ `RangeSlider`,
|
|
73
|
+
`Counter`), `Chip`.
|
|
71
74
|
- **Overlays** — `Dialog`, `Drawer` (+ `DrawerFooter`), `Popover`, `Tooltip`, `CommandMenu`,
|
|
72
75
|
`Alert` (the blocking confirm).
|
|
73
76
|
- **Status / feedback** — `Badge` / `StatusBadge`, `Callout` (inline status), `EmptyState`,
|
|
74
77
|
`CompletionState`, `ActivityIndicator` / `Loading`, `Skeleton`.
|
|
75
|
-
- **Files** — `FileDropzone`, `
|
|
78
|
+
- **Files** — `FileDropzone`, `FileRows` (batteries-included file LIST: tap a row → built-in
|
|
79
|
+
full-screen gallery, ⋯ menu = Download · Open-external · Remove; the default "here are some files"
|
|
80
|
+
surface), `FileGrid` (the upload-aware grid: completed files + a live upload
|
|
81
|
+
queue in one surface — use this for any add-files screen), `FileThumbnail` / `FileThumbnailGrid`
|
|
82
|
+
(square tiles, display-only), `UploadingThumbnail` (the single in-flight tile FileGrid renders —
|
|
83
|
+
reach for it only when hand-rolling a non-grid upload layout), `FileRow` (a horizontal file/document
|
|
84
|
+
LINE — badge-or-placeholder + name + meta + a composable `trailing` slot for a status badge / action
|
|
85
|
+
/ remove; `onPress` makes the whole row a pressable door, `trailing` stays an independently-pressable
|
|
86
|
+
sibling; for checklists & readable lists), `FileBadge` (the two-tone type mark), `FilePreview` /
|
|
76
87
|
`FileGalleryModal`, `ImageGallery`; for gated CRUD compose locally with `useSelectionMode` +
|
|
77
88
|
`shareOrDownloadFiles` + `rotateImageToBlob` (see §Data entry → Attachments).
|
|
78
89
|
- **Specialized work surfaces** — `ScanField` (scan/verify), `Stepper` (a guided run / progress
|
|
@@ -197,23 +208,49 @@ marked + disabled), not a generic text menu. A composition (Badge + Button + Pop
|
|
|
197
208
|
|
|
198
209
|
### Attachments — a full add / preview / DELETE field
|
|
199
210
|
Capture with `<FileDropzone onFiles accept label hint dropLabel height>` (drag-over lights the
|
|
200
|
-
accent; click falls back to a picker); display what landed with
|
|
211
|
+
accent; click falls back to a picker); display what landed with **`<FileGrid files uploads>`** ABOVE
|
|
201
212
|
the dropzone (existing files are the content; the dropzone sinks to the bottom as the "add more"
|
|
202
|
-
affordance — only the empty state leads with it).
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
`
|
|
212
|
-
|
|
213
|
+
affordance — only the empty state leads with it). `FileGrid` is the upload-aware grid: `files` are
|
|
214
|
+
the saved/completed `DisplayFile`s, `uploads` is the LIVE add-queue (`FileUpload[]`) — it interleaves
|
|
215
|
+
both and renders each in-flight tile itself — a LABELED status overlay (uploading spinner · "Retrying"
|
|
216
|
+
· "Paused" · "Upload failed" + a retry button · "Can't upload" for a dead/empty file) — so you never
|
|
217
|
+
hand-map an upload to a `FileThumbnail`. Localize the labels with `labels.upload` (an
|
|
218
|
+
`UploadStatusLabels`; the Lotics `@lotics/ui-internal` adapter already injects vi/en). Make it CRUDable
|
|
219
|
+
by wiring its callbacks: `onFilePress`
|
|
220
|
+
→ set a `number|null` index that drives `<FileGalleryModal files activeIndex onIndexChange>` — a
|
|
221
|
+
FULL-SCREEN viewer with a toolbar (filename · counter · ⋯actions (download, optional
|
|
222
|
+
`onOpenExternal`/`onRemove`) · close-✕), prev/next, ESC, and rotate; `onDisplayRemove`/`onUploadRemove`
|
|
223
|
+
→ drop the file / cancel the upload (the grid renders a ✕ on each tile automatically); `onRetry` /
|
|
224
|
+
`onRetryAll` for failed uploads. In a sandboxed custom-code app the toolbar's "open in new tab" can't
|
|
225
|
+
pop a window — pass `onOpenExternal` wired to the SDK's `openExternal` (omit it elsewhere and the
|
|
226
|
+
action hides). PDF/Word/Excel/CSV all render INLINE here (pdf.js / `@lotics/docx` / `@lotics/xlsx`,
|
|
227
|
+
lazy) — no native `<iframe>` viewer, so they work inside the cross-origin app iframe. Keep
|
|
228
|
+
label+grid+dropzone on a `gap` (a bare `CardBody` has none). By default `FileGrid` tiles FILL the
|
|
229
|
+
container width — a uniform size (≥ `minItemWidth`, default 96) so full rows span the width and reflow
|
|
230
|
+
as it changes; a short last row keeps that size and left-aligns (no stretched tiles, no empty cells).
|
|
231
|
+
Pass `columns` for a fixed column count, or `itemSize` for exact fixed-size tiles. For a dense strip
|
|
232
|
+
in a FIXED-width container that must NOT wrap, pass `singleRow` — it fits as many tiles as the width
|
|
233
|
+
allows and collapses the rest into a clickable "+N" overflow tile (`onOverflowPress(hiddenCount)`,
|
|
234
|
+
which the host wires — e.g. open the gallery at the first hidden file). This is the responsive overflow
|
|
235
|
+
pattern (avatar stack / Finder); prefer it over a hardcoded `maxVisible`, which can't adapt to width.
|
|
236
|
+
Under the hood it composes `FileThumbnail` (the completed tile — right surface per
|
|
237
|
+
MIME: image thumbnail · a doc tile with the `FileBadge` centered + a single-line filename · media
|
|
238
|
+
card; also takes `isTemplate` to overlay a TMPL marker) and `UploadingThumbnail` (the in-flight tile);
|
|
239
|
+
reach for either directly only when hand-rolling a NON-grid layout. **When the filename must be
|
|
240
|
+
readable, use `FileRow`** — a horizontal LINE (`FileBadge` or a `placeholder` + the FULL name + a meta
|
|
241
|
+
line + a composable `trailing` slot). `onPress` makes the whole row a pressable door (open the file);
|
|
242
|
+
`trailing` (a status `Badge`, an action, a remove ✕) stays an independently-pressable sibling. For
|
|
243
|
+
attachment lists / message files / document checklists where a square tile truncates the name. Never hand-build a drop well, a file grid, the upload tiles, or a preview
|
|
244
|
+
modal. In a custom-code APP, don't hand-roll the upload+preview state either — `@lotics/app-sdk`
|
|
245
|
+
`useAttachments()` gives the chat's instant-preview lifecycle (local object-URL now, `file_id` on
|
|
246
|
+
complete); map each `AttachedFile` to a `FileUpload` (ready → `{ status: "complete", id, file: {…} }`,
|
|
247
|
+
else `{ status, id, filename, mimeType: mime_type, previewUrl: preview_url }`) and feed `FileGrid`'s
|
|
248
|
+
`uploads` — `onUploadRemove={remove}`.
|
|
213
249
|
|
|
214
250
|
**Gated file CRUD is a PATTERN, composed locally — not a sealed kit component.** When a file field must
|
|
215
|
-
GUARD deletion (operator-facing, accidental delete is a real risk),
|
|
216
|
-
|
|
251
|
+
GUARD deletion (operator-facing, accidental delete is a real risk), DON'T use `FileGrid` (its per-tile
|
|
252
|
+
✕ is exactly the stray-tap risk you're guarding against) — compose it in the app from the kit pieces;
|
|
253
|
+
don't reach for a one-size widget (the action set + layout vary per app — the host frontend's
|
|
217
254
|
`files_editor` and each app compose their own). The recipe: `FileDropzone` (add) + `FileThumbnailGrid
|
|
218
255
|
files selectedIds onFilePress` (pass `selectedIds` ONLY in select mode) + `FileGalleryModal` (view +
|
|
219
256
|
rotate + persist) + a `Dialog` confirm, all driven by **`useSelectionMode()`** — the reusable LOGIC. The
|
|
@@ -270,15 +307,20 @@ Compose the surfaces as a loop, and reach for the right one by job:
|
|
|
270
307
|
attaching HOLDS the file for review; the human presses Send. The canvas (`tpl_dieline`) uses it this
|
|
271
308
|
way; while running the host swaps `Composer` for `AgentProgress` (same pill geometry, so it reads as
|
|
272
309
|
a morph).
|
|
273
|
-
- **Show the work** — `AgentRun`: a live feed of the agent's work
|
|
274
|
-
as `Markdown`,
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
310
|
+
- **Show the work** — `AgentRun`: a live feed of the agent's work as a TIMELINE. The prop is an
|
|
311
|
+
ORDERED `items` array — `{type:"text"}` prose (rendered as `Markdown`, a trailing ▍ while live) interleaved with `{type:"step"}` tool/step calls, in
|
|
312
|
+
the order they happened (a real run is text → a burst of calls → more text → more calls, NOT all
|
|
313
|
+
text on top of a flat step list). Consecutive `step` items fold into ONE activity group: while the
|
|
314
|
+
agent is mid-tools the tail group is a SINGLE pulsing row whose label swaps in place as each call
|
|
315
|
+
fires (no growing stack of dots); once prose resumes the group settles into a persistent "{final
|
|
316
|
+
action} · {n} steps" HEADER (a `complete` terminal dot — distinct from the filled `done` step dots)
|
|
317
|
+
that STAYS PUT and rolls the calls out BELOW it on press (a `step` can carry `peek` content;
|
|
318
|
+
`detail` is OPTIONAL — real tool I/O, never invented prose). The run ALWAYS ends on the agent's
|
|
319
|
+
text (no global terminal node). A `step` with
|
|
320
|
+
`kind:"tool"` carries the RAW tool name, resolved via a built-in map + an optional `labelForTool`
|
|
321
|
+
override (localize THERE — the kit stays English); `stepsLabel` localizes the "{n} steps" suffix.
|
|
322
|
+
Transparent work, NEVER a bare spinner. On a canvas/composer app reach for `AgentProgress` —
|
|
323
|
+
`AgentRun` collapsed into a floating pill (avatar + current step) that EXPANDS on press; the composer
|
|
282
324
|
morphs into it while running, and reveals again when done.
|
|
283
325
|
- **Review before apply — ONE card + the batch engine, the BODY is compositional.** Every "the agent
|
|
284
326
|
proposes → the human accepts / edits / dismisses → nothing auto-applies" surface wears the same
|
|
@@ -378,8 +420,8 @@ controls — while **status/severity/diffs use functional colour** the way the r
|
|
|
378
420
|
- **Stepper / AgentRun nodes** — progress dots on a spine: `current` a **pulsing accent ring** (white
|
|
379
421
|
centre), `done` a filled accent dot + **white check**, `upcoming` a faint **grey** ring, the terminal
|
|
380
422
|
`complete` a **blackish ring + black check**, `warning` **amber**. Default accent is neutral ink
|
|
381
|
-
(`color` themes it); `AgentRun` keeps the neutral default
|
|
382
|
-
|
|
423
|
+
(`color` themes it); `AgentRun` keeps the neutral default — an active group's tail pulses (`current`),
|
|
424
|
+
a settled group is a `done`/`warning` row, and there is no global terminal node (the run ends on text).
|
|
383
425
|
- **ChangeReview diffs** — the old value struck in **red**, the new value in **green**.
|
|
384
426
|
- **Discrepancy** — the agent's pick gets a **blue** highlight; `Mismatch` reads amber, `Resolved`
|
|
385
427
|
emerald.
|
|
@@ -472,31 +514,49 @@ was only ever about the review surfaces' sparkles/severity glyphs, not functiona
|
|
|
472
514
|
padded container, NO `Divider`s (the gap + rounded hover separate them).
|
|
473
515
|
Right-hand columns align only if every trailing element is FIXED-width (give each trailing action
|
|
474
516
|
a fixed `width`, so amount/status columns don't jitter).
|
|
517
|
+
- **Row actions are ALWAYS-VISIBLE siblings, never hover-revealed, never nested.** A pressable row =
|
|
518
|
+
a role-less surface (the hover/press wash, via DOM `mouseenter` so it covers the whole row) + an
|
|
519
|
+
accessible "Open …" door (the body, a button) + trailing actions (⋯, remove ✕, edit) as SIBLINGS of
|
|
520
|
+
the door — a button must not contain another button, so the action is independently pressable and
|
|
521
|
+
the ✕ isn't swallowed by the row press (`FileRow` and `PressableRow` are the references). Actions
|
|
522
|
+
stay **visible** — a hover-only action is invisible to keyboard and touch users. The ONLY exception
|
|
523
|
+
is a DENSE tree/register where a persistent per-row action would clutter (e.g. the DB sidebar):
|
|
524
|
+
reveal on hover **OR focus-within**, keeping the action in the DOM + tab order and gating only its
|
|
525
|
+
opacity, so it's still keyboard-reachable. Do that locally — there is no shared hover-reveal
|
|
526
|
+
primitive (the old `HoverAction` made the keyboard-inaccessible anti-pattern easy and was removed).
|
|
475
527
|
- **Master-detail = list + workspace `Drawer` with sequencing.** Pressing a PRIMARY entity row
|
|
476
528
|
opens the record workspace in a `Drawer` with `onPrev`/`onNext`/`position` ("3/24") over the
|
|
477
529
|
visible ordering (←/→ built in). Key the drawer body by record id so per-record state resets on
|
|
478
530
|
step. Facts are `DetailRow`s; the commit bar is a `DrawerFooter`. The open row shows a selected
|
|
479
531
|
highlight (and `marked` — a resting blue tint — for a bulk-ticked row). `Peek` is ONLY for
|
|
480
532
|
secondary references, never the primary row press.
|
|
533
|
+
- **One control radius** — every interactive control (`Button`, the inputs/selects/pickers,
|
|
534
|
+
`SearchInput`, `MenuButton`, `Tabs`, the `SegmentedControl` track, `ChipGroup`/`FilterChip`/
|
|
535
|
+
`Chip`) wears `CONTROL_RADIUS` (10, from `control_surface.ts`) — never a literal. Containers
|
|
536
|
+
are deliberately larger (`Card` 16, the review card 12). An element NESTED inside a padded control
|
|
537
|
+
(a segmented thumb, a combobox chip, an ✕ on a chip) takes `CONTROL_RADIUS − padding` so its corner
|
|
538
|
+
stays CONCENTRIC with the parent — set it equal and the inner corner bulges past the outer curve.
|
|
539
|
+
Round-by-function controls (avatars, status dots, switches, sliders, progress bars, `IconButton`)
|
|
540
|
+
stay full (999); a small square toggle (`Checkbox`, 24px) keeps a proportional small radius (6 —
|
|
541
|
+
the same fraction of its box that 10 is of a 40px control), not the band radius. No capsules —
|
|
542
|
+
`Button` has no pill `shape`.
|
|
481
543
|
- **One view-control vocabulary** (all 40px, `sm` labels, in one wrapping band):
|
|
482
|
-
· `SearchInput` is THE search box — a white `TextInputField` preset (
|
|
483
|
-
input, a thin zinc-300 border, the leading search glyph + a clear ✕). It reads as search by its
|
|
544
|
+
· `SearchInput` is THE search box — a white `TextInputField` preset (the shared `CONTROL_RADIUS`
|
|
545
|
+
like every other input, a thin zinc-300 border, the leading search glyph + a clear ✕). It reads as search by its
|
|
484
546
|
ICON + white fill, not a distinct shape, so a toolbar of controls stays one consistent band.
|
|
485
547
|
Never a bare `TextInputField` + search icon.
|
|
486
548
|
· `ChipGroup` is THE one-of-N lens (≤ ~10 options the user flips between; include "All"; counts in
|
|
487
549
|
the label). Filters ONE list to a SUBSET — including by process stage / lifecycle state.
|
|
488
|
-
· `
|
|
550
|
+
· `FilterChip` is THE secondary-dimension filter — a compact chip that opens a composed popover
|
|
489
551
|
editor (`PickerMenu multi` / `RangeSlider` / `Counter` / date range). Single-select closes on
|
|
490
552
|
pick via the `{({close}) => …}` render prop.
|
|
491
553
|
· `ColumnFilter` is the TYPED column filter — give it a `FilterableColumn` (`text` / `number` /
|
|
492
554
|
`select`) + a controlled `ColumnFilterValue` and it renders the right editor (contains / range /
|
|
493
|
-
multi-select) inside a `
|
|
494
|
-
predicates. Reach for it — ONE compact
|
|
555
|
+
multi-select) inside a `FilterChip`, with `columnFilterToConditions` mapping the value to query
|
|
556
|
+
predicates. Reach for it — ONE compact chip per dimension — when a register filters on SEVERAL
|
|
495
557
|
columns (origin, destination, carrier, type…). Never hand-roll a row of bespoke chips/filters,
|
|
496
558
|
and never use a permanently-expanded `ChipGroup` for a secondary dimension.
|
|
497
|
-
· `Combobox
|
|
498
|
-
toolbar-pill surface as `SearchInput`/`FilterPill` (don't put a bare form-look Combobox in a
|
|
499
|
-
toolbar — its transparent input reads grey on the zinc-50 canvas beside the white pills). Reach
|
|
559
|
+
· `Combobox` is the SEARCHABLE picker — reach
|
|
500
560
|
for it over `ColumnFilter` when the value is ONE of a large/growing set you type to narrow (a
|
|
501
561
|
port, a customer) rather than a short fixed list. Pass `recentOptions` (the same option list) so
|
|
502
562
|
a click opens the full list, then typing filters. The **From → To route picker** is two of them
|
|
@@ -588,7 +648,7 @@ section_heading (SectionHeading · SectionHeadingTitle — compound, owns no mar
|
|
|
588
648
|
option_badge (OptionBadge — a select value as its configured colored badge) ·
|
|
589
649
|
member_chip (MemberChip — avatar + name; the universal person render) ·
|
|
590
650
|
member_select (MemberSelect — a Picker of MemberChip options; the member picker) ·
|
|
591
|
-
status_badge · button · icon_button · link · link_button ·
|
|
651
|
+
status_badge · button · icon_button · link · link_button · chip · tabs · segmented_control ·
|
|
592
652
|
picker · combobox · tag_input (TagInput — chip box + Add-popover; for tags, not Combobox multi) ·
|
|
593
653
|
text_input_field · number_input · search_input · form_field · checkbox · checkbox_input · switch ·
|
|
594
654
|
radio_picker · counter · range_slider · date_picker · date_range_filter_field · time_picker ·
|
|
@@ -596,7 +656,7 @@ inline_text_input · inline_number_input · inline_select · inline_member_selec
|
|
|
596
656
|
inline_time_picker (the Inline* family — per-field editors on `inline_edit`'s `useInlineEdit` +
|
|
597
657
|
`InlineEditView`; `InlineSelect`/`InlineMemberSelect` take `renderValue` for a chip/badge at rest) ·
|
|
598
658
|
list · list_item · menu_button · menu_list_item · detail_row · pressable_row · action_menu ·
|
|
599
|
-
floating_action_bar ·
|
|
659
|
+
floating_action_bar · filter_chip · column_filter (ColumnFilter — the typed per-column filter pill +
|
|
600
660
|
columnFilterToConditions; for a register filtering on several columns) · chip_group · search_input ·
|
|
601
661
|
sort_header · table · pagination · accordion · stepper (Stepper + Step — done/current/upcoming/warning/complete progress on a track (horizontal) or spine (vertical); compound `<Step status>children` OR data `steps[]`+`current`; the guided-run / agent-feed primitive — subsumes the old StepList) ·
|
|
602
662
|
step_progress · timeline (heterogeneous event LOG — per-row icon + expandable details, models the past; NOT progress) · drawer (+ DrawerFooter) · dialog · popover · tooltip ·
|
|
@@ -604,7 +664,7 @@ command_menu · alert · peek · empty_state · completion_state · callout (Cal
|
|
|
604
664
|
CalloutText · CalloutActions) · kpi_card · kpi_strip · metric · trend_chip · sparkline ·
|
|
605
665
|
bar_chart · line_chart · pie_chart · ring_gauge · progress_bar · stacked_progress_bar · breakdown ·
|
|
606
666
|
status_grid (StatusGrid + StatusLegend) · heatmap · legend_item · remainder_meter · allocation_row ·
|
|
607
|
-
scan_field · file_dropzone · file_thumbnail · file_thumbnail_grid · file_preview ·
|
|
667
|
+
scan_field · file_dropzone · file_grid (FileGrid — the upload-aware grid: completed files + a live upload queue in one surface; FileUpload/PendingUpload types; the add-files default) · uploading_thumbnail (UploadingThumbnail — the single in-flight upload tile FileGrid renders; reach for it only when hand-rolling a non-grid upload layout) · file_thumbnail · file_thumbnail_grid · file_row · file_rows (FileRows — batteries-included file list: row press → built-in gallery + a ⋯ Download/Open-external/Remove menu; composes FileRow + ActionMenu + FileGalleryModal) · file_preview ·
|
|
608
668
|
file_gallery_modal · image_gallery · use_selection_mode · share_or_download · rotate_image · avatar · skeleton · activity_indicator · loading · divider ·
|
|
609
669
|
spacer · stack · section_card · page_header · page_content · calendar (calendar/index.ts) · gantt ·
|
|
610
670
|
comments_thread · agent_run (live streaming work feed) · agent_progress (its compact floating
|
|
@@ -13,7 +13,7 @@ import { Divider } from "@lotics/ui/divider";
|
|
|
13
13
|
import { Drawer, DrawerFooter } from "@lotics/ui/drawer";
|
|
14
14
|
import { EmptyState } from "@lotics/ui/empty_state";
|
|
15
15
|
import { Counter } from "@lotics/ui/counter";
|
|
16
|
-
import {
|
|
16
|
+
import { FilterChip, selectSummary } from "@lotics/ui/filter_chip";
|
|
17
17
|
import { FloatingActionBar } from "@lotics/ui/floating_action_bar";
|
|
18
18
|
import { FormField } from "@lotics/ui/form_field";
|
|
19
19
|
import { KPIStrip } from "@lotics/ui/kpi_strip";
|
|
@@ -240,7 +240,7 @@ export function TplApprovals() {
|
|
|
240
240
|
/>
|
|
241
241
|
|
|
242
242
|
{/* layered filter band — the hot dimension (type) inline as chips,
|
|
243
|
-
secondary dimensions collapsed into
|
|
243
|
+
secondary dimensions collapsed into FilterChips so the band stays
|
|
244
244
|
scannable as more facets are added */}
|
|
245
245
|
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8, alignItems: "center" }}>
|
|
246
246
|
<ChipGroup
|
|
@@ -254,13 +254,13 @@ export function TplApprovals() {
|
|
|
254
254
|
value={tab}
|
|
255
255
|
onValueChange={(t) => { setTab(t); setOpenId(null); }}
|
|
256
256
|
/>
|
|
257
|
-
<
|
|
257
|
+
<FilterChip label="Requester" summary={reqSummary} onClear={() => setReqSel([])} clearLabel="Clear requester filter">
|
|
258
258
|
<PickerMenu multi enableSelectAll options={REQUESTERS} value={reqSel} onValueChange={setReqSel} />
|
|
259
|
-
</
|
|
260
|
-
<
|
|
259
|
+
</FilterChip>
|
|
260
|
+
<FilterChip label="Role" summary={roleSummary} onClear={() => setRoleSel([])} clearLabel="Clear role filter">
|
|
261
261
|
<PickerMenu multi options={ROLES} value={roleSel} onValueChange={setRoleSel} />
|
|
262
|
-
</
|
|
263
|
-
<
|
|
262
|
+
</FilterChip>
|
|
263
|
+
<FilterChip label="Amount" summary={amtSummary} onClear={() => setAmt([AMT_MIN, AMT_MAX])} clearLabel="Clear amount filter">
|
|
264
264
|
<RangeSlider
|
|
265
265
|
min={AMT_MIN}
|
|
266
266
|
max={AMT_MAX}
|
|
@@ -270,8 +270,8 @@ export function TplApprovals() {
|
|
|
270
270
|
format={fmtAmt}
|
|
271
271
|
accessibilityLabel="Amount"
|
|
272
272
|
/>
|
|
273
|
-
</
|
|
274
|
-
<
|
|
273
|
+
</FilterChip>
|
|
274
|
+
<FilterChip label="Waiting" summary={daysSummary} onClear={() => setMinDays(0)} clearLabel="Clear waiting filter">
|
|
275
275
|
<View style={{ gap: 10 }}>
|
|
276
276
|
<Text size="sm" color="muted">Waiting at least</Text>
|
|
277
277
|
<Counter
|
|
@@ -283,7 +283,7 @@ export function TplApprovals() {
|
|
|
283
283
|
format={(n) => `${n} ${n === 1 ? "day" : "days"}`}
|
|
284
284
|
/>
|
|
285
285
|
</View>
|
|
286
|
-
</
|
|
286
|
+
</FilterChip>
|
|
287
287
|
</View>
|
|
288
288
|
|
|
289
289
|
<Card style={{ padding: 0 }}>
|
package/examples/tpl_convert.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { Card } from "@lotics/ui/card";
|
|
|
8
8
|
import { DetailRow } from "@lotics/ui/detail_row";
|
|
9
9
|
import { Divider } from "@lotics/ui/divider";
|
|
10
10
|
import { Drawer, DrawerFooter } from "@lotics/ui/drawer";
|
|
11
|
-
import {
|
|
11
|
+
import { FileRow } from "@lotics/ui/file_row";
|
|
12
12
|
import { FileDropzone } from "@lotics/ui/file_dropzone";
|
|
13
13
|
import { FileThumbnailGrid } from "@lotics/ui/file_thumbnail_grid";
|
|
14
14
|
import { FileGalleryModal } from "@lotics/ui/file_gallery_modal";
|
|
@@ -37,8 +37,8 @@ import { Timeline, type TimelineItem } from "@lotics/ui/timeline";
|
|
|
37
37
|
// required-document checklist with a free FILE DUMP for anything extra. The
|
|
38
38
|
// drawer's ◀▶ keys walk the manager down the queue without closing it.
|
|
39
39
|
//
|
|
40
|
-
// Reference composition — Drawer (list nav) + Stepper +
|
|
41
|
-
// + Pagination + DetailRow.
|
|
40
|
+
// Reference composition — Drawer (list nav) + Stepper + FileRow (badge + name +
|
|
41
|
+
// meta + a trailing status/action slot) + Pagination + DetailRow.
|
|
42
42
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
43
43
|
|
|
44
44
|
const CODES: Record<string, string> = {
|
|
@@ -141,21 +141,9 @@ function QueueRow({ app, active, onPress }: { app: Applicant; active: boolean; o
|
|
|
141
141
|
);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}) {
|
|
148
|
-
return (
|
|
149
|
-
<View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
|
|
150
|
-
<FileBadge size={30} mimeType={mimeType} placeholder={placeholder} />
|
|
151
|
-
<View style={{ flex: 1 }}>
|
|
152
|
-
<Text size="sm" weight="medium">{name}</Text>
|
|
153
|
-
{meta ? <Text size="xs" color="muted">{meta}</Text> : null}
|
|
154
|
-
</View>
|
|
155
|
-
{action ?? (tone === "received" ? <Badge variant="dot" label="Received" color="emerald" /> : tone === "ready" ? <Badge variant="dot" label="Generated" color="blue" /> : null)}
|
|
156
|
-
</View>
|
|
157
|
-
);
|
|
158
|
-
}
|
|
144
|
+
// Document status chips — the `trailing` slot content for the kit's `FileRow`.
|
|
145
|
+
const receivedBadge = <Badge variant="dot" label="Received" color="emerald" />;
|
|
146
|
+
const generatedBadge = <Badge variant="dot" label="Generated" color="blue" />;
|
|
159
147
|
|
|
160
148
|
// The extra-documents zone — the data-capture attachment field: a thumbnail grid
|
|
161
149
|
// (click to preview in the gallery, ✕ to delete) above a dropzone. Read-only once
|
|
@@ -189,7 +177,7 @@ function UploadsField({ uploads, editable, onAdd, onRemove }: {
|
|
|
189
177
|
) : uploads.length === 0 ? (
|
|
190
178
|
<Text size="sm" color="muted">No extra files.</Text>
|
|
191
179
|
) : null}
|
|
192
|
-
<FileGalleryModal files={uploads} activeIndex={active} onIndexChange={setActive}
|
|
180
|
+
<FileGalleryModal files={uploads} activeIndex={active} onIndexChange={setActive} />
|
|
193
181
|
</View>
|
|
194
182
|
);
|
|
195
183
|
}
|
|
@@ -217,7 +205,7 @@ function StageSection({ app, index, onMarkDoc, onAddUploads, onRemoveUpload }: {
|
|
|
217
205
|
if (status === "pending") {
|
|
218
206
|
body = <Text size="sm" color="muted">{`Unlocks after ${STAGES[index - 1].label.toLowerCase()}.`}</Text>;
|
|
219
207
|
} else if (index === 0) {
|
|
220
|
-
body = <FileRow name="Application fee receipt" meta={status === "done" ? `${formatMoney(app.fee)} · issued` : formatMoney(app.fee)} mimeType="application/pdf"
|
|
208
|
+
body = <FileRow name="Application fee receipt" meta={status === "done" ? `${formatMoney(app.fee)} · issued` : formatMoney(app.fee)} mimeType="application/pdf" trailing={status === "done" ? generatedBadge : undefined} />;
|
|
221
209
|
} else if (index === 1) {
|
|
222
210
|
body = (
|
|
223
211
|
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
|
|
@@ -229,7 +217,7 @@ function StageSection({ app, index, onMarkDoc, onAddUploads, onRemoveUpload }: {
|
|
|
229
217
|
body = (
|
|
230
218
|
<View style={{ gap: 10 }}>
|
|
231
219
|
{GENERATED_DOCS.map((d) => (
|
|
232
|
-
<FileRow key={d} name={d} mimeType="application/pdf" meta={status === "done" ? "generated" : "template"}
|
|
220
|
+
<FileRow key={d} name={d} mimeType="application/pdf" meta={status === "done" ? "generated" : "template"} trailing={status === "done" ? generatedBadge : undefined} />
|
|
233
221
|
))}
|
|
234
222
|
</View>
|
|
235
223
|
);
|
|
@@ -245,9 +233,9 @@ function StageSection({ app, index, onMarkDoc, onAddUploads, onRemoveUpload }: {
|
|
|
245
233
|
meta={d.received ? `${d.file} · received` : "Awaiting upload"}
|
|
246
234
|
mimeType={d.received ? "application/pdf" : undefined}
|
|
247
235
|
placeholder={!d.received}
|
|
248
|
-
|
|
236
|
+
trailing={status === "current"
|
|
249
237
|
? (d.received ? <Button title="Remove" color="muted" onPress={() => onMarkDoc(d.name, false)} /> : <Button title="Upload" color="secondary" onPress={() => onMarkDoc(d.name, true)} />)
|
|
250
|
-
: (d.received ?
|
|
238
|
+
: (d.received ? receivedBadge : undefined)}
|
|
251
239
|
/>
|
|
252
240
|
))}
|
|
253
241
|
</View>
|
|
@@ -20,7 +20,7 @@ import { NumberInput } from "@lotics/ui/number_input";
|
|
|
20
20
|
import { Peek } from "@lotics/ui/peek";
|
|
21
21
|
import { ChipGroup } from "@lotics/ui/chip_group";
|
|
22
22
|
import { DatePicker } from "@lotics/ui/date_picker";
|
|
23
|
-
import {
|
|
23
|
+
import { FilterChip } from "@lotics/ui/filter_chip";
|
|
24
24
|
import { Popover, PopoverContent, PopoverTrigger } from "@lotics/ui/popover";
|
|
25
25
|
import { PressableRow } from "@lotics/ui/pressable_row";
|
|
26
26
|
import { SearchInput } from "@lotics/ui/search_input";
|
|
@@ -158,7 +158,7 @@ function ChiTieuGate({ chiTieu, onSave }: { chiTieu: number; onSave: (n: number)
|
|
|
158
158
|
onSave(draft); setApDung("In effect from today · set by you"); setOpen(false);
|
|
159
159
|
};
|
|
160
160
|
return (
|
|
161
|
-
<
|
|
161
|
+
<FilterChip
|
|
162
162
|
label="Target"
|
|
163
163
|
summary={String(chiTieu)}
|
|
164
164
|
align="end"
|
|
@@ -181,7 +181,7 @@ function ChiTieuGate({ chiTieu, onSave }: { chiTieu: number; onSave: (n: number)
|
|
|
181
181
|
</FormField>
|
|
182
182
|
<Text size="xs" color="muted">{apDung}</Text>
|
|
183
183
|
</View>
|
|
184
|
-
</
|
|
184
|
+
</FilterChip>
|
|
185
185
|
);
|
|
186
186
|
}
|
|
187
187
|
|
package/examples/tpl_dieline.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import { InlineTextInput } from "@lotics/ui/inline_text_input";
|
|
|
10
10
|
import { AgentProgress } from "@lotics/ui/agent_progress";
|
|
11
11
|
import { Composer } from "@lotics/ui/composer";
|
|
12
12
|
import { FileThumbnail, type DisplayFile } from "@lotics/ui/file_thumbnail";
|
|
13
|
-
import { type AgentRunStep } from "@lotics/ui/agent_run";
|
|
13
|
+
import { type AgentRunStep, type AgentRunItem } from "@lotics/ui/agent_run";
|
|
14
14
|
|
|
15
15
|
// A real app fills the attachment from `useFileUpload` (the uploaded file's URL);
|
|
16
16
|
// this mock uses an inline placeholder so the example needs no network image.
|
|
@@ -105,8 +105,9 @@ export function TplDieline() {
|
|
|
105
105
|
return () => clearTimeout(t);
|
|
106
106
|
}, [run, revealed]);
|
|
107
107
|
|
|
108
|
-
const
|
|
108
|
+
const liveItems: AgentRunItem[] = run
|
|
109
109
|
? run.script.slice(0, revealed).map((s, i) => ({
|
|
110
|
+
type: "step",
|
|
110
111
|
...s,
|
|
111
112
|
status: revealed < run.script.length && i === revealed - 1 ? "running" : "done",
|
|
112
113
|
}))
|
|
@@ -167,7 +168,7 @@ export function TplDieline() {
|
|
|
167
168
|
{/* slim top bar */}
|
|
168
169
|
<View style={{ flexDirection: "row", alignItems: "center", gap: 12, paddingHorizontal: 20, paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: colors.zinc[200], backgroundColor: colors.background }}>
|
|
169
170
|
<Text size="sm" weight="semibold" style={{ flex: 1 }}>Carton design</Text>
|
|
170
|
-
{phase !== "empty" ? <Button title="New" color="secondary"
|
|
171
|
+
{phase !== "empty" ? <Button title="New" color="secondary" icon="plus" onPress={newSession} /> : null}
|
|
171
172
|
</View>
|
|
172
173
|
|
|
173
174
|
{/* canvas region — a pannable + zoomable surface (DRAG to pan, the zoom pill
|
|
@@ -208,7 +209,7 @@ export function TplDieline() {
|
|
|
208
209
|
<View style={{ position: "absolute", left: 0, right: 0, bottom: 24, alignItems: "center", paddingHorizontal: 16, pointerEvents: "none" }}>
|
|
209
210
|
<View style={{ width: "100%", maxWidth: 620, pointerEvents: "auto" }}>
|
|
210
211
|
{phase === "working" ? (
|
|
211
|
-
<AgentProgress
|
|
212
|
+
<AgentProgress items={liveItems} state="streaming" />
|
|
212
213
|
) : (
|
|
213
214
|
<Composer
|
|
214
215
|
value={prompt}
|
|
@@ -217,7 +218,7 @@ export function TplDieline() {
|
|
|
217
218
|
sendDisabled={prompt.trim().length === 0 && !attachment}
|
|
218
219
|
placeholder={designReady ? "Describe a change — “make it 5 mm taller”" : "Attach a photo, add a note, then send…"}
|
|
219
220
|
actionsButton={
|
|
220
|
-
<
|
|
221
|
+
<IconButton icon="paperclip" size="lg" color="secondary" accessibilityLabel="Attach a photo" onPress={() => setAttachment(MOCK_PHOTO)} />
|
|
221
222
|
}
|
|
222
223
|
files={attachment ? <FileThumbnail file={attachment} size={56} onRemove={() => setAttachment(null)} /> : null}
|
|
223
224
|
/>
|
|
@@ -272,7 +273,7 @@ function ParamsPanel({ dims, onEditParam, onDownload }: { dims: Dims; onEditPara
|
|
|
272
273
|
))}
|
|
273
274
|
</View>
|
|
274
275
|
|
|
275
|
-
<Button title="Download" color="primary"
|
|
276
|
+
<Button title="Download" color="primary" icon="file-down" alignSelf="stretch" onPress={onDownload} />
|
|
276
277
|
</>
|
|
277
278
|
) : null}
|
|
278
279
|
</View>
|
|
@@ -7,7 +7,7 @@ import { Avatar } from "@lotics/ui/avatar";
|
|
|
7
7
|
import { Badge } from "@lotics/ui/badge";
|
|
8
8
|
import { Button } from "@lotics/ui/button";
|
|
9
9
|
import { ChipGroup } from "@lotics/ui/chip_group";
|
|
10
|
-
import {
|
|
10
|
+
import { FilterChip } from "@lotics/ui/filter_chip";
|
|
11
11
|
import { Counter } from "@lotics/ui/counter";
|
|
12
12
|
import { Card } from "@lotics/ui/card";
|
|
13
13
|
import { Divider } from "@lotics/ui/divider";
|
|
@@ -204,7 +204,7 @@ export function TplDirectory() {
|
|
|
204
204
|
value={tab}
|
|
205
205
|
onValueChange={setTab}
|
|
206
206
|
/>
|
|
207
|
-
<
|
|
207
|
+
<FilterChip
|
|
208
208
|
label="Min orders"
|
|
209
209
|
summary={minOrders > 0 ? `≥ ${minOrders}` : undefined}
|
|
210
210
|
onClear={() => setMinOrders(0)}
|
|
@@ -222,7 +222,7 @@ export function TplDirectory() {
|
|
|
222
222
|
format={(n) => `${n} orders`}
|
|
223
223
|
/>
|
|
224
224
|
</View>
|
|
225
|
-
</
|
|
225
|
+
</FilterChip>
|
|
226
226
|
</View>
|
|
227
227
|
|
|
228
228
|
{/* the register: one card, hairline-separated rows */}
|
package/examples/tpl_dossier.tsx
CHANGED
|
@@ -20,7 +20,7 @@ import { Popover, PopoverContent, PopoverFooter, PopoverTrigger } from "@lotics/
|
|
|
20
20
|
import { Table, TableRow, TableCell, type TableColumn } from "@lotics/ui/table";
|
|
21
21
|
import { SearchInput } from "@lotics/ui/search_input";
|
|
22
22
|
import { ChipGroup } from "@lotics/ui/chip_group";
|
|
23
|
-
import {
|
|
23
|
+
import { FilterChip, selectSummary } from "@lotics/ui/filter_chip";
|
|
24
24
|
import { PickerMenu } from "@lotics/ui/picker_menu";
|
|
25
25
|
import { cycleSort, sortBy, type SortState } from "@lotics/ui/sort_header";
|
|
26
26
|
import { Combobox } from "@lotics/ui/combobox";
|
|
@@ -101,7 +101,7 @@ const TABS: { value: Tab; label: string }[] = [
|
|
|
101
101
|
|
|
102
102
|
const PAGE_SIZE = 8;
|
|
103
103
|
|
|
104
|
-
// Distinct case handlers — the secondary filter dimension (a multi-select
|
|
104
|
+
// Distinct case handlers — the secondary filter dimension (a multi-select FilterChip).
|
|
105
105
|
const ASSIGNEES = [...new Set(HO_SO.map((r) => r.phuTrach))].map((n) => ({ label: n, value: n }));
|
|
106
106
|
|
|
107
107
|
// Columns defined ONCE — Table renders the header from these and each
|
|
@@ -331,7 +331,7 @@ export function TplDossier() {
|
|
|
331
331
|
/>
|
|
332
332
|
|
|
333
333
|
{/* status chips (the hot dimension, inline) + a secondary Assignee
|
|
334
|
-
multi-select
|
|
334
|
+
multi-select FilterChip — the same faceted band as Approvals */}
|
|
335
335
|
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8, alignItems: "center" }}>
|
|
336
336
|
<View style={{ flexGrow: 1, flexBasis: 240, minWidth: 200, maxWidth: 360 }}>
|
|
337
337
|
<SearchInput
|
|
@@ -350,7 +350,7 @@ export function TplDossier() {
|
|
|
350
350
|
value={tab}
|
|
351
351
|
onValueChange={chonTab}
|
|
352
352
|
/>
|
|
353
|
-
<
|
|
353
|
+
<FilterChip
|
|
354
354
|
label="Assignee"
|
|
355
355
|
summary={assigneeSummary}
|
|
356
356
|
onClear={() => { setAssignee([]); setPage(0); }}
|
|
@@ -363,10 +363,10 @@ export function TplDossier() {
|
|
|
363
363
|
value={assignee}
|
|
364
364
|
onValueChange={(v) => { setAssignee(v); setPage(0); }}
|
|
365
365
|
/>
|
|
366
|
-
</
|
|
366
|
+
</FilterChip>
|
|
367
367
|
{/* Single-select sibling — the render-prop `close` shuts the popover on
|
|
368
368
|
pick (the multi above stays open to collect several values). */}
|
|
369
|
-
<
|
|
369
|
+
<FilterChip
|
|
370
370
|
label="Fee"
|
|
371
371
|
summary={feeStatus === "paid" ? "Paid" : feeStatus === "unpaid" ? "Unpaid" : undefined}
|
|
372
372
|
onClear={() => { setFeeStatus(null); setPage(0); }}
|
|
@@ -380,7 +380,7 @@ export function TplDossier() {
|
|
|
380
380
|
onRequestClose={close}
|
|
381
381
|
/>
|
|
382
382
|
)}
|
|
383
|
-
</
|
|
383
|
+
</FilterChip>
|
|
384
384
|
</View>
|
|
385
385
|
|
|
386
386
|
{/* the register — eyebrow band (Table header) → list rows → totals + paging */}
|
package/examples/tpl_draft.tsx
CHANGED
|
@@ -95,7 +95,7 @@ export function TplDraft() {
|
|
|
95
95
|
{phase === "sent" ? (
|
|
96
96
|
<Card>
|
|
97
97
|
<CompletionState title="Reply sent" summary="Your edited draft was sent to Maya Tran.">
|
|
98
|
-
<Button title="Draft another" color="secondary"
|
|
98
|
+
<Button title="Draft another" color="secondary" onPress={() => { setPhase("idle"); setDraft(""); }} />
|
|
99
99
|
</CompletionState>
|
|
100
100
|
</Card>
|
|
101
101
|
) : (
|
|
@@ -129,7 +129,7 @@ export function TplDraft() {
|
|
|
129
129
|
|
|
130
130
|
{phase === "idle" ? (
|
|
131
131
|
<View style={{ alignItems: "flex-start", paddingTop: 4 }}>
|
|
132
|
-
<Button title="Draft a reply" color="primary"
|
|
132
|
+
<Button title="Draft a reply" color="primary" onPress={startDraft} />
|
|
133
133
|
</View>
|
|
134
134
|
) : null}
|
|
135
135
|
|
|
@@ -151,8 +151,8 @@ export function TplDraft() {
|
|
|
151
151
|
{phase === "ready" ? (
|
|
152
152
|
<CardFooter>
|
|
153
153
|
<View style={{ flex: 1 }} />
|
|
154
|
-
<Button title="Regenerate" color="secondary"
|
|
155
|
-
<Button title="Send reply" color="primary"
|
|
154
|
+
<Button title="Regenerate" color="secondary" icon="rotate-ccw" onPress={startDraft} />
|
|
155
|
+
<Button title="Send reply" color="primary" onPress={() => setPhase("sent")} />
|
|
156
156
|
</CardFooter>
|
|
157
157
|
) : null}
|
|
158
158
|
</Card>
|