@srcroot/ui 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +91 -0
- package/package.json +9 -3
- package/registry/calendar.tsx +416 -142
- package/registry/combobox.tsx +174 -0
- package/registry/command.tsx +298 -0
- package/registry/context-menu.tsx +221 -0
- package/registry/date-picker.tsx +179 -0
- package/registry/drawer.tsx +217 -0
- package/registry/dropdown-menu.tsx +1 -30
- package/registry/file-upload.tsx +240 -0
- package/registry/hover-card.tsx +165 -0
- package/registry/kbd.tsx +60 -0
- package/registry/menubar.tsx +246 -0
- package/registry/native-select.tsx +49 -0
- package/registry/pagination.tsx +3 -0
- package/registry/resizable.tsx +213 -0
- package/registry/scroll-area.tsx +60 -0
- package/registry/search.tsx +2 -1
- package/registry/sheet.tsx +1 -0
- package/registry/sidebar.tsx +505 -0
- package/registry/slider.tsx +82 -18
- package/registry/toggle-group.tsx +129 -0
- package/registry/toggle.tsx +72 -0
- package/registry/tooltip.tsx +21 -3
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
// HoverCard Context
|
|
7
|
+
interface HoverCardContextValue {
|
|
8
|
+
open: boolean
|
|
9
|
+
triggerRef: React.RefObject<HTMLDivElement>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const HoverCardContext = React.createContext<HoverCardContextValue | null>(null)
|
|
13
|
+
|
|
14
|
+
function useHoverCard() {
|
|
15
|
+
const context = React.useContext(HoverCardContext)
|
|
16
|
+
if (!context) {
|
|
17
|
+
throw new Error("useHoverCard must be used within a HoverCard")
|
|
18
|
+
}
|
|
19
|
+
return context
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// HoverCard Root
|
|
23
|
+
interface HoverCardProps {
|
|
24
|
+
children: React.ReactNode
|
|
25
|
+
openDelay?: number
|
|
26
|
+
closeDelay?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const HoverCard = ({ children, openDelay = 200, closeDelay = 300 }: HoverCardProps) => {
|
|
30
|
+
const [open, setOpen] = React.useState(false)
|
|
31
|
+
const triggerRef = React.useRef<HTMLDivElement>(null)
|
|
32
|
+
const openTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)
|
|
33
|
+
const closeTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)
|
|
34
|
+
|
|
35
|
+
const handleMouseEnter = React.useCallback(() => {
|
|
36
|
+
if (closeTimeoutRef.current) {
|
|
37
|
+
clearTimeout(closeTimeoutRef.current)
|
|
38
|
+
closeTimeoutRef.current = null
|
|
39
|
+
}
|
|
40
|
+
openTimeoutRef.current = setTimeout(() => {
|
|
41
|
+
setOpen(true)
|
|
42
|
+
}, openDelay)
|
|
43
|
+
}, [openDelay])
|
|
44
|
+
|
|
45
|
+
const handleMouseLeave = React.useCallback(() => {
|
|
46
|
+
if (openTimeoutRef.current) {
|
|
47
|
+
clearTimeout(openTimeoutRef.current)
|
|
48
|
+
openTimeoutRef.current = null
|
|
49
|
+
}
|
|
50
|
+
closeTimeoutRef.current = setTimeout(() => {
|
|
51
|
+
setOpen(false)
|
|
52
|
+
}, closeDelay)
|
|
53
|
+
}, [closeDelay])
|
|
54
|
+
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
return () => {
|
|
57
|
+
if (openTimeoutRef.current) clearTimeout(openTimeoutRef.current)
|
|
58
|
+
if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current)
|
|
59
|
+
}
|
|
60
|
+
}, [])
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<HoverCardContext.Provider value={{ open, triggerRef }}>
|
|
64
|
+
<div
|
|
65
|
+
onMouseEnter={handleMouseEnter}
|
|
66
|
+
onMouseLeave={handleMouseLeave}
|
|
67
|
+
className="inline-block"
|
|
68
|
+
>
|
|
69
|
+
{children}
|
|
70
|
+
</div>
|
|
71
|
+
</HoverCardContext.Provider>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// HoverCard Trigger
|
|
76
|
+
interface HoverCardTriggerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
77
|
+
asChild?: boolean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const HoverCardTrigger = React.forwardRef<HTMLDivElement, HoverCardTriggerProps>(
|
|
81
|
+
({ children, asChild, className, ...props }, ref) => {
|
|
82
|
+
const { triggerRef } = useHoverCard()
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
ref={triggerRef}
|
|
87
|
+
className={cn("inline-block cursor-pointer", className)}
|
|
88
|
+
{...props}
|
|
89
|
+
>
|
|
90
|
+
{children}
|
|
91
|
+
</div>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
HoverCardTrigger.displayName = "HoverCardTrigger"
|
|
96
|
+
|
|
97
|
+
// HoverCard Content
|
|
98
|
+
interface HoverCardContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
99
|
+
align?: "start" | "center" | "end"
|
|
100
|
+
side?: "top" | "bottom"
|
|
101
|
+
sideOffset?: number
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const HoverCardContent = React.forwardRef<HTMLDivElement, HoverCardContentProps>(
|
|
105
|
+
({ children, className, align = "center", side = "bottom", sideOffset = 8, ...props }, ref) => {
|
|
106
|
+
const { open, triggerRef } = useHoverCard()
|
|
107
|
+
const [position, setPosition] = React.useState({ top: 0, left: 0 })
|
|
108
|
+
const contentRef = React.useRef<HTMLDivElement>(null)
|
|
109
|
+
|
|
110
|
+
React.useEffect(() => {
|
|
111
|
+
if (!open || !triggerRef.current || !contentRef.current) return
|
|
112
|
+
|
|
113
|
+
const triggerRect = triggerRef.current.getBoundingClientRect()
|
|
114
|
+
const contentRect = contentRef.current.getBoundingClientRect()
|
|
115
|
+
|
|
116
|
+
let top = 0
|
|
117
|
+
let left = 0
|
|
118
|
+
|
|
119
|
+
// Calculate vertical position
|
|
120
|
+
if (side === "bottom") {
|
|
121
|
+
top = triggerRect.bottom + sideOffset
|
|
122
|
+
} else {
|
|
123
|
+
top = triggerRect.top - contentRect.height - sideOffset
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Calculate horizontal position
|
|
127
|
+
if (align === "start") {
|
|
128
|
+
left = triggerRect.left
|
|
129
|
+
} else if (align === "end") {
|
|
130
|
+
left = triggerRect.right - contentRect.width
|
|
131
|
+
} else {
|
|
132
|
+
left = triggerRect.left + (triggerRect.width - contentRect.width) / 2
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Clamp to viewport
|
|
136
|
+
left = Math.max(8, Math.min(left, window.innerWidth - contentRect.width - 8))
|
|
137
|
+
top = Math.max(8, Math.min(top, window.innerHeight - contentRect.height - 8))
|
|
138
|
+
|
|
139
|
+
setPosition({ top, left })
|
|
140
|
+
}, [open, triggerRef, align, side, sideOffset])
|
|
141
|
+
|
|
142
|
+
if (!open) return null
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<div
|
|
146
|
+
ref={contentRef}
|
|
147
|
+
className={cn(
|
|
148
|
+
"fixed z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
|
|
149
|
+
"animate-in fade-in-0 zoom-in-95",
|
|
150
|
+
className
|
|
151
|
+
)}
|
|
152
|
+
style={{
|
|
153
|
+
top: position.top,
|
|
154
|
+
left: position.left,
|
|
155
|
+
}}
|
|
156
|
+
{...props}
|
|
157
|
+
>
|
|
158
|
+
{children}
|
|
159
|
+
</div>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
HoverCardContent.displayName = "HoverCardContent"
|
|
164
|
+
|
|
165
|
+
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
package/registry/kbd.tsx
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "@/lib/utils"
|
|
3
|
+
|
|
4
|
+
interface KbdProps extends React.HTMLAttributes<HTMLElement> {
|
|
5
|
+
/** Array of keys to display (e.g., ["Ctrl", "K"]) */
|
|
6
|
+
keys?: string[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Kbd - Keyboard key display component
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* <Kbd>⌘</Kbd>
|
|
14
|
+
* <Kbd keys={["Ctrl", "Shift", "P"]} />
|
|
15
|
+
*/
|
|
16
|
+
const Kbd = React.forwardRef<HTMLElement, KbdProps>(
|
|
17
|
+
({ className, children, keys, ...props }, ref) => {
|
|
18
|
+
// If keys array is provided, render each key
|
|
19
|
+
if (keys && keys.length > 0) {
|
|
20
|
+
return (
|
|
21
|
+
<span className="inline-flex items-center gap-1">
|
|
22
|
+
{keys.map((key, index) => (
|
|
23
|
+
<React.Fragment key={index}>
|
|
24
|
+
<kbd
|
|
25
|
+
ref={index === 0 ? ref : undefined}
|
|
26
|
+
className={cn(
|
|
27
|
+
"pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground",
|
|
28
|
+
className
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{key}
|
|
33
|
+
</kbd>
|
|
34
|
+
{index < keys.length - 1 && (
|
|
35
|
+
<span className="text-muted-foreground text-xs">+</span>
|
|
36
|
+
)}
|
|
37
|
+
</React.Fragment>
|
|
38
|
+
))}
|
|
39
|
+
</span>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Single key rendering
|
|
44
|
+
return (
|
|
45
|
+
<kbd
|
|
46
|
+
ref={ref}
|
|
47
|
+
className={cn(
|
|
48
|
+
"pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground",
|
|
49
|
+
className
|
|
50
|
+
)}
|
|
51
|
+
{...props}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</kbd>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
Kbd.displayName = "Kbd"
|
|
59
|
+
|
|
60
|
+
export { Kbd }
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
// Menubar Context
|
|
7
|
+
interface MenubarContextValue {
|
|
8
|
+
activeMenu: string | null
|
|
9
|
+
setActiveMenu: (menu: string | null) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const MenubarContext = React.createContext<MenubarContextValue | null>(null)
|
|
13
|
+
|
|
14
|
+
function useMenubar() {
|
|
15
|
+
const context = React.useContext(MenubarContext)
|
|
16
|
+
if (!context) {
|
|
17
|
+
throw new Error("useMenubar must be used within a Menubar")
|
|
18
|
+
}
|
|
19
|
+
return context
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// MenubarMenu Context
|
|
23
|
+
interface MenubarMenuContextValue {
|
|
24
|
+
menuId: string
|
|
25
|
+
triggerRef: React.RefObject<HTMLButtonElement>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const MenubarMenuContext = React.createContext<MenubarMenuContextValue | null>(null)
|
|
29
|
+
|
|
30
|
+
function useMenubarMenu() {
|
|
31
|
+
const context = React.useContext(MenubarMenuContext)
|
|
32
|
+
if (!context) {
|
|
33
|
+
throw new Error("useMenubarMenu must be used within a MenubarMenu")
|
|
34
|
+
}
|
|
35
|
+
return context
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Menubar Root
|
|
39
|
+
const Menubar = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
40
|
+
({ className, children, ...props }, ref) => {
|
|
41
|
+
const [activeMenu, setActiveMenu] = React.useState<string | null>(null)
|
|
42
|
+
|
|
43
|
+
// Close on outside click
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
if (!activeMenu) return
|
|
46
|
+
const handleClick = (e: MouseEvent) => {
|
|
47
|
+
const target = e.target as Element
|
|
48
|
+
if (!target.closest('[data-menubar]')) {
|
|
49
|
+
setActiveMenu(null)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
document.addEventListener("click", handleClick)
|
|
53
|
+
return () => document.removeEventListener("click", handleClick)
|
|
54
|
+
}, [activeMenu])
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<MenubarContext.Provider value={{ activeMenu, setActiveMenu }}>
|
|
58
|
+
<div
|
|
59
|
+
ref={ref}
|
|
60
|
+
data-menubar
|
|
61
|
+
className={cn(
|
|
62
|
+
"flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
|
|
63
|
+
className
|
|
64
|
+
)}
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
{children}
|
|
68
|
+
</div>
|
|
69
|
+
</MenubarContext.Provider>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
Menubar.displayName = "Menubar"
|
|
74
|
+
|
|
75
|
+
// MenubarMenu
|
|
76
|
+
interface MenubarMenuProps {
|
|
77
|
+
children: React.ReactNode
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const MenubarMenu = ({ children }: MenubarMenuProps) => {
|
|
81
|
+
const menuId = React.useId()
|
|
82
|
+
const triggerRef = React.useRef<HTMLButtonElement>(null)
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<MenubarMenuContext.Provider value={{ menuId, triggerRef }}>
|
|
86
|
+
<div className="relative">
|
|
87
|
+
{children}
|
|
88
|
+
</div>
|
|
89
|
+
</MenubarMenuContext.Provider>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
MenubarMenu.displayName = "MenubarMenu"
|
|
93
|
+
|
|
94
|
+
// MenubarTrigger
|
|
95
|
+
const MenubarTrigger = React.forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
|
|
96
|
+
({ className, children, ...props }, ref) => {
|
|
97
|
+
const { activeMenu, setActiveMenu } = useMenubar()
|
|
98
|
+
const { menuId, triggerRef } = useMenubarMenu()
|
|
99
|
+
const isOpen = activeMenu === menuId
|
|
100
|
+
|
|
101
|
+
const handleClick = () => {
|
|
102
|
+
setActiveMenu(isOpen ? null : menuId)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const handleMouseEnter = () => {
|
|
106
|
+
if (activeMenu && activeMenu !== menuId) {
|
|
107
|
+
setActiveMenu(menuId)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<button
|
|
113
|
+
ref={triggerRef}
|
|
114
|
+
className={cn(
|
|
115
|
+
"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none",
|
|
116
|
+
"focus:bg-accent focus:text-accent-foreground",
|
|
117
|
+
isOpen && "bg-accent text-accent-foreground",
|
|
118
|
+
className
|
|
119
|
+
)}
|
|
120
|
+
onClick={handleClick}
|
|
121
|
+
onMouseEnter={handleMouseEnter}
|
|
122
|
+
{...props}
|
|
123
|
+
>
|
|
124
|
+
{children}
|
|
125
|
+
</button>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
MenubarTrigger.displayName = "MenubarTrigger"
|
|
130
|
+
|
|
131
|
+
// MenubarContent
|
|
132
|
+
const MenubarContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
133
|
+
({ className, children, ...props }, ref) => {
|
|
134
|
+
const { activeMenu, setActiveMenu } = useMenubar()
|
|
135
|
+
const { menuId, triggerRef } = useMenubarMenu()
|
|
136
|
+
const isOpen = activeMenu === menuId
|
|
137
|
+
const [position, setPosition] = React.useState({ top: 0, left: 0 })
|
|
138
|
+
const contentRef = React.useRef<HTMLDivElement>(null)
|
|
139
|
+
|
|
140
|
+
React.useEffect(() => {
|
|
141
|
+
if (!isOpen || !triggerRef.current) return
|
|
142
|
+
const rect = triggerRef.current.getBoundingClientRect()
|
|
143
|
+
setPosition({
|
|
144
|
+
top: rect.bottom + 4,
|
|
145
|
+
left: rect.left,
|
|
146
|
+
})
|
|
147
|
+
}, [isOpen, triggerRef])
|
|
148
|
+
|
|
149
|
+
// Close on Escape
|
|
150
|
+
React.useEffect(() => {
|
|
151
|
+
if (!isOpen) return
|
|
152
|
+
const handleEscape = (e: KeyboardEvent) => {
|
|
153
|
+
if (e.key === "Escape") setActiveMenu(null)
|
|
154
|
+
}
|
|
155
|
+
document.addEventListener("keydown", handleEscape)
|
|
156
|
+
return () => document.removeEventListener("keydown", handleEscape)
|
|
157
|
+
}, [isOpen, setActiveMenu])
|
|
158
|
+
|
|
159
|
+
if (!isOpen) return null
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div
|
|
163
|
+
ref={contentRef}
|
|
164
|
+
className={cn(
|
|
165
|
+
"fixed z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
|
166
|
+
"animate-in fade-in-0 slide-in-from-top-2",
|
|
167
|
+
className
|
|
168
|
+
)}
|
|
169
|
+
style={{
|
|
170
|
+
top: position.top,
|
|
171
|
+
left: position.left,
|
|
172
|
+
}}
|
|
173
|
+
{...props}
|
|
174
|
+
>
|
|
175
|
+
{children}
|
|
176
|
+
</div>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
MenubarContent.displayName = "MenubarContent"
|
|
181
|
+
|
|
182
|
+
// MenubarItem
|
|
183
|
+
interface MenubarItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
184
|
+
inset?: boolean
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const MenubarItem = React.forwardRef<HTMLButtonElement, MenubarItemProps>(
|
|
188
|
+
({ className, inset, children, ...props }, ref) => {
|
|
189
|
+
const { setActiveMenu } = useMenubar()
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<button
|
|
193
|
+
ref={ref}
|
|
194
|
+
className={cn(
|
|
195
|
+
"relative flex w-full cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none",
|
|
196
|
+
"focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground",
|
|
197
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
198
|
+
inset && "pl-8",
|
|
199
|
+
className
|
|
200
|
+
)}
|
|
201
|
+
onClick={() => setActiveMenu(null)}
|
|
202
|
+
{...props}
|
|
203
|
+
>
|
|
204
|
+
{children}
|
|
205
|
+
</button>
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
MenubarItem.displayName = "MenubarItem"
|
|
210
|
+
|
|
211
|
+
// MenubarSeparator
|
|
212
|
+
const MenubarSeparator = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
213
|
+
({ className, ...props }, ref) => (
|
|
214
|
+
<div ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
MenubarSeparator.displayName = "MenubarSeparator"
|
|
218
|
+
|
|
219
|
+
// MenubarLabel
|
|
220
|
+
const MenubarLabel = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & { inset?: boolean }>(
|
|
221
|
+
({ className, inset, ...props }, ref) => (
|
|
222
|
+
<div
|
|
223
|
+
ref={ref}
|
|
224
|
+
className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
|
|
225
|
+
{...props}
|
|
226
|
+
/>
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
MenubarLabel.displayName = "MenubarLabel"
|
|
230
|
+
|
|
231
|
+
// MenubarShortcut
|
|
232
|
+
const MenubarShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (
|
|
233
|
+
<span className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)} {...props} />
|
|
234
|
+
)
|
|
235
|
+
MenubarShortcut.displayName = "MenubarShortcut"
|
|
236
|
+
|
|
237
|
+
export {
|
|
238
|
+
Menubar,
|
|
239
|
+
MenubarMenu,
|
|
240
|
+
MenubarTrigger,
|
|
241
|
+
MenubarContent,
|
|
242
|
+
MenubarItem,
|
|
243
|
+
MenubarSeparator,
|
|
244
|
+
MenubarLabel,
|
|
245
|
+
MenubarShortcut,
|
|
246
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "@/lib/utils"
|
|
3
|
+
|
|
4
|
+
export interface NativeSelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> { }
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* NativeSelect - Styled browser-native select element
|
|
8
|
+
*
|
|
9
|
+
* Uses the browser's native <select> for accessibility and mobile UX,
|
|
10
|
+
* with custom styling to match the design system.
|
|
11
|
+
*/
|
|
12
|
+
const NativeSelect = React.forwardRef<HTMLSelectElement, NativeSelectProps>(
|
|
13
|
+
({ className, children, ...props }, ref) => {
|
|
14
|
+
return (
|
|
15
|
+
<div className="relative">
|
|
16
|
+
<select
|
|
17
|
+
ref={ref}
|
|
18
|
+
className={cn(
|
|
19
|
+
"flex h-10 w-full appearance-none rounded-md border border-input bg-background px-3 py-2 pr-8 text-sm ring-offset-background",
|
|
20
|
+
"focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
21
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
{...props}
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</select>
|
|
28
|
+
{/* Custom chevron icon */}
|
|
29
|
+
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
|
30
|
+
<svg
|
|
31
|
+
className="h-4 w-4 opacity-50"
|
|
32
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
33
|
+
viewBox="0 0 24 24"
|
|
34
|
+
fill="none"
|
|
35
|
+
stroke="currentColor"
|
|
36
|
+
strokeWidth="2"
|
|
37
|
+
strokeLinecap="round"
|
|
38
|
+
strokeLinejoin="round"
|
|
39
|
+
>
|
|
40
|
+
<path d="m6 9 6 6 6-6" />
|
|
41
|
+
</svg>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
NativeSelect.displayName = "NativeSelect"
|
|
48
|
+
|
|
49
|
+
export { NativeSelect }
|
package/registry/pagination.tsx
CHANGED
|
@@ -58,6 +58,9 @@ PaginationItem.displayName = "PaginationItem"
|
|
|
58
58
|
interface PaginationLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
59
59
|
isActive?: boolean
|
|
60
60
|
size?: "default" | "sm" | "lg" | "icon"
|
|
61
|
+
children?: React.ReactNode
|
|
62
|
+
className?: string
|
|
63
|
+
href?: string
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
const PaginationLink = ({
|