@lotics/ui 27.17.1 → 28.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 +13 -10
- package/MIGRATION.md +64 -0
- package/docs/catalog.md +97 -140
- package/docs/data_entry.md +57 -87
- package/docs/templates.md +95 -57
- package/examples/tpl_item_list.tsx +77 -36
- package/examples/tpl_record.tsx +372 -460
- package/package.json +3 -3
- package/src/checklist.tsx +339 -0
- package/src/comments_button.tsx +108 -0
- package/src/control_surface.ts +13 -0
- package/src/detail_row.tsx +2 -2
- package/src/field_annotations.tsx +1 -1
- package/src/icon_button.tsx +17 -1
- package/src/locale.tsx +12 -1
- package/src/stepper.tsx +107 -18
- package/src/stepper_layout.ts +21 -1
- package/src/table.tsx +101 -11
- package/src/pipeline.tsx +0 -231
- package/src/task.tsx +0 -518
- package/src/task_metrics.ts +0 -55
package/src/pipeline.tsx
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
2
|
-
import { View } from "react-native";
|
|
3
|
-
import { Text } from "./text";
|
|
4
|
-
import { Stepper, Step, type StepPositional, type StepStatus } from "./stepper";
|
|
5
|
-
|
|
6
|
-
export interface PipelineProps {
|
|
7
|
-
children?: ReactNode;
|
|
8
|
-
/** Accent for reached nodes + the spine. Defaults to `Stepper`'s neutral ink. */
|
|
9
|
-
color?: string;
|
|
10
|
-
accessibilityLabel?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A pipeline an item WALKS — ordered milestones where each one carries its own
|
|
15
|
-
* controls: the fields that milestone owns, the conditions attached to it, and
|
|
16
|
-
* the one act that leaves it.
|
|
17
|
-
*
|
|
18
|
-
* **Pick this over `TaskList` when the rows are STAGES, not work items.** A task
|
|
19
|
-
* list models N things you tick in any order, each row structurally identical.
|
|
20
|
-
* A pipeline models ONE thing moving through N positions, where the position
|
|
21
|
-
* decides what you can see and do — a dossier at "awaiting review" offers approve
|
|
22
|
-
* and return; the same dossier two stages on offers neither and shows a portal
|
|
23
|
-
* account instead. Rendering that as a checklist forces every row to carry every
|
|
24
|
-
* control, and the reader has to scan all of them to find the one that is theirs.
|
|
25
|
-
*
|
|
26
|
-
* **And over a bare `Stepper` when the stages need bodies.** `Stepper` renders
|
|
27
|
-
* position — done, current, upcoming on a spine. This adds the anatomy that turns
|
|
28
|
-
* position into a workspace: a title that reads by status, a meta line, per-stage
|
|
29
|
-
* fields, notes and actions.
|
|
30
|
-
*
|
|
31
|
-
* The rules the anatomy encodes:
|
|
32
|
-
* - **Only the current stage should carry an act.** A control on an unreached
|
|
33
|
-
* stage invites acting out of order; on a passed one it re-offers something
|
|
34
|
-
* already done. The component does not enforce this — the caller decides what
|
|
35
|
-
* each stage renders — but the styling assumes it.
|
|
36
|
-
* - **A condition belongs to its stage.** Something being wrong at stage 2 is a
|
|
37
|
-
* fact about stage 2, so `PipelineNote` sits inside it rather than floating
|
|
38
|
-
* above the run where it reads as "something is wrong with this record".
|
|
39
|
-
* - **A passed stage stays correctable.** Whatever a stage owns should remain
|
|
40
|
-
* editable behind you, or the only way to fix a mis-entry is direct table
|
|
41
|
-
* access.
|
|
42
|
-
*
|
|
43
|
-
* A stage's values stack UNDER its title by default (`PipelineField`), which
|
|
44
|
-
* costs two lines each. On a ladder long enough that this pushes the run past a
|
|
45
|
-
* screenful, `trailing` puts one value on the title's own row instead.
|
|
46
|
-
*
|
|
47
|
-
* ```tsx
|
|
48
|
-
* <Pipeline>
|
|
49
|
-
* <PipelineStage status="done" title="Submitted">
|
|
50
|
-
* <PipelineField label="Date"><InlineDatePicker … /></PipelineField>
|
|
51
|
-
* </PipelineStage>
|
|
52
|
-
* <PipelineStage status="done" title="Received" trailing={<InlineDatePicker … />} />
|
|
53
|
-
* <PipelineStage status="current" title="In review" meta="Waiting 3d, Ops">
|
|
54
|
-
* <PipelineNote tone="warning">Sent back — missing payslips.</PipelineNote>
|
|
55
|
-
* <PipelineActions>
|
|
56
|
-
* <Button title="Approve" color="primary" />
|
|
57
|
-
* <Button title="Return" color="danger-secondary" />
|
|
58
|
-
* </PipelineActions>
|
|
59
|
-
* </PipelineStage>
|
|
60
|
-
* <PipelineStage status="upcoming" title="Filed" />
|
|
61
|
-
* </Pipeline>
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
|
-
export function Pipeline({ children, color, accessibilityLabel }: PipelineProps) {
|
|
65
|
-
return (
|
|
66
|
-
<Stepper orientation="vertical" color={color} accessibilityLabel={accessibilityLabel}>
|
|
67
|
-
{children}
|
|
68
|
-
</Stepper>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface PipelineStageProps extends StepPositional {
|
|
73
|
-
status: StepStatus;
|
|
74
|
-
/** The milestone's name — the one thing every stage shows. */
|
|
75
|
-
title: string;
|
|
76
|
-
/** A muted line under the title: how long it has sat here, whose desk it is on.
|
|
77
|
-
* Prose, not a value the reader sets. */
|
|
78
|
-
meta?: string;
|
|
79
|
-
/**
|
|
80
|
-
* ONE value the stage owns, on the TITLE's row rather than stacked under it —
|
|
81
|
-
* the date a milestone was reached, its reference number. Use it when the
|
|
82
|
-
* ladder is long enough that a `PipelineField` per stage costs two lines each
|
|
83
|
-
* and pushes the whole run past a screenful; use `PipelineField` when the value
|
|
84
|
-
* needs a label to be read, or when there is more than one.
|
|
85
|
-
*
|
|
86
|
-
* The row is a TEXT line and the value sits on the title's own baseline, so a
|
|
87
|
-
* `DateStamp` beside a stage name reads as one line about one milestone. Put a
|
|
88
|
-
* value here, never a control: an editor drags the row to a control band, the
|
|
89
|
-
* marker follows it, and the ladder stops being something you scan. An ACT
|
|
90
|
-
* belongs in `PipelineActions`.
|
|
91
|
-
*/
|
|
92
|
-
trailing?: ReactNode;
|
|
93
|
-
|
|
94
|
-
/** The stage's own body — `PipelineNote`, `PipelineField`, `PipelineActions`,
|
|
95
|
-
* or anything else. A stage with no body renders as its title alone, which is
|
|
96
|
-
* what an unreached stage should be. */
|
|
97
|
-
children?: ReactNode;
|
|
98
|
-
/**
|
|
99
|
-
* Make the stage's MARKER the completion control — the node becomes a
|
|
100
|
-
* pressable ring instead of a status dot. Reach for it when the ladder is a
|
|
101
|
-
* worklist the reader ticks off, not just a readout.
|
|
102
|
-
*
|
|
103
|
-
* Without it, a caller who wants a tickable ladder puts a checkbox in
|
|
104
|
-
* `trailing`, which lands a second completion affordance beside a ring that
|
|
105
|
-
* already looks like one and reads as two controls for one fact.
|
|
106
|
-
*/
|
|
107
|
-
onToggle?: (done: boolean) => void;
|
|
108
|
-
accessibilityLabel?: string;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** One milestone. Every title reads the same — full ink, medium — because the
|
|
112
|
-
* MARKER carries the status: filled behind, empty ahead. Emphasising one name
|
|
113
|
-
* encoded that a second time, and muting the passed ones read as "these matter
|
|
114
|
-
* less" about the part of the record that already happened.
|
|
115
|
-
*
|
|
116
|
-
* The stage BODY is deliberately not a press target. `Step` can be (a wizard
|
|
117
|
-
* whose steps navigate), but a pipeline stage is a workspace, not a destination
|
|
118
|
-
* — its body already holds the controls, and a press target wrapping them would
|
|
119
|
-
* swallow their taps. `onToggle` is the exception and stays on the MARKER,
|
|
120
|
-
* which owns no other content and stops its own press. */
|
|
121
|
-
export function PipelineStage(props: PipelineStageProps) {
|
|
122
|
-
const { status, title, meta, trailing, children, onToggle, accessibilityLabel, ...positional } = props;
|
|
123
|
-
// `complete` is the terminal stage REACHED — where a finished record sits, not
|
|
124
|
-
// one it walked past. Muting it like an unreached stage leaves a completed run
|
|
125
|
-
// with nothing in full ink, so the eye has no landing point and the last thing
|
|
126
|
-
// that happened reads as the thing that hasn't.
|
|
127
|
-
// Every stage name reads the same: full ink, medium. The MARKER already says
|
|
128
|
-
// where the record sits — a filled ring behind, an empty one ahead — so muting
|
|
129
|
-
// the passed names said it a second time, and said it wrongly: a stage that is
|
|
130
|
-
// done is the RECORD of what happened, not something to de-emphasise. The kit
|
|
131
|
-
// makes the same argument for `CheckCircle`, which is monochrome at every
|
|
132
|
-
// position precisely so a row does not double-code its own state.
|
|
133
|
-
const titleText = (
|
|
134
|
-
<Text size="sm" color="default" weight="medium">
|
|
135
|
-
{title}
|
|
136
|
-
</Text>
|
|
137
|
-
);
|
|
138
|
-
return (
|
|
139
|
-
<Step
|
|
140
|
-
status={status}
|
|
141
|
-
onToggle={onToggle}
|
|
142
|
-
accessibilityLabel={accessibilityLabel ?? title}
|
|
143
|
-
// The marker centres on the FIRST ROW, so it has to be told when that row
|
|
144
|
-
// is a control band and not a line of text — otherwise it stays pinned to
|
|
145
|
-
// the text and every title reads low by half the difference.
|
|
146
|
-
// The row is a text line whatever rides it, so the marker centres on
|
|
147
|
-
// the text rather than on a control band that is no longer there.
|
|
148
|
-
headHeight={undefined}
|
|
149
|
-
{...positional}
|
|
150
|
-
>
|
|
151
|
-
<View style={{ gap: 6 }}>
|
|
152
|
-
<View style={{ gap: 2 }}>
|
|
153
|
-
{trailing != null ? (
|
|
154
|
-
// Fixed at the control band whatever `trailing` holds: a stage whose
|
|
155
|
-
// row height followed its content would step the marker in and out of
|
|
156
|
-
// alignment down the ladder, and a badge would sit on a shorter row
|
|
157
|
-
// than a date picker two stages up.
|
|
158
|
-
// BASELINE, not centre: the value sits beside the title as prose, and
|
|
159
|
-
// two texts of different sizes centred in one row share no baseline —
|
|
160
|
-
// the smaller one floats, which is exactly what "not aligned with the
|
|
161
|
-
// label" looks like. The row also stops forcing a control band, since
|
|
162
|
-
// what rides it now is text, not an editor.
|
|
163
|
-
<View style={{ flexDirection: "row", alignItems: "baseline", gap: 8 }}>
|
|
164
|
-
{/* The title keeps its NATURAL width and the slack goes after the
|
|
165
|
-
value, so the value sits against the label — "Booked · 16 Jun"
|
|
166
|
-
reads as one line about one milestone. Pinning it to the row's
|
|
167
|
-
right edge instead made a column of dates that scanned well and
|
|
168
|
-
said nothing: the reader had to carry the label across the gap
|
|
169
|
-
to know which milestone the date belonged to. */}
|
|
170
|
-
<View style={{ flexShrink: 1, minWidth: 0 }}>{titleText}</View>
|
|
171
|
-
{trailing}
|
|
172
|
-
<View style={{ flex: 1 }} />
|
|
173
|
-
</View>
|
|
174
|
-
) : (
|
|
175
|
-
titleText
|
|
176
|
-
)}
|
|
177
|
-
{meta ? <Text size="xs" color="muted">{meta}</Text> : null}
|
|
178
|
-
</View>
|
|
179
|
-
{children}
|
|
180
|
-
</View>
|
|
181
|
-
</Step>
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export interface PipelineNoteProps {
|
|
186
|
-
children: ReactNode;
|
|
187
|
-
/** `warning` for something outstanding at this stage, `danger` for a refusal,
|
|
188
|
-
* `muted` for a plain remark. */
|
|
189
|
-
tone?: "muted" | "warning" | "danger";
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* A CONDITION attached to one stage — "sent back", "rejected", "waiting on the
|
|
194
|
-
* customer". Prose, never a control: it says what is true, and the controls that
|
|
195
|
-
* answer it sit under it in the same stage.
|
|
196
|
-
*
|
|
197
|
-
* Deliberately not a `Callout`. A callout is a page-level interruption with its
|
|
198
|
-
* own box and tone fill; inside a stage that box competes with the spine and
|
|
199
|
-
* reads as an alert about the whole record. A stage's condition is a line of text
|
|
200
|
-
* in the stage's own column.
|
|
201
|
-
*/
|
|
202
|
-
export function PipelineNote({ children, tone = "muted" }: PipelineNoteProps) {
|
|
203
|
-
return <Text size="xs" color={tone}>{children}</Text>;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export interface PipelineFieldProps {
|
|
207
|
-
/** The value's name. Stacked ABOVE the control rather than in a label column:
|
|
208
|
-
* a stage's content column is already indented past the spine, and a second
|
|
209
|
-
* fixed column inside it leaves nothing for the value. */
|
|
210
|
-
label?: string;
|
|
211
|
-
children: ReactNode;
|
|
212
|
-
/** Cap the control's width so a lone text input does not run the full column. */
|
|
213
|
-
maxWidth?: number;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/** A value this stage OWNS, editable in place. Render it on a passed stage too —
|
|
217
|
-
* a milestone being behind you is not a reason its facts stop being wrong. */
|
|
218
|
-
export function PipelineField({ label, children, maxWidth = 320 }: PipelineFieldProps) {
|
|
219
|
-
return (
|
|
220
|
-
<View style={{ gap: 2, maxWidth }}>
|
|
221
|
-
{label ? <Text size="xs" color="muted">{label}</Text> : null}
|
|
222
|
-
{children}
|
|
223
|
-
</View>
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** The act(s) that leave this stage. Wraps on a narrow column so a stage with two
|
|
228
|
-
* verbs never pushes the spine sideways. */
|
|
229
|
-
export function PipelineActions({ children }: { children: ReactNode }) {
|
|
230
|
-
return <View style={{ flexDirection: "row", gap: 8, flexWrap: "wrap", paddingTop: 2 }}>{children}</View>;
|
|
231
|
-
}
|