@m5kdev/web-ui 0.1.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.
Files changed (127) hide show
  1. package/LICENSE +621 -0
  2. package/README.md +17 -0
  3. package/package.json +169 -0
  4. package/src/animations/card.motion.ts +9 -0
  5. package/src/components/AvatarUpload.tsx +133 -0
  6. package/src/components/Button.tsx +14 -0
  7. package/src/components/Calendar.css +684 -0
  8. package/src/components/Calendar.tsx +32 -0
  9. package/src/components/CardsSelect.tsx +155 -0
  10. package/src/components/CollapsibleSidebarMenuItem.tsx +57 -0
  11. package/src/components/ColorPicker.tsx +56 -0
  12. package/src/components/CopyButton.tsx +45 -0
  13. package/src/components/CropDialog.tsx +154 -0
  14. package/src/components/DialogProvider.tsx +105 -0
  15. package/src/components/ErrorFallback.tsx +17 -0
  16. package/src/components/FileDropzone.tsx +120 -0
  17. package/src/components/MultiSelectDropdown.tsx +233 -0
  18. package/src/components/Orb.tsx +288 -0
  19. package/src/components/PageAlert.tsx +121 -0
  20. package/src/components/SelectChips.tsx +40 -0
  21. package/src/components/SidebarItem.tsx +26 -0
  22. package/src/components/Steps.tsx +340 -0
  23. package/src/components/TablerIconPicker.tsx +4260 -0
  24. package/src/components/app-header.tsx +40 -0
  25. package/src/components/blur-card.tsx +132 -0
  26. package/src/components/features-section-demo-1.tsx +127 -0
  27. package/src/components/features-section-demo-2.tsx +102 -0
  28. package/src/components/features-section-demo-3.tsx +272 -0
  29. package/src/components/mode-toggle.tsx +31 -0
  30. package/src/components/nav-main.tsx +69 -0
  31. package/src/components/pricing-cards.tsx +133 -0
  32. package/src/components/shared/ButtonCopy.tsx +50 -0
  33. package/src/components/team-switcher.tsx +83 -0
  34. package/src/components/theme-provider.tsx +74 -0
  35. package/src/components/typewriter.tsx +90 -0
  36. package/src/components/ui/alert-dialog.tsx +133 -0
  37. package/src/components/ui/alert.tsx +60 -0
  38. package/src/components/ui/avatar.tsx +47 -0
  39. package/src/components/ui/badge.tsx +33 -0
  40. package/src/components/ui/bento-grid.tsx +54 -0
  41. package/src/components/ui/bento-grid2.tsx +66 -0
  42. package/src/components/ui/breadcrumb.tsx +101 -0
  43. package/src/components/ui/button.tsx +50 -0
  44. package/src/components/ui/card.tsx +55 -0
  45. package/src/components/ui/checkbox.tsx +26 -0
  46. package/src/components/ui/collapsible.tsx +9 -0
  47. package/src/components/ui/dialog.tsx +119 -0
  48. package/src/components/ui/dropdown-menu.tsx +186 -0
  49. package/src/components/ui/floating-navbar.tsx +78 -0
  50. package/src/components/ui/form.tsx +167 -0
  51. package/src/components/ui/image.tsx +55 -0
  52. package/src/components/ui/input.tsx +22 -0
  53. package/src/components/ui/label.tsx +19 -0
  54. package/src/components/ui/pagination.tsx +105 -0
  55. package/src/components/ui/progress.tsx +23 -0
  56. package/src/components/ui/resizable-navbar.tsx +260 -0
  57. package/src/components/ui/segment-control.tsx +143 -0
  58. package/src/components/ui/select.tsx +153 -0
  59. package/src/components/ui/separator.tsx +24 -0
  60. package/src/components/ui/sheet.tsx +121 -0
  61. package/src/components/ui/sidebar.tsx +736 -0
  62. package/src/components/ui/skeleton.tsx +7 -0
  63. package/src/components/ui/slider.tsx +23 -0
  64. package/src/components/ui/sonner.tsx +27 -0
  65. package/src/components/ui/spinner.tsx +45 -0
  66. package/src/components/ui/switch.tsx +27 -0
  67. package/src/components/ui/table.tsx +90 -0
  68. package/src/components/ui/tabs.tsx +52 -0
  69. package/src/components/ui/textarea.tsx +18 -0
  70. package/src/components/ui/timeline.tsx +95 -0
  71. package/src/components/ui/toast.tsx +126 -0
  72. package/src/components/ui/tooltip.tsx +55 -0
  73. package/src/components/ui/typewriter-effect.tsx +181 -0
  74. package/src/hooks/use-mobile.ts +19 -0
  75. package/src/hooks/useDialog.ts +25 -0
  76. package/src/icons/GoogleIcon.tsx +32 -0
  77. package/src/icons/LinkedInIcon.tsx +30 -0
  78. package/src/icons/MicrosoftIcon.tsx +21 -0
  79. package/src/lib/chatwoot.ts +51 -0
  80. package/src/lib/utils.ts +6 -0
  81. package/src/modules/app/components/AppLoader.tsx +9 -0
  82. package/src/modules/app/components/AppShell.tsx +21 -0
  83. package/src/modules/app/components/AppSidebar.tsx +26 -0
  84. package/src/modules/app/components/AppSidebarContent.tsx +73 -0
  85. package/src/modules/app/components/AppSidebarHeader.tsx +57 -0
  86. package/src/modules/app/components/AppSidebarInvites.tsx +32 -0
  87. package/src/modules/app/components/AppSidebarUser.tsx +128 -0
  88. package/src/modules/auth/components/AdminUserManagement.tsx +1136 -0
  89. package/src/modules/auth/components/AdminWaitlist.tsx +358 -0
  90. package/src/modules/auth/components/AuthLayout.tsx +13 -0
  91. package/src/modules/auth/components/AuthProviders.tsx +105 -0
  92. package/src/modules/auth/components/AuthRouter.tsx +29 -0
  93. package/src/modules/auth/components/ClaimAccountRoute.tsx +242 -0
  94. package/src/modules/auth/components/ErrorAuthRoute.tsx +121 -0
  95. package/src/modules/auth/components/ForgotPasswordForm.tsx +58 -0
  96. package/src/modules/auth/components/ForgotPasswordRoute.tsx +27 -0
  97. package/src/modules/auth/components/InviteFriends.tsx +273 -0
  98. package/src/modules/auth/components/LastUsedBadge.tsx +22 -0
  99. package/src/modules/auth/components/LoginForm.tsx +104 -0
  100. package/src/modules/auth/components/LoginRoute.tsx +31 -0
  101. package/src/modules/auth/components/LogoutRoute.tsx +21 -0
  102. package/src/modules/auth/components/OrganizationAcceptInvitationRoute.tsx +161 -0
  103. package/src/modules/auth/components/OrganizationMembersRoute.tsx +730 -0
  104. package/src/modules/auth/components/OrganizationSettingsRoute.tsx +280 -0
  105. package/src/modules/auth/components/OrganizationSwitcher.tsx +148 -0
  106. package/src/modules/auth/components/ProfileRoute.tsx +104 -0
  107. package/src/modules/auth/components/RangeNuqsDatePicker.tsx +365 -0
  108. package/src/modules/auth/components/ResetPasswordForm.tsx +103 -0
  109. package/src/modules/auth/components/ResetPasswordRoute.tsx +27 -0
  110. package/src/modules/auth/components/SignupFormRoute.tsx +189 -0
  111. package/src/modules/auth/components/SignupRoute.tsx +53 -0
  112. package/src/modules/auth/components/UserPreferences.tsx +144 -0
  113. package/src/modules/auth/components/WaitlistCard.tsx +78 -0
  114. package/src/modules/auth/components/WaitlistCodeValidation.tsx +79 -0
  115. package/src/modules/billing/components/BillingBetaPage.tsx +124 -0
  116. package/src/modules/billing/components/BillingInvoicePage.tsx +180 -0
  117. package/src/modules/billing/components/BillingPlanSelect.tsx +14 -0
  118. package/src/modules/billing/components/BillingRouter.tsx +20 -0
  119. package/src/modules/billing/components/BillingSinglePlanSelect.tsx +172 -0
  120. package/src/modules/table/components/ColumnOrderAndVisibility.tsx +127 -0
  121. package/src/modules/table/components/NuqsTable.tsx +396 -0
  122. package/src/modules/table/components/TableFiltering.tsx +520 -0
  123. package/src/modules/table/components/TablePagination.tsx +59 -0
  124. package/src/modules/table/components/table.types.ts +11 -0
  125. package/src/modules/table/filterTransformers.ts +323 -0
  126. package/src/types.ts +4 -0
  127. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,121 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import { AlertCircle, AlertTriangle, CheckCircle, Info, X } from "lucide-react";
3
+ import * as React from "react";
4
+ import { useTranslation } from "react-i18next";
5
+
6
+ import { cn } from "#utils";
7
+
8
+ const pageAlertVariants = cva(
9
+ "relative w-full border-b px-4 py-3 text-sm transition-all duration-300 ease-in-out",
10
+ {
11
+ variants: {
12
+ variant: {
13
+ default: "bg-background border-border text-foreground",
14
+ info: "bg-blue-50 border-blue-200 text-blue-900 dark:bg-blue-950/50 dark:border-blue-800 dark:text-blue-100",
15
+ success:
16
+ "bg-green-50 border-green-200 text-green-900 dark:bg-green-950/50 dark:border-green-800 dark:text-green-100",
17
+ warning:
18
+ "bg-yellow-50 border-yellow-200 text-yellow-900 dark:bg-yellow-950/50 dark:border-yellow-800 dark:text-yellow-100",
19
+ destructive:
20
+ "bg-red-50 border-red-200 text-red-900 dark:bg-red-950/50 dark:border-red-800 dark:text-red-100",
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ variant: "default",
25
+ },
26
+ }
27
+ );
28
+
29
+ const iconMap = {
30
+ default: Info,
31
+ info: Info,
32
+ success: CheckCircle,
33
+ warning: AlertTriangle,
34
+ destructive: AlertCircle,
35
+ } as const;
36
+
37
+ interface PageAlertProps
38
+ extends React.ComponentProps<"div">,
39
+ VariantProps<typeof pageAlertVariants> {
40
+ title?: string;
41
+ description?: React.ReactNode;
42
+ icon?: React.ComponentType<{ className?: string }>;
43
+ dismissible?: boolean;
44
+ defaultOpen?: boolean;
45
+ onDismiss?: () => void;
46
+ children?: React.ReactNode;
47
+ }
48
+
49
+ const PageAlert = React.forwardRef<HTMLDivElement, PageAlertProps>(
50
+ (
51
+ {
52
+ className,
53
+ variant = "default",
54
+ title,
55
+ description,
56
+ icon,
57
+ dismissible = true,
58
+ defaultOpen = true,
59
+ onDismiss,
60
+ children,
61
+ ...props
62
+ },
63
+ ref
64
+ ) => {
65
+ const { t } = useTranslation();
66
+ const [isOpen, setIsOpen] = React.useState(defaultOpen);
67
+ const [isAnimatingOut, setIsAnimatingOut] = React.useState(false);
68
+ const IconComponent = icon || iconMap[variant || "default"];
69
+
70
+ const handleDismiss = React.useCallback(() => {
71
+ setIsAnimatingOut(true);
72
+ // Wait for animation to complete before removing from DOM
73
+ setTimeout(() => {
74
+ setIsOpen(false);
75
+ onDismiss?.();
76
+ }, 300); // Match the animation duration
77
+ }, [onDismiss]);
78
+
79
+ if (!isOpen) {
80
+ return null;
81
+ }
82
+
83
+ const content = (
84
+ <div
85
+ ref={ref}
86
+ role="alert"
87
+ className={cn(
88
+ pageAlertVariants({ variant }),
89
+ isAnimatingOut && "transform -translate-y-full opacity-0",
90
+ className
91
+ )}
92
+ {...props}
93
+ >
94
+ <div className="flex items-start gap-3">
95
+ {IconComponent && <IconComponent className="h-4 w-4 mt-0.5 flex-shrink-0" />}
96
+ <div className="flex-1 min-w-0">
97
+ {title && <div className="font-medium leading-none tracking-tight mb-1">{title}</div>}
98
+ {description && <div className="text-sm opacity-90 leading-relaxed">{description}</div>}
99
+ {children}
100
+ </div>
101
+ {dismissible && (
102
+ <button
103
+ type="button"
104
+ onClick={handleDismiss}
105
+ className="flex-shrink-0 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none"
106
+ aria-label={t("web-ui:alert.dismissLabel")}
107
+ >
108
+ <X className="h-4 w-4" />
109
+ </button>
110
+ )}
111
+ </div>
112
+ </div>
113
+ );
114
+
115
+ return content;
116
+ }
117
+ );
118
+
119
+ PageAlert.displayName = "PageAlert";
120
+
121
+ export { PageAlert, type PageAlertProps };
@@ -0,0 +1,40 @@
1
+ import { Button, type ButtonProps, Checkbox } from "@heroui/react";
2
+
3
+ export function SelectChips({
4
+ items,
5
+ selectedItems,
6
+ onSelectionChange,
7
+ buttonProps,
8
+ }: {
9
+ items: string[] | { label: string; value: string }[];
10
+ selectedItems: string[];
11
+ onSelectionChange: (items: string[]) => void;
12
+ buttonProps?: ButtonProps;
13
+ }) {
14
+ return items.map((item) => {
15
+ const value = typeof item === "string" ? item : item.value;
16
+ const label = typeof item === "string" ? item : item.label;
17
+ return (
18
+ <Button
19
+ key={value}
20
+ startContent={
21
+ <Checkbox
22
+ isSelected={selectedItems.includes(value)}
23
+ isReadOnly
24
+ style={{ pointerEvents: "none" }}
25
+ />
26
+ }
27
+ onPress={() =>
28
+ onSelectionChange(
29
+ selectedItems.includes(value)
30
+ ? selectedItems.filter((i) => i !== value)
31
+ : [...selectedItems, value]
32
+ )
33
+ }
34
+ {...buttonProps}
35
+ >
36
+ {label}
37
+ </Button>
38
+ );
39
+ });
40
+ }
@@ -0,0 +1,26 @@
1
+ import type { ReactNode } from "react";
2
+ import { Link } from "react-router";
3
+ import { SidebarMenuButton, SidebarMenuItem } from "#components/ui/sidebar";
4
+
5
+ export function SidebarItem({
6
+ label,
7
+ icon,
8
+ link,
9
+ badge,
10
+ }: {
11
+ label: string;
12
+ icon: ReactNode;
13
+ link: string;
14
+ badge?: ReactNode;
15
+ }) {
16
+ return (
17
+ <SidebarMenuItem>
18
+ <SidebarMenuButton asChild tooltip={label}>
19
+ <Link to={link}>
20
+ {icon}
21
+ {badge ? badge : <span>{label}</span>}
22
+ </Link>
23
+ </SidebarMenuButton>
24
+ </SidebarMenuItem>
25
+ );
26
+ }
@@ -0,0 +1,340 @@
1
+ import { Check } from "lucide-react";
2
+ import type React from "react";
3
+ import { Badge } from "#components/ui/badge";
4
+ import { Separator } from "#components/ui/separator";
5
+ import { cn } from "#utils";
6
+
7
+ export interface Step {
8
+ id: string;
9
+ title: string;
10
+ description?: string;
11
+ status: "completed" | "current" | "upcoming";
12
+ }
13
+
14
+ export interface StepsProps {
15
+ steps: Step[];
16
+ className?: string;
17
+ onStepClick?: (step: number) => void;
18
+ }
19
+
20
+ export const Steps: React.FC<StepsProps> = ({ steps, className, onStepClick }) => {
21
+ return (
22
+ <nav aria-label="Progress" className={cn("w-full", className)}>
23
+ {/* Vertical layout for mobile/tablet */}
24
+ <ol className="lg:hidden space-y-6">
25
+ {steps.map((step, stepIdx) => (
26
+ <li key={step.id} className="relative">
27
+ <div className="flex items-start">
28
+ {/* Step indicator */}
29
+ <div className="flex-shrink-0">
30
+ {step.status === "completed" ? (
31
+ <button type="button" onClick={() => onStepClick?.(stepIdx)}>
32
+ <Badge
33
+ variant="default"
34
+ className="h-10 w-10 rounded-full p-0 flex items-center justify-center"
35
+ >
36
+ <Check className="h-5 w-5" />
37
+ </Badge>
38
+ </button>
39
+ ) : step.status === "current" ? (
40
+ <Badge
41
+ variant="default"
42
+ className="h-10 w-10 rounded-full p-0 flex items-center justify-center text-sm font-medium"
43
+ >
44
+ {stepIdx + 1}
45
+ </Badge>
46
+ ) : (
47
+ <Badge
48
+ variant="outline"
49
+ className="h-10 w-10 rounded-full p-0 flex items-center justify-center text-sm font-medium bg-background"
50
+ >
51
+ {stepIdx + 1}
52
+ </Badge>
53
+ )}
54
+ </div>
55
+
56
+ {/* Step content */}
57
+ <div className="ml-4 min-w-0 flex-1">
58
+ <p
59
+ className={cn(
60
+ "text-sm font-medium",
61
+ step.status === "completed" || step.status === "current"
62
+ ? "text-foreground"
63
+ : "text-muted-foreground"
64
+ )}
65
+ >
66
+ {step.title}
67
+ </p>
68
+ {step.description && (
69
+ <p
70
+ className={cn(
71
+ "text-sm mt-1",
72
+ step.status === "completed" || step.status === "current"
73
+ ? "text-muted-foreground"
74
+ : "text-muted-foreground/60"
75
+ )}
76
+ >
77
+ {step.description}
78
+ </p>
79
+ )}
80
+ </div>
81
+ </div>
82
+
83
+ {/* Connecting line - vertical */}
84
+ {stepIdx !== steps.length - 1 && (
85
+ <div className="absolute left-5 top-10 -ml-px mt-2 h-6">
86
+ <Separator
87
+ orientation="vertical"
88
+ className={cn(
89
+ "h-full",
90
+ step.status === "completed" ? "bg-primary" : "bg-muted-foreground/20"
91
+ )}
92
+ />
93
+ </div>
94
+ )}
95
+ </li>
96
+ ))}
97
+ </ol>
98
+
99
+ {/* Horizontal layout for desktop */}
100
+ <ol className="hidden lg:flex items-center justify-between">
101
+ {steps.map((step, stepIdx) => (
102
+ <li key={step.id} className="relative flex flex-col items-center flex-1">
103
+ {/* Step indicator */}
104
+ <div className="relative z-10 flex-shrink-0">
105
+ {step.status === "completed" ? (
106
+ <button type="button" onClick={() => onStepClick?.(stepIdx)}>
107
+ <Badge
108
+ variant="default"
109
+ className="h-10 w-10 rounded-full p-0 flex items-center justify-center"
110
+ >
111
+ <Check className="h-5 w-5" />
112
+ </Badge>
113
+ </button>
114
+ ) : step.status === "current" ? (
115
+ <Badge
116
+ variant="default"
117
+ className="h-10 w-10 rounded-full p-0 flex items-center justify-center text-sm font-medium"
118
+ >
119
+ {stepIdx + 1}
120
+ </Badge>
121
+ ) : (
122
+ <Badge
123
+ variant="outline"
124
+ className="h-10 w-10 rounded-full p-0 flex items-center justify-center text-sm font-medium bg-background"
125
+ >
126
+ {stepIdx + 1}
127
+ </Badge>
128
+ )}
129
+ </div>
130
+
131
+ {/* Step content */}
132
+ <div className="mt-3 text-center max-w-32">
133
+ <p
134
+ className={cn(
135
+ "text-sm font-medium",
136
+ step.status === "completed" || step.status === "current"
137
+ ? "text-foreground"
138
+ : "text-muted-foreground"
139
+ )}
140
+ >
141
+ {step.title}
142
+ </p>
143
+ {step.description && (
144
+ <p
145
+ className={cn(
146
+ "text-xs mt-1",
147
+ step.status === "completed" || step.status === "current"
148
+ ? "text-muted-foreground"
149
+ : "text-muted-foreground/60"
150
+ )}
151
+ >
152
+ {step.description}
153
+ </p>
154
+ )}
155
+ </div>
156
+
157
+ {/* Connecting line - horizontal */}
158
+ {stepIdx !== steps.length - 1 && (
159
+ <div className="absolute left-1/2 top-5 w-full h-0.5 -z-10">
160
+ <Separator
161
+ orientation="horizontal"
162
+ className={cn(
163
+ "w-full",
164
+ step.status === "completed" ? "bg-primary" : "bg-muted-foreground/20"
165
+ )}
166
+ />
167
+ </div>
168
+ )}
169
+ </li>
170
+ ))}
171
+ </ol>
172
+ </nav>
173
+ );
174
+ };
175
+
176
+ // Keep the individual components for specific use cases
177
+ export const VerticalSteps: React.FC<StepsProps> = ({ steps, className }) => {
178
+ return (
179
+ <nav aria-label="Progress" className={cn("", className)}>
180
+ <ol className="space-y-6">
181
+ {steps.map((step, stepIdx) => (
182
+ <li key={step.id} className="relative">
183
+ <div className="flex items-start">
184
+ {/* Step indicator */}
185
+ <div className="flex-shrink-0">
186
+ {step.status === "completed" ? (
187
+ <Badge
188
+ variant="default"
189
+ className="h-10 w-10 rounded-full p-0 flex items-center justify-center"
190
+ >
191
+ <Check className="h-5 w-5" />
192
+ </Badge>
193
+ ) : step.status === "current" ? (
194
+ <Badge
195
+ variant="default"
196
+ className="h-10 w-10 rounded-full p-0 flex items-center justify-center text-sm font-medium"
197
+ >
198
+ {stepIdx + 1}
199
+ </Badge>
200
+ ) : (
201
+ <Badge
202
+ variant="outline"
203
+ className="h-10 w-10 rounded-full p-0 flex items-center justify-center text-sm font-medium bg-background"
204
+ >
205
+ {stepIdx + 1}
206
+ </Badge>
207
+ )}
208
+ </div>
209
+
210
+ {/* Step content */}
211
+ <div className="ml-4 min-w-0 flex-1">
212
+ <p
213
+ className={cn(
214
+ "text-sm font-medium",
215
+ step.status === "completed" || step.status === "current"
216
+ ? "text-foreground"
217
+ : "text-muted-foreground"
218
+ )}
219
+ >
220
+ {step.title}
221
+ </p>
222
+ {step.description && (
223
+ <p
224
+ className={cn(
225
+ "text-sm mt-1",
226
+ step.status === "completed" || step.status === "current"
227
+ ? "text-muted-foreground"
228
+ : "text-muted-foreground/60"
229
+ )}
230
+ >
231
+ {step.description}
232
+ </p>
233
+ )}
234
+ </div>
235
+ </div>
236
+
237
+ {/* Connecting line */}
238
+ {stepIdx !== steps.length - 1 && (
239
+ <div className="absolute left-5 top-10 -ml-px mt-2 h-6">
240
+ <Separator
241
+ orientation="vertical"
242
+ className={cn(
243
+ "h-full",
244
+ step.status === "completed" ? "bg-primary" : "bg-muted-foreground/20"
245
+ )}
246
+ />
247
+ </div>
248
+ )}
249
+ </li>
250
+ ))}
251
+ </ol>
252
+ </nav>
253
+ );
254
+ };
255
+
256
+ export interface HorizontalStepsProps {
257
+ steps: Step[];
258
+ className?: string;
259
+ }
260
+
261
+ export const HorizontalSteps: React.FC<HorizontalStepsProps> = ({ steps, className }) => {
262
+ return (
263
+ <nav aria-label="Progress" className={cn("w-full", className)}>
264
+ <ol className="flex items-center justify-between">
265
+ {steps.map((step, stepIdx) => (
266
+ <li key={step.id} className="relative flex flex-col items-center flex-1">
267
+ {/* Step indicator */}
268
+ <div className="relative z-10 flex-shrink-0">
269
+ {step.status === "completed" ? (
270
+ <Badge
271
+ variant="default"
272
+ className="h-10 w-10 rounded-full p-0 flex items-center justify-center"
273
+ >
274
+ <Check className="h-5 w-5" />
275
+ </Badge>
276
+ ) : step.status === "current" ? (
277
+ <Badge
278
+ variant="default"
279
+ className="h-10 w-10 rounded-full p-0 flex items-center justify-center text-sm font-medium"
280
+ >
281
+ {stepIdx + 1}
282
+ </Badge>
283
+ ) : (
284
+ <Badge
285
+ variant="outline"
286
+ className="h-10 w-10 rounded-full p-0 flex items-center justify-center text-sm font-medium bg-background"
287
+ >
288
+ {stepIdx + 1}
289
+ </Badge>
290
+ )}
291
+ </div>
292
+
293
+ {/* Step content */}
294
+ <div className="mt-3 text-center max-w-32">
295
+ <p
296
+ className={cn(
297
+ "text-sm font-medium",
298
+ step.status === "completed" || step.status === "current"
299
+ ? "text-foreground"
300
+ : "text-muted-foreground"
301
+ )}
302
+ >
303
+ {step.title}
304
+ </p>
305
+ {step.description && (
306
+ <p
307
+ className={cn(
308
+ "text-xs mt-1",
309
+ step.status === "completed" || step.status === "current"
310
+ ? "text-muted-foreground"
311
+ : "text-muted-foreground/60"
312
+ )}
313
+ >
314
+ {step.description}
315
+ </p>
316
+ )}
317
+ </div>
318
+
319
+ {/* Connecting line */}
320
+ {stepIdx !== steps.length - 1 && (
321
+ <div className="absolute left-1/2 top-5 w-full h-0.5 -z-10">
322
+ <Separator
323
+ orientation="horizontal"
324
+ className={cn(
325
+ "w-full",
326
+ step.status === "completed" ? "bg-primary" : "bg-muted-foreground/20"
327
+ )}
328
+ />
329
+ </div>
330
+ )}
331
+ </li>
332
+ ))}
333
+ </ol>
334
+ </nav>
335
+ );
336
+ };
337
+
338
+ Steps.displayName = "Steps";
339
+ VerticalSteps.displayName = "VerticalSteps";
340
+ HorizontalSteps.displayName = "HorizontalSteps";