import clsx from "clsx"; import { AriaAttributes, forwardRef, lazy, ReactNode, Ref, RefObject, Suspense, useEffect, useId, useImperativeHandle, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import EntityIcon from "~/components/layouts/icons/EntityIcon"; import HintTooltip from "~/components/ui/tooltips/HintTooltip"; import MonacoEditor from "~/components/editors/MonacoEditor"; import { translateLabel } from "~/utils/i18n/translateLabel"; import { TextInput } from "~/ui/components/TextInput"; import { TextArea } from "~/ui/components/TextArea"; const NovelEditor = lazy(() => import("~/modules/novel/ui/editor")); export interface RefInputText { input: RefObject | RefObject; textArea: RefObject; } type EditorSize = "sm" | "md" | "lg" | "auto" | "full" | "screen"; type WithDefaultValue = { defaultValue: string | undefined }; type WithValueAndSetValue = { value: string | undefined; setValue: React.Dispatch> }; // Subframe TextInput/TextArea accept a constrained `type` union. type SubframeFieldType = "text" | "password" | "email" | "number" | "tel" | "url" | "search"; const SUBFRAME_FIELD_TYPES: SubframeFieldType[] = ["text", "password", "email", "number", "tel", "url", "search"]; function toSubframeType(type: string | undefined): SubframeFieldType { return (SUBFRAME_FIELD_TYPES as string[]).includes(type ?? "") ? (type as SubframeFieldType) : "text"; } export type InputTextProps = (WithDefaultValue | WithValueAndSetValue) & { id?: string; name?: string; title?: string; withLabel?: boolean; className?: string; classNameBg?: string; minLength?: number; maxLength?: number; readOnly?: boolean; disabled?: boolean; required?: boolean; autoComplete?: string; withTranslation?: boolean; translationParams?: string[]; placeholder?: string; pattern?: string; rows?: number; button?: ReactNode; // lowercase?: boolean; // uppercase?: boolean; type?: string; darkMode?: boolean; hint?: ReactNode; help?: string; icon?: string; editor?: string; // monaco editorLanguage?: string; // "javascript" | "typescript" | "html" | "css" | "json"; editorHideLineNumbers?: boolean; editorTheme?: "vs-dark" | "light"; editorFontSize?: number; editorOptions?: any; onBlur?: () => void; onFocus?: () => void; borderless?: boolean; editorSize?: EditorSize; autoFocus?: boolean; isError?: boolean; isSuccess?: boolean; ariaDescribedBy?: string; ariaInvalid?: AriaAttributes["aria-invalid"]; hideChars?: boolean; dataUiId?: string; onPaste?: (e: React.ClipboardEvent) => void; }; const InputText = (props: InputTextProps, ref: Ref) => { const { id, name, title, withLabel = true, className, classNameBg, help, disabled = false, readOnly = false, required = false, minLength, maxLength, autoComplete, withTranslation = false, translationParams = [], placeholder, pattern, hint, rows, button, // lowercase, // uppercase, type = "text", darkMode, icon, editor, editorLanguage, editorHideLineNumbers, editorTheme = "vs-dark", editorFontSize, editorOptions, onBlur, onFocus, borderless, editorSize = "sm", autoFocus, isError, // isSuccess, ariaDescribedBy, ariaInvalid, hideChars, dataUiId, onPaste, } = props; const { t, i18n } = useTranslation(); const resolvedTitle = title ? translateLabel(t, i18n, title) : undefined; const value = "value" in props ? props.value : undefined; const setValue = "setValue" in props ? props.setValue : undefined; const defaultValue = "defaultValue" in props ? props.defaultValue : undefined; const [actualEditorSize] = useState(editorSize ?? ("sm" as EditorSize)); // Fall back to a generated id so the visible