@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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -2
  3. package/dist/index.d.mts +583 -0
  4. package/dist/index.d.ts +583 -0
  5. package/dist/index.js +5189 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +5189 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +83 -3
  10. package/src/blocks/app-shell.tsx +441 -0
  11. package/src/blocks/billing-card.tsx +2 -0
  12. package/src/blocks/data-table.tsx +479 -0
  13. package/src/blocks/filter-bar.tsx +134 -0
  14. package/src/blocks/header-navbar.tsx +7 -0
  15. package/src/blocks/metric-grid.tsx +5 -0
  16. package/src/blocks/profile-form.tsx +2 -0
  17. package/src/blocks/security-settings.tsx +2 -0
  18. package/src/blocks/settings-blocks.tsx +406 -0
  19. package/src/blocks/sidebar.tsx +6 -0
  20. package/src/blocks/stat-card.tsx +7 -0
  21. package/src/blocks/stat-cards.tsx +212 -0
  22. package/src/blocks/team-members.tsx +5 -0
  23. package/src/index.ts +114 -0
  24. package/src/primitives/accordion.tsx +61 -0
  25. package/src/primitives/announcement.tsx +33 -0
  26. package/src/primitives/avatar.tsx +156 -0
  27. package/src/primitives/badge.tsx +108 -0
  28. package/src/primitives/breadcrumb.tsx +45 -0
  29. package/src/primitives/button-group.tsx +79 -0
  30. package/src/primitives/button.tsx +165 -0
  31. package/src/primitives/carousel.tsx +84 -0
  32. package/src/primitives/chart.tsx +831 -0
  33. package/src/primitives/checkbox.tsx +26 -0
  34. package/src/primitives/chip.tsx +37 -0
  35. package/src/primitives/close-button.tsx +29 -0
  36. package/src/primitives/color-picker.tsx +62 -0
  37. package/src/primitives/combobox.tsx +123 -0
  38. package/src/primitives/command-palette.tsx +216 -0
  39. package/src/primitives/context-menu.tsx +84 -0
  40. package/src/primitives/date-calendar.tsx +76 -0
  41. package/src/primitives/date-picker.tsx +400 -0
  42. package/src/primitives/dialog.tsx +279 -0
  43. package/src/primitives/divider.tsx +31 -0
  44. package/src/primitives/drawer.tsx +95 -0
  45. package/src/primitives/dropdown-menu.tsx +164 -0
  46. package/src/primitives/file-upload.tsx +123 -0
  47. package/src/primitives/icon-button.tsx +23 -0
  48. package/src/primitives/input.tsx +124 -0
  49. package/src/primitives/modal.tsx +26 -0
  50. package/src/primitives/pagination.tsx +58 -0
  51. package/src/primitives/popover.tsx +28 -0
  52. package/src/primitives/progress.tsx +145 -0
  53. package/src/primitives/radio-group.tsx +63 -0
  54. package/src/primitives/select.tsx +207 -0
  55. package/src/primitives/skeleton.tsx +61 -0
  56. package/src/primitives/slider.tsx +44 -0
  57. package/src/primitives/switch.tsx +146 -0
  58. package/src/primitives/table.tsx +47 -0
  59. package/src/primitives/tabs.tsx +86 -0
  60. package/src/primitives/textarea.tsx +40 -0
  61. package/src/primitives/toast.tsx +71 -0
  62. package/src/primitives/tooltip.tsx +58 -0
  63. package/src/styles/globals.css +49 -0
  64. package/src/templates/control-tower.tsx +41 -0
  65. package/src/templates/executive-analytics.tsx +272 -0
  66. package/src/templates/workspace-settings.tsx +179 -0
  67. 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
+