chore: abstracts CheckboxInput into a uncontrolled input

This commit is contained in:
Jarrod Flesch
2023-01-09 16:07:41 -05:00
parent 84bc5ab299
commit 5cabd8e4c0
3 changed files with 117 additions and 52 deletions

View File

@@ -0,0 +1,62 @@
import React from 'react';
import Check from '../../../icons/Check';
import Label from '../../Label';
import './index.scss';
const baseClass = 'custom-checkbox';
type CheckboxInputProps = {
onToggle: React.MouseEventHandler<HTMLButtonElement>
inputRef?: React.MutableRefObject<HTMLInputElement>
readOnly?: boolean
checked?: boolean
name?: string
id?: string
label?: string
required?: boolean
}
export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
const {
onToggle,
checked,
inputRef,
name,
id,
label,
readOnly,
required,
} = props;
return (
<span
className={[
baseClass,
checked && `${baseClass}--checked`,
readOnly && `${baseClass}--read-only`,
].filter(Boolean).join(' ')}
>
<input
ref={inputRef}
id={id}
type="checkbox"
name={name}
checked={checked}
readOnly
/>
<button
type="button"
onClick={onToggle}
>
<span className={`${baseClass}__input`}>
<Check />
</span>
<Label
htmlFor={id}
label={label}
required={required}
/>
</button>
</span>
);
};

View File

@@ -14,6 +14,46 @@
&__error-wrap {
position: relative;
}
}
.custom-checkbox {
label {
padding-bottom: 0;
}
input {
// hidden HTML checkbox
position: absolute;
top: 0;
left: 0;
opacity: 0;
}
&__input {
// visible checkbox
@include formInput;
padding: 0;
line-height: 0;
position: relative;
width: $baseline;
height: $baseline;
margin-right: base(.5);
svg {
opacity: 0;
}
}
&--read-only {
.custom-checkbox__input {
background-color: var(--theme-elevation-100);
}
label {
color: var(--theme-elevation-400);
}
}
button {
@extend %btn-reset;
@@ -33,45 +73,13 @@
}
}
&__input {
@include formInput;
padding: 0;
line-height: 0;
position: relative;
width: $baseline;
height: $baseline;
margin-right: base(.5);
svg {
opacity: 0;
}
}
input {
position: absolute;
top: 0;
left: 0;
opacity: 0;
}
&--checked {
button {
.checkbox__input {
.custom-checkbox__input {
svg {
opacity: 1;
}
}
}
}
&--read-only {
.checkbox__label {
color: var(--theme-elevation-400);
}
.checkbox__input {
background-color: var(--theme-elevation-100);
}
}
}

View File

@@ -4,10 +4,10 @@ import useField from '../../useField';
import withCondition from '../../withCondition';
import Error from '../../Error';
import { checkbox } from '../../../../../fields/validations';
import Check from '../../../icons/Check';
import FieldDescription from '../../FieldDescription';
import { Props } from './types';
import { getTranslation } from '../../../../../utilities/getTranslation';
import { CheckboxInput } from './Input';
import './index.scss';
@@ -52,6 +52,15 @@ const Checkbox: React.FC<Props> = (props) => {
condition,
});
const onToggle = useCallback(() => {
if (!readOnly) {
setValue(!value);
if (typeof onChange === 'function') onChange(!value);
}
}, [onChange, readOnly, setValue, value]);
const fieldID = `field-${path.replace(/\./gi, '__')}`;
return (
<div
className={[
@@ -73,27 +82,13 @@ const Checkbox: React.FC<Props> = (props) => {
message={errorMessage}
/>
</div>
<input
id={`field-${path.replace(/\./gi, '__')}`}
type="checkbox"
<CheckboxInput
onToggle={onToggle}
id={fieldID}
label={getTranslation(label || name, i18n)}
name={path}
checked={Boolean(value)}
readOnly
/>
<button
type="button"
onClick={readOnly ? undefined : () => {
setValue(!value);
if (typeof onChange === 'function') onChange(!value);
}}
>
<span className={`${baseClass}__input`}>
<Check />
</span>
<span className={`${baseClass}__label`}>
{getTranslation(label || name, i18n)}
</span>
</button>
<FieldDescription
value={value}
description={description}