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;
box-sizing: border-box;
display: block;
padding: calc($unit-2x - 2px);
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 {
-webkit-appearance: none;
}
@ -23,11 +56,6 @@
&:hover {
background-color: var(--input-bound-bg-hover);
}
&::placeholder {
/* Chrome, Firefox, Opera, Safari 10.1+ */
color: var(--text-tertiary) !important;
}
}
&.AlignRight {
@ -39,17 +67,12 @@
}
}
.inputError {
color: $error;
font-size: $font-tiny;
margin: $unit 0;
padding: calc($unit / 2) ($unit * 2);
min-width: 100%;
width: 0;
.counter {
display: none;
}
.input::placeholder {
/* Chrome, Firefox, Opera, Safari 10.1+ */
.input::placeholder,
.input > input::placeholder {
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'
interface Props
extends React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
> {
interface Props extends React.ComponentProps<'input'> {
bound?: boolean
visible?: string
error?: string
label?: string
hide1Password?: boolean
showCounter?: boolean
}
const defaultProps = {
visible: 'true',
bound: false,
hide1Password: true,
showCounter: false,
}
const Input = React.forwardRef<HTMLInputElement, Props>(function Input(
{ value, visible, bound, error, label, ...props }: Props,
const Input = React.forwardRef<HTMLInputElement, Props>(function input(
{ value, bound, showCounter, ...props }: Props,
forwardedRef
) {
// States
const [inputValue, setInputValue] = useState('')
const [currentCount, setCurrentCount] = useState(() =>
props.maxLength ? props.maxLength - (`${value}` || '').length : 0
)
// Classes
const classes = classNames(
const wrapperClasses = classNames(
{
[styles.input]: true,
[styles.bound]: bound,
[styles.wrapper]: showCounter,
[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
// Change value when prop updates
// Hooks
useEffect(() => {
if (value) setInputValue(`${value}`)
}, [value])
if (props.maxLength)
setCurrentCount(props.maxLength - (`${value}` || '').length)
}, [props.maxLength, value])
// Event handlers
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
setInputValue(event.target.value)
if (props.onChange) props.onChange(event)
}
// Rendering
return (
<React.Fragment>
<div className={wrapperClasses}>
<input
{...inputProps}
autoComplete="off"
className={classes}
value={inputValue}
ref={forwardedRef}
data-1p-ignore={props.hide1Password}
autoComplete={props.autoComplete}
className={inputClasses}
type={props.type}
name={props.name}
placeholder={props.placeholder}
defaultValue={value || ''}
onBlur={props.onBlur}
onChange={handleChange}
maxLength={props.maxLength}
ref={forwardedRef}
formNoValidate
/>
{error && error.length > 0 && <p className="InputError">{error}</p>}
</React.Fragment>
<span className={styles.counter}>{currentCount}</span>
</div>
)
})