Skip to content

Commit

Permalink
#5429 for Accordion
Browse files Browse the repository at this point in the history
  • Loading branch information
yigitfindikli committed Nov 29, 2023
1 parent df233a0 commit e465211
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 79 deletions.
145 changes: 71 additions & 74 deletions components/doc/accordion/accessibilitydoc.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,78 @@
import { DevelopmentSection } from '@/components/doc/common/developmentsection';
import { DocSectionText } from '@/components/doc/common/docsectiontext';

export function AccessibilityDoc() {
return (
<DevelopmentSection>
<DocSectionText id="accessibility" label="Accessibility">
<h3>Screen Reader</h3>
<p>
Accordion header elements have a <i>button</i> role and use <i>aria-controls</i> to define the id of the content section along with <i>aria-expanded</i> for the visibility state. The value to read a header element defaults to the
value of the <i>header</i> property and can be customized by defining an <i>aria-label</i> or <i>aria-labelledby</i> via the <i>headerProps</i> property.
</p>
<p>
The content uses <i>region</i> role, defines an id that matches the <i>aria-controls</i> of the header and <i>aria-labelledby</i> referring to the id of the header.
</p>
<DocSectionText id="accessibility" label="Accessibility">
<h3>Screen Reader</h3>
<p>
Accordion header elements have a <i>button</i> role and use <i>aria-controls</i> to define the id of the content section along with <i>aria-expanded</i> for the visibility state. The value to read a header element defaults to the
value of the <i>header</i> property and can be customized by defining an <i>aria-label</i> or <i>aria-labelledby</i> via the <i>headerProps</i> property.
</p>
<p>
The content uses <i>region</i> role, defines an id that matches the <i>aria-controls</i> of the header and <i>aria-labelledby</i> referring to the id of the header.
</p>

<h3>Header Keyboard Support</h3>
<div className="doc-tablewrapper">
<table className="doc-table">
<thead>
<tr>
<th>Key</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<i>tab</i>
</td>
<td>Moves focus to the next the focusable element in the page tab sequence.</td>
</tr>
<tr>
<td>
<i>shift</i> + <i>tab</i>
</td>
<td>Moves focus to the previous the focusable element in the page tab sequence.</td>
</tr>
<tr>
<td>
<i>enter</i>
</td>
<td>Toggles the visibility of the content.</td>
</tr>
<tr>
<td>
<i>space</i>
</td>
<td>Toggles the visibility of the content.</td>
</tr>
<tr>
<td>
<i>down arrow</i>
</td>
<td>Moves focus to the next header.</td>
</tr>
<tr>
<td>
<i>up arrow</i>
</td>
<td>Moves focus to the previous header.</td>
</tr>
<tr>
<td>
<i>home</i>
</td>
<td>Moves focus to the first header.</td>
</tr>
<tr>
<td>
<i>end</i>
</td>
<td>Moves focus to the last header.</td>
</tr>
</tbody>
</table>
</div>
</DocSectionText>
</DevelopmentSection>
<h3>Header Keyboard Support</h3>
<div className="doc-tablewrapper">
<table className="doc-table">
<thead>
<tr>
<th>Key</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<i>tab</i>
</td>
<td>Moves focus to the next the focusable element in the page tab sequence.</td>
</tr>
<tr>
<td>
<i>shift</i> + <i>tab</i>
</td>
<td>Moves focus to the previous the focusable element in the page tab sequence.</td>
</tr>
<tr>
<td>
<i>enter</i>
</td>
<td>Toggles the visibility of the content.</td>
</tr>
<tr>
<td>
<i>space</i>
</td>
<td>Toggles the visibility of the content.</td>
</tr>
<tr>
<td>
<i>down arrow</i>
</td>
<td>Moves focus to the next header.</td>
</tr>
<tr>
<td>
<i>up arrow</i>
</td>
<td>Moves focus to the previous header.</td>
</tr>
<tr>
<td>
<i>home</i>
</td>
<td>Moves focus to the first header.</td>
</tr>
<tr>
<td>
<i>end</i>
</td>
<td>Moves focus to the last header.</td>
</tr>
</tbody>
</table>
</div>
</DocSectionText>
);
}
97 changes: 92 additions & 5 deletions components/lib/accordion/Accordion.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import * as React from 'react';
import { ariaLabel } from '../api/Api';
import { CSSTransition } from '../csstransition/CSSTransition';
import { useMountEffect } from '../hooks/Hooks';
import { classNames, IconUtils, mergeProps, ObjectUtils, UniqueComponentId } from '../utils/Utils';
import { DomHandler, classNames, IconUtils, mergeProps, ObjectUtils, UniqueComponentId } from '../utils/Utils';
import { AccordionBase, AccordionTabBase } from './AccordionBase';
import { ChevronRightIcon } from '../icons/chevronright';
import { ChevronDownIcon } from '../icons/chevrondown';
Expand Down Expand Up @@ -54,6 +53,10 @@ export const Accordion = React.forwardRef((inProps, ref) => {
const getTabProp = (tab, name) => AccordionTabBase.getCProp(tab, name);

const onTabHeaderClick = (event, tab, index) => {
changeActiveIndex(event, tab, index);
};

const changeActiveIndex = (event, tab, index) => {
if (!getTabProp(tab, 'disabled')) {
const selected = isSelected(index);
let newActiveIndex = null;
Expand Down Expand Up @@ -83,6 +86,89 @@ export const Accordion = React.forwardRef((inProps, ref) => {
event.preventDefault();
};

const onTabHeaderKeyDown = (event, tab, index) => {
switch (event.code) {
case 'ArrowDown':
onTabArrowDownKey(event);
break;

case 'ArrowUp':
onTabArrowUpKey(event);
break;

case 'Home':
onTabHomeKey(event);
break;

case 'End':
onTabEndKey(event);
break;

case 'Enter':
case 'Space':
onTabEnterKey(event, tab, index);
break;

default:
break;
}
};

const onTabArrowDownKey = (event) => {
const nextHeaderAction = findNextHeaderAction(event.target.parentElement.parentElement);
nextHeaderAction ? changeFocusedTab(nextHeaderAction) : onTabHomeKey(event);
event.preventDefault();
};

const onTabArrowUpKey = (event) => {
const prevHeaderAction = findPrevHeaderAction(event.target.parentElement.parentElement);
prevHeaderAction ? changeFocusedTab(prevHeaderAction) : onTabEndKey(event);
event.preventDefault();
};

const onTabHomeKey = (event) => {
const firstHeaderAction = findFirstHeaderAction();
changeFocusedTab(firstHeaderAction);
event.preventDefault();
};

const onTabEndKey = (event) => {
const lastHeaderAction = findLastHeaderAction();
changeFocusedTab(lastHeaderAction);
event.preventDefault();
};

const onTabEnterKey = (event, tab, index) => {
changeActiveIndex(event, tab, index);
event.preventDefault();
};

const findNextHeaderAction = (tabElement, selfCheck = false) => {
const nextTabElement = selfCheck ? tabElement : tabElement.nextElementSibling;
const headerElement = DomHandler.findSingle(nextTabElement, '[data-pc-section="header"]');
return headerElement ? (DomHandler.getAttribute(headerElement, 'data-p-disabled') ? findNextHeaderAction(headerElement.parentElement) : DomHandler.findSingle(headerElement, '[data-pc-section="headeraction"]')) : null;
};

const findPrevHeaderAction = (tabElement, selfCheck = false) => {
const prevTabElement = selfCheck ? tabElement : tabElement.previousElementSibling;
const headerElement = DomHandler.findSingle(prevTabElement, '[data-pc-section="header"]');
return headerElement ? (DomHandler.getAttribute(headerElement, 'data-p-disabled') ? findPrevHeaderAction(headerElement.parentElement) : DomHandler.findSingle(headerElement, '[data-pc-section="headeraction"]')) : null;
};

const findFirstHeaderAction = () => {
return findNextHeaderAction(elementRef.current.firstElementChild, true);
};

const findLastHeaderAction = () => {
return findPrevHeaderAction(elementRef.current.lastElementChild, true);
};

const changeFocusedTab = (element) => {
if (element) {
DomHandler.focus(element);
}
};

const isSelected = (index) => {
return props.multiple ? activeIndex && activeIndex.some((i) => i === index) : activeIndex === index;
};
Expand Down Expand Up @@ -116,13 +202,13 @@ export const Accordion = React.forwardRef((inProps, ref) => {
const header = getTabProp(tab, 'headerTemplate') ? ObjectUtils.getJSXElement(getTabProp(tab, 'headerTemplate'), AccordionTabBase.getCProps(tab)) : <span {...headerTitleProps}>{getTabProp(tab, 'header')}</span>;
const headerIconProps = mergeProps(
{
'aria-hidden': 'true',
className: cx('tab.headericon')
},
getTabPT(tab, 'headericon', index)
);
const icon = selected ? props.collapseIcon || <ChevronDownIcon {...headerIconProps} /> : props.expandIcon || <ChevronRightIcon {...headerIconProps} />;
const toggleIcon = IconUtils.getJSXIcon(icon, { ...headerIconProps }, { props, selected });
const label = selected ? ariaLabel('collapseLabel') : ariaLabel('expandLabel');
const headerProps = mergeProps(
{
className: classNames(getTabProp(tab, 'headerClassName'), getTabProp(tab, 'className'), cx('tab.header', { selected, getTabProp, tab })),
Expand All @@ -138,10 +224,11 @@ export const Accordion = React.forwardRef((inProps, ref) => {
id: headerId,
href: '#' + ariaControls,
className: cx('tab.headeraction'),
role: 'tab',
role: 'button',
tabIndex,
onClick: (e) => onTabHeaderClick(e, tab, index),
'aria-label': label,
onKeyDown: (e) => onTabHeaderKeyDown(e, tab, index),
'aria-disabled': getTabProp(tab, 'disabled'),
'aria-controls': ariaControls,
'aria-expanded': selected
},
Expand Down

0 comments on commit e465211

Please sign in to comment.