UNPKG

@aristobyte-ui/dropdown

Version:

react dropdown component with trigger button, dropdownoptions, placement variants, fully typed typescript support, and composable integration with aristobyte ui button

202 lines (199 loc) 6.6 kB
"use client"; // components/Dropdown/index.tsx import * as React2 from "react"; import { AnimatePresence, motion } from "framer-motion"; // components/DropdownOption/index.tsx import * as React from "react"; import { Icons } from "@aristobyte-ui/utils"; import { jsx, jsxs } from "react/jsx-runtime"; var DropdownOption = ({ variant, children, value, selectedValues, onChange, // icon, description, disabled, style = {} }) => { const uniqueId = React.useId(); return /* @__PURE__ */ jsxs( "button", { style, disabled, className: `dropdown-option dropdown-option-variant--${variant} ${disabled ? "dropdown-option--disabled" : ""}`, onClick: onChange, children: [ /* @__PURE__ */ jsxs("div", { className: "dropdown-option__content", children: [ /* @__PURE__ */ jsx("h3", { className: "dropdown-option__title", children }), /* @__PURE__ */ jsx("p", { className: "dropdown-option__description", children: description }) ] }), /* @__PURE__ */ jsx( "div", { className: `dropdown-option__tick ${(selectedValues == null ? void 0 : selectedValues.includes(value)) ? "dropdown-option__tick--active" : ""}`, children: /* @__PURE__ */ jsx(Icons.Success, { size: 18 }) } ) ] }, uniqueId ); }; // components/Dropdown/index.tsx import { Button } from "@aristobyte-ui/button"; import { Portal } from "@aristobyte-ui/utils"; import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime"; import { createElement } from "react"; var Dropdown = ({ children, value, onChange, appearance = "outline", variant = "default", placeholder = "Select", choice = "single", className = "", initiallyOpened = false, disabled = false, button = {}, style = {} }) => { const [isOpened, setIsOpened] = React2.useState(initiallyOpened); const [selected, setSelected] = React2.useState( value ? [value] : [] ); const [position, setPosition] = React2.useState({ top: 0, left: 0, width: 0 }); const [dropdownHeight, setDropdownHeight] = React2.useState(0); const [buttonHeight, setButtonHeight] = React2.useState(0); const buttonRef = React2.useRef(null); const boxRef = React2.useRef(null); const uniqueId = React2.useId(); React2.useLayoutEffect(() => { if (!isOpened) { return; } if (boxRef.current) { setDropdownHeight(boxRef.current.getBoundingClientRect().height); } if (buttonRef.current) { setButtonHeight(buttonRef.current.getBoundingClientRect().height); } }, [isOpened]); const options = React2.Children.toArray(children).filter( (child) => React2.isValidElement(child) && child.type === DropdownOption ); const isValidValue = () => { return !!options.find(({ props }) => props.value === value); }; const handleChange = (currentRadioValue) => { onChange == null ? void 0 : onChange(currentRadioValue); if (!choice) { setSelected([currentRadioValue]); setIsOpened(false); return; } if (choice === "single") { setSelected([currentRadioValue]); } if (choice === "multiple") { setSelected( (prev) => prev.includes(currentRadioValue) ? prev.filter((v) => v !== currentRadioValue) : [...prev, currentRadioValue] ); } }; const handleToggle = (e) => { var _a; if (disabled) return; const rect = (_a = buttonRef.current) == null ? void 0 : _a.getBoundingClientRect(); if (!rect) return; const spaceBelow = window.innerHeight - rect.bottom; const spaceAbove = rect.top; const shouldOpenUpwards = dropdownHeight > 0 && spaceBelow < dropdownHeight && spaceAbove > dropdownHeight; const finalPosition = { top: shouldOpenUpwards ? rect.top + window.scrollY - dropdownHeight - buttonHeight / 2 : rect.top + window.scrollY + buttonHeight + 6, left: rect.left + window.scrollX, width: rect.width }; setPosition(finalPosition); if (button == null ? void 0 : button.onClick) button.onClick(e); setIsOpened((prev) => !prev); }; if (!isValidValue()) { throw new Error( 'The "value" prop did not match with any of the DropdownOption "value" prop' ); } return /* @__PURE__ */ jsxs2(Fragment, { children: [ /* @__PURE__ */ jsx2("div", { className: `dropdown ${className}`, children: /* @__PURE__ */ jsx2( Button, { onClick: handleToggle, className: `${"dropdown__button"} ${(button == null ? void 0 : button.className) || ""}`, appearance: (button == null ? void 0 : button.appearance) || appearance, variant: (button == null ? void 0 : button.variant) || variant, disabled: (button == null ? void 0 : button.disabled) || disabled, ...{ ref: buttonRef }, children: placeholder } ) }), /* @__PURE__ */ jsx2(Portal, { children: /* @__PURE__ */ jsx2(AnimatePresence, { children: isOpened && /* @__PURE__ */ jsxs2( "div", { className: `dropdown__box dropdown__box-variant--${variant}`, style, children: [ /* @__PURE__ */ jsx2( motion.div, { className: "dropdown__box-overlay", initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.3, ease: "easeIn" }, onClick: () => setIsOpened(false) } ), /* @__PURE__ */ jsx2( motion.div, { ref: boxRef, className: "dropdown__box-options", initial: { opacity: 0, y: 20, scale: 0.95 }, animate: { opacity: 1, y: 0, scale: 1 }, exit: { opacity: 0, y: 20, scale: 0.95 }, transition: { duration: 0.2, ease: "easeIn" }, style: { top: position.top, left: position.left, width: position.width }, children: options.map(({ props }) => /* @__PURE__ */ createElement( DropdownOption, { ...props, variant, appearance, key: `${props.value}-${uniqueId}`, selectedValues: selected, onChange: () => handleChange(props.value) } )) } ) ] } ) }) }) ] }); }; export { Dropdown, DropdownOption }; //# sourceMappingURL=index.mjs.map