Skip to content

Commit

Permalink
improve inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
morewings committed Dec 12, 2023
1 parent 33c34ba commit c42e1cb
Show file tree
Hide file tree
Showing 13 changed files with 136 additions and 73 deletions.
7 changes: 6 additions & 1 deletion src/lib/Form/Form.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,15 @@ export const Example: Story = {
<InputText defaultValue="foo" name="foo" id="qux" required />
</FormField>
<InputGroup name="radio-demo" label="Radio group">
<InputRadio required id="foo" value="foo" label="This is a foo name" />
<InputRadio id="foo" value="foo" label="This is a foo name" />
<InputRadio disabled id="bar" value="bar" label="This is a bar name" />
<InputRadio id="bazz" value="bazz" label="This is a bazz name" />
</InputGroup>
<InputGroup name="radio-demo-required" label="Radio group required" required>
<InputRadio id="foo-required" value="foo-required" label="This is a foo name" />
<InputRadio disabled id="bar-required" value="bar-required" label="This is a bar name" />
<InputRadio id="bazz-required" value="bazz-required" label="This is a bazz name" />
</InputGroup>
<InputGroup name="checkbox-demo" label="Radio group">
<InputCheckbox id="zork" value="foo" label="This is a foo name" key="zork" required />
<InputCheckbox required id="gork" value="bar" label="This is a bar name" key="gork" />
Expand Down
1 change: 1 addition & 0 deletions src/lib/FormField/FormField.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
}

.label {
color: var(--text);
cursor: pointer;
font-size: 18px;
line-height: 1;
Expand Down
8 changes: 6 additions & 2 deletions src/lib/Icons/IconError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import Error from '@material-symbols/svg-700/sharp/error.svg?react';

import {Icon} from './Icon.tsx';

export const IconError: FC = () => {
type Props = {
className?: string;
};

export const IconError: FC<Props> = ({className}) => {
return (
<Icon>
<Icon className={className}>
<Error />
</Icon>
);
Expand Down
9 changes: 7 additions & 2 deletions src/lib/Icons/IconLoader.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import type {FC} from 'react';
import Loader from '@material-symbols/svg-700/sharp/progress_activity.svg?react';
import classNames from 'classnames';

import {Icon} from './Icon.tsx';
import classes from './Icon.module.css';

export const IconLoader: FC = () => {
type Props = {
className?: string;
};

export const IconLoader: FC<Props> = ({className}) => {
return (
<Icon className={classes.rotation}>
<Icon className={classNames(classes.rotation, className)}>
<Loader />
</Icon>
);
Expand Down
8 changes: 6 additions & 2 deletions src/lib/Icons/IconValid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import Check from '@material-symbols/svg-700/sharp/check.svg?react';

import {Icon} from './Icon.tsx';

export const IconValid: FC = () => {
type Props = {
className?: string;
};

export const IconValid: FC<Props> = ({className}) => {
return (
<Icon>
<Icon className={className}>
<Check />
</Icon>
);
Expand Down
33 changes: 21 additions & 12 deletions src/lib/InputCheckbox/InputCheckbox.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@
width: var(--size);
}

&:invalid {
--borderColor: red;
}

&:disabled {
--borderColor: var(--gray100);

Expand All @@ -51,23 +47,36 @@
}
}

.wrapper:hover {
.input:not(:disabled, :invalid) {
--borderColor: var(--gray400);
}
}

.label {
color: var(--text);
cursor: pointer;
margin-left: var(--sizeUnit);
}

.icon {
margin-left: auto;
}

.required {
font-size: 24px;
line-height: 18px;
}

.input:disabled + .label {
color: var(--gray200);
cursor: not-allowed;
}

.input:invalid + .label {
color: var(--colorError);
.wrapper:hover {
.input:invalid {
--borderColor: red;
}

.input:invalid + .label {
color: var(--colorError);
}

.input:not(:disabled, :invalid) {
--borderColor: var(--gray400);
}
}
16 changes: 11 additions & 5 deletions src/lib/InputCheckbox/InputCheckbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import classNames from 'classnames';
import type {DataAttributes, LibraryProps} from '@/internal/LibraryAPI';
import {Validation} from '@/internal/inputs';
import type {NativePropsInteractive, CallbackPropsInteractive} from '@/internal/inputs';
import {IconError, IconLoader, IconValid} from '@/lib/Icons';

import classes from './InputCheckbox.module.css';

type Props = DataAttributes &
LibraryProps &
NativePropsInteractive &
CallbackPropsInteractive & {
validation?: keyof typeof Validation;
validator?: (event: ChangeEvent<HTMLInputElement>) => void;
label?: string;
};
Expand All @@ -21,7 +21,6 @@ export const InputCheckbox = forwardRef<HTMLInputElement, Props>(
(
{
className,
validation,
disabled,
value,
onChange = () => {},
Expand All @@ -34,15 +33,20 @@ export const InputCheckbox = forwardRef<HTMLInputElement, Props>(
id,
label,
validator = () => {},
required,
...nativeProps
},
ref
) => {
const [_, setValidity] = useState(validation);
const [validity, setValidity] = useState<keyof typeof Validation | null>(null);
const ValidationIcon = {
[Validation.error]: IconError,
[Validation.valid]: IconValid,
[Validation.inProgress]: IconLoader,
}[validity!];
const handleChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
const nextValidationState = event.target.checkValidity() ? Validation.valid : Validation.error;
console.log('valid', event.target.checkValidity());
setValidity(nextValidationState);
onChange(event);
},
Expand All @@ -66,10 +70,12 @@ export const InputCheckbox = forwardRef<HTMLInputElement, Props>(
onKeyUp={onKeyUp}
onKeyDown={onKeyDown}
onInput={validator}
required={required}
/>
<label className={classes.label} htmlFor={id}>
{label}
{label} {required && <span className={classes.required}>*</span>}
</label>
{validity && <ValidationIcon className={classes.icon} />}
</div>
);
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/InputGroup/InputGroup.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
}

.legend {
color: var(--text);
cursor: default;
font-size: 18px;
margin-bottom: calc(var(--sizeUnit) * 1.5);
Expand Down
47 changes: 28 additions & 19 deletions src/lib/InputGroup/InputGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
type ChangeEvent,
type ReactElement,
type FieldsetHTMLAttributes,
useMemo,
} from 'react';
import classNames from 'classnames';

Expand All @@ -31,34 +32,42 @@ type Props = DataAttributes &
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset
*/
disabled?: FieldsetHTMLAttributes<HTMLFieldSetElement>['disabled'];
required?: boolean;
hint?: string;
};

type ChildProps = {
name?: Props['name'];
disabled?: Props['disabled'];
required?: Props['required'];
};

export const InputGroup = forwardRef<HTMLFieldSetElement, Props>(
({prefix: Prefix, className, validation, id, label, children, name, disabled, hint, ...nativeProps}, ref) => {
(
{prefix: Prefix, className, validation, id, label, children, name, disabled, hint, required, ...nativeProps},
ref
) => {
const childrenWithProps = useMemo(() => {
return Children.map(children, element => {
if (isValidElement(element)) {
const nextProps = {name} as ChildProps;
if (disabled !== undefined) {
nextProps.disabled = disabled;
}
if (required !== undefined) {
nextProps.required = required;
}
return cloneElement<ChildProps>(element, nextProps);
}
return element;
});
}, [children, disabled, name, required]);
return (
<fieldset {...nativeProps} className={classNames(classes.wrapper, className)} disabled={disabled} ref={ref}>
<legend className={classes.legend} data-disabled={disabled}>
{label}
</legend>
<div className={classes.inputs}>
{Children.map(children, element => {
if (isValidElement(element)) {
const nextProps =
disabled !== undefined
? {
name,
disabled,
}
: {name};
return cloneElement<{name?: Props['name']; disabled?: Props['disabled']}>(
element,
nextProps
);
}
return element;
})}
</div>
<div className={classes.inputs}>{childrenWithProps}</div>
{hint && <div className={classes.hint}>{hint}</div>}
</fieldset>
);
Expand Down
35 changes: 22 additions & 13 deletions src/lib/InputRadio/InputRadio.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
flex-direction: row;
}

.label {
cursor: pointer;
margin-left: var(--sizeUnit);
}

.input {
--bgColor: #fff;
--borderColor: var(--gray200);
Expand Down Expand Up @@ -34,10 +39,6 @@
width: var(--size);
}

&:invalid {
--borderColor: red;
}

&:disabled {
--borderColor: var(--gray100);

Expand All @@ -51,24 +52,32 @@
&:focus:not(:disabled, :invalid) {
--borderColor: var(--gray400);
}

&:disabled + .label {
color: var(--gray200);
cursor: not-allowed;
}
}

.wrapper:hover {
.input:not(:disabled, :invalid) {
--borderColor: var(--gray400);
}
}

.label {
cursor: pointer;
margin-left: var(--sizeUnit);
.input:invalid {
--borderColor: red;
}

.input:invalid + .label {
color: var(--colorError);
}
}

.input:disabled + .label {
color: var(--gray200);
cursor: not-allowed;
.icon {
margin-left: auto;
}

.input:invalid + .label {
color: var(--colorError);
.required {
font-size: 24px;
line-height: 18px;
}
8 changes: 4 additions & 4 deletions src/lib/InputRadio/InputRadio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export type Props = DataAttributes &
LibraryProps &
NativePropsInteractive &
CallbackPropsInteractive & {
validation?: keyof typeof Validation;
validator?: (event: ChangeEvent<HTMLInputElement>) => void;
label?: string;
};
Expand All @@ -21,7 +20,6 @@ export const InputRadio = forwardRef<HTMLInputElement, Props>(
(
{
className,
validation,
disabled,
value,
onChange = () => {},
Expand All @@ -34,11 +32,12 @@ export const InputRadio = forwardRef<HTMLInputElement, Props>(
id,
label,
validator = () => {},
required,
...nativeProps
},
ref
) => {
const [_, setValidity] = useState(validation);
const [_, setValidity] = useState<keyof typeof Validation | null>(null);
const handleChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
const nextValidationState = event.target.checkValidity() ? Validation.valid : Validation.error;
Expand All @@ -64,10 +63,11 @@ export const InputRadio = forwardRef<HTMLInputElement, Props>(
onFocus={onFocus}
onKeyUp={onKeyUp}
onKeyDown={onKeyDown}
required={required}
onInput={validator}
/>
<label className={classes.label} htmlFor={id}>
{label}
{label} {required && <span className={classes.required}>*</span>}
</label>
</div>
);
Expand Down
9 changes: 5 additions & 4 deletions src/lib/InputText/InputText.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

&:focus {
background-color: white;
border-color: grey;
border-color: var(--gray400);
outline: none;
}

Expand All @@ -43,6 +43,7 @@
}
}

/* input[type="checkbox"]:required:invalid + label {
color: red;
} */
.required {
font-size: 24px;
line-height: 18px;
}
Loading

0 comments on commit c42e1cb

Please sign in to comment.