"use client";
import * as stylex from "@stylexjs/stylex";
import { type ComponentPropsWithoutRef, useId } from "react";
import { colors, fonts, radius, space } from "./tokens.stylex";
export type TextFieldProps = Omit<
ComponentPropsWithoutRef<"input">,
"className" | "style" | "size"
> & {
label: string;
hint?: string;
error?: string;
};
export function TextField({
label,
hint,
error,
id,
"aria-describedby": describedBy,
...native
}: TextFieldProps) {
const generatedId = useId();
const fieldId = id ?? generatedId;
const note = error ?? hint;
const description =
[describedBy, note ? `${fieldId}-note` : undefined].filter(Boolean).join(" ") || undefined;
return (
{note ? (
{note}
) : null}
);
}
const styles = stylex.create({
field: {
display: "grid",
gap: space.s,
width: "100%",
minWidth: 0,
fontFamily: fonts.body,
textAlign: "start",
},
label: { fontSize: 14, fontWeight: 700, color: colors.text },
input: {
minWidth: 0,
width: "100%",
minHeight: 44,
padding: space.m,
fontFamily: fonts.body,
fontSize: 16,
lineHeight: 1.4,
backgroundColor: colors.surface,
boxShadow: "none",
color: colors.text,
borderRadius: radius.s,
borderWidth: 1,
borderStyle: "solid",
borderColor: { default: colors.border, ":focus-visible": colors.focus },
// The outline sits on top of the border so focus reads as one line, not a ring around a ring.
outline: { default: "none", ":focus-visible": `2px solid ${colors.focus}` },
outlineOffset: -1,
opacity: { default: 1, ":disabled": 0.5 },
},
invalid: { borderColor: colors.danger, outlineColor: colors.danger },
note: { fontSize: 13, color: colors.textMuted, lineHeight: 1.4 },
error: { color: colors.danger },
});