@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,34 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RadixPopover from "@radix-ui/react-popover"
5
+ import { cx } from "@/lib/cx"
6
+
7
+ export const Popover = RadixPopover.Root
8
+ export const PopoverTrigger = RadixPopover.Trigger
9
+ export const PopoverAnchor = RadixPopover.Anchor
10
+
11
+ export const PopoverContent = React.forwardRef<
12
+ React.ElementRef<typeof RadixPopover.Content>,
13
+ React.ComponentPropsWithoutRef<typeof RadixPopover.Content>
14
+ >(({ className, align = "center", sideOffset = 8, ...props }, ref) => (
15
+ <RadixPopover.Portal>
16
+ <RadixPopover.Content
17
+ ref={ref}
18
+ align={align}
19
+ sideOffset={sideOffset}
20
+ className={cx(
21
+ "z-50 min-w-[200px] max-w-sm rounded-xl border border-border bg-card p-4 shadow-lg outline-none",
22
+ "animate-in fade-in-0 zoom-in-95",
23
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
24
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
25
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
26
+ className
27
+ )}
28
+ {...props}
29
+ />
30
+ </RadixPopover.Portal>
31
+ ))
32
+ PopoverContent.displayName = "PopoverContent"
33
+
34
+ export const PopoverClose = RadixPopover.Close
@@ -0,0 +1,63 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RadixProgress from "@radix-ui/react-progress"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+ import { cx } from "@/lib/cx"
7
+
8
+ const trackStyles = cva(
9
+ "relative overflow-hidden rounded-full bg-surface-3 border border-border/20",
10
+ {
11
+ variants: {
12
+ size: {
13
+ sm: "h-1.5",
14
+ md: "h-2.5",
15
+ lg: "h-4",
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ size: "md",
20
+ },
21
+ }
22
+ )
23
+
24
+ const indicatorStyles = cva(
25
+ "h-full w-full flex-1 rounded-full transition-transform duration-500 ease-out",
26
+ {
27
+ variants: {
28
+ variant: {
29
+ default: "bg-foreground",
30
+ success: "bg-success",
31
+ warning: "bg-warning",
32
+ danger: "bg-danger",
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ variant: "default",
37
+ },
38
+ }
39
+ )
40
+
41
+ export interface ProgressProps
42
+ extends React.ComponentPropsWithoutRef<typeof RadixProgress.Root>,
43
+ VariantProps<typeof trackStyles>,
44
+ VariantProps<typeof indicatorStyles> {}
45
+
46
+ export const Progress = React.forwardRef<
47
+ React.ElementRef<typeof RadixProgress.Root>,
48
+ ProgressProps
49
+ >(({ className, size, variant, value, max = 100, ...props }, ref) => (
50
+ <RadixProgress.Root
51
+ ref={ref}
52
+ value={value}
53
+ max={max}
54
+ className={cx(trackStyles({ size }), className)}
55
+ {...props}
56
+ >
57
+ <RadixProgress.Indicator
58
+ className={cx(indicatorStyles({ variant }))}
59
+ style={{ transform: `translateX(-${100 - ((value ?? 0) / (max ?? 100)) * 100}%)` }}
60
+ />
61
+ </RadixProgress.Root>
62
+ ))
63
+ Progress.displayName = "Progress"
@@ -0,0 +1,77 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+ import { cx } from "@/lib/cx"
7
+
8
+ const radioStyles = cva(
9
+ [
10
+ "peer aspect-square shrink-0 rounded-full border transition-all outline-none",
11
+ "focus-visible:ring-1 focus-visible:ring-border-strong focus-visible:ring-offset-1 focus-visible:ring-offset-background",
12
+ "disabled:cursor-not-allowed disabled:opacity-40",
13
+ "border-border hover:border-border-strong",
14
+ "data-[state=checked]:border-foreground",
15
+ ],
16
+ {
17
+ variants: {
18
+ size: {
19
+ sm: "h-3.5 w-3.5",
20
+ md: "h-4 w-4",
21
+ lg: "h-5 w-5",
22
+ },
23
+ },
24
+ defaultVariants: { size: "md" },
25
+ }
26
+ )
27
+
28
+ const indicatorStyles = cva(
29
+ "flex items-center justify-center",
30
+ {
31
+ variants: {
32
+ size: {
33
+ sm: "[&>span]:h-1.5 [&>span]:w-1.5",
34
+ md: "[&>span]:h-2 [&>span]:w-2",
35
+ lg: "[&>span]:h-2.5 [&>span]:w-2.5",
36
+ },
37
+ },
38
+ defaultVariants: { size: "md" },
39
+ }
40
+ )
41
+
42
+ function RadioGroup({
43
+ className,
44
+ ...props
45
+ }: React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>) {
46
+ return (
47
+ <RadioGroupPrimitive.Root
48
+ className={cx("grid gap-2.5", className)}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+ RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
54
+
55
+ export interface RadioGroupItemProps
56
+ extends React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>,
57
+ VariantProps<typeof radioStyles> {}
58
+
59
+ const RadioGroupItem = React.forwardRef<
60
+ React.ElementRef<typeof RadioGroupPrimitive.Item>,
61
+ RadioGroupItemProps
62
+ >(({ className, size, ...props }, ref) => {
63
+ return (
64
+ <RadioGroupPrimitive.Item
65
+ ref={ref}
66
+ className={cx(radioStyles({ size }), className)}
67
+ {...props}
68
+ >
69
+ <RadioGroupPrimitive.Indicator className={indicatorStyles({ size })}>
70
+ <span className="rounded-full bg-foreground block" />
71
+ </RadioGroupPrimitive.Indicator>
72
+ </RadioGroupPrimitive.Item>
73
+ )
74
+ })
75
+ RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
76
+
77
+ export { RadioGroup, RadioGroupItem }
@@ -0,0 +1,250 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cx } from "@/lib/cx"
5
+
6
+ // ── Context ───────────────────────────────────────────────────────────────────
7
+
8
+ interface PanelGroupContextValue {
9
+ direction: "horizontal" | "vertical"
10
+ sizes: number[]
11
+ setSizes: React.Dispatch<React.SetStateAction<number[]>>
12
+ }
13
+
14
+ const PanelGroupContext = React.createContext<PanelGroupContextValue | null>(null)
15
+
16
+ function usePanelGroup() {
17
+ const ctx = React.useContext(PanelGroupContext)
18
+ if (!ctx) throw new Error("ResizablePanel/ResizableHandle must be inside ResizablePanelGroup")
19
+ return ctx
20
+ }
21
+
22
+ // ── PanelGroup ─────────────────────────────────────────────────────────────────
23
+
24
+ export interface ResizablePanelGroupProps extends React.HTMLAttributes<HTMLDivElement> {
25
+ direction?: "horizontal" | "vertical"
26
+ defaultSizes?: number[]
27
+ }
28
+
29
+ export function ResizablePanelGroup({
30
+ className,
31
+ direction = "horizontal",
32
+ defaultSizes,
33
+ children,
34
+ ...props
35
+ }: ResizablePanelGroupProps) {
36
+ // Count panel children to compute default sizes
37
+ const childArray = React.Children.toArray(children)
38
+ const panelCount = childArray.filter(
39
+ (c) => React.isValidElement(c) && (c.type as { displayName?: string }).displayName === "ResizablePanel"
40
+ ).length
41
+
42
+ const initialSizes = React.useMemo(() => {
43
+ if (defaultSizes && defaultSizes.length === panelCount) return defaultSizes
44
+ const n = Math.max(panelCount, 1)
45
+ return Array<number>(n).fill(100 / n)
46
+ // eslint-disable-next-line react-hooks/exhaustive-deps
47
+ }, []) // intentionally mount-only
48
+
49
+ const [sizes, setSizes] = React.useState<number[]>(initialSizes)
50
+
51
+ // Inject panel/handle indexes via cloneElement so consumers don't need explicit index props
52
+ let panelIdx = 0
53
+ let handleIdx = 0
54
+ const enhanced = React.Children.map(children, (child) => {
55
+ if (!React.isValidElement(child)) return child
56
+ const displayName = (child.type as { displayName?: string }).displayName
57
+ if (displayName === "ResizablePanel") {
58
+ return React.cloneElement(child as React.ReactElement<ResizablePanelProps>, {
59
+ _index: panelIdx++,
60
+ })
61
+ }
62
+ if (displayName === "ResizableHandle") {
63
+ return React.cloneElement(child as React.ReactElement<ResizableHandleProps>, {
64
+ _index: handleIdx++,
65
+ })
66
+ }
67
+ return child
68
+ })
69
+
70
+ return (
71
+ <PanelGroupContext.Provider value={{ direction, sizes, setSizes }}>
72
+ <div
73
+ className={cx(
74
+ "flex h-full w-full overflow-hidden",
75
+ direction === "vertical" ? "flex-col" : "flex-row",
76
+ className
77
+ )}
78
+ data-panel-group-direction={direction}
79
+ {...props}
80
+ >
81
+ {enhanced}
82
+ </div>
83
+ </PanelGroupContext.Provider>
84
+ )
85
+ }
86
+ ResizablePanelGroup.displayName = "ResizablePanelGroup"
87
+
88
+ // ── Panel ──────────────────────────────────────────────────────────────────────
89
+
90
+ export interface ResizablePanelProps extends React.HTMLAttributes<HTMLDivElement> {
91
+ minSize?: number
92
+ /** Injected automatically by ResizablePanelGroup — do not pass manually. */
93
+ _index?: number
94
+ }
95
+
96
+ export function ResizablePanel({
97
+ className,
98
+ children,
99
+ minSize = 10,
100
+ _index = 0,
101
+ style,
102
+ ...props
103
+ }: ResizablePanelProps) {
104
+ const { direction, sizes } = usePanelGroup()
105
+ const size = sizes[_index] ?? 100 / Math.max(sizes.length, 1)
106
+
107
+ const sizeStyle: React.CSSProperties =
108
+ direction === "horizontal"
109
+ ? { width: `${size}%`, minWidth: `${minSize}%`, flexShrink: 0 }
110
+ : { height: `${size}%`, minHeight: `${minSize}%`, flexShrink: 0 }
111
+
112
+ return (
113
+ <div
114
+ className={cx("overflow-auto min-w-0 min-h-0", className)}
115
+ style={{ ...sizeStyle, ...style }}
116
+ {...props}
117
+ >
118
+ {children}
119
+ </div>
120
+ )
121
+ }
122
+ ResizablePanel.displayName = "ResizablePanel"
123
+
124
+ // ── Handle ─────────────────────────────────────────────────────────────────────
125
+
126
+ export interface ResizableHandleProps extends React.HTMLAttributes<HTMLDivElement> {
127
+ withHandle?: boolean
128
+ /** Injected automatically by ResizablePanelGroup — do not pass manually. */
129
+ _index?: number
130
+ }
131
+
132
+ export function ResizableHandle({
133
+ className,
134
+ withHandle = true,
135
+ _index = 0,
136
+ ...props
137
+ }: ResizableHandleProps) {
138
+ const { direction, sizes, setSizes } = usePanelGroup()
139
+ const isDragging = React.useRef(false)
140
+ const startPos = React.useRef(0)
141
+ const startSizes = React.useRef<number[]>([])
142
+ const handleRef = React.useRef<HTMLDivElement>(null)
143
+
144
+ function clampSizes(rawSizes: number[], a: number, b: number, newA: number): number[] {
145
+ const total = rawSizes[a] + rawSizes[b]
146
+ const minA = 10
147
+ const minB = 10
148
+ let sa = newA
149
+ let sb = total - sa
150
+ if (sa < minA) { sa = minA; sb = total - minA }
151
+ if (sb < minB) { sb = minB; sa = total - minB }
152
+ const next = [...rawSizes]
153
+ next[a] = sa
154
+ next[b] = sb
155
+ return next
156
+ }
157
+
158
+ function onPointerDown(e: React.PointerEvent<HTMLDivElement>) {
159
+ e.preventDefault()
160
+ isDragging.current = true
161
+ startPos.current = direction === "horizontal" ? e.clientX : e.clientY
162
+ startSizes.current = [...sizes]
163
+ handleRef.current?.setPointerCapture(e.pointerId)
164
+ }
165
+
166
+ function onPointerMove(e: React.PointerEvent<HTMLDivElement>) {
167
+ if (!isDragging.current) return
168
+ e.preventDefault()
169
+ const container = handleRef.current?.parentElement
170
+ if (!container) return
171
+ const rect = container.getBoundingClientRect()
172
+ const totalPx = direction === "horizontal" ? rect.width : rect.height
173
+ if (totalPx === 0) return
174
+
175
+ const currentPos = direction === "horizontal" ? e.clientX : e.clientY
176
+ const deltaPct = ((currentPos - startPos.current) / totalPx) * 100
177
+ const a = _index
178
+ const b = _index + 1
179
+ if (b >= startSizes.current.length) return
180
+
181
+ const newA = (startSizes.current[a] ?? 0) + deltaPct
182
+ setSizes((prev) => clampSizes(prev, a, b, newA))
183
+ }
184
+
185
+ function onPointerUp(e: React.PointerEvent<HTMLDivElement>) {
186
+ isDragging.current = false
187
+ handleRef.current?.releasePointerCapture(e.pointerId)
188
+ }
189
+
190
+ function onKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
191
+ const step = 2
192
+ const isForward = direction === "horizontal" ? e.key === "ArrowRight" : e.key === "ArrowDown"
193
+ const isBack = direction === "horizontal" ? e.key === "ArrowLeft" : e.key === "ArrowUp"
194
+ if (!isForward && !isBack) return
195
+ e.preventDefault()
196
+ const delta = isForward ? step : -step
197
+ const a = _index
198
+ const b = _index + 1
199
+ setSizes((prev) => {
200
+ if (b >= prev.length) return prev
201
+ return clampSizes(prev, a, b, prev[a] + delta)
202
+ })
203
+ }
204
+
205
+ return (
206
+ <div
207
+ ref={handleRef}
208
+ role="separator"
209
+ aria-orientation={direction === "horizontal" ? "vertical" : "horizontal"}
210
+ tabIndex={0}
211
+ onPointerDown={onPointerDown}
212
+ onPointerMove={onPointerMove}
213
+ onPointerUp={onPointerUp}
214
+ onKeyDown={onKeyDown}
215
+ className={cx(
216
+ "relative flex shrink-0 items-center justify-center bg-border transition-colors select-none",
217
+ "hover:bg-border-strong active:bg-border-strong",
218
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-strong focus-visible:ring-offset-1 focus-visible:ring-offset-background",
219
+ direction === "horizontal"
220
+ ? "w-px h-full cursor-col-resize"
221
+ : "h-px w-full cursor-row-resize",
222
+ className
223
+ )}
224
+ {...props}
225
+ >
226
+ {withHandle && (
227
+ <div className="z-10 flex h-5 w-3.5 items-center justify-center rounded border border-border bg-surface-2 shadow-sm pointer-events-none">
228
+ <svg
229
+ width="6"
230
+ height="12"
231
+ viewBox="0 0 6 12"
232
+ fill="none"
233
+ className={cx(
234
+ "text-muted-foreground",
235
+ direction === "vertical" && "rotate-90"
236
+ )}
237
+ >
238
+ <circle cx="1" cy="3" r="1" fill="currentColor" />
239
+ <circle cx="5" cy="3" r="1" fill="currentColor" />
240
+ <circle cx="1" cy="6" r="1" fill="currentColor" />
241
+ <circle cx="5" cy="6" r="1" fill="currentColor" />
242
+ <circle cx="1" cy="9" r="1" fill="currentColor" />
243
+ <circle cx="5" cy="9" r="1" fill="currentColor" />
244
+ </svg>
245
+ </div>
246
+ )}
247
+ </div>
248
+ )
249
+ }
250
+ ResizableHandle.displayName = "ResizableHandle"
@@ -0,0 +1,38 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RadixScrollArea from "@radix-ui/react-scroll-area"
5
+ import { cx } from "@/lib/cx"
6
+
7
+ interface ScrollAreaProps extends React.ComponentPropsWithoutRef<typeof RadixScrollArea.Root> {
8
+ orientation?: "vertical" | "horizontal" | "both"
9
+ }
10
+
11
+ export const ScrollArea = React.forwardRef<
12
+ React.ElementRef<typeof RadixScrollArea.Viewport>,
13
+ ScrollAreaProps
14
+ >(({ className, children, orientation = "vertical", ...props }, ref) => (
15
+ <RadixScrollArea.Root className={cx("relative overflow-hidden", className)} {...props}>
16
+ <RadixScrollArea.Viewport ref={ref} className="h-full w-full rounded-[inherit]">
17
+ {children}
18
+ </RadixScrollArea.Viewport>
19
+ {(orientation === "vertical" || orientation === "both") && (
20
+ <RadixScrollArea.Scrollbar
21
+ orientation="vertical"
22
+ className="flex touch-none select-none transition-colors w-2.5 border-l border-l-transparent p-px"
23
+ >
24
+ <RadixScrollArea.Thumb className="relative flex-1 rounded-full bg-border hover:bg-border-strong transition-colors" />
25
+ </RadixScrollArea.Scrollbar>
26
+ )}
27
+ {(orientation === "horizontal" || orientation === "both") && (
28
+ <RadixScrollArea.Scrollbar
29
+ orientation="horizontal"
30
+ className="flex touch-none select-none transition-colors h-2.5 flex-col border-t border-t-transparent p-px"
31
+ >
32
+ <RadixScrollArea.Thumb className="relative flex-1 rounded-full bg-border hover:bg-border-strong transition-colors" />
33
+ </RadixScrollArea.Scrollbar>
34
+ )}
35
+ <RadixScrollArea.Corner />
36
+ </RadixScrollArea.Root>
37
+ ))
38
+ ScrollArea.displayName = "ScrollArea"
@@ -0,0 +1,128 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RadixSelect from "@radix-ui/react-select"
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react"
6
+ import { cx } from "@/lib/cx"
7
+
8
+ export const Select = RadixSelect.Root
9
+ export const SelectGroup = RadixSelect.Group
10
+ export const SelectValue = RadixSelect.Value
11
+
12
+ export const SelectTrigger = React.forwardRef<
13
+ React.ElementRef<typeof RadixSelect.Trigger>,
14
+ React.ComponentPropsWithoutRef<typeof RadixSelect.Trigger>
15
+ >(({ className, children, ...props }, ref) => (
16
+ <RadixSelect.Trigger
17
+ ref={ref}
18
+ className={cx(
19
+ "flex h-9 w-full items-center justify-between gap-2 rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm outline-none",
20
+ "placeholder:text-muted-foreground",
21
+ "hover:border-border-strong transition-colors",
22
+ "focus-visible:ring-1 focus-visible:ring-border-strong focus-visible:ring-offset-1 focus-visible:ring-offset-background",
23
+ "data-[placeholder]:text-muted-foreground",
24
+ "disabled:cursor-not-allowed disabled:opacity-50",
25
+ className
26
+ )}
27
+ {...props}
28
+ >
29
+ {children}
30
+ <RadixSelect.Icon asChild>
31
+ <ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground" />
32
+ </RadixSelect.Icon>
33
+ </RadixSelect.Trigger>
34
+ ))
35
+ SelectTrigger.displayName = "SelectTrigger"
36
+
37
+ export const SelectScrollUpButton = React.forwardRef<
38
+ React.ElementRef<typeof RadixSelect.ScrollUpButton>,
39
+ React.ComponentPropsWithoutRef<typeof RadixSelect.ScrollUpButton>
40
+ >(({ className, ...props }, ref) => (
41
+ <RadixSelect.ScrollUpButton ref={ref} className={cx("flex cursor-default items-center justify-center py-1", className)} {...props}>
42
+ <ChevronUp className="h-4 w-4" />
43
+ </RadixSelect.ScrollUpButton>
44
+ ))
45
+ SelectScrollUpButton.displayName = "SelectScrollUpButton"
46
+
47
+ export const SelectScrollDownButton = React.forwardRef<
48
+ React.ElementRef<typeof RadixSelect.ScrollDownButton>,
49
+ React.ComponentPropsWithoutRef<typeof RadixSelect.ScrollDownButton>
50
+ >(({ className, ...props }, ref) => (
51
+ <RadixSelect.ScrollDownButton ref={ref} className={cx("flex cursor-default items-center justify-center py-1", className)} {...props}>
52
+ <ChevronDown className="h-4 w-4" />
53
+ </RadixSelect.ScrollDownButton>
54
+ ))
55
+ SelectScrollDownButton.displayName = "SelectScrollDownButton"
56
+
57
+ export const SelectContent = React.forwardRef<
58
+ React.ElementRef<typeof RadixSelect.Content>,
59
+ React.ComponentPropsWithoutRef<typeof RadixSelect.Content>
60
+ >(({ className, children, position = "popper", ...props }, ref) => (
61
+ <RadixSelect.Portal>
62
+ <RadixSelect.Content
63
+ ref={ref}
64
+ position={position}
65
+ className={cx(
66
+ "relative z-50 max-h-60 min-w-[8rem] overflow-hidden rounded-xl border border-border bg-card shadow-lg",
67
+ "animate-in fade-in-0 zoom-in-95",
68
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
69
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
70
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
71
+ position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
72
+ className
73
+ )}
74
+ {...props}
75
+ >
76
+ <SelectScrollUpButton />
77
+ <RadixSelect.Viewport
78
+ className={cx(
79
+ "p-1.5",
80
+ position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
81
+ )}
82
+ >
83
+ {children}
84
+ </RadixSelect.Viewport>
85
+ <SelectScrollDownButton />
86
+ </RadixSelect.Content>
87
+ </RadixSelect.Portal>
88
+ ))
89
+ SelectContent.displayName = "SelectContent"
90
+
91
+ export const SelectLabel = React.forwardRef<
92
+ React.ElementRef<typeof RadixSelect.Label>,
93
+ React.ComponentPropsWithoutRef<typeof RadixSelect.Label>
94
+ >(({ className, ...props }, ref) => (
95
+ <RadixSelect.Label ref={ref} className={cx("px-2.5 py-1.5 text-xs font-medium text-muted-foreground", className)} {...props} />
96
+ ))
97
+ SelectLabel.displayName = "SelectLabel"
98
+
99
+ export const SelectItem = React.forwardRef<
100
+ React.ElementRef<typeof RadixSelect.Item>,
101
+ React.ComponentPropsWithoutRef<typeof RadixSelect.Item>
102
+ >(({ className, children, ...props }, ref) => (
103
+ <RadixSelect.Item
104
+ ref={ref}
105
+ className={cx(
106
+ "relative flex w-full cursor-default select-none items-center rounded-lg py-1.5 pl-8 pr-2.5 text-sm outline-none",
107
+ "focus:bg-surface-2 data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
108
+ className
109
+ )}
110
+ {...props}
111
+ >
112
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
113
+ <RadixSelect.ItemIndicator>
114
+ <Check className="h-4 w-4" />
115
+ </RadixSelect.ItemIndicator>
116
+ </span>
117
+ <RadixSelect.ItemText>{children}</RadixSelect.ItemText>
118
+ </RadixSelect.Item>
119
+ ))
120
+ SelectItem.displayName = "SelectItem"
121
+
122
+ export const SelectSeparator = React.forwardRef<
123
+ React.ElementRef<typeof RadixSelect.Separator>,
124
+ React.ComponentPropsWithoutRef<typeof RadixSelect.Separator>
125
+ >(({ className, ...props }, ref) => (
126
+ <RadixSelect.Separator ref={ref} className={cx("-mx-1.5 my-1.5 h-px bg-border", className)} {...props} />
127
+ ))
128
+ SelectSeparator.displayName = "SelectSeparator"
@@ -0,0 +1,47 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator"
5
+ import { cx } from "@/lib/cx"
6
+
7
+ export interface SeparatorProps
8
+ extends React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> {
9
+ label?: string
10
+ }
11
+
12
+ function Separator({ className, orientation = "horizontal", decorative = true, label, ...props }: SeparatorProps) {
13
+ if (label && orientation === "horizontal") {
14
+ return (
15
+ <div className="flex items-center gap-3">
16
+ <SeparatorPrimitive.Root
17
+ decorative={decorative}
18
+ orientation={orientation}
19
+ className={cx("flex-1 shrink-0 bg-border", "h-px", className)}
20
+ {...props}
21
+ />
22
+ <span className="text-xs text-muted-foreground whitespace-nowrap select-none">{label}</span>
23
+ <SeparatorPrimitive.Root
24
+ decorative={decorative}
25
+ orientation={orientation}
26
+ className={cx("flex-1 shrink-0 bg-border", "h-px", className)}
27
+ />
28
+ </div>
29
+ )
30
+ }
31
+
32
+ return (
33
+ <SeparatorPrimitive.Root
34
+ decorative={decorative}
35
+ orientation={orientation}
36
+ className={cx(
37
+ "shrink-0 bg-border",
38
+ orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
39
+ className,
40
+ )}
41
+ {...props}
42
+ />
43
+ )
44
+ }
45
+ Separator.displayName = SeparatorPrimitive.Root.displayName
46
+
47
+ export { Separator }