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>
This commit is contained in:
adilalloandCursor committed 2026-09-29 15:59:00 -06:00
1 parent fd9f82ea19
commit 20ea4a2f30
35 files changed
+498 -156

No files matched your search

@@ -93,7 +93,7 @@ const ChipContainer = memo<ChipProps>(
onInputChange={isCustom ? setInputValue : undefined}
onInputKeyDown={isCustom ? handleKeyDown : undefined}
inputRef={isCustom ? inputRef : undefined}
ariaLabel={ariaLabel}
ariaLabel={ariaLabel ?? (isCustom ? undefined : label)}
confirmAriaLabel={t("chipConfirm")}
typeToAddPlaceholder={t("chipTypeToAdd")}
closeAriaLabel={t("chipClose")}
+1 -1
View File
@@ -156,7 +156,7 @@ function ChipView({
};
const sharedA11y = {
"aria-label": ariaLabel,
...(ariaLabel ? { "aria-label": ariaLabel } : {}),
...(isToggle ? { "aria-pressed": isSelected } : {}),
};
@@ -46,6 +46,8 @@ const MultiSelectContainer = memo<MultiSelectProps>(
addButtonAriaLabel={
addButtonText || t("multiSelectAddFallback")
}
chipSelectAriaLabel={t("chipSelect")}
chipDeselectAriaLabel={t("chipDeselect")}
formHeader={formHeader}
onCustomChipConfirm={onCustomChipConfirm}
onCustomChipClose={onCustomChipClose}
@@ -85,6 +85,8 @@ export interface MultiSelectViewProps {
addButton: boolean;
addButtonText?: string;
addButtonAriaLabel: string;
chipSelectAriaLabel: string;
chipDeselectAriaLabel: string;
formHeader: boolean;
onCustomChipConfirm?: (chipId: string, value: string) => void;
onCustomChipClose?: (chipId: string) => void;
@@ -16,6 +16,8 @@ function MultiSelectView({
addButton,
addButtonText,
addButtonAriaLabel,
chipSelectAriaLabel,
chipDeselectAriaLabel,
formHeader = true,
onCustomChipConfirm,
onCustomChipClose,
@@ -88,6 +90,14 @@ function MultiSelectView({
palette={palette}
size={chipSize}
disabled={chipDisabled}
ariaLabel={
isCustom || !option.label
? undefined
: (isSelected
? chipDeselectAriaLabel
: chipSelectAriaLabel
).replace("{label}", option.label)
}
onClick={() => {
if (!isCustom && !chipDisabled && onChipClick) {
onChipClick(option.id);
@@ -86,6 +86,21 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
textarea: string;
label: string;
} => {
if (appearance === "compose") {
if (disabled) {
return {
textarea:
"border-0 bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-tertiary,#b4b4b4)] cursor-not-allowed opacity-60 min-h-[356px] h-[356px] px-[var(--measures-spacing-300,12px)] py-[var(--space-250,10px)]",
label: "text-[var(--color-content-default-secondary)]",
};
}
return {
textarea:
"border-0 bg-[var(--color-surface-default-primary)] text-[var(--color-content-default-primary)] placeholder:text-[var(--color-content-default-tertiary,#b4b4b4)] min-h-[356px] h-[356px] px-[var(--measures-spacing-300,12px)] py-[var(--space-250,10px)] focus:outline-none focus:ring-0",
label: "text-[var(--color-content-default-secondary)]",
};
}
if (appearance === "embedded") {
if (disabled) {
return {
@@ -161,8 +176,8 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
const textareaClasses = `
scrollbar-design w-full transition-all duration-200 ease-in-out resize-none
${appearance === "embedded" ? "rounded-[var(--radius-300,12px)]" : "border"}
${currentSize.textarea}
${appearance === "embedded" || appearance === "compose" ? "rounded-[var(--radius-300,12px)]" : "border"}
${appearance === "compose" ? "text-medium-paragraph" : currentSize.textarea}
${stateStyles.textarea}
${className}
`.trim();
@@ -1,9 +1,12 @@
import type { InputStateValue } from "../../../../lib/propNormalization";
import type {
InputStateValue,
TextAreaAppearanceValue,
} from "../../../../lib/propNormalization";
export type TextAreaSizeValue = "small" | "medium" | "large";
export type TextAreaLabelVariantValue = "default" | "horizontal";
export type TextAreaAppearanceValue = "default" | "embedded";
export type { TextAreaAppearanceValue };
export interface TextAreaProps extends Omit<
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
@@ -49,7 +52,8 @@ export interface TextAreaProps extends Omit<
/**
* Visual appearance. "embedded" matches Create modal sections (Figma 20736-12668):
* borderless, darker grey background, tertiary text in default (primary on
* focus). "default" is the standard bordered input.
* focus). "compose" is the tall empty creation field (Figma 23746:49780).
* "default" is the standard bordered input.
* @default "default"
*/
appearance?: TextAreaAppearanceValue;
@@ -79,7 +83,7 @@ export interface TextAreaViewProps {
textHint?: boolean | string;
formHeader?: boolean;
showHelpIcon?: boolean;
appearance?: "default" | "embedded";
appearance?: TextAreaAppearanceValue;
helpIconAlt: string;
hintDefault: string;
}