@stampui/blocks 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ai-chat-shell.d.ts +1 -0
- package/dist/components/ai-chat-shell.js +23 -0
- package/dist/components/prompt-input.d.ts +5 -0
- package/dist/components/prompt-input.js +47 -0
- package/dist/components/registry-card.d.ts +6 -0
- package/dist/components/registry-card.js +15 -0
- package/dist/components/registry-explorer.d.ts +8 -0
- package/dist/components/registry-explorer.js +38 -0
- package/dist/components/token-stream.d.ts +7 -0
- package/dist/components/token-stream.js +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +23 -0
- package/dist/manifests.d.ts +3 -0
- package/dist/manifests.js +1666 -0
- package/dist/types.d.ts +44 -0
- package/dist/types.js +2 -0
- package/package.json +28 -0
- package/src/components/blocks/ai-chat-shell.tsx +97 -0
- package/src/components/blocks/auth-panel.tsx +203 -0
- package/src/components/blocks/feature-grid.tsx +122 -0
- package/src/components/blocks/hero-section.tsx +73 -0
- package/src/components/blocks/notification-center.tsx +185 -0
- package/src/components/blocks/onboarding-flow.tsx +230 -0
- package/src/components/blocks/pricing-section.tsx +135 -0
- package/src/components/blocks/project-command-center.tsx +188 -0
- package/src/components/blocks/prompt-input.tsx +81 -0
- package/src/components/blocks/registry-card.tsx +104 -0
- package/src/components/blocks/registry-explorer.tsx +78 -0
- package/src/components/blocks/settings-layout.tsx +178 -0
- package/src/components/blocks/stats-strip.tsx +100 -0
- package/src/components/blocks/token-stream.tsx +42 -0
- package/src/components/blocks/usage-card.tsx +116 -0
- package/src/components/core/accordion.tsx +58 -0
- package/src/components/core/alert-dialog.tsx +113 -0
- package/src/components/core/alert.tsx +48 -0
- package/src/components/core/animated-number.tsx +77 -0
- package/src/components/core/aspect-ratio.tsx +20 -0
- package/src/components/core/avatar-stack.tsx +61 -0
- package/src/components/core/avatar.tsx +90 -0
- package/src/components/core/badge.tsx +39 -0
- package/src/components/core/breadcrumb.tsx +63 -0
- package/src/components/core/button-group.tsx +37 -0
- package/src/components/core/button.tsx +110 -0
- package/src/components/core/calendar.tsx +143 -0
- package/src/components/core/card.tsx +60 -0
- package/src/components/core/carousel.tsx +170 -0
- package/src/components/core/chart.tsx +377 -0
- package/src/components/core/checkbox.tsx +64 -0
- package/src/components/core/collapsible.tsx +30 -0
- package/src/components/core/combobox.tsx +114 -0
- package/src/components/core/command-box.tsx +22 -0
- package/src/components/core/command.tsx +165 -0
- package/src/components/core/confirm-action.tsx +94 -0
- package/src/components/core/context-menu.tsx +139 -0
- package/src/components/core/copy-button.tsx +41 -0
- package/src/components/core/data-table.tsx +173 -0
- package/src/components/core/date-picker.tsx +73 -0
- package/src/components/core/dialog.tsx +83 -0
- package/src/components/core/drawer.tsx +87 -0
- package/src/components/core/dropdown-menu.tsx +147 -0
- package/src/components/core/empty.tsx +34 -0
- package/src/components/core/field.tsx +39 -0
- package/src/components/core/file-upload.tsx +143 -0
- package/src/components/core/hover-card.tsx +31 -0
- package/src/components/core/inline-edit.tsx +104 -0
- package/src/components/core/input-group.tsx +47 -0
- package/src/components/core/input-otp.tsx +108 -0
- package/src/components/core/input.tsx +37 -0
- package/src/components/core/kbd.tsx +47 -0
- package/src/components/core/label.tsx +28 -0
- package/src/components/core/marquee.tsx +61 -0
- package/src/components/core/menubar.tsx +120 -0
- package/src/components/core/multi-select.tsx +145 -0
- package/src/components/core/native-select.tsx +27 -0
- package/src/components/core/navigation-menu.tsx +130 -0
- package/src/components/core/number-stepper.tsx +80 -0
- package/src/components/core/pagination.tsx +80 -0
- package/src/components/core/password-input.tsx +90 -0
- package/src/components/core/popover.tsx +34 -0
- package/src/components/core/progress.tsx +63 -0
- package/src/components/core/radio-group.tsx +77 -0
- package/src/components/core/resizable.tsx +250 -0
- package/src/components/core/scroll-area.tsx +38 -0
- package/src/components/core/select.tsx +128 -0
- package/src/components/core/separator.tsx +47 -0
- package/src/components/core/sheet.tsx +118 -0
- package/src/components/core/sidebar.tsx +129 -0
- package/src/components/core/skeleton.tsx +32 -0
- package/src/components/core/slider.tsx +97 -0
- package/src/components/core/sonner.tsx +29 -0
- package/src/components/core/spinner.tsx +60 -0
- package/src/components/core/status-pulse.tsx +67 -0
- package/src/components/core/stepper.tsx +111 -0
- package/src/components/core/switch.tsx +72 -0
- package/src/components/core/table.tsx +104 -0
- package/src/components/core/tabs.tsx +55 -0
- package/src/components/core/tag-input.tsx +93 -0
- package/src/components/core/textarea.tsx +44 -0
- package/src/components/core/timeline.tsx +81 -0
- package/src/components/core/toggle-group.tsx +56 -0
- package/src/components/core/toggle.tsx +66 -0
- package/src/components/core/tooltip.tsx +31 -0
- package/src/components/core/typing-indicator.tsx +51 -0
- package/src/index.ts +8 -0
- package/src/manifests.ts +1682 -0
- package/src/types.ts +58 -0
- package/src/ui.ts +13 -0
|
@@ -0,0 +1,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 }
|