@stampui/blocks 1.0.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/dist/components/ai-chat-shell.d.ts +1 -0
- package/dist/components/ai-chat-shell.js +23 -0
- package/dist/components/prompt-input.d.ts +5 -0
- package/dist/components/prompt-input.js +47 -0
- package/dist/components/registry-card.d.ts +6 -0
- package/dist/components/registry-card.js +15 -0
- package/dist/components/registry-explorer.d.ts +8 -0
- package/dist/components/registry-explorer.js +38 -0
- package/dist/components/token-stream.d.ts +7 -0
- package/dist/components/token-stream.js +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +23 -0
- package/dist/manifests.d.ts +3 -0
- package/dist/manifests.js +1666 -0
- package/dist/types.d.ts +44 -0
- package/dist/types.js +2 -0
- package/package.json +28 -0
- package/src/components/blocks/ai-chat-shell.tsx +97 -0
- package/src/components/blocks/auth-panel.tsx +203 -0
- package/src/components/blocks/feature-grid.tsx +122 -0
- package/src/components/blocks/hero-section.tsx +73 -0
- package/src/components/blocks/notification-center.tsx +185 -0
- package/src/components/blocks/onboarding-flow.tsx +230 -0
- package/src/components/blocks/pricing-section.tsx +135 -0
- package/src/components/blocks/project-command-center.tsx +188 -0
- package/src/components/blocks/prompt-input.tsx +81 -0
- package/src/components/blocks/registry-card.tsx +104 -0
- package/src/components/blocks/registry-explorer.tsx +78 -0
- package/src/components/blocks/settings-layout.tsx +178 -0
- package/src/components/blocks/stats-strip.tsx +100 -0
- package/src/components/blocks/token-stream.tsx +42 -0
- package/src/components/blocks/usage-card.tsx +116 -0
- package/src/components/core/accordion.tsx +58 -0
- package/src/components/core/alert-dialog.tsx +113 -0
- package/src/components/core/alert.tsx +48 -0
- package/src/components/core/animated-number.tsx +77 -0
- package/src/components/core/aspect-ratio.tsx +20 -0
- package/src/components/core/avatar-stack.tsx +61 -0
- package/src/components/core/avatar.tsx +90 -0
- package/src/components/core/badge.tsx +39 -0
- package/src/components/core/breadcrumb.tsx +63 -0
- package/src/components/core/button-group.tsx +37 -0
- package/src/components/core/button.tsx +110 -0
- package/src/components/core/calendar.tsx +143 -0
- package/src/components/core/card.tsx +60 -0
- package/src/components/core/carousel.tsx +170 -0
- package/src/components/core/chart.tsx +377 -0
- package/src/components/core/checkbox.tsx +64 -0
- package/src/components/core/collapsible.tsx +30 -0
- package/src/components/core/combobox.tsx +114 -0
- package/src/components/core/command-box.tsx +22 -0
- package/src/components/core/command.tsx +165 -0
- package/src/components/core/confirm-action.tsx +94 -0
- package/src/components/core/context-menu.tsx +139 -0
- package/src/components/core/copy-button.tsx +41 -0
- package/src/components/core/data-table.tsx +173 -0
- package/src/components/core/date-picker.tsx +73 -0
- package/src/components/core/dialog.tsx +83 -0
- package/src/components/core/drawer.tsx +87 -0
- package/src/components/core/dropdown-menu.tsx +147 -0
- package/src/components/core/empty.tsx +34 -0
- package/src/components/core/field.tsx +39 -0
- package/src/components/core/file-upload.tsx +143 -0
- package/src/components/core/hover-card.tsx +31 -0
- package/src/components/core/inline-edit.tsx +104 -0
- package/src/components/core/input-group.tsx +47 -0
- package/src/components/core/input-otp.tsx +108 -0
- package/src/components/core/input.tsx +37 -0
- package/src/components/core/kbd.tsx +47 -0
- package/src/components/core/label.tsx +28 -0
- package/src/components/core/marquee.tsx +61 -0
- package/src/components/core/menubar.tsx +120 -0
- package/src/components/core/multi-select.tsx +145 -0
- package/src/components/core/native-select.tsx +27 -0
- package/src/components/core/navigation-menu.tsx +130 -0
- package/src/components/core/number-stepper.tsx +80 -0
- package/src/components/core/pagination.tsx +80 -0
- package/src/components/core/password-input.tsx +90 -0
- package/src/components/core/popover.tsx +34 -0
- package/src/components/core/progress.tsx +63 -0
- package/src/components/core/radio-group.tsx +77 -0
- package/src/components/core/resizable.tsx +250 -0
- package/src/components/core/scroll-area.tsx +38 -0
- package/src/components/core/select.tsx +128 -0
- package/src/components/core/separator.tsx +47 -0
- package/src/components/core/sheet.tsx +118 -0
- package/src/components/core/sidebar.tsx +129 -0
- package/src/components/core/skeleton.tsx +32 -0
- package/src/components/core/slider.tsx +97 -0
- package/src/components/core/sonner.tsx +29 -0
- package/src/components/core/spinner.tsx +60 -0
- package/src/components/core/status-pulse.tsx +67 -0
- package/src/components/core/stepper.tsx +111 -0
- package/src/components/core/switch.tsx +72 -0
- package/src/components/core/table.tsx +104 -0
- package/src/components/core/tabs.tsx +55 -0
- package/src/components/core/tag-input.tsx +93 -0
- package/src/components/core/textarea.tsx +44 -0
- package/src/components/core/timeline.tsx +81 -0
- package/src/components/core/toggle-group.tsx +56 -0
- package/src/components/core/toggle.tsx +66 -0
- package/src/components/core/tooltip.tsx +31 -0
- package/src/components/core/typing-indicator.tsx +51 -0
- package/src/index.ts +8 -0
- package/src/manifests.ts +1682 -0
- package/src/types.ts +58 -0
- package/src/ui.ts +13 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as RadixDialog from "@radix-ui/react-dialog"
|
|
5
|
+
import { X } from "lucide-react"
|
|
6
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
7
|
+
import { cx } from "@/lib/cx"
|
|
8
|
+
|
|
9
|
+
export const Sheet = RadixDialog.Root
|
|
10
|
+
export const SheetTrigger = RadixDialog.Trigger
|
|
11
|
+
export const SheetClose = RadixDialog.Close
|
|
12
|
+
export const SheetPortal = RadixDialog.Portal
|
|
13
|
+
|
|
14
|
+
export const SheetOverlay = React.forwardRef<
|
|
15
|
+
React.ElementRef<typeof RadixDialog.Overlay>,
|
|
16
|
+
React.ComponentPropsWithoutRef<typeof RadixDialog.Overlay>
|
|
17
|
+
>(({ className, ...props }, ref) => (
|
|
18
|
+
<RadixDialog.Overlay
|
|
19
|
+
ref={ref}
|
|
20
|
+
className={cx(
|
|
21
|
+
"fixed inset-0 z-50 bg-black/40 backdrop-blur-sm",
|
|
22
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
23
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
))
|
|
29
|
+
SheetOverlay.displayName = "SheetOverlay"
|
|
30
|
+
|
|
31
|
+
const sheetContentStyles = cva(
|
|
32
|
+
[
|
|
33
|
+
"fixed z-50 bg-card border-border shadow-xl outline-none",
|
|
34
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
35
|
+
"data-[state=closed]:duration-300 data-[state=open]:duration-300",
|
|
36
|
+
],
|
|
37
|
+
{
|
|
38
|
+
variants: {
|
|
39
|
+
side: {
|
|
40
|
+
right: [
|
|
41
|
+
"inset-y-0 right-0 h-full w-3/4 max-w-sm border-l",
|
|
42
|
+
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right",
|
|
43
|
+
],
|
|
44
|
+
left: [
|
|
45
|
+
"inset-y-0 left-0 h-full w-3/4 max-w-sm border-r",
|
|
46
|
+
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left",
|
|
47
|
+
],
|
|
48
|
+
top: [
|
|
49
|
+
"inset-x-0 top-0 w-full border-b",
|
|
50
|
+
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
51
|
+
],
|
|
52
|
+
bottom: [
|
|
53
|
+
"inset-x-0 bottom-0 w-full rounded-t-2xl border-t",
|
|
54
|
+
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
defaultVariants: {
|
|
59
|
+
side: "right",
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
export interface SheetContentProps
|
|
65
|
+
extends React.ComponentPropsWithoutRef<typeof RadixDialog.Content>,
|
|
66
|
+
VariantProps<typeof sheetContentStyles> {}
|
|
67
|
+
|
|
68
|
+
export const SheetContent = React.forwardRef<
|
|
69
|
+
React.ElementRef<typeof RadixDialog.Content>,
|
|
70
|
+
SheetContentProps
|
|
71
|
+
>(({ className, side, children, ...props }, ref) => (
|
|
72
|
+
<RadixDialog.Portal>
|
|
73
|
+
<SheetOverlay />
|
|
74
|
+
<RadixDialog.Content
|
|
75
|
+
ref={ref}
|
|
76
|
+
className={cx(sheetContentStyles({ side }), className)}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
<RadixDialog.Close className="absolute right-4 top-4 rounded-md p-1 text-muted-foreground transition-colors hover:bg-surface-2 hover:text-foreground outline-none focus-visible:ring-1 focus-visible:ring-border-strong focus-visible:ring-offset-1 focus-visible:ring-offset-background">
|
|
81
|
+
<X className="h-4 w-4" />
|
|
82
|
+
<span className="sr-only">Close</span>
|
|
83
|
+
</RadixDialog.Close>
|
|
84
|
+
</RadixDialog.Content>
|
|
85
|
+
</RadixDialog.Portal>
|
|
86
|
+
))
|
|
87
|
+
SheetContent.displayName = "SheetContent"
|
|
88
|
+
|
|
89
|
+
export const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
90
|
+
<div className={cx("flex flex-col gap-1.5 p-6 pb-0", className)} {...props} />
|
|
91
|
+
)
|
|
92
|
+
SheetHeader.displayName = "SheetHeader"
|
|
93
|
+
|
|
94
|
+
export const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
95
|
+
<div className={cx("flex items-center justify-end gap-2 p-6 pt-0 mt-auto", className)} {...props} />
|
|
96
|
+
)
|
|
97
|
+
SheetFooter.displayName = "SheetFooter"
|
|
98
|
+
|
|
99
|
+
export const SheetTitle = React.forwardRef<
|
|
100
|
+
React.ElementRef<typeof RadixDialog.Title>,
|
|
101
|
+
React.ComponentPropsWithoutRef<typeof RadixDialog.Title>
|
|
102
|
+
>(({ className, ...props }, ref) => (
|
|
103
|
+
<RadixDialog.Title ref={ref} className={cx("text-base font-semibold leading-tight text-foreground", className)} {...props} />
|
|
104
|
+
))
|
|
105
|
+
SheetTitle.displayName = "SheetTitle"
|
|
106
|
+
|
|
107
|
+
export const SheetDescription = React.forwardRef<
|
|
108
|
+
React.ElementRef<typeof RadixDialog.Description>,
|
|
109
|
+
React.ComponentPropsWithoutRef<typeof RadixDialog.Description>
|
|
110
|
+
>(({ className, ...props }, ref) => (
|
|
111
|
+
<RadixDialog.Description ref={ref} className={cx("text-sm text-muted-foreground leading-relaxed", className)} {...props} />
|
|
112
|
+
))
|
|
113
|
+
SheetDescription.displayName = "SheetDescription"
|
|
114
|
+
|
|
115
|
+
export const SheetBody = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
116
|
+
<div className={cx("flex-1 overflow-y-auto px-6 py-4", className)} {...props} />
|
|
117
|
+
)
|
|
118
|
+
SheetBody.displayName = "SheetBody"
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { ChevronRight } from "lucide-react"
|
|
5
|
+
import { cx } from "@/lib/cx"
|
|
6
|
+
|
|
7
|
+
// ── Root ──────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export interface SidebarProps extends React.HTMLAttributes<HTMLElement> {
|
|
10
|
+
collapsed?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Sidebar({ className, collapsed = false, children, ...props }: SidebarProps) {
|
|
14
|
+
return (
|
|
15
|
+
<aside
|
|
16
|
+
data-collapsed={collapsed || undefined}
|
|
17
|
+
className={cx(
|
|
18
|
+
"flex flex-col gap-1 py-4 transition-all duration-200",
|
|
19
|
+
collapsed ? "w-14" : "w-60",
|
|
20
|
+
className
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
{children}
|
|
25
|
+
</aside>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Section ───────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
export interface SidebarSectionProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
32
|
+
title?: string
|
|
33
|
+
collapsed?: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function SidebarSection({ title, collapsed, className, children, ...props }: SidebarSectionProps) {
|
|
37
|
+
return (
|
|
38
|
+
<div className={cx("flex flex-col gap-0.5", className)} {...props}>
|
|
39
|
+
{title && !collapsed && (
|
|
40
|
+
<p className="px-3 py-1 text-[11px] font-semibold text-muted-foreground uppercase tracking-widest">
|
|
41
|
+
{title}
|
|
42
|
+
</p>
|
|
43
|
+
)}
|
|
44
|
+
{children}
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Item ──────────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
export interface SidebarItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
52
|
+
icon?: React.ReactNode
|
|
53
|
+
label: string
|
|
54
|
+
active?: boolean
|
|
55
|
+
badge?: React.ReactNode
|
|
56
|
+
collapsed?: boolean
|
|
57
|
+
href?: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function SidebarItem({ icon, label, active, badge, collapsed, href, className, ...props }: SidebarItemProps) {
|
|
61
|
+
const Tag = href ? "a" : "button"
|
|
62
|
+
return (
|
|
63
|
+
<Tag
|
|
64
|
+
{...(href ? { href } : { type: "button" })}
|
|
65
|
+
aria-current={active ? "page" : undefined}
|
|
66
|
+
title={collapsed ? label : undefined}
|
|
67
|
+
className={cx(
|
|
68
|
+
"flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium transition-colors w-full",
|
|
69
|
+
active
|
|
70
|
+
? "bg-surface-2 text-foreground"
|
|
71
|
+
: "text-muted-foreground hover:bg-surface-2/60 hover:text-foreground",
|
|
72
|
+
collapsed && "justify-center px-2",
|
|
73
|
+
className
|
|
74
|
+
)}
|
|
75
|
+
{...(props as any)}
|
|
76
|
+
>
|
|
77
|
+
{icon && <span className="shrink-0 text-[1em]">{icon}</span>}
|
|
78
|
+
{!collapsed && <span className="flex-1 truncate">{label}</span>}
|
|
79
|
+
{!collapsed && badge && <span className="shrink-0">{badge}</span>}
|
|
80
|
+
</Tag>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Collapsible group ─────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
export interface SidebarGroupProps {
|
|
87
|
+
icon?: React.ReactNode
|
|
88
|
+
label: string
|
|
89
|
+
defaultOpen?: boolean
|
|
90
|
+
collapsed?: boolean
|
|
91
|
+
className?: string
|
|
92
|
+
children: React.ReactNode
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function SidebarGroup({ icon, label, defaultOpen = false, collapsed, className, children }: SidebarGroupProps) {
|
|
96
|
+
const [open, setOpen] = React.useState(defaultOpen)
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div className={cx("flex flex-col gap-0.5", className)}>
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
onClick={() => setOpen((v) => !v)}
|
|
103
|
+
className={cx(
|
|
104
|
+
"flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-surface-2/60 hover:text-foreground transition-colors w-full",
|
|
105
|
+
collapsed && "justify-center px-2"
|
|
106
|
+
)}
|
|
107
|
+
>
|
|
108
|
+
{icon && <span className="shrink-0 text-[1em]">{icon}</span>}
|
|
109
|
+
{!collapsed && (
|
|
110
|
+
<>
|
|
111
|
+
<span className="flex-1 truncate text-left">{label}</span>
|
|
112
|
+
<ChevronRight className={cx("h-3.5 w-3.5 shrink-0 transition-transform duration-200", open && "rotate-90")} />
|
|
113
|
+
</>
|
|
114
|
+
)}
|
|
115
|
+
</button>
|
|
116
|
+
{open && !collapsed && (
|
|
117
|
+
<div className="ml-3 border-l border-border pl-3 flex flex-col gap-0.5">
|
|
118
|
+
{children}
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Separator ─────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
export function SidebarSeparator({ className }: { className?: string }) {
|
|
128
|
+
return <div className={cx("mx-3 my-2 h-px bg-border", className)} />
|
|
129
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cx } from "@/lib/cx"
|
|
4
|
+
|
|
5
|
+
const skeletonStyles = cva(
|
|
6
|
+
"animate-pulse bg-surface-3",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "rounded-lg",
|
|
11
|
+
circle: "rounded-full",
|
|
12
|
+
text: "rounded",
|
|
13
|
+
pill: "rounded-full",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: { variant: "default" },
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
export interface SkeletonProps
|
|
21
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
22
|
+
VariantProps<typeof skeletonStyles> {}
|
|
23
|
+
|
|
24
|
+
export function Skeleton({ className, variant, ...props }: SkeletonProps) {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
className={cx(skeletonStyles({ variant }), className)}
|
|
28
|
+
aria-hidden="true"
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as SliderPrimitive from "@radix-ui/react-slider"
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
6
|
+
import { cx } from "@/lib/cx"
|
|
7
|
+
|
|
8
|
+
const sliderStyles = cva(
|
|
9
|
+
"relative flex touch-none select-none items-center",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
orientation: {
|
|
13
|
+
horizontal: "w-full",
|
|
14
|
+
vertical: "flex-col h-full min-h-[160px] w-auto",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: { orientation: "horizontal" },
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const trackStyles = cva(
|
|
22
|
+
"relative grow overflow-hidden rounded-full bg-surface-3",
|
|
23
|
+
{
|
|
24
|
+
variants: {
|
|
25
|
+
orientation: {
|
|
26
|
+
horizontal: "h-1.5 w-full",
|
|
27
|
+
vertical: "h-full w-1.5",
|
|
28
|
+
},
|
|
29
|
+
size: {
|
|
30
|
+
sm: "",
|
|
31
|
+
md: "",
|
|
32
|
+
lg: "",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
compoundVariants: [
|
|
36
|
+
{ orientation: "horizontal", size: "sm", className: "h-1" },
|
|
37
|
+
{ orientation: "horizontal", size: "lg", className: "h-2" },
|
|
38
|
+
{ orientation: "vertical", size: "sm", className: "w-1" },
|
|
39
|
+
{ orientation: "vertical", size: "lg", className: "w-2" },
|
|
40
|
+
],
|
|
41
|
+
defaultVariants: { orientation: "horizontal", size: "md" },
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const thumbStyles = cva(
|
|
46
|
+
[
|
|
47
|
+
"block rounded-full bg-foreground ring-offset-background",
|
|
48
|
+
"transition-all duration-100",
|
|
49
|
+
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-strong focus-visible:ring-offset-1 focus-visible:ring-offset-background",
|
|
50
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
51
|
+
"hover:scale-110",
|
|
52
|
+
],
|
|
53
|
+
{
|
|
54
|
+
variants: {
|
|
55
|
+
size: {
|
|
56
|
+
sm: "h-3.5 w-3.5",
|
|
57
|
+
md: "h-4 w-4",
|
|
58
|
+
lg: "h-5 w-5",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
defaultVariants: { size: "md" },
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
export interface SliderProps
|
|
66
|
+
extends Omit<React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>, "orientation">,
|
|
67
|
+
VariantProps<typeof sliderStyles>,
|
|
68
|
+
VariantProps<typeof thumbStyles> {
|
|
69
|
+
orientation?: "horizontal" | "vertical"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const Slider = React.forwardRef<
|
|
73
|
+
React.ElementRef<typeof SliderPrimitive.Root>,
|
|
74
|
+
SliderProps
|
|
75
|
+
>(({ className, orientation = "horizontal", size, ...props }, ref) => (
|
|
76
|
+
<SliderPrimitive.Root
|
|
77
|
+
ref={ref}
|
|
78
|
+
orientation={orientation}
|
|
79
|
+
className={cx(sliderStyles({ orientation }), className)}
|
|
80
|
+
{...props}
|
|
81
|
+
>
|
|
82
|
+
<SliderPrimitive.Track className={trackStyles({ orientation, size })}>
|
|
83
|
+
<SliderPrimitive.Range
|
|
84
|
+
className={cx(
|
|
85
|
+
"absolute bg-foreground",
|
|
86
|
+
orientation === "horizontal" ? "h-full" : "w-full",
|
|
87
|
+
)}
|
|
88
|
+
/>
|
|
89
|
+
</SliderPrimitive.Track>
|
|
90
|
+
{(props.defaultValue ?? props.value ?? [0]).map((_, i) => (
|
|
91
|
+
<SliderPrimitive.Thumb key={i} className={thumbStyles({ size })} />
|
|
92
|
+
))}
|
|
93
|
+
</SliderPrimitive.Root>
|
|
94
|
+
))
|
|
95
|
+
Slider.displayName = "Slider"
|
|
96
|
+
|
|
97
|
+
export { Slider }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Toaster as SonnerToaster } from "sonner"
|
|
4
|
+
import { useTheme } from "next-themes"
|
|
5
|
+
|
|
6
|
+
export { toast } from "sonner"
|
|
7
|
+
|
|
8
|
+
export function Toaster() {
|
|
9
|
+
const { resolvedTheme } = useTheme()
|
|
10
|
+
return (
|
|
11
|
+
<SonnerToaster
|
|
12
|
+
theme={resolvedTheme as "light" | "dark" | undefined}
|
|
13
|
+
position="bottom-right"
|
|
14
|
+
toastOptions={{
|
|
15
|
+
classNames: {
|
|
16
|
+
toast:
|
|
17
|
+
"group border border-border bg-card text-foreground shadow-lg rounded-xl text-sm font-medium gap-3 px-4 py-3",
|
|
18
|
+
description: "text-muted-foreground text-xs",
|
|
19
|
+
actionButton: "bg-foreground text-background text-xs font-medium rounded-md px-2.5 py-1 hover:bg-foreground/90",
|
|
20
|
+
cancelButton: "bg-surface-2 text-muted-foreground text-xs font-medium rounded-md px-2.5 py-1 hover:bg-surface-3",
|
|
21
|
+
error: "border-danger/30",
|
|
22
|
+
success: "border-border",
|
|
23
|
+
warning: "border-border",
|
|
24
|
+
info: "border-border",
|
|
25
|
+
},
|
|
26
|
+
}}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cx } from "@/lib/cx"
|
|
4
|
+
|
|
5
|
+
const spinnerStyles = cva(
|
|
6
|
+
"animate-spin text-muted-foreground",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
size: {
|
|
10
|
+
xs: "h-3 w-3",
|
|
11
|
+
sm: "h-4 w-4",
|
|
12
|
+
md: "h-5 w-5",
|
|
13
|
+
lg: "h-6 w-6",
|
|
14
|
+
xl: "h-8 w-8",
|
|
15
|
+
},
|
|
16
|
+
variant: {
|
|
17
|
+
default: "text-muted-foreground",
|
|
18
|
+
primary: "text-foreground",
|
|
19
|
+
white: "text-white",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: { size: "md", variant: "default" },
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
export interface SpinnerProps
|
|
27
|
+
extends Omit<React.SVGAttributes<SVGElement>, "color">,
|
|
28
|
+
VariantProps<typeof spinnerStyles> {
|
|
29
|
+
label?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function Spinner({ className, size, variant, label = "Loading...", ...props }: SpinnerProps) {
|
|
33
|
+
return (
|
|
34
|
+
<svg
|
|
35
|
+
role="status"
|
|
36
|
+
aria-label={label}
|
|
37
|
+
viewBox="0 0 24 24"
|
|
38
|
+
fill="none"
|
|
39
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
40
|
+
className={cx(spinnerStyles({ size, variant }), className)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
<circle
|
|
44
|
+
cx="12" cy="12" r="10"
|
|
45
|
+
stroke="currentColor"
|
|
46
|
+
strokeWidth="3"
|
|
47
|
+
strokeLinecap="round"
|
|
48
|
+
strokeDasharray="40 60"
|
|
49
|
+
opacity="0.25"
|
|
50
|
+
/>
|
|
51
|
+
<circle
|
|
52
|
+
cx="12" cy="12" r="10"
|
|
53
|
+
stroke="currentColor"
|
|
54
|
+
strokeWidth="3"
|
|
55
|
+
strokeLinecap="round"
|
|
56
|
+
strokeDasharray="40 60"
|
|
57
|
+
/>
|
|
58
|
+
</svg>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cx } from "@/lib/cx"
|
|
4
|
+
|
|
5
|
+
const dotStyles = cva("relative inline-flex rounded-full shrink-0", {
|
|
6
|
+
variants: {
|
|
7
|
+
status: {
|
|
8
|
+
online: "bg-green-500",
|
|
9
|
+
offline: "bg-muted-foreground/40",
|
|
10
|
+
processing: "bg-yellow-400",
|
|
11
|
+
error: "bg-red-500",
|
|
12
|
+
warning: "bg-orange-400",
|
|
13
|
+
},
|
|
14
|
+
size: {
|
|
15
|
+
sm: "h-1.5 w-1.5",
|
|
16
|
+
md: "h-2 w-2",
|
|
17
|
+
lg: "h-2.5 w-2.5",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: { status: "online", size: "md" },
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const pingStyles = cva("absolute inline-flex h-full w-full rounded-full opacity-75 animate-ping", {
|
|
24
|
+
variants: {
|
|
25
|
+
status: {
|
|
26
|
+
online: "bg-green-500",
|
|
27
|
+
offline: "hidden",
|
|
28
|
+
processing: "bg-yellow-400",
|
|
29
|
+
error: "bg-red-500",
|
|
30
|
+
warning: "bg-orange-400",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: { status: "online" },
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const labels: Record<string, string> = {
|
|
37
|
+
online: "Online",
|
|
38
|
+
offline: "Offline",
|
|
39
|
+
processing: "Processing",
|
|
40
|
+
error: "Error",
|
|
41
|
+
warning: "Warning",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface StatusPulseProps
|
|
45
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
46
|
+
VariantProps<typeof dotStyles> {
|
|
47
|
+
label?: string | boolean
|
|
48
|
+
pulse?: boolean
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function StatusPulse({ status, size, label, pulse = true, className, ...props }: StatusPulseProps) {
|
|
52
|
+
const showLabel = label === true ? labels[status ?? "online"] : label
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className={cx("inline-flex items-center gap-1.5", className)} {...props}>
|
|
56
|
+
<span className="relative inline-flex items-center justify-center">
|
|
57
|
+
{pulse && status !== "offline" && (
|
|
58
|
+
<span className={pingStyles({ status })} />
|
|
59
|
+
)}
|
|
60
|
+
<span className={dotStyles({ status, size })} />
|
|
61
|
+
</span>
|
|
62
|
+
{showLabel && (
|
|
63
|
+
<span className="text-xs text-muted-foreground">{showLabel}</span>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Check, X } from "lucide-react"
|
|
3
|
+
import { cx } from "@/lib/cx"
|
|
4
|
+
|
|
5
|
+
export type StepStatus = "pending" | "active" | "completed" | "error"
|
|
6
|
+
export type StepperOrientation = "horizontal" | "vertical"
|
|
7
|
+
|
|
8
|
+
export interface Step {
|
|
9
|
+
title: string
|
|
10
|
+
description?: string
|
|
11
|
+
icon?: React.ReactNode
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface StepperProps {
|
|
15
|
+
steps: Step[]
|
|
16
|
+
currentStep: number
|
|
17
|
+
orientation?: StepperOrientation
|
|
18
|
+
className?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function StepIcon({ status, index, icon }: { status: StepStatus; index: number; icon?: React.ReactNode }) {
|
|
22
|
+
if (status === "completed") return <Check className="h-3.5 w-3.5" />
|
|
23
|
+
if (status === "error") return <X className="h-3.5 w-3.5" />
|
|
24
|
+
if (icon && status === "active") return <span className="text-xs">{icon}</span>
|
|
25
|
+
return <span className="text-xs font-medium">{index + 1}</span>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function Stepper({ steps, currentStep, orientation = "horizontal", className }: StepperProps) {
|
|
29
|
+
const isHorizontal = orientation === "horizontal"
|
|
30
|
+
|
|
31
|
+
function getStatus(index: number): StepStatus {
|
|
32
|
+
if (index < currentStep) return "completed"
|
|
33
|
+
if (index === currentStep) return "active"
|
|
34
|
+
return "pending"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (isHorizontal) {
|
|
38
|
+
return (
|
|
39
|
+
<div className={cx("flex items-start w-full", className)}>
|
|
40
|
+
{steps.map((step, i) => {
|
|
41
|
+
const status = getStatus(i)
|
|
42
|
+
const isLast = i === steps.length - 1
|
|
43
|
+
return (
|
|
44
|
+
<React.Fragment key={i}>
|
|
45
|
+
<div className="flex flex-col items-center flex-1 min-w-0">
|
|
46
|
+
<div className={cx(
|
|
47
|
+
"flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 transition-colors",
|
|
48
|
+
status === "completed" && "border-foreground bg-foreground text-background",
|
|
49
|
+
status === "active" && "border-foreground bg-transparent text-foreground",
|
|
50
|
+
status === "pending" && "border-border bg-transparent text-muted-foreground",
|
|
51
|
+
status === "error" && "border-danger bg-danger/10 text-danger",
|
|
52
|
+
)}>
|
|
53
|
+
<StepIcon status={status} index={i} icon={step.icon} />
|
|
54
|
+
</div>
|
|
55
|
+
<div className="mt-2 text-center px-1">
|
|
56
|
+
<p className={cx("text-xs font-medium leading-tight", status === "pending" ? "text-muted-foreground" : "text-foreground")}>
|
|
57
|
+
{step.title}
|
|
58
|
+
</p>
|
|
59
|
+
{step.description && (
|
|
60
|
+
<p className="text-xs text-muted-foreground mt-0.5 leading-snug">{step.description}</p>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
{!isLast && (
|
|
65
|
+
<div className={cx(
|
|
66
|
+
"h-0.5 flex-1 mt-4 mx-2 rounded-full transition-colors",
|
|
67
|
+
i < currentStep ? "bg-foreground" : "bg-border"
|
|
68
|
+
)} />
|
|
69
|
+
)}
|
|
70
|
+
</React.Fragment>
|
|
71
|
+
)
|
|
72
|
+
})}
|
|
73
|
+
</div>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className={cx("flex flex-col", className)}>
|
|
79
|
+
{steps.map((step, i) => {
|
|
80
|
+
const status = getStatus(i)
|
|
81
|
+
const isLast = i === steps.length - 1
|
|
82
|
+
return (
|
|
83
|
+
<div key={i} className="flex gap-4">
|
|
84
|
+
<div className="flex flex-col items-center">
|
|
85
|
+
<div className={cx(
|
|
86
|
+
"flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 transition-colors",
|
|
87
|
+
status === "completed" && "border-foreground bg-foreground text-background",
|
|
88
|
+
status === "active" && "border-foreground bg-transparent text-foreground",
|
|
89
|
+
status === "pending" && "border-border bg-transparent text-muted-foreground",
|
|
90
|
+
status === "error" && "border-danger bg-danger/10 text-danger",
|
|
91
|
+
)}>
|
|
92
|
+
<StepIcon status={status} index={i} icon={step.icon} />
|
|
93
|
+
</div>
|
|
94
|
+
{!isLast && (
|
|
95
|
+
<div className={cx("w-0.5 flex-1 my-1 rounded-full transition-colors min-h-[24px]", i < currentStep ? "bg-foreground" : "bg-border")} />
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
<div className={cx("pb-6 min-w-0", isLast && "pb-0")}>
|
|
99
|
+
<p className={cx("text-sm font-medium leading-tight mt-1.5", status === "pending" ? "text-muted-foreground" : "text-foreground")}>
|
|
100
|
+
{step.title}
|
|
101
|
+
</p>
|
|
102
|
+
{step.description && (
|
|
103
|
+
<p className="text-xs text-muted-foreground mt-0.5 leading-relaxed">{step.description}</p>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
)
|
|
108
|
+
})}
|
|
109
|
+
</div>
|
|
110
|
+
)
|
|
111
|
+
}
|