Combine Input and CharLimitedFieldset

We fixed the input component and added a character counter to it, so we don't need a separate CharLimitedFieldSet anymore.

The input component has been simplified to *just* be an input component, so it no longer displays an error. We will make a new component for error handling and labeling. It will probably be an improvement on our custom Fieldset somehow.
This commit is contained in:
Justin Edmund 2023-06-24 15:30:02 -07:00
parent 33b8f131b4
commit 445da51688
4 changed files with 84 additions and 125 deletions

View file

@ -1,3 +0,0 @@
.Joined .Input::placeholder {
color: var(--text-tertiary);
}

View file

@ -1,79 +0,0 @@
import React, {
ForwardRefRenderFunction,
forwardRef,
useEffect,
useState,
} from 'react'
import classNames from 'classnames'
import styles from './index.module.scss'
interface Props extends React.HTMLProps<HTMLInputElement> {
fieldName: string
placeholder: string
value?: string
limit: number
error: string
onBlur?: (event: React.ChangeEvent<HTMLInputElement>) => void
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
}
const CharLimitedFieldset: ForwardRefRenderFunction<HTMLInputElement, Props> = (
{
fieldName,
placeholder,
value,
limit,
error,
onBlur,
onChange: onInputChange,
...props
},
ref
) => {
// States
const [currentCount, setCurrentCount] = useState(
() => limit - (value || '').length
)
// Hooks
useEffect(() => {
setCurrentCount(limit - (value || '').length)
}, [limit, value])
// Event handlers
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value: inputValue } = event.currentTarget
setCurrentCount(limit - inputValue.length)
if (onInputChange) {
onInputChange(event)
}
}
// Rendering methods
return (
<fieldset className="Fieldset">
<div className={classNames({ Joined: true }, props.className)}>
<input
{...props}
data-1p-ignore
autoComplete="off"
className="Input"
type={props.type}
name={fieldName}
placeholder={placeholder}
defaultValue={value || ''}
onBlur={onBlur}
onChange={handleInputChange}
maxLength={limit}
ref={ref}
formNoValidate
/>
<span className="Counter">{currentCount}</span>
</div>
{error.length > 0 && <p className="InputError">{error}</p>}
</fieldset>
)
}
export default forwardRef(CharLimitedFieldset)

View file

@ -5,9 +5,42 @@
border-radius: $input-corner; border-radius: $input-corner;
box-sizing: border-box; box-sizing: border-box;
display: block; display: block;
padding: calc($unit-2x - 2px);
width: 100%; width: 100%;
&:not(.wrapper) {
padding: $unit * 1.5 $unit-2x;
}
&.wrapper {
$offset: 2px;
align-items: center;
background: var(--input-bg);
border-radius: $input-corner;
border: $offset solid transparent;
box-sizing: border-box;
position: relative;
.counter {
color: var(--text-tertiary);
display: block;
font-weight: $bold;
line-height: 42px;
position: absolute;
right: $unit-2x;
top: 0;
}
input {
background: transparent;
border-radius: $input-corner;
border: none;
box-sizing: border-box;
padding: $unit * 1.5 $unit-2x;
width: 100%;
}
}
&[type='number']::-webkit-inner-spin-button { &[type='number']::-webkit-inner-spin-button {
-webkit-appearance: none; -webkit-appearance: none;
} }
@ -23,11 +56,6 @@
&:hover { &:hover {
background-color: var(--input-bound-bg-hover); background-color: var(--input-bound-bg-hover);
} }
&::placeholder {
/* Chrome, Firefox, Opera, Safari 10.1+ */
color: var(--text-tertiary) !important;
}
} }
&.AlignRight { &.AlignRight {
@ -39,17 +67,12 @@
} }
} }
.inputError { .counter {
color: $error; display: none;
font-size: $font-tiny;
margin: $unit 0;
padding: calc($unit / 2) ($unit * 2);
min-width: 100%;
width: 0;
} }
.input::placeholder { .input::placeholder,
/* Chrome, Firefox, Opera, Safari 10.1+ */ .input > input::placeholder {
color: var(--text-secondary); color: var(--text-secondary);
opacity: 1; /* Firefox */ opacity: 1;
} }

View file

@ -3,61 +3,79 @@ import classNames from 'classnames'
import styles from './index.module.scss' import styles from './index.module.scss'
interface Props interface Props extends React.ComponentProps<'input'> {
extends React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
> {
bound?: boolean bound?: boolean
visible?: string hide1Password?: boolean
error?: string showCounter?: boolean
label?: string
} }
const defaultProps = { const defaultProps = {
visible: 'true', bound: false,
hide1Password: true,
showCounter: false,
} }
const Input = React.forwardRef<HTMLInputElement, Props>(function Input( const Input = React.forwardRef<HTMLInputElement, Props>(function input(
{ value, visible, bound, error, label, ...props }: Props, { value, bound, showCounter, ...props }: Props,
forwardedRef forwardedRef
) { ) {
// States // States
const [inputValue, setInputValue] = useState('') const [currentCount, setCurrentCount] = useState(() =>
props.maxLength ? props.maxLength - (`${value}` || '').length : 0
)
// Classes // Classes
const classes = classNames( const wrapperClasses = classNames(
{ {
[styles.input]: true, [styles.wrapper]: showCounter,
[styles.bound]: bound, [styles.input]: showCounter,
[styles.bound]: showCounter && bound,
}, },
props.className showCounter &&
props.className?.split(' ').map((className) => styles[className])
)
const inputClasses = classNames(
{
[styles.input]: !showCounter,
[styles.bound]: !showCounter && bound,
},
!showCounter &&
props.className?.split(' ').map((className) => styles[className])
) )
const { defaultValue, ...inputProps } = props const { defaultValue, ...inputProps } = props
// Change value when prop updates // Hooks
useEffect(() => { useEffect(() => {
if (value) setInputValue(`${value}`) if (props.maxLength)
}, [value]) setCurrentCount(props.maxLength - (`${value}` || '').length)
}, [props.maxLength, value])
// Event handlers
function handleChange(event: React.ChangeEvent<HTMLInputElement>) { function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
setInputValue(event.target.value)
if (props.onChange) props.onChange(event) if (props.onChange) props.onChange(event)
} }
// Rendering
return ( return (
<React.Fragment> <div className={wrapperClasses}>
<input <input
{...inputProps} {...inputProps}
autoComplete="off" data-1p-ignore={props.hide1Password}
className={classes} autoComplete={props.autoComplete}
value={inputValue} className={inputClasses}
ref={forwardedRef} type={props.type}
name={props.name}
placeholder={props.placeholder}
defaultValue={value || ''}
onBlur={props.onBlur}
onChange={handleChange} onChange={handleChange}
maxLength={props.maxLength}
ref={forwardedRef}
formNoValidate formNoValidate
/> />
{error && error.length > 0 && <p className="InputError">{error}</p>} <span className={styles.counter}>{currentCount}</span>
</React.Fragment> </div>
) )
}) })