@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.
- package/AGENTS.md +323 -0
- package/examples/app_orders.tsx +405 -0
- package/examples/tpl_allocate.tsx +120 -0
- package/examples/tpl_approvals.tsx +375 -0
- package/examples/tpl_attendance.tsx +355 -0
- package/examples/tpl_batch.tsx +234 -0
- package/examples/tpl_calendar.tsx +288 -0
- package/examples/tpl_callsheet.tsx +481 -0
- package/examples/tpl_convert.tsx +490 -0
- package/examples/tpl_crm_desk.tsx +541 -0
- package/examples/tpl_dashboard.tsx +554 -0
- package/examples/tpl_detail.tsx +232 -0
- package/examples/tpl_directory.tsx +263 -0
- package/examples/tpl_dispatch.tsx +289 -0
- package/examples/tpl_dossier.tsx +431 -0
- package/examples/tpl_intake.tsx +206 -0
- package/examples/tpl_inventory.tsx +299 -0
- package/examples/tpl_order.tsx +483 -0
- package/examples/tpl_pick.tsx +240 -0
- package/examples/tpl_quick.tsx +210 -0
- package/examples/tpl_reconcile.tsx +275 -0
- package/examples/tpl_record.tsx +301 -0
- package/examples/tpl_record_plain.tsx +154 -0
- package/examples/tpl_rollup.tsx +300 -0
- package/examples/tpl_run.tsx +235 -0
- package/examples/tpl_settings.tsx +178 -0
- package/examples/tpl_shifts.tsx +421 -0
- package/examples/tpl_stock.tsx +387 -0
- package/examples/tpl_timeline.tsx +244 -0
- package/examples/tpl_tower.tsx +356 -0
- package/examples/tpl_wizard.tsx +223 -0
- package/package.json +11 -2
- package/src/bar_chart.tsx +5 -0
- package/src/combobox.tsx +22 -6
- package/src/form_date_picker.tsx +2 -0
- package/src/form_picker.tsx +1 -0
- package/src/form_switch.tsx +1 -0
- package/src/form_text_input.tsx +2 -0
- package/src/icon.tsx +2 -0
- package/src/icon_button.tsx +5 -2
- package/src/inline_date_picker.tsx +110 -0
- package/src/inline_edit.tsx +228 -0
- package/src/inline_number_input.tsx +70 -0
- package/src/inline_select.tsx +91 -0
- package/src/inline_text_input.tsx +71 -0
- package/src/inline_time_picker.tsx +64 -0
- package/src/line_chart.tsx +4 -0
- package/src/list_item.tsx +5 -0
- package/src/number_input.tsx +12 -1
- package/src/page_content.tsx +5 -0
- package/src/section_heading.tsx +43 -29
- package/src/tag_input.tsx +202 -0
- package/src/time_picker.tsx +15 -3
- 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).
|