@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
JavaScript
"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