@etherealui/react 0.0.0-stage → 0.0.2
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 +21 -0
- package/README.md +62 -2
- package/dist/index.d.mts +583 -0
- package/dist/index.d.ts +583 -0
- package/dist/index.js +5189 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5189 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +83 -3
- package/src/blocks/app-shell.tsx +441 -0
- package/src/blocks/billing-card.tsx +2 -0
- package/src/blocks/data-table.tsx +479 -0
- package/src/blocks/filter-bar.tsx +134 -0
- package/src/blocks/header-navbar.tsx +7 -0
- package/src/blocks/metric-grid.tsx +5 -0
- package/src/blocks/profile-form.tsx +2 -0
- package/src/blocks/security-settings.tsx +2 -0
- package/src/blocks/settings-blocks.tsx +406 -0
- package/src/blocks/sidebar.tsx +6 -0
- package/src/blocks/stat-card.tsx +7 -0
- package/src/blocks/stat-cards.tsx +212 -0
- package/src/blocks/team-members.tsx +5 -0
- package/src/index.ts +114 -0
- package/src/primitives/accordion.tsx +61 -0
- package/src/primitives/announcement.tsx +33 -0
- package/src/primitives/avatar.tsx +156 -0
- package/src/primitives/badge.tsx +108 -0
- package/src/primitives/breadcrumb.tsx +45 -0
- package/src/primitives/button-group.tsx +79 -0
- package/src/primitives/button.tsx +165 -0
- package/src/primitives/carousel.tsx +84 -0
- package/src/primitives/chart.tsx +831 -0
- package/src/primitives/checkbox.tsx +26 -0
- package/src/primitives/chip.tsx +37 -0
- package/src/primitives/close-button.tsx +29 -0
- package/src/primitives/color-picker.tsx +62 -0
- package/src/primitives/combobox.tsx +123 -0
- package/src/primitives/command-palette.tsx +216 -0
- package/src/primitives/context-menu.tsx +84 -0
- package/src/primitives/date-calendar.tsx +76 -0
- package/src/primitives/date-picker.tsx +400 -0
- package/src/primitives/dialog.tsx +279 -0
- package/src/primitives/divider.tsx +31 -0
- package/src/primitives/drawer.tsx +95 -0
- package/src/primitives/dropdown-menu.tsx +164 -0
- package/src/primitives/file-upload.tsx +123 -0
- package/src/primitives/icon-button.tsx +23 -0
- package/src/primitives/input.tsx +124 -0
- package/src/primitives/modal.tsx +26 -0
- package/src/primitives/pagination.tsx +58 -0
- package/src/primitives/popover.tsx +28 -0
- package/src/primitives/progress.tsx +145 -0
- package/src/primitives/radio-group.tsx +63 -0
- package/src/primitives/select.tsx +207 -0
- package/src/primitives/skeleton.tsx +61 -0
- package/src/primitives/slider.tsx +44 -0
- package/src/primitives/switch.tsx +146 -0
- package/src/primitives/table.tsx +47 -0
- package/src/primitives/tabs.tsx +86 -0
- package/src/primitives/textarea.tsx +40 -0
- package/src/primitives/toast.tsx +71 -0
- package/src/primitives/tooltip.tsx +58 -0
- package/src/styles/globals.css +49 -0
- package/src/templates/control-tower.tsx +41 -0
- package/src/templates/executive-analytics.tsx +272 -0
- package/src/templates/workspace-settings.tsx +179 -0
- package/src/utils/cn.ts +7 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Utils
|
|
2
|
+
export { cn } from "./utils/cn"
|
|
3
|
+
|
|
4
|
+
// Individual Primitives
|
|
5
|
+
export { Accordion, type AccordionItemData, type AccordionProps } from "./primitives/accordion"
|
|
6
|
+
export { Announcement, type AnnouncementProps } from "./primitives/announcement"
|
|
7
|
+
export { Avatar, AvatarGroup, avatarVariants, type AvatarProps, type AvatarGroupProps, type AvatarStatusType } from "./primitives/avatar"
|
|
8
|
+
export { Badge, badgeVariants, type BadgeProps } from "./primitives/badge"
|
|
9
|
+
export { Breadcrumb, type BreadcrumbItem, type BreadcrumbProps } from "./primitives/breadcrumb"
|
|
10
|
+
export { Button, buttonVariants, type ButtonProps } from "./primitives/button"
|
|
11
|
+
export { ButtonGroup, ToggleGroup, type ButtonGroupProps, type ToggleGroupItem, type ToggleGroupProps } from "./primitives/button-group"
|
|
12
|
+
export { Carousel, type CarouselSlide, type CarouselProps } from "./primitives/carousel"
|
|
13
|
+
export { Checkbox } from "./primitives/checkbox"
|
|
14
|
+
export { Chip, type ChipProps } from "./primitives/chip"
|
|
15
|
+
export { CloseButton, type CloseButtonProps } from "./primitives/close-button"
|
|
16
|
+
export { ColorPicker, type ColorPickerProps } from "./primitives/color-picker"
|
|
17
|
+
export { Combobox, type ComboboxOption, type ComboboxProps } from "./primitives/combobox"
|
|
18
|
+
export { CommandPalette, type CommandItem, type CommandPaletteProps } from "./primitives/command-palette"
|
|
19
|
+
export { ContextMenuBox, type ContextMenuAction, type ContextMenuBoxProps } from "./primitives/context-menu"
|
|
20
|
+
export { DateCalendar, type DateCalendarProps } from "./primitives/date-calendar"
|
|
21
|
+
export { DateRangePicker, type DateRangePreset, type DateRangePickerProps } from "./primitives/date-picker"
|
|
22
|
+
export {
|
|
23
|
+
Dialog,
|
|
24
|
+
DialogPortal,
|
|
25
|
+
DialogOverlay,
|
|
26
|
+
DialogClose,
|
|
27
|
+
DialogTrigger,
|
|
28
|
+
DialogContent,
|
|
29
|
+
DialogHeader,
|
|
30
|
+
DialogFooter,
|
|
31
|
+
DialogTitle,
|
|
32
|
+
DialogDescription,
|
|
33
|
+
} from "./primitives/dialog"
|
|
34
|
+
export { Divider, type DividerProps } from "./primitives/divider"
|
|
35
|
+
export {
|
|
36
|
+
Drawer,
|
|
37
|
+
DrawerTrigger,
|
|
38
|
+
DrawerClose,
|
|
39
|
+
DrawerContent,
|
|
40
|
+
DrawerHeader,
|
|
41
|
+
DrawerTitle,
|
|
42
|
+
DrawerDescription,
|
|
43
|
+
DrawerFooter,
|
|
44
|
+
type DrawerContentProps,
|
|
45
|
+
} from "./primitives/drawer"
|
|
46
|
+
export {
|
|
47
|
+
DropdownMenu,
|
|
48
|
+
DropdownMenuTrigger,
|
|
49
|
+
DropdownMenuGroup,
|
|
50
|
+
DropdownMenuContent,
|
|
51
|
+
DropdownMenuItem,
|
|
52
|
+
DropdownMenuSeparator,
|
|
53
|
+
DropdownMenuLabel,
|
|
54
|
+
} from "./primitives/dropdown-menu"
|
|
55
|
+
export { FileDropzone, type UploadedFileItem, type FileDropzoneProps } from "./primitives/file-upload"
|
|
56
|
+
export { IconButton, type IconButtonProps } from "./primitives/icon-button"
|
|
57
|
+
export { Input, Label, type InputProps, type LabelProps } from "./primitives/input"
|
|
58
|
+
export {
|
|
59
|
+
Modal,
|
|
60
|
+
ModalPortal,
|
|
61
|
+
ModalOverlay,
|
|
62
|
+
ModalClose,
|
|
63
|
+
ModalTrigger,
|
|
64
|
+
ModalContent,
|
|
65
|
+
ModalHeader,
|
|
66
|
+
ModalFooter,
|
|
67
|
+
ModalTitle,
|
|
68
|
+
ModalDescription,
|
|
69
|
+
} from "./primitives/modal"
|
|
70
|
+
export { Pagination, type PaginationProps } from "./primitives/pagination"
|
|
71
|
+
export { Popover, PopoverTrigger, PopoverContent } from "./primitives/popover"
|
|
72
|
+
export { Progress, CircularProgress, type ProgressProps, type CircularProgressProps } from "./primitives/progress"
|
|
73
|
+
export { RadioGroup, type RadioGroupOption, type RadioGroupProps } from "./primitives/radio-group"
|
|
74
|
+
export {
|
|
75
|
+
Select,
|
|
76
|
+
SelectGroup,
|
|
77
|
+
SelectValue,
|
|
78
|
+
SelectTrigger,
|
|
79
|
+
SelectContent,
|
|
80
|
+
SelectItem,
|
|
81
|
+
} from "./primitives/select"
|
|
82
|
+
export { Skeleton, CardSkeleton, TableRowSkeleton } from "./primitives/skeleton"
|
|
83
|
+
export { Slider, type SliderProps } from "./primitives/slider"
|
|
84
|
+
export { Switch } from "./primitives/switch"
|
|
85
|
+
export { Table, TableHeader, TableBody } from "./primitives/table"
|
|
86
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, type TabsListProps, type TabsTriggerProps } from "./primitives/tabs"
|
|
87
|
+
export { Textarea, type TextareaProps } from "./primitives/textarea"
|
|
88
|
+
export { ToastCard, type ToastVariant, type ToastItem, type ToastCardProps } from "./primitives/toast"
|
|
89
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "./primitives/tooltip"
|
|
90
|
+
export {
|
|
91
|
+
AreaStatisticChart,
|
|
92
|
+
BarStatisticChart,
|
|
93
|
+
LineStatisticChart,
|
|
94
|
+
DonutStatisticChart,
|
|
95
|
+
RadialStatisticChart,
|
|
96
|
+
HeatmapStatisticChart,
|
|
97
|
+
type ChartDataPoint,
|
|
98
|
+
type AreaStatisticChartProps,
|
|
99
|
+
type BarStatisticChartProps,
|
|
100
|
+
type LineStatisticChartProps,
|
|
101
|
+
type DonutSegment,
|
|
102
|
+
} from "./primitives/chart"
|
|
103
|
+
|
|
104
|
+
// Enterprise Blocks
|
|
105
|
+
export * from "./blocks/app-shell"
|
|
106
|
+
export * from "./blocks/stat-cards"
|
|
107
|
+
export * from "./blocks/filter-bar"
|
|
108
|
+
export * from "./blocks/data-table"
|
|
109
|
+
export * from "./blocks/settings-blocks"
|
|
110
|
+
|
|
111
|
+
// Full Page Dashboard Templates
|
|
112
|
+
export * from "./templates/executive-analytics"
|
|
113
|
+
export * from "./templates/control-tower"
|
|
114
|
+
export * from "./templates/workspace-settings"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { ChevronDown } from "lucide-react"
|
|
3
|
+
import { cn } from "../utils/cn"
|
|
4
|
+
|
|
5
|
+
export interface AccordionItemData {
|
|
6
|
+
id: string
|
|
7
|
+
title: string
|
|
8
|
+
content: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface AccordionProps {
|
|
12
|
+
items: AccordionItemData[]
|
|
13
|
+
defaultOpenId?: string
|
|
14
|
+
className?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const Accordion: React.FC<AccordionProps> = ({
|
|
18
|
+
items,
|
|
19
|
+
defaultOpenId,
|
|
20
|
+
className,
|
|
21
|
+
}) => {
|
|
22
|
+
const [openId, setOpenId] = React.useState<string | null>(
|
|
23
|
+
defaultOpenId ?? items[0]?.id ?? null
|
|
24
|
+
)
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
className={cn(
|
|
28
|
+
"w-full divide-y divide-zinc-200 rounded-xl border border-zinc-200 bg-white dark:divide-zinc-800 dark:border-zinc-800 dark:bg-zinc-900",
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
32
|
+
{items.map((item) => {
|
|
33
|
+
const isOpen = openId === item.id
|
|
34
|
+
return (
|
|
35
|
+
<div key={item.id}>
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={() => setOpenId(isOpen ? null : item.id)}
|
|
39
|
+
className="flex w-full items-center justify-between px-4 py-3.5 text-left text-xs font-medium text-zinc-900 hover:bg-zinc-50 dark:text-zinc-100 dark:hover:bg-zinc-800/50 cursor-pointer"
|
|
40
|
+
>
|
|
41
|
+
<span>{item.title}</span>
|
|
42
|
+
<ChevronDown
|
|
43
|
+
className={cn(
|
|
44
|
+
"h-4 w-4 text-zinc-400 transition-transform",
|
|
45
|
+
isOpen && "rotate-180"
|
|
46
|
+
)}
|
|
47
|
+
/>
|
|
48
|
+
</button>
|
|
49
|
+
{isOpen && (
|
|
50
|
+
<div className="px-4 pb-3.5 text-xs text-zinc-500 dark:text-zinc-400 leading-relaxed">
|
|
51
|
+
{item.content}
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
})}
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
Accordion.displayName = "Accordion"
|
|
61
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { ArrowRight } from "lucide-react"
|
|
3
|
+
import { cn } from "../utils/cn"
|
|
4
|
+
|
|
5
|
+
export interface AnnouncementProps {
|
|
6
|
+
tag?: string
|
|
7
|
+
title: string
|
|
8
|
+
href?: string
|
|
9
|
+
className?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const Announcement: React.FC<AnnouncementProps> = ({
|
|
13
|
+
tag = "New",
|
|
14
|
+
title,
|
|
15
|
+
href = "#",
|
|
16
|
+
className,
|
|
17
|
+
}) => (
|
|
18
|
+
<a
|
|
19
|
+
href={href}
|
|
20
|
+
className={cn(
|
|
21
|
+
"inline-flex items-center gap-2 rounded-full border border-zinc-200 bg-white px-3 py-1 text-xs text-zinc-700 shadow-2xs transition-colors hover:bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-200 dark:hover:bg-zinc-800",
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
>
|
|
25
|
+
<span className="rounded-full bg-blue-100 px-2 py-0.5 text-[10px] font-semibold text-blue-700 dark:bg-blue-500/20 dark:text-blue-300">
|
|
26
|
+
{tag}
|
|
27
|
+
</span>
|
|
28
|
+
<span className="font-medium">{title}</span>
|
|
29
|
+
<ArrowRight className="h-3 w-3 text-zinc-400" />
|
|
30
|
+
</a>
|
|
31
|
+
)
|
|
32
|
+
Announcement.displayName = "Announcement"
|
|
33
|
+
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "../utils/cn"
|
|
4
|
+
|
|
5
|
+
const avatarVariants = cva(
|
|
6
|
+
"relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full font-medium select-none transition-colors",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
size: {
|
|
10
|
+
xs: "h-5 w-5 text-[10px]",
|
|
11
|
+
sm: "h-6 w-6 text-[11px]",
|
|
12
|
+
default: "h-8 w-8 text-xs",
|
|
13
|
+
lg: "h-9 w-9 text-sm",
|
|
14
|
+
xl: "h-11 w-11 text-sm",
|
|
15
|
+
},
|
|
16
|
+
tint: {
|
|
17
|
+
neutral:
|
|
18
|
+
"bg-zinc-200/80 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-200",
|
|
19
|
+
blue:
|
|
20
|
+
"bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-300",
|
|
21
|
+
emerald:
|
|
22
|
+
"bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300",
|
|
23
|
+
amber:
|
|
24
|
+
"bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300",
|
|
25
|
+
},
|
|
26
|
+
ring: {
|
|
27
|
+
none: "",
|
|
28
|
+
subtle: "ring-1 ring-zinc-200 dark:ring-zinc-800",
|
|
29
|
+
glow: "ring-2 ring-blue-500/40",
|
|
30
|
+
emerald: "ring-2 ring-emerald-500/40",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
size: "default",
|
|
35
|
+
tint: "neutral",
|
|
36
|
+
ring: "none",
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
export type AvatarStatusType = "online" | "away" | "busy" | "offline"
|
|
42
|
+
|
|
43
|
+
const statusColorMap: Record<AvatarStatusType, string> = {
|
|
44
|
+
online: "bg-emerald-500",
|
|
45
|
+
away: "bg-amber-500",
|
|
46
|
+
busy: "bg-rose-500",
|
|
47
|
+
offline: "bg-zinc-400 dark:bg-zinc-600",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface AvatarProps
|
|
51
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
52
|
+
VariantProps<typeof avatarVariants> {
|
|
53
|
+
src?: string
|
|
54
|
+
alt?: string
|
|
55
|
+
fallback?: string
|
|
56
|
+
status?: AvatarStatusType
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(
|
|
60
|
+
({ className, size, tint, ring, src, alt, fallback, status, children, ...props }, ref) => {
|
|
61
|
+
const [imgError, setImgError] = React.useState(false)
|
|
62
|
+
|
|
63
|
+
const initials = React.useMemo(() => {
|
|
64
|
+
if (fallback) return fallback.slice(0, 2).toUpperCase()
|
|
65
|
+
if (alt) {
|
|
66
|
+
return alt
|
|
67
|
+
.split(" ")
|
|
68
|
+
.map((n) => n[0])
|
|
69
|
+
.join("")
|
|
70
|
+
.slice(0, 2)
|
|
71
|
+
.toUpperCase()
|
|
72
|
+
}
|
|
73
|
+
return "B"
|
|
74
|
+
}, [fallback, alt])
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div className="relative inline-flex shrink-0" ref={ref} {...props}>
|
|
78
|
+
<div className={cn(avatarVariants({ size, tint, ring, className }))}>
|
|
79
|
+
{src && !imgError ? (
|
|
80
|
+
<img
|
|
81
|
+
src={src}
|
|
82
|
+
alt={alt || "Avatar"}
|
|
83
|
+
onError={() => setImgError(true)}
|
|
84
|
+
className="h-full w-full object-cover"
|
|
85
|
+
/>
|
|
86
|
+
) : (
|
|
87
|
+
children || <span className="leading-none">{initials}</span>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
{status && (
|
|
91
|
+
<span
|
|
92
|
+
aria-label={`Status: ${status}`}
|
|
93
|
+
className={cn(
|
|
94
|
+
"absolute bottom-0 right-0 block rounded-full ring-2 ring-white dark:ring-zinc-950",
|
|
95
|
+
size === "xs" || size === "sm" ? "h-1.5 w-1.5" : "h-2.5 w-2.5",
|
|
96
|
+
statusColorMap[status]
|
|
97
|
+
)}
|
|
98
|
+
/>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
Avatar.displayName = "Avatar"
|
|
105
|
+
|
|
106
|
+
export interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
107
|
+
items: Array<{
|
|
108
|
+
src?: string
|
|
109
|
+
alt: string
|
|
110
|
+
fallback?: string
|
|
111
|
+
tint?: "neutral" | "blue" | "emerald" | "amber"
|
|
112
|
+
status?: AvatarStatusType
|
|
113
|
+
}>
|
|
114
|
+
max?: number
|
|
115
|
+
size?: "xs" | "sm" | "default" | "lg"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
|
|
119
|
+
({ className, items, max = 4, size = "default", ...props }, ref) => {
|
|
120
|
+
const visible = items.slice(0, max)
|
|
121
|
+
const remaining = items.length - visible.length
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
ref={ref}
|
|
126
|
+
className={cn("flex items-center -space-x-2", className)}
|
|
127
|
+
{...props}
|
|
128
|
+
>
|
|
129
|
+
{visible.map((item, idx) => (
|
|
130
|
+
<Avatar
|
|
131
|
+
key={`${item.alt}-${idx}`}
|
|
132
|
+
src={item.src}
|
|
133
|
+
alt={item.alt}
|
|
134
|
+
fallback={item.fallback}
|
|
135
|
+
tint={item.tint || (idx % 2 === 1 ? "blue" : "neutral")}
|
|
136
|
+
size={size}
|
|
137
|
+
className="ring-2 ring-white dark:ring-zinc-950"
|
|
138
|
+
/>
|
|
139
|
+
))}
|
|
140
|
+
{remaining > 0 && (
|
|
141
|
+
<div
|
|
142
|
+
className={cn(
|
|
143
|
+
avatarVariants({ size, tint: "neutral" }),
|
|
144
|
+
"ring-2 ring-white dark:ring-zinc-950 text-[11px] font-medium z-0"
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
147
|
+
+{remaining}
|
|
148
|
+
</div>
|
|
149
|
+
)}
|
|
150
|
+
</div>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
AvatarGroup.displayName = "AvatarGroup"
|
|
155
|
+
|
|
156
|
+
export { Avatar, AvatarGroup, avatarVariants }
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { X } from "lucide-react"
|
|
4
|
+
import { cn } from "../utils/cn"
|
|
5
|
+
|
|
6
|
+
const badgeVariants = cva(
|
|
7
|
+
"inline-flex items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium transition-colors select-none",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default:
|
|
12
|
+
"bg-zinc-100 text-zinc-700 border border-zinc-200/80 dark:bg-zinc-800 dark:text-zinc-200 dark:border-zinc-700/80",
|
|
13
|
+
blue:
|
|
14
|
+
"bg-blue-50 text-blue-700 border border-blue-200/80 dark:bg-blue-500/15 dark:text-blue-300 dark:border-blue-500/30",
|
|
15
|
+
glow:
|
|
16
|
+
"bg-blue-50 text-blue-700 border border-blue-200/80 dark:bg-blue-500/15 dark:text-blue-300 dark:border-blue-500/30",
|
|
17
|
+
emerald:
|
|
18
|
+
"bg-emerald-50 text-emerald-700 border border-emerald-200/80 dark:bg-emerald-500/15 dark:text-emerald-300 dark:border-emerald-500/30",
|
|
19
|
+
amber:
|
|
20
|
+
"bg-amber-50 text-amber-700 border border-amber-200/80 dark:bg-amber-500/15 dark:text-amber-300 dark:border-amber-500/30",
|
|
21
|
+
rose:
|
|
22
|
+
"bg-rose-50 text-rose-700 border border-rose-200/80 dark:bg-rose-500/15 dark:text-rose-300 dark:border-rose-500/30",
|
|
23
|
+
outline:
|
|
24
|
+
"bg-transparent text-zinc-600 border border-zinc-200 dark:text-zinc-400 dark:border-zinc-800",
|
|
25
|
+
mono:
|
|
26
|
+
"bg-zinc-100 text-zinc-700 border border-zinc-200 dark:bg-zinc-900 dark:text-zinc-300 dark:border-zinc-800 font-mono text-[11px]",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
variant: "default",
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const dotColorMap: Record<string, string> = {
|
|
36
|
+
default: "bg-zinc-400",
|
|
37
|
+
blue: "bg-blue-500",
|
|
38
|
+
glow: "bg-blue-500",
|
|
39
|
+
emerald: "bg-emerald-500",
|
|
40
|
+
amber: "bg-amber-500",
|
|
41
|
+
rose: "bg-rose-500",
|
|
42
|
+
outline: "bg-zinc-400",
|
|
43
|
+
mono: "bg-blue-500",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface BadgeProps
|
|
47
|
+
extends React.HTMLAttributes<HTMLSpanElement>,
|
|
48
|
+
VariantProps<typeof badgeVariants> {
|
|
49
|
+
dot?: boolean
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
|
|
53
|
+
({ className, variant = "default", dot = false, children, ...props }, ref) => {
|
|
54
|
+
return (
|
|
55
|
+
<span
|
|
56
|
+
ref={ref}
|
|
57
|
+
className={cn(badgeVariants({ variant, className }))}
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
{dot && (
|
|
61
|
+
<span
|
|
62
|
+
className={cn(
|
|
63
|
+
"h-1.5 w-1.5 rounded-full shrink-0",
|
|
64
|
+
dotColorMap[variant || "default"]
|
|
65
|
+
)}
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
68
|
+
{children}
|
|
69
|
+
</span>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
Badge.displayName = "Badge"
|
|
74
|
+
|
|
75
|
+
export interface ChipProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
76
|
+
onRemove?: () => void
|
|
77
|
+
active?: boolean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const Chip = React.forwardRef<HTMLSpanElement, ChipProps>(
|
|
81
|
+
({ className, children, onRemove, active = false, ...props }, ref) => (
|
|
82
|
+
<span
|
|
83
|
+
ref={ref}
|
|
84
|
+
className={cn(
|
|
85
|
+
"inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium transition-colors",
|
|
86
|
+
active
|
|
87
|
+
? "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-500/30 dark:bg-blue-500/15 dark:text-blue-300"
|
|
88
|
+
: "border-zinc-200 bg-white text-zinc-700 hover:bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-300 dark:hover:bg-zinc-800",
|
|
89
|
+
className
|
|
90
|
+
)}
|
|
91
|
+
{...props}
|
|
92
|
+
>
|
|
93
|
+
{children}
|
|
94
|
+
{onRemove && (
|
|
95
|
+
<button
|
|
96
|
+
type="button"
|
|
97
|
+
onClick={onRemove}
|
|
98
|
+
className="rounded-full p-0.5 text-zinc-400 hover:bg-zinc-200/60 hover:text-zinc-700 dark:hover:bg-zinc-700 dark:hover:text-zinc-200 cursor-pointer"
|
|
99
|
+
>
|
|
100
|
+
<X className="h-3 w-3" />
|
|
101
|
+
</button>
|
|
102
|
+
)}
|
|
103
|
+
</span>
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
Chip.displayName = "Chip"
|
|
107
|
+
|
|
108
|
+
export { Badge, Chip, badgeVariants }
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { ChevronRight } from "lucide-react"
|
|
3
|
+
import { cn } from "../utils/cn"
|
|
4
|
+
|
|
5
|
+
export interface BreadcrumbItem {
|
|
6
|
+
label: string
|
|
7
|
+
href?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface BreadcrumbProps {
|
|
11
|
+
items: BreadcrumbItem[]
|
|
12
|
+
className?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const Breadcrumb: React.FC<BreadcrumbProps> = ({ items, className }) => (
|
|
16
|
+
<nav
|
|
17
|
+
aria-label="Breadcrumb"
|
|
18
|
+
className={cn("flex items-center gap-1.5 text-xs", className)}
|
|
19
|
+
>
|
|
20
|
+
{items.map((item, idx) => {
|
|
21
|
+
const isLast = idx === items.length - 1
|
|
22
|
+
return (
|
|
23
|
+
<React.Fragment key={item.label}>
|
|
24
|
+
{idx > 0 && (
|
|
25
|
+
<ChevronRight className="h-3.5 w-3.5 text-zinc-400 shrink-0" />
|
|
26
|
+
)}
|
|
27
|
+
{item.href && !isLast ? (
|
|
28
|
+
<a
|
|
29
|
+
href={item.href}
|
|
30
|
+
className="text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100 transition-colors"
|
|
31
|
+
>
|
|
32
|
+
{item.label}
|
|
33
|
+
</a>
|
|
34
|
+
) : (
|
|
35
|
+
<span className="font-medium text-zinc-900 dark:text-zinc-100">
|
|
36
|
+
{item.label}
|
|
37
|
+
</span>
|
|
38
|
+
)}
|
|
39
|
+
</React.Fragment>
|
|
40
|
+
)
|
|
41
|
+
})}
|
|
42
|
+
</nav>
|
|
43
|
+
)
|
|
44
|
+
Breadcrumb.displayName = "Breadcrumb"
|
|
45
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../utils/cn"
|
|
3
|
+
|
|
4
|
+
export interface ButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
attached?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
|
|
9
|
+
({ className, attached = true, ...props }, ref) => (
|
|
10
|
+
<div
|
|
11
|
+
ref={ref}
|
|
12
|
+
role="group"
|
|
13
|
+
className={cn(
|
|
14
|
+
"inline-flex items-center",
|
|
15
|
+
attached
|
|
16
|
+
? "rounded-lg border border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900 p-0.5 [&>button]:border-0 [&>button]:rounded-md [&>button]:shadow-none"
|
|
17
|
+
: "gap-2",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
ButtonGroup.displayName = "ButtonGroup"
|
|
25
|
+
|
|
26
|
+
export interface ToggleGroupItem {
|
|
27
|
+
value: string
|
|
28
|
+
label: React.ReactNode
|
|
29
|
+
icon?: React.ReactNode
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ToggleGroupProps {
|
|
33
|
+
items: ToggleGroupItem[]
|
|
34
|
+
value: string
|
|
35
|
+
onValueChange: (value: string) => void
|
|
36
|
+
size?: "sm" | "default"
|
|
37
|
+
className?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const ToggleGroup: React.FC<ToggleGroupProps> = ({
|
|
41
|
+
items,
|
|
42
|
+
value,
|
|
43
|
+
onValueChange,
|
|
44
|
+
size = "default",
|
|
45
|
+
className,
|
|
46
|
+
}) => {
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
role="group"
|
|
50
|
+
className={cn(
|
|
51
|
+
"inline-flex items-center rounded-lg border border-zinc-200 bg-zinc-100/80 dark:border-zinc-800 dark:bg-zinc-900 p-0.5 gap-0.5",
|
|
52
|
+
className
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
55
|
+
{items.map((item) => {
|
|
56
|
+
const active = item.value === value
|
|
57
|
+
return (
|
|
58
|
+
<button
|
|
59
|
+
key={item.value}
|
|
60
|
+
type="button"
|
|
61
|
+
onClick={() => onValueChange(item.value)}
|
|
62
|
+
className={cn(
|
|
63
|
+
"inline-flex items-center gap-1.5 rounded-md font-medium transition-all cursor-pointer",
|
|
64
|
+
size === "sm" ? "px-2.5 py-1 text-xs" : "px-3 py-1.5 text-xs",
|
|
65
|
+
active
|
|
66
|
+
? "bg-white text-zinc-900 shadow-2xs dark:bg-zinc-800 dark:text-zinc-100"
|
|
67
|
+
: "text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200"
|
|
68
|
+
)}
|
|
69
|
+
>
|
|
70
|
+
{item.icon}
|
|
71
|
+
{item.label}
|
|
72
|
+
</button>
|
|
73
|
+
)
|
|
74
|
+
})}
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
ToggleGroup.displayName = "ToggleGroup"
|
|
79
|
+
|