Files
community-rule/app/(app)/create/components/ModalTextAreaField.tsx
T
adilalloandCursor 20ea4a2f30 Give MultiSelect chips accessible names and start creation fields empty.
Catalog chips now announce Select or Deselect so they are named for assistive tech. Method and value modals open blank; Generate draft inserts the catalog copy that used to be prefilled, and Customize still keeps the section headings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-29 15:59:00 -06:00

129 lines
4.0 KiB
TypeScript

"use client";
/**
* Shared labelled text area for create-flow modal sections.
* Figma: "Step Modal" Text Area Section (23746:49780) when `generateDraftText`
* is set — tall compose field + Generate draft control. Otherwise the
* embedded Control / Text Area pattern.
*/
import { memo, useId } from "react";
import TextArea from "../../../components/controls/TextArea";
import InputLabel from "../../../components/type/InputLabel";
export interface ModalTextAreaFieldProps {
/** Label rendered above the text area. */
label: string;
/** Show the help "?" icon next to the label. Off until tooltip behavior ships. */
helpIcon?: boolean;
/** Current text value. */
value: string;
/** Fired on every change with the new value (no event). */
onChange: (_value: string) => void;
/** Optional rows for the underlying `<textarea>` (default 4). */
rows?: number;
/** Optional placeholder. */
placeholder?: string;
/** Disable the field. */
disabled?: boolean;
/**
* Catalog/preset copy inserted by Generate draft. When a non-empty string
* is passed, the Figma compose field + ghost control are shown.
*/
generateDraftText?: string;
/** Visible label for the Generate draft control. */
generateDraftLabel?: string;
/** Hint beside Generate draft. */
generateDraftHint?: string;
className?: string;
}
function PlusGlyph() {
return (
<svg
aria-hidden
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="shrink-0 text-[var(--color-content-default-brand-primary,#fefcc9)]"
>
<path
d="M7 3V11M3 7H11"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
function ModalTextAreaFieldComponent({
label,
helpIcon = false,
value,
onChange,
rows = 4,
placeholder,
disabled = false,
generateDraftText,
generateDraftLabel,
generateDraftHint,
className = "",
}: ModalTextAreaFieldProps) {
const labelId = useId();
const draftSource = generateDraftText?.trim() ?? "";
const showGenerateDraft =
draftSource.length > 0 && Boolean(generateDraftLabel);
const compose = showGenerateDraft;
return (
<div
className={`flex flex-col ${compose ? "gap-[12px]" : "gap-2"} ${className}`.trim()}
>
<div id={labelId}>
<InputLabel
label={label}
helpIcon={helpIcon}
size="s"
palette="default"
/>
</div>
<TextArea
formHeader={false}
value={value}
onChange={(e) => onChange(e.target.value)}
size="large"
rows={compose ? 12 : rows}
appearance={compose ? "compose" : "embedded"}
placeholder={placeholder}
disabled={disabled}
aria-labelledby={labelId}
/>
{showGenerateDraft && !disabled ? (
<div className="flex items-center">
<button
type="button"
onClick={() => onChange(draftSource)}
className="flex shrink-0 items-center justify-center gap-[var(--measures-spacing-050,2px)] overflow-clip rounded-[var(--measures-radius-full,9999px)] px-[var(--space-250,10px)] py-[var(--measures-spacing-200,8px)] text-x-small-label text-[color:var(--color-content-default-primary,white)] hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-default-primary)] focus-visible:ring-offset-2 focus-visible:ring-offset-transparent"
>
<PlusGlyph />
{generateDraftLabel}
</button>
{generateDraftHint ? (
<p className="text-[length:var(--sizing-300,12px)] leading-normal text-[color:var(--color-content-default-tertiary,#b4b4b4)]">
{generateDraftHint}
</p>
) : null}
</div>
) : null}
</div>
);
}
ModalTextAreaFieldComponent.displayName = "ModalTextAreaField";
export default memo(ModalTextAreaFieldComponent);