Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 95 additions & 93 deletions src/collection-preferences/content-display/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,105 +69,107 @@ export default function ContentDisplayPreference({
};

return (
<div className={styles[componentPrefix]} {...getAnalyticsInnerContextAttribute('contentDisplay')}>
<h3 className={getClassName('title')} id={titleId}>
{i18n('contentDisplayPreference.title', title)}
</h3>
<p className={getClassName('description')} id={descriptionId}>
{i18n('contentDisplayPreference.description', description)}
</p>
<div role="group" aria-labelledby={titleId} aria-describedby={descriptionId}>
<div className={styles[componentPrefix]} {...getAnalyticsInnerContextAttribute('contentDisplay')}>
<h3 className={getClassName('title')} id={titleId}>
{i18n('contentDisplayPreference.title', title)}
</h3>
<p className={getClassName('description')} id={descriptionId}>
{i18n('contentDisplayPreference.description', description)}
</p>

{/* Filter input */}
{enableColumnFiltering && (
<div className={getClassName('text-filter')}>
<InternalTextFilter
filteringText={columnFilteringText}
filteringPlaceholder={i18n(
'contentDisplayPreference.i18nStrings.columnFilteringPlaceholder',
i18nStrings?.columnFilteringPlaceholder
)}
filteringAriaLabel={i18n(
'contentDisplayPreference.i18nStrings.columnFilteringAriaLabel',
i18nStrings?.columnFilteringAriaLabel
)}
filteringClearAriaLabel={i18n(
'contentDisplayPreference.i18nStrings.columnFilteringClearFilterText',
i18nStrings?.columnFilteringClearFilterText
)}
onChange={({ detail }) => setColumnFilteringText(detail.filteringText)}
countText={i18n(
'contentDisplayPreference.i18nStrings.columnFilteringCountText',
i18nStrings?.columnFilteringCountText
? i18nStrings?.columnFilteringCountText(sortedAndFilteredOptions.length)
: undefined,
format => format({ count: sortedAndFilteredOptions.length })
)}
/>
</div>
)}

{/* No match */}
{sortedAndFilteredOptions.length === 0 && (
<div className={getClassName('no-match')}>
<InternalSpaceBetween size="s" alignItems="center">
<InternalBox margin={{ top: 'm' }}>
{i18n(
'contentDisplayPreference.i18nStrings.columnFilteringNoMatchText',
i18nStrings?.columnFilteringNoMatchText
{/* Filter input */}
{enableColumnFiltering && (
<div className={getClassName('text-filter')}>
<InternalTextFilter
filteringText={columnFilteringText}
filteringPlaceholder={i18n(
'contentDisplayPreference.i18nStrings.columnFilteringPlaceholder',
i18nStrings?.columnFilteringPlaceholder
)}
filteringAriaLabel={i18n(
'contentDisplayPreference.i18nStrings.columnFilteringAriaLabel',
i18nStrings?.columnFilteringAriaLabel
)}
</InternalBox>
<InternalButton onClick={() => setColumnFilteringText('')}>
{i18n(
filteringClearAriaLabel={i18n(
'contentDisplayPreference.i18nStrings.columnFilteringClearFilterText',
i18nStrings?.columnFilteringClearFilterText
)}
</InternalButton>
</InternalSpaceBetween>
</div>
)}
onChange={({ detail }) => setColumnFilteringText(detail.filteringText)}
countText={i18n(
'contentDisplayPreference.i18nStrings.columnFilteringCountText',
i18nStrings?.columnFilteringCountText
? i18nStrings?.columnFilteringCountText(sortedAndFilteredOptions.length)
: undefined,
format => format({ count: sortedAndFilteredOptions.length })
)}
/>
</div>
)}

{/* No match */}
{sortedAndFilteredOptions.length === 0 && (
<div className={getClassName('no-match')}>
<InternalSpaceBetween size="s" alignItems="center">
<InternalBox margin={{ top: 'm' }}>
{i18n(
'contentDisplayPreference.i18nStrings.columnFilteringNoMatchText',
i18nStrings?.columnFilteringNoMatchText
)}
</InternalBox>
<InternalButton onClick={() => setColumnFilteringText('')}>
{i18n(
'contentDisplayPreference.i18nStrings.columnFilteringClearFilterText',
i18nStrings?.columnFilteringClearFilterText
)}
</InternalButton>
</InternalSpaceBetween>
</div>
)}

<InternalList
items={sortedAndFilteredOptions}
renderItem={item => ({
id: item.id,
content: <ContentDisplayOption option={item} onToggle={onToggle} />,
announcementLabel: item.label,
})}
disableItemPaddings={true}
sortable={true}
sortDisabled={columnFilteringText.trim().length > 0}
onSortingChange={({ detail: { items } }) => {
onChange(items);
}}
ariaDescribedby={descriptionId}
ariaLabelledby={titleId}
i18nStrings={{
liveAnnouncementDndStarted: i18n(
'contentDisplayPreference.liveAnnouncementDndStarted',
liveAnnouncementDndStarted,
formatDndStarted
),
liveAnnouncementDndItemReordered: i18n(
'contentDisplayPreference.liveAnnouncementDndItemReordered',
liveAnnouncementDndItemReordered,
formatDndItemReordered
),
liveAnnouncementDndItemCommitted: i18n(
'contentDisplayPreference.liveAnnouncementDndItemCommitted',
liveAnnouncementDndItemCommitted,
formatDndItemCommitted
),
liveAnnouncementDndDiscarded: i18n(
'contentDisplayPreference.liveAnnouncementDndDiscarded',
liveAnnouncementDndDiscarded
),
dragHandleAriaLabel: i18n('contentDisplayPreference.dragHandleAriaLabel', dragHandleAriaLabel),
dragHandleAriaDescription: i18n(
'contentDisplayPreference.dragHandleAriaDescription',
dragHandleAriaDescription
),
}}
/>
<InternalList
items={sortedAndFilteredOptions}
renderItem={item => ({
id: item.id,
content: <ContentDisplayOption option={item} onToggle={onToggle} />,
announcementLabel: item.label,
})}
disableItemPaddings={true}
sortable={true}
sortDisabled={columnFilteringText.trim().length > 0}
onSortingChange={({ detail: { items } }) => {
onChange(items);
}}
ariaDescribedby={descriptionId}
ariaLabelledby={titleId}
i18nStrings={{
liveAnnouncementDndStarted: i18n(
'contentDisplayPreference.liveAnnouncementDndStarted',
liveAnnouncementDndStarted,
formatDndStarted
),
liveAnnouncementDndItemReordered: i18n(
'contentDisplayPreference.liveAnnouncementDndItemReordered',
liveAnnouncementDndItemReordered,
formatDndItemReordered
),
liveAnnouncementDndItemCommitted: i18n(
'contentDisplayPreference.liveAnnouncementDndItemCommitted',
liveAnnouncementDndItemCommitted,
formatDndItemCommitted
),
liveAnnouncementDndDiscarded: i18n(
'contentDisplayPreference.liveAnnouncementDndDiscarded',
liveAnnouncementDndDiscarded
),
dragHandleAriaLabel: i18n('contentDisplayPreference.dragHandleAriaLabel', dragHandleAriaLabel),
dragHandleAriaDescription: i18n(
'contentDisplayPreference.dragHandleAriaDescription',
dragHandleAriaDescription
),
}}
/>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ test('assigns aria-labelledby attribute', () => {
expect(document.querySelector(`.${styles.handle}`)).toHaveAccessibleName('custom label');
});

test('has role="application" if no ariaValue is provided', () => {
test('has role="button" if no ariaValue is provided', () => {
render(<DragHandleButton ariaLabel="drag handle" />);

expect(screen.getByRole('application')).toHaveAccessibleName('drag handle');
expect(screen.getByRole('button')).toHaveAccessibleName('drag handle');
});

test('has role="slider" and aria-value attributes when ariaValue is set', () => {
Expand Down
3 changes: 2 additions & 1 deletion src/internal/components/drag-handle/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const DragHandleButton = forwardRef(
// when it is being dragged.
<div
ref={useMergeRefs(ref, dragHandleRefObject)}
role={ariaValue ? 'slider' : 'application'}
role={ariaValue ? 'slider' : 'button'}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the result of some accessibility feedback? If I recall correctly, role="application" is better for screen readers like NVDA/JAWS because it captures arrow key presses more reliably. Have you tested if wrapping the whole thing with role="application" solves this issue?

tabIndex={0}
className={clsx(
className,
Expand All @@ -73,6 +73,7 @@ const DragHandleButton = forwardRef(
aria-labelledby={ariaLabelledBy}
aria-describedby={ariaDescribedby}
aria-disabled={disabled}
aria-pressed={ariaValue ? undefined : active}
aria-valuemax={ariaValue?.valueMax}
aria-valuemin={ariaValue?.valueMin}
aria-valuenow={ariaValue?.valueNow}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ test('renders all items with correct attributes', () => {
isDragGhost: false,
isSortingActive: false,
dragHandleProps: {
active: false,
ariaLabel: `Drag handle ${items[i].label}`,
ariaDescribedby: expect.any(String),
disabled: false,
Expand Down
1 change: 1 addition & 0 deletions src/internal/components/sortable-area/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ function DraggableItem<Item>({
isDragGhost: false,
dragHandleProps: {
...dragHandleListeners,
active: isDragging,
ariaLabel: joinStrings(dragHandleAriaLabel, itemDefinition.label(item)) ?? '',
ariaDescribedby: attributes['aria-describedby'],
disabled: attributes['aria-disabled'],
Expand Down
Loading