Add input validation state

This commit is contained in:
Arnaud 2024-10-14 20:36:12 +02:00
parent 887f27be70
commit 411b76160c
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
3 changed files with 54 additions and 88 deletions

View File

@ -3,7 +3,8 @@ import {
ComponentType, ComponentType,
CSSProperties, CSSProperties,
forwardRef, forwardRef,
KeyboardEvent, InputHTMLAttributes,
useState,
} from "react"; } from "react";
import { attributes } from "../utils/attributes"; import { attributes } from "../utils/attributes";
import { classnames } from "../utils/classnames"; import { classnames } from "../utils/classnames";
@ -20,74 +21,33 @@ export interface InputCustomStyleCSS extends CSSProperties {
} }
type Props = { type Props = {
label?: string;
id: string; id: string;
/** label?: string;
* OnChange event triggered every time the input value changed.
*/
onChange?: (e: ChangeEvent<HTMLInputElement>) => void | Promise<void>;
onFocus?: () => void | Promise<void>;
onBlur?: () => unknown | Promise<unknown>;
onClick?: (() => void) | undefined;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
onKeyUp?: (e: KeyboardEvent<HTMLInputElement>) => void;
placeholder?: string;
value?: string;
/**
* Apply custom css variables.
* --codex-input-background
* --codex-color
* --codex-border-radius
* --codex-input-border
* --codex-color-primary
* --codex-input-background-disabled
*/
style?: InputCustomStyleCSS;
/** /**
* Helper text to add indication about your input. * Helper text to add indication about your input.
*/ */
helper?: string; helper?: string;
disabled?: boolean;
/** /**
* Add an icon on the left. * Add an icon on the left.
*/ */
Icon?: ComponentType; Icon?: ComponentType;
/**
* If the mode is "auto", the component will check the invalid state
* on change and add an invalid state if it is invalid.
*/
mode?: "auto";
isInvalid?: boolean;
/** /**
* Apply a class to the input element * Apply a class to the input element
*/ */
inputClassName?: string; inputClassName?: string;
} & InputHTMLAttributes<HTMLInputElement>;
/**
* Default is text
*/
type?: string;
step?: string;
name?: string;
min?: number | string;
max?: number | string;
maxLength?: number;
};
export const Input = forwardRef<HTMLInputElement, Props>( export const Input = forwardRef<HTMLInputElement, Props>(
( (
@ -95,28 +55,29 @@ export const Input = forwardRef<HTMLInputElement, Props>(
id, id,
label, label,
helper, helper,
disabled,
value,
onBlur,
onFocus,
placeholder,
onChange,
onMouseEnter,
onMouseLeave,
onClick,
onKeyUp,
style, style,
Icon, Icon,
step,
name,
inputClassName, inputClassName,
maxLength, disabled = false,
type = "text", onChange,
min, mode,
max, isInvalid = false,
...rest
}, },
ref ref
) => { ) => {
const [invalid, setInvalid] = useState(isInvalid);
const onInternalChange = (e: ChangeEvent<HTMLInputElement>) => {
if (mode === "auto") {
setInvalid(e.currentTarget.checkValidity() !== true);
}
onChange?.(e);
};
console.info(rest);
return ( return (
<> <>
{label && ( {label && (
@ -133,38 +94,29 @@ export const Input = forwardRef<HTMLInputElement, Props>(
)} )}
<input <input
id={id} id={id}
name={name}
ref={ref} ref={ref}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={classnames( className={classnames(
["input"], ["input"],
["input--invalid", invalid || isInvalid],
["input-icon-input", !!Icon], ["input-icon-input", !!Icon],
[inputClassName || ""] [inputClassName || ""]
)} )}
onChange={onInternalChange}
style={style} style={style}
{...attributes({ {...attributes({
disabled, disabled,
"aria-disabled": disabled, "aria-disabled": disabled,
})} })}
value={value} {...rest}
placeholder={placeholder}
onBlur={onBlur}
onFocus={onFocus}
onChange={onChange}
onKeyUp={onKeyUp}
type={type}
step={step}
min={min}
max={max}
maxLength={maxLength}
/> />
</div> </div>
{helper && ( {helper && (
<div> <div>
<SimpleText className="input-helper-text" variant="light"> <SimpleText
className="input-helper-text"
variant={invalid || isInvalid ? "error" : "light"}
>
{helper} {helper}
</SimpleText> </SimpleText>
</div> </div>

View File

@ -14,6 +14,11 @@
flex-direction: column; flex-direction: column;
} }
.input--invalid {
color: rgb(var(--codex-color-error));
border-color: rgb(var(--codex-color-error));
}
.input-label { .input-label {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-weight: 500; font-weight: 500;

View File

@ -54,10 +54,19 @@ export const Disabled: Story = {
}, },
}; };
export const CustomStyle: Story = { export const AutoInvalid: Story = {
args: { args: {
id: "custom", id: "autoinvalid",
label: "Label", label: "Auto invalid",
style: { "--codex-input-border": "1px solid red" }, pattern: "a",
mode: "auto"
},
};
export const IsInvalid: Story = {
args: {
id: "autoinvalid",
label: "Auto invalid",
isInvalid: true
}, },
}; };