@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,173 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { ChevronUp, ChevronDown, ChevronsUpDown, ChevronLeft, ChevronRight } from "lucide-react"
|
|
5
|
+
import { cx } from "@/lib/cx"
|
|
6
|
+
|
|
7
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export type SortDirection = "asc" | "desc" | null
|
|
10
|
+
|
|
11
|
+
export interface Column<T> {
|
|
12
|
+
key: string
|
|
13
|
+
header: string
|
|
14
|
+
accessor: (row: T) => React.ReactNode
|
|
15
|
+
sortable?: boolean
|
|
16
|
+
className?: string
|
|
17
|
+
headerClassName?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DataTableProps<T> {
|
|
21
|
+
columns: Column<T>[]
|
|
22
|
+
data: T[]
|
|
23
|
+
rowKey?: (row: T, index: number) => string
|
|
24
|
+
pageSize?: number
|
|
25
|
+
searchable?: boolean
|
|
26
|
+
searchPlaceholder?: string
|
|
27
|
+
searchFn?: (row: T, query: string) => boolean
|
|
28
|
+
className?: string
|
|
29
|
+
emptyMessage?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Component ─────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export function DataTable<T>({
|
|
35
|
+
columns,
|
|
36
|
+
data,
|
|
37
|
+
rowKey,
|
|
38
|
+
pageSize = 10,
|
|
39
|
+
searchable = false,
|
|
40
|
+
searchPlaceholder = "Search...",
|
|
41
|
+
searchFn,
|
|
42
|
+
className,
|
|
43
|
+
emptyMessage = "No results.",
|
|
44
|
+
}: DataTableProps<T>) {
|
|
45
|
+
const [sortKey, setSortKey] = React.useState<string | null>(null)
|
|
46
|
+
const [sortDir, setSortDir] = React.useState<SortDirection>(null)
|
|
47
|
+
const [query, setQuery] = React.useState("")
|
|
48
|
+
const [page, setPage] = React.useState(0)
|
|
49
|
+
|
|
50
|
+
const filtered = React.useMemo(() => {
|
|
51
|
+
if (!query || !searchable) return data
|
|
52
|
+
return data.filter((row) =>
|
|
53
|
+
searchFn
|
|
54
|
+
? searchFn(row, query)
|
|
55
|
+
: columns.some((col) => String(col.accessor(row)).toLowerCase().includes(query.toLowerCase()))
|
|
56
|
+
)
|
|
57
|
+
}, [data, query, searchable, searchFn, columns])
|
|
58
|
+
|
|
59
|
+
const sorted = React.useMemo(() => {
|
|
60
|
+
if (!sortKey || !sortDir) return filtered
|
|
61
|
+
const col = columns.find((c) => c.key === sortKey)
|
|
62
|
+
if (!col) return filtered
|
|
63
|
+
return [...filtered].sort((a, b) => {
|
|
64
|
+
const av = String(col.accessor(a))
|
|
65
|
+
const bv = String(col.accessor(b))
|
|
66
|
+
const cmp = av.localeCompare(bv, undefined, { numeric: true })
|
|
67
|
+
return sortDir === "asc" ? cmp : -cmp
|
|
68
|
+
})
|
|
69
|
+
}, [filtered, sortKey, sortDir, columns])
|
|
70
|
+
|
|
71
|
+
const totalPages = Math.ceil(sorted.length / pageSize)
|
|
72
|
+
const paged = sorted.slice(page * pageSize, (page + 1) * pageSize)
|
|
73
|
+
|
|
74
|
+
React.useEffect(() => { setPage(0) }, [query, sortKey, sortDir])
|
|
75
|
+
|
|
76
|
+
function toggleSort(key: string) {
|
|
77
|
+
if (sortKey !== key) { setSortKey(key); setSortDir("asc") }
|
|
78
|
+
else if (sortDir === "asc") setSortDir("desc")
|
|
79
|
+
else { setSortKey(null); setSortDir(null) }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div className={cx("space-y-3", className)}>
|
|
84
|
+
{searchable && (
|
|
85
|
+
<input
|
|
86
|
+
value={query}
|
|
87
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
88
|
+
placeholder={searchPlaceholder}
|
|
89
|
+
className="h-9 w-full max-w-xs rounded-lg border border-border bg-surface-2 px-3 text-sm outline-none placeholder:text-muted-foreground focus-visible:border-border-strong focus-visible:ring-1 focus-visible:ring-border-strong focus-visible:ring-offset-1 focus-visible:ring-offset-background"
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
<div className="rounded-xl border border-border overflow-hidden">
|
|
93
|
+
<div className="overflow-x-auto">
|
|
94
|
+
<table className="w-full text-sm">
|
|
95
|
+
<thead>
|
|
96
|
+
<tr className="border-b border-border bg-surface-2">
|
|
97
|
+
{columns.map((col) => (
|
|
98
|
+
<th
|
|
99
|
+
key={col.key}
|
|
100
|
+
className={cx(
|
|
101
|
+
"px-4 py-3 text-left text-xs font-medium text-muted-foreground",
|
|
102
|
+
col.sortable && "cursor-pointer select-none hover:text-foreground transition-colors",
|
|
103
|
+
col.headerClassName
|
|
104
|
+
)}
|
|
105
|
+
onClick={col.sortable ? () => toggleSort(col.key) : undefined}
|
|
106
|
+
>
|
|
107
|
+
<div className="flex items-center gap-1">
|
|
108
|
+
{col.header}
|
|
109
|
+
{col.sortable && (
|
|
110
|
+
<span className="text-muted-foreground">
|
|
111
|
+
{sortKey === col.key && sortDir === "asc" ? (
|
|
112
|
+
<ChevronUp className="h-3.5 w-3.5" />
|
|
113
|
+
) : sortKey === col.key && sortDir === "desc" ? (
|
|
114
|
+
<ChevronDown className="h-3.5 w-3.5" />
|
|
115
|
+
) : (
|
|
116
|
+
<ChevronsUpDown className="h-3.5 w-3.5 opacity-40" />
|
|
117
|
+
)}
|
|
118
|
+
</span>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
</th>
|
|
122
|
+
))}
|
|
123
|
+
</tr>
|
|
124
|
+
</thead>
|
|
125
|
+
<tbody>
|
|
126
|
+
{paged.length === 0 ? (
|
|
127
|
+
<tr>
|
|
128
|
+
<td colSpan={columns.length} className="px-4 py-10 text-center text-sm text-muted-foreground">
|
|
129
|
+
{emptyMessage}
|
|
130
|
+
</td>
|
|
131
|
+
</tr>
|
|
132
|
+
) : (
|
|
133
|
+
paged.map((row, i) => (
|
|
134
|
+
<tr key={rowKey ? rowKey(row, i) : i} className="border-b border-border last:border-0 hover:bg-surface-2 transition-colors">
|
|
135
|
+
{columns.map((col) => (
|
|
136
|
+
<td key={col.key} className={cx("px-4 py-3", col.className)}>
|
|
137
|
+
{col.accessor(row)}
|
|
138
|
+
</td>
|
|
139
|
+
))}
|
|
140
|
+
</tr>
|
|
141
|
+
))
|
|
142
|
+
)}
|
|
143
|
+
</tbody>
|
|
144
|
+
</table>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
{totalPages > 1 && (
|
|
148
|
+
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
|
149
|
+
<span>{sorted.length} row{sorted.length !== 1 ? "s" : ""}</span>
|
|
150
|
+
<div className="flex items-center gap-1">
|
|
151
|
+
<button
|
|
152
|
+
onClick={() => setPage((p) => Math.max(0, p - 1))}
|
|
153
|
+
disabled={page === 0}
|
|
154
|
+
className="flex h-7 w-7 items-center justify-center rounded-md border border-border hover:bg-surface-2 disabled:opacity-40 disabled:pointer-events-none transition-colors"
|
|
155
|
+
>
|
|
156
|
+
<ChevronLeft className="h-3.5 w-3.5" />
|
|
157
|
+
</button>
|
|
158
|
+
<span className="px-2 font-medium text-foreground">
|
|
159
|
+
{page + 1} / {totalPages}
|
|
160
|
+
</span>
|
|
161
|
+
<button
|
|
162
|
+
onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
|
|
163
|
+
disabled={page >= totalPages - 1}
|
|
164
|
+
className="flex h-7 w-7 items-center justify-center rounded-md border border-border hover:bg-surface-2 disabled:opacity-40 disabled:pointer-events-none transition-colors"
|
|
165
|
+
>
|
|
166
|
+
<ChevronRight className="h-3.5 w-3.5" />
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as RadixPopover from "@radix-ui/react-popover"
|
|
5
|
+
import { CalendarDays } from "lucide-react"
|
|
6
|
+
import { cx } from "@/lib/cx"
|
|
7
|
+
import { Calendar, type CalendarProps } from "@/components/core/calendar"
|
|
8
|
+
|
|
9
|
+
function formatDate(date: Date): string {
|
|
10
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface DatePickerProps extends Omit<CalendarProps, "className" | "disabled"> {
|
|
14
|
+
placeholder?: string
|
|
15
|
+
disabled?: CalendarProps["disabled"] | boolean
|
|
16
|
+
className?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function DatePicker({
|
|
20
|
+
selected,
|
|
21
|
+
onSelect,
|
|
22
|
+
placeholder = "Pick a date...",
|
|
23
|
+
disabled,
|
|
24
|
+
fromDate,
|
|
25
|
+
toDate,
|
|
26
|
+
className,
|
|
27
|
+
}: DatePickerProps) {
|
|
28
|
+
const [open, setOpen] = React.useState(false)
|
|
29
|
+
const isDisabledTrigger = typeof disabled === "boolean" ? disabled : false
|
|
30
|
+
const disabledFn = typeof disabled === "function" ? disabled : undefined
|
|
31
|
+
|
|
32
|
+
function handleSelect(date: Date) {
|
|
33
|
+
onSelect?.(date)
|
|
34
|
+
setOpen(false)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<RadixPopover.Root open={open} onOpenChange={setOpen}>
|
|
39
|
+
<RadixPopover.Trigger asChild>
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
disabled={isDisabledTrigger}
|
|
43
|
+
className={cx(
|
|
44
|
+
"flex h-9 w-full items-center justify-start gap-2 rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm outline-none",
|
|
45
|
+
"hover:border-border-strong transition-colors text-left",
|
|
46
|
+
"focus-visible:ring-1 focus-visible:ring-border-strong focus-visible:ring-offset-1 focus-visible:ring-offset-background",
|
|
47
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
48
|
+
!selected && "text-muted-foreground",
|
|
49
|
+
className
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
<CalendarDays className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
53
|
+
<span>{selected ? formatDate(selected) : placeholder}</span>
|
|
54
|
+
</button>
|
|
55
|
+
</RadixPopover.Trigger>
|
|
56
|
+
<RadixPopover.Portal>
|
|
57
|
+
<RadixPopover.Content
|
|
58
|
+
align="start"
|
|
59
|
+
sideOffset={4}
|
|
60
|
+
className="z-50 w-72 rounded-xl border border-border bg-card shadow-lg outline-none animate-in fade-in-0 zoom-in-95"
|
|
61
|
+
>
|
|
62
|
+
<Calendar
|
|
63
|
+
selected={selected}
|
|
64
|
+
onSelect={handleSelect}
|
|
65
|
+
disabled={disabledFn}
|
|
66
|
+
fromDate={fromDate}
|
|
67
|
+
toDate={toDate}
|
|
68
|
+
/>
|
|
69
|
+
</RadixPopover.Content>
|
|
70
|
+
</RadixPopover.Portal>
|
|
71
|
+
</RadixPopover.Root>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as RadixDialog from "@radix-ui/react-dialog"
|
|
5
|
+
import { X } from "lucide-react"
|
|
6
|
+
import { cx } from "@/lib/cx"
|
|
7
|
+
|
|
8
|
+
export const Dialog = RadixDialog.Root
|
|
9
|
+
export const DialogTrigger = RadixDialog.Trigger
|
|
10
|
+
export const DialogPortal = RadixDialog.Portal
|
|
11
|
+
export const DialogClose = RadixDialog.Close
|
|
12
|
+
|
|
13
|
+
export const DialogOverlay = React.forwardRef<
|
|
14
|
+
React.ElementRef<typeof RadixDialog.Overlay>,
|
|
15
|
+
React.ComponentPropsWithoutRef<typeof RadixDialog.Overlay>
|
|
16
|
+
>(({ className, ...props }, ref) => (
|
|
17
|
+
<RadixDialog.Overlay
|
|
18
|
+
ref={ref}
|
|
19
|
+
className={cx(
|
|
20
|
+
"fixed inset-0 z-50 bg-black/40 backdrop-blur-sm",
|
|
21
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
22
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
))
|
|
28
|
+
DialogOverlay.displayName = "DialogOverlay"
|
|
29
|
+
|
|
30
|
+
export const DialogContent = React.forwardRef<
|
|
31
|
+
React.ElementRef<typeof RadixDialog.Content>,
|
|
32
|
+
React.ComponentPropsWithoutRef<typeof RadixDialog.Content> & { hideClose?: boolean }
|
|
33
|
+
>(({ className, children, hideClose, ...props }, ref) => (
|
|
34
|
+
<RadixDialog.Portal>
|
|
35
|
+
<DialogOverlay />
|
|
36
|
+
<RadixDialog.Content
|
|
37
|
+
ref={ref}
|
|
38
|
+
className={cx(
|
|
39
|
+
"fixed left-1/2 top-1/2 z-50 w-full max-w-lg -translate-x-1/2 -translate-y-1/2",
|
|
40
|
+
"rounded-2xl border border-border bg-card p-6 shadow-xl outline-none",
|
|
41
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
|
|
42
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]",
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
{children}
|
|
48
|
+
{!hideClose && (
|
|
49
|
+
<RadixDialog.Close className="absolute right-4 top-4 rounded-md p-1 text-muted-foreground transition-colors hover:bg-surface-2 hover:text-foreground outline-none focus-visible:ring-1 focus-visible:ring-border-strong focus-visible:ring-offset-1 focus-visible:ring-offset-background">
|
|
50
|
+
<X className="h-4 w-4" />
|
|
51
|
+
<span className="sr-only">Close</span>
|
|
52
|
+
</RadixDialog.Close>
|
|
53
|
+
)}
|
|
54
|
+
</RadixDialog.Content>
|
|
55
|
+
</RadixDialog.Portal>
|
|
56
|
+
))
|
|
57
|
+
DialogContent.displayName = "DialogContent"
|
|
58
|
+
|
|
59
|
+
export const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
60
|
+
<div className={cx("flex flex-col gap-1.5 mb-4", className)} {...props} />
|
|
61
|
+
)
|
|
62
|
+
DialogHeader.displayName = "DialogHeader"
|
|
63
|
+
|
|
64
|
+
export const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
65
|
+
<div className={cx("flex items-center justify-end gap-2 mt-6", className)} {...props} />
|
|
66
|
+
)
|
|
67
|
+
DialogFooter.displayName = "DialogFooter"
|
|
68
|
+
|
|
69
|
+
export const DialogTitle = React.forwardRef<
|
|
70
|
+
React.ElementRef<typeof RadixDialog.Title>,
|
|
71
|
+
React.ComponentPropsWithoutRef<typeof RadixDialog.Title>
|
|
72
|
+
>(({ className, ...props }, ref) => (
|
|
73
|
+
<RadixDialog.Title ref={ref} className={cx("text-base font-semibold leading-tight text-foreground", className)} {...props} />
|
|
74
|
+
))
|
|
75
|
+
DialogTitle.displayName = "DialogTitle"
|
|
76
|
+
|
|
77
|
+
export const DialogDescription = React.forwardRef<
|
|
78
|
+
React.ElementRef<typeof RadixDialog.Description>,
|
|
79
|
+
React.ComponentPropsWithoutRef<typeof RadixDialog.Description>
|
|
80
|
+
>(({ className, ...props }, ref) => (
|
|
81
|
+
<RadixDialog.Description ref={ref} className={cx("text-sm text-muted-foreground leading-relaxed", className)} {...props} />
|
|
82
|
+
))
|
|
83
|
+
DialogDescription.displayName = "DialogDescription"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as RadixDialog from "@radix-ui/react-dialog"
|
|
5
|
+
import { X } from "lucide-react"
|
|
6
|
+
import { cx } from "@/lib/cx"
|
|
7
|
+
|
|
8
|
+
export const Drawer = RadixDialog.Root
|
|
9
|
+
export const DrawerTrigger = RadixDialog.Trigger
|
|
10
|
+
export const DrawerClose = RadixDialog.Close
|
|
11
|
+
export const DrawerPortal = RadixDialog.Portal
|
|
12
|
+
|
|
13
|
+
export const DrawerOverlay = React.forwardRef<
|
|
14
|
+
React.ElementRef<typeof RadixDialog.Overlay>,
|
|
15
|
+
React.ComponentPropsWithoutRef<typeof RadixDialog.Overlay>
|
|
16
|
+
>(({ className, ...props }, ref) => (
|
|
17
|
+
<RadixDialog.Overlay
|
|
18
|
+
ref={ref}
|
|
19
|
+
className={cx(
|
|
20
|
+
"fixed inset-0 z-50 bg-black/40 backdrop-blur-sm",
|
|
21
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
22
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
))
|
|
28
|
+
DrawerOverlay.displayName = "DrawerOverlay"
|
|
29
|
+
|
|
30
|
+
export const DrawerContent = React.forwardRef<
|
|
31
|
+
React.ElementRef<typeof RadixDialog.Content>,
|
|
32
|
+
React.ComponentPropsWithoutRef<typeof RadixDialog.Content>
|
|
33
|
+
>(({ className, children, ...props }, ref) => (
|
|
34
|
+
<RadixDialog.Portal>
|
|
35
|
+
<DrawerOverlay />
|
|
36
|
+
<RadixDialog.Content
|
|
37
|
+
ref={ref}
|
|
38
|
+
className={cx(
|
|
39
|
+
"fixed inset-x-0 bottom-0 z-50 flex flex-col rounded-t-2xl border-t border-border bg-card pb-safe shadow-xl outline-none",
|
|
40
|
+
"max-h-[85svh]",
|
|
41
|
+
"data-[state=open]:animate-in data-[state=open]:slide-in-from-bottom data-[state=open]:duration-300",
|
|
42
|
+
"data-[state=closed]:animate-out data-[state=closed]:slide-out-to-bottom data-[state=closed]:duration-300",
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
<div className="mx-auto mt-3 mb-2 h-1.5 w-10 shrink-0 rounded-full bg-border" />
|
|
48
|
+
{children}
|
|
49
|
+
<RadixDialog.Close className="absolute right-4 top-4 rounded-md p-1 text-muted-foreground transition-colors hover:bg-surface-2 hover:text-foreground outline-none focus-visible:ring-1 focus-visible:ring-border-strong focus-visible:ring-offset-1 focus-visible:ring-offset-background">
|
|
50
|
+
<X className="h-4 w-4" />
|
|
51
|
+
<span className="sr-only">Close</span>
|
|
52
|
+
</RadixDialog.Close>
|
|
53
|
+
</RadixDialog.Content>
|
|
54
|
+
</RadixDialog.Portal>
|
|
55
|
+
))
|
|
56
|
+
DrawerContent.displayName = "DrawerContent"
|
|
57
|
+
|
|
58
|
+
export const DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
59
|
+
<div className={cx("flex flex-col gap-1.5 px-6 pb-3", className)} {...props} />
|
|
60
|
+
)
|
|
61
|
+
DrawerHeader.displayName = "DrawerHeader"
|
|
62
|
+
|
|
63
|
+
export const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
64
|
+
<div className={cx("flex flex-col gap-2 px-6 pt-0 pb-6 mt-auto", className)} {...props} />
|
|
65
|
+
)
|
|
66
|
+
DrawerFooter.displayName = "DrawerFooter"
|
|
67
|
+
|
|
68
|
+
export const DrawerTitle = React.forwardRef<
|
|
69
|
+
React.ElementRef<typeof RadixDialog.Title>,
|
|
70
|
+
React.ComponentPropsWithoutRef<typeof RadixDialog.Title>
|
|
71
|
+
>(({ className, ...props }, ref) => (
|
|
72
|
+
<RadixDialog.Title ref={ref} className={cx("text-base font-semibold leading-tight text-foreground", className)} {...props} />
|
|
73
|
+
))
|
|
74
|
+
DrawerTitle.displayName = "DrawerTitle"
|
|
75
|
+
|
|
76
|
+
export const DrawerDescription = React.forwardRef<
|
|
77
|
+
React.ElementRef<typeof RadixDialog.Description>,
|
|
78
|
+
React.ComponentPropsWithoutRef<typeof RadixDialog.Description>
|
|
79
|
+
>(({ className, ...props }, ref) => (
|
|
80
|
+
<RadixDialog.Description ref={ref} className={cx("text-sm text-muted-foreground leading-relaxed", className)} {...props} />
|
|
81
|
+
))
|
|
82
|
+
DrawerDescription.displayName = "DrawerDescription"
|
|
83
|
+
|
|
84
|
+
export const DrawerBody = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
85
|
+
<div className={cx("flex-1 overflow-y-auto px-6 py-2", className)} {...props} />
|
|
86
|
+
)
|
|
87
|
+
DrawerBody.displayName = "DrawerBody"
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as RadixDropdown from "@radix-ui/react-dropdown-menu"
|
|
5
|
+
import { Check, ChevronRight, Circle } from "lucide-react"
|
|
6
|
+
import { cx } from "@/lib/cx"
|
|
7
|
+
|
|
8
|
+
export const DropdownMenu = RadixDropdown.Root
|
|
9
|
+
export const DropdownMenuTrigger = RadixDropdown.Trigger
|
|
10
|
+
export const DropdownMenuGroup = RadixDropdown.Group
|
|
11
|
+
export const DropdownMenuPortal = RadixDropdown.Portal
|
|
12
|
+
export const DropdownMenuSub = RadixDropdown.Sub
|
|
13
|
+
export const DropdownMenuRadioGroup = RadixDropdown.RadioGroup
|
|
14
|
+
|
|
15
|
+
export const DropdownMenuContent = React.forwardRef<
|
|
16
|
+
React.ElementRef<typeof RadixDropdown.Content>,
|
|
17
|
+
React.ComponentPropsWithoutRef<typeof RadixDropdown.Content>
|
|
18
|
+
>(({ className, sideOffset = 6, ...props }, ref) => (
|
|
19
|
+
<RadixDropdown.Portal>
|
|
20
|
+
<RadixDropdown.Content
|
|
21
|
+
ref={ref}
|
|
22
|
+
sideOffset={sideOffset}
|
|
23
|
+
className={cx(
|
|
24
|
+
"z-50 min-w-[180px] overflow-hidden rounded-xl border border-border bg-card p-1.5 shadow-lg",
|
|
25
|
+
"animate-in fade-in-0 zoom-in-95",
|
|
26
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
27
|
+
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
|
|
28
|
+
"data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
</RadixDropdown.Portal>
|
|
34
|
+
))
|
|
35
|
+
DropdownMenuContent.displayName = "DropdownMenuContent"
|
|
36
|
+
|
|
37
|
+
const itemBase = cx(
|
|
38
|
+
"relative flex cursor-default select-none items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm outline-none transition-colors",
|
|
39
|
+
"text-foreground focus:bg-surface-2 data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
export const DropdownMenuItem = React.forwardRef<
|
|
43
|
+
React.ElementRef<typeof RadixDropdown.Item>,
|
|
44
|
+
React.ComponentPropsWithoutRef<typeof RadixDropdown.Item> & { inset?: boolean }
|
|
45
|
+
>(({ className, inset, ...props }, ref) => (
|
|
46
|
+
<RadixDropdown.Item
|
|
47
|
+
ref={ref}
|
|
48
|
+
className={cx(itemBase, inset && "pl-8", className)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
))
|
|
52
|
+
DropdownMenuItem.displayName = "DropdownMenuItem"
|
|
53
|
+
|
|
54
|
+
export const DropdownMenuCheckboxItem = React.forwardRef<
|
|
55
|
+
React.ElementRef<typeof RadixDropdown.CheckboxItem>,
|
|
56
|
+
React.ComponentPropsWithoutRef<typeof RadixDropdown.CheckboxItem>
|
|
57
|
+
>(({ className, children, checked, ...props }, ref) => (
|
|
58
|
+
<RadixDropdown.CheckboxItem
|
|
59
|
+
ref={ref}
|
|
60
|
+
checked={checked}
|
|
61
|
+
className={cx(itemBase, "pl-8", className)}
|
|
62
|
+
{...props}
|
|
63
|
+
>
|
|
64
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
65
|
+
<RadixDropdown.ItemIndicator>
|
|
66
|
+
<Check className="h-4 w-4" />
|
|
67
|
+
</RadixDropdown.ItemIndicator>
|
|
68
|
+
</span>
|
|
69
|
+
{children}
|
|
70
|
+
</RadixDropdown.CheckboxItem>
|
|
71
|
+
))
|
|
72
|
+
DropdownMenuCheckboxItem.displayName = "DropdownMenuCheckboxItem"
|
|
73
|
+
|
|
74
|
+
export const DropdownMenuRadioItem = React.forwardRef<
|
|
75
|
+
React.ElementRef<typeof RadixDropdown.RadioItem>,
|
|
76
|
+
React.ComponentPropsWithoutRef<typeof RadixDropdown.RadioItem>
|
|
77
|
+
>(({ className, children, ...props }, ref) => (
|
|
78
|
+
<RadixDropdown.RadioItem
|
|
79
|
+
ref={ref}
|
|
80
|
+
className={cx(itemBase, "pl-8", className)}
|
|
81
|
+
{...props}
|
|
82
|
+
>
|
|
83
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
84
|
+
<RadixDropdown.ItemIndicator>
|
|
85
|
+
<Circle className="h-2 w-2 fill-current" />
|
|
86
|
+
</RadixDropdown.ItemIndicator>
|
|
87
|
+
</span>
|
|
88
|
+
{children}
|
|
89
|
+
</RadixDropdown.RadioItem>
|
|
90
|
+
))
|
|
91
|
+
DropdownMenuRadioItem.displayName = "DropdownMenuRadioItem"
|
|
92
|
+
|
|
93
|
+
export const DropdownMenuLabel = React.forwardRef<
|
|
94
|
+
React.ElementRef<typeof RadixDropdown.Label>,
|
|
95
|
+
React.ComponentPropsWithoutRef<typeof RadixDropdown.Label> & { inset?: boolean }
|
|
96
|
+
>(({ className, inset, ...props }, ref) => (
|
|
97
|
+
<RadixDropdown.Label
|
|
98
|
+
ref={ref}
|
|
99
|
+
className={cx("px-2.5 py-1.5 text-xs font-medium text-muted-foreground", inset && "pl-8", className)}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
))
|
|
103
|
+
DropdownMenuLabel.displayName = "DropdownMenuLabel"
|
|
104
|
+
|
|
105
|
+
export const DropdownMenuSeparator = React.forwardRef<
|
|
106
|
+
React.ElementRef<typeof RadixDropdown.Separator>,
|
|
107
|
+
React.ComponentPropsWithoutRef<typeof RadixDropdown.Separator>
|
|
108
|
+
>(({ className, ...props }, ref) => (
|
|
109
|
+
<RadixDropdown.Separator ref={ref} className={cx("-mx-1.5 my-1.5 h-px bg-border", className)} {...props} />
|
|
110
|
+
))
|
|
111
|
+
DropdownMenuSeparator.displayName = "DropdownMenuSeparator"
|
|
112
|
+
|
|
113
|
+
export const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (
|
|
114
|
+
<span className={cx("ml-auto text-xs tracking-widest text-muted-foreground", className)} {...props} />
|
|
115
|
+
)
|
|
116
|
+
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
|
117
|
+
|
|
118
|
+
export const DropdownMenuSubTrigger = React.forwardRef<
|
|
119
|
+
React.ElementRef<typeof RadixDropdown.SubTrigger>,
|
|
120
|
+
React.ComponentPropsWithoutRef<typeof RadixDropdown.SubTrigger> & { inset?: boolean }
|
|
121
|
+
>(({ className, inset, children, ...props }, ref) => (
|
|
122
|
+
<RadixDropdown.SubTrigger
|
|
123
|
+
ref={ref}
|
|
124
|
+
className={cx(itemBase, "data-[state=open]:bg-surface-2", inset && "pl-8", className)}
|
|
125
|
+
{...props}
|
|
126
|
+
>
|
|
127
|
+
{children}
|
|
128
|
+
<ChevronRight className="ml-auto h-4 w-4" />
|
|
129
|
+
</RadixDropdown.SubTrigger>
|
|
130
|
+
))
|
|
131
|
+
DropdownMenuSubTrigger.displayName = "DropdownMenuSubTrigger"
|
|
132
|
+
|
|
133
|
+
export const DropdownMenuSubContent = React.forwardRef<
|
|
134
|
+
React.ElementRef<typeof RadixDropdown.SubContent>,
|
|
135
|
+
React.ComponentPropsWithoutRef<typeof RadixDropdown.SubContent>
|
|
136
|
+
>(({ className, ...props }, ref) => (
|
|
137
|
+
<RadixDropdown.SubContent
|
|
138
|
+
ref={ref}
|
|
139
|
+
className={cx(
|
|
140
|
+
"z-50 min-w-[160px] overflow-hidden rounded-xl border border-border bg-card p-1.5 shadow-lg",
|
|
141
|
+
"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
142
|
+
className
|
|
143
|
+
)}
|
|
144
|
+
{...props}
|
|
145
|
+
/>
|
|
146
|
+
))
|
|
147
|
+
DropdownMenuSubContent.displayName = "DropdownMenuSubContent"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cx } from "@/lib/cx"
|
|
3
|
+
|
|
4
|
+
export interface EmptyProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
icon?: React.ReactNode
|
|
6
|
+
title: string
|
|
7
|
+
description?: string
|
|
8
|
+
action?: React.ReactNode
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Empty({ icon, title, description, action, className, ...props }: EmptyProps) {
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
className={cx(
|
|
15
|
+
"flex flex-col items-center justify-center gap-3 px-6 py-12 text-center",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
>
|
|
20
|
+
{icon && (
|
|
21
|
+
<div className="flex h-12 w-12 items-center justify-center rounded-xl border border-border bg-surface-2 text-muted-foreground">
|
|
22
|
+
{icon}
|
|
23
|
+
</div>
|
|
24
|
+
)}
|
|
25
|
+
<div className="space-y-1 max-w-xs">
|
|
26
|
+
<p className="text-sm font-medium text-foreground">{title}</p>
|
|
27
|
+
{description && (
|
|
28
|
+
<p className="text-sm text-muted-foreground leading-relaxed">{description}</p>
|
|
29
|
+
)}
|
|
30
|
+
</div>
|
|
31
|
+
{action && <div className="mt-1">{action}</div>}
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as RadixLabel from "@radix-ui/react-label"
|
|
3
|
+
import { cx } from "@/lib/cx"
|
|
4
|
+
|
|
5
|
+
export const Field = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
6
|
+
({ className, ...props }, ref) => (
|
|
7
|
+
<div ref={ref} className={cx("flex flex-col gap-1.5", className)} {...props} />
|
|
8
|
+
)
|
|
9
|
+
)
|
|
10
|
+
Field.displayName = "Field"
|
|
11
|
+
|
|
12
|
+
export const FieldLabel = React.forwardRef<
|
|
13
|
+
React.ElementRef<typeof RadixLabel.Root>,
|
|
14
|
+
React.ComponentPropsWithoutRef<typeof RadixLabel.Root> & { required?: boolean }
|
|
15
|
+
>(({ className, children, required, ...props }, ref) => (
|
|
16
|
+
<RadixLabel.Root
|
|
17
|
+
ref={ref}
|
|
18
|
+
className={cx("text-sm font-medium leading-none text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70", className)}
|
|
19
|
+
{...props}
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
{required && <span className="ml-1 text-muted-foreground">*</span>}
|
|
23
|
+
</RadixLabel.Root>
|
|
24
|
+
))
|
|
25
|
+
FieldLabel.displayName = "FieldLabel"
|
|
26
|
+
|
|
27
|
+
export const FieldHint = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
|
28
|
+
({ className, ...props }, ref) => (
|
|
29
|
+
<p ref={ref} className={cx("text-xs text-muted-foreground", className)} {...props} />
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
FieldHint.displayName = "FieldHint"
|
|
33
|
+
|
|
34
|
+
export const FieldError = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
|
35
|
+
({ className, ...props }, ref) => (
|
|
36
|
+
<p ref={ref} className={cx("text-xs text-danger font-medium", className)} {...props} />
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
FieldError.displayName = "FieldError"
|