UNPKG

shinodalabs-ui

Version:

A collection of reusable React components styled with Tailwind CSS, including buttons, date pickers, modals, tabs, progress bars, forms, checkboxes, radio buttons, selects, inputs, textareas, dropdowns, tooltips, alerts, and badges.

1,168 lines (1,152 loc) 44.4 kB
// src/components/Button/index.tsx import { Loader } from "lucide-react"; import clsx from "clsx"; import { jsx, jsxs } from "react/jsx-runtime"; var baseStyles = "flex items-center h-10 justify-center rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 transition duration-200 ease-in-out cursor-pointer"; var variants = { emerald: "bg-emerald-600 hover:bg-emerald-700 dark:bg-emerald-700 dark:hover:bg-emerald-600 text-white dark:text-white", blue: "bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-600 text-white dark:text-white", red: "bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-600 text-white dark:text-white", neutral: "border border-zinc-300 dark:border-zinc-700 bg-transparent text-zinc-900 dark:text-zinc-100 hover:bg-zinc-100 dark:hover:bg-zinc-800" }; var sizes = { sm: "px-3 py-1 text-sm", md: "px-4 py-2 text-base", lg: "px-5 py-3 text-lg" }; var Button = ({ variant = "emerald", size = "md", icon: Icon, loading = false, children, disabled = false, className, ...props }) => { const isDisabled = disabled || loading; const hasIcon = Icon || loading; const showGap = hasIcon && children; return /* @__PURE__ */ jsxs( "button", { disabled: isDisabled, className: clsx( baseStyles, variants[variant], sizes[size], showGap && "gap-2", isDisabled && "opacity-50 cursor-not-allowed focus:ring-0", className ), ...props, children: [ loading ? /* @__PURE__ */ jsx(Loader, { className: "h-4 w-4 animate-spin" }) : Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }), children && /* @__PURE__ */ jsx("span", { children }) ] } ); }; var Button_default = Button; // src/components/Input/index.tsx import React, { useState, useEffect, useCallback } from "react"; import { Eye, EyeOff } from "lucide-react"; import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime"; var Input = React.forwardRef( ({ label, id, error, type = "text", onChange, value, headerRight, centerContent, maxLength, currency = "BRL", language = "pt-BR", ...props }, ref) => { const inputId = id || `input-${label.toLowerCase().replace(/\s+/g, "-")}`; const [moneyValue, setMoneyValue] = useState(""); const [showPassword, setShowPassword] = useState(false); const formatCurrency = useCallback( (num) => { if (isNaN(num) || num === null) return ""; return new Intl.NumberFormat(language, { style: "currency", currency }).format(num); }, [currency, language] ); const parseNumber = (str) => { if (!str) return 0; const onlyNumbers = str.replace(/[^\d,.-]/g, "").replace(",", "."); const parsed = parseFloat(onlyNumbers); return isNaN(parsed) ? 0 : parsed; }; useEffect(() => { if (type === "money") { if (typeof value === "number") { setMoneyValue(formatCurrency(value)); } else if (typeof value === "string") { const numeric = parseNumber(value); setMoneyValue(formatCurrency(numeric)); } } }, [value, type, formatCurrency]); const handleMoneyChange = (e) => { const rawValue = e.target.value; const onlyDigits = rawValue.replace(/\D/g, ""); const numericValue = parseFloat(onlyDigits) / 100; setMoneyValue(formatCurrency(numericValue)); if (onChange) { onChange(numericValue); } }; const renderLabel = () => /* @__PURE__ */ jsxs2( "div", { className: `flex mb-2 ${centerContent ? "flex-col items-center text-center gap-1" : "items-center justify-between"}`, children: [ /* @__PURE__ */ jsx2("label", { htmlFor: inputId, className: "block text-sm font-medium", children: label }), headerRight && /* @__PURE__ */ jsx2("div", { children: headerRight }) ] } ); const baseInputClass = `w-full px-3 py-2 border rounded-md bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-emerald-500 dark:focus:ring-emerald-600 ${error ? "border-red-500" : "border-zinc-300 dark:border-zinc-700"} ${centerContent ? "text-center" : ""}`; if (type === "money") { return /* @__PURE__ */ jsxs2("div", { children: [ renderLabel(), /* @__PURE__ */ jsx2( "input", { id: inputId, ref, type: "text", value: moneyValue, onChange: handleMoneyChange, placeholder: formatCurrency(0), className: baseInputClass, ...props } ), error && /* @__PURE__ */ jsx2("p", { className: "text-sm text-red-500 mt-2", children: error }) ] }); } if (type === "password") { return /* @__PURE__ */ jsxs2("div", { children: [ renderLabel(), /* @__PURE__ */ jsxs2("div", { className: "flex items-center border border-zinc-300 dark:border-zinc-700 rounded-md px-3 py-2 focus-within:ring-2 focus-within:ring-primary-500 bg-white dark:bg-zinc-900", children: [ /* @__PURE__ */ jsx2( "input", { id: inputId, ref, type: showPassword ? "text" : "password", value: value ?? "", onChange, className: "flex-1 bg-transparent outline-none text-sm text-zinc-900 dark:text-zinc-100", ...props } ), /* @__PURE__ */ jsx2( "button", { type: "button", onClick: () => setShowPassword(!showPassword), className: "ml-2 text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200 transition-all", "aria-label": showPassword ? "Ocultar senha" : "Mostrar senha", "aria-live": "polite", tabIndex: -1, children: showPassword ? /* @__PURE__ */ jsx2(EyeOff, { size: 18 }) : /* @__PURE__ */ jsx2(Eye, { size: 18 }) } ) ] }), error && /* @__PURE__ */ jsx2("p", { className: "text-sm text-red-500 mt-2", children: error }) ] }); } if (type === "number") { return /* @__PURE__ */ jsxs2("div", { children: [ renderLabel(), /* @__PURE__ */ jsx2( "input", { id: inputId, ref, type: "text", value: value ?? "", maxLength, onChange: (e) => { let val = e.target.value; val = val.replace(/[^0-9]/g, ""); if (maxLength && val.length > maxLength) { val = val.slice(0, maxLength); } const syntheticEvent = { ...e, target: { ...e.target, value: val } }; if (onChange) { onChange(syntheticEvent); } }, className: baseInputClass, ...props } ), error && /* @__PURE__ */ jsx2("p", { className: "text-sm text-red-500 mt-2", children: error }) ] }); } return /* @__PURE__ */ jsxs2("div", { children: [ renderLabel(), /* @__PURE__ */ jsx2( "input", { ref, id: inputId, type, value: value ?? "", onChange, className: baseInputClass, ...props } ), error && /* @__PURE__ */ jsx2("p", { className: "text-sm text-red-500 mt-2", children: error }) ] }); } ); Input.displayName = "Input"; var Input_default = Input; // src/components/Select/index.tsx import React2 from "react"; import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime"; var Select = React2.forwardRef( ({ label, options, id, error, ...props }, ref) => { const selectId = id || `select-${label?.toLowerCase().replace(/\s+/g, "-")}`; return /* @__PURE__ */ jsxs3("div", { children: [ label && /* @__PURE__ */ jsx3("label", { htmlFor: selectId, className: "block text-sm font-medium mb-2", children: label }), /* @__PURE__ */ jsx3( "select", { id: selectId, ref, ...props, className: `w-full px-3 py-2 border rounded-md bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-emerald-500 dark:focus:ring-emerald-600 cursor-pointer ${error ? "border-red-500" : "border-zinc-300 dark:border-zinc-700"}`, children: options.map((option) => /* @__PURE__ */ jsx3("option", { value: option.value, children: option.label }, option.value)) } ), error && /* @__PURE__ */ jsx3("p", { className: "text-sm text-red-500 mt-2", children: error }) ] }); } ); Select.displayName = "Select"; var Select_default = Select; // src/components/RadioGroup/index.tsx import { useId } from "react"; import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime"; var RadioGroup = ({ name, label, value, options, onChange }) => { const groupId = useId(); return /* @__PURE__ */ jsxs4("div", { className: "space-y-3", children: [ label && /* @__PURE__ */ jsx4("p", { className: "text-sm font-medium", children: label }), options.map((option) => { const id = `${groupId}-${option.value}`; return /* @__PURE__ */ jsxs4( "label", { htmlFor: id, className: "flex items-center space-x-2 cursor-pointer", children: [ /* @__PURE__ */ jsx4( "input", { id, type: "radio", name, value: option.value, checked: value === option.value, onChange: () => onChange(option.value), className: "w-4 h-4 text-emerald-600 border-zinc-300 dark:border-zinc-700 focus:ring-emerald-500 dark:focus:ring-emerald-600" } ), /* @__PURE__ */ jsx4("span", { className: "text-sm capitalize", children: option.label }) ] }, option.value ); }) ] }); }; var RadioGroup_default = RadioGroup; // src/components/ToggleSwitch/index.tsx import { useId as useId2 } from "react"; import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime"; var ToggleSwitch = ({ label, description, checked, onChange }) => { const id = useId2(); return /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ jsxs5("div", { className: "space-y-0.5", children: [ /* @__PURE__ */ jsx5("label", { htmlFor: id, className: "text-sm font-medium", children: label }), description && /* @__PURE__ */ jsx5("p", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: description }) ] }), /* @__PURE__ */ jsxs5( "label", { htmlFor: id, className: "relative inline-flex items-center cursor-pointer", children: [ /* @__PURE__ */ jsx5( "input", { id, type: "checkbox", checked, onChange: (e) => onChange(e.target.checked), className: "sr-only peer" } ), /* @__PURE__ */ jsx5("div", { className: "w-11 h-6 bg-zinc-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-emerald-300 dark:peer-focus:ring-emerald-800 rounded-full peer dark:bg-zinc-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-zinc-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-zinc-600 peer-checked:bg-emerald-600" }) ] } ) ] }); }; var ToggleSwitch_default = ToggleSwitch; // src/components/DatePicker/index.tsx import { useState as useState2, useId as useId3, useRef, useEffect as useEffect2 } from "react"; import { createPortal } from "react-dom"; import { DayPicker } from "react-day-picker"; import { format, isValid } from "date-fns"; import { ptBR } from "date-fns/locale"; import { XIcon } from "lucide-react"; import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime"; var DatePicker = ({ field, error, label = "Data", placeholder = "Selecione a data" }) => { const [showCalendar, setShowCalendar] = useState2(false); const [selectedDate, setSelectedDate] = useState2(); const id = useId3(); const containerRef = useRef(null); const calendarRef = useRef(null); const inputRef = useRef(null); const [position, setPosition] = useState2({ top: 0, left: 0 }); useEffect2(() => { if (!field.value) { setSelectedDate(void 0); return; } try { const [year, month, day] = field.value.split("T")[0].split("-").map(Number); const localDate = new Date(year, month - 1, day); if (isValid(localDate)) { setSelectedDate(localDate); } else { setSelectedDate(void 0); } } catch { setSelectedDate(void 0); } }, [field.value]); useEffect2(() => { const updatePosition = () => { if (!inputRef.current || !calendarRef.current) return; const inputRect = inputRef.current.getBoundingClientRect(); const calendarHeight = calendarRef.current.offsetHeight; const spaceBelow = window.innerHeight - inputRect.bottom; const spaceAbove = inputRect.top; let top; if (spaceBelow > calendarHeight + 20) { top = inputRect.bottom + window.scrollY + 8; } else if (spaceAbove > calendarHeight + 20) { top = inputRect.top + window.scrollY - calendarHeight - 8; } else { top = window.scrollY + 20; } setPosition({ top, left: inputRect.left + window.scrollX }); }; if (showCalendar) { updatePosition(); window.addEventListener("resize", updatePosition); window.addEventListener("scroll", updatePosition); return () => { window.removeEventListener("resize", updatePosition); window.removeEventListener("scroll", updatePosition); }; } }, [showCalendar]); useEffect2(() => { function handleClickOutside(event) { if (containerRef.current && !containerRef.current.contains(event.target) && calendarRef.current && !calendarRef.current.contains(event.target)) { setShowCalendar(false); } } if (showCalendar) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [showCalendar]); const calendar = /* @__PURE__ */ jsxs6( "div", { ref: calendarRef, className: "fixed z-[99999] bg-white dark:bg-zinc-900 rounded-lg border border-zinc-200 dark:border-zinc-700 shadow-lg p-4 max-w-[320px] w-[min(320px,calc(100vw-40px))] pointer-events-auto", style: { top: position.top, left: position.left }, children: [ /* @__PURE__ */ jsxs6("div", { className: "flex justify-between items-center mb-2", children: [ /* @__PURE__ */ jsx6("span", { className: "font-medium text-sm text-zinc-700 dark:text-zinc-200", children: "Selecione uma data" }), /* @__PURE__ */ jsxs6("div", { className: "flex gap-2 items-center", children: [ /* @__PURE__ */ jsx6( "button", { type: "button", className: "text-xs text-emerald-500 cursor-pointer", onClick: () => { field.onChange(""); setShowCalendar(false); }, children: "Limpar" } ), /* @__PURE__ */ jsx6( "button", { type: "button", className: "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200 text-sm cursor-pointer p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-full", onClick: () => setShowCalendar(false), "aria-label": "Fechar calend\xE1rio", children: /* @__PURE__ */ jsx6(XIcon, { className: "w-5 h-5" }) } ) ] }) ] }), /* @__PURE__ */ jsx6( DayPicker, { mode: "single", navLayout: "around", selected: selectedDate, onSelect: (date) => { if (date) { const localDate = new Date( date.getFullYear(), date.getMonth(), date.getDate(), 12, 0, 0 ); const yyyy = localDate.getFullYear(); const mm = String(localDate.getMonth() + 1).padStart(2, "0"); const dd = String(localDate.getDate()).padStart(2, "0"); const formattedDate = `${yyyy}-${mm}-${dd}`; field.onChange(formattedDate); } else { field.onChange(""); } setShowCalendar(false); }, locale: ptBR, formatters: { formatCaption: (date, options) => { const formatted = format(date, "MMMM yyyy", { locale: options?.locale }); return formatted.charAt(0).toUpperCase() + formatted.slice(1); } }, classNames: { selected: "bg-emerald-500 text-white rounded-full", today: "text-emerald-100 dark:text-emerald-800" } } ) ] } ); return /* @__PURE__ */ jsxs6("div", { ref: containerRef, className: "relative", children: [ /* @__PURE__ */ jsx6( "label", { htmlFor: id, className: "block text-sm font-medium mb-2 text-zinc-700 dark:text-zinc-300", children: label } ), /* @__PURE__ */ jsx6( "input", { ref: inputRef, id, type: "text", readOnly: true, className: `w-full px-3 py-2 border rounded-md bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 cursor-pointer focus:outline-none focus:ring-2 focus:ring-emerald-500 dark:focus:ring-emerald-600 ${error ? "border-red-500" : "border-zinc-300 dark:border-zinc-700"}`, onClick: () => setShowCalendar((v) => !v), value: selectedDate ? format(selectedDate, "dd/MM/yyyy") : "", placeholder } ), showCalendar && typeof window !== "undefined" ? createPortal(calendar, document.body) : null, error && /* @__PURE__ */ jsx6("p", { className: "text-sm text-red-500 mt-2", children: error }) ] }); }; var DatePicker_default = DatePicker; // src/components/MonthDatePicker/index.tsx import { XIcon as XIcon2 } from "lucide-react"; import { useState as useState3, useEffect as useEffect3, useRef as useRef2 } from "react"; import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime"; var monthNames = [ "Janeiro", "Fevereiro", "Mar\xE7o", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro" ]; var MonthDatePicker = ({ selectedDate, onChange, minDate, maxDate, isOpen, onClose }) => { const [year, setYear] = useState3( selectedDate?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear() ); const modalRef = useRef2(null); useEffect3(() => { function handleClickOutside(event) { if (modalRef.current && !modalRef.current.contains(event.target)) { onClose(); } } if (isOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [isOpen, onClose]); useEffect3(() => { if (selectedDate) { setYear(selectedDate.getFullYear()); } }, [selectedDate]); const changeYear = (delta) => { setYear((prev) => prev + delta); }; const isDisabled = (year2, month) => { const date = new Date(year2, month, 1); if (minDate && date < new Date(minDate.getFullYear(), minDate.getMonth(), 1)) return true; if (maxDate && date > new Date(maxDate.getFullYear(), maxDate.getMonth(), 1)) return true; return false; }; if (!isOpen) return null; return /* @__PURE__ */ jsxs7(Fragment, { children: [ /* @__PURE__ */ jsx7("div", { className: "fixed inset-0 z-40 bg-black/20 backdrop-blur-sm" }), /* @__PURE__ */ jsxs7( "div", { ref: modalRef, className: "fixed z-50 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\n bg-white dark:bg-zinc-900 rounded-lg border border-zinc-300 dark:border-zinc-700\n shadow-lg p-6 w-[320px] max-w-full", children: [ /* @__PURE__ */ jsx7("div", { className: "flex justify-end mb-2", children: /* @__PURE__ */ jsx7( "button", { type: "button", className: "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200 p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-full", onClick: onClose, "aria-label": "Fechar calend\xE1rio", children: /* @__PURE__ */ jsx7(XIcon2, { className: "w-5 h-5" }) } ) }), /* @__PURE__ */ jsxs7("div", { className: "flex justify-between items-center mb-4", children: [ /* @__PURE__ */ jsx7( "button", { onClick: () => changeYear(-1), className: "px-2 py-1 rounded hover:bg-zinc-100 dark:hover:bg-zinc-800", "aria-label": "Ano anterior", type: "button", children: "<" } ), /* @__PURE__ */ jsx7("span", { className: "font-semibold text-lg text-zinc-900 dark:text-zinc-100", children: year }), /* @__PURE__ */ jsx7( "button", { onClick: () => changeYear(1), className: "px-2 py-1 rounded hover:bg-zinc-100 dark:hover:bg-zinc-800", "aria-label": "Ano seguinte", type: "button", children: ">" } ) ] }), /* @__PURE__ */ jsx7("div", { className: "grid grid-cols-3 gap-2", children: monthNames.map((monthName, index) => { const disabled = isDisabled(year, index); const isSelected = selectedDate?.getFullYear() === year && selectedDate?.getMonth() === index; return /* @__PURE__ */ jsx7( "button", { disabled, onClick: () => { onChange(new Date(year, index, 1)); onClose(); }, className: `py-2 rounded text-sm font-medium ${disabled ? "text-zinc-400 cursor-not-allowed" : isSelected ? "bg-emerald-500 text-white" : "hover:bg-emerald-100 dark:hover:bg-emerald-700"}`, type: "button", children: monthName }, monthName ); }) }) ] } ) ] }); }; var MonthDatePicker_default = MonthDatePicker; // src/components/Modal/index.tsx import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime"; var Modal = ({ isOpen, title, description, children, onClose, onConfirm, isLoading = false, confirmLabel = "Salvar", cancelLabel = "Cancelar" }) => { if (!isOpen) return null; const handleBackdropClick = (e) => { if (e.target === e.currentTarget) { onClose(); } }; return /* @__PURE__ */ jsx8( "div", { className: "fixed inset-0 z-40 bg-black/60 animate-in fade-in duration-200 flex items-center justify-center p-4 sm:p-6", onClick: handleBackdropClick, children: /* @__PURE__ */ jsxs8( "div", { className: "\n bg-white dark:bg-zinc-950\n border border-zinc-200 dark:border-zinc-800\n rounded-lg\n flex flex-col\n max-h-[90vh]\n overflow-visible\n z-50\n relative\n w-auto max-w-[90vw]\n ", children: [ /* @__PURE__ */ jsxs8("div", { className: "p-6 overflow-y-auto max-h-[calc(90vh - 96px)]", children: [ /* @__PURE__ */ jsx8("h3", { className: "text-lg font-semibold mb-2", children: title }), /* @__PURE__ */ jsxs8("div", { className: "w-full max-w-full", children: [ description && /* @__PURE__ */ jsx8("p", { className: "text-sm text-zinc-500 dark:text-zinc-400 mb-4", children: description }), children ] }) ] }), /* @__PURE__ */ jsxs8("div", { className: "p-6 border-t border-zinc-200 dark:border-zinc-800 flex gap-2 justify-end", children: [ /* @__PURE__ */ jsx8(Button_default, { disabled: isLoading, onClick: onClose, variant: "neutral", children: cancelLabel }), onConfirm && /* @__PURE__ */ jsx8(Button_default, { disabled: isLoading, onClick: onConfirm, children: confirmLabel }) ] }) ] } ) } ); }; var Modal_default = Modal; // src/components/Tabs/index.tsx import { jsx as jsx9 } from "react/jsx-runtime"; var Tabs = ({ tabs, selectedValue, onChange, disabled = false }) => { return /* @__PURE__ */ jsx9("div", { className: "flex border-b border-zinc-200 dark:border-zinc-800", children: tabs.map((tab, index) => { const isActive = selectedValue === tab.value; const isFirst = index === 0; const isLast = index === tabs.length - 1; return /* @__PURE__ */ jsx9( "button", { onClick: () => !disabled && onChange(tab.value), disabled, className: ` px-4 py-2 text-sm font-medium transition-all ${disabled ? "cursor-not-allowed text-zinc-400 dark:text-zinc-600" : "cursor-pointer hover:bg-zinc-100 dark:hover:bg-zinc-800"} ${isActive && !disabled ? "border-b-2 border-emerald-600 dark:border-emerald-500 text-emerald-600 dark:text-emerald-500" : !disabled ? "text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300" : ""} ${isFirst ? "rounded-tl-md" : ""} ${isLast ? "rounded-tr-md" : ""} `, children: tab.label }, tab.value ); }) }); }; var Tabs_default = Tabs; // src/components/ProgressBar/index.tsx import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime"; var ProgressBar = ({ percentage }) => { const isWarning = percentage >= 75 && percentage < 90; const isDanger = percentage >= 90; const progressBarBg = isDanger ? "bg-red-200 dark:bg-red-900" : isWarning ? "bg-amber-200 dark:bg-amber-900" : "bg-zinc-200 dark:bg-zinc-700"; const progressFillColor = isDanger ? "bg-red-500" : isWarning ? "bg-amber-500" : "bg-emerald-500"; const percentageTextColor = isDanger ? "text-red-500" : isWarning ? "text-amber-500" : "text-zinc-500 dark:text-zinc-400"; return /* @__PURE__ */ jsxs9(Fragment2, { children: [ /* @__PURE__ */ jsx10("div", { className: `w-full rounded-full h-2 ${progressBarBg}`, children: /* @__PURE__ */ jsx10( "div", { className: `h-2 rounded-full transition-all duration-300 ${progressFillColor}`, style: { width: `${Math.min(percentage, 100)}%` } } ) }), /* @__PURE__ */ jsxs9("p", { className: `text-right text-sm mt-1 ${percentageTextColor}`, children: [ percentage, "%" ] }) ] }); }; var ProgressBar_default = ProgressBar; // src/components/IconButton/index.tsx import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime"; var IconButton = ({ icon: Icon, name, onClick, disabled }) => { return /* @__PURE__ */ jsxs10( "button", { onClick, disabled, className: "p-2 rounded-md cursor-pointer hover:bg-zinc-100 dark:hover:bg-zinc-800 text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100 transition-all duration-200 ease-in-out disabled:opacity-50 disabled:cursor-not-allowed", children: [ /* @__PURE__ */ jsx11(Icon, { className: "h-5 w-5" }), /* @__PURE__ */ jsx11("span", { className: "sr-only", children: name }) ] } ); }; var IconButton_default = IconButton; // src/components/Dialog/index.tsx import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime"; var Dialog = ({ isOpen, title, description, confirmLabel = "Confirmar", cancelLabel = "Cancelar", onClose, onConfirm, isLoading = false }) => { if (!isOpen) return null; return /* @__PURE__ */ jsx12("div", { className: "fixed inset-0 z-50 bg-black/60 backdrop-blur-sm animate-in fade-in duration-200 flex items-center justify-center p-4 sm:p-6", children: /* @__PURE__ */ jsxs11( "div", { className: "\n w-full h-full sm:h-auto sm:max-w-sm\n bg-white dark:bg-zinc-950\n border border-zinc-200 dark:border-zinc-800\n sm:rounded-lg rounded-none\n flex flex-col justify-between\n ", children: [ /* @__PURE__ */ jsxs11("div", { className: "p-6 overflow-y-auto", children: [ /* @__PURE__ */ jsx12("h3", { className: "text-lg font-semibold mb-2", children: title }), description && /* @__PURE__ */ jsx12("div", { className: "text-sm text-zinc-600 dark:text-zinc-400", children: description }) ] }), /* @__PURE__ */ jsxs11("div", { className: "p-6 border-t border-zinc-200 dark:border-zinc-800 flex gap-2 justify-end", children: [ /* @__PURE__ */ jsx12(Button_default, { onClick: onClose, disabled: isLoading, variant: "neutral", children: cancelLabel }), /* @__PURE__ */ jsx12(Button_default, { onClick: onConfirm, loading: isLoading, children: confirmLabel }) ] }) ] } ) }); }; var Dialog_default = Dialog; // src/components/Pagination/index.tsx import { useState as useState4, useEffect as useEffect4 } from "react"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime"; var Pagination = ({ currentPage, totalPages, totalItems, onPageChange, perPage, onItemsPerPageChange, optionsItemsPerPage, labels }) => { const [itemsPerPage, setItemsPerPage] = useState4(perPage || 10); useEffect4(() => { onItemsPerPageChange(itemsPerPage); }, [itemsPerPage, onItemsPerPageChange, onPageChange]); const handlePrevious = () => { if (currentPage > 1) onPageChange(currentPage - 1); }; const handleNext = () => { if (currentPage < totalPages) onPageChange(currentPage + 1); }; return /* @__PURE__ */ jsxs12("div", { className: "rounded-lg border bg-white dark:bg-zinc-950 shadow-sm flex flex-row sm:flex-col items-center justify-between p-6 border-zinc-200 dark:border-zinc-800 text-sm text-zinc-600 dark:text-zinc-400 gap-2", children: [ /* @__PURE__ */ jsxs12("span", { className: "hidden sm:inline", children: [ labels.showing, " ", (currentPage - 1) * itemsPerPage + 1, " -", " ", Math.min(currentPage * itemsPerPage, totalItems), " ", labels.of, " ", totalItems, " ", labels.results ] }), /* @__PURE__ */ jsxs12("div", { className: "flex flex-row sm:flex-col items-center justify-center gap-2", children: [ /* @__PURE__ */ jsx13( Select_default, { id: "itemsPerPageSelect", className: "flex-1", value: itemsPerPage, onChange: (e) => setItemsPerPage(Number(e.target.value)), "aria-label": labels.itemsPerPage, options: optionsItemsPerPage } ), /* @__PURE__ */ jsxs12("div", { className: "flex flex-row items-center gap-2", children: [ /* @__PURE__ */ jsx13( IconButton_default, { onClick: handlePrevious, disabled: currentPage === 1, icon: ChevronLeft, name: labels.previous, "aria-label": labels.previous } ), /* @__PURE__ */ jsxs12("span", { className: "px-2", children: [ labels.page, " ", currentPage, " ", labels.of, " ", totalPages ] }), /* @__PURE__ */ jsx13( IconButton_default, { onClick: handleNext, disabled: currentPage === totalPages, icon: ChevronRight, name: labels.next, "aria-label": labels.next } ) ] }) ] }) ] }); }; var Pagination_default = Pagination; // src/components/DateRange/index.tsx import { useState as useState5, useRef as useRef3, useEffect as useEffect5, useCallback as useCallback2 } from "react"; import { Calendar, XIcon as XIcon3 } from "lucide-react"; import { format as format2 } from "date-fns"; import { ptBR as ptBR2 } from "date-fns/locale"; import { DayPicker as DayPicker2 } from "react-day-picker"; import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime"; var DateRange = ({ selectedRange, onChange, labels = {} }) => { const [showCalendar, setShowCalendar] = useState5(false); const buttonRef = useRef3(null); const [position, setPosition] = useState5({ top: 0, left: 0, transform: "" }); const updatePosition = useCallback2(() => { if (buttonRef.current) { const rect = buttonRef.current.getBoundingClientRect(); const isMobile = window.innerWidth < 640; setPosition({ top: rect.bottom + 8, left: isMobile ? window.innerWidth / 2 : rect.left, transform: isMobile ? "translateX(-50%)" : "none" }); } }, []); useEffect5(() => { if (showCalendar) { updatePosition(); window.addEventListener("resize", updatePosition); return () => window.removeEventListener("resize", updatePosition); } }, [showCalendar, updatePosition]); const formatRange = (from, to) => { if (!from) return ""; if (!to) return format2(from, "dd/MM/yyyy"); return `${format2(from, "dd/MM/yyyy")} a ${format2(to, "dd/MM/yyyy")}`; }; return /* @__PURE__ */ jsxs13(Fragment3, { children: [ /* @__PURE__ */ jsxs13( "button", { ref: buttonRef, className: `p-3 rounded-md border flex items-center gap-2 transition-colors cursor-pointer ${selectedRange?.from ? "bg-emerald-100 dark:bg-emerald-900 text-emerald-600 dark:text-emerald-400" : "border-zinc-300 dark:border-zinc-700 hover:bg-zinc-100 dark:hover:bg-zinc-800"}`, "aria-label": labels.filterByDate || "Filtrar por data", title: labels.filterByDate || "Filtrar por data", onClick: () => setShowCalendar(true), children: [ /* @__PURE__ */ jsx14(Calendar, { className: "h-4 w-4" }), selectedRange?.from && /* @__PURE__ */ jsx14("span", { className: "text-sm", children: formatRange(selectedRange.from, selectedRange.to) }) ] } ), showCalendar && /* @__PURE__ */ jsxs13( "div", { className: "absolute z-50 p-4 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 shadow-lg rounded-lg", style: { top: position.top, left: position.left, transform: position.transform }, children: [ /* @__PURE__ */ jsxs13("div", { className: "flex justify-between items-center w-full", children: [ /* @__PURE__ */ jsx14("span", { className: "font-medium text-sm text-zinc-700 dark:text-zinc-200", children: labels.selectDate || "Selecione uma data" }), /* @__PURE__ */ jsxs13("div", { className: "flex gap-2 items-center", children: [ /* @__PURE__ */ jsx14( "button", { onClick: () => { onChange({ from: void 0, to: void 0 }); setShowCalendar(false); }, className: "text-xs text-emerald-500 cursor-pointer", children: labels.clear || "Limpar" } ), /* @__PURE__ */ jsx14( "button", { onClick: () => setShowCalendar(false), className: "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200 p-2 rounded-full hover:bg-zinc-100 dark:hover:bg-zinc-800", "aria-label": labels.closeCalendar || "Fechar calend\xE1rio", children: /* @__PURE__ */ jsx14(XIcon3, { className: "w-4 h-4" }) } ) ] }) ] }), /* @__PURE__ */ jsx14( DayPicker2, { mode: "range", selected: selectedRange, onSelect: (range) => { if (range) { onChange({ from: range.from ?? void 0, to: range.to ?? void 0 }); } }, locale: ptBR2, formatters: { formatCaption: (date, options) => { const formatted = format2(date, "MMMM yyyy", { locale: options?.locale }); return formatted.charAt(0).toUpperCase() + formatted.slice(1); } }, classNames: { selected: "bg-emerald-500 text-white rounded-full", today: "text-emerald-100 dark:text-white rounded-full bg-emerald-500", range_start: "bg-emerald-200 dark:bg-emerald-800 rounded-full", range_end: "bg-emerald-200 dark:bg-emerald-800 rounded-full", range_middle: "bg-emerald-200 dark:bg-emerald-200 rounded-full text-emerald-800 dark:text-emerald-800" } } ) ] } ) ] }); }; var DateRange_default = DateRange; // src/components/FilterCard/index.tsx import { useRef as useRef4, useState as useState6, useEffect as useEffect6 } from "react"; import { ChevronUp, ChevronDown } from "lucide-react"; import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime"; var FilterCard = ({ children, title = "Filtros" }) => { const [isOpen, setIsOpen] = useState6(true); const [height, setHeight] = useState6(0); const contentRef = useRef4(null); useEffect6(() => { if (isOpen && contentRef.current) { setHeight(contentRef.current.scrollHeight); const timeout = setTimeout(() => setHeight("auto"), 300); return () => clearTimeout(timeout); } else if (!isOpen) { setHeight(contentRef.current?.scrollHeight || 0); requestAnimationFrame(() => { setHeight(0); }); } }, [isOpen]); return /* @__PURE__ */ jsxs14("div", { className: "rounded-lg border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 shadow-sm", children: [ /* @__PURE__ */ jsxs14("div", { className: "p-6 flex items-center justify-between", children: [ /* @__PURE__ */ jsx15("h3", { className: "text-lg font-semibold", children: title }), /* @__PURE__ */ jsx15( IconButton_default, { icon: isOpen ? ChevronUp : ChevronDown, onClick: () => setIsOpen((prev) => !prev), "aria-label": isOpen ? "Ocultar filtros" : "Mostrar filtros", name: "toggle-filters" } ) ] }), /* @__PURE__ */ jsx15( "div", { ref: contentRef, style: { height: height === "auto" ? "auto" : `${height}px`, overflow: "hidden", transition: "height 300ms ease" }, children: /* @__PURE__ */ jsx15("div", { className: "px-6 py-6", children }) } ) ] }); }; var FilterCard_default = FilterCard; // src/components/Table/index.tsx import { Edit, Trash } from "lucide-react"; import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime"; var TableRoot = ({ children }) => /* @__PURE__ */ jsx16("div", { className: "rounded-lg border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 shadow-sm", children: /* @__PURE__ */ jsx16("div", { className: "p-6", children: /* @__PURE__ */ jsx16("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsx16("table", { className: "w-full", children }) }) }) }); var TableHeader = ({ columns }) => /* @__PURE__ */ jsx16("thead", { children: /* @__PURE__ */ jsx16("tr", { className: "border-b border-zinc-200 dark:border-zinc-800", children: columns.map( ({ key, label, align, hidden, showMobile }) => hidden ? null : /* @__PURE__ */ jsx16( "th", { className: `py-3 px-4 font-medium text-zinc-500 dark:text-zinc-400 ${align === "right" ? "text-right" : "text-left"} ${showMobile === true ? "table-cell sm:hidden" : showMobile === false ? "hidden sm:table-cell" : "table-cell"}`, children: label }, String(key) ) ) }) }); var TableBody = ({ children }) => /* @__PURE__ */ jsx16("tbody", { children }); function TableRow({ data, columns, onClickEdit, onClickDelete }) { return /* @__PURE__ */ jsx16("tr", { className: "border-b border-zinc-200 dark:border-zinc-800 hover:bg-zinc-50 dark:hover:bg-zinc-900", children: columns.map( ({ key, align, hidden, render, format: format3, style, showMobile }) => { if (hidden) return null; const visibilityClass = showMobile === true ? "table-cell sm:hidden" : showMobile === false ? "hidden sm:table-cell" : "table-cell"; if (key === "actions") { return /* @__PURE__ */ jsx16( "td", { className: `py-3 px-4 ${align === "right" ? "text-right" : "text-left"} ${visibilityClass}`, children: /* @__PURE__ */ jsxs15("div", { className: "flex justify-end gap-2", children: [ onClickEdit && /* @__PURE__ */ jsx16( "button", { className: "p-1 rounded-md text-zinc-500 hover:text-emerald-500 dark:text-zinc-400 dark:hover:text-emerald-500 transition-all", "aria-label": "Editar", title: "Editar", onClick: onClickEdit, children: /* @__PURE__ */ jsx16(Edit, { className: "w-4 h-4" }) } ), onClickDelete && /* @__PURE__ */ jsx16( "button", { className: "p-1 rounded-md text-zinc-500 hover:text-red-500 dark:text-zinc-400 dark:hover:text-red-400 transition-all", "aria-label": "Excluir", title: "Excluir", onClick: onClickDelete, children: /* @__PURE__ */ jsx16(Trash, { className: "w-4 h-4" }) } ) ] }) }, "actions" ); } const value = data[key]; const content = render ? render(value, data) : format3 ? format3(value, data) : String(value); const cellStyle = style ? style(value, data) : ""; return /* @__PURE__ */ jsx16( "td", { className: `py-3 px-4 ${align === "right" ? "text-right" : "text-left"} ${cellStyle} ${visibilityClass}`, children: content }, String(key) ); } ) }); } var Table = Object.assign(TableRoot, { Header: TableHeader, Body: TableBody, Row: TableRow }); var Table_default = Table; export { Button_default as Button, DatePicker_default as DatePicker, DateRange_default as DateRange, Dialog_default as Dialog, FilterCard_default as FilterCard, IconButton_default as IconButton, Input_default as Input, Modal_default as Modal, MonthDatePicker_default as MonthDatePicker, Pagination_default as Pagination, ProgressBar_default as ProgressBar, RadioGroup_default as RadioGroup, Select_default as Select, Table_default as Table, Tabs_default as Tabs, ToggleSwitch_default as ToggleSwitch };