import * as React from 'react'; import { TextControl, tailwind } from '@tesseract-design/web-base'; import { useClientSide, useFallbackId } from '@modal-sh/react-utils'; const { tw } = tailwind; const MaskedTextInputDerivedElementComponent = 'input' as const; /** * Derived HTML element of the {@link MaskedTextInput} component. */ export type MaskedTextInputDerivedElement = HTMLElementTagNameMap[ typeof MaskedTextInputDerivedElementComponent ]; type SelectionDirection = 'none' | 'forward' | 'backward'; const AVAILABLE_AUTO_COMPLETE_VALUES = ['current-password', 'new-password'] as const; /** * Props of the {@link MaskedTextInput} component. */ export interface MaskedTextInputProps extends Omit, 'autoComplete' | 'size' | 'type' | 'label' | 'pattern'> { /** * Short textual description indicating the nature of the component's value. */ label?: React.ReactNode, /** * Short textual description as guidelines for valid input values. */ hint?: React.ReactNode, /** * Size of the component. */ size?: TextControl.Size, /** * Should the component display a border? */ border?: boolean, /** * Should the component occupy the whole width of its parent? */ block?: boolean, /** * Style of the component. */ variant?: TextControl.Variant, /** * Is the label hidden? */ hiddenLabel?: boolean, /** * Visual length of the input. */ length?: number, /** * Should the component be enhanced? */ enhanced?: boolean, /** * Autocomplete value type of the component. */ autoComplete?: typeof AVAILABLE_AUTO_COMPLETE_VALUES[number], } /** * Component for inputting textual values. */ export const MaskedTextInput = React.forwardRef< MaskedTextInputDerivedElement, MaskedTextInputProps >(( { label, hint, size = 'medium' as const, border = false as const, block = false as const, variant = 'default' as const, hiddenLabel = false as const, className, id: idProp, style, length, disabled, onKeyDown, onKeyUp, autoComplete, enhanced: enhancedProp = false as const, ...etcProps }: MaskedTextInputProps, forwardedRef, ) => { const { clientSide: enhanced } = useClientSide({ clientSide: enhancedProp }); const labelId = React.useId(); const id = useFallbackId(idProp); const [visible, setVisible] = React.useState(false); const [visibleViaKey, setVisibleViaKey] = React.useState(false); const defaultRef = React.useRef(null); const ref = forwardedRef ?? defaultRef; const handleKeyDown: React.KeyboardEventHandler< MaskedTextInputDerivedElement > = React.useCallback((e) => { if (e.ctrlKey && e.code === 'Space') { setVisibleViaKey(true); } onKeyDown?.(e); }, [onKeyDown]); const handleKeyUp: React.KeyboardEventHandler< MaskedTextInputDerivedElement > = React.useCallback((e) => { if (e.ctrlKey && e.code === 'Space') { setVisibleViaKey(false); setVisible((prev) => !prev); } onKeyUp?.(e); }, [onKeyUp]); const handleToggleVisible = React.useCallback(() => { const effectiveRef = typeof ref === 'object' ? ref : defaultRef; const current = effectiveRef.current as MaskedTextInputDerivedElement; const selectionStart = current.selectionStart as number; const selectionEnd = current.selectionEnd as number; const selectionDirection = current.selectionDirection as SelectionDirection; setVisible((prev) => !prev); setTimeout(() => { current.focus(); current.setSelectionRange(selectionStart, selectionEnd, selectionDirection); }); }, [ref, defaultRef]); // const preserveFocus: React.MouseEventHandler = React.useCallback(() => { // const { current } = typeof ref === 'object' ? ref : defaultRef; // setVisibleViaKey(true); // setTimeout(() => { // current?.focus(); // }); // }, [ref, defaultRef]); // const handleToggleMouseUp: React.MouseEventHandler = React.useCallback(() => { // const { current } = typeof ref === 'object' ? ref : defaultRef; // setVisibleViaKey(false); // setTimeout(() => { // current?.focus(); // }); // }, [ref, defaultRef]); React.useEffect(() => { if (typeof ref === 'function') { const defaultElement = defaultRef.current as MaskedTextInputDerivedElement; ref(defaultElement); } }, [defaultRef, ref]); return (
{label && ( <> {' '} )} {hint && (
{hint}
)} {enhanced && ( )} {border && ( )}
); }); MaskedTextInput.displayName = 'MaskedTextInput'; MaskedTextInput.defaultProps = { label: undefined, hint: undefined, size: 'medium' as const, border: false as const, block: false as const, variant: 'default' as const, hiddenLabel: false as const, length: undefined, enhanced: false as const, autoComplete: undefined, };