analytica-frontend-lib
Version:
Repositório público dos componentes utilizados nas plataformas da Analytica Ensino
1 lines • 17.5 kB
Source Map (JSON)
{"version":3,"sources":["../src/components/Alternative/Alternative.tsx"],"sourcesContent":["import { CheckCircleIcon } from '@phosphor-icons/react/dist/csr/CheckCircle';\nimport { XCircleIcon } from '@phosphor-icons/react/dist/csr/XCircle';\nimport Badge from '../Badge/Badge';\nimport { RadioGroup, RadioGroupItem } from '../Radio/Radio';\nimport { forwardRef, HTMLAttributes, useId, useState } from 'react';\nimport { cn } from '../../utils/utils';\nimport { HtmlMathRenderer } from '../HtmlMathRenderer';\nimport { QuizVariant } from '../Quiz/Quiz.types';\nimport { OptionStatus } from '../../enums/Options';\n\n/**\n * Interface para definir uma alternativa\n */\nexport interface Alternative {\n value: string;\n label: string;\n status?: OptionStatus;\n disabled?: boolean;\n description?: string;\n}\n\n/**\n * Props do componente AlternativesList\n */\nexport interface AlternativesListProps {\n /** Lista de alternativas */\n alternatives: Alternative[];\n /** Nome do grupo de radio */\n name?: string;\n /** Valor selecionado por padrão */\n defaultValue?: string;\n /** Valor controlado */\n value?: string;\n /** Callback quando uma alternativa é selecionada */\n onValueChange?: (value: string) => void;\n /** Se o componente está desabilitado */\n disabled?: boolean;\n /** Layout das alternativas */\n layout?: 'default' | 'compact' | 'detailed';\n /** Classes CSS adicionais */\n className?: string;\n /** Modo de exibição: interativo (com radios funcionais) ou readonly (apenas visual) */\n mode?: 'interactive' | 'readonly';\n /** Valor selecionado pelo usuário (apenas para modo readonly) */\n selectedValue?: string;\n}\n\n/**\n * Componente reutilizável para exibir lista de alternativas com RadioGroup\n *\n * Suporta dois modos:\n * - `interactive`: Permite interação com radios (padrão)\n * - `readonly`: Apenas exibição visual dos estados\n *\n * @example\n * ```tsx\n * // Modo interativo (padrão)\n * <AlternativesList\n * mode=\"interactive\"\n * alternatives={[\n * { value: \"a\", label: \"Alternativa A\", status: \"correct\" },\n * { value: \"b\", label: \"Alternativa B\", status: \"incorrect\" },\n * { value: \"c\", label: \"Alternativa C\" }\n * ]}\n * defaultValue=\"a\"\n * onValueChange={(value) => console.log(value)}\n * />\n *\n * // Modo readonly - mostra seleção do usuário\n * <AlternativesList\n * mode=\"readonly\"\n * selectedValue=\"b\" // O que o usuário selecionou\n * alternatives={[\n * { value: \"a\", label: \"Resposta A\", status: \"correct\" }, // Mostra como correta\n * { value: \"b\", label: \"Resposta B\" }, // Mostra radio selecionado + badge incorreto\n * { value: \"c\", label: \"Resposta C\" }\n * ]}\n * />\n * ```\n */\nconst AlternativesList = ({\n alternatives,\n name,\n defaultValue,\n value,\n onValueChange,\n disabled = false,\n layout = 'default',\n className = '',\n mode = QuizVariant.INTERACTIVE,\n selectedValue,\n}: AlternativesListProps) => {\n // Gerar um ID único para garantir que cada instância tenha seu próprio grupo\n const uniqueId = useId();\n const groupName = name || `alternatives-${uniqueId}`;\n const [actualValue, setActualValue] = useState(value);\n // No modo readonly, não precisamos de interação\n const isReadonly = mode === 'readonly';\n const getStatusStyles = (\n status?: Alternative['status'],\n isReadonly?: boolean\n ) => {\n const hoverClass = isReadonly ? '' : 'hover:bg-background-50';\n\n switch (status) {\n case OptionStatus.CORRECT:\n return 'bg-success-background border-success-300';\n case OptionStatus.INCORRECT:\n return 'bg-error-background border-error-300';\n default:\n return `bg-background border-border-100 ${hoverClass}`;\n }\n };\n\n const getStatusBadge = (status?: Alternative['status']) => {\n switch (status) {\n case OptionStatus.CORRECT:\n return (\n <Badge\n variant=\"solid\"\n action=\"success\"\n iconLeft={<CheckCircleIcon />}\n >\n Resposta correta\n </Badge>\n );\n case OptionStatus.INCORRECT:\n return (\n <Badge variant=\"solid\" action=\"error\" iconLeft={<XCircleIcon />}>\n Resposta incorreta\n </Badge>\n );\n default:\n return null;\n }\n };\n\n const getLayoutClasses = () => {\n switch (layout) {\n case 'compact':\n return 'gap-2';\n case 'detailed':\n return 'gap-4';\n default:\n return 'gap-3.5';\n }\n };\n\n // Componente para renderizar alternativa no modo readonly\n const renderReadonlyAlternative = (alternative: Alternative) => {\n const alternativeId = alternative.value;\n const isUserSelected = selectedValue === alternative.value;\n const isCorrectAnswer = alternative.status === OptionStatus.CORRECT;\n\n // Determinar o status da alternativa para visualização\n let displayStatus: Alternative['status'] = undefined;\n if (isUserSelected && !isCorrectAnswer) {\n // Usuário selecionou alternativa incorreta\n displayStatus = OptionStatus.INCORRECT;\n } else if (isCorrectAnswer) {\n // Alternativa correta (independente se foi selecionada ou não)\n displayStatus = OptionStatus.CORRECT;\n }\n\n const statusStyles = getStatusStyles(displayStatus, true);\n const statusBadge = getStatusBadge(displayStatus);\n\n // Radio visual - apenas mostra selecionado se o usuário escolheu esta alternativa\n const renderRadio = () => {\n const radioClasses = `w-6 h-6 rounded-full border-2 cursor-default transition-all duration-200 flex items-center justify-center ${\n isUserSelected\n ? 'border-primary-950 bg-background'\n : 'border-border-400 bg-background'\n }`;\n\n const dotClasses =\n 'w-3 h-3 rounded-full bg-primary-950 transition-all duration-200';\n\n return (\n <div className={radioClasses}>\n {isUserSelected && <div className={dotClasses} />}\n </div>\n );\n };\n\n if (layout === 'detailed') {\n return (\n <div\n key={alternativeId}\n className={cn(\n 'border-2 rounded-lg p-4 w-full',\n statusStyles,\n alternative.disabled ? 'opacity-50' : ''\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3 flex-1\">\n <div className=\"mt-1\">{renderRadio()}</div>\n <div className=\"flex-1\">\n <HtmlMathRenderer\n content={alternative.label}\n className={cn(\n 'block font-medium',\n selectedValue === alternative.value || statusBadge\n ? 'text-text-950'\n : 'text-text-600'\n )}\n />\n {alternative.description && (\n <p className=\"text-sm text-text-600 mt-1\">\n {alternative.description}\n </p>\n )}\n </div>\n </div>\n {statusBadge && <div className=\"flex-shrink-0\">{statusBadge}</div>}\n </div>\n </div>\n );\n }\n\n return (\n <div\n key={alternativeId}\n className={cn(\n 'flex flex-row justify-between items-start gap-2 p-2 rounded-lg w-full',\n statusStyles,\n alternative.disabled ? 'opacity-50' : ''\n )}\n >\n <div className=\"flex items-center gap-2 flex-1\">\n {renderRadio()}\n <HtmlMathRenderer\n content={alternative.label}\n className={cn(\n 'flex-1',\n selectedValue === alternative.value || statusBadge\n ? 'text-text-950'\n : 'text-text-600'\n )}\n />\n </div>\n {statusBadge && <div className=\"flex-shrink-0\">{statusBadge}</div>}\n </div>\n );\n };\n\n // Se for modo readonly, renderizar sem RadioGroup\n if (isReadonly) {\n return (\n <div\n className={cn('flex flex-col', getLayoutClasses(), 'w-full', className)}\n >\n {alternatives.map((alternative) =>\n renderReadonlyAlternative(alternative)\n )}\n </div>\n );\n }\n\n return (\n <RadioGroup\n name={groupName}\n defaultValue={defaultValue}\n value={value}\n onValueChange={(value) => {\n setActualValue(value);\n onValueChange?.(value);\n }}\n disabled={disabled}\n className={cn('flex flex-col', getLayoutClasses(), className)}\n >\n {alternatives.map((alternative, index) => {\n const alternativeId = alternative.value || `alt-${index}`;\n const statusStyles = getStatusStyles(alternative.status, false);\n const statusBadge = getStatusBadge(alternative.status);\n\n if (layout === 'detailed') {\n return (\n <div\n key={alternativeId}\n className={cn(\n 'border-2 rounded-lg p-4 transition-all',\n statusStyles,\n alternative.disabled\n ? 'opacity-50 cursor-not-allowed'\n : 'cursor-pointer'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3 flex-1\">\n <RadioGroupItem\n value={alternative.value}\n id={alternativeId}\n disabled={alternative.disabled}\n className=\"mt-1\"\n />\n <div className=\"flex-1\">\n <label\n htmlFor={alternativeId}\n className={cn(\n 'block font-medium',\n actualValue === alternative.value\n ? 'text-text-950'\n : 'text-text-600',\n alternative.disabled\n ? 'cursor-not-allowed'\n : 'cursor-pointer'\n )}\n >\n <HtmlMathRenderer content={alternative.label} inline />\n </label>\n {alternative.description && (\n <p className=\"text-sm text-text-600 mt-1\">\n {alternative.description}\n </p>\n )}\n </div>\n </div>\n {statusBadge && (\n <div className=\"flex-shrink-0\">{statusBadge}</div>\n )}\n </div>\n </div>\n );\n }\n\n return (\n <div\n key={alternativeId}\n className={cn(\n 'flex flex-row justify-between gap-2 items-start p-2 rounded-lg transition-all',\n statusStyles,\n alternative.disabled ? 'opacity-50 cursor-not-allowed' : ''\n )}\n >\n <div className=\"flex items-center gap-2 flex-1\">\n <RadioGroupItem\n value={alternative.value}\n id={alternativeId}\n disabled={alternative.disabled}\n />\n <label\n htmlFor={alternativeId}\n className={cn(\n 'flex-1',\n actualValue === alternative.value\n ? 'text-text-950'\n : 'text-text-600',\n alternative.disabled ? 'cursor-not-allowed' : 'cursor-pointer'\n )}\n >\n <HtmlMathRenderer content={alternative.label} inline />\n </label>\n </div>\n {statusBadge && <div className=\"flex-shrink-0\">{statusBadge}</div>}\n </div>\n );\n })}\n </RadioGroup>\n );\n};\n\ninterface HeaderAlternativeProps extends HTMLAttributes<HTMLDivElement> {\n title: string;\n subTitle: string;\n content: string;\n}\n\nconst HeaderAlternative = forwardRef<HTMLDivElement, HeaderAlternativeProps>(\n ({ className, title, subTitle, content, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n 'bg-background p-4 flex flex-col gap-4 rounded-xl',\n className\n )}\n {...props}\n >\n <span className=\"flex flex-col\">\n <p className=\"text-text-950 font-bold text-lg\">{title}</p>\n <p className=\"text-text-700 text-sm \">{subTitle}</p>\n </span>\n\n <HtmlMathRenderer content={content} className=\"text-text-950 text-md\" />\n </div>\n );\n }\n);\n\nexport { AlternativesList, HeaderAlternative };\n"],"mappings":"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","names":["isReadonly","value"]}