Files
element-call/src/input/Input.tsx

174 lines
3.8 KiB
TypeScript
Raw Normal View History

2022-05-04 17:09:48 +01:00
/*
Copyright 2022-2024 New Vector Ltd.
2022-05-04 17:09:48 +01:00
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
2022-05-04 17:09:48 +01:00
*/
import {
ChangeEvent,
FC,
ForwardedRef,
forwardRef,
ReactNode,
useId,
} from "react";
2021-08-19 17:49:45 -07:00
import classNames from "classnames";
2022-08-09 11:44:46 +02:00
2021-08-19 17:49:45 -07:00
import styles from "./Input.module.css";
2023-09-27 19:06:10 -04:00
import CheckIcon from "../icons/Check.svg?react";
2022-10-10 09:19:10 -04:00
import { TranslatedError } from "../TranslatedError";
2021-08-19 17:49:45 -07:00
2022-08-09 11:44:46 +02:00
interface FieldRowProps {
children: ReactNode;
rightAlign?: boolean;
className?: string;
}
export function FieldRow({
children,
rightAlign,
className,
}: FieldRowProps): JSX.Element {
2021-08-19 17:49:45 -07:00
return (
<div
className={classNames(
styles.fieldRow,
{ [styles.rightAlign]: rightAlign },
2023-10-11 10:42:04 -04:00
className,
2021-08-19 17:49:45 -07:00
)}
>
{children}
</div>
);
}
2022-08-09 11:44:46 +02:00
interface FieldProps {
children: ReactNode;
className?: string;
}
function Field({ children, className }: FieldProps): JSX.Element {
2021-08-19 17:49:45 -07:00
return <div className={classNames(styles.field, className)}>{children}</div>;
}
2022-08-09 11:44:46 +02:00
interface InputFieldProps {
label?: string;
2022-08-09 11:44:46 +02:00
type: string;
prefix?: string;
suffix?: string;
id?: string;
checked?: boolean;
className?: string;
description?: string | ReactNode;
2022-08-09 11:44:46 +02:00
disabled?: boolean;
required?: boolean;
// this is a hack. Those variables should be part of `HTMLAttributes<HTMLInputElement> | HTMLAttributes<HTMLTextAreaElement>`
// but extending from this union type does not work
name?: string;
autoComplete?: string;
autoCorrect?: string;
autoCapitalize?: string;
value?: string;
2023-05-22 14:33:20 -04:00
defaultValue?: string;
2022-08-09 11:44:46 +02:00
placeholder?: string;
defaultChecked?: boolean;
min?: number;
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
2022-08-09 11:44:46 +02:00
}
export const InputField = forwardRef<
HTMLInputElement | HTMLTextAreaElement,
InputFieldProps
>(
(
2022-06-06 11:19:40 -04:00
{
id,
label,
className,
type,
checked,
prefix,
suffix,
description,
disabled,
min,
2022-06-06 11:19:40 -04:00
...rest
},
2023-10-11 10:42:04 -04:00
ref,
) => {
const descriptionId = useId();
2021-08-19 17:49:45 -07:00
return (
2021-09-03 15:45:07 -07:00
<Field
className={classNames(
type === "checkbox" ? styles.checkboxField : styles.inputField,
{
[styles.prefix]: !!prefix,
[styles.disabled]: disabled,
},
2023-10-11 10:42:04 -04:00
className,
2021-09-03 15:45:07 -07:00
)}
>
2021-12-15 10:54:01 -08:00
{prefix && <span>{prefix}</span>}
2022-02-23 16:07:14 -08:00
{type === "textarea" ? (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
2022-02-23 16:07:14 -08:00
<textarea
id={id}
ref={ref as ForwardedRef<HTMLTextAreaElement>}
2022-02-23 16:07:14 -08:00
disabled={disabled}
aria-describedby={descriptionId}
2022-08-09 11:44:46 +02:00
{...rest}
2022-02-23 16:07:14 -08:00
/>
) : (
<input
id={id}
ref={ref as ForwardedRef<HTMLInputElement>}
2022-02-23 16:07:14 -08:00
type={type}
checked={checked}
disabled={disabled}
aria-describedby={descriptionId}
min={min}
2022-08-09 11:44:46 +02:00
{...rest}
2022-02-23 16:07:14 -08:00
/>
)}
2021-09-03 15:45:07 -07:00
<label htmlFor={id}>
{type === "checkbox" && (
<div className={styles.checkbox}>
<CheckIcon />
</div>
)}
{label}
</label>
2021-12-15 10:54:01 -08:00
{suffix && <span>{suffix}</span>}
2022-11-04 18:10:53 +00:00
{description && (
<p
id={descriptionId}
className={
label
? styles.description
: classNames(styles.description, styles.noLabel)
}
>
2022-11-04 18:10:53 +00:00
{description}
</p>
)}
2021-08-19 17:49:45 -07:00
</Field>
);
2023-10-11 10:42:04 -04:00
},
2021-08-19 17:49:45 -07:00
);
InputField.displayName = "InputField";
2022-10-10 09:19:10 -04:00
interface ErrorMessageProps {
error: Error;
2021-08-20 16:23:12 -07:00
}
2022-10-10 09:19:10 -04:00
export const ErrorMessage: FC<ErrorMessageProps> = ({ error }) => (
<p className={styles.errorMessage}>
{error instanceof TranslatedError ? error.translatedMessage : error.message}
</p>
);