@srcroot/ui 0.0.48 → 0.0.52

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.
@@ -1,9 +1,12 @@
1
1
  import * as React from "react"
2
+ import { createPortal } from "react-dom"
2
3
  import { cn } from "@/lib/utils"
4
+ import { Slot } from "@/components/ui/slot"
3
5
 
4
6
  interface PopoverContextValue {
5
7
  open: boolean
6
8
  onOpenChange: (open: boolean) => void
9
+ triggerRef: React.RefObject<HTMLButtonElement | null>
7
10
  }
8
11
 
9
12
  const PopoverContext = React.createContext<PopoverContextValue | null>(null)
@@ -30,12 +33,13 @@ interface PopoverProps {
30
33
  */
31
34
  function Popover({ children, open: controlledOpen, onOpenChange, defaultOpen = false }: PopoverProps) {
32
35
  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
36
+ const triggerRef = React.useRef<HTMLButtonElement>(null)
33
37
 
34
38
  const open = controlledOpen !== undefined ? controlledOpen : uncontrolledOpen
35
39
  const setOpen = onOpenChange || setUncontrolledOpen
36
40
 
37
41
  return (
38
- <PopoverContext.Provider value={{ open, onOpenChange: setOpen }}>
42
+ <PopoverContext.Provider value={{ open, onOpenChange: setOpen, triggerRef }}>
39
43
  <div className="relative inline-block">
40
44
  {children}
41
45
  </div>
@@ -57,25 +61,25 @@ const PopoverTrigger = React.forwardRef<HTMLButtonElement, PopoverTriggerProps>(
57
61
  context.onOpenChange(!context.open)
58
62
  }
59
63
 
60
- if (asChild && React.isValidElement(children)) {
61
- return React.cloneElement(children as React.ReactElement<any>, {
62
- onClick: handleClick,
63
- "aria-expanded": context.open,
64
- "aria-haspopup": true,
65
- ref,
66
- })
64
+ // Combine refs
65
+ const combinedRef = (node: HTMLButtonElement | null) => {
66
+ (context.triggerRef as any).current = node
67
+ if (typeof ref === 'function') ref(node)
68
+ else if (ref) ref.current = node
67
69
  }
68
70
 
71
+ const Comp = asChild ? Slot : "button"
72
+
69
73
  return (
70
- <button
71
- ref={ref}
74
+ <Comp
75
+ ref={combinedRef}
72
76
  aria-expanded={context.open}
73
77
  aria-haspopup={true}
74
78
  onClick={handleClick}
75
79
  {...props}
76
80
  >
77
81
  {children}
78
- </button>
82
+ </Comp>
79
83
  )
80
84
  }
81
85
  )
@@ -83,14 +87,30 @@ PopoverTrigger.displayName = "PopoverTrigger"
83
87
 
84
88
  const PopoverContent = React.forwardRef<
85
89
  HTMLDivElement,
86
- React.HTMLAttributes<HTMLDivElement> & { align?: "start" | "center" | "end" }
87
- >(({ className, children, align = "center", ...props }, ref) => {
90
+ React.HTMLAttributes<HTMLDivElement> & { align?: "start" | "center" | "end"; sideOffset?: number; portal?: boolean }
91
+ >(({ className, children, align = "center", sideOffset = 4, portal = true, ...props }, ref) => {
88
92
  const context = React.useContext(PopoverContext)
89
93
  if (!context) throw new Error("PopoverContent must be used within Popover")
94
+ const contentRef = React.useRef<HTMLDivElement>(null)
95
+ const [position, setPosition] = React.useState({ top: 0, left: 0 })
96
+
97
+ // Reset scroll on mount/unmount if needed, but mainly we just need a portal container
98
+ const [mounted, setMounted] = React.useState(false)
99
+ React.useEffect(() => {
100
+ setMounted(true)
101
+ }, [])
90
102
 
91
103
  React.useEffect(() => {
92
- const handleClickOutside = () => {
104
+ const handleClickOutside = (e: MouseEvent) => {
93
105
  if (context.open) {
106
+ const target = e.target as Node
107
+ const content = contentRef.current
108
+ const trigger = context.triggerRef.current
109
+
110
+ // Don't close if clicking inside content or trigger
111
+ if (content?.contains(target) || (trigger && trigger.contains(target))) {
112
+ return
113
+ }
94
114
  context.onOpenChange(false)
95
115
  }
96
116
  }
@@ -101,35 +121,103 @@ const PopoverContent = React.forwardRef<
101
121
  }
102
122
  }
103
123
 
104
- // Delay adding the listener to prevent immediate close
124
+ const checkPosition = () => {
125
+ if (context.open && contentRef.current && context.triggerRef.current) {
126
+ const triggerRect = context.triggerRef.current.getBoundingClientRect()
127
+ const contentRect = contentRef.current.getBoundingClientRect()
128
+ const viewportHeight = window.innerHeight
129
+ const viewportWidth = window.innerWidth
130
+
131
+ let top = 0
132
+ let left = 0
133
+
134
+ // Vertical
135
+ const spaceBelow = viewportHeight - triggerRect.bottom
136
+ const spaceAbove = triggerRect.top
137
+ const neededHeight = contentRect.height + sideOffset
138
+ const onBottom = spaceBelow >= neededHeight || spaceBelow > spaceAbove
139
+
140
+ if (onBottom) {
141
+ top = triggerRect.bottom + sideOffset
142
+ } else {
143
+ top = triggerRect.top - contentRect.height - sideOffset
144
+ }
145
+
146
+ // Horizontal (Alignment)
147
+ if (align === 'start') {
148
+ left = triggerRect.left
149
+ } else if (align === 'end') {
150
+ left = triggerRect.right - contentRect.width
151
+ } else {
152
+ // center
153
+ left = triggerRect.left + (triggerRect.width - contentRect.width) / 2
154
+ }
155
+
156
+ // Clamping
157
+ if (left < 4) left = 4
158
+ if (left + contentRect.width > viewportWidth - 4) {
159
+ left = viewportWidth - contentRect.width - 4
160
+ }
161
+
162
+ setPosition({ top, left })
163
+ }
164
+ }
165
+
166
+ if (context.open) {
167
+ requestAnimationFrame(checkPosition)
168
+ }
169
+
105
170
  const timer = setTimeout(() => {
106
171
  document.addEventListener("click", handleClickOutside)
107
172
  }, 0)
108
173
  document.addEventListener("keydown", handleEscape)
174
+ window.addEventListener("resize", checkPosition)
175
+ window.addEventListener("scroll", checkPosition, true)
109
176
 
110
177
  return () => {
111
178
  clearTimeout(timer)
112
179
  document.removeEventListener("click", handleClickOutside)
113
180
  document.removeEventListener("keydown", handleEscape)
181
+ window.removeEventListener("resize", checkPosition)
182
+ window.removeEventListener("scroll", checkPosition, true)
114
183
  }
115
- }, [context.open, context])
184
+ }, [context.open, context, align, sideOffset])
116
185
 
117
186
  if (!context.open) return null
187
+ if (portal && !mounted) return null
118
188
 
119
- return (
189
+ const content = (
120
190
  <div
121
- ref={ref}
191
+ ref={(node) => {
192
+ (contentRef as any).current = node
193
+ if (typeof ref === 'function') ref(node)
194
+ else if (ref) ref.current = node
195
+ }}
122
196
  className={cn(
123
- "absolute z-50 mt-2 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
124
- align === "end" ? "right-0" : align === "start" ? "left-0" : "left-1/2 -translate-x-1/2",
197
+ "z-50 min-w-[10rem] rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
198
+ "animate-in fade-in-0 zoom-in-95",
199
+ !portal && "absolute",
200
+ !portal && "mt-2",
201
+ portal && "fixed",
125
202
  className
126
203
  )}
204
+ style={{
205
+ top: portal ? position.top : undefined,
206
+ left: portal ? position.left : undefined,
207
+ ...props.style
208
+ }}
127
209
  onClick={(e) => e.stopPropagation()}
128
210
  {...props}
129
211
  >
130
212
  {children}
131
213
  </div>
132
214
  )
215
+
216
+ if (portal) {
217
+ return createPortal(content, document.body)
218
+ }
219
+
220
+ return content
133
221
  })
134
222
  PopoverContent.displayName = "PopoverContent"
135
223
 
@@ -1,4 +1,5 @@
1
1
  import * as React from "react"
2
+ import { createPortal } from "react-dom"
2
3
  import { cn } from "@/lib/utils"
3
4
 
4
5
  interface SelectContextValue {
@@ -6,6 +7,7 @@ interface SelectContextValue {
6
7
  onValueChange: (value: string) => void
7
8
  open: boolean
8
9
  setOpen: (open: boolean) => void
10
+ triggerRef: React.RefObject<HTMLButtonElement | null>
9
11
  }
10
12
 
11
13
  const SelectContext = React.createContext<SelectContextValue | null>(null)
@@ -34,12 +36,13 @@ interface SelectProps {
34
36
  function Select({ children, value: controlledValue, onValueChange, defaultValue = "" }: SelectProps) {
35
37
  const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue)
36
38
  const [open, setOpen] = React.useState(false)
39
+ const triggerRef = React.useRef<HTMLButtonElement>(null)
37
40
 
38
41
  const value = controlledValue !== undefined ? controlledValue : uncontrolledValue
39
42
  const setValue = onValueChange || setUncontrolledValue
40
43
 
41
44
  return (
42
- <SelectContext.Provider value={{ value, onValueChange: setValue, open, setOpen }}>
45
+ <SelectContext.Provider value={{ value, onValueChange: setValue, open, setOpen, triggerRef }}>
43
46
  <div className="relative">
44
47
  {children}
45
48
  </div>
@@ -54,9 +57,16 @@ const SelectTrigger = React.forwardRef<
54
57
  const context = React.useContext(SelectContext)
55
58
  if (!context) throw new Error("SelectTrigger must be used within Select")
56
59
 
60
+ // Combine refs
61
+ const combinedRef = (node: HTMLButtonElement | null) => {
62
+ (context.triggerRef as any).current = node
63
+ if (typeof ref === 'function') ref(node)
64
+ else if (ref) ref.current = node
65
+ }
66
+
57
67
  return (
58
68
  <button
59
- ref={ref}
69
+ ref={combinedRef}
60
70
  type="button"
61
71
  role="combobox"
62
72
  aria-expanded={context.open}
@@ -98,52 +108,142 @@ function SelectValue({ placeholder }: SelectValueProps) {
98
108
  )
99
109
  }
100
110
 
101
- const SelectContent = React.forwardRef<
102
- HTMLDivElement,
103
- React.HTMLAttributes<HTMLDivElement>
104
- >(({ className, children, ...props }, ref) => {
105
- const context = React.useContext(SelectContext)
106
- if (!context) throw new Error("SelectContent must be used within Select")
111
+ interface SelectContentProps extends React.HTMLAttributes<HTMLDivElement> {
112
+ sideOffset?: number
113
+ portal?: boolean
114
+ align?: "start" | "center" | "end"
115
+ side?: "top" | "bottom"
116
+ }
117
+
118
+ const SelectContent = React.forwardRef<HTMLDivElement, SelectContentProps>(
119
+ ({ className, children, sideOffset = 4, portal = true, align = "start", side = "bottom", ...props }, ref) => {
120
+ const context = React.useContext(SelectContext)
121
+ if (!context) throw new Error("SelectContent must be used within Select")
122
+ const contentRef = React.useRef<HTMLDivElement>(null)
123
+ const [position, setPosition] = React.useState({ top: 0, left: 0 })
124
+ const [mounted, setMounted] = React.useState(false)
125
+
126
+ React.useEffect(() => {
127
+ setMounted(true)
128
+ }, [])
129
+
130
+ React.useEffect(() => {
131
+ const handleClickOutside = (e: MouseEvent) => {
132
+ if (context.open) {
133
+ const target = e.target as Node
134
+ const content = contentRef.current
135
+ const trigger = context.triggerRef.current
136
+
137
+ if (content?.contains(target) || (trigger && trigger.contains(target))) {
138
+ return
139
+ }
140
+ context.setOpen(false)
141
+ }
142
+ }
143
+
144
+ const handleEscape = (e: KeyboardEvent) => {
145
+ if (e.key === "Escape" && context.open) {
146
+ context.setOpen(false)
147
+ }
148
+ }
149
+
150
+ const checkPosition = () => {
151
+ if (context.open && contentRef.current && context.triggerRef.current) {
152
+ const triggerRect = context.triggerRef.current.getBoundingClientRect()
153
+ const contentRect = contentRef.current.getBoundingClientRect()
154
+ const viewportHeight = window.innerHeight
155
+ const viewportWidth = window.innerWidth
156
+
157
+ let top = 0
158
+ let left = 0
159
+
160
+ // Vertical
161
+ const spaceBelow = viewportHeight - triggerRect.bottom
162
+ const spaceAbove = triggerRect.top
163
+ const neededHeight = contentRect.height + sideOffset
164
+ const onBottom = spaceBelow >= neededHeight || spaceBelow > spaceAbove
165
+
166
+ if (onBottom) {
167
+ top = triggerRect.bottom + sideOffset
168
+ } else {
169
+ top = triggerRect.top - contentRect.height - sideOffset
170
+ }
171
+
172
+ // Horizontal - Select usually matches trigger width or starts aligned
173
+ left = triggerRect.left
174
+
175
+ // Clamping
176
+ if (left < 4) left = 4
177
+ if (left + contentRect.width > viewportWidth - 4) {
178
+ left = viewportWidth - contentRect.width - 4
179
+ }
180
+
181
+ // Match width if it fits, or at least min-width of trigger
182
+ // For now keeping simple positioning
183
+
184
+ setPosition({ top, left })
185
+ }
186
+ }
107
187
 
108
- React.useEffect(() => {
109
- const handleClickOutside = (e: MouseEvent) => {
110
188
  if (context.open) {
111
- context.setOpen(false)
189
+ requestAnimationFrame(checkPosition)
112
190
  }
113
- }
114
191
 
115
- const handleEscape = (e: KeyboardEvent) => {
116
- if (e.key === "Escape" && context.open) {
117
- context.setOpen(false)
192
+ const timer = setTimeout(() => {
193
+ document.addEventListener("mousedown", handleClickOutside)
194
+ }, 0)
195
+ document.addEventListener("keydown", handleEscape)
196
+ window.addEventListener("resize", checkPosition)
197
+ window.addEventListener("scroll", checkPosition, true)
198
+
199
+ return () => {
200
+ clearTimeout(timer)
201
+ document.removeEventListener("mousedown", handleClickOutside)
202
+ document.removeEventListener("keydown", handleEscape)
203
+ window.removeEventListener("resize", checkPosition)
204
+ window.removeEventListener("scroll", checkPosition, true)
118
205
  }
119
- }
206
+ }, [context.open, context, sideOffset])
120
207
 
121
- document.addEventListener("mousedown", handleClickOutside)
122
- document.addEventListener("keydown", handleEscape)
208
+ if (!context.open) return null
209
+ if (portal && !mounted) return null
123
210
 
124
- return () => {
125
- document.removeEventListener("mousedown", handleClickOutside)
126
- document.removeEventListener("keydown", handleEscape)
127
- }
128
- }, [context.open, context])
211
+ const content = (
212
+ <div
213
+ ref={(node) => {
214
+ (contentRef as any).current = node
215
+ if (typeof ref === 'function') ref(node)
216
+ else if (ref) ref.current = node
217
+ }}
218
+ role="listbox"
219
+ className={cn(
220
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
221
+ "animate-in fade-in-0 zoom-in-95",
222
+ !portal && "absolute",
223
+ !portal && "mt-1",
224
+ portal && "fixed",
225
+ className
226
+ )}
227
+ style={{
228
+ top: portal ? position.top : undefined,
229
+ left: portal ? position.left : undefined,
230
+ width: context.triggerRef.current ? context.triggerRef.current.offsetWidth : undefined,
231
+ ...props.style
232
+ }}
233
+ onClick={(e) => e.stopPropagation()}
234
+ {...props}
235
+ >
236
+ {children}
237
+ </div>
238
+ )
129
239
 
130
- if (!context.open) return null
240
+ if (portal) {
241
+ return createPortal(content, document.body)
242
+ }
131
243
 
132
- return (
133
- <div
134
- ref={ref}
135
- role="listbox"
136
- className={cn(
137
- "absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
138
- className
139
- )}
140
- onClick={(e) => e.stopPropagation()}
141
- {...props}
142
- >
143
- {children}
144
- </div>
145
- )
146
- })
244
+ return content
245
+ }
246
+ )
147
247
  SelectContent.displayName = "SelectContent"
148
248
 
149
249
  interface SelectItemProps extends React.HTMLAttributes<HTMLDivElement> {
@@ -1,7 +1,9 @@
1
1
  'use client'
2
2
  import * as React from "react"
3
+ import { createPortal } from "react-dom"
3
4
  import { cva, type VariantProps } from "class-variance-authority"
4
5
  import { cn } from "@/lib/utils"
6
+ import { Slot } from "@/components/ui/slot"
5
7
 
6
8
  interface SheetContextValue {
7
9
  open: boolean
@@ -19,6 +21,18 @@ interface SheetProps {
19
21
 
20
22
  /**
21
23
  * Sheet (slide-in panel) component with smooth animations
24
+ *
25
+ * @example
26
+ * <Sheet>
27
+ * <SheetTrigger>Open</SheetTrigger>
28
+ * <SheetContent>
29
+ * <SheetHeader>
30
+ * <SheetTitle>Edit profile</SheetTitle>
31
+ * <SheetDescription>Make changes to your profile here.</SheetDescription>
32
+ * </SheetHeader>
33
+ * <div>Content</div>
34
+ * </SheetContent>
35
+ * </Sheet>
22
36
  */
23
37
  function Sheet({ children, open: controlledOpen, onOpenChange, defaultOpen = false }: SheetProps) {
24
38
  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
@@ -47,17 +61,12 @@ const SheetTrigger = React.forwardRef<HTMLButtonElement, SheetTriggerProps>(
47
61
  context.onOpenChange(true)
48
62
  }
49
63
 
50
- if (asChild && React.isValidElement(children)) {
51
- return React.cloneElement(children as React.ReactElement<any>, {
52
- onClick: handleClick,
53
- ref,
54
- })
55
- }
64
+ const Comp = asChild ? Slot : "button"
56
65
 
57
66
  return (
58
- <button ref={ref} onClick={handleClick} {...props}>
67
+ <Comp ref={ref} onClick={handleClick} {...props}>
59
68
  {children}
60
- </button>
69
+ </Comp>
61
70
  )
62
71
  }
63
72
  )
@@ -150,11 +159,18 @@ const SheetContent = React.forwardRef<HTMLDivElement, SheetContentProps>(
150
159
  }
151
160
  }, [context.open, context])
152
161
 
162
+ const [mounted, setMounted] = React.useState(false)
163
+
164
+ React.useEffect(() => {
165
+ setMounted(true)
166
+ }, [])
167
+
153
168
  if (!isVisible) return null
169
+ if (!mounted) return null
154
170
 
155
171
  const sideKey = side || "right"
156
172
 
157
- return (
173
+ return createPortal(
158
174
  <>
159
175
  {/* Overlay with fade animation */}
160
176
  <div
@@ -190,7 +206,8 @@ const SheetContent = React.forwardRef<HTMLDivElement, SheetContentProps>(
190
206
  <span className="sr-only">Close</span>
191
207
  </button>
192
208
  </div>
193
- </>
209
+ </>,
210
+ document.body
194
211
  )
195
212
  }
196
213
  )
@@ -6,6 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority"
6
6
 
7
7
  import { cn } from "@/lib/utils"
8
8
  import { Button } from "@/components/ui/button"
9
+ import { Slot } from "@/components/ui/slot"
9
10
  import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"
10
11
 
11
12
 
@@ -102,6 +103,25 @@ const SidebarProvider = React.forwardRef<
102
103
  })
103
104
  SidebarProvider.displayName = "SidebarProvider"
104
105
 
106
+
107
+ /**
108
+ * Sidebar component for application layout
109
+ *
110
+ * @example
111
+ * <SidebarProvider>
112
+ * <Sidebar>
113
+ * <SidebarHeader />
114
+ * <SidebarContent>
115
+ * <SidebarGroup />
116
+ * <SidebarMenu />
117
+ * </SidebarContent>
118
+ * <SidebarFooter />
119
+ * </Sidebar>
120
+ * <SidebarInset>
121
+ * <main>Content</main>
122
+ * </SidebarInset>
123
+ * </SidebarProvider>
124
+ */
105
125
  const Sidebar = React.forwardRef<
106
126
  HTMLDivElement,
107
127
  React.HTMLAttributes<HTMLDivElement> & {
@@ -355,25 +375,10 @@ const SidebarGroupLabel = React.forwardRef<
355
375
  HTMLDivElement,
356
376
  React.HTMLAttributes<HTMLDivElement> & { asChild?: boolean }
357
377
  >(({ className, asChild = false, ...props }, ref) => {
358
- // Only supporting div rendering for now
359
- if (asChild) {
360
- const child = React.Children.only(props.children) as React.ReactElement<any>
361
- return React.cloneElement(child, {
362
- ref,
363
- "data-sidebar": "group-label",
364
- className: cn(
365
- "duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
366
- "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
367
- className,
368
- child.props.className
369
- ),
370
- ...props,
371
- children: child.props.children,
372
- })
373
- }
378
+ const Comp = asChild ? Slot : "div"
374
379
 
375
380
  return (
376
- <div
381
+ <Comp
377
382
  ref={ref}
378
383
  data-sidebar="group-label"
379
384
  className={cn(
@@ -467,26 +472,10 @@ const SidebarMenuButton = React.forwardRef<
467
472
  }
468
473
  }
469
474
 
470
- if (asChild) {
471
- const child = React.Children.only(props.children) as React.ReactElement<any>
472
-
473
- return React.cloneElement(child, {
474
- ref,
475
- className: cn(buttonClass, child.props.className),
476
- "data-active": isActive,
477
- "data-sidebar": "menu-button",
478
- "data-size": size,
479
- ...props,
480
- onClick: (e: React.MouseEvent<HTMLButtonElement>) => {
481
- handleClick(e)
482
- child.props.onClick?.(e)
483
- },
484
- children: child.props.children
485
- })
486
- }
475
+ const Comp = asChild ? Slot : "button"
487
476
 
488
477
  return (
489
- <button
478
+ <Comp
490
479
  ref={ref}
491
480
  data-sidebar="menu-button"
492
481
  data-size={size}
@@ -504,31 +493,10 @@ const SidebarMenuAction = React.forwardRef<
504
493
  HTMLButtonElement,
505
494
  React.ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean; showOnHover?: boolean }
506
495
  >(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
507
- if (asChild) {
508
- const child = React.Children.only(props.children) as React.ReactElement<any>
509
- return React.cloneElement(child, {
510
- ref,
511
- "data-sidebar": "menu-action",
512
- className: cn(
513
- "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
514
- // Increases the hit area of the button on mobile.
515
- "after:absolute after:-inset-2 after:md:hidden",
516
- "peer-data-[size=sm]/menu-button:top-1",
517
- "peer-data-[size=default]/menu-button:top-1.5",
518
- "peer-data-[size=lg]/menu-button:top-2.5",
519
- "group-data-[collapsible=icon]:hidden",
520
- showOnHover &&
521
- "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
522
- className,
523
- child.props.className
524
- ),
525
- ...props,
526
- children: child.props.children
527
- })
528
- }
496
+ const Comp = asChild ? Slot : "button"
529
497
 
530
498
  return (
531
- <button
499
+ <Comp
532
500
  ref={ref}
533
501
  data-sidebar="menu-action"
534
502
  className={cn(
@@ -580,28 +548,10 @@ const SidebarMenuSubButton = React.forwardRef<
580
548
  isActive?: boolean
581
549
  }
582
550
  >(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
583
- if (asChild) {
584
- const child = React.Children.only(props.children) as React.ReactElement<any>
585
- return React.cloneElement(child, {
586
- ref,
587
- "data-sidebar": "menu-sub-button",
588
- "data-size": size,
589
- "data-active": isActive,
590
- className: cn(
591
- "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 group-data-[collapsible=icon]:hidden",
592
- size === "sm" && "text-xs",
593
- size === "md" && "text-sm",
594
- "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
595
- className,
596
- child.props.className
597
- ),
598
- ...props,
599
- children: child.props.children
600
- })
601
- }
551
+ const Comp = asChild ? Slot : "a"
602
552
 
603
553
  return (
604
- <a
554
+ <Comp
605
555
  ref={ref}
606
556
  data-sidebar="menu-sub-button"
607
557
  data-size={size}