import classNames from 'classnames' import { type ChangeEvent, type HTMLAttributes, type KeyboardEvent, type PropsWithChildren, useCallback, useState, } from 'react' import { CheckIcon } from 'lib/icons/Check.js' import { CloseIcon } from 'lib/icons/Close.js' import { EditIcon } from 'lib/icons/Edit.js' type EditableDeviceNameProps = { onEdit?: (newName: string) => void editable?: boolean tagName?: string value: string } & HTMLAttributes export function EditableDeviceName({ onEdit, editable = true, tagName, value, ...props }: EditableDeviceNameProps): JSX.Element { const [editing, setEditing] = useState(false) const [errorText, setErrorText] = useState(null) const [currentValue, setCurrentValue] = useState(value) const Tag = (tagName ?? 'span') as 'div' const handleCheck = useCallback(() => { const fixedName = fixName(currentValue) const valid = isValidName(fixedName) if (valid.type === 'error') { setErrorText(valid.message) return } setEditing(false) setCurrentValue(fixedName) onEdit?.(fixedName) }, [currentValue, onEdit]) const handleChange = useCallback( (event: ChangeEvent): void => { setCurrentValue(event.target.value) setErrorText(null) }, [] ) const handleCancel = useCallback(() => { setEditing(false) setCurrentValue(value) setErrorText(null) }, [value]) const handleInputKeydown = useCallback( (e: KeyboardEvent): void => { if (e.repeat) return if (e.key === 'Enter') { handleCheck() } else if (e.key === 'Escape') { handleCancel() } }, [handleCheck, handleCancel] ) return ( {editable && ( { setEditing(true) }} onCancel={handleCancel} onCheck={handleCheck} /> )} ) } interface NameViewProps { editing: boolean value: string onChange: (event: ChangeEvent) => void onKeyDown: (event: KeyboardEvent) => void errorText?: string | null } function NameView(props: NameViewProps): JSX.Element { if (!props.editing) { return {props.value} } return ( { setTimeout(() => { el?.focus() }, 0) }} /> {props.errorText != null && ( {props.errorText} )} ) } interface ActionButtonsProps { onEdit: () => void onCancel: () => void onCheck: () => void editing: boolean } function ActionButtons(props: ActionButtonsProps): JSX.Element { if (props.editing) { return ( <> ) } return ( ) } function IconButton( props: PropsWithChildren> ): JSX.Element { return ( ) } const fixName = (name: string): string => { return name.replace(/\s+/g, ' ').trim() } type Result = | { type: 'success' } | { type: 'error' message: string } const isValidName = (name: string): Result => { if (name.length < 2) { return { type: 'error', message: 'Name must be at least 2 characters long', } } if (name.length > 64) { return { type: 'error', message: 'Name must be at most 64 characters long', } } return { type: 'success', } as const }