mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
150 lines
3.7 KiB
TypeScript
150 lines
3.7 KiB
TypeScript
import {Ref, useId} from "react";
|
|
import {HTMLNextUIProps, PropGetter} from "@nextui-org/system";
|
|
import {useMenuTriggerState} from "@react-stately/menu";
|
|
import {MenuTriggerType} from "@react-types/menu";
|
|
import {useMenuTrigger} from "@react-aria/menu";
|
|
import {dropdown} from "@nextui-org/theme";
|
|
import {clsx} from "@nextui-org/shared-utils";
|
|
import {ReactRef, mergeRefs} from "@nextui-org/react-utils";
|
|
import {PopoverProps} from "@nextui-org/popover";
|
|
import {useMemo, useRef} from "react";
|
|
import {mergeProps} from "@react-aria/utils";
|
|
|
|
export interface UseDropdownProps
|
|
extends HTMLNextUIProps<"div", Omit<PopoverProps, "children" | "color" | "variant">> {
|
|
/**
|
|
* Type of overlay that is opened by the trigger.
|
|
*/
|
|
type?: "menu" | "listbox";
|
|
/**
|
|
* Ref to the DOM node.
|
|
*/
|
|
ref?: ReactRef<HTMLElement | null>;
|
|
/**
|
|
* How the menu is triggered.
|
|
* @default 'press'
|
|
*/
|
|
trigger?: MenuTriggerType;
|
|
/**
|
|
* Whether menu trigger is disabled.
|
|
* @default false
|
|
*/
|
|
isDisabled?: boolean;
|
|
/**
|
|
* Whether the Menu closes when a selection is made.
|
|
* @default true
|
|
*/
|
|
closeOnSelect?: boolean;
|
|
}
|
|
|
|
export function useDropdown(props: UseDropdownProps) {
|
|
const {
|
|
as,
|
|
triggerRef: triggerRefProp,
|
|
isOpen,
|
|
defaultOpen,
|
|
onOpenChange,
|
|
type = "menu",
|
|
trigger = "press",
|
|
placement = "bottom",
|
|
isDisabled = false,
|
|
closeOnSelect = true,
|
|
shouldBlockScroll = true,
|
|
classNames: classNamesProp,
|
|
disableAnimation = false,
|
|
onClose,
|
|
className,
|
|
...otherProps
|
|
} = props;
|
|
|
|
const Component = as || "div";
|
|
|
|
const triggerRef = useRef<HTMLElement>(null);
|
|
const menuTriggerRef = triggerRefProp || triggerRef;
|
|
const menuRef = useRef<HTMLUListElement>(null);
|
|
const popoverRef = useRef<HTMLDivElement>(null);
|
|
|
|
const triggerId = useId();
|
|
const menuId = useId();
|
|
|
|
const state = useMenuTriggerState({
|
|
trigger,
|
|
isOpen,
|
|
defaultOpen,
|
|
onOpenChange: (isOpen) => {
|
|
onOpenChange?.(isOpen);
|
|
if (!isOpen) {
|
|
onClose?.();
|
|
}
|
|
},
|
|
});
|
|
|
|
const {menuTriggerProps, menuProps} = useMenuTrigger(
|
|
{type, trigger, isDisabled},
|
|
state,
|
|
menuTriggerRef,
|
|
);
|
|
|
|
const classNames = useMemo(
|
|
() =>
|
|
dropdown({
|
|
className,
|
|
}),
|
|
[className],
|
|
);
|
|
|
|
const getPopoverProps: PropGetter = (props = {}) => ({
|
|
state,
|
|
placement,
|
|
ref: popoverRef,
|
|
disableAnimation,
|
|
shouldBlockScroll,
|
|
scrollRef: menuRef,
|
|
triggerRef: menuTriggerRef,
|
|
...mergeProps(otherProps, props),
|
|
classNames: {
|
|
...classNamesProp,
|
|
...props.classNames,
|
|
base: clsx(classNames, classNamesProp?.base, props.className),
|
|
arrow: clsx("border border-default-100", classNamesProp?.arrow),
|
|
},
|
|
});
|
|
|
|
const getMenuTriggerProps: PropGetter = (
|
|
props = {},
|
|
_ref: Ref<any> | null | undefined = null,
|
|
) => {
|
|
// These props are not needed for the menu trigger since it is handled by the popover trigger.
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const {onKeyDown, onPress, onPressStart, ...otherMenuTriggerProps} = menuTriggerProps;
|
|
|
|
return {
|
|
...mergeProps(otherMenuTriggerProps, props),
|
|
id: triggerId,
|
|
ref: mergeRefs(_ref, triggerRef),
|
|
"aria-controls": menuId,
|
|
};
|
|
};
|
|
|
|
const getMenuProps: PropGetter = (props = {}, _ref: Ref<any> | null | undefined = null) => ({
|
|
...mergeProps(menuProps, props),
|
|
id: menuId,
|
|
ref: mergeRefs(_ref, menuRef),
|
|
"aria-labelledby": triggerId,
|
|
});
|
|
|
|
return {
|
|
Component,
|
|
classNames,
|
|
closeOnSelect,
|
|
onClose: state.close,
|
|
autoFocus: state.focusStrategy || true,
|
|
disableAnimation,
|
|
getPopoverProps,
|
|
getMenuTriggerProps,
|
|
getMenuProps,
|
|
};
|
|
}
|
|
|
|
export type UseDropdownReturn = ReturnType<typeof useDropdown>;
|