'use client' import type { ComponentProps, CSSProperties, ReactElement, ReactNode } from 'react' import { Children, Fragment, isValidElement } from 'react' import { Description, Dropdown as HeroDropdown, Header, Label, Separator } from '@heroui/react' type DropdownKey = string | number export interface DropdownItemProps { children?: ReactNode className?: string color?: 'default' | 'danger' description?: ReactNode key?: DropdownKey startContent?: ReactNode textValue?: string onPress?: () => void } interface DropdownMenuProps { children?: ReactNode 'aria-label'?: string closeOnSelect?: boolean disabledKeys?: Iterable disallowEmptySelection?: boolean selectedKeys?: 'all' | Iterable selectionMode?: 'none' | 'single' | 'multiple' variant?: string onAction?: (key: DropdownKey) => void onSelectionChange?: (keys: 'all' | Set) => void } interface DropdownSectionProps { children?: ReactNode showDivider?: boolean title?: ReactNode } interface DropdownTriggerProps { children?: ReactNode 'aria-label'?: string className?: string isDisabled?: boolean style?: CSSProperties } interface DropdownProps { children?: ReactNode placement?: ComponentProps['placement'] | 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end' trigger?: 'press' | 'longPress' } export function DropdownTrigger({ children }: DropdownTriggerProps) { return children } export function DropdownMenu({ children }: DropdownMenuProps) { return children } export function DropdownItem({ children }: DropdownItemProps) { return children } export function DropdownSection({ children }: DropdownSectionProps) { return children } function itemTextValue(item: ReactElement, id: string): string { if (item.props.textValue) return item.props.textValue if (typeof item.props.children === 'string' || typeof item.props.children === 'number') return String(item.props.children) return id } function renderItem(item: ReactElement, index: number, showIndicator: boolean) { const id = normalizeElementKey(item.key, index) const label = const description = item.props.description ? {item.props.description} : null return ( {showIndicator ? : null} {item.props.startContent} {description ? (
{label} {description}
) : ( label )}
) } function normalizeElementKey(key: ReactElement['key'], index: number): string { if (key == null) return String(index) const value = String(key) return value.startsWith('.$') ? value.slice(2) : value } function renderMenuChildren(children: ReactNode, showIndicator: boolean): ReactNode { return Children.toArray(children).map((child, index) => { if (!isValidElement(child)) return null if (child.type === DropdownSection) { const section = child as ReactElement return ( {section.props.title ?
{section.props.title}
: null} {renderMenuChildren(section.props.children, showIndicator)}
{section.props.showDivider ? : null}
) } return renderItem(child as ReactElement, index, showIndicator) }) } function isButtonLikeTrigger(child: ReactElement): boolean { if (typeof child.type === 'string') return child.type === 'button' const displayName = typeof child.type === 'function' || typeof child.type === 'object' ? (child.type as { displayName?: string; name?: string }).displayName || (child.type as { name?: string }).name : undefined return displayName === 'ConsumerButton' || displayName === 'Button' } /** Button/`button` داخل MenuTrigger reliably باز نمی‌شود؛ محتوا را به Dropdown.Trigger واقعی منتقل می‌کنیم. */ function resolveTrigger(triggerElement: ReactElement): { 'aria-label'?: string className?: string content: ReactNode isDisabled?: boolean style?: CSSProperties } { const { children, className, isDisabled, style } = triggerElement.props const ariaLabel = triggerElement.props['aria-label'] const onlyChild = Children.count(children) === 1 ? Children.toArray(children)[0] : null if (isValidElement(onlyChild) && isButtonLikeTrigger(onlyChild)) { const nested = onlyChild.props as { 'aria-label'?: string children?: ReactNode className?: string disabled?: boolean style?: CSSProperties } return { 'aria-label': nested['aria-label'] ?? ariaLabel, className: nested.className ?? className, content: nested.children, isDisabled: nested.disabled ?? isDisabled, style: nested.style ?? style, } } return { 'aria-label': ariaLabel, className, content: children, isDisabled, style, } } export function Dropdown({ children, placement, trigger }: DropdownProps) { const parts = Children.toArray(children).filter(isValidElement) const triggerElement = parts.find((part) => part.type === DropdownTrigger) as ReactElement | undefined const menuElement = parts.find((part) => part.type === DropdownMenu) as ReactElement | undefined if (!triggerElement || !menuElement) return null const resolvedTrigger = resolveTrigger(triggerElement) const menuChildren = menuElement.props.children const menuProps = { 'aria-label': menuElement.props['aria-label'], disabledKeys: menuElement.props.disabledKeys, disallowEmptySelection: menuElement.props.disallowEmptySelection, selectedKeys: menuElement.props.selectedKeys, selectionMode: menuElement.props.selectionMode, onAction: menuElement.props.onAction, onSelectionChange: menuElement.props.onSelectionChange, } const showIndicator = menuProps.selectionMode === 'single' || menuProps.selectionMode === 'multiple' const normalizedPlacement = placement?.replace('-', ' ') as ComponentProps['placement'] return ( {resolvedTrigger.content} {renderMenuChildren(menuChildren, showIndicator)} ) }