@alpic-ai/ui 0.0.0-staging.fc7750c → 0.0.0-staging.fd696be
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/dist/components/form.mjs +6 -6
- package/dist/components/task-progress.d.mts +27 -0
- package/dist/components/task-progress.mjs +66 -0
- package/dist/components/tooltip.mjs +1 -1
- package/package.json +1 -1
- package/src/components/form.tsx +6 -6
- package/src/components/task-progress.tsx +107 -0
- package/src/components/tooltip.tsx +1 -1
- package/src/stories/task-progress.stories.tsx +81 -0
package/dist/components/form.mjs
CHANGED
|
@@ -90,7 +90,7 @@ function FormDescription({ className, ...props }) {
|
|
|
90
90
|
return /* @__PURE__ */ jsx("p", {
|
|
91
91
|
"data-slot": "form-description",
|
|
92
92
|
id: formDescriptionId,
|
|
93
|
-
className: cn("text-muted-foreground text-sm", className),
|
|
93
|
+
className: cn("text-muted-foreground type-text-sm whitespace-pre-line", className),
|
|
94
94
|
...props
|
|
95
95
|
});
|
|
96
96
|
}
|
|
@@ -136,11 +136,11 @@ function InputField({ control, name, rules, required, label, description, toolti
|
|
|
136
136
|
tooltip,
|
|
137
137
|
children: label
|
|
138
138
|
}),
|
|
139
|
+
description && /* @__PURE__ */ jsx(FormDescription, { children: description }),
|
|
139
140
|
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(Input, {
|
|
140
141
|
...inputProps,
|
|
141
142
|
...field
|
|
142
143
|
}) }),
|
|
143
|
-
description && /* @__PURE__ */ jsx(FormDescription, { children: description }),
|
|
144
144
|
/* @__PURE__ */ jsx(FormMessage, {})
|
|
145
145
|
] })
|
|
146
146
|
});
|
|
@@ -156,11 +156,11 @@ function TextareaField({ control, name, rules, required, label, description, too
|
|
|
156
156
|
tooltip,
|
|
157
157
|
children: label
|
|
158
158
|
}),
|
|
159
|
+
description && /* @__PURE__ */ jsx(FormDescription, { children: description }),
|
|
159
160
|
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(Textarea, {
|
|
160
161
|
...textareaProps,
|
|
161
162
|
...field
|
|
162
163
|
}) }),
|
|
163
|
-
description && /* @__PURE__ */ jsx(FormDescription, { children: description }),
|
|
164
164
|
/* @__PURE__ */ jsx(FormMessage, {})
|
|
165
165
|
] })
|
|
166
166
|
});
|
|
@@ -176,6 +176,7 @@ function SelectField({ control, name, rules, required, label, description, toolt
|
|
|
176
176
|
tooltip,
|
|
177
177
|
children: label
|
|
178
178
|
}),
|
|
179
|
+
description && /* @__PURE__ */ jsx(FormDescription, { children: description }),
|
|
179
180
|
/* @__PURE__ */ jsxs(Select, {
|
|
180
181
|
value: field.value,
|
|
181
182
|
onValueChange: field.onChange,
|
|
@@ -185,7 +186,6 @@ function SelectField({ control, name, rules, required, label, description, toolt
|
|
|
185
186
|
children: option.label
|
|
186
187
|
}, option.value)) })]
|
|
187
188
|
}),
|
|
188
|
-
description && /* @__PURE__ */ jsx(FormDescription, { children: description }),
|
|
189
189
|
/* @__PURE__ */ jsx(FormMessage, {})
|
|
190
190
|
] })
|
|
191
191
|
});
|
|
@@ -201,6 +201,7 @@ function RadioField({ control, name, rules, required, label, description, toolti
|
|
|
201
201
|
tooltip,
|
|
202
202
|
children: label
|
|
203
203
|
}),
|
|
204
|
+
description && /* @__PURE__ */ jsx(FormDescription, { children: description }),
|
|
204
205
|
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(RadioGroup, {
|
|
205
206
|
value: field.value ?? "",
|
|
206
207
|
onValueChange: field.onChange,
|
|
@@ -221,7 +222,6 @@ function RadioField({ control, name, rules, required, label, description, toolti
|
|
|
221
222
|
}, option.value);
|
|
222
223
|
})
|
|
223
224
|
}) }),
|
|
224
|
-
description && /* @__PURE__ */ jsx(FormDescription, { children: description }),
|
|
225
225
|
/* @__PURE__ */ jsx(FormMessage, {})
|
|
226
226
|
] })
|
|
227
227
|
});
|
|
@@ -243,6 +243,7 @@ function ChecklistField({ control, name, rules, required, label, description, to
|
|
|
243
243
|
tooltip,
|
|
244
244
|
children: label
|
|
245
245
|
}),
|
|
246
|
+
description && /* @__PURE__ */ jsx(FormDescription, { children: description }),
|
|
246
247
|
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx("fieldset", {
|
|
247
248
|
className: "m-0 flex flex-col gap-2 border-0 p-0",
|
|
248
249
|
onBlur: field.onBlur,
|
|
@@ -263,7 +264,6 @@ function ChecklistField({ control, name, rules, required, label, description, to
|
|
|
263
264
|
}, option.value);
|
|
264
265
|
})
|
|
265
266
|
}) }),
|
|
266
|
-
description && /* @__PURE__ */ jsx(FormDescription, { children: description }),
|
|
267
267
|
/* @__PURE__ */ jsx(FormMessage, {})
|
|
268
268
|
] });
|
|
269
269
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/components/task-progress.d.ts
|
|
4
|
+
type TaskProgressStatus = "pending" | "running" | "done";
|
|
5
|
+
interface TaskProgressStep {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
status: TaskProgressStatus;
|
|
9
|
+
}
|
|
10
|
+
interface TaskProgressProps extends Omit<React.ComponentProps<"div">, "children"> {
|
|
11
|
+
steps: readonly TaskProgressStep[];
|
|
12
|
+
trailingLabel?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Optional mount-time stagger: each row fades + slides in with `animation-delay = index × stepRevealDelayMs`.
|
|
15
|
+
* Pure CSS, runs once on mount. Omit for no animation.
|
|
16
|
+
*/
|
|
17
|
+
stepRevealDelayMs?: number;
|
|
18
|
+
}
|
|
19
|
+
declare function TaskProgress({
|
|
20
|
+
steps,
|
|
21
|
+
trailingLabel,
|
|
22
|
+
stepRevealDelayMs,
|
|
23
|
+
className,
|
|
24
|
+
...props
|
|
25
|
+
}: TaskProgressProps): _$react_jsx_runtime0.JSX.Element;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { TaskProgress, type TaskProgressStatus, type TaskProgressStep };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.mjs";
|
|
3
|
+
import { Separator } from "./separator.mjs";
|
|
4
|
+
import { CheckIcon } from "lucide-react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region src/components/task-progress.tsx
|
|
7
|
+
const REVEAL_CLASSES = "animate-in fade-in slide-in-from-bottom-2 duration-300";
|
|
8
|
+
function TaskProgress({ steps, trailingLabel, stepRevealDelayMs, className, ...props }) {
|
|
9
|
+
const total = steps.length;
|
|
10
|
+
const doneCount = steps.filter((step) => step.status === "done").length;
|
|
11
|
+
const percent = total > 0 ? Math.round(doneCount / total * 100) : 0;
|
|
12
|
+
const stagger = stepRevealDelayMs != null;
|
|
13
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
14
|
+
className: cn("flex flex-col gap-4", className),
|
|
15
|
+
...props,
|
|
16
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
17
|
+
className: "bg-muted h-2 w-full overflow-hidden rounded-full",
|
|
18
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
19
|
+
className: "h-full rounded-full bg-success transition-all duration-500",
|
|
20
|
+
style: { width: `${percent}%` }
|
|
21
|
+
})
|
|
22
|
+
}), /* @__PURE__ */ jsxs("div", { children: [steps.map((step, idx) => /* @__PURE__ */ jsxs("div", {
|
|
23
|
+
className: stagger ? REVEAL_CLASSES : void 0,
|
|
24
|
+
style: stagger ? {
|
|
25
|
+
animationDelay: `${idx * (stepRevealDelayMs ?? 0)}ms`,
|
|
26
|
+
animationFillMode: "both"
|
|
27
|
+
} : void 0,
|
|
28
|
+
children: [/* @__PURE__ */ jsx(TaskProgressRow, {
|
|
29
|
+
label: step.label,
|
|
30
|
+
status: step.status
|
|
31
|
+
}), idx < steps.length - 1 && /* @__PURE__ */ jsx(Separator, {})]
|
|
32
|
+
}, step.id)), trailingLabel && /* @__PURE__ */ jsxs("div", {
|
|
33
|
+
className: stagger ? REVEAL_CLASSES : void 0,
|
|
34
|
+
children: [/* @__PURE__ */ jsx(Separator, {}), /* @__PURE__ */ jsx(TaskProgressRow, {
|
|
35
|
+
label: trailingLabel,
|
|
36
|
+
status: "running",
|
|
37
|
+
muted: true
|
|
38
|
+
})]
|
|
39
|
+
})] })]
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function TaskProgressRow({ label, status, muted }) {
|
|
43
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
44
|
+
className: "flex items-center justify-between py-2",
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ jsx("span", {
|
|
47
|
+
className: cn("type-text-sm", muted && "text-muted-foreground"),
|
|
48
|
+
children: label
|
|
49
|
+
}),
|
|
50
|
+
status === "done" && /* @__PURE__ */ jsxs("span", {
|
|
51
|
+
className: "flex items-center gap-1 type-text-sm text-success",
|
|
52
|
+
children: [/* @__PURE__ */ jsx(CheckIcon, { className: "size-3.5" }), /* @__PURE__ */ jsx("span", { children: "done" })]
|
|
53
|
+
}),
|
|
54
|
+
status === "running" && /* @__PURE__ */ jsxs("span", {
|
|
55
|
+
className: "flex items-center gap-1.5 type-text-sm text-warning",
|
|
56
|
+
children: [/* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-warning" }), /* @__PURE__ */ jsx("span", { children: "running…" })]
|
|
57
|
+
}),
|
|
58
|
+
status === "pending" && /* @__PURE__ */ jsxs("span", {
|
|
59
|
+
className: "flex items-center gap-1.5 type-text-sm text-muted-foreground",
|
|
60
|
+
children: [/* @__PURE__ */ jsx("span", { className: "size-2 rounded-full border border-muted-foreground" }), /* @__PURE__ */ jsx("span", { children: "pending" })]
|
|
61
|
+
})
|
|
62
|
+
]
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { TaskProgress };
|
|
@@ -26,7 +26,7 @@ function TooltipContent({ className, sideOffset = 6, children, ...props }) {
|
|
|
26
26
|
return /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs(TooltipPrimitive.Content, {
|
|
27
27
|
"data-slot": "tooltip-content",
|
|
28
28
|
sideOffset,
|
|
29
|
-
className: cn("bg-inverted text-inverted-foreground dark:bg-subtle dark:text-foreground", "z-50 w-fit rounded-md px-3 py-2 shadow-lg dark:shadow-none dark:drop-shadow-[0_0_0.5px_var(--color-border)]", "type-text-xs font-semibold text-balance text-center", "animate-in fade-in-0 zoom-in-95", "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "origin-(--radix-tooltip-content-transform-origin)", className),
|
|
29
|
+
className: cn("bg-inverted text-inverted-foreground dark:bg-subtle dark:text-foreground", "z-50 w-fit rounded-md px-3 py-2 shadow-lg dark:shadow-none dark:drop-shadow-[0_0_0.5px_var(--color-border)]", "type-text-xs font-semibold text-balance text-center whitespace-pre-line", "animate-in fade-in-0 zoom-in-95", "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "origin-(--radix-tooltip-content-transform-origin)", className),
|
|
30
30
|
...props,
|
|
31
31
|
children: [children, /* @__PURE__ */ jsx(TooltipPrimitive.Arrow, { className: "bg-inverted fill-inverted dark:bg-subtle dark:fill-subtle z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" })]
|
|
32
32
|
}) });
|
package/package.json
CHANGED
package/src/components/form.tsx
CHANGED
|
@@ -143,7 +143,7 @@ function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
|
143
143
|
<p
|
|
144
144
|
data-slot="form-description"
|
|
145
145
|
id={formDescriptionId}
|
|
146
|
-
className={cn("text-muted-foreground text-sm", className)}
|
|
146
|
+
className={cn("text-muted-foreground type-text-sm whitespace-pre-line", className)}
|
|
147
147
|
{...props}
|
|
148
148
|
/>
|
|
149
149
|
);
|
|
@@ -222,10 +222,10 @@ function InputField<TFieldValues extends FieldValues, TName extends FieldPath<TF
|
|
|
222
222
|
{label}
|
|
223
223
|
</FormLabel>
|
|
224
224
|
)}
|
|
225
|
+
{description && <FormDescription>{description}</FormDescription>}
|
|
225
226
|
<FormControl>
|
|
226
227
|
<Input {...inputProps} {...field} />
|
|
227
228
|
</FormControl>
|
|
228
|
-
{description && <FormDescription>{description}</FormDescription>}
|
|
229
229
|
<FormMessage />
|
|
230
230
|
</FormItem>
|
|
231
231
|
)}
|
|
@@ -259,10 +259,10 @@ function TextareaField<TFieldValues extends FieldValues, TName extends FieldPath
|
|
|
259
259
|
{label}
|
|
260
260
|
</FormLabel>
|
|
261
261
|
)}
|
|
262
|
+
{description && <FormDescription>{description}</FormDescription>}
|
|
262
263
|
<FormControl>
|
|
263
264
|
<Textarea {...textareaProps} {...field} />
|
|
264
265
|
</FormControl>
|
|
265
|
-
{description && <FormDescription>{description}</FormDescription>}
|
|
266
266
|
<FormMessage />
|
|
267
267
|
</FormItem>
|
|
268
268
|
)}
|
|
@@ -305,6 +305,7 @@ function SelectField<TFieldValues extends FieldValues, TName extends FieldPath<T
|
|
|
305
305
|
{label}
|
|
306
306
|
</FormLabel>
|
|
307
307
|
)}
|
|
308
|
+
{description && <FormDescription>{description}</FormDescription>}
|
|
308
309
|
<Select value={field.value} onValueChange={field.onChange}>
|
|
309
310
|
<FormControl>
|
|
310
311
|
<SelectTrigger>
|
|
@@ -319,7 +320,6 @@ function SelectField<TFieldValues extends FieldValues, TName extends FieldPath<T
|
|
|
319
320
|
))}
|
|
320
321
|
</SelectContent>
|
|
321
322
|
</Select>
|
|
322
|
-
{description && <FormDescription>{description}</FormDescription>}
|
|
323
323
|
<FormMessage />
|
|
324
324
|
</FormItem>
|
|
325
325
|
)}
|
|
@@ -354,6 +354,7 @@ function RadioField<TFieldValues extends FieldValues, TName extends FieldPath<TF
|
|
|
354
354
|
{label}
|
|
355
355
|
</FormLabel>
|
|
356
356
|
)}
|
|
357
|
+
{description && <FormDescription>{description}</FormDescription>}
|
|
357
358
|
<FormControl>
|
|
358
359
|
<RadioGroup value={field.value ?? ""} onValueChange={field.onChange} onBlur={field.onBlur}>
|
|
359
360
|
{options.map((option) => {
|
|
@@ -369,7 +370,6 @@ function RadioField<TFieldValues extends FieldValues, TName extends FieldPath<TF
|
|
|
369
370
|
})}
|
|
370
371
|
</RadioGroup>
|
|
371
372
|
</FormControl>
|
|
372
|
-
{description && <FormDescription>{description}</FormDescription>}
|
|
373
373
|
<FormMessage />
|
|
374
374
|
</FormItem>
|
|
375
375
|
)}
|
|
@@ -413,6 +413,7 @@ function ChecklistField<TFieldValues extends FieldValues, TName extends FieldPat
|
|
|
413
413
|
{label}
|
|
414
414
|
</FormLabel>
|
|
415
415
|
)}
|
|
416
|
+
{description && <FormDescription>{description}</FormDescription>}
|
|
416
417
|
<FormControl>
|
|
417
418
|
<fieldset className="m-0 flex flex-col gap-2 border-0 p-0" onBlur={field.onBlur}>
|
|
418
419
|
{options.map((option) => {
|
|
@@ -434,7 +435,6 @@ function ChecklistField<TFieldValues extends FieldValues, TName extends FieldPat
|
|
|
434
435
|
})}
|
|
435
436
|
</fieldset>
|
|
436
437
|
</FormControl>
|
|
437
|
-
{description && <FormDescription>{description}</FormDescription>}
|
|
438
438
|
<FormMessage />
|
|
439
439
|
</FormItem>
|
|
440
440
|
);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* TaskProgress — multi-step async progress primitive.
|
|
5
|
+
*
|
|
6
|
+
* Dumb component: the consumer owns orchestration (timer, websocket, polling) and
|
|
7
|
+
* passes `steps` with each step's status. Renders a percentage bar at the top
|
|
8
|
+
* and one row per step with done/running/pending state badges.
|
|
9
|
+
*
|
|
10
|
+
* `trailingLabel` is for "still working on something not in the step list" —
|
|
11
|
+
* shows after the last step as a running row that isn't counted in the percent.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { CheckIcon } from "lucide-react";
|
|
15
|
+
|
|
16
|
+
import { cn } from "../lib/cn";
|
|
17
|
+
import { Separator } from "./separator";
|
|
18
|
+
|
|
19
|
+
type TaskProgressStatus = "pending" | "running" | "done";
|
|
20
|
+
|
|
21
|
+
interface TaskProgressStep {
|
|
22
|
+
id: string;
|
|
23
|
+
label: string;
|
|
24
|
+
status: TaskProgressStatus;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface TaskProgressProps extends Omit<React.ComponentProps<"div">, "children"> {
|
|
28
|
+
steps: readonly TaskProgressStep[];
|
|
29
|
+
trailingLabel?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Optional mount-time stagger: each row fades + slides in with `animation-delay = index × stepRevealDelayMs`.
|
|
32
|
+
* Pure CSS, runs once on mount. Omit for no animation.
|
|
33
|
+
*/
|
|
34
|
+
stepRevealDelayMs?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const REVEAL_CLASSES = "animate-in fade-in slide-in-from-bottom-2 duration-300";
|
|
38
|
+
|
|
39
|
+
function TaskProgress({ steps, trailingLabel, stepRevealDelayMs, className, ...props }: TaskProgressProps) {
|
|
40
|
+
const total = steps.length;
|
|
41
|
+
const doneCount = steps.filter((step) => step.status === "done").length;
|
|
42
|
+
const percent = total > 0 ? Math.round((doneCount / total) * 100) : 0;
|
|
43
|
+
const stagger = stepRevealDelayMs != null;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className={cn("flex flex-col gap-4", className)} {...props}>
|
|
47
|
+
<div className="bg-muted h-2 w-full overflow-hidden rounded-full">
|
|
48
|
+
<div className="h-full rounded-full bg-success transition-all duration-500" style={{ width: `${percent}%` }} />
|
|
49
|
+
</div>
|
|
50
|
+
<div>
|
|
51
|
+
{steps.map((step, idx) => (
|
|
52
|
+
<div
|
|
53
|
+
key={step.id}
|
|
54
|
+
className={stagger ? REVEAL_CLASSES : undefined}
|
|
55
|
+
style={
|
|
56
|
+
stagger ? { animationDelay: `${idx * (stepRevealDelayMs ?? 0)}ms`, animationFillMode: "both" } : undefined
|
|
57
|
+
}
|
|
58
|
+
>
|
|
59
|
+
<TaskProgressRow label={step.label} status={step.status} />
|
|
60
|
+
{idx < steps.length - 1 && <Separator />}
|
|
61
|
+
</div>
|
|
62
|
+
))}
|
|
63
|
+
{trailingLabel && (
|
|
64
|
+
<div className={stagger ? REVEAL_CLASSES : undefined}>
|
|
65
|
+
<Separator />
|
|
66
|
+
<TaskProgressRow label={trailingLabel} status="running" muted />
|
|
67
|
+
</div>
|
|
68
|
+
)}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface TaskProgressRowProps {
|
|
75
|
+
label: string;
|
|
76
|
+
status: TaskProgressStatus;
|
|
77
|
+
muted?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function TaskProgressRow({ label, status, muted }: TaskProgressRowProps) {
|
|
81
|
+
return (
|
|
82
|
+
<div className="flex items-center justify-between py-2">
|
|
83
|
+
<span className={cn("type-text-sm", muted && "text-muted-foreground")}>{label}</span>
|
|
84
|
+
{status === "done" && (
|
|
85
|
+
<span className="flex items-center gap-1 type-text-sm text-success">
|
|
86
|
+
<CheckIcon className="size-3.5" />
|
|
87
|
+
<span>done</span>
|
|
88
|
+
</span>
|
|
89
|
+
)}
|
|
90
|
+
{status === "running" && (
|
|
91
|
+
<span className="flex items-center gap-1.5 type-text-sm text-warning">
|
|
92
|
+
<span className="size-2 rounded-full bg-warning" />
|
|
93
|
+
<span>running…</span>
|
|
94
|
+
</span>
|
|
95
|
+
)}
|
|
96
|
+
{status === "pending" && (
|
|
97
|
+
<span className="flex items-center gap-1.5 type-text-sm text-muted-foreground">
|
|
98
|
+
<span className="size-2 rounded-full border border-muted-foreground" />
|
|
99
|
+
<span>pending</span>
|
|
100
|
+
</span>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type { TaskProgressStatus, TaskProgressStep };
|
|
107
|
+
export { TaskProgress };
|
|
@@ -35,7 +35,7 @@ function TooltipContent({
|
|
|
35
35
|
className={cn(
|
|
36
36
|
"bg-inverted text-inverted-foreground dark:bg-subtle dark:text-foreground",
|
|
37
37
|
"z-50 w-fit rounded-md px-3 py-2 shadow-lg dark:shadow-none dark:drop-shadow-[0_0_0.5px_var(--color-border)]",
|
|
38
|
-
"type-text-xs font-semibold text-balance text-center",
|
|
38
|
+
"type-text-xs font-semibold text-balance text-center whitespace-pre-line",
|
|
39
39
|
"animate-in fade-in-0 zoom-in-95",
|
|
40
40
|
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
41
41
|
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Story } from "@ladle/react";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
|
|
4
|
+
import { TaskProgress, type TaskProgressStep } from "../components/task-progress";
|
|
5
|
+
|
|
6
|
+
const SECTION_HEADER = "type-text-xs font-medium text-subtle-foreground uppercase tracking-wide pt-4";
|
|
7
|
+
|
|
8
|
+
const beaconSteps: TaskProgressStep[] = [
|
|
9
|
+
{ id: "init", label: "Initialize MCP connection", status: "done" },
|
|
10
|
+
{ id: "fetch", label: "Fetch tools and resources", status: "done" },
|
|
11
|
+
{ id: "compat", label: "Check ChatGPT & Claude.ai compatibility", status: "done" },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const partialSteps: TaskProgressStep[] = [
|
|
15
|
+
{ id: "a", label: "Reading project metadata", status: "done" },
|
|
16
|
+
{ id: "b", label: "Fetching MCP server manifest", status: "running" },
|
|
17
|
+
{ id: "c", label: "Preparing your submission", status: "pending" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const allDoneSteps: TaskProgressStep[] = [
|
|
21
|
+
{ id: "a", label: "Reading project metadata", status: "done" },
|
|
22
|
+
{ id: "b", label: "Fetching MCP server manifest", status: "done" },
|
|
23
|
+
{ id: "c", label: "Preparing your submission", status: "done" },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const STEP_INTERVAL_MS = 1200;
|
|
27
|
+
|
|
28
|
+
function AnimatedExample() {
|
|
29
|
+
const [activeIdx, setActiveIdx] = useState(0);
|
|
30
|
+
|
|
31
|
+
const labels = ["Reading project metadata", "Fetching MCP server manifest", "Preparing your submission"];
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (activeIdx >= labels.length) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const timeoutId = setTimeout(() => setActiveIdx((prev) => prev + 1), STEP_INTERVAL_MS);
|
|
38
|
+
return () => clearTimeout(timeoutId);
|
|
39
|
+
}, [activeIdx, labels.length]);
|
|
40
|
+
|
|
41
|
+
const steps: TaskProgressStep[] = labels.map((label, idx) => ({
|
|
42
|
+
id: String(idx),
|
|
43
|
+
label,
|
|
44
|
+
status: idx < activeIdx ? "done" : idx === activeIdx ? "running" : "pending",
|
|
45
|
+
}));
|
|
46
|
+
const allDone = activeIdx >= labels.length;
|
|
47
|
+
|
|
48
|
+
return <TaskProgress steps={steps} trailingLabel={allDone ? "Almost there…" : undefined} />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const AllVariants: Story = () => (
|
|
52
|
+
<div className="flex flex-col gap-8 p-8 max-w-[640px]">
|
|
53
|
+
<div>
|
|
54
|
+
<p className={SECTION_HEADER}>In-progress (running step in the middle)</p>
|
|
55
|
+
<div className="mt-4">
|
|
56
|
+
<TaskProgress steps={partialSteps} />
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div>
|
|
61
|
+
<p className={SECTION_HEADER}>All done</p>
|
|
62
|
+
<div className="mt-4">
|
|
63
|
+
<TaskProgress steps={allDoneSteps} />
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div>
|
|
68
|
+
<p className={SECTION_HEADER}>All done + trailing "Running checks…" row</p>
|
|
69
|
+
<div className="mt-4">
|
|
70
|
+
<TaskProgress steps={beaconSteps} trailingLabel="Running checks…" />
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div>
|
|
75
|
+
<p className={SECTION_HEADER}>Animated (timer-driven, mirrors the submission-prefill flow)</p>
|
|
76
|
+
<div className="mt-4">
|
|
77
|
+
<AnimatedExample />
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
);
|