Update shopify collection
This commit is contained in:
@@ -1,474 +1,161 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { createPortal } from "react-dom"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface DropdownMenuContextValue {
|
||||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
triggerRef: React.RefObject<HTMLButtonElement | null>
|
||||
}
|
||||
|
||||
const DropdownMenuContext = React.createContext<DropdownMenuContextValue | null>(null)
|
||||
|
||||
function useDropdownMenu() {
|
||||
const context = React.useContext(DropdownMenuContext)
|
||||
if (!context) {
|
||||
throw new Error("useDropdownMenu must be used within a DropdownMenu")
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
interface DropdownMenuProps {
|
||||
children: React.ReactNode
|
||||
open?: boolean
|
||||
defaultOpen?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
}
|
||||
|
||||
function DropdownMenu({
|
||||
children,
|
||||
open: controlledOpen,
|
||||
defaultOpen = false,
|
||||
onOpenChange,
|
||||
}: DropdownMenuProps) {
|
||||
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
|
||||
const triggerRef = React.useRef<HTMLButtonElement>(null)
|
||||
|
||||
const isControlled = controlledOpen !== undefined
|
||||
const open = isControlled ? controlledOpen : uncontrolledOpen
|
||||
|
||||
const setOpen = React.useCallback((value: boolean) => {
|
||||
if (!isControlled) {
|
||||
setUncontrolledOpen(value)
|
||||
}
|
||||
onOpenChange?.(value)
|
||||
}, [isControlled, onOpenChange])
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
||||
}
|
||||
|
||||
function DropdownMenuPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||
return (
|
||||
<DropdownMenuContext.Provider value={{ open, setOpen, triggerRef }}>
|
||||
{children}
|
||||
</DropdownMenuContext.Provider>
|
||||
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuTrigger({
|
||||
className,
|
||||
children,
|
||||
asChild,
|
||||
...props
|
||||
}: React.ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean }) {
|
||||
const { open, setOpen, triggerRef } = useDropdownMenu()
|
||||
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault()
|
||||
setOpen(!open)
|
||||
props.onClick?.(e)
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
|
||||
if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
|
||||
e.preventDefault()
|
||||
setOpen(true)
|
||||
}
|
||||
props.onKeyDown?.(e)
|
||||
}
|
||||
|
||||
if (asChild && React.isValidElement(children)) {
|
||||
return React.cloneElement(children as React.ReactElement<any>, {
|
||||
ref: triggerRef,
|
||||
onClick: handleClick,
|
||||
onKeyDown: handleKeyDown,
|
||||
"aria-expanded": open,
|
||||
"aria-haspopup": "menu",
|
||||
"data-state": open ? "open" : "closed",
|
||||
})
|
||||
}
|
||||
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
||||
return (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
<DropdownMenuPrimitive.Trigger
|
||||
data-slot="dropdown-menu-trigger"
|
||||
aria-expanded={open}
|
||||
aria-haspopup="menu"
|
||||
data-state={open ? "open" : "closed"}
|
||||
className={cn("relative", className)}
|
||||
onClick={handleClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuPortal({ children }: { children: React.ReactNode }) {
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
interface DropdownMenuContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
sideOffset?: number
|
||||
align?: "start" | "center" | "end"
|
||||
side?: "top" | "right" | "bottom" | "left"
|
||||
}
|
||||
|
||||
function DropdownMenuContent({
|
||||
className,
|
||||
sideOffset = 4,
|
||||
align = "start",
|
||||
side = "bottom",
|
||||
children,
|
||||
...props
|
||||
}: DropdownMenuContentProps) {
|
||||
const { open, setOpen, triggerRef } = useDropdownMenu()
|
||||
const contentRef = React.useRef<HTMLDivElement>(null)
|
||||
const [position, setPosition] = React.useState({ top: 0, left: 0 })
|
||||
const [mounted, setMounted] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (!open || !triggerRef.current || !contentRef.current) return
|
||||
|
||||
const trigger = triggerRef.current.getBoundingClientRect()
|
||||
const content = contentRef.current.getBoundingClientRect()
|
||||
|
||||
let top = 0
|
||||
let left = 0
|
||||
|
||||
// Calculate position based on side
|
||||
switch (side) {
|
||||
case "top":
|
||||
top = trigger.top - content.height - sideOffset
|
||||
break
|
||||
case "bottom":
|
||||
top = trigger.bottom + sideOffset
|
||||
break
|
||||
case "left":
|
||||
left = trigger.left - content.width - sideOffset
|
||||
top = trigger.top
|
||||
break
|
||||
case "right":
|
||||
left = trigger.right + sideOffset
|
||||
top = trigger.top
|
||||
break
|
||||
}
|
||||
|
||||
// Calculate alignment for top/bottom
|
||||
if (side === "top" || side === "bottom") {
|
||||
switch (align) {
|
||||
case "start":
|
||||
left = trigger.left
|
||||
break
|
||||
case "center":
|
||||
left = trigger.left + (trigger.width - content.width) / 2
|
||||
break
|
||||
case "end":
|
||||
left = trigger.right - content.width
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate alignment for left/right
|
||||
if (side === "left" || side === "right") {
|
||||
switch (align) {
|
||||
case "start":
|
||||
top = trigger.top
|
||||
break
|
||||
case "center":
|
||||
top = trigger.top + (trigger.height - content.height) / 2
|
||||
break
|
||||
case "end":
|
||||
top = trigger.bottom - content.height
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
setPosition({ top, left })
|
||||
}, [open, side, align, sideOffset, triggerRef])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!open) return
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const target = event.target as Node
|
||||
if (
|
||||
contentRef.current &&
|
||||
!contentRef.current.contains(target) &&
|
||||
triggerRef.current &&
|
||||
!triggerRef.current.contains(target)
|
||||
) {
|
||||
setOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleEscape = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") {
|
||||
setOpen(false)
|
||||
triggerRef.current?.focus()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside)
|
||||
document.addEventListener("keydown", handleEscape)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside)
|
||||
document.removeEventListener("keydown", handleEscape)
|
||||
}
|
||||
}, [open, setOpen, triggerRef])
|
||||
|
||||
if (!open || !mounted) return null
|
||||
|
||||
const slideClasses = {
|
||||
top: "slide-in-from-bottom-2",
|
||||
bottom: "slide-in-from-top-2",
|
||||
left: "slide-in-from-right-2",
|
||||
right: "slide-in-from-left-2",
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
ref={contentRef}
|
||||
data-slot="dropdown-menu-content"
|
||||
data-state={open ? "open" : "closed"}
|
||||
role="menu"
|
||||
className={cn(
|
||||
"fixed z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
||||
"animate-in fade-in-0 zoom-in-95",
|
||||
slideClasses[side],
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
top: position.top,
|
||||
left: position.left,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>,
|
||||
document.body
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuGroup({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dropdown-menu-group"
|
||||
role="group"
|
||||
className={className}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
interface DropdownMenuItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
inset?: boolean
|
||||
variant?: "default" | "destructive"
|
||||
asChild?: boolean
|
||||
function DropdownMenuContent({
|
||||
className,
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
data-slot="dropdown-menu-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuItem({
|
||||
className,
|
||||
inset,
|
||||
variant = "default",
|
||||
children,
|
||||
asChild,
|
||||
...props
|
||||
}: DropdownMenuItemProps) {
|
||||
const { setOpen } = useDropdownMenu()
|
||||
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
props.onClick?.(e)
|
||||
if (!e.defaultPrevented) {
|
||||
setOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault()
|
||||
handleClick(e as unknown as React.MouseEvent<HTMLButtonElement>)
|
||||
}
|
||||
props.onKeyDown?.(e)
|
||||
}
|
||||
|
||||
const itemClasses = cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none",
|
||||
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"[&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
variant === "destructive" && "text-destructive focus:bg-destructive/10 focus:text-destructive dark:focus:bg-destructive/20",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)
|
||||
|
||||
if (asChild && React.isValidElement(children)) {
|
||||
return React.cloneElement(children as React.ReactElement<any>, {
|
||||
className: itemClasses,
|
||||
role: "menuitem",
|
||||
tabIndex: -1,
|
||||
onClick: handleClick,
|
||||
onKeyDown: handleKeyDown,
|
||||
})
|
||||
}
|
||||
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
variant?: "default" | "destructive"
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
<DropdownMenuPrimitive.Item
|
||||
data-slot="dropdown-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
role="menuitem"
|
||||
tabIndex={-1}
|
||||
className={itemClasses}
|
||||
onClick={handleClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
interface DropdownMenuCheckboxItemProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "checked"> {
|
||||
checked?: boolean
|
||||
onCheckedChange?: (checked: boolean) => void
|
||||
}
|
||||
|
||||
function DropdownMenuCheckboxItem({
|
||||
className,
|
||||
children,
|
||||
checked,
|
||||
onCheckedChange,
|
||||
...props
|
||||
}: DropdownMenuCheckboxItemProps) {
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault()
|
||||
onCheckedChange?.(!checked)
|
||||
props.onClick?.(e)
|
||||
}
|
||||
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
role="menuitemcheckbox"
|
||||
aria-checked={checked}
|
||||
tabIndex={-1}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none",
|
||||
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
onClick={handleClick}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{checked && <CheckIcon className="size-4" />}
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</button>
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
)
|
||||
}
|
||||
|
||||
interface DropdownMenuRadioGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
value?: string
|
||||
onValueChange?: (value: string) => void
|
||||
}
|
||||
|
||||
const RadioGroupContext = React.createContext<{
|
||||
value?: string
|
||||
onValueChange?: (value: string) => void
|
||||
} | null>(null)
|
||||
|
||||
function DropdownMenuRadioGroup({
|
||||
value,
|
||||
onValueChange,
|
||||
children,
|
||||
...props
|
||||
}: DropdownMenuRadioGroupProps) {
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
||||
return (
|
||||
<RadioGroupContext.Provider value={{ value, onValueChange }}>
|
||||
<div
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
role="group"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</RadioGroupContext.Provider>
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
interface DropdownMenuRadioItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
value: string
|
||||
}
|
||||
|
||||
function DropdownMenuRadioItem({
|
||||
className,
|
||||
children,
|
||||
value,
|
||||
...props
|
||||
}: DropdownMenuRadioItemProps) {
|
||||
const context = React.useContext(RadioGroupContext)
|
||||
const checked = context?.value === value
|
||||
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault()
|
||||
context?.onValueChange?.(value)
|
||||
props.onClick?.(e)
|
||||
}
|
||||
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
role="menuitemradio"
|
||||
aria-checked={checked}
|
||||
tabIndex={-1}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none",
|
||||
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
onClick={handleClick}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{checked && <CircleIcon className="size-2 fill-current" />}
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</button>
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
)
|
||||
}
|
||||
|
||||
interface DropdownMenuLabelProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
inset?: boolean
|
||||
}
|
||||
|
||||
function DropdownMenuLabel({
|
||||
className,
|
||||
inset,
|
||||
...props
|
||||
}: DropdownMenuLabelProps) {
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
<DropdownMenuPrimitive.Label
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-medium",
|
||||
inset && "pl-8",
|
||||
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -479,11 +166,10 @@ function DropdownMenuLabel({
|
||||
function DropdownMenuSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
||||
return (
|
||||
<div
|
||||
<DropdownMenuPrimitive.Separator
|
||||
data-slot="dropdown-menu-separator"
|
||||
role="separator"
|
||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -493,7 +179,7 @@ function DropdownMenuSeparator({
|
||||
function DropdownMenuShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) {
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
@@ -506,27 +192,10 @@ function DropdownMenuShortcut({
|
||||
)
|
||||
}
|
||||
|
||||
interface SubMenuContextValue {
|
||||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
}
|
||||
|
||||
const SubMenuContext = React.createContext<SubMenuContextValue | null>(null)
|
||||
|
||||
function DropdownMenuSub({ children }: { children: React.ReactNode }) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
return (
|
||||
<SubMenuContext.Provider value={{ open, setOpen }}>
|
||||
<div data-slot="dropdown-menu-sub" className="relative">
|
||||
{children}
|
||||
</div>
|
||||
</SubMenuContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
interface DropdownMenuSubTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
inset?: boolean
|
||||
function DropdownMenuSub({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
||||
}
|
||||
|
||||
function DropdownMenuSubTrigger({
|
||||
@@ -534,64 +203,36 @@ function DropdownMenuSubTrigger({
|
||||
inset,
|
||||
children,
|
||||
...props
|
||||
}: DropdownMenuSubTriggerProps) {
|
||||
const context = React.useContext(SubMenuContext)
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
context?.setOpen(true)
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
context?.setOpen(false)
|
||||
}
|
||||
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
data-state={context?.open ? "open" : "closed"}
|
||||
role="menuitem"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={context?.open}
|
||||
tabIndex={-1}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
||||
"[&_svg:not([class*='text-'])]:text-muted-foreground flex w-full cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none",
|
||||
inset && "pl-8",
|
||||
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto size-4" />
|
||||
</button>
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSubContent({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const context = React.useContext(SubMenuContext)
|
||||
|
||||
if (!context?.open) return null
|
||||
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
||||
return (
|
||||
<div
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
data-state={context.open ? "open" : "closed"}
|
||||
role="menu"
|
||||
className={cn(
|
||||
"absolute left-full top-0 z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg",
|
||||
"animate-in fade-in-0 zoom-in-95 slide-in-from-left-2",
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||
className
|
||||
)}
|
||||
onMouseEnter={() => context.setOpen(true)}
|
||||
onMouseLeave={() => context.setOpen(false)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user