@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.
Files changed (107) hide show
  1. package/dist/components/ai-chat-shell.d.ts +1 -0
  2. package/dist/components/ai-chat-shell.js +23 -0
  3. package/dist/components/prompt-input.d.ts +5 -0
  4. package/dist/components/prompt-input.js +47 -0
  5. package/dist/components/registry-card.d.ts +6 -0
  6. package/dist/components/registry-card.js +15 -0
  7. package/dist/components/registry-explorer.d.ts +8 -0
  8. package/dist/components/registry-explorer.js +38 -0
  9. package/dist/components/token-stream.d.ts +7 -0
  10. package/dist/components/token-stream.js +21 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +23 -0
  13. package/dist/manifests.d.ts +3 -0
  14. package/dist/manifests.js +1666 -0
  15. package/dist/types.d.ts +44 -0
  16. package/dist/types.js +2 -0
  17. package/package.json +28 -0
  18. package/src/components/blocks/ai-chat-shell.tsx +97 -0
  19. package/src/components/blocks/auth-panel.tsx +203 -0
  20. package/src/components/blocks/feature-grid.tsx +122 -0
  21. package/src/components/blocks/hero-section.tsx +73 -0
  22. package/src/components/blocks/notification-center.tsx +185 -0
  23. package/src/components/blocks/onboarding-flow.tsx +230 -0
  24. package/src/components/blocks/pricing-section.tsx +135 -0
  25. package/src/components/blocks/project-command-center.tsx +188 -0
  26. package/src/components/blocks/prompt-input.tsx +81 -0
  27. package/src/components/blocks/registry-card.tsx +104 -0
  28. package/src/components/blocks/registry-explorer.tsx +78 -0
  29. package/src/components/blocks/settings-layout.tsx +178 -0
  30. package/src/components/blocks/stats-strip.tsx +100 -0
  31. package/src/components/blocks/token-stream.tsx +42 -0
  32. package/src/components/blocks/usage-card.tsx +116 -0
  33. package/src/components/core/accordion.tsx +58 -0
  34. package/src/components/core/alert-dialog.tsx +113 -0
  35. package/src/components/core/alert.tsx +48 -0
  36. package/src/components/core/animated-number.tsx +77 -0
  37. package/src/components/core/aspect-ratio.tsx +20 -0
  38. package/src/components/core/avatar-stack.tsx +61 -0
  39. package/src/components/core/avatar.tsx +90 -0
  40. package/src/components/core/badge.tsx +39 -0
  41. package/src/components/core/breadcrumb.tsx +63 -0
  42. package/src/components/core/button-group.tsx +37 -0
  43. package/src/components/core/button.tsx +110 -0
  44. package/src/components/core/calendar.tsx +143 -0
  45. package/src/components/core/card.tsx +60 -0
  46. package/src/components/core/carousel.tsx +170 -0
  47. package/src/components/core/chart.tsx +377 -0
  48. package/src/components/core/checkbox.tsx +64 -0
  49. package/src/components/core/collapsible.tsx +30 -0
  50. package/src/components/core/combobox.tsx +114 -0
  51. package/src/components/core/command-box.tsx +22 -0
  52. package/src/components/core/command.tsx +165 -0
  53. package/src/components/core/confirm-action.tsx +94 -0
  54. package/src/components/core/context-menu.tsx +139 -0
  55. package/src/components/core/copy-button.tsx +41 -0
  56. package/src/components/core/data-table.tsx +173 -0
  57. package/src/components/core/date-picker.tsx +73 -0
  58. package/src/components/core/dialog.tsx +83 -0
  59. package/src/components/core/drawer.tsx +87 -0
  60. package/src/components/core/dropdown-menu.tsx +147 -0
  61. package/src/components/core/empty.tsx +34 -0
  62. package/src/components/core/field.tsx +39 -0
  63. package/src/components/core/file-upload.tsx +143 -0
  64. package/src/components/core/hover-card.tsx +31 -0
  65. package/src/components/core/inline-edit.tsx +104 -0
  66. package/src/components/core/input-group.tsx +47 -0
  67. package/src/components/core/input-otp.tsx +108 -0
  68. package/src/components/core/input.tsx +37 -0
  69. package/src/components/core/kbd.tsx +47 -0
  70. package/src/components/core/label.tsx +28 -0
  71. package/src/components/core/marquee.tsx +61 -0
  72. package/src/components/core/menubar.tsx +120 -0
  73. package/src/components/core/multi-select.tsx +145 -0
  74. package/src/components/core/native-select.tsx +27 -0
  75. package/src/components/core/navigation-menu.tsx +130 -0
  76. package/src/components/core/number-stepper.tsx +80 -0
  77. package/src/components/core/pagination.tsx +80 -0
  78. package/src/components/core/password-input.tsx +90 -0
  79. package/src/components/core/popover.tsx +34 -0
  80. package/src/components/core/progress.tsx +63 -0
  81. package/src/components/core/radio-group.tsx +77 -0
  82. package/src/components/core/resizable.tsx +250 -0
  83. package/src/components/core/scroll-area.tsx +38 -0
  84. package/src/components/core/select.tsx +128 -0
  85. package/src/components/core/separator.tsx +47 -0
  86. package/src/components/core/sheet.tsx +118 -0
  87. package/src/components/core/sidebar.tsx +129 -0
  88. package/src/components/core/skeleton.tsx +32 -0
  89. package/src/components/core/slider.tsx +97 -0
  90. package/src/components/core/sonner.tsx +29 -0
  91. package/src/components/core/spinner.tsx +60 -0
  92. package/src/components/core/status-pulse.tsx +67 -0
  93. package/src/components/core/stepper.tsx +111 -0
  94. package/src/components/core/switch.tsx +72 -0
  95. package/src/components/core/table.tsx +104 -0
  96. package/src/components/core/tabs.tsx +55 -0
  97. package/src/components/core/tag-input.tsx +93 -0
  98. package/src/components/core/textarea.tsx +44 -0
  99. package/src/components/core/timeline.tsx +81 -0
  100. package/src/components/core/toggle-group.tsx +56 -0
  101. package/src/components/core/toggle.tsx +66 -0
  102. package/src/components/core/tooltip.tsx +31 -0
  103. package/src/components/core/typing-indicator.tsx +51 -0
  104. package/src/index.ts +8 -0
  105. package/src/manifests.ts +1682 -0
  106. package/src/types.ts +58 -0
  107. package/src/ui.ts +13 -0
@@ -0,0 +1,90 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+ import { cx } from "@/lib/cx"
7
+
8
+ const avatarStyles = cva(
9
+ "relative flex shrink-0 overflow-hidden rounded-full",
10
+ {
11
+ variants: {
12
+ size: {
13
+ xs: "h-6 w-6 text-[10px]",
14
+ sm: "h-8 w-8 text-xs",
15
+ md: "h-10 w-10 text-sm",
16
+ lg: "h-12 w-12 text-base",
17
+ xl: "h-16 w-16 text-lg",
18
+ },
19
+ },
20
+ defaultVariants: { size: "md" },
21
+ }
22
+ )
23
+
24
+ export interface AvatarProps
25
+ extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>,
26
+ VariantProps<typeof avatarStyles> {}
27
+
28
+ const Avatar = React.forwardRef<
29
+ React.ElementRef<typeof AvatarPrimitive.Root>,
30
+ AvatarProps
31
+ >(({ className, size, ...props }, ref) => (
32
+ <AvatarPrimitive.Root
33
+ ref={ref}
34
+ className={cx(avatarStyles({ size }), className)}
35
+ {...props}
36
+ />
37
+ ))
38
+ Avatar.displayName = AvatarPrimitive.Root.displayName
39
+
40
+ const AvatarImage = React.forwardRef<
41
+ React.ElementRef<typeof AvatarPrimitive.Image>,
42
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
43
+ >(({ className, ...props }, ref) => (
44
+ <AvatarPrimitive.Image
45
+ ref={ref}
46
+ className={cx("aspect-square h-full w-full object-cover", className)}
47
+ {...props}
48
+ />
49
+ ))
50
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
51
+
52
+ const AvatarFallback = React.forwardRef<
53
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
54
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
55
+ >(({ className, ...props }, ref) => (
56
+ <AvatarPrimitive.Fallback
57
+ ref={ref}
58
+ className={cx(
59
+ "flex h-full w-full items-center justify-center rounded-full",
60
+ "bg-surface-3 text-muted-foreground font-medium",
61
+ className,
62
+ )}
63
+ {...props}
64
+ />
65
+ ))
66
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
67
+
68
+ interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
69
+ max?: number
70
+ children: React.ReactNode
71
+ }
72
+
73
+ function AvatarGroup({ children, max, className, ...props }: AvatarGroupProps) {
74
+ const childArray = React.Children.toArray(children)
75
+ const shown = max ? childArray.slice(0, max) : childArray
76
+ const overflow = max ? childArray.length - max : 0
77
+
78
+ return (
79
+ <div className={cx("flex -space-x-2", className)} {...props}>
80
+ {shown}
81
+ {overflow > 0 && (
82
+ <div className="relative flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-surface-3 border-2 border-background text-xs font-medium text-muted-foreground">
83
+ +{overflow}
84
+ </div>
85
+ )}
86
+ </div>
87
+ )
88
+ }
89
+
90
+ export { Avatar, AvatarImage, AvatarFallback, AvatarGroup }
@@ -0,0 +1,39 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cx } from "@/lib/cx"
4
+
5
+ const badgeStyles = cva(
6
+ "inline-flex items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium tracking-wide transition-colors",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-surface-2 text-foreground border border-border",
11
+ secondary: "bg-surface-2 text-muted-foreground border border-border-soft",
12
+ success: "bg-green-950/60 text-green-400 border border-green-900/50",
13
+ warning: "bg-yellow-950/60 text-yellow-400 border border-yellow-900/50",
14
+ danger: "bg-red-950/60 text-red-400 border border-red-900/50",
15
+ info: "bg-blue-950/60 text-blue-400 border border-blue-900/50",
16
+ outline: "bg-transparent text-muted-foreground border border-border",
17
+ neutral: "bg-surface-2 text-muted-foreground border border-border-soft",
18
+ free: "bg-surface-2 text-muted-foreground border border-border-soft",
19
+ pro: "bg-surface-3 text-foreground border border-border-strong",
20
+ new: "bg-surface-raised text-foreground border border-border",
21
+ signal: "bg-surface-3 text-foreground border border-border-strong",
22
+ locked: "bg-surface-3 text-muted-foreground border border-border",
23
+ },
24
+ },
25
+ defaultVariants: {
26
+ variant: "default",
27
+ },
28
+ }
29
+ )
30
+
31
+ export interface BadgeProps
32
+ extends React.HTMLAttributes<HTMLSpanElement>,
33
+ VariantProps<typeof badgeStyles> {}
34
+
35
+ export function Badge({ className, variant, ...props }: BadgeProps) {
36
+ return (
37
+ <span className={cx(badgeStyles({ variant }), className)} {...props} />
38
+ )
39
+ }
@@ -0,0 +1,63 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react"
4
+ import { cx } from "@/lib/cx"
5
+
6
+ export function Breadcrumb({ className, ...props }: React.ComponentPropsWithoutRef<"nav">) {
7
+ return <nav aria-label="breadcrumb" className={className} {...props} />
8
+ }
9
+
10
+ export function BreadcrumbList({ className, ...props }: React.ComponentPropsWithoutRef<"ol">) {
11
+ return (
12
+ <ol className={cx("flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground", className)} {...props} />
13
+ )
14
+ }
15
+
16
+ export function BreadcrumbItem({ className, ...props }: React.ComponentPropsWithoutRef<"li">) {
17
+ return <li className={cx("inline-flex items-center gap-1.5", className)} {...props} />
18
+ }
19
+
20
+ export function BreadcrumbLink({
21
+ className,
22
+ asChild,
23
+ ...props
24
+ }: React.ComponentPropsWithoutRef<"a"> & { asChild?: boolean }) {
25
+ const Comp = asChild ? Slot : "a"
26
+ return (
27
+ <Comp className={cx("transition-colors hover:text-foreground", className)} {...props} />
28
+ )
29
+ }
30
+
31
+ export function BreadcrumbPage({ className, ...props }: React.ComponentPropsWithoutRef<"span">) {
32
+ return (
33
+ <span
34
+ role="link"
35
+ aria-current="page"
36
+ aria-disabled="true"
37
+ className={cx("font-medium text-foreground", className)}
38
+ {...props}
39
+ />
40
+ )
41
+ }
42
+
43
+ export function BreadcrumbSeparator({ children, className }: { children?: React.ReactNode; className?: string }) {
44
+ return (
45
+ <li role="presentation" aria-hidden="true" className={cx("text-muted-foreground", className)}>
46
+ {children ?? <ChevronRight className="h-3.5 w-3.5" />}
47
+ </li>
48
+ )
49
+ }
50
+
51
+ export function BreadcrumbEllipsis({ className, ...props }: React.ComponentPropsWithoutRef<"span">) {
52
+ return (
53
+ <span
54
+ role="presentation"
55
+ aria-hidden="true"
56
+ className={cx("flex h-9 w-9 items-center justify-center", className)}
57
+ {...props}
58
+ >
59
+ <MoreHorizontal className="h-4 w-4" />
60
+ <span className="sr-only">More</span>
61
+ </span>
62
+ )
63
+ }
@@ -0,0 +1,37 @@
1
+ import * as React from "react"
2
+ import { cx } from "@/lib/cx"
3
+
4
+ interface ButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ orientation?: "horizontal" | "vertical"
6
+ }
7
+
8
+ export function ButtonGroup({
9
+ className,
10
+ orientation = "horizontal",
11
+ ...props
12
+ }: ButtonGroupProps) {
13
+ return (
14
+ <div
15
+ role="group"
16
+ data-orientation={orientation}
17
+ className={cx(
18
+ "inline-flex",
19
+ orientation === "horizontal"
20
+ ? [
21
+ "flex-row",
22
+ "[&>*:not(:first-child)]:border-l-0",
23
+ "[&>*:not(:first-child)]:rounded-l-none",
24
+ "[&>*:not(:last-child)]:rounded-r-none",
25
+ ]
26
+ : [
27
+ "flex-col",
28
+ "[&>*:not(:first-child)]:border-t-0",
29
+ "[&>*:not(:first-child)]:rounded-t-none",
30
+ "[&>*:not(:last-child)]:rounded-b-none",
31
+ ],
32
+ className,
33
+ )}
34
+ {...props}
35
+ />
36
+ )
37
+ }
@@ -0,0 +1,110 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cx } from "@/lib/cx"
5
+
6
+ /**
7
+ * Floyka Button Component
8
+ *
9
+ * Design tokens used (defined in globals.css / tailwind.config):
10
+ * --foreground #FAFAFA (primary text)
11
+ * --background #070708 (main bg)
12
+ * --surface-2 #09090B (raised surface)
13
+ * --border #23252A (default border)
14
+ * --border-strong slightly brighter graphite
15
+ * --muted-foreground dimmed text
16
+ *
17
+ * Variants follow Floyka design rules:
18
+ * - primary → white bg / dark text (the only "colored" action)
19
+ * - outline → transparent bg / border / foreground text
20
+ * - ghost → transparent bg / muted text, subtle hover
21
+ * - danger → danger-tinted surface, used for destructive actions
22
+ *
23
+ * All variants:
24
+ * - rounded-lg (12px radius per design rules)
25
+ * - no glow, no thick borders, no scale animations
26
+ * - 150-200ms ease-out transitions
27
+ * - focus-visible ring uses ring-border-strong (NOT blue/neon)
28
+ */
29
+
30
+ const buttonVariants = cva(
31
+ [
32
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap",
33
+ "rounded-lg text-sm font-medium",
34
+ "transition-all duration-[150ms] ease-out",
35
+ "outline-none focus-visible:ring-1 focus-visible:ring-border-strong focus-visible:ring-offset-1 focus-visible:ring-offset-background",
36
+ "disabled:pointer-events-none disabled:opacity-40",
37
+ "[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
38
+ ],
39
+ {
40
+ variants: {
41
+ size: {
42
+ sm: "h-8 px-3 text-xs rounded-md gap-1.5",
43
+ md: "h-9 px-4",
44
+ lg: "h-10 px-5 text-base",
45
+ icon: "h-9 w-9 rounded-lg",
46
+ "icon-sm": "h-8 w-8 rounded-md",
47
+ },
48
+ variant: {
49
+ /** Primary — white bg, dark text. The strongest CTA. */
50
+ primary:
51
+ "bg-foreground text-background hover:bg-foreground/90",
52
+
53
+ /** Outline — bordered, transparent. Secondary actions. */
54
+ outline:
55
+ "border border-border bg-transparent text-foreground hover:bg-surface-2 hover:border-border-strong",
56
+
57
+ /** Ghost — no border, no bg. Nav actions, icon rows. */
58
+ ghost:
59
+ "bg-transparent text-muted-foreground hover:bg-surface-2 hover:text-foreground",
60
+
61
+ /** Danger — destructive action. Subtle tinted surface. */
62
+ danger:
63
+ "bg-transparent border border-border text-foreground hover:bg-red-950/40 hover:border-red-900/50",
64
+
65
+ /** Rounded — pill-shaped outline. Decorative / marketing CTAs. */
66
+ rounded:
67
+ "border border-border bg-transparent text-foreground hover:bg-surface-2 hover:border-border-strong rounded-full",
68
+
69
+ /** Dashed — dashed border. Draft states, placeholder actions. */
70
+ dashed:
71
+ "border border-dashed border-border bg-transparent text-muted-foreground hover:bg-surface-2 hover:text-foreground hover:border-border-strong",
72
+
73
+ /** Dashed Rounded — pill shape with dashed border. */
74
+ "dashed-rounded":
75
+ "border border-dashed border-border bg-transparent text-muted-foreground hover:bg-surface-2 hover:text-foreground hover:border-border-strong rounded-full",
76
+ },
77
+ },
78
+ defaultVariants: {
79
+ variant: "primary",
80
+ size: "md",
81
+ },
82
+ }
83
+ )
84
+
85
+ export interface ButtonProps
86
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
87
+ VariantProps<typeof buttonVariants> {
88
+ /**
89
+ * When true, the button renders as a `Slot` — allowing you to pass any
90
+ * child element (e.g. `<a>` or Next.js `<Link>`) while keeping all
91
+ * button styles applied.
92
+ */
93
+ asChild?: boolean
94
+ }
95
+
96
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
97
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
98
+ const Comp = asChild ? Slot : "button"
99
+ return (
100
+ <Comp
101
+ ref={ref}
102
+ className={cx(buttonVariants({ variant, size, className }))}
103
+ {...props}
104
+ />
105
+ )
106
+ }
107
+ )
108
+ Button.displayName = "Button"
109
+
110
+ export { Button, buttonVariants }
@@ -0,0 +1,143 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ChevronLeft, ChevronRight } from "lucide-react"
5
+ import { cx } from "@/lib/cx"
6
+
7
+ const MONTHS = ["January", "February", "March", "April", "May", "June",
8
+ "July", "August", "September", "October", "November", "December"]
9
+ const DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
10
+
11
+ function getDaysInMonth(year: number, month: number) {
12
+ return new Date(year, month + 1, 0).getDate()
13
+ }
14
+
15
+ function getFirstDayOfMonth(year: number, month: number) {
16
+ return new Date(year, month, 1).getDay()
17
+ }
18
+
19
+ function isSameDay(a: Date, b: Date) {
20
+ return a.getFullYear() === b.getFullYear() &&
21
+ a.getMonth() === b.getMonth() &&
22
+ a.getDate() === b.getDate()
23
+ }
24
+
25
+ function isToday(date: Date) {
26
+ return isSameDay(date, new Date())
27
+ }
28
+
29
+ export interface CalendarProps {
30
+ selected?: Date
31
+ onSelect?: (date: Date) => void
32
+ disabled?: (date: Date) => boolean
33
+ fromDate?: Date
34
+ toDate?: Date
35
+ className?: string
36
+ initialMonth?: Date
37
+ }
38
+
39
+ export function Calendar({
40
+ selected,
41
+ onSelect,
42
+ disabled,
43
+ fromDate,
44
+ toDate,
45
+ className,
46
+ initialMonth,
47
+ }: CalendarProps) {
48
+ const today = new Date()
49
+ const [viewYear, setViewYear] = React.useState(
50
+ initialMonth?.getFullYear() ?? selected?.getFullYear() ?? today.getFullYear()
51
+ )
52
+ const [viewMonth, setViewMonth] = React.useState(
53
+ initialMonth?.getMonth() ?? selected?.getMonth() ?? today.getMonth()
54
+ )
55
+
56
+ function prevMonth() {
57
+ if (viewMonth === 0) { setViewMonth(11); setViewYear(viewYear - 1) }
58
+ else setViewMonth(viewMonth - 1)
59
+ }
60
+
61
+ function nextMonth() {
62
+ if (viewMonth === 11) { setViewMonth(0); setViewYear(viewYear + 1) }
63
+ else setViewMonth(viewMonth + 1)
64
+ }
65
+
66
+ const daysInMonth = getDaysInMonth(viewYear, viewMonth)
67
+ const firstDay = getFirstDayOfMonth(viewYear, viewMonth)
68
+ const cells: (Date | null)[] = [
69
+ ...Array(firstDay).fill(null),
70
+ ...Array.from({ length: daysInMonth }, (_, i) => new Date(viewYear, viewMonth, i + 1)),
71
+ ]
72
+ while (cells.length % 7 !== 0) cells.push(null)
73
+
74
+ function isDisabled(date: Date) {
75
+ if (disabled?.(date)) return true
76
+ if (fromDate && date < fromDate) return true
77
+ if (toDate && date > toDate) return true
78
+ return false
79
+ }
80
+
81
+ return (
82
+ <div className={cx("w-full select-none p-3", className)}>
83
+ {/* Header */}
84
+ <div className="flex items-center justify-between mb-3">
85
+ <button
86
+ type="button"
87
+ onClick={prevMonth}
88
+ className="flex h-7 w-7 items-center justify-center rounded-md border border-border text-muted-foreground hover:bg-surface-2 hover:text-foreground transition-colors"
89
+ >
90
+ <ChevronLeft className="h-4 w-4" />
91
+ </button>
92
+ <span className="text-sm font-medium">
93
+ {MONTHS[viewMonth]} {viewYear}
94
+ </span>
95
+ <button
96
+ type="button"
97
+ onClick={nextMonth}
98
+ className="flex h-7 w-7 items-center justify-center rounded-md border border-border text-muted-foreground hover:bg-surface-2 hover:text-foreground transition-colors"
99
+ >
100
+ <ChevronRight className="h-4 w-4" />
101
+ </button>
102
+ </div>
103
+
104
+ {/* Day headers */}
105
+ <div className="grid grid-cols-7 mb-1">
106
+ {DAYS.map((d) => (
107
+ <div key={d} className="flex h-8 items-center justify-center text-xs font-medium text-muted-foreground">
108
+ {d}
109
+ </div>
110
+ ))}
111
+ </div>
112
+
113
+ {/* Day cells */}
114
+ <div className="grid grid-cols-7 gap-y-0.5">
115
+ {cells.map((date, i) => {
116
+ if (!date) return <div key={`empty-${i}`} />
117
+ const sel = selected && isSameDay(date, selected)
118
+ const tod = isToday(date)
119
+ const dis = isDisabled(date)
120
+ return (
121
+ <button
122
+ key={date.toISOString()}
123
+ type="button"
124
+ disabled={dis}
125
+ onClick={() => !dis && onSelect?.(date)}
126
+ className={cx(
127
+ "flex h-8 w-full items-center justify-center rounded-md text-sm transition-colors",
128
+ "disabled:pointer-events-none disabled:opacity-30",
129
+ sel
130
+ ? "bg-foreground text-background font-medium"
131
+ : tod
132
+ ? "border border-border font-medium text-foreground hover:bg-surface-2"
133
+ : "text-foreground hover:bg-surface-2"
134
+ )}
135
+ >
136
+ {date.getDate()}
137
+ </button>
138
+ )
139
+ })}
140
+ </div>
141
+ </div>
142
+ )
143
+ }
@@ -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 cardStyles = cva(
6
+ "flex flex-col rounded-xl overflow-hidden text-card-foreground",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ surface: "bg-card border border-border",
11
+ elevated: "bg-surface-2 border border-border-strong",
12
+ command: "bg-surface-3 border border-border-strong",
13
+ preview: "bg-surface-2 border border-dashed border-border-soft",
14
+ muted: "bg-muted border border-transparent",
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ variant: "surface",
19
+ },
20
+ }
21
+ )
22
+
23
+ export interface CardProps
24
+ extends React.HTMLAttributes<HTMLDivElement>,
25
+ VariantProps<typeof cardStyles> {}
26
+
27
+ export const Card = React.forwardRef<HTMLDivElement, CardProps>(
28
+ ({ className, variant, ...props }, ref) => (
29
+ <div ref={ref} className={cx(cardStyles({ variant }), className)} {...props} />
30
+ )
31
+ )
32
+ Card.displayName = "Card"
33
+
34
+ export const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
35
+ ({ className, ...props }, ref) => (
36
+ <div ref={ref} className={cx("px-6 py-5 flex flex-col gap-1.5", className)} {...props} />
37
+ )
38
+ )
39
+ CardHeader.displayName = "CardHeader"
40
+
41
+ export const CardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
42
+ ({ className, ...props }, ref) => (
43
+ <h3 ref={ref} className={cx("text-base font-semibold leading-tight text-foreground", className)} {...props} />
44
+ )
45
+ )
46
+ CardTitle.displayName = "CardTitle"
47
+
48
+ export const CardBody = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
49
+ ({ className, ...props }, ref) => (
50
+ <div ref={ref} className={cx("px-6 pb-6 flex-1", className)} {...props} />
51
+ )
52
+ )
53
+ CardBody.displayName = "CardBody"
54
+
55
+ export const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
56
+ ({ className, ...props }, ref) => (
57
+ <div ref={ref} className={cx("px-6 py-4 bg-muted/10 border-t border-border/50 flex items-center", className)} {...props} />
58
+ )
59
+ )
60
+ CardFooter.displayName = "CardFooter"