analytica-frontend-lib 1.2.8 → 1.2.10
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.
- package/dist/Quiz/index.js +38 -5
- package/dist/Quiz/index.js.map +1 -1
- package/dist/Quiz/index.mjs +38 -5
- package/dist/Quiz/index.mjs.map +1 -1
- package/dist/Quiz/useQuizStore/index.d.mts +3 -0
- package/dist/Quiz/useQuizStore/index.d.ts +3 -0
- package/dist/Quiz/useQuizStore/index.js +10 -2
- package/dist/Quiz/useQuizStore/index.js.map +1 -1
- package/dist/Quiz/useQuizStore/index.mjs +10 -2
- package/dist/Quiz/useQuizStore/index.mjs.map +1 -1
- package/dist/TextArea/index.d.mts +4 -0
- package/dist/TextArea/index.d.ts +4 -0
- package/dist/TextArea/index.js +22 -1
- package/dist/TextArea/index.js.map +1 -1
- package/dist/TextArea/index.mjs +22 -1
- package/dist/TextArea/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +109 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +108 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/TextArea/index.js
CHANGED
|
@@ -150,11 +150,16 @@ var TextArea = (0, import_react.forwardRef)(
|
|
|
150
150
|
onChange,
|
|
151
151
|
placeholder,
|
|
152
152
|
required,
|
|
153
|
+
showCharacterCount = false,
|
|
154
|
+
maxLength,
|
|
155
|
+
value,
|
|
153
156
|
...props
|
|
154
157
|
}, ref) => {
|
|
155
158
|
const generatedId = (0, import_react.useId)();
|
|
156
159
|
const inputId = id ?? `textarea-${generatedId}`;
|
|
157
160
|
const [isFocused, setIsFocused] = (0, import_react.useState)(false);
|
|
161
|
+
const currentLength = typeof value === "string" ? value.length : 0;
|
|
162
|
+
const isNearLimit = maxLength && currentLength >= maxLength * 0.8;
|
|
158
163
|
const handleChange = (event) => {
|
|
159
164
|
onChange?.(event);
|
|
160
165
|
};
|
|
@@ -209,6 +214,8 @@ var TextArea = (0, import_react.forwardRef)(
|
|
|
209
214
|
className: textareaClasses,
|
|
210
215
|
placeholder,
|
|
211
216
|
required,
|
|
217
|
+
maxLength,
|
|
218
|
+
value,
|
|
212
219
|
...props
|
|
213
220
|
}
|
|
214
221
|
),
|
|
@@ -217,7 +224,21 @@ var TextArea = (0, import_react.forwardRef)(
|
|
|
217
224
|
" ",
|
|
218
225
|
errorMessage
|
|
219
226
|
] }),
|
|
220
|
-
|
|
227
|
+
!errorMessage && showCharacterCount && maxLength && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
228
|
+
Text_default,
|
|
229
|
+
{
|
|
230
|
+
size: "sm",
|
|
231
|
+
weight: "normal",
|
|
232
|
+
className: `mt-1.5 ${isNearLimit ? "text-indicator-warning" : "text-text-500"}`,
|
|
233
|
+
children: [
|
|
234
|
+
currentLength,
|
|
235
|
+
"/",
|
|
236
|
+
maxLength,
|
|
237
|
+
" caracteres"
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
),
|
|
241
|
+
!errorMessage && helperMessage && !(showCharacterCount && maxLength) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text_default, { size: "sm", weight: "normal", className: "mt-1.5 text-text-500", children: helperMessage })
|
|
221
242
|
] });
|
|
222
243
|
}
|
|
223
244
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/TextArea/TextArea.tsx","../../src/utils/utils.ts","../../src/components/Text/Text.tsx"],"sourcesContent":["import {\n TextareaHTMLAttributes,\n ReactNode,\n forwardRef,\n useState,\n useId,\n ChangeEvent,\n FocusEvent,\n} from 'react';\nimport { WarningCircle } from 'phosphor-react';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\n\n/**\n * TextArea size variants\n */\ntype TextAreaSize = 'small' | 'medium' | 'large' | 'extraLarge';\n\n/**\n * TextArea visual state\n */\ntype TextAreaState = 'default' | 'hovered' | 'focused' | 'invalid' | 'disabled';\n\n/**\n * Size configurations with exact pixel specifications\n */\nconst SIZE_CLASSES = {\n small: {\n textarea: 'h-24 text-sm', // 96px height, 14px font\n textSize: 'sm' as const,\n },\n medium: {\n textarea: 'h-24 text-base', // 96px height, 16px font\n textSize: 'md' as const,\n },\n large: {\n textarea: 'h-24 text-lg', // 96px height, 18px font\n textSize: 'lg' as const,\n },\n extraLarge: {\n textarea: 'h-24 text-xl', // 96px height, 20px font\n textSize: 'xl' as const,\n },\n} as const;\n\n/**\n * Base textarea styling classes using design system colors\n */\nconst BASE_TEXTAREA_CLASSES =\n 'w-full box-border p-3 bg-background border border-solid rounded-[4px] resize-none focus:outline-none font-roboto font-normal leading-[150%] placeholder:text-text-600 transition-all duration-200';\n\n/**\n * State-based styling classes using design system colors from styles.css\n */\nconst STATE_CLASSES = {\n default: {\n base: 'border-border-300 bg-background text-text-600',\n hover: 'hover:border-border-400',\n focus: 'focus:border-border-500',\n },\n hovered: {\n base: 'border-border-400 bg-background text-text-600',\n hover: '',\n focus: 'focus:border-border-500',\n },\n focused: {\n base: 'border-2 border-primary-950 bg-background text-text-900',\n hover: '',\n focus: '',\n },\n invalid: {\n base: 'border-2 border-red-700 bg-white text-gray-800',\n hover: 'hover:border-red-700',\n focus: 'focus:border-red-700',\n },\n disabled: {\n base: 'border-border-300 bg-background text-text-600 cursor-not-allowed opacity-40',\n hover: '',\n focus: '',\n },\n} as const;\n\n/**\n * TextArea component props interface\n */\nexport type TextAreaProps = {\n /** Label text to display above the textarea */\n label?: ReactNode;\n /** Size variant of the textarea */\n size?: TextAreaSize;\n /** Visual state of the textarea */\n state?: TextAreaState;\n /** Error message to display */\n errorMessage?: string;\n /** Helper text to display */\n helperMessage?: string;\n /** Additional CSS classes */\n className?: string;\n /** Label CSS classes */\n labelClassName?: string;\n} & Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'>;\n\n/**\n * TextArea component for Analytica Ensino platforms\n *\n * A textarea component with essential states, sizes and themes.\n * Uses exact design specifications with 288px width, 96px height, and specific\n * color values. Includes Text component integration for consistent typography.\n *\n * @example\n * ```tsx\n * // Basic textarea\n * <TextArea label=\"Description\" placeholder=\"Enter description...\" />\n *\n * // Small size\n * <TextArea size=\"small\" label=\"Comment\" />\n *\n * // Invalid state\n * <TextArea state=\"invalid\" label=\"Required field\" errorMessage=\"This field is required\" />\n *\n * // Disabled state\n * <TextArea disabled label=\"Read-only field\" />\n * ```\n */\nconst TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(\n (\n {\n label,\n size = 'medium',\n state = 'default',\n errorMessage,\n helperMessage,\n className = '',\n labelClassName = '',\n disabled,\n id,\n onChange,\n placeholder,\n required,\n ...props\n },\n ref\n ) => {\n // Generate unique ID if not provided\n const generatedId = useId();\n const inputId = id ?? `textarea-${generatedId}`;\n\n // Internal state for focus tracking\n const [isFocused, setIsFocused] = useState(false);\n\n // Handle change events\n const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {\n onChange?.(event);\n };\n\n // Handle focus events\n const handleFocus = (event: FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(true);\n props.onFocus?.(event);\n };\n\n // Handle blur events\n const handleBlur = (event: FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(false);\n props.onBlur?.(event);\n };\n\n // Determine current state based on props and focus\n let currentState = disabled ? 'disabled' : state;\n\n // Override state based on focus\n if (\n isFocused &&\n currentState !== 'invalid' &&\n currentState !== 'disabled'\n ) {\n currentState = 'focused';\n }\n\n // Get size classes\n const sizeClasses = SIZE_CLASSES[size];\n\n // Get styling classes\n const stateClasses = STATE_CLASSES[currentState];\n\n // Get final textarea classes\n const textareaClasses = cn(\n BASE_TEXTAREA_CLASSES,\n sizeClasses.textarea,\n stateClasses.base,\n stateClasses.hover,\n stateClasses.focus,\n className\n );\n\n return (\n <div className={`flex flex-col`}>\n {/* Label */}\n {label && (\n <Text\n as=\"label\"\n htmlFor={inputId}\n size={sizeClasses.textSize}\n weight=\"medium\"\n color=\"text-text-950\"\n className={cn('mb-1.5', labelClassName)}\n >\n {label}{' '}\n {required && <span className=\"text-indicator-error\">*</span>}\n </Text>\n )}\n\n {/* Textarea */}\n <textarea\n ref={ref}\n id={inputId}\n disabled={disabled}\n onChange={handleChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n className={textareaClasses}\n placeholder={placeholder}\n required={required}\n {...props}\n />\n\n {/* Error message */}\n {errorMessage && (\n <p className=\"flex gap-1 items-center text-sm text-indicator-error mt-1.5\">\n <WarningCircle size={16} /> {errorMessage}\n </p>\n )}\n\n {/* Helper text */}\n {helperMessage && !errorMessage && (\n <Text size=\"sm\" weight=\"normal\" className=\"mt-1.5 text-text-500\">\n {helperMessage}\n </Text>\n )}\n </div>\n );\n }\n);\n\nTextArea.displayName = 'TextArea';\n\nexport default TextArea;\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';\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"],"mappings":"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","names":["import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/TextArea/TextArea.tsx","../../src/utils/utils.ts","../../src/components/Text/Text.tsx"],"sourcesContent":["import {\n TextareaHTMLAttributes,\n ReactNode,\n forwardRef,\n useState,\n useId,\n ChangeEvent,\n FocusEvent,\n} from 'react';\nimport { WarningCircle } from 'phosphor-react';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\n\n/**\n * TextArea size variants\n */\ntype TextAreaSize = 'small' | 'medium' | 'large' | 'extraLarge';\n\n/**\n * TextArea visual state\n */\ntype TextAreaState = 'default' | 'hovered' | 'focused' | 'invalid' | 'disabled';\n\n/**\n * Size configurations with exact pixel specifications\n */\nconst SIZE_CLASSES = {\n small: {\n textarea: 'h-24 text-sm', // 96px height, 14px font\n textSize: 'sm' as const,\n },\n medium: {\n textarea: 'h-24 text-base', // 96px height, 16px font\n textSize: 'md' as const,\n },\n large: {\n textarea: 'h-24 text-lg', // 96px height, 18px font\n textSize: 'lg' as const,\n },\n extraLarge: {\n textarea: 'h-24 text-xl', // 96px height, 20px font\n textSize: 'xl' as const,\n },\n} as const;\n\n/**\n * Base textarea styling classes using design system colors\n */\nconst BASE_TEXTAREA_CLASSES =\n 'w-full box-border p-3 bg-background border border-solid rounded-[4px] resize-none focus:outline-none font-roboto font-normal leading-[150%] placeholder:text-text-600 transition-all duration-200';\n\n/**\n * State-based styling classes using design system colors from styles.css\n */\nconst STATE_CLASSES = {\n default: {\n base: 'border-border-300 bg-background text-text-600',\n hover: 'hover:border-border-400',\n focus: 'focus:border-border-500',\n },\n hovered: {\n base: 'border-border-400 bg-background text-text-600',\n hover: '',\n focus: 'focus:border-border-500',\n },\n focused: {\n base: 'border-2 border-primary-950 bg-background text-text-900',\n hover: '',\n focus: '',\n },\n invalid: {\n base: 'border-2 border-red-700 bg-white text-gray-800',\n hover: 'hover:border-red-700',\n focus: 'focus:border-red-700',\n },\n disabled: {\n base: 'border-border-300 bg-background text-text-600 cursor-not-allowed opacity-40',\n hover: '',\n focus: '',\n },\n} as const;\n\n/**\n * TextArea component props interface\n */\nexport type TextAreaProps = {\n /** Label text to display above the textarea */\n label?: ReactNode;\n /** Size variant of the textarea */\n size?: TextAreaSize;\n /** Visual state of the textarea */\n state?: TextAreaState;\n /** Error message to display */\n errorMessage?: string;\n /** Helper text to display */\n helperMessage?: string;\n /** Additional CSS classes */\n className?: string;\n /** Label CSS classes */\n labelClassName?: string;\n /** Show character count when maxLength is provided */\n showCharacterCount?: boolean;\n} & Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'>;\n\n/**\n * TextArea component for Analytica Ensino platforms\n *\n * A textarea component with essential states, sizes and themes.\n * Uses exact design specifications with 288px width, 96px height, and specific\n * color values. Includes Text component integration for consistent typography.\n *\n * @example\n * ```tsx\n * // Basic textarea\n * <TextArea label=\"Description\" placeholder=\"Enter description...\" />\n *\n * // Small size\n * <TextArea size=\"small\" label=\"Comment\" />\n *\n * // Invalid state\n * <TextArea state=\"invalid\" label=\"Required field\" errorMessage=\"This field is required\" />\n *\n * // Disabled state\n * <TextArea disabled label=\"Read-only field\" />\n * ```\n */\nconst TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(\n (\n {\n label,\n size = 'medium',\n state = 'default',\n errorMessage,\n helperMessage,\n className = '',\n labelClassName = '',\n disabled,\n id,\n onChange,\n placeholder,\n required,\n showCharacterCount = false,\n maxLength,\n value,\n ...props\n },\n ref\n ) => {\n // Generate unique ID if not provided\n const generatedId = useId();\n const inputId = id ?? `textarea-${generatedId}`;\n\n // Internal state for focus tracking\n const [isFocused, setIsFocused] = useState(false);\n\n // Calculate current character count\n const currentLength = typeof value === 'string' ? value.length : 0;\n const isNearLimit = maxLength && currentLength >= maxLength * 0.8;\n\n // Handle change events\n const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {\n onChange?.(event);\n };\n\n // Handle focus events\n const handleFocus = (event: FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(true);\n props.onFocus?.(event);\n };\n\n // Handle blur events\n const handleBlur = (event: FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(false);\n props.onBlur?.(event);\n };\n\n // Determine current state based on props and focus\n let currentState = disabled ? 'disabled' : state;\n\n // Override state based on focus\n if (\n isFocused &&\n currentState !== 'invalid' &&\n currentState !== 'disabled'\n ) {\n currentState = 'focused';\n }\n\n // Get size classes\n const sizeClasses = SIZE_CLASSES[size];\n\n // Get styling classes\n const stateClasses = STATE_CLASSES[currentState];\n\n // Get final textarea classes\n const textareaClasses = cn(\n BASE_TEXTAREA_CLASSES,\n sizeClasses.textarea,\n stateClasses.base,\n stateClasses.hover,\n stateClasses.focus,\n className\n );\n\n return (\n <div className={`flex flex-col`}>\n {/* Label */}\n {label && (\n <Text\n as=\"label\"\n htmlFor={inputId}\n size={sizeClasses.textSize}\n weight=\"medium\"\n color=\"text-text-950\"\n className={cn('mb-1.5', labelClassName)}\n >\n {label}{' '}\n {required && <span className=\"text-indicator-error\">*</span>}\n </Text>\n )}\n\n {/* Textarea */}\n <textarea\n ref={ref}\n id={inputId}\n disabled={disabled}\n onChange={handleChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n className={textareaClasses}\n placeholder={placeholder}\n required={required}\n maxLength={maxLength}\n value={value}\n {...props}\n />\n\n {/* Error message */}\n {errorMessage && (\n <p className=\"flex gap-1 items-center text-sm text-indicator-error mt-1.5\">\n <WarningCircle size={16} /> {errorMessage}\n </p>\n )}\n\n {/* Helper text or Character count */}\n {!errorMessage && showCharacterCount && maxLength && (\n <Text\n size=\"sm\"\n weight=\"normal\"\n className={`mt-1.5 ${isNearLimit ? 'text-indicator-warning' : 'text-text-500'}`}\n >\n {currentLength}/{maxLength} caracteres\n </Text>\n )}\n {!errorMessage &&\n helperMessage &&\n !(showCharacterCount && maxLength) && (\n <Text size=\"sm\" weight=\"normal\" className=\"mt-1.5 text-text-500\">\n {helperMessage}\n </Text>\n )}\n </div>\n );\n }\n);\n\nTextArea.displayName = 'TextArea';\n\nexport default TextArea;\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';\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"],"mappings":"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","names":["import_jsx_runtime"]}
|
package/dist/TextArea/index.mjs
CHANGED
|
@@ -130,11 +130,16 @@ var TextArea = forwardRef(
|
|
|
130
130
|
onChange,
|
|
131
131
|
placeholder,
|
|
132
132
|
required,
|
|
133
|
+
showCharacterCount = false,
|
|
134
|
+
maxLength,
|
|
135
|
+
value,
|
|
133
136
|
...props
|
|
134
137
|
}, ref) => {
|
|
135
138
|
const generatedId = useId();
|
|
136
139
|
const inputId = id ?? `textarea-${generatedId}`;
|
|
137
140
|
const [isFocused, setIsFocused] = useState(false);
|
|
141
|
+
const currentLength = typeof value === "string" ? value.length : 0;
|
|
142
|
+
const isNearLimit = maxLength && currentLength >= maxLength * 0.8;
|
|
138
143
|
const handleChange = (event) => {
|
|
139
144
|
onChange?.(event);
|
|
140
145
|
};
|
|
@@ -189,6 +194,8 @@ var TextArea = forwardRef(
|
|
|
189
194
|
className: textareaClasses,
|
|
190
195
|
placeholder,
|
|
191
196
|
required,
|
|
197
|
+
maxLength,
|
|
198
|
+
value,
|
|
192
199
|
...props
|
|
193
200
|
}
|
|
194
201
|
),
|
|
@@ -197,7 +204,21 @@ var TextArea = forwardRef(
|
|
|
197
204
|
" ",
|
|
198
205
|
errorMessage
|
|
199
206
|
] }),
|
|
200
|
-
|
|
207
|
+
!errorMessage && showCharacterCount && maxLength && /* @__PURE__ */ jsxs(
|
|
208
|
+
Text_default,
|
|
209
|
+
{
|
|
210
|
+
size: "sm",
|
|
211
|
+
weight: "normal",
|
|
212
|
+
className: `mt-1.5 ${isNearLimit ? "text-indicator-warning" : "text-text-500"}`,
|
|
213
|
+
children: [
|
|
214
|
+
currentLength,
|
|
215
|
+
"/",
|
|
216
|
+
maxLength,
|
|
217
|
+
" caracteres"
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
),
|
|
221
|
+
!errorMessage && helperMessage && !(showCharacterCount && maxLength) && /* @__PURE__ */ jsx2(Text_default, { size: "sm", weight: "normal", className: "mt-1.5 text-text-500", children: helperMessage })
|
|
201
222
|
] });
|
|
202
223
|
}
|
|
203
224
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/TextArea/TextArea.tsx","../../src/utils/utils.ts","../../src/components/Text/Text.tsx"],"sourcesContent":["import {\n TextareaHTMLAttributes,\n ReactNode,\n forwardRef,\n useState,\n useId,\n ChangeEvent,\n FocusEvent,\n} from 'react';\nimport { WarningCircle } from 'phosphor-react';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\n\n/**\n * TextArea size variants\n */\ntype TextAreaSize = 'small' | 'medium' | 'large' | 'extraLarge';\n\n/**\n * TextArea visual state\n */\ntype TextAreaState = 'default' | 'hovered' | 'focused' | 'invalid' | 'disabled';\n\n/**\n * Size configurations with exact pixel specifications\n */\nconst SIZE_CLASSES = {\n small: {\n textarea: 'h-24 text-sm', // 96px height, 14px font\n textSize: 'sm' as const,\n },\n medium: {\n textarea: 'h-24 text-base', // 96px height, 16px font\n textSize: 'md' as const,\n },\n large: {\n textarea: 'h-24 text-lg', // 96px height, 18px font\n textSize: 'lg' as const,\n },\n extraLarge: {\n textarea: 'h-24 text-xl', // 96px height, 20px font\n textSize: 'xl' as const,\n },\n} as const;\n\n/**\n * Base textarea styling classes using design system colors\n */\nconst BASE_TEXTAREA_CLASSES =\n 'w-full box-border p-3 bg-background border border-solid rounded-[4px] resize-none focus:outline-none font-roboto font-normal leading-[150%] placeholder:text-text-600 transition-all duration-200';\n\n/**\n * State-based styling classes using design system colors from styles.css\n */\nconst STATE_CLASSES = {\n default: {\n base: 'border-border-300 bg-background text-text-600',\n hover: 'hover:border-border-400',\n focus: 'focus:border-border-500',\n },\n hovered: {\n base: 'border-border-400 bg-background text-text-600',\n hover: '',\n focus: 'focus:border-border-500',\n },\n focused: {\n base: 'border-2 border-primary-950 bg-background text-text-900',\n hover: '',\n focus: '',\n },\n invalid: {\n base: 'border-2 border-red-700 bg-white text-gray-800',\n hover: 'hover:border-red-700',\n focus: 'focus:border-red-700',\n },\n disabled: {\n base: 'border-border-300 bg-background text-text-600 cursor-not-allowed opacity-40',\n hover: '',\n focus: '',\n },\n} as const;\n\n/**\n * TextArea component props interface\n */\nexport type TextAreaProps = {\n /** Label text to display above the textarea */\n label?: ReactNode;\n /** Size variant of the textarea */\n size?: TextAreaSize;\n /** Visual state of the textarea */\n state?: TextAreaState;\n /** Error message to display */\n errorMessage?: string;\n /** Helper text to display */\n helperMessage?: string;\n /** Additional CSS classes */\n className?: string;\n /** Label CSS classes */\n labelClassName?: string;\n} & Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'>;\n\n/**\n * TextArea component for Analytica Ensino platforms\n *\n * A textarea component with essential states, sizes and themes.\n * Uses exact design specifications with 288px width, 96px height, and specific\n * color values. Includes Text component integration for consistent typography.\n *\n * @example\n * ```tsx\n * // Basic textarea\n * <TextArea label=\"Description\" placeholder=\"Enter description...\" />\n *\n * // Small size\n * <TextArea size=\"small\" label=\"Comment\" />\n *\n * // Invalid state\n * <TextArea state=\"invalid\" label=\"Required field\" errorMessage=\"This field is required\" />\n *\n * // Disabled state\n * <TextArea disabled label=\"Read-only field\" />\n * ```\n */\nconst TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(\n (\n {\n label,\n size = 'medium',\n state = 'default',\n errorMessage,\n helperMessage,\n className = '',\n labelClassName = '',\n disabled,\n id,\n onChange,\n placeholder,\n required,\n ...props\n },\n ref\n ) => {\n // Generate unique ID if not provided\n const generatedId = useId();\n const inputId = id ?? `textarea-${generatedId}`;\n\n // Internal state for focus tracking\n const [isFocused, setIsFocused] = useState(false);\n\n // Handle change events\n const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {\n onChange?.(event);\n };\n\n // Handle focus events\n const handleFocus = (event: FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(true);\n props.onFocus?.(event);\n };\n\n // Handle blur events\n const handleBlur = (event: FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(false);\n props.onBlur?.(event);\n };\n\n // Determine current state based on props and focus\n let currentState = disabled ? 'disabled' : state;\n\n // Override state based on focus\n if (\n isFocused &&\n currentState !== 'invalid' &&\n currentState !== 'disabled'\n ) {\n currentState = 'focused';\n }\n\n // Get size classes\n const sizeClasses = SIZE_CLASSES[size];\n\n // Get styling classes\n const stateClasses = STATE_CLASSES[currentState];\n\n // Get final textarea classes\n const textareaClasses = cn(\n BASE_TEXTAREA_CLASSES,\n sizeClasses.textarea,\n stateClasses.base,\n stateClasses.hover,\n stateClasses.focus,\n className\n );\n\n return (\n <div className={`flex flex-col`}>\n {/* Label */}\n {label && (\n <Text\n as=\"label\"\n htmlFor={inputId}\n size={sizeClasses.textSize}\n weight=\"medium\"\n color=\"text-text-950\"\n className={cn('mb-1.5', labelClassName)}\n >\n {label}{' '}\n {required && <span className=\"text-indicator-error\">*</span>}\n </Text>\n )}\n\n {/* Textarea */}\n <textarea\n ref={ref}\n id={inputId}\n disabled={disabled}\n onChange={handleChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n className={textareaClasses}\n placeholder={placeholder}\n required={required}\n {...props}\n />\n\n {/* Error message */}\n {errorMessage && (\n <p className=\"flex gap-1 items-center text-sm text-indicator-error mt-1.5\">\n <WarningCircle size={16} /> {errorMessage}\n </p>\n )}\n\n {/* Helper text */}\n {helperMessage && !errorMessage && (\n <Text size=\"sm\" weight=\"normal\" className=\"mt-1.5 text-text-500\">\n {helperMessage}\n </Text>\n )}\n </div>\n );\n }\n);\n\nTextArea.displayName = 'TextArea';\n\nexport default TextArea;\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';\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"],"mappings":";AAAA;AAAA,EAGE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,qBAAqB;;;ACT9B,SAAS,YAA6B;AACtC,SAAS,eAAe;AAEjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;ACsHI;AA/CJ,IAAM,OAAO,CAA8B;AAAA,EACzC;AAAA,EACA,OAAO;AAAA,EACP,SAAS;AAAA,EACT,QAAQ;AAAA,EACR;AAAA,EACA,YAAY;AAAA,EACZ,GAAG;AACL,MAAoB;AAClB,MAAI,cAAc;AAClB,MAAI,gBAAgB;AAGpB,QAAM,eAAe;AAAA,IACnB,OAAO;AAAA,IACP,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AAEA,gBAAc,aAAa,IAAI,KAAK,aAAa;AAGjD,QAAM,iBAAiB;AAAA,IACrB,UAAU;AAAA,IACV,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,WAAW;AAAA,IACX,OAAO;AAAA,EACT;AAEA,kBAAgB,eAAe,MAAM,KAAK,eAAe;AAEzD,QAAM,cAAc;AACpB,QAAM,YAAY,MAAO;AAEzB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,aAAa,aAAa,eAAe,OAAO,SAAS;AAAA,MACtE,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,eAAQ;;;AFmEL,SASe,OAAAA,MATf;AA7KV,IAAM,eAAe;AAAA,EACnB,OAAO;AAAA,IACL,UAAU;AAAA;AAAA,IACV,UAAU;AAAA,EACZ;AAAA,EACA,QAAQ;AAAA,IACN,UAAU;AAAA;AAAA,IACV,UAAU;AAAA,EACZ;AAAA,EACA,OAAO;AAAA,IACL,UAAU;AAAA;AAAA,IACV,UAAU;AAAA,EACZ;AAAA,EACA,YAAY;AAAA,IACV,UAAU;AAAA;AAAA,IACV,UAAU;AAAA,EACZ;AACF;AAKA,IAAM,wBACJ;AAKF,IAAM,gBAAgB;AAAA,EACpB,SAAS;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AAAA,EACA,UAAU;AAAA,IACR,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AACF;AA4CA,IAAM,WAAW;AAAA,EACf,CACE;AAAA,IACE;AAAA,IACA,OAAO;AAAA,IACP,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GACA,QACG;AAEH,UAAM,cAAc,MAAM;AAC1B,UAAM,UAAU,MAAM,YAAY,WAAW;AAG7C,UAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAGhD,UAAM,eAAe,CAAC,UAA4C;AAChE,iBAAW,KAAK;AAAA,IAClB;AAGA,UAAM,cAAc,CAAC,UAA2C;AAC9D,mBAAa,IAAI;AACjB,YAAM,UAAU,KAAK;AAAA,IACvB;AAGA,UAAM,aAAa,CAAC,UAA2C;AAC7D,mBAAa,KAAK;AAClB,YAAM,SAAS,KAAK;AAAA,IACtB;AAGA,QAAI,eAAe,WAAW,aAAa;AAG3C,QACE,aACA,iBAAiB,aACjB,iBAAiB,YACjB;AACA,qBAAe;AAAA,IACjB;AAGA,UAAM,cAAc,aAAa,IAAI;AAGrC,UAAM,eAAe,cAAc,YAAY;AAG/C,UAAM,kBAAkB;AAAA,MACtB;AAAA,MACA,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,aAAa;AAAA,MACb,aAAa;AAAA,MACb;AAAA,IACF;AAEA,WACE,qBAAC,SAAI,WAAW,iBAEb;AAAA,eACC;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,SAAS;AAAA,UACT,MAAM,YAAY;AAAA,UAClB,QAAO;AAAA,UACP,OAAM;AAAA,UACN,WAAW,GAAG,UAAU,cAAc;AAAA,UAErC;AAAA;AAAA,YAAO;AAAA,YACP,YAAY,gBAAAA,KAAC,UAAK,WAAU,wBAAuB,eAAC;AAAA;AAAA;AAAA,MACvD;AAAA,MAIF,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,IAAI;AAAA,UACJ;AAAA,UACA,UAAU;AAAA,UACV,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,WAAW;AAAA,UACX;AAAA,UACA;AAAA,UACC,GAAG;AAAA;AAAA,MACN;AAAA,MAGC,gBACC,qBAAC,OAAE,WAAU,+DACX;AAAA,wBAAAA,KAAC,iBAAc,MAAM,IAAI;AAAA,QAAE;AAAA,QAAE;AAAA,SAC/B;AAAA,MAID,iBAAiB,CAAC,gBACjB,gBAAAA,KAAC,gBAAK,MAAK,MAAK,QAAO,UAAS,WAAU,wBACvC,yBACH;AAAA,OAEJ;AAAA,EAEJ;AACF;AAEA,SAAS,cAAc;AAEvB,IAAO,mBAAQ;","names":["jsx"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/TextArea/TextArea.tsx","../../src/utils/utils.ts","../../src/components/Text/Text.tsx"],"sourcesContent":["import {\n TextareaHTMLAttributes,\n ReactNode,\n forwardRef,\n useState,\n useId,\n ChangeEvent,\n FocusEvent,\n} from 'react';\nimport { WarningCircle } from 'phosphor-react';\nimport Text from '../Text/Text';\nimport { cn } from '../../utils/utils';\n\n/**\n * TextArea size variants\n */\ntype TextAreaSize = 'small' | 'medium' | 'large' | 'extraLarge';\n\n/**\n * TextArea visual state\n */\ntype TextAreaState = 'default' | 'hovered' | 'focused' | 'invalid' | 'disabled';\n\n/**\n * Size configurations with exact pixel specifications\n */\nconst SIZE_CLASSES = {\n small: {\n textarea: 'h-24 text-sm', // 96px height, 14px font\n textSize: 'sm' as const,\n },\n medium: {\n textarea: 'h-24 text-base', // 96px height, 16px font\n textSize: 'md' as const,\n },\n large: {\n textarea: 'h-24 text-lg', // 96px height, 18px font\n textSize: 'lg' as const,\n },\n extraLarge: {\n textarea: 'h-24 text-xl', // 96px height, 20px font\n textSize: 'xl' as const,\n },\n} as const;\n\n/**\n * Base textarea styling classes using design system colors\n */\nconst BASE_TEXTAREA_CLASSES =\n 'w-full box-border p-3 bg-background border border-solid rounded-[4px] resize-none focus:outline-none font-roboto font-normal leading-[150%] placeholder:text-text-600 transition-all duration-200';\n\n/**\n * State-based styling classes using design system colors from styles.css\n */\nconst STATE_CLASSES = {\n default: {\n base: 'border-border-300 bg-background text-text-600',\n hover: 'hover:border-border-400',\n focus: 'focus:border-border-500',\n },\n hovered: {\n base: 'border-border-400 bg-background text-text-600',\n hover: '',\n focus: 'focus:border-border-500',\n },\n focused: {\n base: 'border-2 border-primary-950 bg-background text-text-900',\n hover: '',\n focus: '',\n },\n invalid: {\n base: 'border-2 border-red-700 bg-white text-gray-800',\n hover: 'hover:border-red-700',\n focus: 'focus:border-red-700',\n },\n disabled: {\n base: 'border-border-300 bg-background text-text-600 cursor-not-allowed opacity-40',\n hover: '',\n focus: '',\n },\n} as const;\n\n/**\n * TextArea component props interface\n */\nexport type TextAreaProps = {\n /** Label text to display above the textarea */\n label?: ReactNode;\n /** Size variant of the textarea */\n size?: TextAreaSize;\n /** Visual state of the textarea */\n state?: TextAreaState;\n /** Error message to display */\n errorMessage?: string;\n /** Helper text to display */\n helperMessage?: string;\n /** Additional CSS classes */\n className?: string;\n /** Label CSS classes */\n labelClassName?: string;\n /** Show character count when maxLength is provided */\n showCharacterCount?: boolean;\n} & Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'>;\n\n/**\n * TextArea component for Analytica Ensino platforms\n *\n * A textarea component with essential states, sizes and themes.\n * Uses exact design specifications with 288px width, 96px height, and specific\n * color values. Includes Text component integration for consistent typography.\n *\n * @example\n * ```tsx\n * // Basic textarea\n * <TextArea label=\"Description\" placeholder=\"Enter description...\" />\n *\n * // Small size\n * <TextArea size=\"small\" label=\"Comment\" />\n *\n * // Invalid state\n * <TextArea state=\"invalid\" label=\"Required field\" errorMessage=\"This field is required\" />\n *\n * // Disabled state\n * <TextArea disabled label=\"Read-only field\" />\n * ```\n */\nconst TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(\n (\n {\n label,\n size = 'medium',\n state = 'default',\n errorMessage,\n helperMessage,\n className = '',\n labelClassName = '',\n disabled,\n id,\n onChange,\n placeholder,\n required,\n showCharacterCount = false,\n maxLength,\n value,\n ...props\n },\n ref\n ) => {\n // Generate unique ID if not provided\n const generatedId = useId();\n const inputId = id ?? `textarea-${generatedId}`;\n\n // Internal state for focus tracking\n const [isFocused, setIsFocused] = useState(false);\n\n // Calculate current character count\n const currentLength = typeof value === 'string' ? value.length : 0;\n const isNearLimit = maxLength && currentLength >= maxLength * 0.8;\n\n // Handle change events\n const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {\n onChange?.(event);\n };\n\n // Handle focus events\n const handleFocus = (event: FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(true);\n props.onFocus?.(event);\n };\n\n // Handle blur events\n const handleBlur = (event: FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(false);\n props.onBlur?.(event);\n };\n\n // Determine current state based on props and focus\n let currentState = disabled ? 'disabled' : state;\n\n // Override state based on focus\n if (\n isFocused &&\n currentState !== 'invalid' &&\n currentState !== 'disabled'\n ) {\n currentState = 'focused';\n }\n\n // Get size classes\n const sizeClasses = SIZE_CLASSES[size];\n\n // Get styling classes\n const stateClasses = STATE_CLASSES[currentState];\n\n // Get final textarea classes\n const textareaClasses = cn(\n BASE_TEXTAREA_CLASSES,\n sizeClasses.textarea,\n stateClasses.base,\n stateClasses.hover,\n stateClasses.focus,\n className\n );\n\n return (\n <div className={`flex flex-col`}>\n {/* Label */}\n {label && (\n <Text\n as=\"label\"\n htmlFor={inputId}\n size={sizeClasses.textSize}\n weight=\"medium\"\n color=\"text-text-950\"\n className={cn('mb-1.5', labelClassName)}\n >\n {label}{' '}\n {required && <span className=\"text-indicator-error\">*</span>}\n </Text>\n )}\n\n {/* Textarea */}\n <textarea\n ref={ref}\n id={inputId}\n disabled={disabled}\n onChange={handleChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n className={textareaClasses}\n placeholder={placeholder}\n required={required}\n maxLength={maxLength}\n value={value}\n {...props}\n />\n\n {/* Error message */}\n {errorMessage && (\n <p className=\"flex gap-1 items-center text-sm text-indicator-error mt-1.5\">\n <WarningCircle size={16} /> {errorMessage}\n </p>\n )}\n\n {/* Helper text or Character count */}\n {!errorMessage && showCharacterCount && maxLength && (\n <Text\n size=\"sm\"\n weight=\"normal\"\n className={`mt-1.5 ${isNearLimit ? 'text-indicator-warning' : 'text-text-500'}`}\n >\n {currentLength}/{maxLength} caracteres\n </Text>\n )}\n {!errorMessage &&\n helperMessage &&\n !(showCharacterCount && maxLength) && (\n <Text size=\"sm\" weight=\"normal\" className=\"mt-1.5 text-text-500\">\n {helperMessage}\n </Text>\n )}\n </div>\n );\n }\n);\n\nTextArea.displayName = 'TextArea';\n\nexport default TextArea;\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';\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"],"mappings":"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","names":["jsx"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -34,7 +34,7 @@ export { ThemeToggle } from './ThemeToggle/index.mjs';
|
|
|
34
34
|
export { d as SubjectData, e as SubjectEnum, I as SubjectIconProps, S as SubjectInfo, b as getSubjectColorClass, a as getSubjectIcon, g as getSubjectInfo, c as getSubjectName } from './SubjectInfo-Dvt0OodP.mjs';
|
|
35
35
|
export { c as ThemeActions, T as ThemeMode, b as ThemeState, a as ThemeStore, u as useThemeStore } from './themeStore-P2X64zC-.mjs';
|
|
36
36
|
export { default as DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, MenuLabel, ProfileMenuFooter, ProfileMenuHeader, ProfileMenuSection, ProfileMenuTrigger, ProfileToggleTheme } from './DropdownMenu/index.mjs';
|
|
37
|
-
export { default as Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from './Table/index.mjs';
|
|
37
|
+
export { default as Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, useTableSort } from './Table/index.mjs';
|
|
38
38
|
export { default as Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './Select/index.mjs';
|
|
39
39
|
export { default as Menu, MenuContent, MenuItem, MenuOverflow } from './Menu/index.mjs';
|
|
40
40
|
export { CardActivitiesResults, CardAudio, CardPerformance, CardProgress, CardQuestions, CardResults, CardSimulado, CardSimulationHistory, CardStatus, CardTest, CardTopic } from './Card/index.mjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export { ThemeToggle } from './ThemeToggle/index.js';
|
|
|
34
34
|
export { d as SubjectData, e as SubjectEnum, I as SubjectIconProps, S as SubjectInfo, b as getSubjectColorClass, a as getSubjectIcon, g as getSubjectInfo, c as getSubjectName } from './SubjectInfo-Dvt0OodP.js';
|
|
35
35
|
export { c as ThemeActions, T as ThemeMode, b as ThemeState, a as ThemeStore, u as useThemeStore } from './themeStore-P2X64zC-.js';
|
|
36
36
|
export { default as DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, MenuLabel, ProfileMenuFooter, ProfileMenuHeader, ProfileMenuSection, ProfileMenuTrigger, ProfileToggleTheme } from './DropdownMenu/index.js';
|
|
37
|
-
export { default as Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from './Table/index.js';
|
|
37
|
+
export { default as Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, useTableSort } from './Table/index.js';
|
|
38
38
|
export { default as Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './Select/index.js';
|
|
39
39
|
export { default as Menu, MenuContent, MenuItem, MenuOverflow } from './Menu/index.js';
|
|
40
40
|
export { CardActivitiesResults, CardAudio, CardPerformance, CardProgress, CardQuestions, CardResults, CardSimulado, CardSimulationHistory, CardStatus, CardTest, CardTopic } from './Card/index.js';
|
package/dist/index.js
CHANGED
|
@@ -179,6 +179,7 @@ __export(src_exports, {
|
|
|
179
179
|
useMobile: () => useMobile,
|
|
180
180
|
useQuizStore: () => useQuizStore,
|
|
181
181
|
useRouteAuth: () => useRouteAuth,
|
|
182
|
+
useTableSort: () => useTableSort,
|
|
182
183
|
useTheme: () => useTheme,
|
|
183
184
|
useThemeStore: () => useThemeStore,
|
|
184
185
|
useToastStore: () => ToastStore_default,
|
|
@@ -2029,11 +2030,16 @@ var TextArea = (0, import_react9.forwardRef)(
|
|
|
2029
2030
|
onChange,
|
|
2030
2031
|
placeholder,
|
|
2031
2032
|
required,
|
|
2033
|
+
showCharacterCount = false,
|
|
2034
|
+
maxLength,
|
|
2035
|
+
value,
|
|
2032
2036
|
...props
|
|
2033
2037
|
}, ref) => {
|
|
2034
2038
|
const generatedId = (0, import_react9.useId)();
|
|
2035
2039
|
const inputId = id ?? `textarea-${generatedId}`;
|
|
2036
2040
|
const [isFocused, setIsFocused] = (0, import_react9.useState)(false);
|
|
2041
|
+
const currentLength = typeof value === "string" ? value.length : 0;
|
|
2042
|
+
const isNearLimit = maxLength && currentLength >= maxLength * 0.8;
|
|
2037
2043
|
const handleChange = (event) => {
|
|
2038
2044
|
onChange?.(event);
|
|
2039
2045
|
};
|
|
@@ -2088,6 +2094,8 @@ var TextArea = (0, import_react9.forwardRef)(
|
|
|
2088
2094
|
className: textareaClasses,
|
|
2089
2095
|
placeholder,
|
|
2090
2096
|
required,
|
|
2097
|
+
maxLength,
|
|
2098
|
+
value,
|
|
2091
2099
|
...props
|
|
2092
2100
|
}
|
|
2093
2101
|
),
|
|
@@ -2096,7 +2104,21 @@ var TextArea = (0, import_react9.forwardRef)(
|
|
|
2096
2104
|
" ",
|
|
2097
2105
|
errorMessage
|
|
2098
2106
|
] }),
|
|
2099
|
-
|
|
2107
|
+
!errorMessage && showCharacterCount && maxLength && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
2108
|
+
Text_default,
|
|
2109
|
+
{
|
|
2110
|
+
size: "sm",
|
|
2111
|
+
weight: "normal",
|
|
2112
|
+
className: `mt-1.5 ${isNearLimit ? "text-indicator-warning" : "text-text-500"}`,
|
|
2113
|
+
children: [
|
|
2114
|
+
currentLength,
|
|
2115
|
+
"/",
|
|
2116
|
+
maxLength,
|
|
2117
|
+
" caracteres"
|
|
2118
|
+
]
|
|
2119
|
+
}
|
|
2120
|
+
),
|
|
2121
|
+
!errorMessage && helperMessage && !(showCharacterCount && maxLength) && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text_default, { size: "sm", weight: "normal", className: "mt-1.5 text-text-500", children: helperMessage })
|
|
2100
2122
|
] });
|
|
2101
2123
|
}
|
|
2102
2124
|
);
|
|
@@ -6632,6 +6654,75 @@ var createNotificationsHook = (apiClient) => {
|
|
|
6632
6654
|
var import_react23 = require("react");
|
|
6633
6655
|
var import_phosphor_react16 = require("phosphor-react");
|
|
6634
6656
|
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
6657
|
+
function useTableSort(data, options = {}) {
|
|
6658
|
+
const { syncWithUrl = false } = options;
|
|
6659
|
+
const getInitialState = () => {
|
|
6660
|
+
if (!syncWithUrl || globalThis.window === void 0) {
|
|
6661
|
+
return { column: null, direction: null };
|
|
6662
|
+
}
|
|
6663
|
+
const params = new URLSearchParams(globalThis.location.search);
|
|
6664
|
+
const sortBy = params.get("sortBy");
|
|
6665
|
+
const sort = params.get("sort");
|
|
6666
|
+
if (sortBy && sort && (sort === "ASC" || sort === "DESC")) {
|
|
6667
|
+
return {
|
|
6668
|
+
column: sortBy,
|
|
6669
|
+
direction: sort.toLowerCase()
|
|
6670
|
+
};
|
|
6671
|
+
}
|
|
6672
|
+
return { column: null, direction: null };
|
|
6673
|
+
};
|
|
6674
|
+
const initialState = getInitialState();
|
|
6675
|
+
const [sortColumn, setSortColumn] = (0, import_react23.useState)(
|
|
6676
|
+
initialState.column
|
|
6677
|
+
);
|
|
6678
|
+
const [sortDirection, setSortDirection] = (0, import_react23.useState)(
|
|
6679
|
+
initialState.direction
|
|
6680
|
+
);
|
|
6681
|
+
(0, import_react23.useEffect)(() => {
|
|
6682
|
+
if (!syncWithUrl || globalThis.window === void 0) return;
|
|
6683
|
+
const url = new URL(globalThis.location.href);
|
|
6684
|
+
const params = url.searchParams;
|
|
6685
|
+
if (sortColumn && sortDirection) {
|
|
6686
|
+
params.set("sortBy", sortColumn);
|
|
6687
|
+
params.set("sort", sortDirection.toUpperCase());
|
|
6688
|
+
} else {
|
|
6689
|
+
params.delete("sortBy");
|
|
6690
|
+
params.delete("sort");
|
|
6691
|
+
}
|
|
6692
|
+
globalThis.history.replaceState({}, "", url.toString());
|
|
6693
|
+
}, [sortColumn, sortDirection, syncWithUrl]);
|
|
6694
|
+
const handleSort = (column) => {
|
|
6695
|
+
if (sortColumn === column) {
|
|
6696
|
+
if (sortDirection === "asc") {
|
|
6697
|
+
setSortDirection("desc");
|
|
6698
|
+
} else if (sortDirection === "desc") {
|
|
6699
|
+
setSortColumn(null);
|
|
6700
|
+
setSortDirection(null);
|
|
6701
|
+
}
|
|
6702
|
+
} else {
|
|
6703
|
+
setSortColumn(column);
|
|
6704
|
+
setSortDirection("asc");
|
|
6705
|
+
}
|
|
6706
|
+
};
|
|
6707
|
+
const sortedData = (0, import_react23.useMemo)(() => {
|
|
6708
|
+
if (!sortColumn || !sortDirection) {
|
|
6709
|
+
return data;
|
|
6710
|
+
}
|
|
6711
|
+
return [...data].sort((a, b) => {
|
|
6712
|
+
const aValue = a[sortColumn];
|
|
6713
|
+
const bValue = b[sortColumn];
|
|
6714
|
+
if (typeof aValue === "string" && typeof bValue === "string") {
|
|
6715
|
+
const comparison = aValue.localeCompare(bValue);
|
|
6716
|
+
return sortDirection === "asc" ? comparison : -comparison;
|
|
6717
|
+
}
|
|
6718
|
+
if (typeof aValue === "number" && typeof bValue === "number") {
|
|
6719
|
+
return sortDirection === "asc" ? aValue - bValue : bValue - aValue;
|
|
6720
|
+
}
|
|
6721
|
+
return 0;
|
|
6722
|
+
});
|
|
6723
|
+
}, [data, sortColumn, sortDirection]);
|
|
6724
|
+
return { sortedData, sortColumn, sortDirection, handleSort };
|
|
6725
|
+
}
|
|
6635
6726
|
var Table = (0, import_react23.forwardRef)(
|
|
6636
6727
|
({ variant = "default", className, children, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
6637
6728
|
"div",
|
|
@@ -11168,6 +11259,7 @@ var useQuizStore = (0, import_zustand10.create)()(
|
|
|
11168
11259
|
userId: "",
|
|
11169
11260
|
variant: "default",
|
|
11170
11261
|
minuteCallback: null,
|
|
11262
|
+
dissertativeCharLimit: void 0,
|
|
11171
11263
|
questionsResult: null,
|
|
11172
11264
|
currentQuestionResult: null,
|
|
11173
11265
|
// Setters
|
|
@@ -11177,6 +11269,8 @@ var useQuizStore = (0, import_zustand10.create)()(
|
|
|
11177
11269
|
getUserId: () => get().userId,
|
|
11178
11270
|
setVariant: (variant) => set({ variant }),
|
|
11179
11271
|
setQuestionResult: (questionsResult) => set({ questionsResult }),
|
|
11272
|
+
setDissertativeCharLimit: (limit) => set({ dissertativeCharLimit: limit }),
|
|
11273
|
+
getDissertativeCharLimit: () => get().dissertativeCharLimit,
|
|
11180
11274
|
// Navigation
|
|
11181
11275
|
goToNextQuestion: () => {
|
|
11182
11276
|
const { currentQuestionIndex, getTotalQuestions } = get();
|
|
@@ -11266,7 +11360,7 @@ var useQuizStore = (0, import_zustand10.create)()(
|
|
|
11266
11360
|
});
|
|
11267
11361
|
},
|
|
11268
11362
|
selectDissertativeAnswer: (questionId, answer) => {
|
|
11269
|
-
const { quiz, userAnswers } = get();
|
|
11363
|
+
const { quiz, userAnswers, dissertativeCharLimit } = get();
|
|
11270
11364
|
if (!quiz) return;
|
|
11271
11365
|
const activityId = quiz.id;
|
|
11272
11366
|
const userId = get().getUserId();
|
|
@@ -11277,6 +11371,10 @@ var useQuizStore = (0, import_zustand10.create)()(
|
|
|
11277
11371
|
if (!question || question.questionType !== "DISSERTATIVA" /* DISSERTATIVA */) {
|
|
11278
11372
|
return;
|
|
11279
11373
|
}
|
|
11374
|
+
let validatedAnswer = answer;
|
|
11375
|
+
if (dissertativeCharLimit !== void 0 && answer.length > dissertativeCharLimit) {
|
|
11376
|
+
validatedAnswer = answer.substring(0, dissertativeCharLimit);
|
|
11377
|
+
}
|
|
11280
11378
|
const existingAnswerIndex = userAnswers.findIndex(
|
|
11281
11379
|
(answerItem) => answerItem.questionId === questionId
|
|
11282
11380
|
);
|
|
@@ -11284,7 +11382,7 @@ var useQuizStore = (0, import_zustand10.create)()(
|
|
|
11284
11382
|
questionId,
|
|
11285
11383
|
activityId,
|
|
11286
11384
|
userId,
|
|
11287
|
-
answer,
|
|
11385
|
+
answer: validatedAnswer,
|
|
11288
11386
|
optionId: null,
|
|
11289
11387
|
questionType: "DISSERTATIVA" /* DISSERTATIVA */,
|
|
11290
11388
|
answerStatus: "PENDENTE_AVALIACAO" /* PENDENTE_AVALIACAO */
|
|
@@ -11397,6 +11495,7 @@ var useQuizStore = (0, import_zustand10.create)()(
|
|
|
11397
11495
|
userId: "",
|
|
11398
11496
|
variant: "default",
|
|
11399
11497
|
minuteCallback: null,
|
|
11498
|
+
dissertativeCharLimit: void 0,
|
|
11400
11499
|
questionsResult: null,
|
|
11401
11500
|
currentQuestionResult: null
|
|
11402
11501
|
});
|
|
@@ -12003,7 +12102,8 @@ var QuizDissertative = ({ paddingBottom }) => {
|
|
|
12003
12102
|
getCurrentAnswer,
|
|
12004
12103
|
selectDissertativeAnswer,
|
|
12005
12104
|
getQuestionResultByQuestionId,
|
|
12006
|
-
variant
|
|
12105
|
+
variant,
|
|
12106
|
+
getDissertativeCharLimit
|
|
12007
12107
|
} = useQuizStore();
|
|
12008
12108
|
const currentQuestion = getCurrentQuestion();
|
|
12009
12109
|
const currentQuestionResult = getQuestionResultByQuestionId(
|
|
@@ -12011,6 +12111,7 @@ var QuizDissertative = ({ paddingBottom }) => {
|
|
|
12011
12111
|
);
|
|
12012
12112
|
const currentAnswer = getCurrentAnswer();
|
|
12013
12113
|
const textareaRef = (0, import_react38.useRef)(null);
|
|
12114
|
+
const charLimit = getDissertativeCharLimit();
|
|
12014
12115
|
const handleAnswerChange = (value) => {
|
|
12015
12116
|
if (currentQuestion) {
|
|
12016
12117
|
selectDissertativeAnswer(currentQuestion.id, value);
|
|
@@ -12043,7 +12144,9 @@ var QuizDissertative = ({ paddingBottom }) => {
|
|
|
12043
12144
|
value: localAnswer,
|
|
12044
12145
|
onChange: (e) => handleAnswerChange(e.target.value),
|
|
12045
12146
|
rows: 4,
|
|
12046
|
-
className: "min-h-[120px] max-h-[400px] resize-none overflow-y-auto"
|
|
12147
|
+
className: "min-h-[120px] max-h-[400px] resize-none overflow-y-auto",
|
|
12148
|
+
maxLength: charLimit,
|
|
12149
|
+
showCharacterCount: !!charLimit
|
|
12047
12150
|
}
|
|
12048
12151
|
) }) : /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { className: "space-y-4", children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("p", { className: "text-text-600 text-md whitespace-pre-wrap", children: localAnswer || "Nenhuma resposta fornecida" }) }) }) }),
|
|
12049
12152
|
variant === "result" && currentQuestionResult?.answerStatus == "RESPOSTA_INCORRETA" /* RESPOSTA_INCORRETA */ && /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_jsx_runtime54.Fragment, { children: [
|
|
@@ -14185,6 +14288,7 @@ function useAppContent(config) {
|
|
|
14185
14288
|
useMobile,
|
|
14186
14289
|
useQuizStore,
|
|
14187
14290
|
useRouteAuth,
|
|
14291
|
+
useTableSort,
|
|
14188
14292
|
useTheme,
|
|
14189
14293
|
useThemeStore,
|
|
14190
14294
|
useToastStore,
|