UNPKG

analytica-frontend-lib

Version:

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

1 lines 461 kB
{"version":3,"sources":["../../src/components/RecommendedLessonsHistory/RecommendedLessonsHistory.tsx","../../src/utils/utils.ts","../../src/components/Text/Text.tsx","../../src/components/Button/Button.tsx","../../src/components/IconButton/IconButton.tsx","../../src/components/Badge/Badge.tsx","../../src/components/EmptyState/EmptyState.tsx","../../src/components/Menu/Menu.tsx","../../src/components/TableProvider/TableProvider.tsx","../../src/components/Table/Table.tsx","../../src/components/NoSearchResult/NoSearchResult.tsx","../../src/components/Skeleton/Skeleton.tsx","../../src/components/Table/TablePagination.tsx","../../src/components/Filter/useTableFilter.ts","../../src/components/Search/Search.tsx","../../src/components/DropdownMenu/DropdownMenu.tsx","../../src/components/Modal/Modal.tsx","../../src/components/Modal/utils/videoUtils.ts","../../src/components/ThemeToggle/ThemeToggle.tsx","../../src/components/SelectionButton/SelectionButton.tsx","../../src/hooks/useTheme.ts","../../src/store/themeStore.ts","../../src/components/CheckBoxGroup/CheckBoxGroup.tsx","../../src/components/CheckBox/CheckBox.tsx","../../src/components/Divider/Divider.tsx","../../src/components/ProgressBar/ProgressBar.tsx","../../src/components/Accordation/Accordation.tsx","../../src/components/Card/Card.tsx","../../src/components/IconRender/IconRender.tsx","../../src/assets/icons/subjects/ChatPT.tsx","../../src/assets/icons/subjects/ChatEN.tsx","../../src/assets/icons/subjects/ChatES.tsx","../../src/components/Accordation/AccordionGroup.tsx","../../src/assets/icons/subjects/BookOpenText.tsx","../../src/assets/icons/subjects/HeadCircuit.tsx","../../src/assets/icons/subjects/Microscope.tsx","../../src/components/SubjectInfo/SubjectInfo.tsx","../../src/types/common.ts","../../src/types/recommendedLessons.ts","../../src/hooks/useRecommendedLessons.ts","../../src/components/CheckBoxGroup/CheckBoxGroup.helpers.ts","../../src/components/Filter/FilterModal.tsx"],"sourcesContent":["import type { MouseEvent, ReactNode } from 'react';\nimport { useState, useCallback, useMemo, useRef } from 'react';\nimport { Plus, CaretRight, Trash, PencilSimple } from 'phosphor-react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport IconButton from '../IconButton/IconButton';\nimport Badge from '../Badge/Badge';\nimport EmptyState from '../EmptyState/EmptyState';\nimport { Menu, MenuItem, MenuContent } from '../Menu/Menu';\nimport { TableProvider } from '../TableProvider/TableProvider';\nimport ProgressBar from '../ProgressBar/ProgressBar';\nimport { getSubjectInfo } from '../SubjectInfo/SubjectInfo';\nimport { cn } from '../../utils/utils';\nimport { SubjectEnum } from '../../enums/SubjectEnum';\nimport type { ColumnConfig, TableParams } from '../TableProvider/TableProvider';\nimport type { FilterConfig } from '../Filter';\nimport {\n GoalDisplayStatus,\n GoalApiStatus,\n getGoalStatusBadgeAction,\n GOAL_FILTER_STATUS_OPTIONS,\n type GoalTableItem,\n type GoalHistoryFilters,\n type GoalsHistoryApiResponse,\n type GoalUserFilterData,\n type GoalFilterOption,\n} from '../../types/recommendedLessons';\nimport {\n createUseRecommendedLessonsHistory,\n type UseRecommendedLessonsHistoryReturn,\n} from '../../hooks/useRecommendedLessons';\n\n/**\n * Enum for page tabs\n */\nenum PageTab {\n HISTORY = 'history',\n DRAFTS = 'drafts',\n MODELS = 'models',\n}\n\n/**\n * Props for the RecommendedLessonsHistory component\n */\nexport interface RecommendedLessonsHistoryProps {\n /** Function to fetch goals history from API. Must return GoalsHistoryApiResponse. */\n fetchGoalsHistory: (\n filters?: GoalHistoryFilters\n ) => Promise<GoalsHistoryApiResponse>;\n /** Callback when create lesson button is clicked */\n onCreateLesson: () => void;\n /** Callback when a row is clicked */\n onRowClick: (row: GoalTableItem) => void;\n /** Callback when delete action is clicked */\n onDeleteGoal?: (id: string) => void;\n /** Callback when edit action is clicked */\n onEditGoal?: (id: string) => void;\n /** Image for empty state */\n emptyStateImage?: string;\n /** Image for no search results */\n noSearchImage?: string;\n /** Function to map subject name to SubjectEnum */\n mapSubjectNameToEnum?: (subjectName: string) => SubjectEnum | null;\n /** User data for populating filter options */\n userFilterData?: GoalUserFilterData;\n /** Page title */\n title?: string;\n /** Create button text */\n createButtonText?: string;\n /** Search placeholder */\n searchPlaceholder?: string;\n}\n\n/**\n * Check if param is a non-empty array\n */\nconst isNonEmptyArray = (param: unknown): param is string[] =>\n Array.isArray(param) && param.length > 0;\n\n/**\n * Extract filter value from params for single/multiple selection\n */\nconst extractFilterValue = (\n param: unknown\n): { single?: string; multiple?: string[] } => {\n if (!isNonEmptyArray(param)) return {};\n return param.length === 1 ? { single: param[0] } : { multiple: param };\n};\n\n/**\n * Build goal history filters from table params\n */\nconst buildFiltersFromParams = (params: TableParams): GoalHistoryFilters => {\n const filters: GoalHistoryFilters = {\n page: params.page,\n limit: params.limit,\n };\n\n if (params.search) {\n filters.search = params.search;\n }\n\n // Status filter (single selection)\n if (isNonEmptyArray(params.status)) {\n filters.status = params.status[0] as GoalApiStatus;\n }\n\n // School filter\n const schoolFilter = extractFilterValue(params.school);\n if (schoolFilter.single) filters.schoolId = schoolFilter.single;\n if (schoolFilter.multiple) filters.schoolIds = schoolFilter.multiple;\n\n // Class filter\n const classFilter = extractFilterValue(params.class);\n if (classFilter.single) filters.classId = classFilter.single;\n if (classFilter.multiple) filters.classIds = classFilter.multiple;\n\n // Students filter (always multiple)\n if (isNonEmptyArray(params.students)) {\n filters.studentIds = params.students;\n }\n\n // Subject filter (single selection)\n if (isNonEmptyArray(params.subject)) {\n filters.subjectId = params.subject[0];\n }\n\n // Start date filter\n if (params.startDate && typeof params.startDate === 'string') {\n filters.startDate = params.startDate;\n }\n\n return filters;\n};\n\n/**\n * Get school options from user data\n */\nconst getSchoolOptions = (\n data: GoalUserFilterData | undefined\n): GoalFilterOption[] => {\n if (!data?.schools) return [];\n return data.schools.map((school) => ({\n id: school.id,\n name: school.name,\n }));\n};\n\n/**\n * Get subject options from user data\n */\nconst getSubjectOptions = (\n data: GoalUserFilterData | undefined\n): GoalFilterOption[] => {\n if (!data?.subjects) return [];\n return data.subjects.map((subject) => ({\n id: subject.id,\n name: subject.name,\n }));\n};\n\n/**\n * Get school year options from user data\n */\nconst getSchoolYearOptions = (\n data: GoalUserFilterData | undefined\n): GoalFilterOption[] => {\n if (!data?.schoolYears) return [];\n return data.schoolYears.map((year) => ({\n id: year.id,\n name: year.name,\n }));\n};\n\n/**\n * Get class options from user data\n */\nconst getClassOptions = (\n data: GoalUserFilterData | undefined\n): GoalFilterOption[] => {\n if (!data?.classes) return [];\n return data.classes.map((cls) => ({\n id: cls.id,\n name: cls.name,\n }));\n};\n\n/**\n * Create filter configuration for goals\n */\nconst createGoalFiltersConfig = (\n userData: GoalUserFilterData | undefined\n): FilterConfig[] => [\n {\n key: 'academic',\n label: 'DADOS ACADÊMICOS',\n categories: [\n {\n key: 'school',\n label: 'Escola',\n selectedIds: [],\n itens: getSchoolOptions(userData),\n },\n {\n key: 'schoolYear',\n label: 'Série',\n selectedIds: [],\n itens: getSchoolYearOptions(userData),\n },\n {\n key: 'class',\n label: 'Turma',\n selectedIds: [],\n itens: getClassOptions(userData),\n },\n {\n key: 'students',\n label: 'Alunos',\n selectedIds: [],\n itens: [],\n },\n ],\n },\n {\n key: 'content',\n label: 'CONTEÚDO',\n categories: [\n {\n key: 'knowledgeArea',\n label: 'Área de conhecimento',\n selectedIds: [],\n itens: [],\n },\n {\n key: 'subject',\n label: 'Matéria',\n selectedIds: [],\n itens: getSubjectOptions(userData),\n },\n {\n key: 'theme',\n label: 'Tema',\n selectedIds: [],\n itens: [],\n },\n {\n key: 'subtheme',\n label: 'Subtema',\n selectedIds: [],\n itens: [],\n },\n {\n key: 'topic',\n label: 'Assunto',\n selectedIds: [],\n itens: [],\n },\n ],\n },\n {\n key: 'lesson',\n label: 'AULA',\n categories: [\n {\n key: 'status',\n label: 'Status',\n selectedIds: [],\n itens: GOAL_FILTER_STATUS_OPTIONS,\n },\n ],\n },\n];\n\n/**\n * Create table columns configuration\n */\nconst createTableColumns = (\n mapSubjectNameToEnum: ((name: string) => SubjectEnum | null) | undefined,\n onDeleteGoal: ((id: string) => void) | undefined,\n onEditGoal: ((id: string) => void) | undefined\n): ColumnConfig<GoalTableItem>[] => [\n {\n key: 'startDate',\n label: 'Início',\n sortable: true,\n },\n {\n key: 'deadline',\n label: 'Prazo',\n sortable: true,\n },\n {\n key: 'title',\n label: 'Título',\n sortable: true,\n className: 'max-w-[200px] truncate',\n render: (value: unknown) => {\n const title = typeof value === 'string' ? value : '';\n return (\n <Text size=\"sm\" title={title}>\n {title}\n </Text>\n );\n },\n },\n {\n key: 'school',\n label: 'Escola',\n sortable: true,\n className: 'max-w-[150px] truncate',\n render: (value: unknown) => {\n const school = typeof value === 'string' ? value : '';\n return (\n <Text size=\"sm\" title={school}>\n {school}\n </Text>\n );\n },\n },\n {\n key: 'year',\n label: 'Ano',\n sortable: true,\n },\n {\n key: 'subject',\n label: 'Matéria',\n sortable: true,\n className: 'max-w-[140px]',\n render: (value: unknown) => {\n const subjectName = typeof value === 'string' ? value : '';\n const subjectEnum = mapSubjectNameToEnum?.(subjectName);\n\n if (!subjectEnum) {\n return (\n <Text size=\"sm\" className=\"truncate\" title={subjectName}>\n {subjectName}\n </Text>\n );\n }\n\n const subjectInfo = getSubjectInfo(subjectEnum);\n\n return (\n <div className=\"flex items-center gap-2\" title={subjectName}>\n <span\n className={cn(\n 'w-[21px] h-[21px] flex items-center justify-center rounded-sm text-text-950 shrink-0',\n subjectInfo.colorClass\n )}\n >\n {subjectInfo.icon}\n </span>\n <Text size=\"sm\" className=\"truncate\">\n {subjectName}\n </Text>\n </div>\n );\n },\n },\n {\n key: 'class',\n label: 'Turma',\n sortable: true,\n },\n {\n key: 'status',\n label: 'Status',\n sortable: true,\n render: (value: unknown) => {\n const status = typeof value === 'string' ? value : '';\n if (!status) {\n return (\n <Text size=\"sm\" color=\"text-text-500\">\n -\n </Text>\n );\n }\n return (\n <Badge\n variant=\"solid\"\n action={getGoalStatusBadgeAction(status as GoalDisplayStatus)}\n size=\"small\"\n >\n {status}\n </Badge>\n );\n },\n },\n {\n key: 'completionPercentage',\n label: 'Conclusão',\n sortable: true,\n render: (value: unknown) => (\n <ProgressBar\n value={Number(value)}\n variant=\"blue\"\n size=\"medium\"\n layout=\"compact\"\n showPercentage={true}\n compactWidth=\"w-[100px]\"\n />\n ),\n },\n {\n key: 'actions',\n label: '',\n sortable: false,\n className: 'w-20',\n render: (_value: unknown, row: GoalTableItem) => {\n const handleDelete = (e: MouseEvent) => {\n e.stopPropagation();\n onDeleteGoal?.(row.id);\n };\n\n const handleEdit = (e: MouseEvent) => {\n e.stopPropagation();\n onEditGoal?.(row.id);\n };\n\n return (\n <div className=\"flex justify-center gap-2\">\n <IconButton\n icon={<Trash size={20} />}\n size=\"sm\"\n title=\"Excluir\"\n onClick={handleDelete}\n />\n <IconButton\n icon={<PencilSimple size={20} />}\n size=\"sm\"\n title=\"Editar\"\n onClick={handleEdit}\n />\n </div>\n );\n },\n },\n {\n key: 'navigation',\n label: '',\n sortable: false,\n className: 'w-12',\n render: () => (\n <div className=\"flex justify-center\">\n <CaretRight size={20} className=\"text-text-600\" />\n </div>\n ),\n },\n];\n\n/**\n * RecommendedLessonsHistory component\n * Displays goals/recommended lessons history with tabs, filters, and table\n */\nexport const RecommendedLessonsHistory = ({\n fetchGoalsHistory,\n onCreateLesson,\n onRowClick,\n onDeleteGoal,\n onEditGoal,\n emptyStateImage,\n noSearchImage,\n mapSubjectNameToEnum,\n userFilterData,\n title = 'Histórico de aulas recomendadas',\n createButtonText = 'Criar aula',\n searchPlaceholder = 'Buscar aula',\n}: RecommendedLessonsHistoryProps) => {\n const [activeTab, setActiveTab] = useState<PageTab>(PageTab.HISTORY);\n\n // Use ref to keep stable reference of fetchGoalsHistory\n // This prevents hook recreation if parent doesn't memoize the function\n const fetchGoalsHistoryRef = useRef(fetchGoalsHistory);\n fetchGoalsHistoryRef.current = fetchGoalsHistory;\n\n // Create hook instance with stable fetch function wrapper\n const useGoalsHistory = useMemo(\n () =>\n createUseRecommendedLessonsHistory((filters) =>\n fetchGoalsHistoryRef.current(filters)\n ),\n []\n );\n\n // Use the hook\n const {\n goals,\n loading,\n error,\n pagination,\n fetchGoals,\n }: UseRecommendedLessonsHistoryReturn = useGoalsHistory();\n\n // Create filter and column configurations\n const initialFilterConfigs = useMemo(\n () => createGoalFiltersConfig(userFilterData),\n [userFilterData]\n );\n\n const tableColumns = useMemo(\n () => createTableColumns(mapSubjectNameToEnum, onDeleteGoal, onEditGoal),\n [mapSubjectNameToEnum, onDeleteGoal, onEditGoal]\n );\n\n /**\n * Handle table params change\n * Note: TableProvider calls this on mount with initial params,\n * so no separate useEffect for initial fetch is needed\n */\n const handleParamsChange = useCallback(\n (params: TableParams) => {\n const filters = buildFiltersFromParams(params);\n fetchGoals(filters);\n },\n [fetchGoals]\n );\n\n return (\n <div\n data-testid=\"recommended-lessons-history\"\n className=\"flex flex-col w-full h-auto relative justify-center items-center mb-5 overflow-hidden\"\n >\n {/* Background decoration */}\n <span className=\"absolute top-0 left-0 h-[150px] w-full z-0\" />\n\n {/* Main container */}\n <div className=\"flex flex-col w-full h-full max-w-[1350px] mx-auto z-10 lg:px-0 px-4 pt-4 sm:pt-0\">\n {/* Header Section */}\n <div className=\"flex flex-col sm:flex-row w-full mb-6 items-start sm:items-center sm:justify-between gap-0 sm:gap-4\">\n {/* Page Title */}\n <Text\n as=\"h1\"\n weight=\"bold\"\n className=\"leading-[28px] tracking-[0.2px] text-xl lg:text-2xl\"\n >\n {title}\n </Text>\n\n {/* Tabs Menu */}\n <div className=\"flex-shrink-0 lg:w-auto self-center sm:self-auto\">\n <Menu\n defaultValue={PageTab.HISTORY}\n value={activeTab}\n onValueChange={(value: string) => setActiveTab(value as PageTab)}\n variant=\"menu2\"\n className=\"bg-transparent shadow-none px-0\"\n >\n <MenuContent\n variant=\"menu2\"\n className=\"w-full lg:w-auto max-w-full min-w-0\"\n >\n <MenuItem\n variant=\"menu2\"\n value={PageTab.HISTORY}\n data-testid=\"menu-item-history\"\n className=\"whitespace-nowrap flex-1 lg:flex-none\"\n >\n Histórico\n </MenuItem>\n <MenuItem\n variant=\"menu2\"\n value={PageTab.DRAFTS}\n data-testid=\"menu-item-drafts\"\n className=\"whitespace-nowrap flex-1 lg:flex-none\"\n >\n Rascunhos\n </MenuItem>\n <MenuItem\n variant=\"menu2\"\n value={PageTab.MODELS}\n data-testid=\"menu-item-models\"\n className=\"whitespace-nowrap flex-1 lg:flex-none\"\n >\n Modelos\n </MenuItem>\n </MenuContent>\n </Menu>\n </div>\n </div>\n\n {/* Content Area */}\n <div className=\"flex flex-col items-center w-full min-h-0 flex-1\">\n {activeTab === PageTab.HISTORY && (\n <>\n {/* Error State */}\n {error ? (\n <div className=\"flex items-center justify-center bg-background rounded-xl w-full min-h-[705px]\">\n <Text size=\"lg\" color=\"text-error-500\">\n {error}\n </Text>\n </div>\n ) : (\n <div className=\"w-full\">\n <TableProvider\n data={goals}\n headers={tableColumns}\n loading={loading}\n variant=\"borderless\"\n enableSearch\n enableFilters\n enableTableSort\n enablePagination\n enableRowClick\n initialFilters={initialFilterConfigs}\n paginationConfig={{\n itemLabel: 'aulas',\n itemsPerPageOptions: [10, 20, 50, 100],\n defaultItemsPerPage: 10,\n totalItems: pagination.total,\n totalPages: pagination.totalPages,\n }}\n searchPlaceholder={searchPlaceholder}\n noSearchResultState={{\n image: noSearchImage,\n }}\n emptyState={{\n component: (\n <EmptyState\n image={emptyStateImage}\n title=\"Crie uma nova aula\"\n description=\"Selecione um conjunto de aulas organizadas por tema e ajude seus alunos a estudarem de forma estruturada e eficiente!\"\n buttonText={createButtonText}\n buttonIcon={<Plus size={18} />}\n buttonVariant=\"outline\"\n buttonAction=\"primary\"\n onButtonClick={onCreateLesson}\n />\n ),\n }}\n onParamsChange={handleParamsChange}\n onRowClick={onRowClick}\n >\n {(renderProps: unknown) => {\n const {\n controls,\n table,\n pagination: paginationComponent,\n } = renderProps as {\n controls: ReactNode;\n table: ReactNode;\n pagination: ReactNode;\n };\n return (\n <div className=\"space-y-4\">\n {/* Header row: Button on left, Controls on right */}\n <div className=\"flex items-center justify-between gap-4\">\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n onClick={onCreateLesson}\n iconLeft={<Plus size={18} weight=\"bold\" />}\n >\n {createButtonText}\n </Button>\n {controls}\n </div>\n {/* Table and pagination */}\n <div className=\"bg-background rounded-xl p-6 space-y-4\">\n {table}\n {paginationComponent}\n </div>\n </div>\n );\n }}\n </TableProvider>\n </div>\n )}\n </>\n )}\n\n {activeTab === PageTab.DRAFTS && (\n <div className=\"flex items-center justify-center bg-background rounded-xl w-full min-h-[705px]\">\n <Text size=\"lg\" color=\"text-text-600\">\n Rascunhos em desenvolvimento\n </Text>\n </div>\n )}\n\n {activeTab === PageTab.MODELS && (\n <div className=\"flex items-center justify-center bg-background rounded-xl w-full min-h-[705px]\">\n <Text size=\"lg\" color=\"text-text-600\">\n Modelos em desenvolvimento\n </Text>\n </div>\n )}\n </div>\n </div>\n </div>\n );\n};\n\nexport default RecommendedLessonsHistory;\n","import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\nexport { syncDropdownState } from './dropdown';\nexport {\n getSelectedIdsFromCategories,\n toggleArrayItem,\n toggleSingleValue,\n areFiltersEqual,\n} from './activityFilters';\nexport {\n mapQuestionTypeToEnum,\n mapQuestionTypeToEnumRequired,\n} from './questionTypeUtils';\nexport {\n getStatusBadgeConfig,\n formatTimeSpent,\n formatQuestionNumbers,\n formatDateToBrazilian,\n} from './activityDetailsUtils';\n\n/**\n * Retorna a cor hexadecimal com opacidade 0.3 (4d) se não estiver em dark mode.\n * Se estiver em dark mode, retorna a cor original.\n *\n * @param hexColor - Cor hexadecimal (ex: \"#0066b8\" ou \"0066b8\")\n * @param isDark - booleano indicando se está em dark mode\n * @returns string - cor hexadecimal com opacidade se necessário\n */\nexport function getSubjectColorWithOpacity(\n hexColor: string | undefined,\n isDark: boolean\n): string | undefined {\n if (!hexColor) return undefined;\n // Remove o '#' se existir\n let color = hexColor.replace(/^#/, '').toLowerCase();\n\n if (isDark) {\n // Se está em dark mode, sempre remove opacidade se existir\n if (color.length === 8) {\n color = color.slice(0, 6);\n }\n return `#${color}`;\n } else {\n // Se não está em dark mode (light mode)\n let resultColor: string;\n if (color.length === 6) {\n // Adiciona opacidade 0.3 (4D) para cores de 6 dígitos\n resultColor = `#${color}4d`;\n } else if (color.length === 8) {\n // Já tem opacidade, retorna como está\n resultColor = `#${color}`;\n } else {\n // Para outros tamanhos (3, 4, 5 dígitos), retorna como está\n resultColor = `#${color}`;\n }\n return resultColor;\n }\n}\n","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 { 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 { HTMLAttributes, ReactNode } from 'react';\nimport { Bell } from 'phosphor-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 error: 'bg-error-background text-error-700 focus-visible:outline-none',\n warning: 'bg-warning text-warning-800 focus-visible:outline-none',\n success: 'bg-success text-success-800 focus-visible:outline-none',\n info: 'bg-info text-info-800 focus-visible:outline-none',\n muted: 'bg-background-muted text-background-800 focus-visible:outline-none',\n },\n outlined: {\n error:\n 'bg-error text-error-700 border border-error-300 focus-visible:outline-none',\n warning:\n 'bg-warning text-warning-800 border border-warning-300 focus-visible:outline-none',\n success:\n 'bg-success text-success-800 border border-success-300 focus-visible:outline-none',\n info: 'bg-info text-info-800 border border-info-300 focus-visible:outline-none',\n muted:\n 'bg-background-muted text-background-800 border border-border-300 focus-visible:outline-none',\n },\n exams: {\n exam1: 'bg-exam-1 text-info-700 focus-visible:outline-none',\n exam2: 'bg-exam-2 text-typography-1 focus-visible:outline-none',\n exam3: 'bg-exam-3 text-typography-2 focus-visible:outline-none',\n exam4: 'bg-exam-4 text-success-700 focus-visible:outline-none',\n },\n examsOutlined: {\n exam1:\n 'bg-exam-1 text-info-700 border border-info-700 focus-visible:outline-none',\n exam2:\n 'bg-exam-2 text-typography-1 border border-typography-1 focus-visible:outline-none',\n exam3:\n 'bg-exam-3 text-typography-2 border border-typography-2 focus-visible:outline-none',\n exam4:\n 'bg-exam-4 text-success-700 border border-success-700 focus-visible:outline-none',\n },\n resultStatus: {\n negative: 'bg-error text-error-800 focus-visible:outline-none',\n positive: 'bg-success text-success-800 focus-visible:outline-none',\n },\n notification: 'text-primary',\n} as const;\n\n/**\n * Lookup table for size classes\n */\nconst SIZE_CLASSES = {\n small: 'text-2xs px-2 py-1',\n medium: 'text-xs px-2 py-1',\n large: 'text-sm px-2 py-1',\n} as const;\n\nconst SIZE_CLASSES_ICON = {\n small: 'size-3',\n medium: 'size-3.5',\n large: 'size-4',\n} as const;\n\n/**\n * Badge component props interface\n */\ntype BadgeProps = {\n /** Content to be displayed inside the badge */\n children?: ReactNode;\n /** Ícone à direita do texto */\n iconRight?: ReactNode;\n /** Ícone à esquerda do texto */\n iconLeft?: ReactNode;\n /** Size of the badge */\n size?: 'small' | 'medium' | 'large';\n /** Visual variant of the badge */\n variant?:\n | 'solid'\n | 'outlined'\n | 'exams'\n | 'examsOutlined'\n | 'resultStatus'\n | 'notification';\n /** Action type of the badge */\n action?:\n | 'error'\n | 'warning'\n | 'success'\n | 'info'\n | 'muted'\n | 'exam1'\n | 'exam2'\n | 'exam3'\n | 'exam4'\n | 'positive'\n | 'negative';\n /** Additional CSS classes to apply */\n className?: string;\n notificationActive?: boolean;\n} & HTMLAttributes<HTMLDivElement>;\n\n/**\n * Badge 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 badge\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 div HTML attributes\n * @returns A styled badge element\n *\n * @example\n * ```tsx\n * <Badge variant=\"solid\" action=\"info\" size=\"medium\">\n * Information\n * </Badge>\n * ```\n */\nconst Badge = ({\n children,\n iconLeft,\n iconRight,\n size = 'medium',\n variant = 'solid',\n action = 'error',\n className = '',\n notificationActive = false,\n ...props\n}: BadgeProps) => {\n // Get classes from lookup tables\n const sizeClasses = SIZE_CLASSES[size];\n const sizeClassesIcon = SIZE_CLASSES_ICON[size];\n const variantActionMap = VARIANT_ACTION_CLASSES[variant] || {};\n const variantClasses =\n typeof variantActionMap === 'string'\n ? variantActionMap\n : ((variantActionMap as Record<string, string>)[action] ??\n (variantActionMap as Record<string, string>).muted ??\n '');\n\n const baseClasses =\n 'inline-flex items-center justify-center rounded-xs font-normal gap-1 relative';\n\n const baseClassesIcon = 'flex items-center';\n if (variant === 'notification') {\n return (\n <div\n className={cn(baseClasses, variantClasses, sizeClasses, className)}\n {...props}\n >\n <Bell size={24} className=\"text-current\" aria-hidden=\"true\" />\n\n {notificationActive && (\n <span\n data-testid=\"notification-dot\"\n className=\"absolute top-[5px] right-[10px] block h-2 w-2 rounded-full bg-indicator-error ring-2 ring-white\"\n />\n )}\n </div>\n );\n }\n return (\n <div\n className={cn(baseClasses, variantClasses, sizeClasses, className)}\n {...props}\n >\n {iconLeft && (\n <span className={cn(baseClassesIcon, sizeClassesIcon)}>{iconLeft}</span>\n )}\n {children}\n {iconRight && (\n <span className={cn(baseClassesIcon, sizeClassesIcon)}>\n {iconRight}\n </span>\n )}\n </div>\n );\n};\n\nexport default Badge;\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","import { create, StoreApi, useStore } from 'zustand';\nimport {\n ReactNode,\n useEffect,\n useRef,\n forwardRef,\n HTMLAttributes,\n KeyboardEvent,\n MouseEvent,\n ReactElement,\n isValidElement,\n Children,\n cloneElement,\n useState,\n} from 'react';\nimport { CaretLeft, CaretRight } from 'phosphor-react';\nimport { cn } from '../../utils/utils';\n\ntype MenuVariant = 'menu' | 'menu2' | 'menu-overflow' | 'breadcrumb';\n\ninterface MenuStore {\n value: string;\n setValue: (value: string) => void;\n onValueChange?: (value: string) => void;\n}\n\ntype MenuStoreApi = StoreApi<MenuStore>;\n\nconst createMenuStore = (\n onValueChange?: (value: string) => void\n): MenuStoreApi =>\n create<MenuStore>((set) => ({\n value: '',\n setValue: (value) => {\n set({ value });\n onValueChange?.(value);\n },\n onValueChange,\n }));\n\nexport const useMenuStore = (externalStore?: MenuStoreApi) => {\n if (!externalStore) throw new Error('MenuItem must be inside Menu');\n return externalStore;\n};\n\ninterface MenuProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n defaultValue: string;\n value?: string;\n variant?: MenuVariant;\n onValueChange?: (value: string) => void;\n}\n\nconst VARIANT_CLASSES = {\n menu: 'bg-background shadow-soft-shadow-1 px-6',\n menu2: '',\n 'menu-overflow': '',\n breadcrumb: 'bg-transparent shadow-none !px-0',\n};\n\nconst Menu = forwardRef<HTMLDivElement, MenuProps>(\n (\n {\n className,\n children,\n defaultValue,\n value: propValue,\n variant = 'menu',\n onValueChange,\n ...props\n },\n ref\n ) => {\n const storeRef = useRef<MenuStoreApi>(null);\n storeRef.current ??= createMenuStore(onValueChange);\n const store = storeRef.current;\n const { setValue } = useStore(store, (s) => s);\n\n useEffect(() => {\n setValue(propValue ?? defaultValue);\n }, [defaultValue, propValue, setValue]);\n\n const baseClasses =\n variant === 'menu-overflow'\n ? 'w-fit py-2 flex flex-row items-center justify-center'\n : 'w-full py-2 flex flex-row items-center justify-center';\n const variantClasses = VARIANT_CLASSES[variant];\n\n return (\n <div\n ref={ref}\n className={`\n ${baseClasses}\n ${variantClasses}\n ${className ?? ''}\n `}\n {...props}\n >\n {injectStore(children, store)}\n </div>\n );\n }\n);\nMenu.displayName = 'Menu';\n\ninterface MenuContentProps extends HTMLAttributes<HTMLUListElement> {\n children: ReactNode;\n variant?: MenuVariant;\n}\n\nconst MenuContent = forwardRef<HTMLUListElement, MenuContentProps>(\n ({ className, children, variant = 'menu', ...props }, ref) => {\n const baseClasses = 'w-full flex flex-row items-center gap-2';\n\n const variantClasses =\n variant === 'menu2' || variant === 'menu-overflow'\n ? 'overflow-x-auto scroll-smooth'\n : '';\n\n return (\n <ul\n ref={ref}\n className={`\n ${baseClasses}\n ${variantClasses}\n ${variant == 'breadcrumb' ? 'flex-wrap' : ''}\n ${className ?? ''}\n `}\n style={\n variant === 'menu2' || variant === 'menu-overflow'\n ? { scrollbarWidth: 'none', msOverflowStyle: 'none' }\n : undefined\n }\n {...props}\n >\n {children}\n </ul>\n );\n }\n);\nMe