@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.
- package/LICENSE +621 -0
- package/README.md +17 -0
- package/package.json +169 -0
- package/src/animations/card.motion.ts +9 -0
- package/src/components/AvatarUpload.tsx +133 -0
- package/src/components/Button.tsx +14 -0
- package/src/components/Calendar.css +684 -0
- package/src/components/Calendar.tsx +32 -0
- package/src/components/CardsSelect.tsx +155 -0
- package/src/components/CollapsibleSidebarMenuItem.tsx +57 -0
- package/src/components/ColorPicker.tsx +56 -0
- package/src/components/CopyButton.tsx +45 -0
- package/src/components/CropDialog.tsx +154 -0
- package/src/components/DialogProvider.tsx +105 -0
- package/src/components/ErrorFallback.tsx +17 -0
- package/src/components/FileDropzone.tsx +120 -0
- package/src/components/MultiSelectDropdown.tsx +233 -0
- package/src/components/Orb.tsx +288 -0
- package/src/components/PageAlert.tsx +121 -0
- package/src/components/SelectChips.tsx +40 -0
- package/src/components/SidebarItem.tsx +26 -0
- package/src/components/Steps.tsx +340 -0
- package/src/components/TablerIconPicker.tsx +4260 -0
- package/src/components/app-header.tsx +40 -0
- package/src/components/blur-card.tsx +132 -0
- package/src/components/features-section-demo-1.tsx +127 -0
- package/src/components/features-section-demo-2.tsx +102 -0
- package/src/components/features-section-demo-3.tsx +272 -0
- package/src/components/mode-toggle.tsx +31 -0
- package/src/components/nav-main.tsx +69 -0
- package/src/components/pricing-cards.tsx +133 -0
- package/src/components/shared/ButtonCopy.tsx +50 -0
- package/src/components/team-switcher.tsx +83 -0
- package/src/components/theme-provider.tsx +74 -0
- package/src/components/typewriter.tsx +90 -0
- package/src/components/ui/alert-dialog.tsx +133 -0
- package/src/components/ui/alert.tsx +60 -0
- package/src/components/ui/avatar.tsx +47 -0
- package/src/components/ui/badge.tsx +33 -0
- package/src/components/ui/bento-grid.tsx +54 -0
- package/src/components/ui/bento-grid2.tsx +66 -0
- package/src/components/ui/breadcrumb.tsx +101 -0
- package/src/components/ui/button.tsx +50 -0
- package/src/components/ui/card.tsx +55 -0
- package/src/components/ui/checkbox.tsx +26 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/dialog.tsx +119 -0
- package/src/components/ui/dropdown-menu.tsx +186 -0
- package/src/components/ui/floating-navbar.tsx +78 -0
- package/src/components/ui/form.tsx +167 -0
- package/src/components/ui/image.tsx +55 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/pagination.tsx +105 -0
- package/src/components/ui/progress.tsx +23 -0
- package/src/components/ui/resizable-navbar.tsx +260 -0
- package/src/components/ui/segment-control.tsx +143 -0
- package/src/components/ui/select.tsx +153 -0
- package/src/components/ui/separator.tsx +24 -0
- package/src/components/ui/sheet.tsx +121 -0
- package/src/components/ui/sidebar.tsx +736 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/slider.tsx +23 -0
- package/src/components/ui/sonner.tsx +27 -0
- package/src/components/ui/spinner.tsx +45 -0
- package/src/components/ui/switch.tsx +27 -0
- package/src/components/ui/table.tsx +90 -0
- package/src/components/ui/tabs.tsx +52 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/timeline.tsx +95 -0
- package/src/components/ui/toast.tsx +126 -0
- package/src/components/ui/tooltip.tsx +55 -0
- package/src/components/ui/typewriter-effect.tsx +181 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/hooks/useDialog.ts +25 -0
- package/src/icons/GoogleIcon.tsx +32 -0
- package/src/icons/LinkedInIcon.tsx +30 -0
- package/src/icons/MicrosoftIcon.tsx +21 -0
- package/src/lib/chatwoot.ts +51 -0
- package/src/lib/utils.ts +6 -0
- package/src/modules/app/components/AppLoader.tsx +9 -0
- package/src/modules/app/components/AppShell.tsx +21 -0
- package/src/modules/app/components/AppSidebar.tsx +26 -0
- package/src/modules/app/components/AppSidebarContent.tsx +73 -0
- package/src/modules/app/components/AppSidebarHeader.tsx +57 -0
- package/src/modules/app/components/AppSidebarInvites.tsx +32 -0
- package/src/modules/app/components/AppSidebarUser.tsx +128 -0
- package/src/modules/auth/components/AdminUserManagement.tsx +1136 -0
- package/src/modules/auth/components/AdminWaitlist.tsx +358 -0
- package/src/modules/auth/components/AuthLayout.tsx +13 -0
- package/src/modules/auth/components/AuthProviders.tsx +105 -0
- package/src/modules/auth/components/AuthRouter.tsx +29 -0
- package/src/modules/auth/components/ClaimAccountRoute.tsx +242 -0
- package/src/modules/auth/components/ErrorAuthRoute.tsx +121 -0
- package/src/modules/auth/components/ForgotPasswordForm.tsx +58 -0
- package/src/modules/auth/components/ForgotPasswordRoute.tsx +27 -0
- package/src/modules/auth/components/InviteFriends.tsx +273 -0
- package/src/modules/auth/components/LastUsedBadge.tsx +22 -0
- package/src/modules/auth/components/LoginForm.tsx +104 -0
- package/src/modules/auth/components/LoginRoute.tsx +31 -0
- package/src/modules/auth/components/LogoutRoute.tsx +21 -0
- package/src/modules/auth/components/OrganizationAcceptInvitationRoute.tsx +161 -0
- package/src/modules/auth/components/OrganizationMembersRoute.tsx +730 -0
- package/src/modules/auth/components/OrganizationSettingsRoute.tsx +280 -0
- package/src/modules/auth/components/OrganizationSwitcher.tsx +148 -0
- package/src/modules/auth/components/ProfileRoute.tsx +104 -0
- package/src/modules/auth/components/RangeNuqsDatePicker.tsx +365 -0
- package/src/modules/auth/components/ResetPasswordForm.tsx +103 -0
- package/src/modules/auth/components/ResetPasswordRoute.tsx +27 -0
- package/src/modules/auth/components/SignupFormRoute.tsx +189 -0
- package/src/modules/auth/components/SignupRoute.tsx +53 -0
- package/src/modules/auth/components/UserPreferences.tsx +144 -0
- package/src/modules/auth/components/WaitlistCard.tsx +78 -0
- package/src/modules/auth/components/WaitlistCodeValidation.tsx +79 -0
- package/src/modules/billing/components/BillingBetaPage.tsx +124 -0
- package/src/modules/billing/components/BillingInvoicePage.tsx +180 -0
- package/src/modules/billing/components/BillingPlanSelect.tsx +14 -0
- package/src/modules/billing/components/BillingRouter.tsx +20 -0
- package/src/modules/billing/components/BillingSinglePlanSelect.tsx +172 -0
- package/src/modules/table/components/ColumnOrderAndVisibility.tsx +127 -0
- package/src/modules/table/components/NuqsTable.tsx +396 -0
- package/src/modules/table/components/TableFiltering.tsx +520 -0
- package/src/modules/table/components/TablePagination.tsx +59 -0
- package/src/modules/table/components/table.types.ts +11 -0
- package/src/modules/table/filterTransformers.ts +323 -0
- package/src/types.ts +4 -0
- 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";
|