@lotics/ui 3.6.0 → 4.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.
Files changed (54) hide show
  1. package/AGENTS.md +323 -0
  2. package/examples/app_orders.tsx +405 -0
  3. package/examples/tpl_allocate.tsx +120 -0
  4. package/examples/tpl_approvals.tsx +375 -0
  5. package/examples/tpl_attendance.tsx +355 -0
  6. package/examples/tpl_batch.tsx +234 -0
  7. package/examples/tpl_calendar.tsx +288 -0
  8. package/examples/tpl_callsheet.tsx +481 -0
  9. package/examples/tpl_convert.tsx +490 -0
  10. package/examples/tpl_crm_desk.tsx +541 -0
  11. package/examples/tpl_dashboard.tsx +554 -0
  12. package/examples/tpl_detail.tsx +232 -0
  13. package/examples/tpl_directory.tsx +263 -0
  14. package/examples/tpl_dispatch.tsx +289 -0
  15. package/examples/tpl_dossier.tsx +431 -0
  16. package/examples/tpl_intake.tsx +206 -0
  17. package/examples/tpl_inventory.tsx +299 -0
  18. package/examples/tpl_order.tsx +483 -0
  19. package/examples/tpl_pick.tsx +240 -0
  20. package/examples/tpl_quick.tsx +210 -0
  21. package/examples/tpl_reconcile.tsx +275 -0
  22. package/examples/tpl_record.tsx +301 -0
  23. package/examples/tpl_record_plain.tsx +154 -0
  24. package/examples/tpl_rollup.tsx +300 -0
  25. package/examples/tpl_run.tsx +235 -0
  26. package/examples/tpl_settings.tsx +178 -0
  27. package/examples/tpl_shifts.tsx +421 -0
  28. package/examples/tpl_stock.tsx +387 -0
  29. package/examples/tpl_timeline.tsx +244 -0
  30. package/examples/tpl_tower.tsx +356 -0
  31. package/examples/tpl_wizard.tsx +223 -0
  32. package/package.json +11 -2
  33. package/src/bar_chart.tsx +5 -0
  34. package/src/combobox.tsx +22 -6
  35. package/src/form_date_picker.tsx +2 -0
  36. package/src/form_picker.tsx +1 -0
  37. package/src/form_switch.tsx +1 -0
  38. package/src/form_text_input.tsx +2 -0
  39. package/src/icon.tsx +2 -0
  40. package/src/icon_button.tsx +5 -2
  41. package/src/inline_date_picker.tsx +110 -0
  42. package/src/inline_edit.tsx +228 -0
  43. package/src/inline_number_input.tsx +70 -0
  44. package/src/inline_select.tsx +91 -0
  45. package/src/inline_text_input.tsx +71 -0
  46. package/src/inline_time_picker.tsx +64 -0
  47. package/src/line_chart.tsx +4 -0
  48. package/src/list_item.tsx +5 -0
  49. package/src/number_input.tsx +12 -1
  50. package/src/page_content.tsx +5 -0
  51. package/src/section_heading.tsx +43 -29
  52. package/src/tag_input.tsx +202 -0
  53. package/src/time_picker.tsx +15 -3
  54. package/src/tooltip.tsx +19 -0
package/AGENTS.md ADDED
@@ -0,0 +1,323 @@
1
+ # @lotics/ui — the UI reference
2
+
3
+ A React-Native-Web component kit (renders on web **and** native). This file is the design
4
+ law + the "reach for what" guide. The **exact API** of any component is its source, which
5
+ ships with the package: read `node_modules/@lotics/ui/src/<name>.tsx`. **Never guess a prop —
6
+ open the file.** If the closest component lacks a capability, **extend it** (a platform change
7
+ every app inherits), never inline a one-off `View`/`Text` rebuild — that forfeits the
8
+ typeahead, async search, virtualization, and a11y the primitive already ships.
9
+
10
+ - Import per module: `import { Combobox } from "@lotics/ui/combobox"`.
11
+ - Floating content (`Dialog` · `Popover` · `Tooltip` · `Alert` · `CommandMenu`) needs a
12
+ `PortalHost` at the app root.
13
+ - RN-Web only: `View`/`ScrollView` from `react-native`, the `Text` primitive (no raw
14
+ `div`/`span`, no raw `fontSize`/`fontWeight`); styles are RN objects.
15
+ - i18n: most components are string-free; the few that emit their own user-facing strings take
16
+ a `labels` prop (`DateRangeFilterField`, `Pagination`, `SortHeader`/`Table.sortLabels`) —
17
+ localize there.
18
+
19
+ ---
20
+
21
+ ## Reach by role — what exists
22
+
23
+ Pick by capability, not by name. (→ the source file for the API.)
24
+
25
+ - **Actions** — `Button` (labelled; `color` = emphasis/risk, `shape` pill|rounded),
26
+ `IconButton` (icon-only, needs `accessibilityLabel`/`tooltip`), `LinkButton` (quiet ghost
27
+ action — Clear / Select all), `PillButton` (dismissible facet chip). A button is never a raw
28
+ `Pressable`.
29
+ - **Pick from a list** — `Picker` (known options, native typeahead, single/multi/custom-render,
30
+ no search box). Search-as-you-type / async / create-new → `Combobox`. A selectable card row →
31
+ `CardSelectItem`.
32
+ - **Tags / multi-select chip box** — `TagInput` (chips + an Add-popover checklist + create), NOT
33
+ `Combobox multi` (see §Data entry).
34
+ - **Text & form** — `TextInputField`, `NumberInput`, `SearchInput`; wrap with `FormField`;
35
+ `Checkbox`, `Switch`, `RadioPicker`; dates via `DatePicker` / `DateRangeFilterField`, times
36
+ via `TimePicker`.
37
+ - **Edit a record's fields in place** — the `Inline*` family: `InlineTextInput` ·
38
+ `InlineNumberInput` · `InlineSelect` · `InlineDatePicker` · `InlineTimePicker` (see §Data entry).
39
+ - **Tabular data** — `Table` (columns defined once; sortable headers via `SortHeader`; paired
40
+ with `Pagination`). Never an HTML `<table>` or a `.map` of rows.
41
+ - **Numbers & charts** — `KPIStrip` / `KPICard` / `Metric` (headline figures), `TrendChip`
42
+ (delta), `Sparkline`, `BarChart` / `LineChart` / `PieChart` (the canonical SVG set — no
43
+ recharts), `RingGauge`, `ProgressBar` / `StackedProgressBar` / `StepProgress`, `Breakdown`,
44
+ `StatusGrid` + `StatusLegend`, `Heatmap`.
45
+ - **Surfaces & layout** — `Card` (+ `CardHeader`/`CardHeaderTitle`/`CardHeaderMeta`/`CardBody`/
46
+ `CardFooter`), `SectionCard`, `SectionHeading` (+ `SectionHeadingTitle`), `PageHeader` /
47
+ `PageContent`, `Stack`, `Spacer`, `Divider`, `Accordion`, `Tabs`, `SegmentedControl`, `Stepper`.
48
+ - **Rows & registers** — `PressableRow` (THE register row), `ListItem`, `MenuButton`,
49
+ `MenuListItem`, `DetailRow` (label+value for drawers/peeks), `ActionMenu` (⋯), `FloatingActionBar`
50
+ (bulk-select bar).
51
+ - **Filters & view controls** — `SearchInput`, `ChipGroup`, `FilterPill` (+ `RangeSlider`,
52
+ `Counter`), `PillButton`.
53
+ - **Overlays** — `Dialog`, `Drawer` (+ `DrawerFooter`), `Popover`, `Tooltip`, `CommandMenu`,
54
+ `Alert` (the blocking confirm).
55
+ - **Status / feedback** — `Badge` / `StatusBadge`, `Callout` (inline status), `EmptyState`,
56
+ `CompletionState`, `ActivityIndicator` / `Loading`, `Skeleton`.
57
+ - **Files** — `FileDropzone`, `FileThumbnail` / `FileThumbnailGrid`, `FilePreview` /
58
+ `FileGalleryModal`, `ImageGallery` (see §Data entry → Attachments).
59
+ - **Specialized work surfaces** — `ScanField` (scan/verify), `StepList` (guided run),
60
+ `RemainderMeter` + `AllocationRow` (allocation), `Timeline`, `Calendar`, `Gantt`,
61
+ `comments_thread`.
62
+
63
+ ---
64
+
65
+ ## Data entry — which pattern for which job
66
+
67
+ This is the most common thing to get right. Match the JOB to the pattern:
68
+
69
+ | You're capturing… | Reach for | Why |
70
+ |---|---|---|
71
+ | an EXISTING record's fields | **Inline edit** (`Inline*`) | edit in place, no form mode |
72
+ | a brand-NEW record (a form) | **fieldset form** (`FormField` + the 2-col grid) | structured entry + a Save |
73
+ | a RELATED record (pick or make) | **find-or-create** (`Combobox allowCustom`) | one control covers both |
74
+ | REPEATING rows you build & revise | **line items** (create→preview→edit) | add / edit / remove, live totals |
75
+ | a multi-value TAG field | **`TagInput`** | a chip box, not a search input |
76
+ | a STATUS with terminal outcomes | **disposition** (open → resolve → revise) | guides the decision |
77
+ | FILES | **attachment field** (dropzone + grid + gallery) | add / preview / delete |
78
+ | many entries FAST | **quick capture** (one row, Enter to add) | repeat-entry speed |
79
+ | a state TRANSITION mid-flow | **stage gate** (popover / dialog by weight) | right-sized friction |
80
+
81
+ ### Inline edit — the preferred way to edit an existing record
82
+ When the whole record is editable (a detail/record screen, dense settings), don't wrap it in a
83
+ form mode or a preview↔edit card — make each VALUE inline-editable: it reads as plain text,
84
+ hover tints it (no pencil — that shifts), click swaps the input in **at the same height** (zero
85
+ reflow, the whole point), and it commits on blur (Enter saves, Escape reverts) or via
86
+ `controls="buttons"` (✓ primary / ✕). One per type — `InlineTextInput` · `InlineNumberInput`
87
+ (`format` for currency/units) · `InlineSelect` (plain options OR `renderOptionContent`; floats a
88
+ `Picker` menu in a popover so the row never grows) · `InlineDatePicker` · `InlineTimePicker` — all
89
+ on `useInlineEdit` + `InlineEditView` (custom inputs join via those). `onSave` is async: the
90
+ saving spinner sits INSIDE the control (never a sibling — that reflows); an error shows inline
91
+ without losing the edit. Pair with `DetailRow` (label left, inline value right). Not every field
92
+ is a same-height swap — a tag field, a status, or an attachment grid edit in place too (below).
93
+
94
+ ### Fieldset form — fields lay out on a RESPONSIVE two-column grid
95
+ Never hard-code columns, never 3-up. A fieldset is `flexDirection:row, flexWrap:wrap,
96
+ columnGap:16`; each `FormField` declares an intrinsic width — `half` (`flexGrow:1,
97
+ flexBasis:240`) or `full` (`flexGrow:1, flexBasis:"100%"`) — so two halves sit side-by-side on a
98
+ wide card and stack on a narrow one with zero media queries. Pair short, related fields as halves
99
+ (phone/email, qty/price); give long or singular values the full row (legal name, address, notes).
100
+ Past two columns the label→field link breaks — MANY inputs means GROUPING into labeled fieldsets
101
+ (each its own 2-up grid, `Divider` between), never a third column. `FormDatePicker`/`FormPicker`
102
+ wrap their OWN `FormField` (and omit `style`) — for a grid cell use a bare `FormField style={half}`
103
+ wrapping `DatePicker`/`Picker`.
104
+
105
+ ### Find-or-create — `Combobox` IS the control
106
+ `Combobox` is a SELECT by default (no leading icon, a trailing chevron); opt INTO the search-box
107
+ look with `icon="search"` only when typing-to-search is primary (a large/remote set).
108
+ `allowCustom` appends a "Create …" row (`customOptionLabel`) when the query matches no option,
109
+ emitting the typed text as the value — so a value not in the known set means CREATE. Wire that
110
+ branch to a create overlay (a modal `Dialog` for 4+ fields, an anchored popover for 1–3) that
111
+ builds the new record and attaches it; existing matches attach directly. The create row sits
112
+ BELOW matches by default (the keyboard highlight lands on the first MATCH, so Enter on a partial
113
+ picks it, never a duplicate); pass `customOptionPlacement="top"` to pin it above. Use
114
+ `reflectSelection={false}` and render the attached record below as a card with a Change action.
115
+
116
+ ### Line items — create → preview → edit (a composition, not a primitive)
117
+ For a list of records you build then revise (invoice rows, repair lines, config entries), each
118
+ item is a `Card` toggling between a read-only PREVIEW (a stack of `DetailRow`s) and an EDIT form
119
+ (the inputs), via a local `editing` flag + Edit/Save/Cancel in the `CardFooter`. A freshly-added
120
+ item opens in edit; Save collapses it to the preview; Edit reopens it. **Edit is cancelable** —
121
+ snapshot the item on Edit so Cancel reverts, or DISCARDS a freshly-added one. Duplicate sits left;
122
+ the destructive **Delete is `danger-secondary`**; Cancel + the Save/Edit toggle sit right. Every
123
+ screen composes its own (~15 lines) so the preview rows + form fit the data.
124
+
125
+ ### Tag field — `TagInput`, NOT `Combobox multi`
126
+ A tag field's resting state should be a tidy CHIP BOX: `TagInput` is a bordered box of removable
127
+ chips + an Add affordance; searching/creating happens in a POPOVER (a searchable, checkable list +
128
+ an optional `allowCreate` row), never an inline token input. `Combobox multi` squeezes a
129
+ `TextInput` among the chips — right for a SEARCH field that accumulates picks, wrong for a chip box
130
+ you occasionally add to. One owner per shape: chips-you-edit → `TagInput`; search-that-accumulates
131
+ → `Combobox multi`.
132
+
133
+ ### Disposition — lifecycle status is ASYMMETRIC by phase
134
+ When a status is an OPEN default plus terminal OUTCOMES the user decides (Lead → Closed/Lost, Draft
135
+ → Confirmed/Cancelled, Open → Approved/Rejected), a 3-way segmented/dropdown is wrong — it treats a
136
+ lifecycle as flat peers and guides neither the decision nor the revision. Split by phase: while
137
+ OPEN, surface the outcomes as ACTIONS with valence (`Mark closed` = `primary`, `Mark lost` =
138
+ `danger-secondary`; the open state a quiet dot `Badge`). Coloring the buttons is right here — a
139
+ SINGLE record-level decision is not the approvals-queue "wall of loud buttons." Once RESOLVED, show
140
+ the colored STATE (`Badge variant="dot"` — emerald positive / red negative / blue open) with a
141
+ quiet, REVERSIBLE **Change**: a popover STATE SWITCHER (each state a colored dot + label, current
142
+ marked + disabled), not a generic text menu. A composition (Badge + Button + Popover/MenuButton).
143
+
144
+ ### Attachments — a full add / preview / DELETE field
145
+ Capture with `<FileDropzone onFiles accept label hint dropLabel height>` (drag-over lights the
146
+ accent; click falls back to a picker); display what landed with `<FileThumbnailGrid files>` ABOVE
147
+ the dropzone (existing files are the content; the dropzone sinks to the bottom as the "add more"
148
+ affordance — only the empty state leads with it). Make it CRUDable by wiring the grid's two
149
+ callbacks: `onFilePress` → set a `number|null` index that drives `<FileGalleryModal files
150
+ activeIndex onIndexChange>` (built-in prev/next/ESC/rotate); `onRemove` → drop the file from state
151
+ (the grid renders a ✕ on each thumbnail automatically). Keep label+grid+dropzone on a `gap` (a bare
152
+ `CardBody` has none). `FileThumbnail` shows the right surface per MIME (image thumbnail · doc card ·
153
+ media card). Never hand-build a drop well, a file row grid, or a preview modal.
154
+
155
+ ### Quick capture — the SPEED surface
156
+ For fast repeat entry (logging activity, expenses), one compact capture row (a `SegmentedControl`
157
+ mode, a `Combobox`-as-select, a `Counter`, an Enter-to-submit `TextInputField`); each entry commits
158
+ on its own into an undoable log below, the field keeps focus and the prior choices carry over. No
159
+ screen-wide Save.
160
+
161
+ ### Stage gates — tiered by weight
162
+ A transition that needs NO input is one click. 1–3 quick fields → a POPOVER FORM anchored to its
163
+ action button (title + one-line stake + `FormField`s; confirm in `PopoverFooter`). A
164
+ destructive/exception path (anything touching money or locks) → a `Dialog` (consequence in prose,
165
+ danger confirm + cancel in `DialogFooter`) or an `Alert.alert(title, message, [{cancel},
166
+ {destructive}])` confirm. Never an inline expanding panel for a gate — it shifts layout and loses
167
+ the "this is a gate" framing.
168
+
169
+ ---
170
+
171
+ ## Composition grammar
172
+
173
+ - **Canvas**: full-bleed `colors.zinc[50]` ScrollView; content column `maxWidth` 880–1040,
174
+ centered, `padding: 28`. Cards float on the canvas — never white-on-white.
175
+ - **Heading vocabulary — ONE construct per altitude, no drift.**
176
+ · **Page band**: one per screen — `Text size="xl" weight="semibold"` title + one `sm muted`
177
+ subtitle that says what the screen decides. Right side: the screen's ONE primary action and/or
178
+ a period filter — never a summary Badge (those belong to the KPI strip).
179
+ · **Card header** (a card's own title band, or separate banded cards): `CardHeader` +
180
+ `CardHeaderTitle` (`sm semibold`; `info` when the title alone doesn't define the numbers).
181
+ · **Section title** (a substantial titled block — a form, a timeline, a field group — INSIDE one
182
+ multi-section card or drawer): a `Text size="sm" weight="semibold"` at the TOP of the section's
183
+ content, sections separated by ONE `Divider`. Do NOT wrap each section in its own `CardHeader`
184
+ (its auto-divider double-stacks hairlines around every title). A card-less full page uses
185
+ `SectionHeading` + `SectionHeadingTitle`. NEVER a bare eyebrow standing in for a section title.
186
+ · **Eyebrow** (`<Text size="xs" color="muted" transform="uppercase">`, WEIGHTLESS): ONLY a
187
+ column header or a truly minor one-line label — not a section that owns real content. Don't add
188
+ `weight`. (A COLORED status word like "Blocked" — `weight="semibold"` + `color="danger"` — is a
189
+ different element, the verdict word, and keeps its weight.)
190
+ · **Gate header**: a `Dialog` uses `DialogHeaderTitle`; a popover form uses `Text size="sm"
191
+ weight="semibold"` + an optional `xs muted` subtitle.
192
+ - **Time-constrained data gets a period filter** in the header band — `DateRangeFilterField`, never
193
+ a static period badge. Every period-dependent number MUST follow the selection.
194
+ - **Cards are banded — and composable** (all from `@lotics/ui/card`):
195
+ ```tsx
196
+ <Card style={{ padding: 0 }}>
197
+ <CardHeader><CardHeaderTitle info="what this shows">Title</CardHeaderTitle><CardHeaderMeta>12</CardHeaderMeta></CardHeader>
198
+ <CardBody>…</CardBody> {/* repeat with <Divider/> between */}
199
+ <CardFooter>hint(flex:1) + actions</CardFooter>
200
+ </Card>
201
+ ```
202
+ Never nest cards; never hand-compose a title or footer band. `info` is standard, not garnish.
203
+ - **The stat band**: `<KPIStrip items={[{label, value, trend?, caption?, tone?, info?}…]}>` is the
204
+ ONE way to open a screen with numbers. KPI strips are INFORMATIONAL — they never filter or
205
+ navigate (that's the tabs/chips' job), and they carry cross-cutting health numbers the tabs
206
+ can't show; if there are none, drop the strip. Card stat rails use `KPICard`.
207
+ - **Numbers**: free-standing numerals `<Text tabular>`. Money: `formatMoney(n)` from
208
+ `@lotics/ui/format_money` (`compact` for strip captions). Dates: `formatDate(value, opts)` from
209
+ `@lotics/ui/format_date` (`format:"datetime"`, `compact` → dd/MM). Never hand-roll grouping/₫ or
210
+ dd/MM.
211
+ - **Every number is a door** (except the KPI strip). A component that summarizes records leads to
212
+ the records behind it when pressed — switch to the filtered list, expand in place, or navigate.
213
+ Expansion happens IMMEDIATELY below the pressed element — the composable `Accordion` family
214
+ (`AccordionHeader`/`AccordionTitle`/`AccordionMeta`/`AccordionContent`); header-only = a plain row
215
+ of identical rhythm, so lists mix expandable + static rows.
216
+ - **No dead rows.** Every listed record is actionable. PRIMARY entity rows press-open the workspace
217
+ `Drawer` (sequenced); read-only drill-downs expand via `Accordion` or glance via `Peek`; every
218
+ other row gets an `ActionMenu` (⋯ → MenuButton items: destructive last + **confirmed** via
219
+ `Alert`). The whole surface is the door: register rows are `PressableRow` (full-bleed wash incl.
220
+ nested controls). Two row patterns, never un-separated:
221
+ · **Register (default)** — px-20 square rows, wash to the card edges, separated by full-bleed
222
+ `Divider`s (`{i > 0 ? <Divider/> : null}`), NO wrapping padding. A COLUMNAR register is
223
+ `Table`/`TableRow`/`TableCell` (columns defined once → header + widths can't drift); paginate
224
+ OUTSIDE (slice + `Pagination` in the `CardFooter`).
225
+ · **Inset contained list** — grouped/gap-separated lists: `PressableRow variant="inset"` in a
226
+ padded container, NO `Divider`s (the gap + rounded hover separate them).
227
+ Right-hand columns align only if every trailing element is FIXED-width (give each trailing action
228
+ a fixed `width`, so amount/status columns don't jitter).
229
+ - **Master-detail = list + workspace `Drawer` with sequencing.** Pressing a PRIMARY entity row
230
+ opens the record workspace in a `Drawer` with `onPrev`/`onNext`/`position` ("3/24") over the
231
+ visible ordering (←/→ built in). Key the drawer body by record id so per-record state resets on
232
+ step. Facts are `DetailRow`s; the commit bar is a `DrawerFooter`. The open row shows a selected
233
+ highlight (and `marked` — a resting blue tint — for a bulk-ticked row). `Peek` is ONLY for
234
+ secondary references, never the primary row press.
235
+ - **One view-control vocabulary** (all 40px, `sm` labels, in one wrapping band):
236
+ · `SearchInput` is THE search box — a rounded white pill with a thin zinc-300 border. Never a
237
+ bare `TextInputField` + search icon.
238
+ · `ChipGroup` is THE one-of-N lens (≤ ~10 options the user flips between; include "All"; counts in
239
+ the label). Filters ONE list to a SUBSET — including by process stage / lifecycle state.
240
+ · `FilterPill` is THE secondary-dimension filter — a compact pill that opens a composed popover
241
+ editor (`PickerMenu multi` / `RangeSlider` / `Counter` / date range). Single-select closes on
242
+ pick via the `{({close}) => …}` render prop.
243
+ · `Tabs` switch between VIEWS/sections (different content/layout) — never a subset of one list.
244
+ `SegmentedControl` chooses a MODE/PARAMETER of the SAME view (2–4 peers, no panel swap).
245
+ - **Footer actions align RIGHT.** `PopoverFooter`/`DialogFooter`/`DrawerFooter` default
246
+ `align="end"` — commit at the right edge, secondary to its left. Never left-flow a Save/Submit.
247
+ - **Empty result**: `<EmptyState message hint? icon? action?>` — never a bare muted Text.
248
+ - **Inline status**: `<Callout tone="info|success|warning|error|neutral">` — compound (like Card):
249
+ compose `CalloutTitle`/`CalloutText`/`CalloutActions` inside. `Callout` is INLINE; `Alert` is the
250
+ blocking modal; `Badge` is a one-word pill.
251
+ - **One primary action per surface.** Everything else secondary/muted; destructive = `danger`
252
+ styling + explicit label. (A grouped BUILDER section — line items — MAY give its own Add action
253
+ `primary`; it sits at a different altitude than the form's one terminal commit.)
254
+ - **Button labels carry no trailing ellipsis** ("Assign", not "Assign…"). **Button color is
255
+ VALENCE/RISK, never category**: the ladder `muted < secondary < primary` is the emphasis axis,
256
+ `danger` marks destructive — that's the whole axis. No "success"/green. Decision UIs put
257
+ positive/negative color on the STATUS (dot) and verdict (colored `Text`), not the buttons.
258
+ - **Color discipline** — every status / data-viz / accent color comes from a NAMED helper, never a
259
+ hand-picked `colors.<family>[shade]`: `solid(name)` = the 500 (dots, series, meter accents);
260
+ `tint(name, α)` = a wash; `ramp(name, count)` = N shades of ONE family for a coherent dimension.
261
+ A status is ONE family NAME (`{ color: "emerald" }`) and every weight derives from it. One accent
262
+ per screen purpose (blue=pipeline, emerald=money, red=danger, amber=waiting). Direct `colors.*` is
263
+ reserved for NEUTRALS (`zinc`, `border`, `white`, the `*[50]` selection washes).
264
+ - **Status indicators have a WEIGHT — match to prominence, never to a metric.** colored `Text` (a
265
+ verdict WORD in a header) < `Badge variant="dot"` (a categorical STATE in a scannable column /
266
+ legend) < `Badge` tonal pill (a record's PROMINENT status, a drawer/detail header). A register's
267
+ dense rows read lighter — the row's primary status is `variant="dot"`, its drawer twin tonal. A
268
+ `Badge` is never a metric value.
269
+ - **Typography**: only the `Text` primitive (size/weight/color/transform). Uppercase tracking is
270
+ built in.
271
+ - **Touch & whitespace**: anything pressable is ≥ 40px tall (8px min gap between pressables). 16px
272
+ between cards, 24–28 canvas padding, 16–20 inside bands, 10–12 between content lines. Density
273
+ comes from alignment + hierarchy, not cramming.
274
+
275
+ ---
276
+
277
+ ## Worked examples (templates)
278
+
279
+ Full worked-example screens ship in the package — **read the source at
280
+ `node_modules/@lotics/ui/examples/<name>.tsx`** (pure `@lotics/ui` + mock data, each a usable
281
+ recipe for a screen JOB; copy and adapt). Pick by the job:
282
+
283
+ - **Monitor & decide** — `tpl_dashboard` (KPIStrip + trends + attention list) · `tpl_stock`
284
+ (10k-record faceted funnel) · `tpl_tower` (live `StatusGrid` wallboard) · `tpl_rollup`
285
+ (hierarchical totals).
286
+ - **Work execution** — `app_orders` (stage-gated work queue) · `tpl_crm_desk` (assign & work +
287
+ `FloatingActionBar`) · `tpl_callsheet` (call console) · `tpl_convert` (staged conversion) ·
288
+ `tpl_inventory` (threshold actions) · `tpl_dossier` (high-volume paginated register) ·
289
+ `tpl_approvals` (verdicts: bulk + over-policy `Dialog`) · `tpl_reconcile` (pair matching) ·
290
+ `tpl_dispatch` (capacity allocation) · `tpl_batch` (compose from parts) · `tpl_pick` (guided
291
+ `ScanField` run) · `tpl_allocate` (`RemainderMeter` split) · `tpl_run` (preview → resolve → post).
292
+ - **Data capture** — `tpl_intake` (multi-fieldset form + attachments) · `tpl_order` (the
293
+ transactional form: find-or-create + line items + attachment CRUD + 2-col grid) · `tpl_quick`
294
+ (quick-capture log) · `tpl_wizard` (`Stepper` form + review) · `tpl_record` (inline edit + Stage
295
+ disposition + `TagInput` + Documents grid; `tpl_record_plain` = card-less).
296
+ - **Records & lookup** — `tpl_detail` (full record + tabs) · `tpl_directory` (searchable register) ·
297
+ `tpl_timeline` (audit feed).
298
+ - **Planning & time** — `tpl_calendar` · `tpl_attendance` · `tpl_shifts`.
299
+ - **Administration** — `tpl_settings`.
300
+
301
+ ---
302
+
303
+ ## Full component inventory (import as `@lotics/ui/<module>`)
304
+
305
+ text · card (Card · CardHeader · CardHeaderTitle · CardHeaderMeta · CardBody · CardFooter) ·
306
+ section_heading (SectionHeading · SectionHeadingTitle — compound, owns no margin) · badge ·
307
+ status_badge · button · icon_button · link_button · pill_button · tabs · segmented_control ·
308
+ picker · combobox · tag_input (TagInput — chip box + Add-popover; for tags, not Combobox multi) ·
309
+ text_input_field · number_input · search_input · form_field · checkbox · checkbox_input · switch ·
310
+ radio_picker · counter · range_slider · date_picker · date_range_filter_field · time_picker ·
311
+ inline_text_input · inline_number_input · inline_select · inline_date_picker · inline_time_picker
312
+ (the Inline* family — per-field editors on `inline_edit`'s `useInlineEdit` + `InlineEditView`) ·
313
+ list · list_item · menu_button · menu_list_item · detail_row · pressable_row · action_menu ·
314
+ floating_action_bar · filter_pill · sort_header · table · pagination · accordion · stepper ·
315
+ step_progress · step_list · timeline · drawer (+ DrawerFooter) · dialog · popover · tooltip ·
316
+ command_menu · alert · peek · empty_state · completion_state · callout (Callout · CalloutTitle ·
317
+ CalloutText · CalloutActions) · kpi_card · kpi_strip · metric · trend_chip · sparkline ·
318
+ bar_chart · line_chart · pie_chart · ring_gauge · progress_bar · stacked_progress_bar · breakdown ·
319
+ status_grid (StatusGrid + StatusLegend) · heatmap · legend_item · remainder_meter · allocation_row ·
320
+ scan_field · file_dropzone · file_thumbnail · file_thumbnail_grid · file_preview ·
321
+ file_gallery_modal · image_gallery · avatar · skeleton · activity_indicator · loading · divider ·
322
+ spacer · stack · section_card · page_header · page_content · calendar (calendar/index.ts) · gantt ·
323
+ comments_thread · format_money · format_date · colors (solid · tint · ramp · ColorName).