@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpic-ai/ui",
3
- "version": "0.0.0-staging.fc7750c",
3
+ "version": "0.0.0-staging.fd696be",
4
4
  "description": "Alpic design system — shared UI components",
5
5
  "type": "module",
6
6
  "exports": {
@@ -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
+ );