UNPKG

analytica-frontend-lib

Version:

Repositório público dos componentes utilizados nas plataformas da Analytica Ensino

484 lines (464 loc) 20.3 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _chunkLGPTKTMPjs = require('./chunk-LGPTKTMP.js'); var _chunk34ST3MKOjs = require('./chunk-34ST3MKO.js'); var _chunkANT66KVKjs = require('./chunk-ANT66KVK.js'); var _chunkTN3AYOMVjs = require('./chunk-TN3AYOMV.js'); // src/components/SearchSelect/SearchSelect.tsx var _react = require('react'); var _reactdom = require('react-dom'); var _CaretDown = require('@phosphor-icons/react/dist/csr/CaretDown'); var _Check = require('@phosphor-icons/react/dist/csr/Check'); var _MagnifyingGlass = require('@phosphor-icons/react/dist/csr/MagnifyingGlass'); var _SpinnerGap = require('@phosphor-icons/react/dist/csr/SpinnerGap'); var _jsxruntime = require('react/jsx-runtime'); var SIZE_CLASSES = { small: "text-sm h-8", medium: "text-md h-9", large: "text-lg h-10" }; var PADDING_CLASSES = { small: "px-3 py-1", medium: "px-3 py-2", large: "px-4 py-3" }; var VARIANT_CLASSES = { outlined: "border-2 rounded-lg focus:border-primary-950", underlined: "border-b-2 focus:border-primary-950", rounded: "border-2 rounded-full focus:border-primary-950" }; function useDebounce(value, delay) { const [debouncedValue, setDebouncedValue] = _react.useState.call(void 0, value); _react.useEffect.call(void 0, () => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(handler); }; }, [value, delay]); return debouncedValue; } var SearchSelect = _react.forwardRef.call(void 0, ({ value, onValueChange, options, placeholder = "Selecione...", searchPlaceholder = "Buscar...", label, helperText, errorMessage, disabled = false, loading = false, size = "small", variant = "outlined", className, emptyText = "Nenhum resultado encontrado", loadingText = "Carregando...", onSearch, searchDebounce = 300, pagination, onLoadMore, loadingMore = false, id, filterLocally = true }, ref) => { const [open, setOpen] = _react.useState.call(void 0, false); const [searchQuery, setSearchQuery] = _react.useState.call(void 0, ""); const [highlightedIndex, setHighlightedIndex] = _react.useState.call(void 0, -1); const triggerRef = _react.useRef.call(void 0, null); const contentRef = _react.useRef.call(void 0, null); const searchInputRef = _react.useRef.call(void 0, null); const listRef = _react.useRef.call(void 0, null); const isFetchingMoreRef = _react.useRef.call(void 0, false); const generatedId = _react.useId.call(void 0, ); const selectId = _nullishCoalesce(id, () => ( `search-select-${generatedId}`)); const listboxId = `${selectId}-listbox`; const helperId = `${selectId}-helper`; const errorId = `${selectId}-error`; const debouncedSearch = useDebounce(searchQuery, searchDebounce); _react.useEffect.call(void 0, () => { if (onSearch && debouncedSearch !== void 0) { onSearch(debouncedSearch); } }, [debouncedSearch, onSearch]); const filteredOptions = _react.useMemo.call(void 0, () => { if (!filterLocally || !searchQuery) { return options; } const query = searchQuery.toLowerCase(); return options.filter((opt) => opt.label.toLowerCase().includes(query)); }, [options, searchQuery, filterLocally]); _react.useEffect.call(void 0, () => { if (highlightedIndex >= filteredOptions.length) { setHighlightedIndex(Math.max(filteredOptions.length - 1, -1)); } }, [filteredOptions.length, highlightedIndex]); const selectedLabel = _react.useMemo.call(void 0, () => { const selected = options.find((opt) => opt.value === value); return _optionalChain([selected, 'optionalAccess', _ => _.label]); }, [options, value]); const [triggerRect, setTriggerRect] = _react.useState.call(void 0, null); const updateTriggerRect = _react.useCallback.call(void 0, () => { if (triggerRef.current) { setTriggerRect(triggerRef.current.getBoundingClientRect()); } }, []); const handleToggle = _react.useCallback.call(void 0, () => { if (disabled || loading) return; const newOpen = !open; if (newOpen) { updateTriggerRect(); setSearchQuery(""); setHighlightedIndex(-1); } setOpen(newOpen); }, [disabled, loading, open, updateTriggerRect]); const handleSelect = _react.useCallback.call(void 0, (optionValue) => { _optionalChain([onValueChange, 'optionalCall', _2 => _2(optionValue)]); setOpen(false); setSearchQuery(""); requestAnimationFrame(() => { _optionalChain([triggerRef, 'access', _3 => _3.current, 'optionalAccess', _4 => _4.focus, 'call', _5 => _5()]); }); }, [onValueChange] ); _react.useEffect.call(void 0, () => { if (open && searchInputRef.current) { setTimeout(() => { _optionalChain([searchInputRef, 'access', _6 => _6.current, 'optionalAccess', _7 => _7.focus, 'call', _8 => _8()]); }, 0); } }, [open]); _react.useEffect.call(void 0, () => { if (!open) return; const handleClickOutside = (event) => { const target = event.target; const isInsideTrigger = _optionalChain([triggerRef, 'access', _9 => _9.current, 'optionalAccess', _10 => _10.contains, 'call', _11 => _11(target)]); const isInsideContent = _optionalChain([contentRef, 'access', _12 => _12.current, 'optionalAccess', _13 => _13.contains, 'call', _14 => _14(target)]); if (!isInsideTrigger && !isInsideContent) { setOpen(false); setSearchQuery(""); } }; document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [open]); _react.useEffect.call(void 0, () => { if (!open) return; const handleUpdate = () => updateTriggerRect(); window.addEventListener("scroll", handleUpdate, true); window.addEventListener("resize", handleUpdate); return () => { window.removeEventListener("scroll", handleUpdate, true); window.removeEventListener("resize", handleUpdate); }; }, [open, updateTriggerRect]); const findNextEnabledIndex = _react.useCallback.call(void 0, (currentIndex, direction) => { const len = filteredOptions.length; if (len === 0) return -1; let startIndex; if (currentIndex < 0) { startIndex = direction === "next" ? -1 : len; } else { startIndex = currentIndex; } for (let i = 0; i < len; i++) { if (direction === "next") { startIndex = (startIndex + 1) % len; } else { startIndex = (startIndex - 1 + len) % len; } if (!filteredOptions[startIndex].disabled) { return startIndex; } } return -1; }, [filteredOptions] ); const handleKeyDown = _react.useCallback.call(void 0, (e) => { switch (e.key) { case "ArrowDown": e.preventDefault(); setHighlightedIndex((prev) => findNextEnabledIndex(prev, "next")); break; case "ArrowUp": e.preventDefault(); setHighlightedIndex((prev) => findNextEnabledIndex(prev, "prev")); break; case "Enter": e.preventDefault(); if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length && !filteredOptions[highlightedIndex].disabled) { handleSelect(filteredOptions[highlightedIndex].value); } break; case "Escape": e.preventDefault(); setOpen(false); setSearchQuery(""); _optionalChain([triggerRef, 'access', _15 => _15.current, 'optionalAccess', _16 => _16.focus, 'call', _17 => _17()]); break; } }, [filteredOptions, highlightedIndex, handleSelect, findNextEnabledIndex] ); _react.useEffect.call(void 0, () => { if (!loadingMore) { isFetchingMoreRef.current = false; } }, [loadingMore]); const handleScroll = _react.useCallback.call(void 0, () => { if (!listRef.current || !_optionalChain([pagination, 'optionalAccess', _18 => _18.hasNext]) || loadingMore || isFetchingMoreRef.current || !onLoadMore) return; const { scrollTop, scrollHeight, clientHeight } = listRef.current; const scrollThreshold = 50; if (scrollHeight - scrollTop - clientHeight < scrollThreshold) { isFetchingMoreRef.current = true; const result = onLoadMore(); if (result && typeof result.then === "function") { result.finally(() => { isFetchingMoreRef.current = false; }); } } }, [_optionalChain([pagination, 'optionalAccess', _19 => _19.hasNext]), loadingMore, onLoadMore]); _react.useEffect.call(void 0, () => { if (highlightedIndex >= 0 && listRef.current) { const items = listRef.current.querySelectorAll("[data-option]"); const item = items[highlightedIndex]; if (item) { item.scrollIntoView({ block: "nearest" }); } } }, [highlightedIndex]); const setRefs = _react.useCallback.call(void 0, (element) => { triggerRef.current = element; if (typeof ref === "function") { ref(element); } else if (ref) { ref.current = element; } }, [ref] ); const sizeClasses = SIZE_CLASSES[size]; const paddingClasses = PADDING_CLASSES[size]; const variantClasses = VARIANT_CLASSES[variant]; const invalid = !!errorMessage; const getDropdownStyles = _react.useCallback.call(void 0, () => { if (!triggerRect) return {}; const gap = 4; const dropdownMaxHeight = 300; const viewportHeight = window.innerHeight; const spaceBelow = viewportHeight - triggerRect.bottom - gap; const spaceAbove = triggerRect.top - gap; const openAbove = spaceBelow < dropdownMaxHeight && spaceAbove > spaceBelow; const styles = { position: "fixed", left: triggerRect.left, width: triggerRect.width, zIndex: 9999, maxHeight: openAbove ? Math.min(spaceAbove, dropdownMaxHeight) : Math.min(spaceBelow, dropdownMaxHeight) }; if (openAbove) { styles.bottom = viewportHeight - triggerRect.top + gap; } else { styles.top = triggerRect.bottom + gap; } return styles; }, [triggerRect]); const renderOptionsContent = () => { if (loading) { return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center justify-center gap-2 p-4 text-text-500", children: [ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _SpinnerGap.SpinnerGapIcon, { size: 18, className: "animate-spin" }), /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "sm", className: "text-text-500", children: loadingText }) ] }); } if (filteredOptions.length === 0) { return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "sm", className: "p-4 text-center text-text-500", children: emptyText }); } return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [ filteredOptions.map((option, index) => { const isSelected = option.value === value; const isHighlighted = index === highlightedIndex; const optionId = `${listboxId}-option-${index}`; return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { id: optionId, "data-option": true, role: "option", "aria-selected": isSelected, "aria-disabled": option.disabled, tabIndex: -1, onClick: () => { if (!option.disabled) { handleSelect(option.value); } }, onKeyDown: (e) => { if (option.disabled) return; if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleSelect(option.value); } }, className: _chunkTN3AYOMVjs.cn.call(void 0, "relative flex items-center gap-2 px-3 py-2.5 cursor-pointer transition-colors text-sm", isSelected && "bg-primary-50", isHighlighted && !isSelected && "bg-background-50", option.disabled && "opacity-50 cursor-not-allowed pointer-events-none", !option.disabled && !isSelected && !isHighlighted && "hover:bg-background-50" ), children: [ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "sm", className: "flex-1 text-text-700", children: option.label }), isSelected && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _Check.CheckIcon, { size: 16, className: "text-primary-700", weight: "bold" } ) ] }, option.value ); }), loadingMore && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center justify-center gap-2 p-3 text-text-500 border-t border-border-100", children: [ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _SpinnerGap.SpinnerGapIcon, { size: 16, className: "animate-spin" }), /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "xs", className: "text-text-500", children: "Carregando mais..." }) ] }), pagination && !loadingMore && pagination.total > 0 && !(filterLocally && searchQuery) && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkANT66KVKjs.Text_default, { size: "xs", className: "px-3 py-2 text-text-400 border-t border-border-100 text-center", children: [ options.length, " de ", pagination.total, " itens" ] } ) ] }); }; const dropdownContent = open && triggerRect && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { ref: contentRef, "data-search-select-id": selectId, style: getDropdownStyles(), className: "bg-secondary rounded-md border border-border-100 shadow-lg overflow-hidden flex flex-col", children: [ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "p-2 border-b border-border-100", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGPTKTMPjs.Input_default, { ref: searchInputRef, type: "text", role: "combobox", "aria-autocomplete": "list", "aria-controls": listboxId, "aria-activedescendant": highlightedIndex >= 0 ? `${listboxId}-option-${highlightedIndex}` : void 0, value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), onKeyDown: handleKeyDown, placeholder: searchPlaceholder, size: "small", variant: "outlined", iconRight: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _MagnifyingGlass.MagnifyingGlassIcon, { size: 16 }), containerClassName: "w-full" } ) }), /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { ref: listRef, id: listboxId, role: "listbox", "aria-label": label || "Options", onScroll: handleScroll, className: "flex-1 overflow-y-auto", children: renderOptionsContent() } ) ] } ); const getDescribedById = () => { if (errorMessage) return errorId; if (helperText) return helperId; return void 0; }; const describedById = getDescribedById(); return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkTN3AYOMVjs.cn.call(void 0, "w-full", className), children: [ label && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { htmlFor: selectId, className: _chunkTN3AYOMVjs.cn.call(void 0, "block font-bold text-text-900 mb-1.5", size === "small" && "text-sm", size === "medium" && "text-md", size === "large" && "text-lg" ), children: label } ), /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk34ST3MKOjs.Button_default, { ref: setRefs, id: selectId, variant: "raw", onClick: handleToggle, disabled: disabled || loading, "aria-expanded": open, "aria-haspopup": "listbox", "aria-controls": open ? listboxId : void 0, "aria-invalid": !!errorMessage, "aria-describedby": describedById, className: _chunkTN3AYOMVjs.cn.call(void 0, "flex w-full items-center justify-between border-border-300 bg-background", sizeClasses, paddingClasses, variantClasses, invalid && "border-indicator-error", disabled || loading ? "cursor-not-allowed opacity-50 text-text-400" : "cursor-pointer hover:bg-background-50 text-text-700" ), iconRight: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _CaretDown.CaretDownIcon, { className: _chunkTN3AYOMVjs.cn.call(void 0, "h-4 w-4 opacity-50 transition-transform shrink-0", open && "rotate-180" ) } ), children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: _chunkTN3AYOMVjs.cn.call(void 0, "truncate", !selectedLabel && "text-text-500"), children: loading ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _SpinnerGap.SpinnerGapIcon, { size: 14, className: "animate-spin" }), /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { size: "sm", className: "text-text-500", children: loadingText }) ] }) : selectedLabel || placeholder }) } ), (helperText || errorMessage) && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "mt-1.5", children: [ helperText && !errorMessage && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { id: helperId, size: "sm", className: "text-text-500", children: helperText }), errorMessage && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkANT66KVKjs.Text_default, { id: errorId, size: "sm", className: "text-indicator-error", children: errorMessage }) ] }), typeof document !== "undefined" && _reactdom.createPortal.call(void 0, dropdownContent, document.body) ] }); } ); SearchSelect.displayName = "SearchSelect"; var SearchSelect_default = SearchSelect; exports.SearchSelect = SearchSelect; exports.SearchSelect_default = SearchSelect_default; //# sourceMappingURL=chunk-C5FRUHXC.js.map