Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 72x 72x 2291x 2291x 72x | /**
* Textarea — the Job Book's ruled multi-line field.
*
* `.textarea` in `.context/mockups/_base.css`: the same rule, ground and
* radius as `.input`, a taller minimum, and a vertical resize handle.
*
* `CharCountTextarea` composes this (it is this control plus a counter), so
* the field chrome lives in exactly one place.
*
* Error *messages* are not this component's job: wrap it in a `Field` and add
* an `ErrorText`, which sets `aria-invalid` here automatically.
*/
import { cva, type VariantProps } from "class-variance-authority";
import { forwardRef, type TextareaHTMLAttributes } from "react";
import { cn } from "../../lib/utils";
import { useFieldControl } from "./field";
const textareaVariants = cva(
"block min-h-21 w-full rounded-md border bg-background-base px-2.5 py-2 text-sm leading-relaxed text-foreground-default transition-colors placeholder:text-foreground-subtle focus-visible:border-foreground-default focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-foreground-default disabled:cursor-not-allowed disabled:opacity-50",
{
variants: {
resize: {
vertical: "resize-y",
none: "resize-none",
},
invalid: {
// Colour never travels alone: the ErrorText under the control is the
// message, this is only the ink on the rule.
true: "border-error ring-1 ring-error",
// The portal's fields resolve their stroke through --border-strong,
// 3:1 against both grounds (WCAG 1.4.11, UI component boundary).
// --border-strong is 1.67:1 light / 1.91:1 dark and is NOT a legal
// field stroke; it was a placeholder while the Earthy override
// still shadowed the package tokens.
false: "border-input",
},
},
defaultVariants: {
resize: "vertical",
invalid: false,
},
},
);
interface TextareaProps
extends TextareaHTMLAttributes<HTMLTextAreaElement>,
Omit<VariantProps<typeof textareaVariants>, "invalid"> {
/** Marks the control invalid. A `Field` with an `ErrorText` sets this too. */
invalid?: boolean;
}
const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
(
{
className,
resize,
invalid,
id,
required,
"aria-describedby": describedBy,
...props
},
ref,
) => {
const field = useFieldControl({ id, describedBy, invalid, required });
return (
<textarea
ref={ref}
id={field.id}
required={field.required}
aria-describedby={field["aria-describedby"]}
aria-invalid={field["aria-invalid"]}
className={cn(
textareaVariants({ resize, invalid: field.invalid }),
className,
)}
{...props}
/>
);
},
);
Textarea.displayName = "Textarea";
export type { TextareaProps };
export { Textarea, textareaVariants };
|