@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/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
- }