Files
sk-react-toolkit/web/TextInput.tsx
T

73 lines
2.8 KiB
TypeScript

import Clear from '@mui/icons-material/Clear'
import Visibility from '@mui/icons-material/Visibility'
import VisibilityOff from '@mui/icons-material/VisibilityOff'
import { IconButton, InputAdornment, TextField } from '@mui/material'
import { useState, type KeyboardEvent, type MouseEvent, type ReactNode } from 'react'
import { verbatim } from './fonts.ts'
/** Keeps the focus in the input when one of its buttons is pressed. */
function keepFocus(event: MouseEvent<HTMLButtonElement>) {
event.preventDefault()
}
/**
* A text field with a clear button, a show/hide button for secrets, and several lines for 'lines'. The browser
* may not change what is typed (no spell check, capitalisation or completion). A small field shows no label; the
* label remains its accessible name.
*/
export default function TextInput(props: {
label: string
value: string
kind?: 'text' | 'secret' | 'lines'
problem?: string
help?: string
disabled?: boolean
autoFocus?: boolean
start?: ReactNode
size?: 'small' | 'medium'
onChange: (value: string) => void
onBlur?: () => void
onKeyDown?: (event: KeyboardEvent<HTMLDivElement>) => void
}) {
const { label, value, kind = 'text', problem, help, disabled = false, onChange } = props
const [shown, setShown] = useState(false)
return (
<TextField
label={props.size === 'small' ? undefined : label}
value={value}
size={props.size ?? (kind === 'lines' ? 'medium' : undefined)}
fullWidth
onBlur={props.onBlur}
onKeyDown={props.onKeyDown}
autoFocus={props.autoFocus}
disabled={disabled}
error={problem !== undefined}
helperText={props.size === 'small' ? problem : problem || help || ' '}
multiline={kind === 'lines'}
minRows={kind === 'lines' ? 2 : undefined}
type={kind === 'secret' && !shown ? 'password' : 'text'}
onChange={(event) => onChange(event.target.value)}
slotProps={{
htmlInput: { ...verbatim, 'aria-label': label },
input: {
startAdornment: props.start === undefined ? undefined : <InputAdornment position="start">{props.start}</InputAdornment>,
endAdornment: (
<InputAdornment position="end">
{kind === 'secret' && (
<IconButton aria-label={shown ? `hide ${label}` : `show ${label}`} disabled={disabled} onClick={() => setShown(!shown)} onMouseDown={keepFocus} onMouseUp={keepFocus}>
{shown ? <VisibilityOff /> : <Visibility />}
</IconButton>
)}
{value !== '' && (
<IconButton aria-label={`clear ${label}`} disabled={disabled} edge="end" onClick={() => onChange('')} onMouseDown={keepFocus} onMouseUp={keepFocus}>
<Clear />
</IconButton>
)}
</InputAdornment>
),
},
}}
/>
)
}