) */
id: string;
/** id du message à passer en aria-describedby (peut être undefined) */
describedBy: string | undefined;
/** L'état d'invalidité, à passer en aria-invalid */
invalid: boolean | undefined;
};
type FieldProps = {
label?: ReactNode;
hint?: ReactNode;
error?: ReactNode;
success?: ReactNode;
required?: boolean;
className?: string;
id?: string;
children: (arg: FieldRenderArg) => ReactNode;
};
export function Field({
label,
hint,
error,
success,
required,
className,
id: idProp,
children,
}: FieldProps) {
const auto = useId();
const id = idProp ?? auto;
const messageId = `${id}-message`;
const hasMessage = Boolean(error || success || hint);
const describedBy = hasMessage ? messageId : undefined;
const state = error ? "error" : success ? "success" : undefined;
return (
{label && (
{label}
)}
{children({ id, describedBy, invalid: error ? true : undefined })}
{error ? (
{error}
) : success ? (
{success}
) : hint ? (
{hint}
) : null}
);
}
/* — Input ————————————————————————————— */
export type InputProps = Omit, "size"> & {
label?: ReactNode;
hint?: ReactNode;
error?: ReactNode;
success?: ReactNode;
size?: "sm" | "md" | "lg";
prefixIcon?: IconName;
suffixIcon?: IconName;
};
export const Input = forwardRef(function Input(
{ label, hint, error, success, size = "md", prefixIcon, suffixIcon, required, className, ...rest },
ref,
) {
const cls = cx(
"mmg-input",
size !== "md" && `mmg-input--${size}`,
className,
);
return (
{({ id, describedBy, invalid }) =>
prefixIcon || suffixIcon ? (
{prefixIcon && (
)}
{suffixIcon && (
)}
) : (
)
}
);
});
/* — Textarea ————————————————————————————— */
export type TextareaProps = TextareaHTMLAttributes & {
label?: ReactNode;
hint?: ReactNode;
error?: ReactNode;
success?: ReactNode;
fieldSize?: "sm" | "md" | "lg";
};
export const Textarea = forwardRef(
function Textarea(
{ label, hint, error, success, fieldSize = "md", required, className, ...rest },
ref,
) {
const cls = cx(
"mmg-textarea",
fieldSize !== "md" && `mmg-textarea--${fieldSize}`,
className,
);
return (
{({ id, describedBy, invalid }) => (
)}
);
},
);
/* — Select ————————————————————————————— */
export type SelectOption = { value: string; label: string; disabled?: boolean };
export type SelectProps = Omit, "size"> & {
label?: ReactNode;
hint?: ReactNode;
error?: ReactNode;
success?: ReactNode;
size?: "sm" | "md" | "lg";
options: SelectOption[];
placeholder?: string;
};
export const Select = forwardRef(function Select(
{ label, hint, error, success, size = "md", options, placeholder, required, className, ...rest },
ref,
) {
const cls = cx("mmg-select", size !== "md" && `mmg-select--${size}`, className);
return (
{({ id, describedBy, invalid }) => (
{placeholder && {placeholder} }
{options.map((o) => (
{o.label}
))}
)}
);
});
/* — Checkbox / Radio ————————————————————————————— */
type CheckProps = InputHTMLAttributes & {
label: ReactNode;
};
export const Checkbox = forwardRef(function Checkbox(
{ label, className, ...rest },
ref,
) {
return (
{label}
);
});
export const Radio = forwardRef(function Radio(
{ label, className, ...rest },
ref,
) {
return (
{label}
);
});
/* — Switch ————————————————————————————— */
export const Switch = forwardRef(function Switch(
{ label, className, ...rest },
ref,
) {
return (
{label}
);
});