057d41e1c2
Build & Push / Pipeline Tests (push) Failing after 1m5s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 52s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Carplace Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped
98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
import { BidiText } from '@/components/foundation/BidiText';
|
|
import { classNames } from '@/lib/components/classNames';
|
|
import {
|
|
forwardRef,
|
|
type InputHTMLAttributes,
|
|
type SelectHTMLAttributes,
|
|
type TextareaHTMLAttributes,
|
|
} from 'react';
|
|
import styles from './TextInput.module.css';
|
|
|
|
export type TextInputType = 'text' | 'email' | 'tel' | 'url' | 'search';
|
|
|
|
interface TextInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
|
|
type?: TextInputType;
|
|
invalid?: boolean;
|
|
describedBy?: string | undefined;
|
|
bidiValue?: boolean;
|
|
}
|
|
|
|
export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(function TextInput(
|
|
{ type = 'text', invalid = false, describedBy, bidiValue = false, className, ...props },
|
|
ref,
|
|
) {
|
|
const dir = bidiValue || type === 'email' || type === 'tel' || type === 'url' ? 'ltr' : undefined;
|
|
const maxLength = props.maxLength ?? (type === 'email' ? 254 : type === 'tel' ? 20 : undefined);
|
|
const pattern = props.pattern ?? (type === 'tel' ? '^(?:(?:\\+|00)212|0)\\s?[5-7](?:\\s?\\d){8}$' : undefined);
|
|
return (
|
|
<input
|
|
ref={ref}
|
|
type={type}
|
|
dir={dir}
|
|
maxLength={maxLength}
|
|
pattern={pattern}
|
|
className={classNames(styles.control, className)}
|
|
aria-invalid={invalid || undefined}
|
|
aria-describedby={describedBy}
|
|
{...props}
|
|
/>
|
|
);
|
|
});
|
|
|
|
interface TextAreaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
invalid?: boolean;
|
|
describedBy?: string | undefined;
|
|
characterCount?: { current: number; max: number; label: string };
|
|
}
|
|
|
|
export function TextArea({
|
|
invalid = false,
|
|
describedBy,
|
|
characterCount,
|
|
className,
|
|
...props
|
|
}: TextAreaProps) {
|
|
const countId = characterCount && props.id ? `${props.id}-count` : undefined;
|
|
const ids = [describedBy, countId].filter(Boolean).join(' ') || undefined;
|
|
return (
|
|
<div className={styles.textareaGroup}>
|
|
<textarea
|
|
className={classNames(styles.control, styles.textarea, className)}
|
|
aria-invalid={invalid || undefined}
|
|
aria-describedby={ids}
|
|
{...props}
|
|
/>
|
|
{characterCount ? (
|
|
<p id={countId} className={styles.count} aria-live="polite">
|
|
<BidiText>{`${characterCount.current}/${characterCount.max}`}</BidiText>{' '}
|
|
{characterCount.label}
|
|
</p>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
|
|
invalid?: boolean;
|
|
describedBy?: string | undefined;
|
|
}
|
|
|
|
export function Select({
|
|
invalid = false,
|
|
describedBy,
|
|
className,
|
|
children,
|
|
...props
|
|
}: SelectProps) {
|
|
return (
|
|
<select
|
|
className={classNames(styles.control, styles.select, className)}
|
|
aria-invalid={invalid || undefined}
|
|
aria-describedby={describedBy}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</select>
|
|
);
|
|
}
|