UNPKG

@ralorotech/duino-ui

Version:
1,389 lines (1,388 loc) 81.8 kB
import React2, { createContext, useRef, useState, useEffect, useId, useCallback, useMemo, useContext, isValidElement, cloneElement } from 'react'; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import { createPortal } from 'react-dom'; // src/components/Button/Button.tsx var Button = React2.forwardRef( ({ variant = "primary", size = "md", shape = "default", block = false, loading = false, icon, iconPosition = "start", loadingText = "Cargando...", danger = false, href, target, className = "", children, disabled, onClick, ...rest }, ref) => { const finalVariant = danger && variant !== "text" ? "danger" : variant; const baseClass = "duino-btn"; const classes = [ baseClass, `${baseClass}--${finalVariant}`, `${baseClass}--${size}`, shape !== "default" && `${baseClass}--${shape}`, block && `${baseClass}--block`, loading && `${baseClass}--loading`, (disabled || loading) && `${baseClass}--disabled`, !children && icon && `${baseClass}--icon-only`, className ].filter(Boolean).join(" "); const content = /* @__PURE__ */ jsxs(Fragment, { children: [ loading && /* @__PURE__ */ jsx( "span", { className: `${baseClass}__loading`, role: "status", "aria-label": loadingText, children: /* @__PURE__ */ jsx("svg", { className: `${baseClass}__spinner`, viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx( "circle", { className: `${baseClass}__spinner-circle`, cx: "12", cy: "12", r: "10", fill: "none", strokeWidth: "2" } ) }) } ), icon && iconPosition === "start" && !loading && /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon ${baseClass}__icon--start`, children: icon }), children && /* @__PURE__ */ jsx("span", { className: `${baseClass}__text`, children }), icon && iconPosition === "end" && !loading && /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon ${baseClass}__icon--end`, children: icon }) ] }); if (href) { return /* @__PURE__ */ jsx( "a", { ref, href: disabled || loading ? void 0 : href, target, className: classes, ...disabled || loading ? { "aria-disabled": "true" } : {}, onClick: disabled || loading ? (e) => e.preventDefault() : onClick, ...rest, children: content } ); } return /* @__PURE__ */ jsx( "button", { ref, type: "button", className: classes, disabled: disabled || loading, onClick, ...rest, children: content } ); } ); Button.displayName = "Button"; var useCollapseAnimation = (isOpen, contentRef) => { const [height, setHeight] = useState("0px"); const [isAnimating, setIsAnimating] = useState(false); useEffect(() => { if (!contentRef.current) return; const element = contentRef.current; if (isOpen) { setIsAnimating(true); element.style.height = "auto"; const scrollHeight = element.scrollHeight; element.style.height = "0px"; requestAnimationFrame(() => { setHeight(`${scrollHeight}px`); }); } else { setIsAnimating(true); setHeight("0px"); } const timer = setTimeout(() => { setIsAnimating(false); if (isOpen) { setHeight("auto"); } }, 300); return () => clearTimeout(timer); }, [isOpen]); return { height, isAnimating }; }; var CollapsePanel = ({ header, children, disabled = false, extra, showArrow = true, className = "", isActive = false, onToggle, size = "md", variant = "default", expandIcon, expandIconPosition = "end", collapsible = "header" }) => { const contentRef = useRef(null); const { height, isAnimating } = useCollapseAnimation(isActive, contentRef); const handleHeaderClick = () => { if (disabled || collapsible === "disabled") return; onToggle == null ? void 0 : onToggle(); }; const handleIconClick = (e) => { e.stopPropagation(); if (disabled || collapsible === "disabled") return; if (collapsible === "icon") { onToggle == null ? void 0 : onToggle(); } }; const renderExpandIcon = () => { if (!showArrow) return null; if (expandIcon) { return expandIcon({ isActive, disabled }); } return /* @__PURE__ */ jsx( "svg", { className: "duino-collapse__arrow", viewBox: "0 0 12 12", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M6 8.825L1.175 4L2.35 2.825L6 6.475L9.65 2.825L10.825 4L6 8.825Z" }) } ); }; const panelClasses = [ "duino-collapse__panel", `duino-collapse__panel--${size}`, `duino-collapse__panel--${variant}`, isActive && "duino-collapse__panel--active", disabled && "duino-collapse__panel--disabled", isAnimating && "duino-collapse__panel--animating", className ].filter(Boolean).join(" "); const headerClasses = [ "duino-collapse__header", collapsible !== "disabled" && !disabled && "duino-collapse__header--clickable" ].filter(Boolean).join(" "); return /* @__PURE__ */ jsxs("div", { className: panelClasses, children: [ /* @__PURE__ */ jsxs( "div", { className: headerClasses, onClick: collapsible === "header" ? handleHeaderClick : void 0, role: "button", tabIndex: disabled || collapsible === "disabled" ? -1 : 0, "aria-expanded": isActive ? "true" : "false", "aria-disabled": disabled ? "true" : "false", onKeyDown: (e) => { if ((e.key === "Enter" || e.key === " ") && collapsible === "header") { e.preventDefault(); handleHeaderClick(); } }, children: [ expandIconPosition === "start" && /* @__PURE__ */ jsx( "div", { className: "duino-collapse__icon duino-collapse__icon--start", onClick: collapsible === "icon" ? handleIconClick : void 0, children: renderExpandIcon() } ), /* @__PURE__ */ jsx("div", { className: "duino-collapse__header-content", children: header }), extra && /* @__PURE__ */ jsx("div", { className: "duino-collapse__extra", children: extra }), expandIconPosition === "end" && /* @__PURE__ */ jsx( "div", { className: "duino-collapse__icon duino-collapse__icon--end", onClick: collapsible === "icon" ? handleIconClick : void 0, children: renderExpandIcon() } ) ] } ), /* @__PURE__ */ jsx( "div", { className: "duino-collapse__content", style: { height }, ref: contentRef, children: /* @__PURE__ */ jsx("div", { className: "duino-collapse__body", children: (isActive || !isAnimating) && children }) } ) ] }); }; var Collapse = ({ items, children, activeKey: controlledActiveKey, defaultActiveKey, accordion = false, collapsible = "header", destroyInactivePanel = false, size = "md", variant = "default", bordered = false, ghost = false, className = "", onChange, expandIcon, expandIconPosition = "end" }) => { const [internalActiveKey, setInternalActiveKey] = useState( controlledActiveKey !== void 0 ? controlledActiveKey : defaultActiveKey || (accordion ? "" : []) ); const currentActiveKey = controlledActiveKey !== void 0 ? controlledActiveKey : internalActiveKey; const handlePanelToggle = (key) => { let newActiveKey; if (accordion) { newActiveKey = currentActiveKey === key ? "" : key; } else { const currentArray = Array.isArray(currentActiveKey) ? currentActiveKey : []; if (currentArray.includes(key)) { newActiveKey = currentArray.filter((k) => k !== key); } else { newActiveKey = [...currentArray, key]; } } if (controlledActiveKey === void 0) { setInternalActiveKey(newActiveKey); } onChange == null ? void 0 : onChange(newActiveKey); }; const isKeyActive = (key) => { if (accordion) { return currentActiveKey === key; } else { return Array.isArray(currentActiveKey) && currentActiveKey.includes(key); } }; const collapseClasses = [ "duino-collapse", `duino-collapse--${size}`, `duino-collapse--${variant}`, bordered && "duino-collapse--bordered", ghost && "duino-collapse--ghost", accordion && "duino-collapse--accordion", className ].filter(Boolean).join(" "); if (items && items.length > 0) { return /* @__PURE__ */ jsx("div", { className: collapseClasses, children: items.map((item) => /* @__PURE__ */ jsx( CollapsePanel, { header: item.label, disabled: item.disabled, extra: item.extra, showArrow: item.showArrow, className: item.className, isActive: isKeyActive(item.key), onToggle: () => handlePanelToggle(item.key), size, variant, expandIcon, expandIconPosition, collapsible, children: (isKeyActive(item.key) || !destroyInactivePanel) && item.children }, item.key )) }); } return /* @__PURE__ */ jsx("div", { className: collapseClasses, children: React2.Children.map(children, (child, index) => { if (!React2.isValidElement(child)) return child; const key = child.key || index; return React2.cloneElement(child, { isActive: isKeyActive(String(key)), onToggle: () => handlePanelToggle(String(key)), size, variant, expandIcon, expandIconPosition, collapsible }); }) }); }; Collapse.displayName = "Collapse"; CollapsePanel.displayName = "CollapsePanel"; var CircleSpinner = ({ size }) => /* @__PURE__ */ jsx("svg", { className: `duino-spin__svg duino-spin__svg--${size}`, viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx( "circle", { className: "duino-spin__circle", cx: "12", cy: "12", r: "10", fill: "none", strokeWidth: "2" } ) }); var DotsSpinner = ({ size }) => /* @__PURE__ */ jsxs("div", { className: `duino-spin__dots duino-spin__dots--${size}`, children: [ /* @__PURE__ */ jsx("div", { className: "duino-spin__dot duino-spin__dot--1" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__dot duino-spin__dot--2" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__dot duino-spin__dot--3" }) ] }); var PulseSpinner = ({ size }) => /* @__PURE__ */ jsxs("div", { className: `duino-spin__pulse duino-spin__pulse--${size}`, children: [ /* @__PURE__ */ jsx("div", { className: "duino-spin__pulse-ring duino-spin__pulse-ring--1" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__pulse-ring duino-spin__pulse-ring--2" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__pulse-ring duino-spin__pulse-ring--3" }) ] }); var BarsSpinner = ({ size }) => /* @__PURE__ */ jsxs("div", { className: `duino-spin__bars duino-spin__bars--${size}`, children: [ /* @__PURE__ */ jsx("div", { className: "duino-spin__bar duino-spin__bar--1" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__bar duino-spin__bar--2" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__bar duino-spin__bar--3" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__bar duino-spin__bar--4" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__bar duino-spin__bar--5" }) ] }); var RingSpinner = ({ size }) => /* @__PURE__ */ jsxs("div", { className: `duino-spin__ring duino-spin__ring--${size}`, children: [ /* @__PURE__ */ jsx("div", { className: "duino-spin__ring-segment duino-spin__ring-segment--1" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__ring-segment duino-spin__ring-segment--2" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__ring-segment duino-spin__ring-segment--3" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__ring-segment duino-spin__ring-segment--4" }) ] }); var WaveSpinner = ({ size }) => /* @__PURE__ */ jsxs("div", { className: `duino-spin__wave duino-spin__wave--${size}`, children: [ /* @__PURE__ */ jsx("div", { className: "duino-spin__wave-bar duino-spin__wave-bar--1" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__wave-bar duino-spin__wave-bar--2" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__wave-bar duino-spin__wave-bar--3" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__wave-bar duino-spin__wave-bar--4" }), /* @__PURE__ */ jsx("div", { className: "duino-spin__wave-bar duino-spin__wave-bar--5" }) ] }); var Spin = ({ spinning = true, size = "md", type = "circle", children, tip, className = "", style, wrapperClassName = "", indicator, delay = 0, "aria-label": ariaLabel = "Cargando...", id }) => { const [showSpinner, setShowSpinner] = React2.useState(delay === 0); React2.useEffect(() => { if (delay > 0 && spinning) { const timer = setTimeout(() => setShowSpinner(true), delay); return () => clearTimeout(timer); } else { setShowSpinner(spinning); } }, [spinning, delay]); const renderSpinner = () => { if (indicator) { return indicator; } switch (type) { case "dots": return /* @__PURE__ */ jsx(DotsSpinner, { size }); case "pulse": return /* @__PURE__ */ jsx(PulseSpinner, { size }); case "bars": return /* @__PURE__ */ jsx(BarsSpinner, { size }); case "ring": return /* @__PURE__ */ jsx(RingSpinner, { size }); case "wave": return /* @__PURE__ */ jsx(WaveSpinner, { size }); case "circle": default: return /* @__PURE__ */ jsx(CircleSpinner, { size }); } }; const spinClasses = [ "duino-spin", `duino-spin--${size}`, `duino-spin--${type}`, showSpinner && spinning && "duino-spin--spinning", className ].filter(Boolean).join(" "); if (!children) { return /* @__PURE__ */ jsx( "div", { className: spinClasses, style, role: "status", "aria-label": ariaLabel, "aria-live": "polite", id, children: showSpinner && spinning && /* @__PURE__ */ jsxs(Fragment, { children: [ renderSpinner(), tip && /* @__PURE__ */ jsx("div", { className: "duino-spin__tip", children: tip }) ] }) } ); } const wrapperClasses = [ "duino-spin-wrapper", showSpinner && spinning && "duino-spin-wrapper--spinning", wrapperClassName ].filter(Boolean).join(" "); return /* @__PURE__ */ jsxs("div", { className: wrapperClasses, style, children: [ showSpinner && spinning && /* @__PURE__ */ jsx("div", { className: "duino-spin-overlay", children: /* @__PURE__ */ jsxs("div", { className: spinClasses, role: "status", "aria-label": ariaLabel, id, children: [ renderSpinner(), tip && /* @__PURE__ */ jsx("div", { className: "duino-spin__tip", children: tip }) ] }) }), /* @__PURE__ */ jsx("div", { className: showSpinner && spinning ? "duino-spin-content--blurred" : "duino-spin-content", children }) ] }); }; Spin.displayName = "Spin"; var useIntersectionObserver = (elementRef, options) => { const [isIntersecting, setIsIntersecting] = useState(false); useEffect(() => { const element = elementRef.current; if (!element) return; const observer = new IntersectionObserver(([entry]) => { setIsIntersecting(entry.isIntersecting); }, options); observer.observe(element); return () => { observer.unobserve(element); observer.disconnect(); }; }, [elementRef, options]); return isIntersecting; }; var Image = ({ src, alt, width, height, lazy = true, preview = false, fallback, placeholder, fit = "cover", shape = "rounded", bordered = false, shadow = false, loading: customLoading, error: customError, onLoad, onError, onPreview, rootMargin = "50px", threshold = 0.1, caption, className = "", style, ...rest }) => { const [imageState, setImageState] = useState("loading"); const [showPreview, setShowPreview] = useState(false); const imageRef = useRef(null); const imgRef = useRef(null); const isInView = useIntersectionObserver(imageRef, { rootMargin, threshold }); const shouldLoad = !lazy || isInView; const handleLoad = (event) => { setImageState("loaded"); onLoad == null ? void 0 : onLoad(event); }; const handleError = (event) => { setImageState("error"); onError == null ? void 0 : onError(event); }; const handlePreviewClick = () => { if (preview && imageState === "loaded") { setShowPreview(true); onPreview == null ? void 0 : onPreview(); } }; useEffect(() => { const handleKeyDown = (e) => { if (e.key === "Escape" && showPreview) { setShowPreview(false); } }; if (showPreview) { document.addEventListener("keydown", handleKeyDown); document.body.style.overflow = "hidden"; } return () => { document.removeEventListener("keydown", handleKeyDown); document.body.style.overflow = ""; }; }, [showPreview]); const imageClasses = [ "duino-image", `duino-image--${fit}`, `duino-image--${shape}`, bordered && "duino-image--bordered", shadow && "duino-image--shadow", preview && imageState === "loaded" && "duino-image--preview", className ].filter(Boolean).join(" "); const renderLoadingContent = () => { if (customLoading) { return customLoading; } return /* @__PURE__ */ jsx("div", { className: "duino-image__loading", children: /* @__PURE__ */ jsx(Spin, { size: "sm", type: "circle" }) }); }; const renderErrorContent = () => { if (customError) { return customError; } if (fallback) { return fallback; } return /* @__PURE__ */ jsxs("div", { className: "duino-image__error", children: [ /* @__PURE__ */ jsx("svg", { className: "duino-image__error-icon", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z" }) }), /* @__PURE__ */ jsx("span", { className: "duino-image__error-text", children: "Error al cargar imagen" }) ] }); }; const renderPlaceholder = () => { if (placeholder) { return placeholder; } return /* @__PURE__ */ jsx("div", { className: "duino-image__placeholder", children: /* @__PURE__ */ jsx("svg", { className: "duino-image__placeholder-icon", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z" }) }) }); }; return /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsxs( "div", { ref: imageRef, className: imageClasses, style: { width, height, ...style }, onClick: handlePreviewClick, children: [ !shouldLoad && renderPlaceholder(), shouldLoad && /* @__PURE__ */ jsxs(Fragment, { children: [ imageState === "loading" && renderLoadingContent(), imageState === "error" && renderErrorContent(), /* @__PURE__ */ jsx( "img", { ref: imgRef, src, alt, className: `duino-image__img duino-image__img--${imageState}`, onLoad: handleLoad, onError: handleError, ...rest } ), preview && imageState === "loaded" && /* @__PURE__ */ jsx("div", { className: "duino-image__preview-overlay", children: /* @__PURE__ */ jsx("svg", { className: "duino-image__preview-icon", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" }) }) }) ] }), caption && /* @__PURE__ */ jsx("div", { className: "duino-image__caption", children: caption }) ] } ), showPreview && /* @__PURE__ */ jsx( "div", { className: "duino-image__preview-modal", onClick: () => setShowPreview(false), children: /* @__PURE__ */ jsxs("div", { className: "duino-image__preview-content", children: [ /* @__PURE__ */ jsx( "button", { className: "duino-image__preview-close", onClick: () => setShowPreview(false), "aria-label": "Cerrar preview", children: "\xD7" } ), /* @__PURE__ */ jsx( "img", { src, alt, className: "duino-image__preview-img" } ), caption && /* @__PURE__ */ jsx("div", { className: "duino-image__preview-caption", children: caption }) ] }) } ) ] }); }; Image.displayName = "Image"; var Modal = ({ open, title, children, onOk, onCancel, okText = "OK", cancelText = "Cancel", maskClosable = true, centered = true, width = 520, destroyOnClose = false, footer, className = "" }) => { const titleId = useId(); const containerRef = useRef(null); const previouslyFocused = useRef(null); useEffect(() => { var _a, _b; if (open) { previouslyFocused.current = document.activeElement; document.body.classList.add("is-modal-open"); setTimeout(() => { var _a2; (_a2 = containerRef.current) == null ? void 0 : _a2.focus(); }, 0); } else { document.body.classList.remove("is-modal-open"); (_b = (_a = previouslyFocused.current) == null ? void 0 : _a.focus) == null ? void 0 : _b.call(_a); } return () => { document.body.classList.remove("is-modal-open"); }; }, [open]); useEffect(() => { if (!open) return; const onKey = (e) => { var _a; if (e.key === "Escape") onCancel == null ? void 0 : onCancel(); if (e.key === "Tab") { const focusables = (_a = containerRef.current) == null ? void 0 : _a.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); if (!focusables || focusables.length === 0) return; const first = focusables[0]; const last = focusables[focusables.length - 1]; const active = document.activeElement; if (e.shiftKey && active === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && active === last) { e.preventDefault(); first.focus(); } } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, onCancel]); if (!open && destroyOnClose) return null; const handleMaskClick = (e) => { if (!maskClosable) return; if (e.target === e.currentTarget) onCancel == null ? void 0 : onCancel(); }; const styleWidth = typeof width === "number" ? `${width}px` : width != null ? width : void 0; const modal = /* @__PURE__ */ jsxs( "div", { className: `duino-modal ${open ? "duino-modal--open" : ""} ${centered ? "duino-modal--centered" : ""}`, onMouseDown: handleMaskClick, children: [ /* @__PURE__ */ jsx("div", { className: "duino-modal__mask" }), /* @__PURE__ */ jsxs( "div", { className: `duino-modal__container ${className}`, role: "dialog", "aria-modal": "true", "aria-labelledby": title ? titleId : void 0, tabIndex: -1, ref: containerRef, style: { width: styleWidth }, onMouseDown: (e) => { e.stopPropagation(); }, children: [ /* @__PURE__ */ jsxs("div", { className: "duino-modal__header", children: [ title && /* @__PURE__ */ jsx("h2", { id: titleId, className: "duino-modal__title", children: title }), /* @__PURE__ */ jsx( "button", { type: "button", className: "duino-modal__close", "aria-label": "Close", onClick: onCancel, children: "\xD7" } ) ] }), /* @__PURE__ */ jsx("div", { className: "duino-modal__body", children: open || !destroyOnClose ? children : null }), footer !== null && /* @__PURE__ */ jsx("div", { className: "duino-modal__footer", children: footer != null ? footer : /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx( "button", { type: "button", className: "duino-btn duino-btn--ghost", onClick: onCancel, children: cancelText } ), /* @__PURE__ */ jsx( "button", { type: "button", className: "duino-btn duino-btn--primary", onClick: onOk, children: okText } ) ] }) }) ] } ) ] } ); return createPortal(modal, document.body); }; var MessageCtx = createContext(null); function MessageProvider({ children, placement = "top-right", maxCount = 5 }) { const [items, setItems] = useState([]); const timersRef = useRef(/* @__PURE__ */ new Map()); const remove = useCallback((id) => { setItems((prev) => prev.filter((m) => m.id !== id)); const t = timersRef.current.get(id); if (t) { window.clearTimeout(t); timersRef.current.delete(id); } }, []); const add = useCallback((opts) => { var _a, _b, _c, _d; const id = Math.random().toString(36).slice(2); const msg = { id, type: (_a = opts.type) != null ? _a : "info", content: opts.content, duration: (_b = opts.duration) != null ? _b : 2500, closable: (_c = opts.closable) != null ? _c : false }; setItems((prev) => { const next = [...prev, msg]; if (next.length > maxCount) next.shift(); return next; }); if (((_d = msg.duration) != null ? _d : 0) > 0) { const t = window.setTimeout(() => remove(id), msg.duration); timersRef.current.set(id, t); } return { id, close: () => remove(id) }; }, [maxCount, remove]); const portal = useMemo(() => { const root = typeof document !== "undefined" ? document.body : null; if (!root) return null; return createPortal( /* @__PURE__ */ jsx("div", { className: `duino-message duino-message--${placement}`, role: "status", "aria-live": "polite", children: items.map((m) => { var _a; return /* @__PURE__ */ jsxs("div", { className: `duino-message__item duino-message__item--${m.type}`, children: [ /* @__PURE__ */ jsx("span", { className: "duino-message__icon", "aria-hidden": "true", children: m.type === "success" ? "\u2713" : m.type === "error" ? "\u2715" : m.type === "warning" ? "!" : m.type === "loading" ? "\u27F3" : "i" }), /* @__PURE__ */ jsx("div", { className: "duino-message__content", children: m.content }), (m.closable || ((_a = m.duration) != null ? _a : 0) <= 0) && /* @__PURE__ */ jsx( "button", { type: "button", className: "duino-message__close", onClick: () => remove(m.id), "aria-label": "Close notification", children: "\xD7" } ) ] }, m.id); }) }), root ); }, [items, placement, remove]); const ctxValue = useMemo(() => ({ add, remove, placement, maxCount }), [add, remove, placement, maxCount]); return /* @__PURE__ */ jsxs(MessageCtx.Provider, { value: ctxValue, children: [ children, portal ] }); } function useMessage() { const ctx = useContext(MessageCtx); if (!ctx) { throw new Error("useMessage must be used within <MessageProvider>"); } const open = ctx.add; return { open, info: (content, opts) => open({ type: "info", content, ...opts }), success: (content, opts) => open({ type: "success", content, ...opts }), warning: (content, opts) => open({ type: "warning", content, ...opts }), error: (content, opts) => open({ type: "error", content, ...opts }), loading: (content, opts) => { var _a; return open({ type: "loading", content, ...opts, duration: (_a = opts == null ? void 0 : opts.duration) != null ? _a : 0 }); } // por defecto persistente }; } var usePopoverPosition = (triggerRef, popoverRef, placement, visible, autoAdjustOverflow = true) => { const [position, setPosition] = useState({}); const [actualPlacement, setActualPlacement] = useState(placement); const calculatePosition = useCallback(() => { if (!triggerRef.current || !popoverRef.current || !visible) return; const trigger = triggerRef.current.getBoundingClientRect(); const popover = popoverRef.current.getBoundingClientRect(); const viewport = { width: window.innerWidth, height: window.innerHeight }; const spacing = 8; const arrowSize = 8; let top = 0; let left = 0; let currentPlacement = placement; const calculateForPlacement = (targetPlacement) => { const [mainSide, alignment] = targetPlacement.split("-"); switch (mainSide) { case "top": top = trigger.top - popover.height - spacing - arrowSize; left = alignment === "start" ? trigger.left : alignment === "end" ? trigger.right - popover.width : trigger.left + trigger.width / 2 - popover.width / 2; break; case "bottom": top = trigger.bottom + spacing + arrowSize; left = alignment === "start" ? trigger.left : alignment === "end" ? trigger.right - popover.width : trigger.left + trigger.width / 2 - popover.width / 2; break; case "left": left = trigger.left - popover.width - spacing - arrowSize; top = alignment === "start" ? trigger.top : alignment === "end" ? trigger.bottom - popover.height : trigger.top + trigger.height / 2 - popover.height / 2; break; case "right": left = trigger.right + spacing + arrowSize; top = alignment === "start" ? trigger.top : alignment === "end" ? trigger.bottom - popover.height : trigger.top + trigger.height / 2 - popover.height / 2; break; } return { top, left }; }; const { top: calcTop, left: calcLeft } = calculateForPlacement(placement); top = calcTop; left = calcLeft; if (autoAdjustOverflow) { const wouldOverflow = { top: top < spacing, bottom: top + popover.height > viewport.height - spacing, left: left < spacing, right: left + popover.width > viewport.width - spacing }; if (wouldOverflow.top || wouldOverflow.bottom || wouldOverflow.left || wouldOverflow.right) { const alternativePlacements = []; if (placement.startsWith("top")) { alternativePlacements.push("bottom", "bottom-start", "bottom-end"); } else if (placement.startsWith("bottom")) { alternativePlacements.push("top", "top-start", "top-end"); } else if (placement.startsWith("left")) { alternativePlacements.push("right", "right-start", "right-end"); } else if (placement.startsWith("right")) { alternativePlacements.push("left", "left-start", "left-end"); } for (const altPlacement of alternativePlacements) { const { top: altTop, left: altLeft } = calculateForPlacement(altPlacement); const altWouldOverflow = { top: altTop < spacing, bottom: altTop + popover.height > viewport.height - spacing, left: altLeft < spacing, right: altLeft + popover.width > viewport.width - spacing }; if (!altWouldOverflow.top && !altWouldOverflow.bottom && !altWouldOverflow.left && !altWouldOverflow.right) { top = altTop; left = altLeft; currentPlacement = altPlacement; break; } } } if (left < spacing) left = spacing; if (left + popover.width > viewport.width - spacing) { left = viewport.width - popover.width - spacing; } if (top < spacing) top = spacing; if (top + popover.height > viewport.height - spacing) { top = viewport.height - popover.height - spacing; } } setPosition({ position: "fixed", top: Math.round(top), left: Math.round(left), zIndex: 1050 }); setActualPlacement(currentPlacement); }, [triggerRef, popoverRef, placement, visible, autoAdjustOverflow]); useEffect(() => { if (visible) { const timer = setTimeout(calculatePosition, 0); return () => clearTimeout(timer); } }, [visible, calculatePosition]); useEffect(() => { if (visible) { window.addEventListener("scroll", calculatePosition, true); window.addEventListener("resize", calculatePosition); return () => { window.removeEventListener("scroll", calculatePosition, true); window.removeEventListener("resize", calculatePosition); }; } }, [visible, calculatePosition]); return { position, actualPlacement }; }; var useClickOutside = (triggerRef, popoverRef, handler, enabled) => { useEffect(() => { if (!enabled) return; const handleClickOutside = (event) => { var _a, _b; const target = event.target; if (((_a = triggerRef.current) == null ? void 0 : _a.contains(target)) || ((_b = popoverRef.current) == null ? void 0 : _b.contains(target))) { return; } handler(); }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [triggerRef, popoverRef, handler, enabled]); }; var Popover = ({ content, title, children, trigger = "hover", placement = "top", visible: controlledVisible, defaultVisible = false, disabled = false, className = "", overlayClassName = "", arrowPointAtCenter = false, mouseEnterDelay = 100, mouseLeaveDelay = 100, onVisibleChange, getPopupContainer, destroyTooltipOnHide = false, autoAdjustOverflow = true, "aria-label": ariaLabel, id }) => { const [internalVisible, setInternalVisible] = useState(defaultVisible); const triggerRef = useRef(null); const popoverRef = useRef(null); const enterTimerRef = useRef(null); const leaveTimerRef = useRef(null); const isVisible = controlledVisible !== void 0 ? controlledVisible : internalVisible; const triggers = Array.isArray(trigger) ? trigger : [trigger]; const { position, actualPlacement } = usePopoverPosition( triggerRef, popoverRef, placement, isVisible, autoAdjustOverflow ); const handleVisibleChange = useCallback((newVisible) => { if (disabled) return; if (controlledVisible === void 0) { setInternalVisible(newVisible); } onVisibleChange == null ? void 0 : onVisibleChange(newVisible); }, [disabled, controlledVisible, onVisibleChange]); const showPopover = useCallback(() => { if (enterTimerRef.current) { clearTimeout(enterTimerRef.current); } if (leaveTimerRef.current) { clearTimeout(leaveTimerRef.current); } if (!isVisible) { enterTimerRef.current = setTimeout(() => { handleVisibleChange(true); }, mouseEnterDelay); } }, [isVisible, handleVisibleChange, mouseEnterDelay]); const hidePopover = useCallback(() => { if (enterTimerRef.current) { clearTimeout(enterTimerRef.current); } if (leaveTimerRef.current) { clearTimeout(leaveTimerRef.current); } if (isVisible) { leaveTimerRef.current = setTimeout(() => { handleVisibleChange(false); }, mouseLeaveDelay); } }, [isVisible, handleVisibleChange, mouseLeaveDelay]); const togglePopover = useCallback(() => { handleVisibleChange(!isVisible); }, [isVisible, handleVisibleChange]); useClickOutside( triggerRef, popoverRef, () => handleVisibleChange(false), isVisible && triggers.includes("click") ); const eventHandlers = {}; if (triggers.includes("hover")) { eventHandlers.onMouseEnter = showPopover; eventHandlers.onMouseLeave = hidePopover; } if (triggers.includes("click")) { eventHandlers.onClick = togglePopover; } if (triggers.includes("focus")) { eventHandlers.onFocus = showPopover; eventHandlers.onBlur = hidePopover; } if (triggers.includes("contextmenu")) { eventHandlers.onContextMenu = (e) => { e.preventDefault(); togglePopover(); }; } useEffect(() => { return () => { if (enterTimerRef.current) clearTimeout(enterTimerRef.current); if (leaveTimerRef.current) clearTimeout(leaveTimerRef.current); }; }, []); const triggerElement = isValidElement(children) ? cloneElement(children, { ref: (node) => { triggerRef.current = node; const originalRef = children.ref; if (typeof originalRef === "function") { originalRef(node); } else if (originalRef && typeof originalRef === "object") { originalRef.current = node; } }, ...eventHandlers }) : /* @__PURE__ */ jsx("span", { ref: triggerRef, ...eventHandlers, children }); const popoverContent = (isVisible || !destroyTooltipOnHide) && /* @__PURE__ */ jsxs( "div", { ref: popoverRef, className: `duino-popover ${overlayClassName}`, style: { ...position, visibility: isVisible ? "visible" : "hidden", opacity: isVisible ? 1 : 0 }, role: "tooltip", "aria-label": ariaLabel, id, children: [ /* @__PURE__ */ jsxs("div", { className: `duino-popover__content duino-popover__content--${actualPlacement}`, children: [ /* @__PURE__ */ jsx("div", { className: "duino-popover__arrow" }), /* @__PURE__ */ jsxs("div", { className: "duino-popover__inner", children: [ title && /* @__PURE__ */ jsx("div", { className: "duino-popover__title", children: title }), /* @__PURE__ */ jsx("div", { className: "duino-popover__body", children: content }) ] }) ] }), triggers.includes("hover") && /* @__PURE__ */ jsx( "div", { className: "duino-popover__hover-bridge", onMouseEnter: showPopover, onMouseLeave: hidePopover } ) ] } ); return /* @__PURE__ */ jsxs(Fragment, { children: [ triggerElement, getPopupContainer ? createPortal(popoverContent, getPopupContainer()) : createPortal(popoverContent, document.body) ] }); }; Popover.displayName = "Popover"; var useClickOutside2 = (ref, handler) => { useEffect(() => { const listener = (event) => { if (!ref.current || ref.current.contains(event.target)) { return; } handler(); }; document.addEventListener("mousedown", listener); document.addEventListener("touchstart", listener); return () => { document.removeEventListener("mousedown", listener); document.removeEventListener("touchstart", listener); }; }, [ref, handler]); }; var Select = ({ value: controlledValue, defaultValue, placeholder = "Seleccionar...", disabled = false, loading = false, clearable = false, options = [], multiple = false, maxTagCount = 3, maxTagPlaceholder = (omitted) => `+${omitted.length} m\xE1s`, searchable = false, searchPlaceholder = "Buscar...", notFoundContent = "No se encontraron opciones", filterOption, size = "md", variant = "default", className = "", dropdownClassName = "", error = false, success = false, onChange, onSearch, onFocus, onBlur, onDropdownVisibleChange, dropdownMaxHeight = 300, dropdownMatchSelectWidth = true, getPopupContainer, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, id }) => { const [internalValue, setInternalValue] = useState( controlledValue !== void 0 ? controlledValue : defaultValue || (multiple ? [] : "") ); const [isOpen, setIsOpen] = useState(false); const [searchValue, setSearchValue] = useState(""); const [focusedIndex, setFocusedIndex] = useState(-1); const selectRef = useRef(null); const dropdownRef = useRef(null); const searchInputRef = useRef(null); const currentValue = controlledValue !== void 0 ? controlledValue : internalValue; useClickOutside2(selectRef, () => { if (isOpen) { setIsOpen(false); onDropdownVisibleChange == null ? void 0 : onDropdownVisibleChange(false); } }); const filteredOptions = useMemo(() => { if (!searchable || !searchValue) return options; const defaultFilter = (input, option) => { const label = typeof option.label === "string" ? option.label : String(option.value); return label.toLowerCase().includes(input.toLowerCase()); }; const filter = filterOption || defaultFilter; return options.filter((option) => filter(searchValue, option)); }, [options, searchValue, searchable, filterOption]); const groupedOptions = useMemo(() => { const groups = {}; const ungrouped = []; filteredOptions.forEach((option) => { if (option.group) { if (!groups[option.group]) { groups[option.group] = []; } groups[option.group].push(option); } else { ungrouped.push(option); } }); return { groups, ungrouped }; }, [filteredOptions]); const selectedOptions = useMemo(() => { if (multiple && Array.isArray(currentValue)) { return options.filter((option) => currentValue.includes(option.value)); } else if (!multiple && currentValue !== "" && currentValue !== void 0) { return options.filter((option) => option.value === currentValue); } return []; }, [currentValue, options, multiple]); const handleOptionClick = (option) => { if (option.disabled) return; let newValue; let selectedOption; if (multiple) { const currentArray = Array.isArray(currentValue) ? currentValue : []; if (currentArray.includes(option.value)) { newValue = currentArray.filter((v) => v !== option.value); } else { newValue = [...currentArray, option.value]; } selectedOption = options.filter((opt) => newValue.includes(opt.value)); } else { newValue = option.value; selectedOption = option; setIsOpen(false); onDropdownVisibleChange == null ? void 0 : onDropdownVisibleChange(false); } if (controlledValue === void 0) { setInternalValue(newValue); } onChange == null ? void 0 : onChange(newValue, selectedOption); }; const handleTagRemove = (optionValue, e) => { e.stopPropagation(); if (!multiple || !Array.isArray(currentValue)) return; const newValue = currentValue.filter((v) => v !== optionValue); const selectedOption = options.filter((opt) => newValue.includes(opt.value)); if (controlledValue === void 0) { setInternalValue(newValue); } onChange == null ? void 0 : onChange(newValue, selectedOption); }; const handleClear = (e) => { e.stopPropagation(); const newValue = multiple ? [] : ""; if (controlledValue === void 0) { setInternalValue(newValue); } onChange == null ? void 0 : onChange(newValue, multiple ? [] : {}); }; const handleSearch = (value) => { setSearchValue(value); setFocusedIndex(-1); onSearch == null ? void 0 : onSearch(value); }; const handleKeyDown = (e) => { if (disabled) return; switch (e.key) { case "Enter": e.preventDefault(); if (!isOpen) { setIsOpen(true); onDropdownVisibleChange == null ? void 0 : onDropdownVisibleChange(true); } else if (focusedIndex >= 0 && filteredOptions[focusedIndex]) { handleOptionClick(filteredOptions[focusedIndex]); } break; case "Escape": if (isOpen) { setIsOpen(false); onDropdownVisibleChange == null ? void 0 : onDropdownVisibleChange(false); } break; case "ArrowDown": e.preventDefault(); if (!isOpen) { setIsOpen(true); onDropdownVisibleChange == null ? void 0 : onDropdownVisibleChange(true); } else { setFocusedIndex( (prev) => prev < filteredOptions.length - 1 ? prev + 1 : 0 ); } break; case "ArrowUp": e.preventDefault(); if (isOpen) { setFocusedIndex( (prev) => prev > 0 ? prev - 1 : filteredOptions.length - 1 ); } break; } }; const renderTags = () => { if (!multiple || !Array.isArray(currentValue) || currentValue.length === 0) { return null; } const visibleTags = selectedOptions.slice(0, maxTagCount); const hiddenTags = selectedOptions.slice(maxTagCount); return /* @__PURE__ */ jsxs("div", { className: "duino-select__tags", children: [ visibleTags.map((option) => /* @__PURE__ */ jsxs("span", { className: "duino-select__tag", children: [ /* @__PURE__ */ jsx("span", { className: "duino-select__tag-content", children: option.label }), /* @__PURE__ */ jsx( "button", { type: "button", className: "duino-select__tag-close", onClick: (e) => handleTagRemove(option.value, e), "aria-label": `Remover ${option.label}`, children: "\xD7" } ) ] }, option.value)), hiddenTags.length > 0 && /* @__PURE__ */ jsx("span", { className: "duino-select__tag duino-select__tag--more", children: maxTagPlaceholder(hiddenTags.map((opt) => opt.value)) }) ] }); }; const renderSelectorContent = () => { if (multiple && Array.isArray(currentValue) && currentValue.length > 0) { return renderTags(); } if (!multiple && currentValue !== "" && currentValue !== void 0) { const selected = selectedOptions[0]; return selected ? /* @__PURE__ */ jsxs("div", { className: "duino-select__single-value", children: [ selected.icon && /* @__PURE__ */ jsx("span", { className: "duino-select__option-icon", children: selected.icon }), /* @__PURE__ */ jsx("span", { children: selected.label }) ] }) : null; } return /* @__PURE__ */ jsx("span", { className: "duino-select__placeholder", children: placeholder }); }; const selectClasses = [ "duino-select", `duino-select--${size}`, `duino-select--${variant}`, isOpen && "duino-select--open", disabled && "duino-select--disabled", error && "duino-select--error", success && "duino-select--success", loading && "duino-select--loading", multiple && "duino-select--multiple", className ].filter(Boolean).join(" "); const dropdownClasses = [ "duino-select__dropdown", dropdownClassName ].filter(Boolean).join(" "); const [dropdownStyle, setDropdownStyle] = useState({}); useEffect(() => { if (isOpen && selectRef.current) { const rect = selectRef.current.getBoundingClientRect(); const viewportHeight = window.innerHeight; const spaceBelow = viewportHeight - rect.bottom; const spaceAbove = rect.top; const dropdownHeight = Math.min(dropdownMaxHeight, 300); const showAbove = spaceBelow < dropdownHeight && spaceAbove > spaceBelow; setDropdownStyle({ position: "fixed", left: rect.left, width: dropdownMatchSelectWidth ? rect.width : "auto", minWidth: dropdownMatchSelectWidth ? rect.width : 200, maxHeight: dropdownMaxHeight, zIndex: 1050, ...showAbove ? { bottom: viewportHeight - rect.top } : { top: rect.bottom } }); } }, [isOpen, dropdownMaxHeight, dropdownMatchSelectWidth]); useEffect(() => { if (isOpen && searchable && searchInputRef.current) { searchInputRef.current.focus(); } }, [isOpen, searchable]); const dropdownContent = isOpen && /* @__PURE__ */ jsxs( "div", { ref: dropdownRef, className: dropdownClasses, style: dropdownStyle, id: `${id || "select"}-dropdown`, role: "listbox", children: [ searchable && /* @__PURE__ */ jsx("div", { className: "duino-select__search", children: /* @__PURE__ */ jsx( "input", { ref: searchInputRef, type: "text", className: "duino-select__search-input", placeholder: searchPlaceholder, value: searchValue, onChange: (e) => handleSearch(e.target.value), "aria-label": "Buscar opciones" } ) }), /* @__PURE__ */ jsx("div", { className: "duino-select__options", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "duino-select__empty", children: notFoundContent }) : /* @__PURE__ */ jsxs(Fragment, { children: [ groupedOptions.ungrouped.map((option, index) => { const isSelected