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:
parent
33b8f131b4
commit
445da51688
4 changed files with 84 additions and 125 deletions
|
|
@ -1,3 +0,0 @@
|
||||||
.Joined .Input::placeholder {
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue