UNPKG

seti-ramesesv1

Version:

Reusable components and context for Next.js apps

60 lines (57 loc) 3.94 kB
import { jsxs, jsx } from 'react/jsx-runtime'; import { clsx } from '../../node_modules/clsx/dist/clsx.js'; import { useState, useRef, useEffect, useLayoutEffect } from 'react'; import styles from '../../styles/Select.module.css.js'; const getTextWidth = (text, font = "16px Arial") => { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); if (!context) return 0; context.font = font; return context.measureText(text).width; }; function Select({ optionKey = "id", label, disabled = false, size = "md", value, defaultValue, onChange, options, className, fullWidth = false, readOnly = false, helperText, }) { const [isOpen, setIsOpen] = useState(false); const [isFocused, setIsFocused] = useState(false); const [internalValue, setInternalValue] = useState(value || defaultValue); const [menuWidth, setMenuWidth] = useState(160); const triggerRef = useRef(null); const wrapperRef = useRef(null); const selected = options.find((opt) => opt.value === (value ?? internalValue)); const selectedLabel = selected?.title; const hasValue = !!selectedLabel || isOpen; const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1); const handleSelect = (opt) => { if (!opt.value) return; setInternalValue(opt.value); onChange?.(opt.value); setIsOpen(false); }; // Close dropdown on outside click useEffect(() => { const handleClickOutside = (event) => { if (wrapperRef.current && !wrapperRef.current.contains(event.target)) { setIsOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); // Dynamically calculate dropdown width based on longest label useLayoutEffect(() => { const font = "16px Arial"; const longest = Math.max(...options.map((opt) => getTextWidth(opt.title, font))); const padded = Math.ceil(longest + 56); // account for padding and icon setMenuWidth(Math.min(padded, 360)); // max width 360px }, [options]); const labelClass = clsx(styles.labelBase, styles[`labelBase${capitalize(size)}`], styles[`label${capitalize(size)}`], hasValue && styles.labelFloat, hasValue && styles[`labelFloat${capitalize(size)}`], isFocused && styles.labelFocused); return (jsxs("div", { ref: wrapperRef, className: clsx(styles.selectWrapper, className), style: !fullWidth ? { width: menuWidth } : undefined, children: [jsxs("button", { ref: triggerRef, disabled: disabled, onClick: () => { if (!disabled && !readOnly) setIsOpen((prev) => !prev); }, onFocus: () => setIsFocused(true), onBlur: () => setIsFocused(false), className: clsx(styles.selectButton, !helperText && styles.withHelperText, styles[`select${capitalize(size)}`], disabled && styles.selectDisabled, !disabled && !readOnly && isFocused && styles.selectFocused, readOnly && isFocused && styles.selectReadOnlyFocused), children: [jsx("span", { className: "truncate flex-1", title: selectedLabel, children: selectedLabel || " " }), jsx("span", { className: "shrink-0 text-gray-500", children: "\u25BE" })] }), label && jsx("label", { className: labelClass, children: label }), isOpen && (jsx("div", { className: styles.dropdown, style: { width: fullWidth ? "100%" : menuWidth }, children: options.map((opt) => (jsx("div", { onClick: () => handleSelect(opt), className: clsx(styles.dropdownItem, { [styles.selected]: (value ?? internalValue) === opt.value, }), children: opt.title }, opt[optionKey] ?? opt.value))) })), helperText && jsx("p", { className: styles.helperText, children: helperText })] })); } export { Select, Select as default }; //# sourceMappingURL=Select.js.map