UNPKG

analytica-frontend-lib

Version:

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

1,425 lines (1,407 loc) 77.2 kB
// src/components/NotificationCard/NotificationCard.tsx import { DotsThreeVertical, Bell as Bell2 } from "phosphor-react"; import { useState as useState4, useEffect as useEffect6 } from "react"; // src/utils/utils.ts import { clsx } from "clsx"; import { twMerge } from "tailwind-merge"; function cn(...inputs) { return twMerge(clsx(inputs)); } // src/components/DropdownMenu/DropdownMenu.tsx import { CaretRight, SignOut, User } from "phosphor-react"; import { forwardRef as forwardRef2, useEffect as useEffect4, useLayoutEffect, useRef, isValidElement, Children, cloneElement, useState as useState2 } from "react"; import { createPortal } from "react-dom"; import { create as create2, useStore } from "zustand"; // src/components/Button/Button.tsx import { jsx, jsxs } from "react/jsx-runtime"; var VARIANT_ACTION_CLASSES = { solid: { primary: "bg-primary-950 text-text border border-primary-950 hover:bg-primary-800 hover:border-primary-800 focus-visible:outline-none focus-visible:bg-primary-950 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:bg-primary-700 active:border-primary-700 disabled:bg-primary-500 disabled:border-primary-500 disabled:opacity-40 disabled:cursor-not-allowed", positive: "bg-success-500 text-text border border-success-500 hover:bg-success-600 hover:border-success-600 focus-visible:outline-none focus-visible:bg-success-500 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:bg-success-700 active:border-success-700 disabled:bg-success-500 disabled:border-success-500 disabled:opacity-40 disabled:cursor-not-allowed", negative: "bg-error-500 text-text border border-error-500 hover:bg-error-600 hover:border-error-600 focus-visible:outline-none focus-visible:bg-error-500 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:bg-error-700 active:border-error-700 disabled:bg-error-500 disabled:border-error-500 disabled:opacity-40 disabled:cursor-not-allowed" }, outline: { primary: "bg-transparent text-primary-950 border border-primary-950 hover:bg-background-50 hover:text-primary-400 hover:border-primary-400 focus-visible:border-0 focus-visible:outline-none focus-visible:text-primary-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-primary-700 active:border-primary-700 disabled:opacity-40 disabled:cursor-not-allowed", positive: "bg-transparent text-success-500 border border-success-300 hover:bg-background-50 hover:text-success-400 hover:border-success-400 focus-visible:border-0 focus-visible:outline-none focus-visible:text-success-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-success-700 active:border-success-700 disabled:opacity-40 disabled:cursor-not-allowed", negative: "bg-transparent text-error-500 border border-error-300 hover:bg-background-50 hover:text-error-400 hover:border-error-400 focus-visible:border-0 focus-visible:outline-none focus-visible:text-error-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-error-700 active:border-error-700 disabled:opacity-40 disabled:cursor-not-allowed" }, link: { primary: "bg-transparent text-primary-950 hover:text-primary-400 focus-visible:outline-none focus-visible:text-primary-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-primary-700 disabled:opacity-40 disabled:cursor-not-allowed", positive: "bg-transparent text-success-500 hover:text-success-400 focus-visible:outline-none focus-visible:text-success-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-success-700 disabled:opacity-40 disabled:cursor-not-allowed", negative: "bg-transparent text-error-500 hover:text-error-400 focus-visible:outline-none focus-visible:text-error-600 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-indicator-info active:text-error-700 disabled:opacity-40 disabled:cursor-not-allowed" } }; var SIZE_CLASSES = { "extra-small": "text-xs px-3.5 py-2", small: "text-sm px-4 py-2.5", medium: "text-md px-5 py-2.5", large: "text-lg px-6 py-3", "extra-large": "text-lg px-7 py-3.5" }; var Button = ({ children, iconLeft, iconRight, size = "medium", variant = "solid", action = "primary", className = "", disabled, type = "button", ...props }) => { const sizeClasses = SIZE_CLASSES[size]; const variantClasses = VARIANT_ACTION_CLASSES[variant][action]; const baseClasses = "inline-flex items-center justify-center rounded-full cursor-pointer font-medium"; return /* @__PURE__ */ jsxs( "button", { className: cn(baseClasses, variantClasses, sizeClasses, className), disabled, type, ...props, children: [ iconLeft && /* @__PURE__ */ jsx("span", { className: "mr-2 flex items-center", children: iconLeft }), children, iconRight && /* @__PURE__ */ jsx("span", { className: "ml-2 flex items-center", children: iconRight }) ] } ); }; var Button_default = Button; // src/components/Text/Text.tsx import { jsx as jsx2 } from "react/jsx-runtime"; var Text = ({ children, size = "md", weight = "normal", color = "text-text-950", as, className = "", ...props }) => { let sizeClasses = ""; let weightClasses = ""; const sizeClassMap = { "2xs": "text-2xs", xs: "text-xs", sm: "text-sm", md: "text-md", lg: "text-lg", xl: "text-xl", "2xl": "text-2xl", "3xl": "text-3xl", "4xl": "text-4xl", "5xl": "text-5xl", "6xl": "text-6xl" }; sizeClasses = sizeClassMap[size] ?? sizeClassMap.md; const weightClassMap = { hairline: "font-hairline", light: "font-light", normal: "font-normal", medium: "font-medium", semibold: "font-semibold", bold: "font-bold", extrabold: "font-extrabold", black: "font-black" }; weightClasses = weightClassMap[weight] ?? weightClassMap.normal; const baseClasses = "font-primary"; const Component = as ?? "p"; return /* @__PURE__ */ jsx2( Component, { className: cn(baseClasses, sizeClasses, weightClasses, color, className), ...props, children } ); }; var Text_default = Text; // src/components/Modal/Modal.tsx import { useEffect, useId } from "react"; import { X } from "phosphor-react"; // src/components/Modal/utils/videoUtils.ts var isYouTubeUrl = (url) => { const youtubeRegex = /^(https?:\/\/)?((www|m|music)\.)?(youtube\.com|youtu\.be|youtube-nocookie\.com)\/.+/i; return youtubeRegex.test(url); }; var isValidYouTubeHost = (host) => { if (host === "youtu.be") return "youtu.be"; const isValidYouTubeCom = host === "youtube.com" || host.endsWith(".youtube.com") && /^(www|m|music)\.youtube\.com$/.test(host); if (isValidYouTubeCom) return "youtube"; const isValidNoCookie = host === "youtube-nocookie.com" || host.endsWith(".youtube-nocookie.com") && /^(www|m|music)\.youtube-nocookie\.com$/.test(host); if (isValidNoCookie) return "nocookie"; return null; }; var extractYoutuBeId = (pathname) => { const firstSeg = pathname.split("/").filter(Boolean)[0]; return firstSeg || null; }; var extractYouTubeId = (pathname, searchParams) => { const parts = pathname.split("/").filter(Boolean); const [first, second] = parts; if (first === "embed" && second) return second; if (first === "shorts" && second) return second; if (first === "live" && second) return second; const v = searchParams.get("v"); if (v) return v; return null; }; var getYouTubeVideoId = (url) => { try { const u = new URL(url); const hostType = isValidYouTubeHost(u.hostname.toLowerCase()); if (!hostType) return null; if (hostType === "youtu.be") { return extractYoutuBeId(u.pathname); } return extractYouTubeId(u.pathname, u.searchParams); } catch { return null; } }; var getYouTubeEmbedUrl = (videoId) => { return `https://www.youtube-nocookie.com/embed/${videoId}?autoplay=0&rel=0&modestbranding=1`; }; // src/components/Modal/Modal.tsx import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime"; var SIZE_CLASSES2 = { xs: "max-w-[360px]", sm: "max-w-[420px]", md: "max-w-[510px]", lg: "max-w-[640px]", xl: "max-w-[970px]" }; var Modal = ({ isOpen, onClose, title, children, size = "md", className = "", closeOnEscape = true, footer, hideCloseButton = false, variant = "default", description, image, imageAlt, actionLink, actionLabel, contentClassName = "" }) => { const titleId = useId(); useEffect(() => { if (!isOpen || !closeOnEscape) return; const handleEscape = (event) => { if (event.key === "Escape") { onClose(); } }; document.addEventListener("keydown", handleEscape); return () => document.removeEventListener("keydown", handleEscape); }, [isOpen, closeOnEscape, onClose]); useEffect(() => { if (!isOpen) return; const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; const originalOverflow = document.body.style.overflow; const originalPaddingRight = document.body.style.paddingRight; document.body.style.overflow = "hidden"; if (scrollbarWidth > 0) { document.body.style.paddingRight = `${scrollbarWidth}px`; const overlay = document.createElement("div"); overlay.id = "modal-scrollbar-overlay"; overlay.style.cssText = ` position: fixed; top: 0; right: 0; width: ${scrollbarWidth}px; height: 100vh; background-color: rgb(0 0 0 / 0.6); z-index: 40; pointer-events: none; `; document.body.appendChild(overlay); } return () => { document.body.style.overflow = originalOverflow; document.body.style.paddingRight = originalPaddingRight; const overlay = document.getElementById("modal-scrollbar-overlay"); if (overlay) { overlay.remove(); } }; }, [isOpen]); if (!isOpen) return null; const sizeClasses = SIZE_CLASSES2[size]; const baseClasses = "bg-secondary-50 rounded-3xl shadow-hard-shadow-2 border border-border-100 w-full mx-4"; const dialogResetClasses = "p-0 m-0 border-none outline-none max-h-none static"; const modalClasses = cn( baseClasses, sizeClasses, dialogResetClasses, className ); const normalizeUrl = (href) => /^https?:\/\//i.test(href) ? href : `https://${href}`; const handleActionClick = () => { if (actionLink) { window.open(normalizeUrl(actionLink), "_blank", "noopener,noreferrer"); } }; if (variant === "activity") { return /* @__PURE__ */ jsx3("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-xs border-none p-0 m-0 w-full cursor-default", children: /* @__PURE__ */ jsxs2( "dialog", { className: modalClasses, "aria-labelledby": titleId, "aria-modal": "true", open: true, children: [ /* @__PURE__ */ jsx3("div", { className: "flex justify-end p-6 pb-0", children: !hideCloseButton && /* @__PURE__ */ jsx3( "button", { onClick: onClose, className: "p-1 text-text-500 hover:text-text-700 hover:bg-background-50 rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-indicator-info focus:ring-offset-2", "aria-label": "Fechar modal", children: /* @__PURE__ */ jsx3(X, { size: 18 }) } ) }), /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center px-6 pb-6 gap-5", children: [ image && /* @__PURE__ */ jsx3("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx3( "img", { src: image, alt: imageAlt ?? "", className: "w-[122px] h-[122px] object-contain" } ) }), /* @__PURE__ */ jsx3( "h2", { id: titleId, className: "text-lg font-semibold text-text-950 text-center", children: title } ), description && /* @__PURE__ */ jsx3("p", { className: "text-sm font-normal text-text-400 text-center max-w-md leading-[21px]", children: description }), actionLink && /* @__PURE__ */ jsxs2("div", { className: "w-full", children: [ (() => { const normalized = normalizeUrl(actionLink); const isYT = isYouTubeUrl(normalized); if (!isYT) return null; const id = getYouTubeVideoId(normalized); if (!id) { return /* @__PURE__ */ jsx3( Button_default, { variant: "solid", action: "primary", size: "large", className: "w-full", onClick: handleActionClick, children: actionLabel || "Iniciar Atividade" } ); } return /* @__PURE__ */ jsx3( "iframe", { src: getYouTubeEmbedUrl(id), className: "w-full aspect-video rounded-lg", allowFullScreen: true, allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture", title: "V\xEDdeo YouTube" } ); })(), !isYouTubeUrl(normalizeUrl(actionLink)) && /* @__PURE__ */ jsx3( Button_default, { variant: "solid", action: "primary", size: "large", className: "w-full", onClick: handleActionClick, children: actionLabel || "Iniciar Atividade" } ) ] }) ] }) ] } ) }); } return /* @__PURE__ */ jsx3("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-xs border-none p-0 m-0 w-full cursor-default", children: /* @__PURE__ */ jsxs2( "dialog", { className: modalClasses, "aria-labelledby": titleId, "aria-modal": "true", open: true, children: [ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between px-6 py-6", children: [ /* @__PURE__ */ jsx3("h2", { id: titleId, className: "text-lg font-semibold text-text-950", children: title }), !hideCloseButton && /* @__PURE__ */ jsx3( "button", { onClick: onClose, className: "p-1 text-text-500 hover:text-text-700 hover:bg-background-50 rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-indicator-info focus:ring-offset-2", "aria-label": "Fechar modal", children: /* @__PURE__ */ jsx3(X, { size: 18 }) } ) ] }), children && /* @__PURE__ */ jsx3("div", { className: cn("px-6 pb-6", contentClassName), children: /* @__PURE__ */ jsx3("div", { className: "text-text-500 font-normal text-sm leading-6", children }) }), footer && /* @__PURE__ */ jsx3("div", { className: "flex justify-end gap-3 px-6 pb-6", children: footer }) ] } ) }); }; var Modal_default = Modal; // src/components/ThemeToggle/ThemeToggle.tsx import { Moon, Sun } from "phosphor-react"; import { useState, useEffect as useEffect3 } from "react"; // src/components/SelectionButton/SelectionButton.tsx import { forwardRef } from "react"; import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime"; var SelectionButton = forwardRef( ({ icon, label, selected = false, className = "", disabled, ...props }, ref) => { const baseClasses = [ "inline-flex", "items-center", "justify-start", "gap-2", "p-4", "rounded-xl", "cursor-pointer", "border", "border-border-50", "bg-background", "text-sm", "text-text-700", "font-bold", "shadow-soft-shadow-1", "hover:bg-background-100", "focus-visible:outline-none", "focus-visible:ring-2", "focus-visible:ring-indicator-info", "focus-visible:ring-offset-0", "focus-visible:shadow-none", "active:ring-2", "active:ring-primary-950", "active:ring-offset-0", "active:shadow-none", "disabled:opacity-50", "disabled:cursor-not-allowed" ]; const stateClasses = selected ? ["ring-primary-950", "ring-2", "ring-offset-0", "shadow-none"] : []; const allClasses = [...baseClasses, ...stateClasses].join(" "); return /* @__PURE__ */ jsxs3( "button", { ref, type: "button", className: cn(allClasses, className), disabled, "aria-pressed": selected, ...props, children: [ /* @__PURE__ */ jsx4("span", { className: "flex items-center justify-center w-6 h-6", children: icon }), /* @__PURE__ */ jsx4("span", { children: label }) ] } ); } ); SelectionButton.displayName = "SelectionButton"; var SelectionButton_default = SelectionButton; // src/hooks/useTheme.ts import { useEffect as useEffect2 } from "react"; // src/store/themeStore.ts import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; var applyThemeToDOM = (mode) => { const htmlElement = document.documentElement; const originalTheme = htmlElement.getAttribute("data-original-theme"); if (mode === "dark") { htmlElement.setAttribute("data-theme", "dark"); return true; } else if (mode === "light") { if (originalTheme) { htmlElement.setAttribute("data-theme", originalTheme); } return false; } else if (mode === "system") { const isSystemDark = window.matchMedia( "(prefers-color-scheme: dark)" ).matches; if (isSystemDark) { htmlElement.setAttribute("data-theme", "dark"); return true; } else if (originalTheme) { htmlElement.setAttribute("data-theme", originalTheme); return false; } } return false; }; var saveOriginalTheme = () => { const htmlElement = document.documentElement; const currentTheme = htmlElement.getAttribute("data-theme"); if (currentTheme && !htmlElement.getAttribute("data-original-theme")) { htmlElement.setAttribute("data-original-theme", currentTheme); } }; var useThemeStore = create()( devtools( persist( (set, get) => ({ // Initial state themeMode: "system", isDark: false, // Actions applyTheme: (mode) => { const isDark = applyThemeToDOM(mode); set({ isDark }); }, toggleTheme: () => { const { themeMode, applyTheme } = get(); let newMode; if (themeMode === "light") { newMode = "dark"; } else if (themeMode === "dark") { newMode = "light"; } else { newMode = "dark"; } set({ themeMode: newMode }); applyTheme(newMode); }, setTheme: (mode) => { const { applyTheme } = get(); set({ themeMode: mode }); applyTheme(mode); }, initializeTheme: () => { const { themeMode, applyTheme } = get(); saveOriginalTheme(); applyTheme(themeMode); }, handleSystemThemeChange: () => { const { themeMode, applyTheme } = get(); if (themeMode === "system") { applyTheme("system"); } } }), { name: "theme-store", // Nome da chave no localStorage partialize: (state) => ({ themeMode: state.themeMode }) // Só persiste o themeMode, não o isDark } ), { name: "theme-store" } ) ); // src/hooks/useTheme.ts var useTheme = () => { const { themeMode, isDark, toggleTheme, setTheme, initializeTheme, handleSystemThemeChange } = useThemeStore(); useEffect2(() => { initializeTheme(); const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); mediaQuery.addEventListener("change", handleSystemThemeChange); return () => { mediaQuery.removeEventListener("change", handleSystemThemeChange); }; }, [initializeTheme, handleSystemThemeChange]); return { themeMode, isDark, toggleTheme, setTheme }; }; // src/components/ThemeToggle/ThemeToggle.tsx import { jsx as jsx5 } from "react/jsx-runtime"; var ThemeToggle = ({ variant = "default", onToggle }) => { const { themeMode, setTheme } = useTheme(); const [tempTheme, setTempTheme] = useState(themeMode); useEffect3(() => { setTempTheme(themeMode); }, [themeMode]); const problemTypes = [ { id: "light", title: "Claro", icon: /* @__PURE__ */ jsx5(Sun, { size: 24 }) }, { id: "dark", title: "Escuro", icon: /* @__PURE__ */ jsx5(Moon, { size: 24 }) }, { id: "system", title: "Sistema", icon: /* @__PURE__ */ jsx5( "svg", { width: "25", height: "25", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx5( "path", { d: "M12.5 2.75C15.085 2.75276 17.5637 3.78054 19.3916 5.6084C21.2195 7.43628 22.2473 9.915 22.25 12.5C22.25 14.4284 21.6778 16.3136 20.6064 17.917C19.5352 19.5201 18.0128 20.7699 16.2314 21.5078C14.4499 22.2458 12.489 22.4387 10.5977 22.0625C8.70642 21.6863 6.96899 20.758 5.60547 19.3945C4.24197 18.031 3.31374 16.2936 2.9375 14.4023C2.56129 12.511 2.75423 10.5501 3.49219 8.76855C4.23012 6.98718 5.47982 5.46483 7.08301 4.39355C8.68639 3.32221 10.5716 2.75 12.5 2.75ZM11.75 4.28516C9.70145 4.47452 7.7973 5.42115 6.41016 6.94043C5.02299 8.4599 4.25247 10.4426 4.25 12.5C4.25247 14.5574 5.02299 16.5401 6.41016 18.0596C7.7973 19.5789 9.70145 20.5255 11.75 20.7148V4.28516Z", fill: "#525252" } ) } ) } ]; const handleThemeSelect = (selectedTheme) => { if (variant === "with-save") { setTempTheme(selectedTheme); } else { setTheme(selectedTheme); } if (onToggle) { onToggle(selectedTheme); } }; const currentTheme = variant === "with-save" ? tempTheme : themeMode; return /* @__PURE__ */ jsx5("div", { className: "flex flex-row gap-2 sm:gap-4 py-2", children: problemTypes.map((type) => /* @__PURE__ */ jsx5( SelectionButton_default, { icon: type.icon, label: type.title, selected: currentTheme === type.id, onClick: () => handleThemeSelect(type.id), className: "w-full p-2 sm:p-4" }, type.id )) }); }; // src/components/DropdownMenu/DropdownMenu.tsx import { Fragment, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime"; function createDropdownStore() { return create2((set) => ({ open: false, setOpen: (open) => set({ open }) })); } var useDropdownStore = (externalStore) => { if (!externalStore) { throw new Error( "Component must be used within a DropdownMenu (store is missing)" ); } return externalStore; }; var injectStore = (children, store) => { return Children.map(children, (child) => { if (isValidElement(child)) { const typedChild = child; const displayName = typedChild.type.displayName; const allowed = [ "DropdownMenuTrigger", "DropdownContent", "DropdownMenuContent", "DropdownMenuSeparator", "DropdownMenuItem", "MenuLabel", "ProfileMenuTrigger", "ProfileMenuHeader", "ProfileMenuFooter", "ProfileToggleTheme" ]; let newProps = {}; if (allowed.includes(displayName)) { newProps.store = store; } if (typedChild.props.children) { newProps.children = injectStore(typedChild.props.children, store); } return cloneElement(typedChild, newProps); } return child; }); }; var DropdownMenu = ({ children, open: propOpen, onOpenChange }) => { const storeRef = useRef(null); storeRef.current ??= createDropdownStore(); const store = storeRef.current; const { open, setOpen: storeSetOpen } = useStore(store, (s) => s); const setOpen = (newOpen) => { storeSetOpen(newOpen); }; const menuRef = useRef(null); const handleArrowDownOrArrowUp = (event) => { const menuContent = menuRef.current?.querySelector('[role="menu"]'); if (menuContent) { event.preventDefault(); const items = Array.from( menuContent.querySelectorAll( '[role="menuitem"]:not([aria-disabled="true"])' ) ).filter((el) => el instanceof HTMLElement); if (items.length === 0) return; const focusedItem = document.activeElement; const currentIndex = items.indexOf(focusedItem); let nextIndex; if (event.key === "ArrowDown") { nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % items.length; } else { nextIndex = currentIndex === -1 ? items.length - 1 : (currentIndex - 1 + items.length) % items.length; } items[nextIndex]?.focus(); } }; const handleDownkey = (event) => { if (event.key === "Escape") { setOpen(false); } else if (event.key === "ArrowDown" || event.key === "ArrowUp") { handleArrowDownOrArrowUp(event); } }; const handleClickOutside = (event) => { const target = event.target; if (menuRef.current?.contains(target)) { return; } if (target instanceof Element && target.closest('[data-dropdown-content="true"]')) { return; } setOpen(false); }; useEffect4(() => { if (open) { document.addEventListener("pointerdown", handleClickOutside); document.addEventListener("keydown", handleDownkey); } return () => { document.removeEventListener("pointerdown", handleClickOutside); document.removeEventListener("keydown", handleDownkey); }; }, [open]); useEffect4(() => { onOpenChange?.(open); }, [open, onOpenChange]); useEffect4(() => { if (propOpen !== void 0) { setOpen(propOpen); } }, [propOpen]); return /* @__PURE__ */ jsx6("div", { className: "relative", ref: menuRef, children: injectStore(children, store) }); }; var DropdownMenuTrigger = forwardRef2(({ className, children, onClick, store: externalStore, ...props }, ref) => { const store = useDropdownStore(externalStore); const open = useStore(store, (s) => s.open); const toggleOpen = () => store.setState({ open: !open }); return /* @__PURE__ */ jsx6( "button", { ref, type: "button", onClick: (e) => { e.stopPropagation(); toggleOpen(); onClick?.(e); }, "aria-expanded": open, className: cn( "appearance-none bg-transparent border-none p-0", className ), ...props, children } ); }); DropdownMenuTrigger.displayName = "DropdownMenuTrigger"; var ITEM_SIZE_CLASSES = { small: "text-sm", medium: "text-md" }; var SIDE_CLASSES = { top: "bottom-full", right: "top-full", bottom: "top-full", left: "top-full" }; var ALIGN_CLASSES = { start: "left-0", center: "left-1/2 -translate-x-1/2", end: "right-0" }; var MENUCONTENT_VARIANT_CLASSES = { menu: "p-1", profile: "p-6" }; var MenuLabel = forwardRef2(({ className, inset, store: _store, ...props }, ref) => { return /* @__PURE__ */ jsx6( "div", { ref, className: cn("text-sm w-full", inset ? "pl-8" : "", className), ...props } ); }); MenuLabel.displayName = "MenuLabel"; var DropdownMenuContent = forwardRef2( ({ className, align = "start", side = "bottom", variant = "menu", sideOffset = 4, children, store: externalStore, portal = false, triggerRef, ...props }, ref) => { const store = useDropdownStore(externalStore); const open = useStore(store, (s) => s.open); const [isVisible, setIsVisible] = useState2(open); const [portalPosition, setPortalPosition] = useState2({ top: 0, left: 0 }); const contentRef = useRef(null); useEffect4(() => { if (open) { setIsVisible(true); } else { const timer = setTimeout(() => setIsVisible(false), 200); return () => clearTimeout(timer); } }, [open]); useLayoutEffect(() => { if (portal && open && triggerRef?.current) { const rect = triggerRef.current.getBoundingClientRect(); let top = rect.bottom + sideOffset; let left = rect.left; if (side === "left") { left = rect.left - sideOffset; top = rect.top; } else if (side === "right") { left = rect.right + sideOffset; top = rect.top; } else { if (align === "end") { left = rect.right; } else if (align === "center") { left = rect.left + rect.width / 2; } if (side === "top") { top = rect.top - sideOffset; } } setPortalPosition({ top, left }); } }, [portal, open, triggerRef, align, side, sideOffset]); if (!isVisible) return null; const getPositionClasses = () => { if (portal) { return "fixed"; } const vertical = SIDE_CLASSES[side]; const horizontal = ALIGN_CLASSES[align]; return `absolute ${vertical} ${horizontal}`; }; const getPortalAlignStyle = () => { if (!portal) return {}; const baseStyle = { top: portalPosition.top }; if (align === "end") { baseStyle.right = window.innerWidth - portalPosition.left; } else if (align === "center") { baseStyle.left = portalPosition.left; baseStyle.transform = "translateX(-50%)"; } else { baseStyle.left = portalPosition.left; } return baseStyle; }; const variantClasses = MENUCONTENT_VARIANT_CLASSES[variant]; const content = /* @__PURE__ */ jsx6( "div", { ref: portal ? contentRef : ref, role: "menu", "data-dropdown-content": "true", className: ` bg-background z-50 min-w-[210px] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md border-border-100 ${open ? "animate-in fade-in-0 zoom-in-95" : "animate-out fade-out-0 zoom-out-95"} ${getPositionClasses()} ${variantClasses} ${className} `, style: { ...portal ? getPortalAlignStyle() : { marginTop: side === "bottom" ? sideOffset : void 0, marginBottom: side === "top" ? sideOffset : void 0, marginLeft: side === "right" ? sideOffset : void 0, marginRight: side === "left" ? sideOffset : void 0 } }, ...props, children } ); if (portal && typeof document !== "undefined") { return createPortal(content, document.body); } return content; } ); DropdownMenuContent.displayName = "DropdownMenuContent"; var DropdownMenuItem = forwardRef2( ({ className, size = "small", children, iconRight, iconLeft, disabled = false, onClick, variant = "menu", store: externalStore, preventClose = false, ...props }, ref) => { const store = useDropdownStore(externalStore); const setOpen = useStore(store, (s) => s.setOpen); const sizeClasses = ITEM_SIZE_CLASSES[size]; const handleClick = (e) => { if (disabled) { e.preventDefault(); e.stopPropagation(); return; } if (e.type === "click") { onClick?.(e); } else if (e.type === "keydown") { if (e.key === "Enter" || e.key === " ") { onClick?.(e); } props.onKeyDown?.(e); } if (!preventClose) { setOpen(false); } }; const getVariantClasses = () => { if (variant === "profile") { return "relative flex flex-row justify-between select-none items-center gap-2 rounded-sm p-4 text-sm outline-none transition-colors [&>svg]:size-6 [&>svg]:shrink-0"; } return "relative flex select-none items-center gap-2 rounded-sm p-3 text-sm outline-none transition-colors [&>svg]:size-4 [&>svg]:shrink-0"; }; const getVariantProps = () => { return variant === "profile" ? { "data-variant": "profile" } : {}; }; return /* @__PURE__ */ jsxs4( "div", { ref, role: "menuitem", ...getVariantProps(), "aria-disabled": disabled, className: ` focus-visible:bg-background-50 ${getVariantClasses()} ${sizeClasses} ${className} ${disabled ? "cursor-not-allowed text-text-400" : "cursor-pointer hover:bg-background-50 text-text-700 focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground"} `, onClick: handleClick, onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); e.stopPropagation(); handleClick(e); } }, tabIndex: disabled ? -1 : 0, ...props, children: [ iconLeft, /* @__PURE__ */ jsx6("div", { className: "w-full", children }), iconRight ] } ); } ); DropdownMenuItem.displayName = "DropdownMenuItem"; var DropdownMenuSeparator = forwardRef2(({ className, store: _store, ...props }, ref) => /* @__PURE__ */ jsx6( "div", { ref, className: cn("my-1 h-px bg-border-200", className), ...props } )); DropdownMenuSeparator.displayName = "DropdownMenuSeparator"; var ProfileMenuTrigger = forwardRef2(({ className, onClick, store: externalStore, ...props }, ref) => { const store = useDropdownStore(externalStore); const open = useStore(store, (s) => s.open); const toggleOpen = () => store.setState({ open: !open }); return /* @__PURE__ */ jsx6( "button", { ref, className: cn( "rounded-lg size-10 bg-primary-50 flex items-center justify-center cursor-pointer", className ), onClick: (e) => { e.stopPropagation(); toggleOpen(); onClick?.(e); }, "aria-expanded": open, ...props, children: /* @__PURE__ */ jsx6("span", { className: "size-6 rounded-full bg-primary-100 flex items-center justify-center", children: /* @__PURE__ */ jsx6(User, { className: "text-primary-950", size: 18 }) }) } ); }); ProfileMenuTrigger.displayName = "ProfileMenuTrigger"; var ProfileMenuHeader = forwardRef2(({ className, name, email, photoUrl, store: _store, ...props }, ref) => { return /* @__PURE__ */ jsxs4( "div", { ref, "data-component": "ProfileMenuHeader", className: cn( "flex flex-row gap-4 items-center min-w-[280px]", className ), ...props, children: [ /* @__PURE__ */ jsx6("span", { className: "w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center overflow-hidden flex-shrink-0", children: photoUrl ? /* @__PURE__ */ jsx6( "img", { src: photoUrl, alt: "Foto de perfil", className: "w-full h-full object-cover" } ) : /* @__PURE__ */ jsx6(User, { size: 34, className: "text-primary-800" }) }), /* @__PURE__ */ jsxs4("div", { className: "flex flex-col min-w-0", children: [ /* @__PURE__ */ jsx6( Text_default, { size: "xl", weight: "bold", color: "text-text-950", className: "truncate", children: name } ), /* @__PURE__ */ jsx6(Text_default, { size: "md", color: "text-text-600", className: "truncate", children: email }) ] }) ] } ); }); ProfileMenuHeader.displayName = "ProfileMenuHeader"; var ProfileMenuInfo = forwardRef2( ({ className, schoolName, classYearName, schoolYearName, store: _store, ...props }, ref) => { return /* @__PURE__ */ jsxs4( "div", { ref, "data-component": "ProfileMenuInfo", className: cn("flex flex-row gap-4 items-center", className), ...props, children: [ /* @__PURE__ */ jsx6("span", { className: "w-16 h-16" }), /* @__PURE__ */ jsxs4("div", { className: "flex flex-col ", children: [ /* @__PURE__ */ jsx6(Text_default, { size: "md", color: "text-text-600", children: schoolName }), /* @__PURE__ */ jsxs4("span", { className: "flex flex-row items-center gap-2", children: [ /* @__PURE__ */ jsx6(Text_default, { size: "md", color: "text-text-600", children: classYearName }), /* @__PURE__ */ jsx6("p", { className: "text-text-600 text-xs align-middle", children: "\u25CF" }), /* @__PURE__ */ jsx6(Text_default, { size: "md", color: "text-text-600", children: schoolYearName }) ] }) ] }) ] } ); } ); ProfileMenuInfo.displayName = "ProfileMenuInfo"; var ProfileToggleTheme = ({ store: externalStore, ...props }) => { const { themeMode, setTheme } = useTheme(); const [modalThemeToggle, setModalThemeToggle] = useState2(false); const [selectedTheme, setSelectedTheme] = useState2(themeMode); const internalStoreRef = useRef(null); internalStoreRef.current ??= createDropdownStore(); const store = externalStore ?? internalStoreRef.current; const setOpen = useStore(store, (s) => s.setOpen); const handleClick = (e) => { e.preventDefault(); e.stopPropagation(); setModalThemeToggle(true); }; const handleSave = () => { setTheme(selectedTheme); setModalThemeToggle(false); setOpen(false); }; const handleCancel = () => { setSelectedTheme(themeMode); setModalThemeToggle(false); setOpen(false); }; return /* @__PURE__ */ jsxs4(Fragment, { children: [ /* @__PURE__ */ jsx6( DropdownMenuItem, { variant: "profile", preventClose: true, store, iconLeft: /* @__PURE__ */ jsx6( "svg", { width: "24", height: "24", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx6( "path", { d: "M12.5 2.75C15.085 2.75276 17.5637 3.78054 19.3916 5.6084C21.2195 7.43628 22.2473 9.915 22.25 12.5C22.25 14.4284 21.6778 16.3136 20.6064 17.917C19.5352 19.5201 18.0128 20.7699 16.2314 21.5078C14.4499 22.2458 12.489 22.4387 10.5977 22.0625C8.70642 21.6863 6.96899 20.758 5.60547 19.3945C4.24197 18.031 3.31374 16.2936 2.9375 14.4023C2.56129 12.511 2.75423 10.5501 3.49219 8.76855C4.23012 6.98718 5.47982 5.46483 7.08301 4.39355C8.68639 3.32221 10.5716 2.75 12.5 2.75ZM11.75 4.28516C9.70145 4.47452 7.7973 5.42115 6.41016 6.94043C5.02299 8.4599 4.25247 10.4426 4.25 12.5C4.25247 14.5574 5.02299 16.5401 6.41016 18.0596C7.7973 19.5789 9.70145 20.5255 11.75 20.7148V4.28516Z", fill: "currentColor" } ) } ), iconRight: /* @__PURE__ */ jsx6(CaretRight, {}), onClick: handleClick, onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); e.stopPropagation(); setModalThemeToggle(true); } }, ...props, children: /* @__PURE__ */ jsx6(Text_default, { size: "md", color: "text-text-700", children: "Apar\xEAncia" }) } ), /* @__PURE__ */ jsx6( Modal_default, { isOpen: modalThemeToggle, onClose: handleCancel, title: "Apar\xEAncia", size: "md", footer: /* @__PURE__ */ jsxs4("div", { className: "flex gap-3", children: [ /* @__PURE__ */ jsx6(Button_default, { variant: "outline", onClick: handleCancel, children: "Cancelar" }), /* @__PURE__ */ jsx6(Button_default, { variant: "solid", onClick: handleSave, children: "Salvar" }) ] }), children: /* @__PURE__ */ jsxs4("div", { className: "flex flex-col", children: [ /* @__PURE__ */ jsx6("p", { className: "text-sm text-text-500", children: "Escolha o tema:" }), /* @__PURE__ */ jsx6(ThemeToggle, { variant: "with-save", onToggle: setSelectedTheme }) ] }) } ) ] }); }; ProfileToggleTheme.displayName = "ProfileToggleTheme"; var ProfileMenuSection = forwardRef2(({ className, children, store: _store, ...props }, ref) => { return /* @__PURE__ */ jsx6("div", { ref, className: cn("flex flex-col p-2", className), ...props, children }); }); ProfileMenuSection.displayName = "ProfileMenuSection"; var ProfileMenuFooter = ({ className, disabled = false, onClick, store: externalStore, ...props }) => { const store = useDropdownStore(externalStore); const setOpen = useStore(store, (s) => s.setOpen); return /* @__PURE__ */ jsxs4( Button_default, { variant: "outline", className: cn("w-full", className), disabled, onClick: (e) => { setOpen(false); onClick?.(e); }, ...props, children: [ /* @__PURE__ */ jsx6("span", { className: "mr-2 flex items-center", children: /* @__PURE__ */ jsx6(SignOut, { className: "text-inherit" }) }), /* @__PURE__ */ jsx6(Text_default, { color: "inherit", children: "Sair" }) ] } ); }; ProfileMenuFooter.displayName = "ProfileMenuFooter"; var DropdownMenu_default = DropdownMenu; // src/components/Skeleton/Skeleton.tsx import { forwardRef as forwardRef3 } from "react"; import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime"; var SKELETON_ANIMATION_CLASSES = { pulse: "animate-pulse", none: "" }; var SKELETON_VARIANT_CLASSES = { text: "h-4 bg-background-200 rounded", circular: "bg-background-200 rounded-full", rectangular: "bg-background-200", rounded: "bg-background-200 rounded-lg" }; var SPACING_CLASSES = { none: "", small: "space-y-1", medium: "space-y-2", large: "space-y-3" }; var Skeleton = forwardRef3( ({ variant = "text", width, height, animation = "pulse", lines = 1, spacing = "none", className = "", children, ...props }, ref) => { const animationClass = SKELETON_ANIMATION_CLASSES[animation]; const variantClass = SKELETON_VARIANT_CLASSES[variant]; const spacingClass = SPACING_CLASSES[spacing]; const style = { width: typeof width === "number" ? `${width}px` : width, height: typeof height === "number" ? `${height}px` : height }; if (variant === "text" && lines > 1) { return /* @__PURE__ */ jsx7( "div", { ref, className: cn("flex flex-col", spacingClass, className), ...props, children: Array.from({ length: lines }, (_, index) => /* @__PURE__ */ jsx7( "div", { className: cn(variantClass, animationClass), style: index === lines - 1 ? { width: "60%" } : void 0 }, index )) } ); } return /* @__PURE__ */ jsx7( "div", { ref, className: cn(variantClass, animationClass, className), style, ...props, children } ); } ); var SkeletonText = forwardRef3( (props, ref) => /* @__PURE__ */ jsx7(Skeleton, { ref, variant: "text", ...props }) ); var SkeletonCircle = forwardRef3((props, ref) => /* @__PURE__ */ jsx7(Skeleton, { ref, variant: "circular", ...props })); var SkeletonRectangle = forwardRef3((props, ref) => /* @__PURE__ */ jsx7(Skeleton, { ref, variant: "rectangular", ...props })); var SkeletonRounded = forwardRef3((props, ref) => /* @__PURE__ */ jsx7(Skeleton, { ref, variant: "rounded", ...props })); var SkeletonCard = forwardRef3( ({ showAvatar = true, showTitle = true, showDescription = true, showActions = true, lines = 2, className = "", ...props }, ref) => { return /* @__PURE__ */ jsxs5( "div", { ref, className: cn( "w-full p-4 bg-background border border-border-200 rounded-lg", className ), ...props, children: [ /* @__PURE__ */ jsxs5("div", { className: "flex items-start space-x-3", children: [ showAvatar && /* @__PURE__ */ jsx7(SkeletonCircle, { width: 40, height: 40 }), /* @__PURE__ */ jsxs5("div", { className: "flex-1 space-y-2", children: [ showTitle && /* @__PURE__ */ jsx7(SkeletonText, { width: "60%", height: 20 }), showDescription && /* @__PURE__ */ jsx7(SkeletonText, { lines, spacing: "small" }) ] }) ] }), showActions && /* @__PURE__ */ jsxs5("div", { className: "flex justify-end space-x-2 mt-4", children: [ /* @__PURE__ */ jsx7(SkeletonRectangle, { width: 80, height: 32 }), /* @__PURE__ */ jsx7(SkeletonRectangle, { width: 80, height: 32 }) ] }) ] } ); } ); var SkeletonList = forwardRef3( ({ items = 3, showAvatar = true, showTitle = true, showDescription = true, lines = 1, className = "", ...props }, ref) => { return /* @__PURE__ */ jsx7("div", { ref, className: cn("space-y-3", className), ...props, children: Array.from({ length: items }, (_, index) => /* @__PURE__ */ jsxs5("div", { className: "flex items-start space-x-3 p-3", children: [ showAvatar && /* @__PURE__ */ jsx7(SkeletonCircle, { width: 32, height: 32 }), /* @__PURE__ */ jsxs5("div", { className: "flex-1 space-y-2", children: [ showTitle && /* @__PURE__ */ jsx7(SkeletonText, { width: "40%", height: 16 }), showDescription && /* @__PURE__ */ jsx7(SkeletonText, { lines, spacing: "small" }) ] }) ] }, index)) }); } ); var SkeletonTable = forwardRef3( ({ rows = 5, columns = 4, showHeader = true, className = "", ...props }, ref) => { return /* @__PURE__ */ jsxs5("div", { ref, className: cn("w-full", className), ...props, children: [ showHeader && /* @__PURE__ */ jsx7("div", { className: "flex space-x-2 mb-3", children: Array.from({ length: columns }, (_, index) => /* @__PURE__ */ jsx7( SkeletonText, { width: `${100 / columns}%`, height: 20 }, index )) }), /* @__PURE__ */ jsx7("div", { className: "space-y-2", children: Array.from({ length: rows }, (_, rowIndex) => /* @__PURE__ */ jsx7("div", { className: "flex space-x-2", children: Array.from({ length: columns }, (_2, colIndex) => /* @__PURE__ */ jsx7( SkeletonText, { width: `${100 / columns}%`, height: 16 }, colIndex )) }, rowIndex)) }) ] }); } ); // src/components/IconButton/IconButton.tsx import { forwardRef as forwardRef4 } from "react"; import { jsx as jsx8 } from "react/jsx-runtime"; var IconButton = forwardRef4( ({ icon, size = "md", active = false, className = "", disabled, ...props }, ref) => { const baseClasses = [ "inline-flex", "items-center", "justify-center", "rounded-lg", "font-medium", "bg-transparent", "text-text-950", "cursor-pointer", "hover:bg-primary-600", "hover:text-text", "focus-visible:outline-none", "focus-visible:ring-2", "focus-visible:ring-offset-0", "focus-visible:ring-indicator-info", "disabled:opacity-50", "disabled:cursor-not-allowed", "disabled:pointer-events-none" ]; const sizeClasses = { sm: ["w-6", "h-6", "text-sm"], md: ["w-10", "h-10", "text-base"] }; const activeClasses = active ? ["!bg-primary-50", "!text-primary-950", "hover:!bg-primary-100"] : []; const allClasses = [ ...baseClasses, ...sizeClasses[size], ...activeClasses ].join(" "); const ariaLabel = props["aria-label"] ?? "Bot\xE3o de a\xE7\xE3o"; return /* @__PURE__ */ jsx8( "button", { ref, type: "button", className: cn(allClasses, className), disabled, "aria-pressed": active, "aria-label": ariaLabel, ...props, children: /* @__PURE__ */ jsx8("span", { className: "flex items-center justify-center", children: icon }) } ); } ); IconButton.displayName = "IconButton"; var IconButton_default = IconButton; // src/components/Badge/Badge.tsx import { Bell } from "phosphor-react"; import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime"; var VARIANT_ACTION_CLASSES2 = { solid: { error: "bg-error-background text-error-700 focus-visible:outline-none", warning: "bg-warning text-warning-800 focus-visible:outline-none", success: "bg-success text-success-800 focus-visible:outline-none", info: "bg-info text-info-800 focus-visible:outline-none", muted: "bg-background-muted text-background-800 focus-visible:outline-none" }, outlined: { error: "bg-error text-error-700 border border-error-300 focus-visible:outline-none", warning: "bg-warning text-warning-800 border border-warning-300 focus-visible:outline-none", success: "bg-success text-success-800 border border-success-300 focus-visible:outline-none", info: "bg-info text-info-800 border border-info-300 focus-visible:outline-none",