@codinglane/dropdown
Version:
An easy-to-use react dropdown
186 lines (185 loc) • 10.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Dropdown = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = __importDefault(require("react"));
const Icons = __importStar(require("react-feather"));
require("./Dropdown.css");
const Components = __importStar(require("../components"));
const Contracts = __importStar(require("../contracts"));
/**
* @param id
* The current id of the dropdown.
* @param value
* The current chosen value for the dropdown, typeof string.
* The value is not getting managed inside the component. You have to provide a function to manage the current chosen value.
* @param searchable
* Set this value to true, if you want to have a dropdown with an input field to search for a specific option
* @param className
* Set the classname of the dropdown, if you want to set your custom style.
* @param closeOnSelect
* Set this value to true, if you want that the options of the dropdown are getting closed as soon as you choose an option.
* @param fields
* These are the possible options for the dropdown. If you want to have grouped dropdown options, set the type of your fields to GroupedDropdownOptions
* and set the group tag for all of the fields.
* @param placeholder
* The placeholder for the dropdown. When the placeholder is set and the value is undefined or not assignable to a field, the placeholder is getting showed.
* @param onChange
* This is the function to manage the current chosen value for the dropdown.
* @param onFocus
* This function is getting called on focus of the options menu.
* @param onBlur
* This function is getting called on blur of the options menu.
* @param onFavorizeOption
* The on favorize option is getting called as soon as the favorize icon in the option menu is getting clicked.
* This dropdown do not manage the favorites on its own. You have to manage the favorites, to see changes in the component.
* @param favoriteLabels
* The labels for the favorite and non favorite group.
* @param style
* Custom stylesheet for the dropdown. It is possible to set the bg color, the color (for text & border), the font size and the font family.
* @param data-testid
* For testing purpose.
* @returns {JSX.Element}
*/
const Dropdown = (_a) => {
var _b;
var { id, fields, value, searchable = false, className, closeOnSelect = false, placeholder, onChange, onBlur, onFocus, onFavorizeOption, style } = _a, props = __rest(_a, ["id", "fields", "value", "searchable", "className", "closeOnSelect", "placeholder", "onChange", "onBlur", "onFocus", "onFavorizeOption", "style"]);
const dropdown = react_1.default.useRef();
const setDropdown = (ref) => (dropdown.current = ref);
const input = react_1.default.useRef();
const setInput = (ref) => (input.current = ref);
const menu = react_1.default.useRef();
const setMenu = (ref) => (menu.current = ref);
const textContainer = react_1.default.useRef();
const setTextContainer = (ref) => (textContainer.current = ref);
const text = react_1.default.useRef();
const setText = (ref) => (text.current = ref);
const [currentVisibleOptions, setCurrentVisibleOptions] = react_1.default.useState([]);
const grouped = fields.some((fld) => fld.group !== undefined);
const favorize = fields.some((fld) => fld.favorite !== undefined);
const [active, setActive] = react_1.default.useState(false);
const [search, setSearch] = react_1.default.useState(null);
const field = react_1.default.useMemo(() => { var _a; return (_a = fields.find((fld) => fld.value === value)) === null || _a === void 0 ? void 0 : _a.label; }, [value, fields]);
const anchor = react_1.default.useMemo(() => {
if (!active)
return;
if (!input.current)
return;
const height = document.body.clientHeight;
const isScrollable = document.body.clientHeight < document.body.scrollHeight;
const at = input.current.getBoundingClientRect().bottom + 5;
if (input.current.getBoundingClientRect().bottom + Contracts.MENU_MAX_HEIGHT + 10 > height)
return {
at: height -
input.current.getBoundingClientRect().top +
5 -
(isScrollable ? Contracts.getScrollbarWidth() : 0),
direction: 'UP',
};
return { at, direction: 'DOWN' };
}, [active, input.current]);
const handleOptionClick = (option) => {
onChange(option);
setSearch(null);
if (!closeOnSelect)
return;
setActive(false);
};
react_1.default.useEffect(() => {
Contracts.setStyleSheet(style);
}, [style]);
react_1.default.useEffect(() => {
if (active && onFocus)
onFocus();
if (!active && onBlur)
onBlur();
}, [active]);
const handleSearch = (event) => setSearch(event.currentTarget.value);
const toggle = () => setActive((prev) => !prev);
const enterSearch = (event) => {
event.persist();
if (event.key.toLowerCase() !== 'enter')
return;
if (search === null)
return;
if (currentVisibleOptions.length > 0)
onChange(currentVisibleOptions[0]);
setSearch(null);
toggle();
};
react_1.default.useEffect(() => {
if (!input.current)
return;
if (active)
input.current.focus();
else {
setSearch(null);
input.current.blur();
}
}, [input.current, active]);
react_1.default.useEffect(() => {
const clickListener = (event) => {
var _a;
if ((_a = dropdown.current) === null || _a === void 0 ? void 0 : _a.contains(event.target))
return;
setActive(false);
};
const scrollListener = (event) => {
var _a;
if ((_a = menu.current) === null || _a === void 0 ? void 0 : _a.contains(event.target))
return;
setActive(false);
};
document.addEventListener('click', clickListener);
document.addEventListener('scroll', scrollListener, true);
return () => {
document.removeEventListener('click', clickListener);
document.removeEventListener('scroll', scrollListener, true);
};
}, []);
react_1.default.useLayoutEffect(() => {
if (!textContainer.current || !text.current || !input.current)
return;
textContainer.current.style.width = `${text.current.clientWidth + 17.5}px`;
input.current.style.width = `${text.current.clientWidth - 4.5}px`;
}, [text.current, textContainer.current, value]);
return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ className: `dropdown ${active ? 'active' : ''} ${className !== null && className !== void 0 ? className : ''}`, ref: setDropdown }, props, { children: [(0, jsx_runtime_1.jsxs)("div", Object.assign({ className: 'customBase dropdown-search' }, { children: [(0, jsx_runtime_1.jsxs)("div", Object.assign({ className: 'dropdown-text'.concat(!searchable ? ' dropdown-readonly' : ''), onClick: toggle, ref: setTextContainer }, { children: [!search && ((0, jsx_runtime_1.jsx)("div", Object.assign({ className: `dropdown-searchtext ${field === undefined ? 'dropdown-placeholder' : ''}`, ref: setText }, { children: field !== null && field !== void 0 ? field : placeholder }))), (0, jsx_runtime_1.jsx)(Components.BaseInput, { value: search !== null && search !== void 0 ? search : '', id: id === null || id === void 0 ? void 0 : id.concat('input'), onChange: handleSearch, className: 'dropdown-searchinput', autoComplete: 'off', autoCapitalize: 'off', autoCorrect: 'off', disabled: !searchable, ref: setInput, onKeyUp: enterSearch, "data-testid": (_b = props['data-testid']) === null || _b === void 0 ? void 0 : _b.concat('-input') })] })), (0, jsx_runtime_1.jsx)(Icons.ChevronDown, { size: 16, className: 'dropdown-searchicon', onClick: toggle })] })), grouped || favorize ? ((0, jsx_runtime_1.jsx)(Components.Grouped, Object.assign({ id: id, onOptionClick: handleOptionClick, onFilteredChange: setCurrentVisibleOptions, options: fields, current: value, anchor: anchor, ref: setMenu, filter: search, onFavorize: onFavorizeOption, grouping: grouped, favorize: favorize }, props))) : ((0, jsx_runtime_1.jsx)(Components.Standard, Object.assign({ id: id, onOptionClick: handleOptionClick, options: fields, current: value, filter: search, anchor: anchor, ref: setMenu, onFilteredChange: setCurrentVisibleOptions }, props)))] })));
};
exports.Dropdown = Dropdown;