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;
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue