@aristobyte-ui/dropdown
Version:
react dropdown component with trigger button, dropdownoptions, placement variants, fully typed typescript support, and composable integration with aristobyte ui button
1 lines • 11.8 kB
Source Map (JSON)
{"version":3,"sources":["../components/Dropdown/index.tsx","../components/DropdownOption/index.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\n\nimport { DropdownOption, type IDropdownOption } from \"../DropdownOption\";\nimport { Button, type IButton } from \"@aristobyte-ui/button\";\nimport { Portal } from \"@aristobyte-ui/utils\";\n\ntype PositionType = {\n top: 0;\n left: 0;\n width: 0;\n};\n\nexport interface IDropdown {\n children:\n | React.ReactElement<IDropdownOption>\n | React.ReactElement<IDropdownOption>[];\n value: string;\n button?: Omit<IButton, \"children\" | \"dangerouslySetInnerHTML\">;\n variant?:\n | \"default\"\n | \"primary\"\n | \"secondary\"\n | \"success\"\n | \"error\"\n | \"warning\";\n appearance?:\n | \"solid\"\n | \"outline\"\n | \"outline-dashed\"\n | \"no-outline\"\n | \"glowing\";\n onChange?: (newValue: string) => void;\n initiallyOpened?: boolean;\n choice?: \"multiple\" | \"single\";\n placeholder?: string;\n disabled?: boolean;\n className?: string;\n style?: React.CSSProperties;\n}\n\nexport const Dropdown: React.FC<IDropdown> = ({\n children,\n value,\n onChange,\n appearance = \"outline\",\n variant = \"default\",\n placeholder = \"Select\",\n choice = \"single\",\n className = \"\",\n initiallyOpened = false,\n disabled = false,\n button = {},\n style = {},\n}) => {\n const [isOpened, setIsOpened] = React.useState<boolean>(initiallyOpened);\n const [selected, setSelected] = React.useState<string[]>(\n value ? [value] : []\n );\n const [position, setPosition] = React.useState<PositionType>({\n top: 0,\n left: 0,\n width: 0,\n });\n const [dropdownHeight, setDropdownHeight] = React.useState(0);\n const [buttonHeight, setButtonHeight] = React.useState(0);\n const buttonRef = React.useRef<HTMLButtonElement>(null);\n const boxRef = React.useRef<HTMLDivElement>(null);\n const uniqueId = React.useId();\n\n React.useLayoutEffect(() => {\n if (!isOpened) {\n return;\n }\n\n if (boxRef.current) {\n setDropdownHeight(boxRef.current.getBoundingClientRect().height);\n }\n\n if (buttonRef.current) {\n setButtonHeight(buttonRef.current.getBoundingClientRect().height);\n }\n }, [isOpened]);\n\n const options = React.Children.toArray(children).filter(\n (child): child is React.ReactElement<IDropdownOption> =>\n React.isValidElement(child) && child.type === DropdownOption\n );\n\n const isValidValue = () => {\n return !!options.find(({ props }) => props.value === value);\n };\n\n const handleChange = (currentRadioValue: string) => {\n onChange?.(currentRadioValue);\n if (!choice) {\n setSelected([currentRadioValue]);\n setIsOpened(false);\n return;\n }\n\n if (choice === \"single\") {\n setSelected([currentRadioValue]);\n }\n\n if (choice === \"multiple\") {\n setSelected((prev) =>\n prev.includes(currentRadioValue)\n ? prev.filter((v) => v !== currentRadioValue)\n : [...prev, currentRadioValue]\n );\n }\n };\n\n const handleToggle = (\n e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement, MouseEvent>\n ) => {\n if (disabled) return;\n\n const rect = buttonRef.current?.getBoundingClientRect();\n if (!rect) return;\n\n const spaceBelow = window.innerHeight - rect.bottom;\n const spaceAbove = rect.top;\n\n const shouldOpenUpwards =\n dropdownHeight > 0 &&\n spaceBelow < dropdownHeight &&\n spaceAbove > dropdownHeight;\n\n const finalPosition = {\n top: shouldOpenUpwards\n ? rect.top + window.scrollY - dropdownHeight - buttonHeight / 2\n : rect.top + window.scrollY + buttonHeight + 6,\n left: rect.left + window.scrollX,\n width: rect.width,\n } as PositionType;\n\n setPosition(finalPosition);\n\n if (button?.onClick) button.onClick(e);\n\n setIsOpened((prev) => !prev);\n };\n\n if (!isValidValue()) {\n throw new Error(\n 'The \"value\" prop did not match with any of the DropdownOption \"value\" prop'\n );\n }\n\n return (\n <>\n <div className={`dropdown ${className}`}>\n <Button\n onClick={handleToggle}\n className={`${\"dropdown__button\"} ${button?.className || \"\"}`}\n appearance={button?.appearance || appearance}\n variant={button?.variant || variant}\n disabled={button?.disabled || disabled}\n {...{ ref: buttonRef }}\n >\n {placeholder}\n </Button>\n </div>\n\n <Portal>\n <AnimatePresence>\n {isOpened && (\n <div\n className={`dropdown__box dropdown__box-variant--${variant}`}\n style={style}\n >\n <motion.div\n className={\"dropdown__box-overlay\"}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.3, ease: \"easeIn\" }}\n onClick={() => setIsOpened(false)}\n />\n <motion.div\n ref={boxRef}\n className={\"dropdown__box-options\"}\n initial={{ opacity: 0, y: 20, scale: 0.95 }}\n animate={{ opacity: 1, y: 0, scale: 1 }}\n exit={{ opacity: 0, y: 20, scale: 0.95 }}\n transition={{ duration: 0.2, ease: \"easeIn\" }}\n style={{\n top: position.top,\n left: position.left,\n width: position.width,\n }}\n >\n {options.map(({ props }) => (\n <DropdownOption\n {...props}\n variant={variant}\n appearance={appearance}\n key={`${props.value}-${uniqueId}`}\n selectedValues={selected}\n onChange={() => handleChange(props.value)}\n />\n ))}\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n </Portal>\n </>\n );\n};\n","import * as React from \"react\";\n\nimport { Icons } from \"@aristobyte-ui/utils\";\n\nexport interface IDropdownOption {\n variant?:\n | \"default\"\n | \"primary\"\n | \"secondary\"\n | \"success\"\n | \"error\"\n | \"warning\";\n appearance?:\n | \"solid\"\n | \"outline\"\n | \"outline-dashed\"\n | \"no-outline\"\n | \"glowing\";\n children: string;\n value: string;\n onChange?: () => void;\n selectedValues?: string[];\n description?: string;\n icon?: string;\n disabled?: boolean;\n choice?: \"multiple\" | \"single\";\n style?: React.CSSProperties;\n}\n\nexport const DropdownOption: React.FC<IDropdownOption> = ({\n variant,\n children,\n value,\n selectedValues,\n onChange,\n // icon,\n description,\n disabled,\n style = {},\n}) => {\n const uniqueId = React.useId();\n return (\n <button\n style={style}\n key={uniqueId}\n disabled={disabled}\n className={`dropdown-option dropdown-option-variant--${variant} ${disabled ? \"dropdown-option--disabled\" : \"\"}`}\n onClick={onChange}\n >\n <div className={\"dropdown-option__content\"}>\n <h3 className={\"dropdown-option__title\"}>{children}</h3>\n <p className={\"dropdown-option__description\"}>{description}</p>\n </div>\n <div\n className={`dropdown-option__tick ${selectedValues?.includes(value) ? \"dropdown-option__tick--active\" : \"\"}`}\n >\n <Icons.Success size={18} />\n </div>\n </button>\n );\n};\n"],"mappings":"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","names":["React","jsx","jsxs"]}