panelui-native 0.30.0 → 0.34.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/README.md +4 -2
- package/lib/module/components/button/index.js +106 -11
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/calendar/index.js +107 -24
- package/lib/module/components/calendar/index.js.map +1 -1
- package/lib/module/components/drawer/index.js +477 -0
- package/lib/module/components/drawer/index.js.map +1 -0
- package/lib/module/components/heatmap-chart/index.js +9 -8
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/map/index.js +50 -4
- package/lib/module/components/map/index.js.map +1 -1
- package/lib/module/components/panelside/index.js +1024 -0
- package/lib/module/components/panelside/index.js.map +1 -0
- package/lib/module/components/plan/index.js +307 -21
- package/lib/module/components/plan/index.js.map +1 -1
- package/lib/module/components/post/index.js +992 -0
- package/lib/module/components/post/index.js.map +1 -0
- package/lib/module/components/response/index.js +296 -0
- package/lib/module/components/response/index.js.map +1 -0
- package/lib/module/components/response/markdown.js +463 -0
- package/lib/module/components/response/markdown.js.map +1 -0
- package/lib/module/components/select/index.js +111 -18
- package/lib/module/components/select/index.js.map +1 -1
- package/lib/module/components/table/index.js +632 -0
- package/lib/module/components/table/index.js.map +1 -0
- package/lib/module/icons/index.js +220 -0
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +6 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/index.js +30 -0
- package/lib/module/native/index.js.map +1 -1
- package/lib/module/utils/date.js +129 -22
- package/lib/module/utils/date.js.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts +22 -1
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/calendar/index.d.ts +12 -3
- package/lib/typescript/src/components/calendar/index.d.ts.map +1 -1
- package/lib/typescript/src/components/drawer/index.d.ts +146 -0
- package/lib/typescript/src/components/drawer/index.d.ts.map +1 -0
- package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/map/index.d.ts +8 -1
- package/lib/typescript/src/components/map/index.d.ts.map +1 -1
- package/lib/typescript/src/components/panelside/index.d.ts +354 -0
- package/lib/typescript/src/components/panelside/index.d.ts.map +1 -0
- package/lib/typescript/src/components/plan/index.d.ts +121 -10
- package/lib/typescript/src/components/plan/index.d.ts.map +1 -1
- package/lib/typescript/src/components/post/index.d.ts +358 -0
- package/lib/typescript/src/components/post/index.d.ts.map +1 -0
- package/lib/typescript/src/components/response/index.d.ts +91 -0
- package/lib/typescript/src/components/response/index.d.ts.map +1 -0
- package/lib/typescript/src/components/response/markdown.d.ts +88 -0
- package/lib/typescript/src/components/response/markdown.d.ts.map +1 -0
- package/lib/typescript/src/components/select/index.d.ts +27 -2
- package/lib/typescript/src/components/select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/table/index.d.ts +274 -0
- package/lib/typescript/src/components/table/index.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +41 -1
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +7 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/index.d.ts +20 -0
- package/lib/typescript/src/native/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/date.d.ts +49 -5
- package/lib/typescript/src/utils/date.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/button/index.tsx +140 -8
- package/src/components/calendar/index.tsx +136 -23
- package/src/components/drawer/index.tsx +662 -0
- package/src/components/heatmap-chart/index.tsx +9 -9
- package/src/components/map/index.tsx +53 -4
- package/src/components/panelside/index.tsx +1358 -0
- package/src/components/plan/index.tsx +344 -21
- package/src/components/post/index.tsx +1116 -0
- package/src/components/response/index.tsx +390 -0
- package/src/components/response/markdown.ts +489 -0
- package/src/components/select/index.tsx +155 -15
- package/src/components/table/index.tsx +747 -0
- package/src/icons/index.tsx +176 -1
- package/src/index.ts +93 -0
- package/src/native/index.ts +51 -0
- package/src/utils/date.ts +146 -22
|
@@ -6,20 +6,41 @@
|
|
|
6
6
|
* and stops is not a matter of interpretation, and a footer that can hold the
|
|
7
7
|
* button that approves it.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* The body is a rail of steps rather than prose, because a plan is an ordered
|
|
10
|
+
* list of things that will happen and the reader's question is always *which
|
|
11
|
+
* one is running*. A paragraph describing the same four steps cannot be glanced
|
|
12
|
+
* at to answer that; a column of markers can. The rail fills behind the steps
|
|
13
|
+
* that are done, so progress is legible from its left edge alone, and
|
|
14
|
+
* `Plan.Steps` counts itself so `Plan.Progress` can say `2 of 4` up in the
|
|
15
|
+
* header without the total being stated twice.
|
|
16
|
+
*
|
|
17
|
+
* It streams. `isStreaming` puts a shimmer on the title, the description and
|
|
18
|
+
* the one step that is running, which is the honest way to render text that is
|
|
19
|
+
* still arriving — the alternative is a title that grows a word at a time and
|
|
20
|
+
* reads as finished at every intermediate length.
|
|
13
21
|
*
|
|
14
22
|
* ```tsx
|
|
15
23
|
* <Plan isStreaming={streaming}>
|
|
16
24
|
* <Plan.Header>
|
|
25
|
+
* <Plan.Icon><FileIcon size={16} /></Plan.Icon>
|
|
17
26
|
* <Plan.Title>Migrate the calendar</Plan.Title>
|
|
18
27
|
* <Plan.Description>Four files, no API change.</Plan.Description>
|
|
19
|
-
* <Plan.Action
|
|
28
|
+
* <Plan.Action>
|
|
29
|
+
* <Plan.Progress />
|
|
30
|
+
* <Plan.Trigger />
|
|
31
|
+
* </Plan.Action>
|
|
20
32
|
* </Plan.Header>
|
|
21
|
-
* <Plan.Content
|
|
22
|
-
*
|
|
33
|
+
* <Plan.Content>
|
|
34
|
+
* <Plan.Steps>
|
|
35
|
+
* <Plan.Step status="done" meta="utils/date.ts">Read the date utils</Plan.Step>
|
|
36
|
+
* <Plan.Step status="active">Replace the month grid</Plan.Step>
|
|
37
|
+
* <Plan.Step>Update the docs page</Plan.Step>
|
|
38
|
+
* </Plan.Steps>
|
|
39
|
+
* </Plan.Content>
|
|
40
|
+
* <Plan.Footer>
|
|
41
|
+
* <Button variant="outline">Revise</Button>
|
|
42
|
+
* <Button>Approve</Button>
|
|
43
|
+
* </Plan.Footer>
|
|
23
44
|
* </Plan>
|
|
24
45
|
* ```
|
|
25
46
|
*
|
|
@@ -31,12 +52,15 @@
|
|
|
31
52
|
* a title before it has anything else.
|
|
32
53
|
*/
|
|
33
54
|
import {
|
|
55
|
+
Children,
|
|
34
56
|
createContext,
|
|
57
|
+
isValidElement,
|
|
35
58
|
useCallback,
|
|
36
59
|
useContext,
|
|
37
60
|
useEffect,
|
|
38
61
|
useMemo,
|
|
39
62
|
useState,
|
|
63
|
+
type ReactElement,
|
|
40
64
|
type ReactNode,
|
|
41
65
|
} from 'react';
|
|
42
66
|
import { Pressable, View, type PressableProps, type ViewProps } from 'react-native';
|
|
@@ -47,30 +71,90 @@ import Animated, {
|
|
|
47
71
|
withTiming,
|
|
48
72
|
} from 'react-native-reanimated';
|
|
49
73
|
import { tv } from 'tailwind-variants';
|
|
50
|
-
import {
|
|
74
|
+
import { useCSSVariable } from 'uniwind';
|
|
75
|
+
import { CheckIcon, ChevronDownIcon } from '../../icons';
|
|
51
76
|
import { Collapse } from '../../primitives/collapse';
|
|
52
|
-
import { Text, textChildren } from '../../primitives/text';
|
|
77
|
+
import { Text, textChildren, type TextProps } from '../../primitives/text';
|
|
53
78
|
import { cn } from '../../utils/cn';
|
|
54
79
|
import { Shimmer } from '../shimmer';
|
|
55
80
|
|
|
56
81
|
const planVariants = tv({
|
|
57
82
|
slots: {
|
|
58
|
-
root: 'w-full overflow-hidden rounded-2xl border border-border bg-card',
|
|
83
|
+
root: 'w-full overflow-hidden rounded-2xl border border-border bg-card shadow-sm',
|
|
59
84
|
header: 'flex-row items-start gap-3 p-4',
|
|
85
|
+
icon: 'h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-muted',
|
|
60
86
|
heading: 'min-w-0 flex-1 gap-1',
|
|
61
87
|
title: 'text-base font-semibold text-card-foreground',
|
|
62
88
|
description: 'text-sm text-muted-foreground',
|
|
63
89
|
action: 'shrink-0 flex-row items-center gap-1',
|
|
90
|
+
progress: 'text-xs font-medium text-muted-foreground',
|
|
64
91
|
trigger: 'h-8 w-8 items-center justify-center rounded-full active:bg-accent',
|
|
65
92
|
content: 'gap-2 px-4 pb-4',
|
|
66
|
-
|
|
93
|
+
steps: 'w-full',
|
|
94
|
+
step: 'w-full flex-row gap-3',
|
|
95
|
+
stepRail: 'w-4 items-center pt-px',
|
|
96
|
+
stepMarker: 'h-4 w-4 items-center justify-center rounded-full',
|
|
97
|
+
stepConnector: 'w-px flex-1 rounded-full',
|
|
98
|
+
stepBody: 'min-w-0 flex-1 gap-0.5 pb-3',
|
|
99
|
+
stepTitle: 'text-sm',
|
|
100
|
+
stepDescription: 'text-xs text-muted-foreground',
|
|
101
|
+
stepMeta:
|
|
102
|
+
'self-start rounded-md bg-muted px-1.5 py-0.5 font-mono text-[11px] text-muted-foreground',
|
|
103
|
+
footer: 'flex-row items-center gap-2 border-t border-border p-4',
|
|
67
104
|
},
|
|
68
105
|
});
|
|
69
106
|
|
|
107
|
+
export type PlanStepStatus = 'pending' | 'active' | 'done' | 'skipped';
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* What each state does to a step's marker, its title and the rail below it.
|
|
111
|
+
*
|
|
112
|
+
* A lookup rather than a `tv()` variant because it belongs to `Plan.Step`, not
|
|
113
|
+
* to `Plan` — a variant here would be read off the root and documented as
|
|
114
|
+
* something you could pass to the card.
|
|
115
|
+
*
|
|
116
|
+
* The connector is filled behind a step that is finished, which is what turns
|
|
117
|
+
* the left-hand column into a progress bar stood on its end: how far the plan
|
|
118
|
+
* has got is readable from the rail alone, without counting markers.
|
|
119
|
+
*/
|
|
120
|
+
const stepStatus: Record<
|
|
121
|
+
PlanStepStatus,
|
|
122
|
+
{ marker: string; title: string; connector: string }
|
|
123
|
+
> = {
|
|
124
|
+
pending: {
|
|
125
|
+
marker: 'border border-border',
|
|
126
|
+
title: 'text-muted-foreground',
|
|
127
|
+
connector: 'bg-border',
|
|
128
|
+
},
|
|
129
|
+
active: {
|
|
130
|
+
marker: 'border-2 border-primary',
|
|
131
|
+
title: 'font-medium text-foreground',
|
|
132
|
+
connector: 'bg-border',
|
|
133
|
+
},
|
|
134
|
+
done: {
|
|
135
|
+
marker: 'bg-primary',
|
|
136
|
+
title: 'text-foreground',
|
|
137
|
+
connector: 'bg-primary',
|
|
138
|
+
},
|
|
139
|
+
skipped: {
|
|
140
|
+
marker: 'border border-dashed border-border',
|
|
141
|
+
title: 'text-muted-foreground line-through',
|
|
142
|
+
connector: 'bg-border',
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/** How far down the rail the plan has got. Reported by `Plan.Steps`. */
|
|
147
|
+
export interface PlanStepCounts {
|
|
148
|
+
done: number;
|
|
149
|
+
total: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
70
152
|
interface PlanContextValue {
|
|
71
153
|
isStreaming: boolean;
|
|
72
154
|
open: boolean;
|
|
73
155
|
setOpen: (open: boolean) => void;
|
|
156
|
+
counts: PlanStepCounts | null;
|
|
157
|
+
reportCounts: (counts: PlanStepCounts) => void;
|
|
74
158
|
}
|
|
75
159
|
|
|
76
160
|
const PlanContext = createContext<PlanContextValue | null>(null);
|
|
@@ -117,9 +201,19 @@ function PlanRoot({
|
|
|
117
201
|
[isControlled, onOpenChange]
|
|
118
202
|
);
|
|
119
203
|
|
|
204
|
+
// `Plan.Steps` counts its own children and reports up, so `Plan.Progress` can
|
|
205
|
+
// sit in the header — above the rail, and with no way to reach it — without
|
|
206
|
+
// the caller having to say how many steps there are twice.
|
|
207
|
+
const [counts, setCounts] = useState<PlanStepCounts | null>(null);
|
|
208
|
+
const reportCounts = useCallback((next: PlanStepCounts) => {
|
|
209
|
+
setCounts((current) =>
|
|
210
|
+
current && current.done === next.done && current.total === next.total ? current : next
|
|
211
|
+
);
|
|
212
|
+
}, []);
|
|
213
|
+
|
|
120
214
|
const context = useMemo(
|
|
121
|
-
() => ({ isStreaming, open, setOpen }),
|
|
122
|
-
[isStreaming, open, setOpen]
|
|
215
|
+
() => ({ isStreaming, open, setOpen, counts, reportCounts }),
|
|
216
|
+
[isStreaming, open, setOpen, counts, reportCounts]
|
|
123
217
|
);
|
|
124
218
|
|
|
125
219
|
return (
|
|
@@ -141,32 +235,53 @@ export interface PlanHeaderProps extends ViewProps {
|
|
|
141
235
|
* The title, the description and whatever acts on them.
|
|
142
236
|
*
|
|
143
237
|
* The heading is wrapped in a column of its own so the action stays pinned to
|
|
144
|
-
* the trailing edge as the title wraps, rather than riding down with it.
|
|
238
|
+
* the trailing edge as the title wraps, rather than riding down with it. An
|
|
239
|
+
* icon is pulled out to the leading edge for the same reason, in the other
|
|
240
|
+
* direction — a badge that rode down with a wrapping title would stop reading
|
|
241
|
+
* as a badge for the plan.
|
|
145
242
|
*/
|
|
146
243
|
function PlanHeader({ className, children, ...props }: PlanHeaderProps) {
|
|
147
244
|
const { header, heading } = planVariants();
|
|
148
245
|
|
|
246
|
+
const icons: ReactNode[] = [];
|
|
149
247
|
const headings: ReactNode[] = [];
|
|
150
248
|
const actions: ReactNode[] = [];
|
|
151
249
|
for (const child of Array.isArray(children) ? children : [children]) {
|
|
152
|
-
if (
|
|
250
|
+
if (isType(child, PlanAction)) actions.push(child);
|
|
251
|
+
else if (isType(child, PlanIcon)) icons.push(child);
|
|
153
252
|
else headings.push(child);
|
|
154
253
|
}
|
|
155
254
|
|
|
156
255
|
return (
|
|
157
256
|
<View {...props} className={cn(header(), className)}>
|
|
257
|
+
{icons}
|
|
158
258
|
<View className={heading()}>{headings}</View>
|
|
159
259
|
{actions}
|
|
160
260
|
</View>
|
|
161
261
|
);
|
|
162
262
|
}
|
|
163
263
|
|
|
164
|
-
function
|
|
264
|
+
function isType(child: ReactNode, type: unknown): boolean {
|
|
165
265
|
return (
|
|
166
266
|
typeof child === 'object' &&
|
|
167
267
|
child !== null &&
|
|
168
268
|
'type' in child &&
|
|
169
|
-
(child as { type?: unknown }).type ===
|
|
269
|
+
(child as { type?: unknown }).type === type
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface PlanIconProps extends ViewProps {
|
|
274
|
+
className?: string;
|
|
275
|
+
children?: ReactNode;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** A badge on the header's leading edge — what kind of plan this is. */
|
|
279
|
+
function PlanIcon({ className, children, ...props }: PlanIconProps) {
|
|
280
|
+
const { icon } = planVariants();
|
|
281
|
+
return (
|
|
282
|
+
<View {...props} className={cn(icon(), className)}>
|
|
283
|
+
{children}
|
|
284
|
+
</View>
|
|
170
285
|
);
|
|
171
286
|
}
|
|
172
287
|
|
|
@@ -291,35 +406,243 @@ function PlanContent({ className, children, ...props }: PlanContentProps) {
|
|
|
291
406
|
);
|
|
292
407
|
}
|
|
293
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Where a step sits in the rail. Provided by `Plan.Steps` rather than passed
|
|
411
|
+
* down, so steps produced by a `.map()` through a component of your own still
|
|
412
|
+
* know they are last — a prop set on that wrapper would never reach the step.
|
|
413
|
+
*/
|
|
414
|
+
const PlanStepContext = createContext<{ last: boolean }>({ last: false });
|
|
415
|
+
|
|
416
|
+
export interface PlanStepsProps extends ViewProps {
|
|
417
|
+
className?: string;
|
|
418
|
+
children?: ReactNode;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* The rail. A plan is an ordered list of things that will happen, and this is
|
|
423
|
+
* the list — a paragraph describing the same four steps cannot be glanced at to
|
|
424
|
+
* see which one is running.
|
|
425
|
+
*
|
|
426
|
+
* It counts its own steps and reports the count up, which is what lets
|
|
427
|
+
* `Plan.Progress` sit in the header without the caller stating the total twice.
|
|
428
|
+
*/
|
|
429
|
+
function PlanSteps({ className, children, ...props }: PlanStepsProps) {
|
|
430
|
+
const { reportCounts } = usePlan('Plan.Steps');
|
|
431
|
+
const { steps } = planVariants();
|
|
432
|
+
|
|
433
|
+
const items = useMemo(
|
|
434
|
+
() => Children.toArray(children).filter((child) => isValidElement(child)),
|
|
435
|
+
[children]
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
const counts = useMemo(() => {
|
|
439
|
+
let done = 0;
|
|
440
|
+
let total = 0;
|
|
441
|
+
for (const item of items) {
|
|
442
|
+
if (!isType(item, PlanStep)) continue;
|
|
443
|
+
total += 1;
|
|
444
|
+
// A skipped step is settled too — a plan that reports 2 of 4 while two
|
|
445
|
+
// more were deliberately passed over is reporting the wrong thing.
|
|
446
|
+
const status = (item as ReactElement<PlanStepProps>).props.status;
|
|
447
|
+
if (status === 'done' || status === 'skipped') done += 1;
|
|
448
|
+
}
|
|
449
|
+
return { done, total };
|
|
450
|
+
}, [items]);
|
|
451
|
+
|
|
452
|
+
useEffect(() => {
|
|
453
|
+
reportCounts(counts);
|
|
454
|
+
}, [counts, reportCounts]);
|
|
455
|
+
|
|
456
|
+
return (
|
|
457
|
+
<View {...props} role="list" className={cn(steps(), className)}>
|
|
458
|
+
{items.map((item, index) => (
|
|
459
|
+
<PlanStepContext.Provider
|
|
460
|
+
// The step's own key stays on the element; this wrapper needs its own.
|
|
461
|
+
key={index}
|
|
462
|
+
value={{ last: index === items.length - 1 }}
|
|
463
|
+
>
|
|
464
|
+
{item}
|
|
465
|
+
</PlanStepContext.Provider>
|
|
466
|
+
))}
|
|
467
|
+
</View>
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export interface PlanStepProps extends Omit<ViewProps, 'children'> {
|
|
472
|
+
className?: string;
|
|
473
|
+
/** How far this step has got. Decides the marker, the title and the rail below it. */
|
|
474
|
+
status?: PlanStepStatus;
|
|
475
|
+
/** A line under the title — what the step will touch, or what it found. */
|
|
476
|
+
description?: ReactNode;
|
|
477
|
+
/** A file path, a count, a duration. Rendered as a small mono chip. */
|
|
478
|
+
meta?: ReactNode;
|
|
479
|
+
/** Drop the connector below this step. `Plan.Steps` sets it for you. */
|
|
480
|
+
last?: boolean;
|
|
481
|
+
children?: ReactNode;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* One step. The marker says which of the four states it is in and the rail
|
|
486
|
+
* below it is filled once it is done, so the plan's progress is legible from
|
|
487
|
+
* the shape of the left-hand column alone.
|
|
488
|
+
*/
|
|
489
|
+
function PlanStep({
|
|
490
|
+
className,
|
|
491
|
+
status = 'pending',
|
|
492
|
+
description,
|
|
493
|
+
meta,
|
|
494
|
+
last,
|
|
495
|
+
children,
|
|
496
|
+
...props
|
|
497
|
+
}: PlanStepProps) {
|
|
498
|
+
const { isStreaming } = usePlan('Plan.Step');
|
|
499
|
+
const position = useContext(PlanStepContext);
|
|
500
|
+
const isLast = last ?? position.last;
|
|
501
|
+
const {
|
|
502
|
+
step,
|
|
503
|
+
stepRail,
|
|
504
|
+
stepMarker,
|
|
505
|
+
stepConnector,
|
|
506
|
+
stepBody,
|
|
507
|
+
stepTitle,
|
|
508
|
+
stepDescription,
|
|
509
|
+
stepMeta,
|
|
510
|
+
} = planVariants();
|
|
511
|
+
const tone = stepStatus[status];
|
|
512
|
+
const onPrimary = useCSSVariable('--color-primary-foreground');
|
|
513
|
+
|
|
514
|
+
const title = stepTitle({ className: tone.title });
|
|
515
|
+
// Only the step that is running is still arriving. Shimmering a finished one
|
|
516
|
+
// would say it is still being written, and shimmering the whole rail would
|
|
517
|
+
// say nothing at all.
|
|
518
|
+
const streamingTitle = isStreaming && status === 'active' && typeof children === 'string';
|
|
519
|
+
|
|
520
|
+
return (
|
|
521
|
+
<View {...props} role="listitem" className={cn(step(), className)}>
|
|
522
|
+
<View className={stepRail()}>
|
|
523
|
+
<View className={stepMarker({ className: tone.marker })}>
|
|
524
|
+
{status === 'done' ? (
|
|
525
|
+
<CheckIcon
|
|
526
|
+
size={10}
|
|
527
|
+
color={typeof onPrimary === 'string' ? onPrimary : '#ffffff'}
|
|
528
|
+
/>
|
|
529
|
+
) : status === 'active' ? (
|
|
530
|
+
<View className="h-1.5 w-1.5 rounded-full bg-primary" />
|
|
531
|
+
) : null}
|
|
532
|
+
</View>
|
|
533
|
+
{isLast ? null : <View className={stepConnector({ className: tone.connector })} />}
|
|
534
|
+
</View>
|
|
535
|
+
|
|
536
|
+
<View className={cn(stepBody(), isLast && 'pb-0')}>
|
|
537
|
+
{streamingTitle ? (
|
|
538
|
+
<Shimmer textClassName={title}>{children as string}</Shimmer>
|
|
539
|
+
) : (
|
|
540
|
+
textChildren(children, (text) => <Text className={title}>{text}</Text>)
|
|
541
|
+
)}
|
|
542
|
+
{textChildren(description, (text) => (
|
|
543
|
+
<Text className={stepDescription()}>{text}</Text>
|
|
544
|
+
))}
|
|
545
|
+
{meta === undefined ? null : (
|
|
546
|
+
<View className={stepMeta()}>
|
|
547
|
+
{textChildren(meta, (text) => (
|
|
548
|
+
<Text className="font-mono text-[11px] text-muted-foreground">{text}</Text>
|
|
549
|
+
))}
|
|
550
|
+
</View>
|
|
551
|
+
)}
|
|
552
|
+
</View>
|
|
553
|
+
</View>
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export interface PlanProgressProps extends TextProps {
|
|
558
|
+
className?: string;
|
|
559
|
+
/** Steps settled so far. Defaults to what `Plan.Steps` counted. */
|
|
560
|
+
value?: number;
|
|
561
|
+
/** Steps in total. Defaults to what `Plan.Steps` counted. */
|
|
562
|
+
total?: number;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* How far down the rail the plan has got, as `2 of 4`. Renders nothing until
|
|
567
|
+
* there is a rail to count — a plan with no steps has no progress to report,
|
|
568
|
+
* and `0 of 0` is worse than silence.
|
|
569
|
+
*/
|
|
570
|
+
function PlanProgress({ className, value, total, ...props }: PlanProgressProps) {
|
|
571
|
+
const { counts } = usePlan('Plan.Progress');
|
|
572
|
+
const { progress } = planVariants();
|
|
573
|
+
|
|
574
|
+
const done = value ?? counts?.done;
|
|
575
|
+
const all = total ?? counts?.total;
|
|
576
|
+
if (done === undefined || !all) return null;
|
|
577
|
+
|
|
578
|
+
return (
|
|
579
|
+
<Text className={cn(progress(), className)} {...props}>
|
|
580
|
+
{done} of {all}
|
|
581
|
+
</Text>
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
294
585
|
export interface PlanFooterProps extends ViewProps {
|
|
295
586
|
className?: string;
|
|
587
|
+
/**
|
|
588
|
+
* How the actions divide the row. `stretch` splits it between them, which is
|
|
589
|
+
* what a phone wants: the decision is the point of the card, and the two
|
|
590
|
+
* buttons that make it should be the width of a thumb. `end` packs them
|
|
591
|
+
* against the trailing edge for a plan sitting inside something denser.
|
|
592
|
+
*/
|
|
593
|
+
layout?: 'stretch' | 'end';
|
|
296
594
|
children?: ReactNode;
|
|
297
595
|
}
|
|
298
596
|
|
|
299
|
-
/**
|
|
300
|
-
|
|
597
|
+
/**
|
|
598
|
+
* Where the buttons that answer the plan go.
|
|
599
|
+
*
|
|
600
|
+
* Each action takes an equal share of the row by default. A pair of small
|
|
601
|
+
* buttons hugging the trailing corner is a pointer-and-cursor shape; on a phone
|
|
602
|
+
* the answer to "shall I do this" is the most important control on the screen
|
|
603
|
+
* and wants to be hit without aiming.
|
|
604
|
+
*/
|
|
605
|
+
function PlanFooter({ className, layout = 'stretch', children, ...props }: PlanFooterProps) {
|
|
301
606
|
const { footer } = planVariants();
|
|
607
|
+
|
|
302
608
|
return (
|
|
303
|
-
<View
|
|
304
|
-
{
|
|
609
|
+
<View
|
|
610
|
+
{...props}
|
|
611
|
+
className={cn(footer(), layout === 'end' && 'justify-end', className)}
|
|
612
|
+
>
|
|
613
|
+
{layout === 'stretch'
|
|
614
|
+
? // A view stretches its children across the cross axis by default, so
|
|
615
|
+
// a button inside one of these fills it without being told to.
|
|
616
|
+
Children.map(children, (child) =>
|
|
617
|
+
isValidElement(child) ? <View className="flex-1">{child}</View> : child
|
|
618
|
+
)
|
|
619
|
+
: children}
|
|
305
620
|
</View>
|
|
306
621
|
);
|
|
307
622
|
}
|
|
308
623
|
|
|
309
624
|
PlanHeader.displayName = 'Plan.Header';
|
|
625
|
+
PlanIcon.displayName = 'Plan.Icon';
|
|
310
626
|
PlanTitle.displayName = 'Plan.Title';
|
|
311
627
|
PlanDescription.displayName = 'Plan.Description';
|
|
312
628
|
PlanAction.displayName = 'Plan.Action';
|
|
629
|
+
PlanProgress.displayName = 'Plan.Progress';
|
|
313
630
|
PlanTrigger.displayName = 'Plan.Trigger';
|
|
314
631
|
PlanContent.displayName = 'Plan.Content';
|
|
632
|
+
PlanSteps.displayName = 'Plan.Steps';
|
|
633
|
+
PlanStep.displayName = 'Plan.Step';
|
|
315
634
|
PlanFooter.displayName = 'Plan.Footer';
|
|
316
635
|
|
|
317
636
|
export const Plan = Object.assign(PlanRoot, {
|
|
318
637
|
Header: PlanHeader,
|
|
638
|
+
Icon: PlanIcon,
|
|
319
639
|
Title: PlanTitle,
|
|
320
640
|
Description: PlanDescription,
|
|
321
641
|
Action: PlanAction,
|
|
642
|
+
Progress: PlanProgress,
|
|
322
643
|
Trigger: PlanTrigger,
|
|
323
644
|
Content: PlanContent,
|
|
645
|
+
Steps: PlanSteps,
|
|
646
|
+
Step: PlanStep,
|
|
324
647
|
Footer: PlanFooter,
|
|
325
648
|
});
|