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:
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")}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user