analytica-frontend-lib 1.2.44 → 1.2.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/Accordation/index.js.map +1 -1
  2. package/dist/Accordation/index.mjs.map +1 -1
  3. package/dist/ActivityCardQuestionBanks/index.js.map +1 -1
  4. package/dist/ActivityCardQuestionBanks/index.mjs.map +1 -1
  5. package/dist/ActivityCardQuestionPreview/index.js.map +1 -1
  6. package/dist/ActivityCardQuestionPreview/index.mjs.map +1 -1
  7. package/dist/ActivityDetails/index.d.ts.map +1 -1
  8. package/dist/ActivityDetails/index.js +59 -50
  9. package/dist/ActivityDetails/index.js.map +1 -1
  10. package/dist/ActivityDetails/index.mjs +59 -50
  11. package/dist/ActivityDetails/index.mjs.map +1 -1
  12. package/dist/ActivityFilters/index.js.map +1 -1
  13. package/dist/ActivityFilters/index.mjs.map +1 -1
  14. package/dist/ActivityPreview/index.js.map +1 -1
  15. package/dist/ActivityPreview/index.mjs.map +1 -1
  16. package/dist/Alert/index.js.map +1 -1
  17. package/dist/Alert/index.mjs.map +1 -1
  18. package/dist/AlertDialog/index.js.map +1 -1
  19. package/dist/AlertDialog/index.mjs.map +1 -1
  20. package/dist/AlertManager/index.js.map +1 -1
  21. package/dist/AlertManager/index.mjs.map +1 -1
  22. package/dist/AlertManagerView/index.js.map +1 -1
  23. package/dist/AlertManagerView/index.mjs.map +1 -1
  24. package/dist/Alternative/index.js.map +1 -1
  25. package/dist/Alternative/index.mjs.map +1 -1
  26. package/dist/Badge/index.js.map +1 -1
  27. package/dist/Badge/index.mjs.map +1 -1
  28. package/dist/BreadcrumbMenu/index.js.map +1 -1
  29. package/dist/BreadcrumbMenu/index.mjs.map +1 -1
  30. package/dist/Button/index.js.map +1 -1
  31. package/dist/Button/index.mjs.map +1 -1
  32. package/dist/Calendar/index.js.map +1 -1
  33. package/dist/Calendar/index.mjs.map +1 -1
  34. package/dist/Card/index.js.map +1 -1
  35. package/dist/Card/index.mjs.map +1 -1
  36. package/dist/CheckBox/index.js.map +1 -1
  37. package/dist/CheckBox/index.mjs.map +1 -1
  38. package/dist/Chips/index.js.map +1 -1
  39. package/dist/Chips/index.mjs.map +1 -1
  40. package/dist/CorrectActivityModal/index.js.map +1 -1
  41. package/dist/CorrectActivityModal/index.mjs.map +1 -1
  42. package/dist/Divider/index.js.map +1 -1
  43. package/dist/Divider/index.mjs.map +1 -1
  44. package/dist/DownloadButton/index.js.map +1 -1
  45. package/dist/DownloadButton/index.mjs.map +1 -1
  46. package/dist/DropdownMenu/index.js.map +1 -1
  47. package/dist/DropdownMenu/index.mjs.map +1 -1
  48. package/dist/EmptyState/index.js.map +1 -1
  49. package/dist/EmptyState/index.mjs.map +1 -1
  50. package/dist/FileAttachment/index.js.map +1 -1
  51. package/dist/FileAttachment/index.mjs.map +1 -1
  52. package/dist/IconButton/index.js.map +1 -1
  53. package/dist/IconButton/index.mjs.map +1 -1
  54. package/dist/IconRoundedButton/index.js.map +1 -1
  55. package/dist/IconRoundedButton/index.mjs.map +1 -1
  56. package/dist/LatexRenderer/index.js.map +1 -1
  57. package/dist/LatexRenderer/index.mjs.map +1 -1
  58. package/dist/Menu/index.js.map +1 -1
  59. package/dist/Menu/index.mjs.map +1 -1
  60. package/dist/Modal/index.js.map +1 -1
  61. package/dist/Modal/index.mjs.map +1 -1
  62. package/dist/MultipleChoice/index.js.map +1 -1
  63. package/dist/MultipleChoice/index.mjs.map +1 -1
  64. package/dist/NavButton/index.js.map +1 -1
  65. package/dist/NavButton/index.mjs.map +1 -1
  66. package/dist/NoSearchResult/index.js.map +1 -1
  67. package/dist/NoSearchResult/index.mjs.map +1 -1
  68. package/dist/NotFound/index.js.map +1 -1
  69. package/dist/NotFound/index.mjs.map +1 -1
  70. package/dist/NotificationCard/index.js.map +1 -1
  71. package/dist/NotificationCard/index.mjs.map +1 -1
  72. package/dist/ProgressBar/index.js.map +1 -1
  73. package/dist/ProgressBar/index.mjs.map +1 -1
  74. package/dist/ProgressCircle/index.js.map +1 -1
  75. package/dist/ProgressCircle/index.mjs.map +1 -1
  76. package/dist/Quiz/index.js.map +1 -1
  77. package/dist/Quiz/index.mjs.map +1 -1
  78. package/dist/Radio/index.js.map +1 -1
  79. package/dist/Radio/index.mjs.map +1 -1
  80. package/dist/Search/index.js.map +1 -1
  81. package/dist/Search/index.mjs.map +1 -1
  82. package/dist/Select/index.js.map +1 -1
  83. package/dist/Select/index.mjs.map +1 -1
  84. package/dist/SelectionButton/index.js.map +1 -1
  85. package/dist/SelectionButton/index.mjs.map +1 -1
  86. package/dist/SendActivityModal/index.js.map +1 -1
  87. package/dist/SendActivityModal/index.mjs.map +1 -1
  88. package/dist/Skeleton/index.js.map +1 -1
  89. package/dist/Skeleton/index.mjs.map +1 -1
  90. package/dist/StatisticsCard/index.js.map +1 -1
  91. package/dist/StatisticsCard/index.mjs.map +1 -1
  92. package/dist/Stepper/index.js.map +1 -1
  93. package/dist/Stepper/index.mjs.map +1 -1
  94. package/dist/Support/TicketModal/index.js.map +1 -1
  95. package/dist/Support/TicketModal/index.mjs.map +1 -1
  96. package/dist/Support/index.js.map +1 -1
  97. package/dist/Support/index.mjs.map +1 -1
  98. package/dist/Table/TablePagination/index.js.map +1 -1
  99. package/dist/Table/TablePagination/index.mjs.map +1 -1
  100. package/dist/Table/index.js.map +1 -1
  101. package/dist/Table/index.mjs.map +1 -1
  102. package/dist/TableProvider/index.js.map +1 -1
  103. package/dist/TableProvider/index.mjs.map +1 -1
  104. package/dist/Text/index.js.map +1 -1
  105. package/dist/Text/index.mjs.map +1 -1
  106. package/dist/TextArea/index.js.map +1 -1
  107. package/dist/TextArea/index.mjs.map +1 -1
  108. package/dist/ThemeToggle/index.js.map +1 -1
  109. package/dist/ThemeToggle/index.mjs.map +1 -1
  110. package/dist/Toast/Toaster/index.js.map +1 -1
  111. package/dist/Toast/Toaster/index.mjs.map +1 -1
  112. package/dist/Toast/index.js.map +1 -1
  113. package/dist/Toast/index.mjs.map +1 -1
  114. package/dist/VideoPlayer/index.js.map +1 -1
  115. package/dist/VideoPlayer/index.mjs.map +1 -1
  116. package/dist/Whiteboard/index.js.map +1 -1
  117. package/dist/Whiteboard/index.mjs.map +1 -1
  118. package/dist/index.d.ts +2 -1
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +62 -55
  121. package/dist/index.js.map +1 -1
  122. package/dist/index.mjs +62 -55
  123. package/dist/index.mjs.map +1 -1
  124. package/dist/types/activityDetails.d.ts +0 -24
  125. package/dist/types/activityDetails.d.ts.map +1 -1
  126. package/dist/utils/activityDetailsUtils.d.ts +30 -0
  127. package/dist/utils/activityDetailsUtils.d.ts.map +1 -0
  128. package/dist/utils/index.d.ts +1 -0
  129. package/dist/utils/index.d.ts.map +1 -1
  130. package/dist/utils/index.js +65 -0
  131. package/dist/utils/index.js.map +1 -1
  132. package/dist/utils/index.mjs +61 -0
  133. package/dist/utils/index.mjs.map +1 -1
  134. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/utils.ts","../../src/components/Text/Text.tsx","../../src/components/Button/Button.tsx","../../src/components/EmptyState/EmptyState.tsx"],"sourcesContent":["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} from './activityFilters';\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","import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Base text component props\n */\ntype BaseTextProps = {\n /** Content to be displayed */\n children?: ReactNode;\n /** Text size variant */\n size?:\n | '2xs'\n | 'xs'\n | 'sm'\n | 'md'\n | 'lg'\n | 'xl'\n | '2xl'\n | '3xl'\n | '4xl'\n | '5xl'\n | '6xl';\n /** Font weight variant */\n weight?:\n | 'hairline'\n | 'light'\n | 'normal'\n | 'medium'\n | 'semibold'\n | 'bold'\n | 'extrabold'\n | 'black';\n /** Color variant - white for light backgrounds, black for dark backgrounds */\n color?: string;\n /** Additional CSS classes to apply */\n className?: string;\n};\n\n/**\n * Polymorphic text component props that ensures type safety based on the 'as' prop\n */\ntype TextProps<T extends ElementType = 'p'> = BaseTextProps & {\n /** HTML tag to render */\n as?: T;\n} & Omit<ComponentPropsWithoutRef<T>, keyof BaseTextProps>;\n\n/**\n * Text component for Analytica Ensino platforms\n *\n * A flexible polymorphic text component with multiple sizes, weights, and colors.\n * Automatically adapts to dark and light themes with full type safety.\n *\n * @param children - The content to display\n * @param size - The text size variant (2xs, xs, sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl)\n * @param weight - The font weight variant (hairline, light, normal, medium, semibold, bold, extrabold, black)\n * @param color - The color variant - adapts to theme\n * @param as - The HTML tag to render - determines allowed attributes via TypeScript\n * @param className - Additional CSS classes\n * @param props - HTML attributes valid for the chosen tag only\n * @returns A styled text element with type-safe attributes\n *\n * @example\n * ```tsx\n * <Text size=\"lg\" weight=\"bold\" color=\"text-info-800\">\n * This is a large, bold text\n * </Text>\n *\n * <Text as=\"a\" href=\"/link\" target=\"_blank\">\n * Link with type-safe anchor attributes\n * </Text>\n *\n * <Text as=\"button\" onClick={handleClick} disabled>\n * Button with type-safe button attributes\n * </Text>\n * ```\n */\nconst Text = <T extends ElementType = 'p'>({\n children,\n size = 'md',\n weight = 'normal',\n color = 'text-text-950',\n as,\n className = '',\n ...props\n}: TextProps<T>) => {\n let sizeClasses = '';\n let weightClasses = '';\n\n // Text size classes mapping\n const sizeClassMap = {\n '2xs': 'text-2xs',\n xs: 'text-xs',\n sm: 'text-sm',\n md: 'text-md',\n lg: 'text-lg',\n xl: 'text-xl',\n '2xl': 'text-2xl',\n '3xl': 'text-3xl',\n '4xl': 'text-4xl',\n '5xl': 'text-5xl',\n '6xl': 'text-6xl',\n } as const;\n\n sizeClasses = sizeClassMap[size] ?? sizeClassMap.md;\n\n // Font weight classes mapping\n const weightClassMap = {\n hairline: 'font-hairline',\n light: 'font-light',\n normal: 'font-normal',\n medium: 'font-medium',\n semibold: 'font-semibold',\n bold: 'font-bold',\n extrabold: 'font-extrabold',\n black: 'font-black',\n } as const;\n\n weightClasses = weightClassMap[weight] ?? weightClassMap.normal;\n\n const baseClasses = 'font-primary';\n const Component = as ?? ('p' as ElementType);\n\n return (\n <Component\n className={cn(baseClasses, sizeClasses, weightClasses, color, className)}\n {...props}\n >\n {children}\n </Component>\n );\n};\n\nexport default Text;\n","import { ButtonHTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Lookup table for variant and action class combinations\n */\nconst VARIANT_ACTION_CLASSES = {\n solid: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n outline: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n link: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n} as const;\n\n/**\n * Lookup table for size classes\n */\nconst SIZE_CLASSES = {\n 'extra-small': 'text-xs px-3.5 py-2',\n small: 'text-sm px-4 py-2.5',\n medium: 'text-md px-5 py-2.5',\n large: 'text-lg px-6 py-3',\n 'extra-large': 'text-lg px-7 py-3.5',\n} as const;\n\n/**\n * Button component props interface\n */\ntype ButtonProps = {\n /** Content to be displayed inside the button */\n children: ReactNode;\n /** Ícone à esquerda do texto */\n iconLeft?: ReactNode;\n /** Ícone à direita do texto */\n iconRight?: ReactNode;\n /** Size of the button */\n size?: 'extra-small' | 'small' | 'medium' | 'large' | 'extra-large';\n /** Visual variant of the button */\n variant?: 'solid' | 'outline' | 'link';\n /** Action type of the button */\n action?: 'primary' | 'positive' | 'negative';\n /** Additional CSS classes to apply */\n className?: string;\n} & ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * Button component for Analytica Ensino platforms\n *\n * A flexible button component with multiple variants, sizes and actions.\n *\n * @param children - The content to display inside the button\n * @param size - The size variant (extra-small, small, medium, large, extra-large)\n * @param variant - The visual style variant (solid, outline, link)\n * @param action - The action type (primary, positive, negative)\n * @param className - Additional CSS classes\n * @param props - All other standard button HTML attributes\n * @returns A styled button element\n *\n * @example\n * ```tsx\n * <Button variant=\"solid\" action=\"primary\" size=\"medium\" onClick={() => console.log('clicked')}>\n * Click me\n * </Button>\n * ```\n */\nconst Button = ({\n children,\n iconLeft,\n iconRight,\n size = 'medium',\n variant = 'solid',\n action = 'primary',\n className = '',\n disabled,\n type = 'button',\n ...props\n}: ButtonProps) => {\n // Get classes from lookup tables\n const sizeClasses = SIZE_CLASSES[size];\n const variantClasses = VARIANT_ACTION_CLASSES[variant][action];\n\n const baseClasses =\n 'inline-flex items-center justify-center rounded-full cursor-pointer font-medium';\n\n return (\n <button\n className={cn(baseClasses, variantClasses, sizeClasses, className)}\n disabled={disabled}\n type={type}\n {...props}\n >\n {iconLeft && <span className=\"mr-2 flex items-center\">{iconLeft}</span>}\n {children}\n {iconRight && <span className=\"ml-2 flex items-center\">{iconRight}</span>}\n </button>\n );\n};\n\nexport default Button;\n","import { type ReactNode } from 'react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\n\nexport interface EmptyStateProps {\n /**\n * Image source for the illustration (optional)\n */\n image?: string;\n /**\n * Title text to display\n * @default \"Nenhum dado disponível\"\n */\n title?: string;\n /**\n * Description text to display below the title\n * @default \"Não há dados para exibir no momento.\"\n */\n description?: string;\n /**\n * Button text (optional - if not provided, button won't be displayed)\n */\n buttonText?: string;\n /**\n * Icon to display on the left side of the button\n */\n buttonIcon?: ReactNode;\n /**\n * Callback function when button is clicked\n */\n onButtonClick?: () => void;\n /**\n * Button variant\n * @default \"solid\"\n */\n buttonVariant?: 'solid' | 'outline' | 'link';\n /**\n * Button action color\n * @default \"primary\"\n */\n buttonAction?: 'primary' | 'positive' | 'negative';\n}\n\n/**\n * Component displayed when there is no data to show (empty state)\n * Shows an illustration with customizable title, description, and optional button in horizontal layout\n *\n * @example\n * ```tsx\n * import { EmptyState } from 'analytica-frontend-lib';\n * import activityImage from './assets/activity.png';\n * import { Plus } from 'phosphor-react';\n *\n * <EmptyState\n * image={activityImage}\n * title=\"Incentive sua turma ao aprendizado\"\n * description=\"Crie uma nova atividade e ajude seus alunos a colocarem o conteúdo em prática!\"\n * buttonText=\"Criar atividade\"\n * buttonIcon={<Plus size={18} />}\n * buttonVariant=\"outline\"\n * onButtonClick={handleCreateActivity}\n * />\n * ```\n */\nconst EmptyState = ({\n image,\n title,\n description,\n buttonText,\n buttonIcon,\n onButtonClick,\n buttonVariant = 'solid',\n buttonAction = 'primary',\n}: EmptyStateProps) => {\n const displayTitle = title || 'Nenhum dado disponível';\n const displayDescription =\n description || 'Não há dados para exibir no momento.';\n\n return (\n <div className=\"flex flex-col justify-center items-center gap-6 w-full min-h-[705px] bg-background rounded-xl p-6\">\n {/* Illustration */}\n {image && (\n <img src={image} alt={displayTitle} className=\"w-[170px] h-[150px]\" />\n )}\n\n {/* Text Content Container */}\n <div className=\"flex flex-col items-center gap-4 w-full max-w-[600px] px-6\">\n {/* Title */}\n <Text\n as=\"h2\"\n className=\"text-text-950 font-semibold text-3xl leading-[35px] text-center\"\n >\n {displayTitle}\n </Text>\n\n {/* Description */}\n <Text className=\"text-text-600 font-normal text-[18px] leading-[27px] text-center\">\n {displayDescription}\n </Text>\n </div>\n\n {/* Button */}\n {buttonText && onButtonClick && (\n <Button\n variant={buttonVariant}\n action={buttonAction}\n size=\"large\"\n onClick={onButtonClick}\n iconLeft={buttonIcon}\n className=\"rounded-full px-5 py-2.5\"\n >\n {buttonText}\n </Button>\n )}\n </div>\n );\n};\n\nexport default EmptyState;\n"],"mappings":"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","names":["jsx","jsx","jsxs"]}
1
+ {"version":3,"sources":["../../src/utils/utils.ts","../../src/components/Text/Text.tsx","../../src/components/Button/Button.tsx","../../src/components/EmptyState/EmptyState.tsx"],"sourcesContent":["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} from './activityFilters';\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","import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Base text component props\n */\ntype BaseTextProps = {\n /** Content to be displayed */\n children?: ReactNode;\n /** Text size variant */\n size?:\n | '2xs'\n | 'xs'\n | 'sm'\n | 'md'\n | 'lg'\n | 'xl'\n | '2xl'\n | '3xl'\n | '4xl'\n | '5xl'\n | '6xl';\n /** Font weight variant */\n weight?:\n | 'hairline'\n | 'light'\n | 'normal'\n | 'medium'\n | 'semibold'\n | 'bold'\n | 'extrabold'\n | 'black';\n /** Color variant - white for light backgrounds, black for dark backgrounds */\n color?: string;\n /** Additional CSS classes to apply */\n className?: string;\n};\n\n/**\n * Polymorphic text component props that ensures type safety based on the 'as' prop\n */\ntype TextProps<T extends ElementType = 'p'> = BaseTextProps & {\n /** HTML tag to render */\n as?: T;\n} & Omit<ComponentPropsWithoutRef<T>, keyof BaseTextProps>;\n\n/**\n * Text component for Analytica Ensino platforms\n *\n * A flexible polymorphic text component with multiple sizes, weights, and colors.\n * Automatically adapts to dark and light themes with full type safety.\n *\n * @param children - The content to display\n * @param size - The text size variant (2xs, xs, sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl)\n * @param weight - The font weight variant (hairline, light, normal, medium, semibold, bold, extrabold, black)\n * @param color - The color variant - adapts to theme\n * @param as - The HTML tag to render - determines allowed attributes via TypeScript\n * @param className - Additional CSS classes\n * @param props - HTML attributes valid for the chosen tag only\n * @returns A styled text element with type-safe attributes\n *\n * @example\n * ```tsx\n * <Text size=\"lg\" weight=\"bold\" color=\"text-info-800\">\n * This is a large, bold text\n * </Text>\n *\n * <Text as=\"a\" href=\"/link\" target=\"_blank\">\n * Link with type-safe anchor attributes\n * </Text>\n *\n * <Text as=\"button\" onClick={handleClick} disabled>\n * Button with type-safe button attributes\n * </Text>\n * ```\n */\nconst Text = <T extends ElementType = 'p'>({\n children,\n size = 'md',\n weight = 'normal',\n color = 'text-text-950',\n as,\n className = '',\n ...props\n}: TextProps<T>) => {\n let sizeClasses = '';\n let weightClasses = '';\n\n // Text size classes mapping\n const sizeClassMap = {\n '2xs': 'text-2xs',\n xs: 'text-xs',\n sm: 'text-sm',\n md: 'text-md',\n lg: 'text-lg',\n xl: 'text-xl',\n '2xl': 'text-2xl',\n '3xl': 'text-3xl',\n '4xl': 'text-4xl',\n '5xl': 'text-5xl',\n '6xl': 'text-6xl',\n } as const;\n\n sizeClasses = sizeClassMap[size] ?? sizeClassMap.md;\n\n // Font weight classes mapping\n const weightClassMap = {\n hairline: 'font-hairline',\n light: 'font-light',\n normal: 'font-normal',\n medium: 'font-medium',\n semibold: 'font-semibold',\n bold: 'font-bold',\n extrabold: 'font-extrabold',\n black: 'font-black',\n } as const;\n\n weightClasses = weightClassMap[weight] ?? weightClassMap.normal;\n\n const baseClasses = 'font-primary';\n const Component = as ?? ('p' as ElementType);\n\n return (\n <Component\n className={cn(baseClasses, sizeClasses, weightClasses, color, className)}\n {...props}\n >\n {children}\n </Component>\n );\n};\n\nexport default Text;\n","import { ButtonHTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Lookup table for variant and action class combinations\n */\nconst VARIANT_ACTION_CLASSES = {\n solid: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n outline: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n link: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n} as const;\n\n/**\n * Lookup table for size classes\n */\nconst SIZE_CLASSES = {\n 'extra-small': 'text-xs px-3.5 py-2',\n small: 'text-sm px-4 py-2.5',\n medium: 'text-md px-5 py-2.5',\n large: 'text-lg px-6 py-3',\n 'extra-large': 'text-lg px-7 py-3.5',\n} as const;\n\n/**\n * Button component props interface\n */\ntype ButtonProps = {\n /** Content to be displayed inside the button */\n children: ReactNode;\n /** Ícone à esquerda do texto */\n iconLeft?: ReactNode;\n /** Ícone à direita do texto */\n iconRight?: ReactNode;\n /** Size of the button */\n size?: 'extra-small' | 'small' | 'medium' | 'large' | 'extra-large';\n /** Visual variant of the button */\n variant?: 'solid' | 'outline' | 'link';\n /** Action type of the button */\n action?: 'primary' | 'positive' | 'negative';\n /** Additional CSS classes to apply */\n className?: string;\n} & ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * Button component for Analytica Ensino platforms\n *\n * A flexible button component with multiple variants, sizes and actions.\n *\n * @param children - The content to display inside the button\n * @param size - The size variant (extra-small, small, medium, large, extra-large)\n * @param variant - The visual style variant (solid, outline, link)\n * @param action - The action type (primary, positive, negative)\n * @param className - Additional CSS classes\n * @param props - All other standard button HTML attributes\n * @returns A styled button element\n *\n * @example\n * ```tsx\n * <Button variant=\"solid\" action=\"primary\" size=\"medium\" onClick={() => console.log('clicked')}>\n * Click me\n * </Button>\n * ```\n */\nconst Button = ({\n children,\n iconLeft,\n iconRight,\n size = 'medium',\n variant = 'solid',\n action = 'primary',\n className = '',\n disabled,\n type = 'button',\n ...props\n}: ButtonProps) => {\n // Get classes from lookup tables\n const sizeClasses = SIZE_CLASSES[size];\n const variantClasses = VARIANT_ACTION_CLASSES[variant][action];\n\n const baseClasses =\n 'inline-flex items-center justify-center rounded-full cursor-pointer font-medium';\n\n return (\n <button\n className={cn(baseClasses, variantClasses, sizeClasses, className)}\n disabled={disabled}\n type={type}\n {...props}\n >\n {iconLeft && <span className=\"mr-2 flex items-center\">{iconLeft}</span>}\n {children}\n {iconRight && <span className=\"ml-2 flex items-center\">{iconRight}</span>}\n </button>\n );\n};\n\nexport default Button;\n","import { type ReactNode } from 'react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\n\nexport interface EmptyStateProps {\n /**\n * Image source for the illustration (optional)\n */\n image?: string;\n /**\n * Title text to display\n * @default \"Nenhum dado disponível\"\n */\n title?: string;\n /**\n * Description text to display below the title\n * @default \"Não há dados para exibir no momento.\"\n */\n description?: string;\n /**\n * Button text (optional - if not provided, button won't be displayed)\n */\n buttonText?: string;\n /**\n * Icon to display on the left side of the button\n */\n buttonIcon?: ReactNode;\n /**\n * Callback function when button is clicked\n */\n onButtonClick?: () => void;\n /**\n * Button variant\n * @default \"solid\"\n */\n buttonVariant?: 'solid' | 'outline' | 'link';\n /**\n * Button action color\n * @default \"primary\"\n */\n buttonAction?: 'primary' | 'positive' | 'negative';\n}\n\n/**\n * Component displayed when there is no data to show (empty state)\n * Shows an illustration with customizable title, description, and optional button in horizontal layout\n *\n * @example\n * ```tsx\n * import { EmptyState } from 'analytica-frontend-lib';\n * import activityImage from './assets/activity.png';\n * import { Plus } from 'phosphor-react';\n *\n * <EmptyState\n * image={activityImage}\n * title=\"Incentive sua turma ao aprendizado\"\n * description=\"Crie uma nova atividade e ajude seus alunos a colocarem o conteúdo em prática!\"\n * buttonText=\"Criar atividade\"\n * buttonIcon={<Plus size={18} />}\n * buttonVariant=\"outline\"\n * onButtonClick={handleCreateActivity}\n * />\n * ```\n */\nconst EmptyState = ({\n image,\n title,\n description,\n buttonText,\n buttonIcon,\n onButtonClick,\n buttonVariant = 'solid',\n buttonAction = 'primary',\n}: EmptyStateProps) => {\n const displayTitle = title || 'Nenhum dado disponível';\n const displayDescription =\n description || 'Não há dados para exibir no momento.';\n\n return (\n <div className=\"flex flex-col justify-center items-center gap-6 w-full min-h-[705px] bg-background rounded-xl p-6\">\n {/* Illustration */}\n {image && (\n <img src={image} alt={displayTitle} className=\"w-[170px] h-[150px]\" />\n )}\n\n {/* Text Content Container */}\n <div className=\"flex flex-col items-center gap-4 w-full max-w-[600px] px-6\">\n {/* Title */}\n <Text\n as=\"h2\"\n className=\"text-text-950 font-semibold text-3xl leading-[35px] text-center\"\n >\n {displayTitle}\n </Text>\n\n {/* Description */}\n <Text className=\"text-text-600 font-normal text-[18px] leading-[27px] text-center\">\n {displayDescription}\n </Text>\n </div>\n\n {/* Button */}\n {buttonText && onButtonClick && (\n <Button\n variant={buttonVariant}\n action={buttonAction}\n size=\"large\"\n onClick={onButtonClick}\n iconLeft={buttonIcon}\n className=\"rounded-full px-5 py-2.5\"\n >\n {buttonText}\n </Button>\n )}\n </div>\n );\n};\n\nexport default EmptyState;\n"],"mappings":"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","names":["jsx","jsx","jsxs"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/FileAttachment/FileAttachment.tsx","../../src/utils/utils.ts","../../src/components/Button/Button.tsx","../../src/components/Text/Text.tsx"],"sourcesContent":["import React, { useRef } from 'react';\nimport { Paperclip, FileText, X } from 'phosphor-react';\nimport Button from '../Button/Button';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\n\n/**\n * Represents an attached file with unique identifier\n */\nexport interface AttachedFile {\n /** The file object */\n file: File;\n /** Unique identifier for the file */\n id: string;\n}\n\n/**\n * Props for the FileAttachment component\n */\nexport interface FileAttachmentProps {\n /** List of attached files */\n files: AttachedFile[];\n /** Callback when files are added */\n onFilesAdd: (files: File[]) => void;\n /** Callback when a file is removed */\n onFileRemove: (id: string) => void;\n /** Whether the files are read-only (no removal allowed) */\n readOnly?: boolean;\n /** Text for the attach button */\n buttonLabel?: string;\n /** Whether to accept multiple files */\n multiple?: boolean;\n /** Additional CSS class */\n className?: string;\n /** Whether to hide the attach button */\n hideButton?: boolean;\n}\n\n/**\n * Formats file size to human-readable string\n * @param bytes - File size in bytes\n * @returns Formatted string (e.g., \"1.5 MB\")\n */\nconst formatFileSize = (bytes: number): string => {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024)\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n};\n\n/**\n * Generates a unique ID for file tracking\n * @returns Unique string identifier\n */\nconst generateFileId = (): string => {\n return crypto.randomUUID();\n};\n\n/**\n * Reusable file attachment component\n *\n * Allows users to attach multiple files with preview and removal functionality.\n * No file size or type restrictions are applied on the client side.\n *\n * @param props - Component props\n * @returns JSX element\n *\n * @example\n * ```tsx\n * const [files, setFiles] = useState<AttachedFile[]>([]);\n *\n * <FileAttachment\n * files={files}\n * onFilesAdd={(newFiles) => {\n * const attachedFiles = newFiles.map(file => ({\n * file,\n * id: generateFileId()\n * }));\n * setFiles(prev => [...prev, ...attachedFiles]);\n * }}\n * onFileRemove={(id) => setFiles(prev => prev.filter(f => f.id !== id))}\n * multiple\n * />\n * ```\n */\nconst FileAttachment = ({\n files,\n onFilesAdd,\n onFileRemove,\n readOnly = false,\n buttonLabel = 'Anexar',\n multiple = true,\n className,\n hideButton = false,\n}: FileAttachmentProps) => {\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n /**\n * Handle file input change\n * @param event - Input change event\n */\n const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n const selectedFiles = event.target.files;\n if (selectedFiles && selectedFiles.length > 0) {\n const filesArray: File[] = Array.from(selectedFiles);\n onFilesAdd(filesArray);\n }\n // Reset input to allow selecting the same file again\n if (fileInputRef.current) {\n fileInputRef.current.value = '';\n }\n };\n\n /**\n * Trigger file input click\n */\n const handleAttachClick = () => {\n fileInputRef.current?.click();\n };\n\n return (\n <div className={cn('flex flex-col gap-2', className)}>\n {/* Hidden file input */}\n <input\n type=\"file\"\n ref={fileInputRef}\n className=\"hidden\"\n onChange={handleFileChange}\n multiple={multiple}\n aria-label=\"Selecionar arquivos\"\n />\n\n {/* Attach button */}\n {!readOnly && !hideButton && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"small\"\n onClick={handleAttachClick}\n className=\"self-start flex items-center gap-2\"\n >\n <Paperclip size={16} />\n {buttonLabel}\n </Button>\n )}\n\n {/* Attached files list */}\n {files.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {files.map((attachedFile) => (\n <div\n key={attachedFile.id}\n className=\"flex items-center gap-2 bg-background-50 px-3 py-2 rounded-lg border border-border-100\"\n >\n <FileText size={16} className=\"text-text-500 shrink-0\" />\n <Text className=\"text-sm text-text-700 truncate max-w-[200px]\">\n {attachedFile.file.name}\n </Text>\n <Text className=\"text-xs text-text-400 shrink-0\">\n {formatFileSize(attachedFile.file.size)}\n </Text>\n {!readOnly && (\n <button\n type=\"button\"\n onClick={() => onFileRemove(attachedFile.id)}\n className=\"text-text-400 hover:text-error-500 transition-colors shrink-0\"\n aria-label={`Remover ${attachedFile.file.name}`}\n >\n <X size={14} />\n </button>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n};\n\nexport { generateFileId, formatFileSize };\nexport default FileAttachment;\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} from './activityFilters';\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","import { ButtonHTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Lookup table for variant and action class combinations\n */\nconst VARIANT_ACTION_CLASSES = {\n solid: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n outline: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n link: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n} as const;\n\n/**\n * Lookup table for size classes\n */\nconst SIZE_CLASSES = {\n 'extra-small': 'text-xs px-3.5 py-2',\n small: 'text-sm px-4 py-2.5',\n medium: 'text-md px-5 py-2.5',\n large: 'text-lg px-6 py-3',\n 'extra-large': 'text-lg px-7 py-3.5',\n} as const;\n\n/**\n * Button component props interface\n */\ntype ButtonProps = {\n /** Content to be displayed inside the button */\n children: ReactNode;\n /** Ícone à esquerda do texto */\n iconLeft?: ReactNode;\n /** Ícone à direita do texto */\n iconRight?: ReactNode;\n /** Size of the button */\n size?: 'extra-small' | 'small' | 'medium' | 'large' | 'extra-large';\n /** Visual variant of the button */\n variant?: 'solid' | 'outline' | 'link';\n /** Action type of the button */\n action?: 'primary' | 'positive' | 'negative';\n /** Additional CSS classes to apply */\n className?: string;\n} & ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * Button component for Analytica Ensino platforms\n *\n * A flexible button component with multiple variants, sizes and actions.\n *\n * @param children - The content to display inside the button\n * @param size - The size variant (extra-small, small, medium, large, extra-large)\n * @param variant - The visual style variant (solid, outline, link)\n * @param action - The action type (primary, positive, negative)\n * @param className - Additional CSS classes\n * @param props - All other standard button HTML attributes\n * @returns A styled button element\n *\n * @example\n * ```tsx\n * <Button variant=\"solid\" action=\"primary\" size=\"medium\" onClick={() => console.log('clicked')}>\n * Click me\n * </Button>\n * ```\n */\nconst Button = ({\n children,\n iconLeft,\n iconRight,\n size = 'medium',\n variant = 'solid',\n action = 'primary',\n className = '',\n disabled,\n type = 'button',\n ...props\n}: ButtonProps) => {\n // Get classes from lookup tables\n const sizeClasses = SIZE_CLASSES[size];\n const variantClasses = VARIANT_ACTION_CLASSES[variant][action];\n\n const baseClasses =\n 'inline-flex items-center justify-center rounded-full cursor-pointer font-medium';\n\n return (\n <button\n className={cn(baseClasses, variantClasses, sizeClasses, className)}\n disabled={disabled}\n type={type}\n {...props}\n >\n {iconLeft && <span className=\"mr-2 flex items-center\">{iconLeft}</span>}\n {children}\n {iconRight && <span className=\"ml-2 flex items-center\">{iconRight}</span>}\n </button>\n );\n};\n\nexport default Button;\n","import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Base text component props\n */\ntype BaseTextProps = {\n /** Content to be displayed */\n children?: ReactNode;\n /** Text size variant */\n size?:\n | '2xs'\n | 'xs'\n | 'sm'\n | 'md'\n | 'lg'\n | 'xl'\n | '2xl'\n | '3xl'\n | '4xl'\n | '5xl'\n | '6xl';\n /** Font weight variant */\n weight?:\n | 'hairline'\n | 'light'\n | 'normal'\n | 'medium'\n | 'semibold'\n | 'bold'\n | 'extrabold'\n | 'black';\n /** Color variant - white for light backgrounds, black for dark backgrounds */\n color?: string;\n /** Additional CSS classes to apply */\n className?: string;\n};\n\n/**\n * Polymorphic text component props that ensures type safety based on the 'as' prop\n */\ntype TextProps<T extends ElementType = 'p'> = BaseTextProps & {\n /** HTML tag to render */\n as?: T;\n} & Omit<ComponentPropsWithoutRef<T>, keyof BaseTextProps>;\n\n/**\n * Text component for Analytica Ensino platforms\n *\n * A flexible polymorphic text component with multiple sizes, weights, and colors.\n * Automatically adapts to dark and light themes with full type safety.\n *\n * @param children - The content to display\n * @param size - The text size variant (2xs, xs, sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl)\n * @param weight - The font weight variant (hairline, light, normal, medium, semibold, bold, extrabold, black)\n * @param color - The color variant - adapts to theme\n * @param as - The HTML tag to render - determines allowed attributes via TypeScript\n * @param className - Additional CSS classes\n * @param props - HTML attributes valid for the chosen tag only\n * @returns A styled text element with type-safe attributes\n *\n * @example\n * ```tsx\n * <Text size=\"lg\" weight=\"bold\" color=\"text-info-800\">\n * This is a large, bold text\n * </Text>\n *\n * <Text as=\"a\" href=\"/link\" target=\"_blank\">\n * Link with type-safe anchor attributes\n * </Text>\n *\n * <Text as=\"button\" onClick={handleClick} disabled>\n * Button with type-safe button attributes\n * </Text>\n * ```\n */\nconst Text = <T extends ElementType = 'p'>({\n children,\n size = 'md',\n weight = 'normal',\n color = 'text-text-950',\n as,\n className = '',\n ...props\n}: TextProps<T>) => {\n let sizeClasses = '';\n let weightClasses = '';\n\n // Text size classes mapping\n const sizeClassMap = {\n '2xs': 'text-2xs',\n xs: 'text-xs',\n sm: 'text-sm',\n md: 'text-md',\n lg: 'text-lg',\n xl: 'text-xl',\n '2xl': 'text-2xl',\n '3xl': 'text-3xl',\n '4xl': 'text-4xl',\n '5xl': 'text-5xl',\n '6xl': 'text-6xl',\n } as const;\n\n sizeClasses = sizeClassMap[size] ?? sizeClassMap.md;\n\n // Font weight classes mapping\n const weightClassMap = {\n hairline: 'font-hairline',\n light: 'font-light',\n normal: 'font-normal',\n medium: 'font-medium',\n semibold: 'font-semibold',\n bold: 'font-bold',\n extrabold: 'font-extrabold',\n black: 'font-black',\n } as const;\n\n weightClasses = weightClassMap[weight] ?? weightClassMap.normal;\n\n const baseClasses = 'font-primary';\n const Component = as ?? ('p' as ElementType);\n\n return (\n <Component\n className={cn(baseClasses, sizeClasses, weightClasses, color, className)}\n {...props}\n >\n {children}\n </Component>\n );\n};\n\nexport default Text;\n"],"mappings":"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","names":["import_jsx_runtime","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../../src/components/FileAttachment/FileAttachment.tsx","../../src/utils/utils.ts","../../src/components/Button/Button.tsx","../../src/components/Text/Text.tsx"],"sourcesContent":["import React, { useRef } from 'react';\nimport { Paperclip, FileText, X } from 'phosphor-react';\nimport Button from '../Button/Button';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\n\n/**\n * Represents an attached file with unique identifier\n */\nexport interface AttachedFile {\n /** The file object */\n file: File;\n /** Unique identifier for the file */\n id: string;\n}\n\n/**\n * Props for the FileAttachment component\n */\nexport interface FileAttachmentProps {\n /** List of attached files */\n files: AttachedFile[];\n /** Callback when files are added */\n onFilesAdd: (files: File[]) => void;\n /** Callback when a file is removed */\n onFileRemove: (id: string) => void;\n /** Whether the files are read-only (no removal allowed) */\n readOnly?: boolean;\n /** Text for the attach button */\n buttonLabel?: string;\n /** Whether to accept multiple files */\n multiple?: boolean;\n /** Additional CSS class */\n className?: string;\n /** Whether to hide the attach button */\n hideButton?: boolean;\n}\n\n/**\n * Formats file size to human-readable string\n * @param bytes - File size in bytes\n * @returns Formatted string (e.g., \"1.5 MB\")\n */\nconst formatFileSize = (bytes: number): string => {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024)\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n};\n\n/**\n * Generates a unique ID for file tracking\n * @returns Unique string identifier\n */\nconst generateFileId = (): string => {\n return crypto.randomUUID();\n};\n\n/**\n * Reusable file attachment component\n *\n * Allows users to attach multiple files with preview and removal functionality.\n * No file size or type restrictions are applied on the client side.\n *\n * @param props - Component props\n * @returns JSX element\n *\n * @example\n * ```tsx\n * const [files, setFiles] = useState<AttachedFile[]>([]);\n *\n * <FileAttachment\n * files={files}\n * onFilesAdd={(newFiles) => {\n * const attachedFiles = newFiles.map(file => ({\n * file,\n * id: generateFileId()\n * }));\n * setFiles(prev => [...prev, ...attachedFiles]);\n * }}\n * onFileRemove={(id) => setFiles(prev => prev.filter(f => f.id !== id))}\n * multiple\n * />\n * ```\n */\nconst FileAttachment = ({\n files,\n onFilesAdd,\n onFileRemove,\n readOnly = false,\n buttonLabel = 'Anexar',\n multiple = true,\n className,\n hideButton = false,\n}: FileAttachmentProps) => {\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n /**\n * Handle file input change\n * @param event - Input change event\n */\n const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n const selectedFiles = event.target.files;\n if (selectedFiles && selectedFiles.length > 0) {\n const filesArray: File[] = Array.from(selectedFiles);\n onFilesAdd(filesArray);\n }\n // Reset input to allow selecting the same file again\n if (fileInputRef.current) {\n fileInputRef.current.value = '';\n }\n };\n\n /**\n * Trigger file input click\n */\n const handleAttachClick = () => {\n fileInputRef.current?.click();\n };\n\n return (\n <div className={cn('flex flex-col gap-2', className)}>\n {/* Hidden file input */}\n <input\n type=\"file\"\n ref={fileInputRef}\n className=\"hidden\"\n onChange={handleFileChange}\n multiple={multiple}\n aria-label=\"Selecionar arquivos\"\n />\n\n {/* Attach button */}\n {!readOnly && !hideButton && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"small\"\n onClick={handleAttachClick}\n className=\"self-start flex items-center gap-2\"\n >\n <Paperclip size={16} />\n {buttonLabel}\n </Button>\n )}\n\n {/* Attached files list */}\n {files.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {files.map((attachedFile) => (\n <div\n key={attachedFile.id}\n className=\"flex items-center gap-2 bg-background-50 px-3 py-2 rounded-lg border border-border-100\"\n >\n <FileText size={16} className=\"text-text-500 shrink-0\" />\n <Text className=\"text-sm text-text-700 truncate max-w-[200px]\">\n {attachedFile.file.name}\n </Text>\n <Text className=\"text-xs text-text-400 shrink-0\">\n {formatFileSize(attachedFile.file.size)}\n </Text>\n {!readOnly && (\n <button\n type=\"button\"\n onClick={() => onFileRemove(attachedFile.id)}\n className=\"text-text-400 hover:text-error-500 transition-colors shrink-0\"\n aria-label={`Remover ${attachedFile.file.name}`}\n >\n <X size={14} />\n </button>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n};\n\nexport { generateFileId, formatFileSize };\nexport default FileAttachment;\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} from './activityFilters';\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","import { ButtonHTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Lookup table for variant and action class combinations\n */\nconst VARIANT_ACTION_CLASSES = {\n solid: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n outline: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n link: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n} as const;\n\n/**\n * Lookup table for size classes\n */\nconst SIZE_CLASSES = {\n 'extra-small': 'text-xs px-3.5 py-2',\n small: 'text-sm px-4 py-2.5',\n medium: 'text-md px-5 py-2.5',\n large: 'text-lg px-6 py-3',\n 'extra-large': 'text-lg px-7 py-3.5',\n} as const;\n\n/**\n * Button component props interface\n */\ntype ButtonProps = {\n /** Content to be displayed inside the button */\n children: ReactNode;\n /** Ícone à esquerda do texto */\n iconLeft?: ReactNode;\n /** Ícone à direita do texto */\n iconRight?: ReactNode;\n /** Size of the button */\n size?: 'extra-small' | 'small' | 'medium' | 'large' | 'extra-large';\n /** Visual variant of the button */\n variant?: 'solid' | 'outline' | 'link';\n /** Action type of the button */\n action?: 'primary' | 'positive' | 'negative';\n /** Additional CSS classes to apply */\n className?: string;\n} & ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * Button component for Analytica Ensino platforms\n *\n * A flexible button component with multiple variants, sizes and actions.\n *\n * @param children - The content to display inside the button\n * @param size - The size variant (extra-small, small, medium, large, extra-large)\n * @param variant - The visual style variant (solid, outline, link)\n * @param action - The action type (primary, positive, negative)\n * @param className - Additional CSS classes\n * @param props - All other standard button HTML attributes\n * @returns A styled button element\n *\n * @example\n * ```tsx\n * <Button variant=\"solid\" action=\"primary\" size=\"medium\" onClick={() => console.log('clicked')}>\n * Click me\n * </Button>\n * ```\n */\nconst Button = ({\n children,\n iconLeft,\n iconRight,\n size = 'medium',\n variant = 'solid',\n action = 'primary',\n className = '',\n disabled,\n type = 'button',\n ...props\n}: ButtonProps) => {\n // Get classes from lookup tables\n const sizeClasses = SIZE_CLASSES[size];\n const variantClasses = VARIANT_ACTION_CLASSES[variant][action];\n\n const baseClasses =\n 'inline-flex items-center justify-center rounded-full cursor-pointer font-medium';\n\n return (\n <button\n className={cn(baseClasses, variantClasses, sizeClasses, className)}\n disabled={disabled}\n type={type}\n {...props}\n >\n {iconLeft && <span className=\"mr-2 flex items-center\">{iconLeft}</span>}\n {children}\n {iconRight && <span className=\"ml-2 flex items-center\">{iconRight}</span>}\n </button>\n );\n};\n\nexport default Button;\n","import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Base text component props\n */\ntype BaseTextProps = {\n /** Content to be displayed */\n children?: ReactNode;\n /** Text size variant */\n size?:\n | '2xs'\n | 'xs'\n | 'sm'\n | 'md'\n | 'lg'\n | 'xl'\n | '2xl'\n | '3xl'\n | '4xl'\n | '5xl'\n | '6xl';\n /** Font weight variant */\n weight?:\n | 'hairline'\n | 'light'\n | 'normal'\n | 'medium'\n | 'semibold'\n | 'bold'\n | 'extrabold'\n | 'black';\n /** Color variant - white for light backgrounds, black for dark backgrounds */\n color?: string;\n /** Additional CSS classes to apply */\n className?: string;\n};\n\n/**\n * Polymorphic text component props that ensures type safety based on the 'as' prop\n */\ntype TextProps<T extends ElementType = 'p'> = BaseTextProps & {\n /** HTML tag to render */\n as?: T;\n} & Omit<ComponentPropsWithoutRef<T>, keyof BaseTextProps>;\n\n/**\n * Text component for Analytica Ensino platforms\n *\n * A flexible polymorphic text component with multiple sizes, weights, and colors.\n * Automatically adapts to dark and light themes with full type safety.\n *\n * @param children - The content to display\n * @param size - The text size variant (2xs, xs, sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl)\n * @param weight - The font weight variant (hairline, light, normal, medium, semibold, bold, extrabold, black)\n * @param color - The color variant - adapts to theme\n * @param as - The HTML tag to render - determines allowed attributes via TypeScript\n * @param className - Additional CSS classes\n * @param props - HTML attributes valid for the chosen tag only\n * @returns A styled text element with type-safe attributes\n *\n * @example\n * ```tsx\n * <Text size=\"lg\" weight=\"bold\" color=\"text-info-800\">\n * This is a large, bold text\n * </Text>\n *\n * <Text as=\"a\" href=\"/link\" target=\"_blank\">\n * Link with type-safe anchor attributes\n * </Text>\n *\n * <Text as=\"button\" onClick={handleClick} disabled>\n * Button with type-safe button attributes\n * </Text>\n * ```\n */\nconst Text = <T extends ElementType = 'p'>({\n children,\n size = 'md',\n weight = 'normal',\n color = 'text-text-950',\n as,\n className = '',\n ...props\n}: TextProps<T>) => {\n let sizeClasses = '';\n let weightClasses = '';\n\n // Text size classes mapping\n const sizeClassMap = {\n '2xs': 'text-2xs',\n xs: 'text-xs',\n sm: 'text-sm',\n md: 'text-md',\n lg: 'text-lg',\n xl: 'text-xl',\n '2xl': 'text-2xl',\n '3xl': 'text-3xl',\n '4xl': 'text-4xl',\n '5xl': 'text-5xl',\n '6xl': 'text-6xl',\n } as const;\n\n sizeClasses = sizeClassMap[size] ?? sizeClassMap.md;\n\n // Font weight classes mapping\n const weightClassMap = {\n hairline: 'font-hairline',\n light: 'font-light',\n normal: 'font-normal',\n medium: 'font-medium',\n semibold: 'font-semibold',\n bold: 'font-bold',\n extrabold: 'font-extrabold',\n black: 'font-black',\n } as const;\n\n weightClasses = weightClassMap[weight] ?? weightClassMap.normal;\n\n const baseClasses = 'font-primary';\n const Component = as ?? ('p' as ElementType);\n\n return (\n <Component\n className={cn(baseClasses, sizeClasses, weightClasses, color, className)}\n {...props}\n >\n {children}\n </Component>\n );\n};\n\nexport default Text;\n"],"mappings":"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","names":["import_jsx_runtime","import_jsx_runtime"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/FileAttachment/FileAttachment.tsx","../../src/utils/utils.ts","../../src/components/Button/Button.tsx","../../src/components/Text/Text.tsx"],"sourcesContent":["import React, { useRef } from 'react';\nimport { Paperclip, FileText, X } from 'phosphor-react';\nimport Button from '../Button/Button';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\n\n/**\n * Represents an attached file with unique identifier\n */\nexport interface AttachedFile {\n /** The file object */\n file: File;\n /** Unique identifier for the file */\n id: string;\n}\n\n/**\n * Props for the FileAttachment component\n */\nexport interface FileAttachmentProps {\n /** List of attached files */\n files: AttachedFile[];\n /** Callback when files are added */\n onFilesAdd: (files: File[]) => void;\n /** Callback when a file is removed */\n onFileRemove: (id: string) => void;\n /** Whether the files are read-only (no removal allowed) */\n readOnly?: boolean;\n /** Text for the attach button */\n buttonLabel?: string;\n /** Whether to accept multiple files */\n multiple?: boolean;\n /** Additional CSS class */\n className?: string;\n /** Whether to hide the attach button */\n hideButton?: boolean;\n}\n\n/**\n * Formats file size to human-readable string\n * @param bytes - File size in bytes\n * @returns Formatted string (e.g., \"1.5 MB\")\n */\nconst formatFileSize = (bytes: number): string => {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024)\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n};\n\n/**\n * Generates a unique ID for file tracking\n * @returns Unique string identifier\n */\nconst generateFileId = (): string => {\n return crypto.randomUUID();\n};\n\n/**\n * Reusable file attachment component\n *\n * Allows users to attach multiple files with preview and removal functionality.\n * No file size or type restrictions are applied on the client side.\n *\n * @param props - Component props\n * @returns JSX element\n *\n * @example\n * ```tsx\n * const [files, setFiles] = useState<AttachedFile[]>([]);\n *\n * <FileAttachment\n * files={files}\n * onFilesAdd={(newFiles) => {\n * const attachedFiles = newFiles.map(file => ({\n * file,\n * id: generateFileId()\n * }));\n * setFiles(prev => [...prev, ...attachedFiles]);\n * }}\n * onFileRemove={(id) => setFiles(prev => prev.filter(f => f.id !== id))}\n * multiple\n * />\n * ```\n */\nconst FileAttachment = ({\n files,\n onFilesAdd,\n onFileRemove,\n readOnly = false,\n buttonLabel = 'Anexar',\n multiple = true,\n className,\n hideButton = false,\n}: FileAttachmentProps) => {\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n /**\n * Handle file input change\n * @param event - Input change event\n */\n const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n const selectedFiles = event.target.files;\n if (selectedFiles && selectedFiles.length > 0) {\n const filesArray: File[] = Array.from(selectedFiles);\n onFilesAdd(filesArray);\n }\n // Reset input to allow selecting the same file again\n if (fileInputRef.current) {\n fileInputRef.current.value = '';\n }\n };\n\n /**\n * Trigger file input click\n */\n const handleAttachClick = () => {\n fileInputRef.current?.click();\n };\n\n return (\n <div className={cn('flex flex-col gap-2', className)}>\n {/* Hidden file input */}\n <input\n type=\"file\"\n ref={fileInputRef}\n className=\"hidden\"\n onChange={handleFileChange}\n multiple={multiple}\n aria-label=\"Selecionar arquivos\"\n />\n\n {/* Attach button */}\n {!readOnly && !hideButton && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"small\"\n onClick={handleAttachClick}\n className=\"self-start flex items-center gap-2\"\n >\n <Paperclip size={16} />\n {buttonLabel}\n </Button>\n )}\n\n {/* Attached files list */}\n {files.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {files.map((attachedFile) => (\n <div\n key={attachedFile.id}\n className=\"flex items-center gap-2 bg-background-50 px-3 py-2 rounded-lg border border-border-100\"\n >\n <FileText size={16} className=\"text-text-500 shrink-0\" />\n <Text className=\"text-sm text-text-700 truncate max-w-[200px]\">\n {attachedFile.file.name}\n </Text>\n <Text className=\"text-xs text-text-400 shrink-0\">\n {formatFileSize(attachedFile.file.size)}\n </Text>\n {!readOnly && (\n <button\n type=\"button\"\n onClick={() => onFileRemove(attachedFile.id)}\n className=\"text-text-400 hover:text-error-500 transition-colors shrink-0\"\n aria-label={`Remover ${attachedFile.file.name}`}\n >\n <X size={14} />\n </button>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n};\n\nexport { generateFileId, formatFileSize };\nexport default FileAttachment;\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} from './activityFilters';\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","import { ButtonHTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Lookup table for variant and action class combinations\n */\nconst VARIANT_ACTION_CLASSES = {\n solid: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n outline: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n link: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n} as const;\n\n/**\n * Lookup table for size classes\n */\nconst SIZE_CLASSES = {\n 'extra-small': 'text-xs px-3.5 py-2',\n small: 'text-sm px-4 py-2.5',\n medium: 'text-md px-5 py-2.5',\n large: 'text-lg px-6 py-3',\n 'extra-large': 'text-lg px-7 py-3.5',\n} as const;\n\n/**\n * Button component props interface\n */\ntype ButtonProps = {\n /** Content to be displayed inside the button */\n children: ReactNode;\n /** Ícone à esquerda do texto */\n iconLeft?: ReactNode;\n /** Ícone à direita do texto */\n iconRight?: ReactNode;\n /** Size of the button */\n size?: 'extra-small' | 'small' | 'medium' | 'large' | 'extra-large';\n /** Visual variant of the button */\n variant?: 'solid' | 'outline' | 'link';\n /** Action type of the button */\n action?: 'primary' | 'positive' | 'negative';\n /** Additional CSS classes to apply */\n className?: string;\n} & ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * Button component for Analytica Ensino platforms\n *\n * A flexible button component with multiple variants, sizes and actions.\n *\n * @param children - The content to display inside the button\n * @param size - The size variant (extra-small, small, medium, large, extra-large)\n * @param variant - The visual style variant (solid, outline, link)\n * @param action - The action type (primary, positive, negative)\n * @param className - Additional CSS classes\n * @param props - All other standard button HTML attributes\n * @returns A styled button element\n *\n * @example\n * ```tsx\n * <Button variant=\"solid\" action=\"primary\" size=\"medium\" onClick={() => console.log('clicked')}>\n * Click me\n * </Button>\n * ```\n */\nconst Button = ({\n children,\n iconLeft,\n iconRight,\n size = 'medium',\n variant = 'solid',\n action = 'primary',\n className = '',\n disabled,\n type = 'button',\n ...props\n}: ButtonProps) => {\n // Get classes from lookup tables\n const sizeClasses = SIZE_CLASSES[size];\n const variantClasses = VARIANT_ACTION_CLASSES[variant][action];\n\n const baseClasses =\n 'inline-flex items-center justify-center rounded-full cursor-pointer font-medium';\n\n return (\n <button\n className={cn(baseClasses, variantClasses, sizeClasses, className)}\n disabled={disabled}\n type={type}\n {...props}\n >\n {iconLeft && <span className=\"mr-2 flex items-center\">{iconLeft}</span>}\n {children}\n {iconRight && <span className=\"ml-2 flex items-center\">{iconRight}</span>}\n </button>\n );\n};\n\nexport default Button;\n","import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Base text component props\n */\ntype BaseTextProps = {\n /** Content to be displayed */\n children?: ReactNode;\n /** Text size variant */\n size?:\n | '2xs'\n | 'xs'\n | 'sm'\n | 'md'\n | 'lg'\n | 'xl'\n | '2xl'\n | '3xl'\n | '4xl'\n | '5xl'\n | '6xl';\n /** Font weight variant */\n weight?:\n | 'hairline'\n | 'light'\n | 'normal'\n | 'medium'\n | 'semibold'\n | 'bold'\n | 'extrabold'\n | 'black';\n /** Color variant - white for light backgrounds, black for dark backgrounds */\n color?: string;\n /** Additional CSS classes to apply */\n className?: string;\n};\n\n/**\n * Polymorphic text component props that ensures type safety based on the 'as' prop\n */\ntype TextProps<T extends ElementType = 'p'> = BaseTextProps & {\n /** HTML tag to render */\n as?: T;\n} & Omit<ComponentPropsWithoutRef<T>, keyof BaseTextProps>;\n\n/**\n * Text component for Analytica Ensino platforms\n *\n * A flexible polymorphic text component with multiple sizes, weights, and colors.\n * Automatically adapts to dark and light themes with full type safety.\n *\n * @param children - The content to display\n * @param size - The text size variant (2xs, xs, sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl)\n * @param weight - The font weight variant (hairline, light, normal, medium, semibold, bold, extrabold, black)\n * @param color - The color variant - adapts to theme\n * @param as - The HTML tag to render - determines allowed attributes via TypeScript\n * @param className - Additional CSS classes\n * @param props - HTML attributes valid for the chosen tag only\n * @returns A styled text element with type-safe attributes\n *\n * @example\n * ```tsx\n * <Text size=\"lg\" weight=\"bold\" color=\"text-info-800\">\n * This is a large, bold text\n * </Text>\n *\n * <Text as=\"a\" href=\"/link\" target=\"_blank\">\n * Link with type-safe anchor attributes\n * </Text>\n *\n * <Text as=\"button\" onClick={handleClick} disabled>\n * Button with type-safe button attributes\n * </Text>\n * ```\n */\nconst Text = <T extends ElementType = 'p'>({\n children,\n size = 'md',\n weight = 'normal',\n color = 'text-text-950',\n as,\n className = '',\n ...props\n}: TextProps<T>) => {\n let sizeClasses = '';\n let weightClasses = '';\n\n // Text size classes mapping\n const sizeClassMap = {\n '2xs': 'text-2xs',\n xs: 'text-xs',\n sm: 'text-sm',\n md: 'text-md',\n lg: 'text-lg',\n xl: 'text-xl',\n '2xl': 'text-2xl',\n '3xl': 'text-3xl',\n '4xl': 'text-4xl',\n '5xl': 'text-5xl',\n '6xl': 'text-6xl',\n } as const;\n\n sizeClasses = sizeClassMap[size] ?? sizeClassMap.md;\n\n // Font weight classes mapping\n const weightClassMap = {\n hairline: 'font-hairline',\n light: 'font-light',\n normal: 'font-normal',\n medium: 'font-medium',\n semibold: 'font-semibold',\n bold: 'font-bold',\n extrabold: 'font-extrabold',\n black: 'font-black',\n } as const;\n\n weightClasses = weightClassMap[weight] ?? weightClassMap.normal;\n\n const baseClasses = 'font-primary';\n const Component = as ?? ('p' as ElementType);\n\n return (\n <Component\n className={cn(baseClasses, sizeClasses, weightClasses, color, className)}\n {...props}\n >\n {children}\n </Component>\n );\n};\n\nexport default Text;\n"],"mappings":"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","names":["jsx","jsx","jsxs"]}
1
+ {"version":3,"sources":["../../src/components/FileAttachment/FileAttachment.tsx","../../src/utils/utils.ts","../../src/components/Button/Button.tsx","../../src/components/Text/Text.tsx"],"sourcesContent":["import React, { useRef } from 'react';\nimport { Paperclip, FileText, X } from 'phosphor-react';\nimport Button from '../Button/Button';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\n\n/**\n * Represents an attached file with unique identifier\n */\nexport interface AttachedFile {\n /** The file object */\n file: File;\n /** Unique identifier for the file */\n id: string;\n}\n\n/**\n * Props for the FileAttachment component\n */\nexport interface FileAttachmentProps {\n /** List of attached files */\n files: AttachedFile[];\n /** Callback when files are added */\n onFilesAdd: (files: File[]) => void;\n /** Callback when a file is removed */\n onFileRemove: (id: string) => void;\n /** Whether the files are read-only (no removal allowed) */\n readOnly?: boolean;\n /** Text for the attach button */\n buttonLabel?: string;\n /** Whether to accept multiple files */\n multiple?: boolean;\n /** Additional CSS class */\n className?: string;\n /** Whether to hide the attach button */\n hideButton?: boolean;\n}\n\n/**\n * Formats file size to human-readable string\n * @param bytes - File size in bytes\n * @returns Formatted string (e.g., \"1.5 MB\")\n */\nconst formatFileSize = (bytes: number): string => {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024)\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n};\n\n/**\n * Generates a unique ID for file tracking\n * @returns Unique string identifier\n */\nconst generateFileId = (): string => {\n return crypto.randomUUID();\n};\n\n/**\n * Reusable file attachment component\n *\n * Allows users to attach multiple files with preview and removal functionality.\n * No file size or type restrictions are applied on the client side.\n *\n * @param props - Component props\n * @returns JSX element\n *\n * @example\n * ```tsx\n * const [files, setFiles] = useState<AttachedFile[]>([]);\n *\n * <FileAttachment\n * files={files}\n * onFilesAdd={(newFiles) => {\n * const attachedFiles = newFiles.map(file => ({\n * file,\n * id: generateFileId()\n * }));\n * setFiles(prev => [...prev, ...attachedFiles]);\n * }}\n * onFileRemove={(id) => setFiles(prev => prev.filter(f => f.id !== id))}\n * multiple\n * />\n * ```\n */\nconst FileAttachment = ({\n files,\n onFilesAdd,\n onFileRemove,\n readOnly = false,\n buttonLabel = 'Anexar',\n multiple = true,\n className,\n hideButton = false,\n}: FileAttachmentProps) => {\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n /**\n * Handle file input change\n * @param event - Input change event\n */\n const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n const selectedFiles = event.target.files;\n if (selectedFiles && selectedFiles.length > 0) {\n const filesArray: File[] = Array.from(selectedFiles);\n onFilesAdd(filesArray);\n }\n // Reset input to allow selecting the same file again\n if (fileInputRef.current) {\n fileInputRef.current.value = '';\n }\n };\n\n /**\n * Trigger file input click\n */\n const handleAttachClick = () => {\n fileInputRef.current?.click();\n };\n\n return (\n <div className={cn('flex flex-col gap-2', className)}>\n {/* Hidden file input */}\n <input\n type=\"file\"\n ref={fileInputRef}\n className=\"hidden\"\n onChange={handleFileChange}\n multiple={multiple}\n aria-label=\"Selecionar arquivos\"\n />\n\n {/* Attach button */}\n {!readOnly && !hideButton && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"small\"\n onClick={handleAttachClick}\n className=\"self-start flex items-center gap-2\"\n >\n <Paperclip size={16} />\n {buttonLabel}\n </Button>\n )}\n\n {/* Attached files list */}\n {files.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {files.map((attachedFile) => (\n <div\n key={attachedFile.id}\n className=\"flex items-center gap-2 bg-background-50 px-3 py-2 rounded-lg border border-border-100\"\n >\n <FileText size={16} className=\"text-text-500 shrink-0\" />\n <Text className=\"text-sm text-text-700 truncate max-w-[200px]\">\n {attachedFile.file.name}\n </Text>\n <Text className=\"text-xs text-text-400 shrink-0\">\n {formatFileSize(attachedFile.file.size)}\n </Text>\n {!readOnly && (\n <button\n type=\"button\"\n onClick={() => onFileRemove(attachedFile.id)}\n className=\"text-text-400 hover:text-error-500 transition-colors shrink-0\"\n aria-label={`Remover ${attachedFile.file.name}`}\n >\n <X size={14} />\n </button>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n};\n\nexport { generateFileId, formatFileSize };\nexport default FileAttachment;\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} from './activityFilters';\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","import { ButtonHTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Lookup table for variant and action class combinations\n */\nconst VARIANT_ACTION_CLASSES = {\n solid: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n outline: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n link: {\n primary:\n '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',\n positive:\n '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',\n negative:\n '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',\n },\n} as const;\n\n/**\n * Lookup table for size classes\n */\nconst SIZE_CLASSES = {\n 'extra-small': 'text-xs px-3.5 py-2',\n small: 'text-sm px-4 py-2.5',\n medium: 'text-md px-5 py-2.5',\n large: 'text-lg px-6 py-3',\n 'extra-large': 'text-lg px-7 py-3.5',\n} as const;\n\n/**\n * Button component props interface\n */\ntype ButtonProps = {\n /** Content to be displayed inside the button */\n children: ReactNode;\n /** Ícone à esquerda do texto */\n iconLeft?: ReactNode;\n /** Ícone à direita do texto */\n iconRight?: ReactNode;\n /** Size of the button */\n size?: 'extra-small' | 'small' | 'medium' | 'large' | 'extra-large';\n /** Visual variant of the button */\n variant?: 'solid' | 'outline' | 'link';\n /** Action type of the button */\n action?: 'primary' | 'positive' | 'negative';\n /** Additional CSS classes to apply */\n className?: string;\n} & ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * Button component for Analytica Ensino platforms\n *\n * A flexible button component with multiple variants, sizes and actions.\n *\n * @param children - The content to display inside the button\n * @param size - The size variant (extra-small, small, medium, large, extra-large)\n * @param variant - The visual style variant (solid, outline, link)\n * @param action - The action type (primary, positive, negative)\n * @param className - Additional CSS classes\n * @param props - All other standard button HTML attributes\n * @returns A styled button element\n *\n * @example\n * ```tsx\n * <Button variant=\"solid\" action=\"primary\" size=\"medium\" onClick={() => console.log('clicked')}>\n * Click me\n * </Button>\n * ```\n */\nconst Button = ({\n children,\n iconLeft,\n iconRight,\n size = 'medium',\n variant = 'solid',\n action = 'primary',\n className = '',\n disabled,\n type = 'button',\n ...props\n}: ButtonProps) => {\n // Get classes from lookup tables\n const sizeClasses = SIZE_CLASSES[size];\n const variantClasses = VARIANT_ACTION_CLASSES[variant][action];\n\n const baseClasses =\n 'inline-flex items-center justify-center rounded-full cursor-pointer font-medium';\n\n return (\n <button\n className={cn(baseClasses, variantClasses, sizeClasses, className)}\n disabled={disabled}\n type={type}\n {...props}\n >\n {iconLeft && <span className=\"mr-2 flex items-center\">{iconLeft}</span>}\n {children}\n {iconRight && <span className=\"ml-2 flex items-center\">{iconRight}</span>}\n </button>\n );\n};\n\nexport default Button;\n","import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * Base text component props\n */\ntype BaseTextProps = {\n /** Content to be displayed */\n children?: ReactNode;\n /** Text size variant */\n size?:\n | '2xs'\n | 'xs'\n | 'sm'\n | 'md'\n | 'lg'\n | 'xl'\n | '2xl'\n | '3xl'\n | '4xl'\n | '5xl'\n | '6xl';\n /** Font weight variant */\n weight?:\n | 'hairline'\n | 'light'\n | 'normal'\n | 'medium'\n | 'semibold'\n | 'bold'\n | 'extrabold'\n | 'black';\n /** Color variant - white for light backgrounds, black for dark backgrounds */\n color?: string;\n /** Additional CSS classes to apply */\n className?: string;\n};\n\n/**\n * Polymorphic text component props that ensures type safety based on the 'as' prop\n */\ntype TextProps<T extends ElementType = 'p'> = BaseTextProps & {\n /** HTML tag to render */\n as?: T;\n} & Omit<ComponentPropsWithoutRef<T>, keyof BaseTextProps>;\n\n/**\n * Text component for Analytica Ensino platforms\n *\n * A flexible polymorphic text component with multiple sizes, weights, and colors.\n * Automatically adapts to dark and light themes with full type safety.\n *\n * @param children - The content to display\n * @param size - The text size variant (2xs, xs, sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl)\n * @param weight - The font weight variant (hairline, light, normal, medium, semibold, bold, extrabold, black)\n * @param color - The color variant - adapts to theme\n * @param as - The HTML tag to render - determines allowed attributes via TypeScript\n * @param className - Additional CSS classes\n * @param props - HTML attributes valid for the chosen tag only\n * @returns A styled text element with type-safe attributes\n *\n * @example\n * ```tsx\n * <Text size=\"lg\" weight=\"bold\" color=\"text-info-800\">\n * This is a large, bold text\n * </Text>\n *\n * <Text as=\"a\" href=\"/link\" target=\"_blank\">\n * Link with type-safe anchor attributes\n * </Text>\n *\n * <Text as=\"button\" onClick={handleClick} disabled>\n * Button with type-safe button attributes\n * </Text>\n * ```\n */\nconst Text = <T extends ElementType = 'p'>({\n children,\n size = 'md',\n weight = 'normal',\n color = 'text-text-950',\n as,\n className = '',\n ...props\n}: TextProps<T>) => {\n let sizeClasses = '';\n let weightClasses = '';\n\n // Text size classes mapping\n const sizeClassMap = {\n '2xs': 'text-2xs',\n xs: 'text-xs',\n sm: 'text-sm',\n md: 'text-md',\n lg: 'text-lg',\n xl: 'text-xl',\n '2xl': 'text-2xl',\n '3xl': 'text-3xl',\n '4xl': 'text-4xl',\n '5xl': 'text-5xl',\n '6xl': 'text-6xl',\n } as const;\n\n sizeClasses = sizeClassMap[size] ?? sizeClassMap.md;\n\n // Font weight classes mapping\n const weightClassMap = {\n hairline: 'font-hairline',\n light: 'font-light',\n normal: 'font-normal',\n medium: 'font-medium',\n semibold: 'font-semibold',\n bold: 'font-bold',\n extrabold: 'font-extrabold',\n black: 'font-black',\n } as const;\n\n weightClasses = weightClassMap[weight] ?? weightClassMap.normal;\n\n const baseClasses = 'font-primary';\n const Component = as ?? ('p' as ElementType);\n\n return (\n <Component\n className={cn(baseClasses, sizeClasses, weightClasses, color, className)}\n {...props}\n >\n {children}\n </Component>\n );\n};\n\nexport default Text;\n"],"mappings":"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","names":["jsx","jsx","jsxs"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/IconButton/IconButton.tsx","../../src/utils/utils.ts"],"sourcesContent":["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} from './activityFilters';\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;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA4D;;;ACA5D,kBAAsC;AACtC,4BAAwB;AAEjB,SAAS,MAAM,QAAsB;AAC1C,aAAO,mCAAQ,kBAAK,MAAM,CAAC;AAC7B;;;ADoHQ;AAxDR,IAAM,iBAAa;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,sDAAC,UAAK,WAAU,oCAAoC,gBAAK;AAAA;AAAA,IAC3D;AAAA,EAEJ;AACF;AAEA,WAAW,cAAc;AAEzB,IAAO,qBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/IconButton/IconButton.tsx","../../src/utils/utils.ts"],"sourcesContent":["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} from './activityFilters';\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;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA4D;;;ACA5D,kBAAsC;AACtC,4BAAwB;AAEjB,SAAS,MAAM,QAAsB;AAC1C,aAAO,mCAAQ,kBAAK,MAAM,CAAC;AAC7B;;;ADoHQ;AAxDR,IAAM,iBAAa;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,sDAAC,UAAK,WAAU,oCAAoC,gBAAK;AAAA;AAAA,IAC3D;AAAA,EAEJ;AACF;AAEA,WAAW,cAAc;AAEzB,IAAO,qBAAQ;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/IconButton/IconButton.tsx","../../src/utils/utils.ts"],"sourcesContent":["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} from './activityFilters';\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,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;","names":[]}
1
+ {"version":3,"sources":["../../src/components/IconButton/IconButton.tsx","../../src/utils/utils.ts"],"sourcesContent":["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} from './activityFilters';\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,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;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/IconRoundedButton/IconRoundedButton.tsx","../../src/utils/utils.ts"],"sourcesContent":["import { ButtonHTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * IconRoundedButton component props interface\n */\ntype IconRoundedButtonProps = {\n /** Ícone a ser exibido no botão */\n icon: ReactNode;\n /** Additional CSS classes to apply */\n className?: string;\n} & ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * IconRoundedButton component for Analytica Ensino platforms\n *\n * Um botão redondo simples que exibe apenas um ícone.\n * Ideal para ações como navegação, fechar, editar, etc.\n *\n * @param icon - O ícone a ser exibido no 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 estilizado e redondo\n *\n * @example\n * ```tsx\n * <IconRoundedButton\n * icon={<ChevronRightIcon />}\n * onClick={() => console.log('clicked')}\n * />\n * ```\n */\nconst IconRoundedButton = ({\n icon,\n className = '',\n disabled,\n ...props\n}: IconRoundedButtonProps) => {\n const baseClasses = [\n 'inline-flex',\n 'items-center',\n 'justify-center',\n 'w-8',\n 'h-8',\n 'rounded-full',\n 'cursor-pointer',\n 'border',\n 'border-background-200',\n 'bg-background',\n 'text-text-950',\n 'hover:shadow-hard-shadow-1',\n 'focus-visible:outline-none',\n 'focus-visible:shadow-hard-shadow-1',\n 'focus-visible:ring-2',\n 'focus-visible:ring-indicator-info',\n 'focus-visible:ring-offset-0',\n 'disabled:opacity-50',\n 'disabled:cursor-not-allowed',\n ].join(' ');\n\n return (\n <button\n type=\"button\"\n className={cn(baseClasses, className)}\n disabled={disabled}\n {...props}\n >\n <span className=\"flex items-center justify-center w-5 h-5\">{icon}</span>\n </button>\n );\n};\n\nexport default IconRoundedButton;\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} from './activityFilters';\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;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAAsC;AACtC,4BAAwB;AAEjB,SAAS,MAAM,QAAsB;AAC1C,aAAO,mCAAQ,kBAAK,MAAM,CAAC;AAC7B;;;AD8DM;AAnCN,IAAM,oBAAoB,CAAC;AAAA,EACzB;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,GAAG;AACL,MAA8B;AAC5B,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,GAAG;AAEV,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAW,GAAG,aAAa,SAAS;AAAA,MACpC;AAAA,MACC,GAAG;AAAA,MAEJ,sDAAC,UAAK,WAAU,4CAA4C,gBAAK;AAAA;AAAA,EACnE;AAEJ;AAEA,IAAO,4BAAQ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/IconRoundedButton/IconRoundedButton.tsx","../../src/utils/utils.ts"],"sourcesContent":["import { ButtonHTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * IconRoundedButton component props interface\n */\ntype IconRoundedButtonProps = {\n /** Ícone a ser exibido no botão */\n icon: ReactNode;\n /** Additional CSS classes to apply */\n className?: string;\n} & ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * IconRoundedButton component for Analytica Ensino platforms\n *\n * Um botão redondo simples que exibe apenas um ícone.\n * Ideal para ações como navegação, fechar, editar, etc.\n *\n * @param icon - O ícone a ser exibido no 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 estilizado e redondo\n *\n * @example\n * ```tsx\n * <IconRoundedButton\n * icon={<ChevronRightIcon />}\n * onClick={() => console.log('clicked')}\n * />\n * ```\n */\nconst IconRoundedButton = ({\n icon,\n className = '',\n disabled,\n ...props\n}: IconRoundedButtonProps) => {\n const baseClasses = [\n 'inline-flex',\n 'items-center',\n 'justify-center',\n 'w-8',\n 'h-8',\n 'rounded-full',\n 'cursor-pointer',\n 'border',\n 'border-background-200',\n 'bg-background',\n 'text-text-950',\n 'hover:shadow-hard-shadow-1',\n 'focus-visible:outline-none',\n 'focus-visible:shadow-hard-shadow-1',\n 'focus-visible:ring-2',\n 'focus-visible:ring-indicator-info',\n 'focus-visible:ring-offset-0',\n 'disabled:opacity-50',\n 'disabled:cursor-not-allowed',\n ].join(' ');\n\n return (\n <button\n type=\"button\"\n className={cn(baseClasses, className)}\n disabled={disabled}\n {...props}\n >\n <span className=\"flex items-center justify-center w-5 h-5\">{icon}</span>\n </button>\n );\n};\n\nexport default IconRoundedButton;\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} from './activityFilters';\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;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAAsC;AACtC,4BAAwB;AAEjB,SAAS,MAAM,QAAsB;AAC1C,aAAO,mCAAQ,kBAAK,MAAM,CAAC;AAC7B;;;AD8DM;AAnCN,IAAM,oBAAoB,CAAC;AAAA,EACzB;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,GAAG;AACL,MAA8B;AAC5B,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,GAAG;AAEV,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAW,GAAG,aAAa,SAAS;AAAA,MACpC;AAAA,MACC,GAAG;AAAA,MAEJ,sDAAC,UAAK,WAAU,4CAA4C,gBAAK;AAAA;AAAA,EACnE;AAEJ;AAEA,IAAO,4BAAQ;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/utils.ts","../../src/components/IconRoundedButton/IconRoundedButton.tsx"],"sourcesContent":["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} from './activityFilters';\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","import { ButtonHTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * IconRoundedButton component props interface\n */\ntype IconRoundedButtonProps = {\n /** Ícone a ser exibido no botão */\n icon: ReactNode;\n /** Additional CSS classes to apply */\n className?: string;\n} & ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * IconRoundedButton component for Analytica Ensino platforms\n *\n * Um botão redondo simples que exibe apenas um ícone.\n * Ideal para ações como navegação, fechar, editar, etc.\n *\n * @param icon - O ícone a ser exibido no 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 estilizado e redondo\n *\n * @example\n * ```tsx\n * <IconRoundedButton\n * icon={<ChevronRightIcon />}\n * onClick={() => console.log('clicked')}\n * />\n * ```\n */\nconst IconRoundedButton = ({\n icon,\n className = '',\n disabled,\n ...props\n}: IconRoundedButtonProps) => {\n const baseClasses = [\n 'inline-flex',\n 'items-center',\n 'justify-center',\n 'w-8',\n 'h-8',\n 'rounded-full',\n 'cursor-pointer',\n 'border',\n 'border-background-200',\n 'bg-background',\n 'text-text-950',\n 'hover:shadow-hard-shadow-1',\n 'focus-visible:outline-none',\n 'focus-visible:shadow-hard-shadow-1',\n 'focus-visible:ring-2',\n 'focus-visible:ring-indicator-info',\n 'focus-visible:ring-offset-0',\n 'disabled:opacity-50',\n 'disabled:cursor-not-allowed',\n ].join(' ');\n\n return (\n <button\n type=\"button\"\n className={cn(baseClasses, className)}\n disabled={disabled}\n {...props}\n >\n <span className=\"flex items-center justify-center w-5 h-5\">{icon}</span>\n </button>\n );\n};\n\nexport default IconRoundedButton;\n"],"mappings":";AAAA,SAAS,YAA6B;AACtC,SAAS,eAAe;AAEjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;AC8DM;AAnCN,IAAM,oBAAoB,CAAC;AAAA,EACzB;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,GAAG;AACL,MAA8B;AAC5B,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,GAAG;AAEV,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAW,GAAG,aAAa,SAAS;AAAA,MACpC;AAAA,MACC,GAAG;AAAA,MAEJ,8BAAC,UAAK,WAAU,4CAA4C,gBAAK;AAAA;AAAA,EACnE;AAEJ;AAEA,IAAO,4BAAQ;","names":[]}
1
+ {"version":3,"sources":["../../src/utils/utils.ts","../../src/components/IconRoundedButton/IconRoundedButton.tsx"],"sourcesContent":["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} from './activityFilters';\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","import { ButtonHTMLAttributes, ReactNode } from 'react';\nimport { cn } from '../../utils/utils';\n\n/**\n * IconRoundedButton component props interface\n */\ntype IconRoundedButtonProps = {\n /** Ícone a ser exibido no botão */\n icon: ReactNode;\n /** Additional CSS classes to apply */\n className?: string;\n} & ButtonHTMLAttributes<HTMLButtonElement>;\n\n/**\n * IconRoundedButton component for Analytica Ensino platforms\n *\n * Um botão redondo simples que exibe apenas um ícone.\n * Ideal para ações como navegação, fechar, editar, etc.\n *\n * @param icon - O ícone a ser exibido no 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 estilizado e redondo\n *\n * @example\n * ```tsx\n * <IconRoundedButton\n * icon={<ChevronRightIcon />}\n * onClick={() => console.log('clicked')}\n * />\n * ```\n */\nconst IconRoundedButton = ({\n icon,\n className = '',\n disabled,\n ...props\n}: IconRoundedButtonProps) => {\n const baseClasses = [\n 'inline-flex',\n 'items-center',\n 'justify-center',\n 'w-8',\n 'h-8',\n 'rounded-full',\n 'cursor-pointer',\n 'border',\n 'border-background-200',\n 'bg-background',\n 'text-text-950',\n 'hover:shadow-hard-shadow-1',\n 'focus-visible:outline-none',\n 'focus-visible:shadow-hard-shadow-1',\n 'focus-visible:ring-2',\n 'focus-visible:ring-indicator-info',\n 'focus-visible:ring-offset-0',\n 'disabled:opacity-50',\n 'disabled:cursor-not-allowed',\n ].join(' ');\n\n return (\n <button\n type=\"button\"\n className={cn(baseClasses, className)}\n disabled={disabled}\n {...props}\n >\n <span className=\"flex items-center justify-center w-5 h-5\">{icon}</span>\n </button>\n );\n};\n\nexport default IconRoundedButton;\n"],"mappings":";AAAA,SAAS,YAA6B;AACtC,SAAS,eAAe;AAEjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;AC8DM;AAnCN,IAAM,oBAAoB,CAAC;AAAA,EACzB;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,GAAG;AACL,MAA8B;AAC5B,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,GAAG;AAEV,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAW,GAAG,aAAa,SAAS;AAAA,MACpC;AAAA,MACC,GAAG;AAAA,MAEJ,8BAAC,UAAK,WAAU,4CAA4C,gBAAK;AAAA;AAAA,EACnE;AAEJ;AAEA,IAAO,4BAAQ;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/LatexRenderer/LatexRenderer.tsx","../../src/utils/utils.ts"],"sourcesContent":["import { CSSProperties, ReactNode } from 'react';\nimport 'katex/dist/katex.min.css';\nimport { InlineMath, BlockMath } from 'react-katex';\nimport DOMPurify from 'dompurify';\nimport parse, {\n DOMNode,\n Element,\n HTMLReactParserOptions,\n} from 'html-react-parser';\nimport { cn } from '../../utils/utils';\n\n/**\n * Props for the LatexRenderer component\n */\nexport interface LatexRendererProps {\n /** HTML content containing LaTeX expressions to render */\n content: string;\n /** Additional CSS classes to apply */\n className?: string;\n /** Inline styles to apply */\n style?: CSSProperties;\n /** Custom error renderer for invalid LaTeX expressions */\n onError?: (latex: string) => ReactNode;\n}\n\n/**\n * Helper function to sanitize HTML content to prevent XSS attacks\n */\nconst sanitizeHtml = (value: string): string => {\n return DOMPurify.sanitize(value, {\n ADD_ATTR: ['data-latex', 'data-display-mode', 'data-math', 'data-math-id'],\n });\n};\n\n/**\n * Helper function to decode HTML entities\n * Converts entities like &amp;, &lt;, &gt; back to their actual characters\n */\nconst decodeHtmlEntities = (str: string): string => {\n const textarea = document.createElement('textarea');\n textarea.innerHTML = str;\n return textarea.value;\n};\n\n/**\n * Helper function to clean latex from invisible characters\n */\nconst cleanLatex = (str: string): string => {\n // Remove zero-width characters, invisible characters, and other problematic Unicode\n return str.replaceAll(/[\\u200B-\\u200D\\uFEFF]/g, '').trim();\n};\n\n/**\n * Type for math parts used in rendering\n */\ninterface MathPart {\n id: number;\n type: 'inline' | 'block';\n latex: string;\n}\n\n/**\n * Creates a replace function for html-react-parser that replaces math placeholders\n * with KaTeX components\n */\nconst createMathReplacer = (\n mathParts: MathPart[],\n errorRenderer: (latex: string) => ReactNode\n) => {\n return (domNode: DOMNode) => {\n if (\n domNode instanceof Element &&\n domNode.name === 'span' &&\n domNode.attribs['data-math-id']\n ) {\n const mathId = Number.parseInt(domNode.attribs['data-math-id'], 10);\n const mathPart = mathParts[mathId];\n\n if (!mathPart) return domNode;\n\n if (mathPart.type === 'inline') {\n return (\n <InlineMath\n key={`math-${mathId}`}\n math={mathPart.latex}\n renderError={() => errorRenderer(mathPart.latex)}\n />\n );\n } else {\n return (\n <div key={`math-${mathId}`} className=\"my-2.5 text-center\">\n <BlockMath\n math={mathPart.latex}\n renderError={() => errorRenderer(mathPart.latex)}\n />\n </div>\n );\n }\n }\n return domNode;\n };\n};\n\n/**\n * LatexRenderer component for Analytica Ensino platforms\n *\n * Renders HTML content with embedded LaTeX/KaTeX mathematical expressions.\n * Supports multiple LaTeX formats:\n * - Inline math: `$...$` or `<latex>...</latex>`\n * - Block math: `$$...$$`\n * - LaTeX environments: `\\begin{equation}...\\end{equation}`, etc.\n * - Editor format: `<span class=\"math-formula\" data-latex=\"...\">...</span>`\n *\n * @param content - HTML content with LaTeX expressions\n * @param className - Additional CSS classes\n * @param style - Inline styles\n * @param onError - Custom error renderer\n * @returns Rendered content with mathematical expressions\n *\n * @example\n * ```tsx\n * <LatexRenderer content=\"The formula is $E = mc^2$\" />\n *\n * <LatexRenderer\n * content=\"Block equation: $$\\sum_{i=1}^{n} x_i$$\"\n * className=\"my-custom-class\"\n * />\n *\n * <LatexRenderer\n * content=\"<p>Matrix: \\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}</p>\"\n * onError={(latex) => <span>Invalid: {latex}</span>}\n * />\n * ```\n */\nconst LatexRenderer = ({\n content,\n className,\n style,\n onError,\n}: LatexRendererProps) => {\n const renderContentWithMath = (htmlContent: string) => {\n if (!htmlContent) return null;\n\n let processedContent = htmlContent;\n const mathParts: MathPart[] = [];\n\n // Step 1: Handle math-formula spans (from the editor)\n const mathFormulaPattern =\n /<span[^>]*class=\"math-formula\"[^>]*data-latex=\"([^\"]*)\"[^>]*>[\\s\\S]*?<\\/span>/g;\n processedContent = processedContent.replaceAll(\n mathFormulaPattern,\n (match, latex) => {\n const isDisplayMode = match.includes('data-display-mode=\"true\"');\n const id = mathParts.length;\n mathParts.push({\n id,\n type: isDisplayMode ? 'block' : 'inline',\n latex: cleanLatex(decodeHtmlEntities(latex)),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 2: Handle wrapped math expressions (from math modal - legacy)\n const wrappedMathPattern =\n /<span[^>]*class=\"math-expression\"[^>]*data-math=\"([^\"]*)\"[^>]*>.*?<\\/span>/g;\n processedContent = processedContent.replaceAll(\n wrappedMathPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'inline',\n latex: cleanLatex(decodeHtmlEntities(latex)),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 3: Handle raw $$...$$ expressions (manual input or saved content) - BEFORE single $\n const doubleDollarPattern = /(?<!\\\\)\\$\\$([\\s\\S]+?)\\$\\$/g;\n processedContent = processedContent.replaceAll(\n doubleDollarPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'block',\n latex: cleanLatex(latex),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 4: Handle single $...$ expressions for inline math\n const singleDollarPattern = /(?<!\\\\)\\$([\\s\\S]+?)\\$/g;\n processedContent = processedContent.replaceAll(\n singleDollarPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'inline',\n latex: cleanLatex(latex),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 5: Handle <latex>...</latex> tags for inline math\n const latexTagPattern =\n /(?:<latex>|&lt;latex&gt;)([\\s\\S]*?)(?:<\\/latex>|&lt;\\/latex&gt;)/g;\n processedContent = processedContent.replaceAll(\n latexTagPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'inline',\n latex: cleanLatex(latex),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 6: Handle standalone LaTeX environments (align, equation, pmatrix, etc.) for block math\n const latexEnvPattern = /\\\\begin\\{([^}]+)\\}([\\s\\S]*?)\\\\end\\{\\1\\}/g;\n processedContent = processedContent.replaceAll(latexEnvPattern, (match) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'block',\n latex: cleanLatex(match),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n });\n\n // Sanitize the HTML with placeholders\n const sanitizedContent = sanitizeHtml(processedContent);\n\n // Default error renderer\n const defaultErrorRenderer = (latex: string) => (\n <span className=\"text-red-600\">Math Error: {latex}</span>\n );\n\n const errorRenderer = onError || defaultErrorRenderer;\n\n // Parse HTML and replace math placeholders with React components\n const options: HTMLReactParserOptions = {\n replace: createMathReplacer(mathParts, errorRenderer),\n };\n\n return <>{parse(sanitizedContent, options)}</>;\n };\n\n return (\n <div\n className={cn('whitespace-pre-wrap leading-relaxed', className)}\n style={style}\n >\n {renderContentWithMath(content)}\n </div>\n );\n};\n\nexport default LatexRenderer;\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} from './activityFilters';\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":"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","names":["DOMPurify","parse"]}
1
+ {"version":3,"sources":["../../src/components/LatexRenderer/LatexRenderer.tsx","../../src/utils/utils.ts"],"sourcesContent":["import { CSSProperties, ReactNode } from 'react';\nimport 'katex/dist/katex.min.css';\nimport { InlineMath, BlockMath } from 'react-katex';\nimport DOMPurify from 'dompurify';\nimport parse, {\n DOMNode,\n Element,\n HTMLReactParserOptions,\n} from 'html-react-parser';\nimport { cn } from '../../utils/utils';\n\n/**\n * Props for the LatexRenderer component\n */\nexport interface LatexRendererProps {\n /** HTML content containing LaTeX expressions to render */\n content: string;\n /** Additional CSS classes to apply */\n className?: string;\n /** Inline styles to apply */\n style?: CSSProperties;\n /** Custom error renderer for invalid LaTeX expressions */\n onError?: (latex: string) => ReactNode;\n}\n\n/**\n * Helper function to sanitize HTML content to prevent XSS attacks\n */\nconst sanitizeHtml = (value: string): string => {\n return DOMPurify.sanitize(value, {\n ADD_ATTR: ['data-latex', 'data-display-mode', 'data-math', 'data-math-id'],\n });\n};\n\n/**\n * Helper function to decode HTML entities\n * Converts entities like &amp;, &lt;, &gt; back to their actual characters\n */\nconst decodeHtmlEntities = (str: string): string => {\n const textarea = document.createElement('textarea');\n textarea.innerHTML = str;\n return textarea.value;\n};\n\n/**\n * Helper function to clean latex from invisible characters\n */\nconst cleanLatex = (str: string): string => {\n // Remove zero-width characters, invisible characters, and other problematic Unicode\n return str.replaceAll(/[\\u200B-\\u200D\\uFEFF]/g, '').trim();\n};\n\n/**\n * Type for math parts used in rendering\n */\ninterface MathPart {\n id: number;\n type: 'inline' | 'block';\n latex: string;\n}\n\n/**\n * Creates a replace function for html-react-parser that replaces math placeholders\n * with KaTeX components\n */\nconst createMathReplacer = (\n mathParts: MathPart[],\n errorRenderer: (latex: string) => ReactNode\n) => {\n return (domNode: DOMNode) => {\n if (\n domNode instanceof Element &&\n domNode.name === 'span' &&\n domNode.attribs['data-math-id']\n ) {\n const mathId = Number.parseInt(domNode.attribs['data-math-id'], 10);\n const mathPart = mathParts[mathId];\n\n if (!mathPart) return domNode;\n\n if (mathPart.type === 'inline') {\n return (\n <InlineMath\n key={`math-${mathId}`}\n math={mathPart.latex}\n renderError={() => errorRenderer(mathPart.latex)}\n />\n );\n } else {\n return (\n <div key={`math-${mathId}`} className=\"my-2.5 text-center\">\n <BlockMath\n math={mathPart.latex}\n renderError={() => errorRenderer(mathPart.latex)}\n />\n </div>\n );\n }\n }\n return domNode;\n };\n};\n\n/**\n * LatexRenderer component for Analytica Ensino platforms\n *\n * Renders HTML content with embedded LaTeX/KaTeX mathematical expressions.\n * Supports multiple LaTeX formats:\n * - Inline math: `$...$` or `<latex>...</latex>`\n * - Block math: `$$...$$`\n * - LaTeX environments: `\\begin{equation}...\\end{equation}`, etc.\n * - Editor format: `<span class=\"math-formula\" data-latex=\"...\">...</span>`\n *\n * @param content - HTML content with LaTeX expressions\n * @param className - Additional CSS classes\n * @param style - Inline styles\n * @param onError - Custom error renderer\n * @returns Rendered content with mathematical expressions\n *\n * @example\n * ```tsx\n * <LatexRenderer content=\"The formula is $E = mc^2$\" />\n *\n * <LatexRenderer\n * content=\"Block equation: $$\\sum_{i=1}^{n} x_i$$\"\n * className=\"my-custom-class\"\n * />\n *\n * <LatexRenderer\n * content=\"<p>Matrix: \\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}</p>\"\n * onError={(latex) => <span>Invalid: {latex}</span>}\n * />\n * ```\n */\nconst LatexRenderer = ({\n content,\n className,\n style,\n onError,\n}: LatexRendererProps) => {\n const renderContentWithMath = (htmlContent: string) => {\n if (!htmlContent) return null;\n\n let processedContent = htmlContent;\n const mathParts: MathPart[] = [];\n\n // Step 1: Handle math-formula spans (from the editor)\n const mathFormulaPattern =\n /<span[^>]*class=\"math-formula\"[^>]*data-latex=\"([^\"]*)\"[^>]*>[\\s\\S]*?<\\/span>/g;\n processedContent = processedContent.replaceAll(\n mathFormulaPattern,\n (match, latex) => {\n const isDisplayMode = match.includes('data-display-mode=\"true\"');\n const id = mathParts.length;\n mathParts.push({\n id,\n type: isDisplayMode ? 'block' : 'inline',\n latex: cleanLatex(decodeHtmlEntities(latex)),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 2: Handle wrapped math expressions (from math modal - legacy)\n const wrappedMathPattern =\n /<span[^>]*class=\"math-expression\"[^>]*data-math=\"([^\"]*)\"[^>]*>.*?<\\/span>/g;\n processedContent = processedContent.replaceAll(\n wrappedMathPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'inline',\n latex: cleanLatex(decodeHtmlEntities(latex)),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 3: Handle raw $$...$$ expressions (manual input or saved content) - BEFORE single $\n const doubleDollarPattern = /(?<!\\\\)\\$\\$([\\s\\S]+?)\\$\\$/g;\n processedContent = processedContent.replaceAll(\n doubleDollarPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'block',\n latex: cleanLatex(latex),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 4: Handle single $...$ expressions for inline math\n const singleDollarPattern = /(?<!\\\\)\\$([\\s\\S]+?)\\$/g;\n processedContent = processedContent.replaceAll(\n singleDollarPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'inline',\n latex: cleanLatex(latex),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 5: Handle <latex>...</latex> tags for inline math\n const latexTagPattern =\n /(?:<latex>|&lt;latex&gt;)([\\s\\S]*?)(?:<\\/latex>|&lt;\\/latex&gt;)/g;\n processedContent = processedContent.replaceAll(\n latexTagPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'inline',\n latex: cleanLatex(latex),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 6: Handle standalone LaTeX environments (align, equation, pmatrix, etc.) for block math\n const latexEnvPattern = /\\\\begin\\{([^}]+)\\}([\\s\\S]*?)\\\\end\\{\\1\\}/g;\n processedContent = processedContent.replaceAll(latexEnvPattern, (match) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'block',\n latex: cleanLatex(match),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n });\n\n // Sanitize the HTML with placeholders\n const sanitizedContent = sanitizeHtml(processedContent);\n\n // Default error renderer\n const defaultErrorRenderer = (latex: string) => (\n <span className=\"text-red-600\">Math Error: {latex}</span>\n );\n\n const errorRenderer = onError || defaultErrorRenderer;\n\n // Parse HTML and replace math placeholders with React components\n const options: HTMLReactParserOptions = {\n replace: createMathReplacer(mathParts, errorRenderer),\n };\n\n return <>{parse(sanitizedContent, options)}</>;\n };\n\n return (\n <div\n className={cn('whitespace-pre-wrap leading-relaxed', className)}\n style={style}\n >\n {renderContentWithMath(content)}\n </div>\n );\n};\n\nexport default LatexRenderer;\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} from './activityFilters';\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":"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","names":["DOMPurify","parse"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/LatexRenderer/LatexRenderer.tsx","../../src/utils/utils.ts"],"sourcesContent":["import { CSSProperties, ReactNode } from 'react';\nimport 'katex/dist/katex.min.css';\nimport { InlineMath, BlockMath } from 'react-katex';\nimport DOMPurify from 'dompurify';\nimport parse, {\n DOMNode,\n Element,\n HTMLReactParserOptions,\n} from 'html-react-parser';\nimport { cn } from '../../utils/utils';\n\n/**\n * Props for the LatexRenderer component\n */\nexport interface LatexRendererProps {\n /** HTML content containing LaTeX expressions to render */\n content: string;\n /** Additional CSS classes to apply */\n className?: string;\n /** Inline styles to apply */\n style?: CSSProperties;\n /** Custom error renderer for invalid LaTeX expressions */\n onError?: (latex: string) => ReactNode;\n}\n\n/**\n * Helper function to sanitize HTML content to prevent XSS attacks\n */\nconst sanitizeHtml = (value: string): string => {\n return DOMPurify.sanitize(value, {\n ADD_ATTR: ['data-latex', 'data-display-mode', 'data-math', 'data-math-id'],\n });\n};\n\n/**\n * Helper function to decode HTML entities\n * Converts entities like &amp;, &lt;, &gt; back to their actual characters\n */\nconst decodeHtmlEntities = (str: string): string => {\n const textarea = document.createElement('textarea');\n textarea.innerHTML = str;\n return textarea.value;\n};\n\n/**\n * Helper function to clean latex from invisible characters\n */\nconst cleanLatex = (str: string): string => {\n // Remove zero-width characters, invisible characters, and other problematic Unicode\n return str.replaceAll(/[\\u200B-\\u200D\\uFEFF]/g, '').trim();\n};\n\n/**\n * Type for math parts used in rendering\n */\ninterface MathPart {\n id: number;\n type: 'inline' | 'block';\n latex: string;\n}\n\n/**\n * Creates a replace function for html-react-parser that replaces math placeholders\n * with KaTeX components\n */\nconst createMathReplacer = (\n mathParts: MathPart[],\n errorRenderer: (latex: string) => ReactNode\n) => {\n return (domNode: DOMNode) => {\n if (\n domNode instanceof Element &&\n domNode.name === 'span' &&\n domNode.attribs['data-math-id']\n ) {\n const mathId = Number.parseInt(domNode.attribs['data-math-id'], 10);\n const mathPart = mathParts[mathId];\n\n if (!mathPart) return domNode;\n\n if (mathPart.type === 'inline') {\n return (\n <InlineMath\n key={`math-${mathId}`}\n math={mathPart.latex}\n renderError={() => errorRenderer(mathPart.latex)}\n />\n );\n } else {\n return (\n <div key={`math-${mathId}`} className=\"my-2.5 text-center\">\n <BlockMath\n math={mathPart.latex}\n renderError={() => errorRenderer(mathPart.latex)}\n />\n </div>\n );\n }\n }\n return domNode;\n };\n};\n\n/**\n * LatexRenderer component for Analytica Ensino platforms\n *\n * Renders HTML content with embedded LaTeX/KaTeX mathematical expressions.\n * Supports multiple LaTeX formats:\n * - Inline math: `$...$` or `<latex>...</latex>`\n * - Block math: `$$...$$`\n * - LaTeX environments: `\\begin{equation}...\\end{equation}`, etc.\n * - Editor format: `<span class=\"math-formula\" data-latex=\"...\">...</span>`\n *\n * @param content - HTML content with LaTeX expressions\n * @param className - Additional CSS classes\n * @param style - Inline styles\n * @param onError - Custom error renderer\n * @returns Rendered content with mathematical expressions\n *\n * @example\n * ```tsx\n * <LatexRenderer content=\"The formula is $E = mc^2$\" />\n *\n * <LatexRenderer\n * content=\"Block equation: $$\\sum_{i=1}^{n} x_i$$\"\n * className=\"my-custom-class\"\n * />\n *\n * <LatexRenderer\n * content=\"<p>Matrix: \\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}</p>\"\n * onError={(latex) => <span>Invalid: {latex}</span>}\n * />\n * ```\n */\nconst LatexRenderer = ({\n content,\n className,\n style,\n onError,\n}: LatexRendererProps) => {\n const renderContentWithMath = (htmlContent: string) => {\n if (!htmlContent) return null;\n\n let processedContent = htmlContent;\n const mathParts: MathPart[] = [];\n\n // Step 1: Handle math-formula spans (from the editor)\n const mathFormulaPattern =\n /<span[^>]*class=\"math-formula\"[^>]*data-latex=\"([^\"]*)\"[^>]*>[\\s\\S]*?<\\/span>/g;\n processedContent = processedContent.replaceAll(\n mathFormulaPattern,\n (match, latex) => {\n const isDisplayMode = match.includes('data-display-mode=\"true\"');\n const id = mathParts.length;\n mathParts.push({\n id,\n type: isDisplayMode ? 'block' : 'inline',\n latex: cleanLatex(decodeHtmlEntities(latex)),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 2: Handle wrapped math expressions (from math modal - legacy)\n const wrappedMathPattern =\n /<span[^>]*class=\"math-expression\"[^>]*data-math=\"([^\"]*)\"[^>]*>.*?<\\/span>/g;\n processedContent = processedContent.replaceAll(\n wrappedMathPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'inline',\n latex: cleanLatex(decodeHtmlEntities(latex)),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 3: Handle raw $$...$$ expressions (manual input or saved content) - BEFORE single $\n const doubleDollarPattern = /(?<!\\\\)\\$\\$([\\s\\S]+?)\\$\\$/g;\n processedContent = processedContent.replaceAll(\n doubleDollarPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'block',\n latex: cleanLatex(latex),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 4: Handle single $...$ expressions for inline math\n const singleDollarPattern = /(?<!\\\\)\\$([\\s\\S]+?)\\$/g;\n processedContent = processedContent.replaceAll(\n singleDollarPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'inline',\n latex: cleanLatex(latex),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 5: Handle <latex>...</latex> tags for inline math\n const latexTagPattern =\n /(?:<latex>|&lt;latex&gt;)([\\s\\S]*?)(?:<\\/latex>|&lt;\\/latex&gt;)/g;\n processedContent = processedContent.replaceAll(\n latexTagPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'inline',\n latex: cleanLatex(latex),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 6: Handle standalone LaTeX environments (align, equation, pmatrix, etc.) for block math\n const latexEnvPattern = /\\\\begin\\{([^}]+)\\}([\\s\\S]*?)\\\\end\\{\\1\\}/g;\n processedContent = processedContent.replaceAll(latexEnvPattern, (match) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'block',\n latex: cleanLatex(match),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n });\n\n // Sanitize the HTML with placeholders\n const sanitizedContent = sanitizeHtml(processedContent);\n\n // Default error renderer\n const defaultErrorRenderer = (latex: string) => (\n <span className=\"text-red-600\">Math Error: {latex}</span>\n );\n\n const errorRenderer = onError || defaultErrorRenderer;\n\n // Parse HTML and replace math placeholders with React components\n const options: HTMLReactParserOptions = {\n replace: createMathReplacer(mathParts, errorRenderer),\n };\n\n return <>{parse(sanitizedContent, options)}</>;\n };\n\n return (\n <div\n className={cn('whitespace-pre-wrap leading-relaxed', className)}\n style={style}\n >\n {renderContentWithMath(content)}\n </div>\n );\n};\n\nexport default LatexRenderer;\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} from './activityFilters';\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":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/LatexRenderer/LatexRenderer.tsx","../../src/utils/utils.ts"],"sourcesContent":["import { CSSProperties, ReactNode } from 'react';\nimport 'katex/dist/katex.min.css';\nimport { InlineMath, BlockMath } from 'react-katex';\nimport DOMPurify from 'dompurify';\nimport parse, {\n DOMNode,\n Element,\n HTMLReactParserOptions,\n} from 'html-react-parser';\nimport { cn } from '../../utils/utils';\n\n/**\n * Props for the LatexRenderer component\n */\nexport interface LatexRendererProps {\n /** HTML content containing LaTeX expressions to render */\n content: string;\n /** Additional CSS classes to apply */\n className?: string;\n /** Inline styles to apply */\n style?: CSSProperties;\n /** Custom error renderer for invalid LaTeX expressions */\n onError?: (latex: string) => ReactNode;\n}\n\n/**\n * Helper function to sanitize HTML content to prevent XSS attacks\n */\nconst sanitizeHtml = (value: string): string => {\n return DOMPurify.sanitize(value, {\n ADD_ATTR: ['data-latex', 'data-display-mode', 'data-math', 'data-math-id'],\n });\n};\n\n/**\n * Helper function to decode HTML entities\n * Converts entities like &amp;, &lt;, &gt; back to their actual characters\n */\nconst decodeHtmlEntities = (str: string): string => {\n const textarea = document.createElement('textarea');\n textarea.innerHTML = str;\n return textarea.value;\n};\n\n/**\n * Helper function to clean latex from invisible characters\n */\nconst cleanLatex = (str: string): string => {\n // Remove zero-width characters, invisible characters, and other problematic Unicode\n return str.replaceAll(/[\\u200B-\\u200D\\uFEFF]/g, '').trim();\n};\n\n/**\n * Type for math parts used in rendering\n */\ninterface MathPart {\n id: number;\n type: 'inline' | 'block';\n latex: string;\n}\n\n/**\n * Creates a replace function for html-react-parser that replaces math placeholders\n * with KaTeX components\n */\nconst createMathReplacer = (\n mathParts: MathPart[],\n errorRenderer: (latex: string) => ReactNode\n) => {\n return (domNode: DOMNode) => {\n if (\n domNode instanceof Element &&\n domNode.name === 'span' &&\n domNode.attribs['data-math-id']\n ) {\n const mathId = Number.parseInt(domNode.attribs['data-math-id'], 10);\n const mathPart = mathParts[mathId];\n\n if (!mathPart) return domNode;\n\n if (mathPart.type === 'inline') {\n return (\n <InlineMath\n key={`math-${mathId}`}\n math={mathPart.latex}\n renderError={() => errorRenderer(mathPart.latex)}\n />\n );\n } else {\n return (\n <div key={`math-${mathId}`} className=\"my-2.5 text-center\">\n <BlockMath\n math={mathPart.latex}\n renderError={() => errorRenderer(mathPart.latex)}\n />\n </div>\n );\n }\n }\n return domNode;\n };\n};\n\n/**\n * LatexRenderer component for Analytica Ensino platforms\n *\n * Renders HTML content with embedded LaTeX/KaTeX mathematical expressions.\n * Supports multiple LaTeX formats:\n * - Inline math: `$...$` or `<latex>...</latex>`\n * - Block math: `$$...$$`\n * - LaTeX environments: `\\begin{equation}...\\end{equation}`, etc.\n * - Editor format: `<span class=\"math-formula\" data-latex=\"...\">...</span>`\n *\n * @param content - HTML content with LaTeX expressions\n * @param className - Additional CSS classes\n * @param style - Inline styles\n * @param onError - Custom error renderer\n * @returns Rendered content with mathematical expressions\n *\n * @example\n * ```tsx\n * <LatexRenderer content=\"The formula is $E = mc^2$\" />\n *\n * <LatexRenderer\n * content=\"Block equation: $$\\sum_{i=1}^{n} x_i$$\"\n * className=\"my-custom-class\"\n * />\n *\n * <LatexRenderer\n * content=\"<p>Matrix: \\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}</p>\"\n * onError={(latex) => <span>Invalid: {latex}</span>}\n * />\n * ```\n */\nconst LatexRenderer = ({\n content,\n className,\n style,\n onError,\n}: LatexRendererProps) => {\n const renderContentWithMath = (htmlContent: string) => {\n if (!htmlContent) return null;\n\n let processedContent = htmlContent;\n const mathParts: MathPart[] = [];\n\n // Step 1: Handle math-formula spans (from the editor)\n const mathFormulaPattern =\n /<span[^>]*class=\"math-formula\"[^>]*data-latex=\"([^\"]*)\"[^>]*>[\\s\\S]*?<\\/span>/g;\n processedContent = processedContent.replaceAll(\n mathFormulaPattern,\n (match, latex) => {\n const isDisplayMode = match.includes('data-display-mode=\"true\"');\n const id = mathParts.length;\n mathParts.push({\n id,\n type: isDisplayMode ? 'block' : 'inline',\n latex: cleanLatex(decodeHtmlEntities(latex)),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 2: Handle wrapped math expressions (from math modal - legacy)\n const wrappedMathPattern =\n /<span[^>]*class=\"math-expression\"[^>]*data-math=\"([^\"]*)\"[^>]*>.*?<\\/span>/g;\n processedContent = processedContent.replaceAll(\n wrappedMathPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'inline',\n latex: cleanLatex(decodeHtmlEntities(latex)),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 3: Handle raw $$...$$ expressions (manual input or saved content) - BEFORE single $\n const doubleDollarPattern = /(?<!\\\\)\\$\\$([\\s\\S]+?)\\$\\$/g;\n processedContent = processedContent.replaceAll(\n doubleDollarPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'block',\n latex: cleanLatex(latex),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 4: Handle single $...$ expressions for inline math\n const singleDollarPattern = /(?<!\\\\)\\$([\\s\\S]+?)\\$/g;\n processedContent = processedContent.replaceAll(\n singleDollarPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'inline',\n latex: cleanLatex(latex),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 5: Handle <latex>...</latex> tags for inline math\n const latexTagPattern =\n /(?:<latex>|&lt;latex&gt;)([\\s\\S]*?)(?:<\\/latex>|&lt;\\/latex&gt;)/g;\n processedContent = processedContent.replaceAll(\n latexTagPattern,\n (match, latex) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'inline',\n latex: cleanLatex(latex),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n }\n );\n\n // Step 6: Handle standalone LaTeX environments (align, equation, pmatrix, etc.) for block math\n const latexEnvPattern = /\\\\begin\\{([^}]+)\\}([\\s\\S]*?)\\\\end\\{\\1\\}/g;\n processedContent = processedContent.replaceAll(latexEnvPattern, (match) => {\n const id = mathParts.length;\n mathParts.push({\n id,\n type: 'block',\n latex: cleanLatex(match),\n });\n return `<span data-math-id=\"${id}\"></span>`;\n });\n\n // Sanitize the HTML with placeholders\n const sanitizedContent = sanitizeHtml(processedContent);\n\n // Default error renderer\n const defaultErrorRenderer = (latex: string) => (\n <span className=\"text-red-600\">Math Error: {latex}</span>\n );\n\n const errorRenderer = onError || defaultErrorRenderer;\n\n // Parse HTML and replace math placeholders with React components\n const options: HTMLReactParserOptions = {\n replace: createMathReplacer(mathParts, errorRenderer),\n };\n\n return <>{parse(sanitizedContent, options)}</>;\n };\n\n return (\n <div\n className={cn('whitespace-pre-wrap leading-relaxed', className)}\n style={style}\n >\n {renderContentWithMath(content)}\n </div>\n );\n};\n\nexport default LatexRenderer;\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} from './activityFilters';\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":"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","names":[]}