analytica-frontend-lib
Version:
Repositório público dos componentes utilizados nas plataformas da Analytica Ensino
1 lines • 19.3 kB
Source Map (JSON)
{"version":3,"sources":["../../src/components/DownloadButton/DownloadButton.tsx","../../src/components/IconButton/IconButton.tsx","../../src/utils/utils.ts"],"sourcesContent":["import { useCallback, useState } from 'react';\nimport { DownloadSimple } from 'phosphor-react';\nimport IconButton from '../IconButton/IconButton';\nimport { cn } from '../../utils/utils';\n\n/**\n * Download content interface for lesson materials\n */\nexport interface DownloadContent {\n /** Document URL (PDF) */\n urlDoc?: string;\n /** Initial frame image URL */\n urlInitialFrame?: string;\n /** Final frame image URL */\n urlFinalFrame?: string;\n /** Podcast audio URL */\n urlPodcast?: string;\n /** Video URL */\n urlVideo?: string;\n}\n\n/**\n * Props for DownloadButton component\n */\nexport interface DownloadButtonProps {\n /** Content URLs to download */\n content: DownloadContent;\n /** Additional CSS classes */\n className?: string;\n /** Callback fired when download starts */\n onDownloadStart?: (contentType: string) => void;\n /** Callback fired when download completes */\n onDownloadComplete?: (contentType: string) => void;\n /** Callback fired when download fails */\n onDownloadError?: (contentType: string, error: Error) => void;\n /** Lesson title for download file naming */\n lessonTitle?: string;\n /** Whether the button is disabled */\n disabled?: boolean;\n}\n\n/**\n * Get MIME type based on file extension\n * @param url - URL to extract extension from\n * @returns MIME type string\n */\nconst getMimeType = (url: string): string => {\n const extension = getFileExtension(url);\n const mimeTypes: Record<string, string> = {\n pdf: 'application/pdf',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n mp3: 'audio/mpeg',\n mp4: 'video/mp4',\n vtt: 'text/vtt',\n };\n return mimeTypes[extension] || 'application/octet-stream';\n};\n\n/**\n * Download file via fetch and blob to ensure proper download behavior\n * @param url - URL to download\n * @param filename - Filename for the download\n * @returns Promise<void>\n */\nconst triggerDownload = async (\n url: string,\n filename: string\n): Promise<void> => {\n try {\n // Fetch the file as blob\n const response = await fetch(url, {\n mode: 'cors',\n credentials: 'same-origin',\n });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch file: ${response.status} ${response.statusText}`\n );\n }\n\n const blob = await response.blob();\n const mimeType = getMimeType(url);\n\n // Create a blob with the correct MIME type\n const typedBlob = new Blob([blob], { type: mimeType });\n\n // Create object URL\n const blobUrl = URL.createObjectURL(typedBlob);\n\n // Create download link\n const link = document.createElement('a');\n link.href = blobUrl;\n link.download = filename;\n link.rel = 'noopener noreferrer';\n\n // Add to DOM, click, and remove\n document.body.appendChild(link);\n link.click();\n link.remove();\n\n // Clean up object URL after a short delay\n setTimeout(() => {\n URL.revokeObjectURL(blobUrl);\n }, 1000);\n } catch (error) {\n // Fallback to direct link if fetch fails\n console.warn('Fetch download failed, falling back to direct link:', error);\n\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n link.rel = 'noopener noreferrer';\n link.target = '_blank'; // Open in new tab as fallback\n\n document.body.appendChild(link);\n link.click();\n link.remove();\n }\n};\n\n/**\n * Get file extension from URL\n * @param url - URL to extract extension from\n * @returns File extension or default\n */\nconst getFileExtension = (url: string): string => {\n try {\n const u = new URL(url, globalThis.location?.origin || 'http://localhost');\n url = u.pathname;\n } catch {\n // keep original url (likely relative)\n }\n const path = url.split(/[?#]/)[0];\n const dot = path.lastIndexOf('.');\n return dot > -1 ? path.slice(dot + 1).toLowerCase() : 'file';\n};\n\n/**\n * Generate filename for download\n * @param contentType - Type of content being downloaded\n * @param lessonTitle - Title of the lesson\n * @param url - URL to get extension from\n * @returns Generated filename\n */\nconst generateFilename = (\n contentType: string,\n url: string,\n lessonTitle: string = 'aula'\n): string => {\n const sanitizedTitle = lessonTitle\n .toLowerCase()\n .replaceAll(/[^a-z0-9\\s]/g, '')\n .replaceAll(/\\s+/g, '-')\n .substring(0, 50);\n\n const extension = getFileExtension(url);\n return `${sanitizedTitle}-${contentType}.${extension}`;\n};\n\n/**\n * DownloadButton component for downloading lesson content\n * Provides a single button that downloads all available content for a lesson\n *\n * @param props - DownloadButton component props\n * @returns Download button element\n */\nconst DownloadButton = ({\n content,\n className,\n onDownloadStart,\n onDownloadComplete,\n onDownloadError,\n lessonTitle = 'aula',\n disabled = false,\n}: DownloadButtonProps) => {\n const [isDownloading, setIsDownloading] = useState(false);\n\n /**\n * Check if URL is valid and not empty\n * @param url - URL to validate\n * @returns Whether URL is valid\n */\n const isValidUrl = useCallback((url?: string): boolean => {\n return Boolean(\n url && url.trim() !== '' && url !== 'undefined' && url !== 'null'\n );\n }, []);\n\n /**\n * Get available download content\n * @returns Array of available download items\n */\n const getAvailableContent = useCallback(() => {\n const downloads: Array<{ type: string; url: string; label: string }> = [];\n\n if (isValidUrl(content.urlDoc)) {\n downloads.push({\n type: 'documento',\n url: content.urlDoc!,\n label: 'Documento',\n });\n }\n\n if (isValidUrl(content.urlInitialFrame)) {\n downloads.push({\n type: 'quadro-inicial',\n url: content.urlInitialFrame!,\n label: 'Quadro Inicial',\n });\n }\n\n if (isValidUrl(content.urlFinalFrame)) {\n downloads.push({\n type: 'quadro-final',\n url: content.urlFinalFrame!,\n label: 'Quadro Final',\n });\n }\n\n if (isValidUrl(content.urlPodcast)) {\n downloads.push({\n type: 'podcast',\n url: content.urlPodcast!,\n label: 'Podcast',\n });\n }\n\n if (isValidUrl(content.urlVideo)) {\n downloads.push({ type: 'video', url: content.urlVideo!, label: 'Vídeo' });\n }\n\n return downloads;\n }, [content, isValidUrl]);\n\n /**\n * Handle download of all available content\n */\n const handleDownload = useCallback(async () => {\n if (disabled || isDownloading) return;\n\n const availableContent = getAvailableContent();\n\n if (availableContent.length === 0) {\n return;\n }\n\n setIsDownloading(true);\n\n try {\n // Download each available content sequentially with small delay\n for (let i = 0; i < availableContent.length; i++) {\n const item = availableContent[i];\n\n try {\n onDownloadStart?.(item.type);\n\n const filename = generateFilename(item.type, item.url, lessonTitle);\n await triggerDownload(item.url, filename);\n\n onDownloadComplete?.(item.type);\n\n // Add small delay between downloads to prevent browser blocking\n if (i < availableContent.length - 1) {\n await new Promise((resolve) => setTimeout(resolve, 200));\n }\n } catch (error) {\n // Silent error handling - delegate to callback\n onDownloadError?.(\n item.type,\n error instanceof Error\n ? error\n : new Error(`Falha ao baixar ${item.label}`)\n );\n }\n }\n } finally {\n setIsDownloading(false);\n }\n }, [\n disabled,\n isDownloading,\n getAvailableContent,\n lessonTitle,\n onDownloadStart,\n onDownloadComplete,\n onDownloadError,\n ]);\n\n // Don't render if no content is available\n const hasContent = getAvailableContent().length > 0;\n\n if (!hasContent) {\n return null;\n }\n\n return (\n <div className={cn('flex items-center', className)}>\n <IconButton\n icon={<DownloadSimple size={24} />}\n onClick={handleDownload}\n disabled={disabled || isDownloading}\n aria-label={(() => {\n if (isDownloading) {\n return 'Baixando conteúdo...';\n }\n const contentCount = getAvailableContent().length;\n const suffix = contentCount > 1 ? 's' : '';\n return `Baixar conteúdo da aula (${contentCount} arquivo${suffix})`;\n })()}\n className={cn(\n '!bg-transparent hover:!bg-black/10 transition-colors',\n isDownloading && 'opacity-60 cursor-not-allowed'\n )}\n />\n </div>\n );\n};\n\nexport default DownloadButton;\n","import { ButtonHTMLAttributes, ReactNode, forwardRef } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * IconButton component props interface\n */\nexport type IconButtonProps = {\n /** Ícone a ser exibido no botão */\n icon: ReactNode;\n /** Tamanho do botão */\n size?: 'sm' | 'md';\n /** Estado de seleção/ativo do botão - permanece ativo até ser clicado novamente ou outro botão ser ativado */\n active?: boolean;\n /** Additional CSS classes to apply */\n className?: string;\n} & ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * IconButton component for Analytica Ensino platforms\n *\n * Um botão compacto apenas com ícone, ideal para menus dropdown,\n * barras de ferramentas e ações secundárias.\n * Oferece dois tamanhos com estilo consistente.\n * Estado ativo permanece até ser clicado novamente ou outro botão ser ativado.\n * Suporta forwardRef para acesso programático ao elemento DOM.\n *\n * @param icon - O ícone a ser exibido no botão\n * @param size - Tamanho do botão (sm, md)\n * @param active - Estado ativo/selecionado do botão\n * @param className - Classes CSS adicionais\n * @param props - Todos os outros atributos HTML padrão de button\n * @returns Um elemento button compacto estilizado apenas com ícone\n *\n * @example\n * ```tsx\n * <IconButton\n * icon={<MoreVerticalIcon />}\n * size=\"sm\"\n * onClick={() => openMenu()}\n * />\n * ```\n *\n * @example\n * ```tsx\n * // Botão ativo em uma barra de ferramentas - permanece ativo até outro clique\n * <IconButton\n * icon={<BoldIcon />}\n * active={isBold}\n * onClick={toggleBold}\n * />\n * ```\n *\n * @example\n * ```tsx\n * // Usando ref para controle programático\n * const buttonRef = useRef<HTMLButtonElement>(null);\n *\n * <IconButton\n * ref={buttonRef}\n * icon={<EditIcon />}\n * size=\"md\"\n * onClick={() => startEditing()}\n * />\n * ```\n */\nconst IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(\n (\n { icon, size = 'md', active = false, className = '', disabled, ...props },\n ref\n ) => {\n // Classes base para todos os estados\n const baseClasses = [\n 'inline-flex',\n 'items-center',\n 'justify-center',\n 'rounded-lg',\n 'font-medium',\n 'bg-transparent',\n 'text-text-950',\n 'cursor-pointer',\n 'hover:bg-primary-600',\n 'hover:text-text',\n 'focus-visible:outline-none',\n 'focus-visible:ring-2',\n 'focus-visible:ring-offset-0',\n 'focus-visible:ring-indicator-info',\n 'disabled:opacity-50',\n 'disabled:cursor-not-allowed',\n 'disabled:pointer-events-none',\n ];\n\n // Classes de tamanho\n const sizeClasses = {\n sm: ['w-6', 'h-6', 'text-sm'],\n md: ['w-10', 'h-10', 'text-base'],\n };\n\n // Classes de estado ativo\n const activeClasses = active\n ? ['!bg-primary-50', '!text-primary-950', 'hover:!bg-primary-100']\n : [];\n\n const allClasses = [\n ...baseClasses,\n ...sizeClasses[size],\n ...activeClasses,\n ].join(' ');\n\n // Garantir acessibilidade com aria-label padrão\n const ariaLabel = props['aria-label'] ?? 'Botão de ação';\n\n return (\n <button\n ref={ref}\n type=\"button\"\n className={cn(allClasses, className)}\n disabled={disabled}\n aria-pressed={active}\n aria-label={ariaLabel}\n {...props}\n >\n <span className=\"flex items-center justify-center\">{icon}</span>\n </button>\n );\n }\n);\n\nIconButton.displayName = 'IconButton';\n\nexport default IconButton;\n","import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\nexport { syncDropdownState } from './dropdown';\nexport {\n getSelectedIdsFromCategories,\n toggleArrayItem,\n toggleSingleValue,\n areFiltersEqual,\n} from './activityFilters';\nexport {\n mapQuestionTypeToEnum,\n mapQuestionTypeToEnumRequired,\n} from './questionTypeUtils';\nexport {\n getStatusBadgeConfig,\n formatTimeSpent,\n formatQuestionNumbers,\n formatDateToBrazilian,\n} from './activityDetailsUtils';\n\n/**\n * Retorna a cor hexadecimal com opacidade 0.3 (4d) se não estiver em dark mode.\n * Se estiver em dark mode, retorna a cor original.\n *\n * @param hexColor - Cor hexadecimal (ex: \"#0066b8\" ou \"0066b8\")\n * @param isDark - booleano indicando se está em dark mode\n * @returns string - cor hexadecimal com opacidade se necessário\n */\nexport function getSubjectColorWithOpacity(\n hexColor: string | undefined,\n isDark: boolean\n): string | undefined {\n if (!hexColor) return undefined;\n // Remove o '#' se existir\n let color = hexColor.replace(/^#/, '').toLowerCase();\n\n if (isDark) {\n // Se está em dark mode, sempre remove opacidade se existir\n if (color.length === 8) {\n color = color.slice(0, 6);\n }\n return `#${color}`;\n } else {\n // Se não está em dark mode (light mode)\n let resultColor: string;\n if (color.length === 6) {\n // Adiciona opacidade 0.3 (4D) para cores de 6 dígitos\n resultColor = `#${color}4d`;\n } else if (color.length === 8) {\n // Já tem opacidade, retorna como está\n resultColor = `#${color}`;\n } else {\n // Para outros tamanhos (3, 4, 5 dígitos), retorna como está\n resultColor = `#${color}`;\n }\n return resultColor;\n }\n}\n"],"mappings":";AAAA,SAAS,aAAa,gBAAgB;AACtC,SAAS,sBAAsB;;;ACD/B,SAA0C,kBAAkB;;;ACA5D,SAAS,YAA6B;AACtC,SAAS,eAAe;AAEjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;ADoHQ;AAxDR,IAAM,aAAa;AAAA,EACjB,CACE,EAAE,MAAM,OAAO,MAAM,SAAS,OAAO,YAAY,IAAI,UAAU,GAAG,MAAM,GACxE,QACG;AAEH,UAAM,cAAc;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAGA,UAAM,cAAc;AAAA,MAClB,IAAI,CAAC,OAAO,OAAO,SAAS;AAAA,MAC5B,IAAI,CAAC,QAAQ,QAAQ,WAAW;AAAA,IAClC;AAGA,UAAM,gBAAgB,SAClB,CAAC,kBAAkB,qBAAqB,uBAAuB,IAC/D,CAAC;AAEL,UAAM,aAAa;AAAA,MACjB,GAAG;AAAA,MACH,GAAG,YAAY,IAAI;AAAA,MACnB,GAAG;AAAA,IACL,EAAE,KAAK,GAAG;AAGV,UAAM,YAAY,MAAM,YAAY,KAAK;AAEzC,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,WAAW,GAAG,YAAY,SAAS;AAAA,QACnC;AAAA,QACA,gBAAc;AAAA,QACd,cAAY;AAAA,QACX,GAAG;AAAA,QAEJ,8BAAC,UAAK,WAAU,oCAAoC,gBAAK;AAAA;AAAA,IAC3D;AAAA,EAEJ;AACF;AAEA,WAAW,cAAc;AAEzB,IAAO,qBAAQ;;;AD4KD,gBAAAA,YAAA;AA/Pd,IAAM,cAAc,CAAC,QAAwB;AAC3C,QAAM,YAAY,iBAAiB,GAAG;AACtC,QAAM,YAAoC;AAAA,IACxC,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACA,SAAO,UAAU,SAAS,KAAK;AACjC;AAQA,IAAM,kBAAkB,OACtB,KACA,aACkB;AAClB,MAAI;AAEF,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,MAAM;AAAA,MACN,aAAa;AAAA,IACf,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,yBAAyB,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,MACjE;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,WAAW,YAAY,GAAG;AAGhC,UAAM,YAAY,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,SAAS,CAAC;AAGrD,UAAM,UAAU,IAAI,gBAAgB,SAAS;AAG7C,UAAM,OAAO,SAAS,cAAc,GAAG;AACvC,SAAK,OAAO;AACZ,SAAK,WAAW;AAChB,SAAK,MAAM;AAGX,aAAS,KAAK,YAAY,IAAI;AAC9B,SAAK,MAAM;AACX,SAAK,OAAO;AAGZ,eAAW,MAAM;AACf,UAAI,gBAAgB,OAAO;AAAA,IAC7B,GAAG,GAAI;AAAA,EACT,SAAS,OAAO;AAEd,YAAQ,KAAK,uDAAuD,KAAK;AAEzE,UAAM,OAAO,SAAS,cAAc,GAAG;AACvC,SAAK,OAAO;AACZ,SAAK,WAAW;AAChB,SAAK,MAAM;AACX,SAAK,SAAS;AAEd,aAAS,KAAK,YAAY,IAAI;AAC9B,SAAK,MAAM;AACX,SAAK,OAAO;AAAA,EACd;AACF;AAOA,IAAM,mBAAmB,CAAC,QAAwB;AAChD,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,KAAK,WAAW,UAAU,UAAU,kBAAkB;AACxE,UAAM,EAAE;AAAA,EACV,QAAQ;AAAA,EAER;AACA,QAAM,OAAO,IAAI,MAAM,MAAM,EAAE,CAAC;AAChC,QAAM,MAAM,KAAK,YAAY,GAAG;AAChC,SAAO,MAAM,KAAK,KAAK,MAAM,MAAM,CAAC,EAAE,YAAY,IAAI;AACxD;AASA,IAAM,mBAAmB,CACvB,aACA,KACA,cAAsB,WACX;AACX,QAAM,iBAAiB,YACpB,YAAY,EACZ,WAAW,gBAAgB,EAAE,EAC7B,WAAW,QAAQ,GAAG,EACtB,UAAU,GAAG,EAAE;AAElB,QAAM,YAAY,iBAAiB,GAAG;AACtC,SAAO,GAAG,cAAc,IAAI,WAAW,IAAI,SAAS;AACtD;AASA,IAAM,iBAAiB,CAAC;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,WAAW;AACb,MAA2B;AACzB,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,KAAK;AAOxD,QAAM,aAAa,YAAY,CAAC,QAA0B;AACxD,WAAO;AAAA,MACL,OAAO,IAAI,KAAK,MAAM,MAAM,QAAQ,eAAe,QAAQ;AAAA,IAC7D;AAAA,EACF,GAAG,CAAC,CAAC;AAML,QAAM,sBAAsB,YAAY,MAAM;AAC5C,UAAM,YAAiE,CAAC;AAExE,QAAI,WAAW,QAAQ,MAAM,GAAG;AAC9B,gBAAU,KAAK;AAAA,QACb,MAAM;AAAA,QACN,KAAK,QAAQ;AAAA,QACb,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAEA,QAAI,WAAW,QAAQ,eAAe,GAAG;AACvC,gBAAU,KAAK;AAAA,QACb,MAAM;AAAA,QACN,KAAK,QAAQ;AAAA,QACb,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAEA,QAAI,WAAW,QAAQ,aAAa,GAAG;AACrC,gBAAU,KAAK;AAAA,QACb,MAAM;AAAA,QACN,KAAK,QAAQ;AAAA,QACb,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAEA,QAAI,WAAW,QAAQ,UAAU,GAAG;AAClC,gBAAU,KAAK;AAAA,QACb,MAAM;AAAA,QACN,KAAK,QAAQ;AAAA,QACb,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAEA,QAAI,WAAW,QAAQ,QAAQ,GAAG;AAChC,gBAAU,KAAK,EAAE,MAAM,SAAS,KAAK,QAAQ,UAAW,OAAO,WAAQ,CAAC;AAAA,IAC1E;AAEA,WAAO;AAAA,EACT,GAAG,CAAC,SAAS,UAAU,CAAC;AAKxB,QAAM,iBAAiB,YAAY,YAAY;AAC7C,QAAI,YAAY,cAAe;AAE/B,UAAM,mBAAmB,oBAAoB;AAE7C,QAAI,iBAAiB,WAAW,GAAG;AACjC;AAAA,IACF;AAEA,qBAAiB,IAAI;AAErB,QAAI;AAEF,eAAS,IAAI,GAAG,IAAI,iBAAiB,QAAQ,KAAK;AAChD,cAAM,OAAO,iBAAiB,CAAC;AAE/B,YAAI;AACF,4BAAkB,KAAK,IAAI;AAE3B,gBAAM,WAAW,iBAAiB,KAAK,MAAM,KAAK,KAAK,WAAW;AAClE,gBAAM,gBAAgB,KAAK,KAAK,QAAQ;AAExC,+BAAqB,KAAK,IAAI;AAG9B,cAAI,IAAI,iBAAiB,SAAS,GAAG;AACnC,kBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAG,CAAC;AAAA,UACzD;AAAA,QACF,SAAS,OAAO;AAEd;AAAA,YACE,KAAK;AAAA,YACL,iBAAiB,QACb,QACA,IAAI,MAAM,mBAAmB,KAAK,KAAK,EAAE;AAAA,UAC/C;AAAA,QACF;AAAA,MACF;AAAA,IACF,UAAE;AACA,uBAAiB,KAAK;AAAA,IACxB;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAGD,QAAM,aAAa,oBAAoB,EAAE,SAAS;AAElD,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,SACE,gBAAAA,KAAC,SAAI,WAAW,GAAG,qBAAqB,SAAS,GAC/C,0BAAAA;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,gBAAAA,KAAC,kBAAe,MAAM,IAAI;AAAA,MAChC,SAAS;AAAA,MACT,UAAU,YAAY;AAAA,MACtB,eAAa,MAAM;AACjB,YAAI,eAAe;AACjB,iBAAO;AAAA,QACT;AACA,cAAM,eAAe,oBAAoB,EAAE;AAC3C,cAAM,SAAS,eAAe,IAAI,MAAM;AACxC,eAAO,+BAA4B,YAAY,WAAW,MAAM;AAAA,MAClE,GAAG;AAAA,MACH,WAAW;AAAA,QACT;AAAA,QACA,iBAAiB;AAAA,MACnB;AAAA;AAAA,EACF,GACF;AAEJ;AAEA,IAAO,yBAAQ;","names":["jsx"]}