@alpic-ai/ui 0.0.0-dev.edeed3c → 0.0.0-dev.ee56854

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.
Files changed (41) hide show
  1. package/dist/components/button.d.mts +3 -1
  2. package/dist/components/button.mjs +20 -6
  3. package/dist/components/form.d.mts +36 -1
  4. package/dist/components/form.mjs +114 -4
  5. package/dist/components/github-button.d.mts +13 -0
  6. package/dist/components/github-button.mjs +24 -0
  7. package/dist/components/page-loader.d.mts +11 -0
  8. package/dist/components/page-loader.mjs +122 -0
  9. package/dist/components/shimmer-text.d.mts +12 -0
  10. package/dist/components/shimmer-text.mjs +22 -0
  11. package/dist/components/sidebar.mjs +61 -17
  12. package/dist/components/table.d.mts +10 -1
  13. package/dist/components/table.mjs +4 -4
  14. package/dist/components/tabs.mjs +4 -4
  15. package/dist/components/task-progress.d.mts +27 -0
  16. package/dist/components/task-progress.mjs +66 -0
  17. package/dist/components/tooltip.mjs +1 -1
  18. package/dist/components/wizard.d.mts +34 -0
  19. package/dist/components/wizard.mjs +46 -0
  20. package/package.json +13 -13
  21. package/src/components/button.tsx +13 -9
  22. package/src/components/combobox.tsx +18 -6
  23. package/src/components/form.tsx +164 -3
  24. package/src/components/github-button.tsx +34 -0
  25. package/src/components/page-loader.tsx +59 -0
  26. package/src/components/shimmer-text.tsx +23 -0
  27. package/src/components/sidebar.tsx +59 -20
  28. package/src/components/table.tsx +17 -4
  29. package/src/components/tabs.tsx +4 -4
  30. package/src/components/task-progress.tsx +107 -0
  31. package/src/components/tooltip.tsx +1 -1
  32. package/src/components/wizard.tsx +69 -0
  33. package/src/hooks/use-copy-to-clipboard.ts +6 -2
  34. package/src/stories/button.stories.tsx +23 -1
  35. package/src/stories/form.stories.tsx +64 -2
  36. package/src/stories/sidebar.stories.tsx +6 -3
  37. package/src/stories/table.stories.tsx +2 -2
  38. package/src/stories/tabs.stories.tsx +4 -2
  39. package/src/stories/task-progress.stories.tsx +81 -0
  40. package/src/stories/wizard.stories.tsx +64 -0
  41. package/src/styles/tokens.css +217 -0
@@ -1,6 +1,17 @@
1
1
  import type { Story } from "@ladle/react";
2
2
  import { useForm } from "react-hook-form";
3
- import { Form, FormFields, FormHeader, InputField, SelectField, TextareaField } from "../components/form";
3
+
4
+ import {
5
+ CheckboxField,
6
+ ChecklistField,
7
+ Form,
8
+ FormFields,
9
+ FormHeader,
10
+ InputField,
11
+ RadioField,
12
+ SelectField,
13
+ TextareaField,
14
+ } from "../components/form";
4
15
 
5
16
  /* ── Types ───────────────────────────────────────────────────────────────── */
6
17
 
@@ -8,6 +19,9 @@ interface CreateProjectForm {
8
19
  email: string;
9
20
  role: string;
10
21
  description: string;
22
+ visibility: string;
23
+ capabilities: string[];
24
+ acceptTerms: boolean;
11
25
  }
12
26
 
13
27
  const roles = [
@@ -16,11 +30,31 @@ const roles = [
16
30
  { value: "viewer", label: "Viewer" },
17
31
  ];
18
32
 
33
+ const visibilities = [
34
+ { value: "public", label: "Public" },
35
+ { value: "private", label: "Private" },
36
+ { value: "internal", label: "Internal" },
37
+ ];
38
+
39
+ const capabilities = [
40
+ { value: "read", label: "Read" },
41
+ { value: "write", label: "Write" },
42
+ { value: "deploy", label: "Deploy" },
43
+ { value: "admin", label: "Admin" },
44
+ ];
45
+
19
46
  /* ── Composed Form ───────────────────────────────────────────────────────── */
20
47
 
21
48
  function ComposedForm() {
22
49
  const form = useForm<CreateProjectForm>({
23
- defaultValues: { email: "", role: "", description: "" },
50
+ defaultValues: {
51
+ email: "",
52
+ role: "",
53
+ description: "",
54
+ visibility: "",
55
+ capabilities: [],
56
+ acceptTerms: false,
57
+ },
24
58
  });
25
59
 
26
60
  const onSubmit = (data: CreateProjectForm) => {
@@ -64,6 +98,34 @@ function ComposedForm() {
64
98
  placeholder="A short description of your project..."
65
99
  required
66
100
  />
101
+
102
+ <RadioField
103
+ control={form.control}
104
+ name="visibility"
105
+ rules={{ required: "Pick a visibility." }}
106
+ label="Visibility"
107
+ description="Who can see and access this project."
108
+ options={visibilities}
109
+ required
110
+ />
111
+
112
+ <ChecklistField
113
+ control={form.control}
114
+ name="capabilities"
115
+ rules={{ validate: (value) => (value?.length ? true : "Pick at least one capability.") }}
116
+ label="Capabilities"
117
+ description="Select all that apply."
118
+ options={capabilities}
119
+ required
120
+ />
121
+
122
+ <CheckboxField
123
+ control={form.control}
124
+ name="acceptTerms"
125
+ rules={{ validate: (value) => (value ? true : "You must accept the terms.") }}
126
+ label="I accept the terms and conditions"
127
+ description="Required to create the project."
128
+ />
67
129
  </FormFields>
68
130
 
69
131
  <button
@@ -107,7 +107,8 @@ export const AllVariants: Story = () => (
107
107
  <UserFooter />
108
108
  </Sidebar>
109
109
  <SidebarInset>
110
- <div className="p-4">
110
+ <div className="flex items-center gap-2 p-4">
111
+ <SidebarTrigger />
111
112
  <span className="type-text-sm text-muted-foreground">Sub-menus expand inline under their parent</span>
112
113
  </div>
113
114
  </SidebarInset>
@@ -169,7 +170,8 @@ export const AllVariants: Story = () => (
169
170
  <UserFooter />
170
171
  </Sidebar>
171
172
  <SidebarInset>
172
- <div className="p-4">
173
+ <div className="flex items-center gap-2 p-4">
174
+ <SidebarTrigger />
173
175
  <span className="type-text-sm text-muted-foreground">Groups with labels, badges, and separator</span>
174
176
  </div>
175
177
  </SidebarInset>
@@ -195,7 +197,8 @@ export const AllVariants: Story = () => (
195
197
  <UserFooter />
196
198
  </Sidebar>
197
199
  <SidebarInset>
198
- <div className="p-4">
200
+ <div className="flex items-center gap-2 p-4">
201
+ <SidebarTrigger />
199
202
  <span className="type-text-sm text-muted-foreground">Loading state with skeleton placeholders</span>
200
203
  </div>
201
204
  </SidebarInset>
@@ -190,8 +190,8 @@ export const AllVariants: Story = () => (
190
190
  </TableRow>
191
191
  </TableHeader>
192
192
  <TableBody>
193
- {USERS.slice(0, 3).map((user, i) => (
194
- <TableRow key={user.id} data-state={i === 1 ? "selected" : undefined}>
193
+ {USERS.slice(0, 3).map((user, index) => (
194
+ <TableRow key={user.id} data-state={index === 1 ? "selected" : undefined}>
195
195
  <TableCell className="font-medium text-foreground">{user.name}</TableCell>
196
196
  <TableCell className="text-subtle-foreground">{user.role}</TableCell>
197
197
  <TableCell className="text-subtle-foreground">{user.email}</TableCell>
@@ -18,9 +18,11 @@ const navTabs = [
18
18
 
19
19
  const sideNavTabs = [
20
20
  { id: "general", label: "General" },
21
+ { id: "build-settings", label: "Build settings" },
22
+ { id: "environment-variables", label: "Environment variables" },
23
+ { id: "domains", label: "Domains" },
21
24
  { id: "authentication", label: "Authentication" },
22
- { id: "notifications", label: "Notifications" },
23
- { id: "billing", label: "Billing" },
25
+ { id: "marketplace", label: "Marketplace" },
24
26
  ];
25
27
 
26
28
  function useHashRoute(fallback: string) {
@@ -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
+ );
@@ -0,0 +1,64 @@
1
+ import type { Story } from "@ladle/react";
2
+ import { useState } from "react";
3
+
4
+ import { WizardProgress, type WizardStep, WizardSteps } from "../components/wizard";
5
+
6
+ const SECTION_HEADER = "type-text-xs font-medium text-muted-foreground uppercase tracking-wide pt-4";
7
+
8
+ const steps: WizardStep[] = [
9
+ { id: "overview", label: "Overview" },
10
+ { id: "branding", label: "Branding & metadata" },
11
+ { id: "auth", label: "Authentication" },
12
+ { id: "tools", label: "Tools & test cases" },
13
+ { id: "review", label: "Review & submit" },
14
+ ];
15
+
16
+ export const AllVariants: Story = () => {
17
+ const [activeIdx, setActiveIdx] = useState(1);
18
+
19
+ return (
20
+ <div className="flex flex-col gap-10 p-8">
21
+ {/* Full rail (steps + progress) */}
22
+ <div>
23
+ <p className={SECTION_HEADER}>Full rail — steps + progress</p>
24
+ <div className="mt-4 flex gap-6">
25
+ <aside className="basis-56 shrink-0 flex flex-col gap-4 self-start">
26
+ <WizardSteps steps={steps} activeIdx={activeIdx} onSelect={setActiveIdx} ariaLabel="Submission steps" />
27
+ <WizardProgress current={activeIdx + 1} total={steps.length} />
28
+ </aside>
29
+ <div className="flex-1 rounded-md border p-4">
30
+ <p className="type-text-sm text-muted-foreground">Content for step "{steps[activeIdx]?.label}"</p>
31
+ </div>
32
+ </div>
33
+ </div>
34
+
35
+ {/* Steps only */}
36
+ <div>
37
+ <p className={SECTION_HEADER}>Steps only</p>
38
+ <div className="mt-4 max-w-56">
39
+ <WizardSteps steps={steps} activeIdx={activeIdx} onSelect={setActiveIdx} />
40
+ </div>
41
+ </div>
42
+
43
+ {/* Progress only — various positions */}
44
+ <div>
45
+ <p className={SECTION_HEADER}>Progress — first step</p>
46
+ <div className="mt-4 max-w-56">
47
+ <WizardProgress current={1} total={5} />
48
+ </div>
49
+ </div>
50
+ <div>
51
+ <p className={SECTION_HEADER}>Progress — mid-flow</p>
52
+ <div className="mt-4 max-w-56">
53
+ <WizardProgress current={3} total={5} />
54
+ </div>
55
+ </div>
56
+ <div>
57
+ <p className={SECTION_HEADER}>Progress — complete</p>
58
+ <div className="mt-4 max-w-56">
59
+ <WizardProgress current={5} total={5} />
60
+ </div>
61
+ </div>
62
+ </div>
63
+ );
64
+ };
@@ -39,6 +39,9 @@
39
39
 
40
40
  --color-ring: #f22b79; /* Figma: focus-ring */
41
41
 
42
+ /* cta — decorative gradient accent, used with --color-primary in the CTA button ring */
43
+ --color-cta-accent: #6eece7; /* Figma: CTA border accent (cyan) */
44
+
42
45
  /* sidebar */
43
46
  --color-sidebar: #f8fafa; /* Figma: bg-secondary-subtle */
44
47
  --color-sidebar-foreground: #3a4848; /* Figma: fg-secondary */
@@ -106,6 +109,26 @@
106
109
  /* animations */
107
110
  --animate-accordion-down: accordion-down 200ms ease-out;
108
111
  --animate-accordion-up: accordion-up 200ms ease-out;
112
+ --animate-beacon-ring-core: beacon-ring-core 2.2s ease-in-out infinite;
113
+ --animate-beacon-ring-pulse: beacon-ring-pulse 2.2s ease-in-out infinite;
114
+ }
115
+
116
+ @keyframes shimmer-text {
117
+ from {
118
+ background-position: 100% center;
119
+ }
120
+ to {
121
+ background-position: 0% center;
122
+ }
123
+ }
124
+
125
+ @keyframes alpic-ride {
126
+ 0% {
127
+ transform: translate(0px, 0px);
128
+ }
129
+ 100% {
130
+ transform: translate(237px, -64px);
131
+ }
109
132
  }
110
133
 
111
134
  @keyframes accordion-down {
@@ -126,6 +149,184 @@
126
149
  }
127
150
  }
128
151
 
152
+ @keyframes beacon-ring-core {
153
+ 0%,
154
+ 100% {
155
+ opacity: 0.55;
156
+ transform: scale(0.98);
157
+ }
158
+ 50% {
159
+ opacity: 0.85;
160
+ transform: scale(1.02);
161
+ }
162
+ }
163
+
164
+ @keyframes beacon-ring-pulse {
165
+ 0%,
166
+ 100% {
167
+ opacity: 0.55;
168
+ transform: scale(1);
169
+ }
170
+ 50% {
171
+ opacity: 0.9;
172
+ transform: scale(1.02);
173
+ }
174
+ }
175
+
176
+ /* ─── CTA button — animated conic gradient ring ───────────────────────────── */
177
+
178
+ @property --cta-angle {
179
+ syntax: "<angle>";
180
+ inherits: false;
181
+ initial-value: 135deg;
182
+ }
183
+
184
+ @keyframes cta-rotate {
185
+ to {
186
+ --cta-angle: 495deg;
187
+ }
188
+ }
189
+
190
+ .button-cta {
191
+ position: relative;
192
+ isolation: isolate;
193
+ /* light mode: whisper-of-gradient surface tint + soft rose drop-shadow */
194
+ background-image: linear-gradient(
195
+ 135deg,
196
+ color-mix(in oklab, var(--color-primary) 5%, transparent) 0%,
197
+ color-mix(in oklab, var(--color-cta-accent) 5%, transparent) 100%
198
+ );
199
+ box-shadow:
200
+ 0 6px 24px -10px color-mix(in oklab, var(--color-primary) 38%, transparent),
201
+ 0 2px 6px -4px color-mix(in oklab, var(--color-cta-accent) 30%, transparent);
202
+ transition:
203
+ box-shadow 400ms ease,
204
+ transform 300ms ease,
205
+ filter 300ms ease;
206
+ }
207
+
208
+ @media (hover: hover) {
209
+ .button-cta:hover {
210
+ box-shadow:
211
+ 0 10px 30px -8px color-mix(in oklab, var(--color-primary) 52%, transparent),
212
+ 0 3px 10px -3px color-mix(in oklab, var(--color-cta-accent) 40%, transparent);
213
+ }
214
+ }
215
+
216
+ /* dark mode: solid inverted surface, no tint or shadow — let the halo do the work */
217
+ .dark .button-cta {
218
+ background-image: none;
219
+ box-shadow: none;
220
+ }
221
+
222
+ .button-cta::before,
223
+ .button-cta::after {
224
+ content: "";
225
+ position: absolute;
226
+ inset: 0;
227
+ border-radius: inherit;
228
+ pointer-events: none;
229
+ /* Always "running" in browser terms, but paused at rest — freezes at current
230
+ angle on unhover instead of snapping back. */
231
+ animation: cta-rotate 3.2s linear infinite;
232
+ animation-play-state: paused;
233
+ }
234
+
235
+ /* Gradient ring (masked so only the border shows) */
236
+ .button-cta::before {
237
+ padding: 1.5px;
238
+ background: conic-gradient(
239
+ from var(--cta-angle),
240
+ var(--color-cta-accent) 0deg,
241
+ var(--color-primary) 150deg,
242
+ var(--color-cta-accent) 300deg,
243
+ var(--color-cta-accent) 360deg
244
+ );
245
+ -webkit-mask:
246
+ linear-gradient(#000 0 0) content-box,
247
+ linear-gradient(#000 0 0);
248
+ -webkit-mask-composite: xor;
249
+ mask-composite: exclude;
250
+ transition: filter 400ms ease;
251
+ }
252
+
253
+ /* Blurred glow halo behind the button — subtle in light, bolder in dark */
254
+ .button-cta::after {
255
+ z-index: -1;
256
+ background: conic-gradient(
257
+ from var(--cta-angle),
258
+ var(--color-cta-accent) 0deg,
259
+ var(--color-primary) 150deg,
260
+ var(--color-cta-accent) 300deg,
261
+ var(--color-cta-accent) 360deg
262
+ );
263
+ filter: blur(12px);
264
+ opacity: 0.05;
265
+ transition: opacity 400ms ease;
266
+ }
267
+
268
+ .dark .button-cta::after {
269
+ opacity: 0.14;
270
+ }
271
+
272
+ @media (hover: hover) {
273
+ .button-cta:hover::before,
274
+ .button-cta:hover::after {
275
+ animation-play-state: running;
276
+ }
277
+ .button-cta:hover::before {
278
+ filter: saturate(1.15) brightness(1.05);
279
+ }
280
+ .button-cta:hover::after {
281
+ opacity: 0.18;
282
+ }
283
+ .dark .button-cta:hover::after {
284
+ opacity: 0.32;
285
+ }
286
+ }
287
+
288
+ .button-cta:focus-visible::before,
289
+ .button-cta:focus-visible::after {
290
+ animation-play-state: running;
291
+ }
292
+
293
+ .button-cta:disabled::before,
294
+ .button-cta:disabled::after,
295
+ [aria-busy="true"].button-cta::before,
296
+ [aria-busy="true"].button-cta::after {
297
+ animation-play-state: paused;
298
+ }
299
+ .button-cta:disabled::after {
300
+ opacity: 0;
301
+ }
302
+
303
+ @media (prefers-reduced-motion: reduce) {
304
+ .button-cta::before,
305
+ .button-cta::after {
306
+ animation: none;
307
+ }
308
+ }
309
+
310
+ /* Icon slide on hover (applied to [data-cta-icon-trailing]) */
311
+ .button-cta [data-cta-icon-trailing] {
312
+ transition: transform 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
313
+ }
314
+
315
+ @media (hover: hover) {
316
+ .button-cta:hover [data-cta-icon-trailing] {
317
+ transform: translateX(2px);
318
+ }
319
+ }
320
+
321
+ @media (prefers-reduced-motion: reduce) {
322
+ .button-cta [data-cta-icon-trailing] {
323
+ transition: none;
324
+ }
325
+ .button-cta:hover [data-cta-icon-trailing] {
326
+ transform: none;
327
+ }
328
+ }
329
+
129
330
  /* ─── Dark mode ───────────────────────────────────────────────────────────── */
130
331
 
131
332
  .dark {
@@ -163,6 +364,9 @@
163
364
 
164
365
  --color-ring: #f22b79; /* Figma: focus-ring */
165
366
 
367
+ /* cta — decorative gradient accent, used with --color-primary in the CTA button ring */
368
+ --color-cta-accent: #6eece7; /* Figma: CTA border accent (cyan) */
369
+
166
370
  /* sidebar */
167
371
  --color-sidebar: #0c1c1c; /* Figma: bg-secondary */
168
372
  --color-sidebar-foreground: #90a4a4; /* Figma: fg-secondary */
@@ -221,6 +425,14 @@
221
425
 
222
426
  @custom-variant dark (&:where(.dark, .dark *));
223
427
 
428
+ :root {
429
+ --gradient-sidebar: linear-gradient(0deg, #c9e2e280 0%, #ffffff 70%); /* Figma: bg-nav-gradiant-light */
430
+ }
431
+
432
+ .dark {
433
+ --gradient-sidebar: linear-gradient(0deg, #213535 0%, #121e1e 70%); /* Figma: bg-nav-gradiant-dark */
434
+ }
435
+
224
436
  @layer base {
225
437
  * {
226
438
  @apply border-border shadow-shadow;
@@ -274,6 +486,11 @@
274
486
 
275
487
  /* ─── Type preset utilities ───────────────────────────────────────────────── */
276
488
 
489
+ @utility bg-sidebar-surface {
490
+ background-color: var(--color-background);
491
+ background-image: var(--gradient-sidebar);
492
+ }
493
+
277
494
  @utility type-display-2xl {
278
495
  font-family: var(--font-display);
279
496
  font-size: var(--font-size-display-2xl);