Skip to content

Commit

Permalink
fix(react-card): infer a11y id from immediate header element (#28266)
Browse files Browse the repository at this point in the history
Co-authored-by: Martin Hochel <hochelmartin@gmail.com>
  • Loading branch information
marcosmoura and Hotell authored Jun 21, 2023
1 parent 2f0c320 commit 1a34d5c
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: infer a11y id from immediate header element",
"packageName": "@fluentui/react-card",
"email": "marcosvmmoura@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,29 @@ const CardSample = (props: CardProps) => (
</>
);

const CardWithCustomHeader = ({
customHeaderId = 'custom-header-id',
...props
}: CardProps & { customHeaderId: string }) => (
<>
<p tabIndex={0} id="before">
Before
</p>

<Card id="card" {...props}>
<CardHeader
image={<img src={resolveAsset('powerpoint_logo.svg')} alt="Microsoft PowerPoint logo" />}
header={<b id={customHeaderId}>App Name</b>}
description={<span>Developer</span>}
/>
</Card>

<p tabIndex={0} id="after">
After
</p>
</>
);

const CardWithPreview = (props: CardProps) => (
<>
<p tabIndex={0} id="before">
Expand Down Expand Up @@ -423,6 +446,17 @@ describe('Card', () => {
});
});

it('should sync selectable aria-labelledby with card header immediate child', () => {
const customHeaderId = 'custom-header';

mountFluent(<CardWithCustomHeader customHeaderId={customHeaderId} selected />);

cy.get(`.${cardHeaderClassNames.header}`).should('not.have.attr', 'id');
cy.get(`.${cardClassNames.checkbox}`).then(slot => {
cy.get(`#${customHeaderId}`).then(() => expect(customHeaderId).equals(slot.attr('aria-labelledby')));
});
});

it('should sync selectable aria-label with card preview alt', () => {
mountFluent(<CardWithPreview selected />);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,44 @@ import type { CardHeaderProps, CardHeaderState } from './CardHeader.types';
import { useCardContext_unstable } from '../Card/CardContext';
import { cardHeaderClassNames } from './useCardHeaderStyles.styles';

/**
* Finds the first child of CardHeader with an id prop.
*
* @param header - the header prop of CardHeader
*/
function getChildWithId(header: CardHeaderProps['header']) {
function isReactElementWithIdProp(element: React.ReactNode): element is React.ReactElement {
return React.isValidElement(element) && Boolean(element.props.id);
}

return React.Children.toArray(header).find(isReactElementWithIdProp);
}

/**
* Returns the id to use for the CardHeader root element.
*
* @param headerId - the id prop of the CardHeader component
* @param childWithId - the first child of the CardHeader component with an id prop
* @param generatedId - a generated id
*
* @returns the id to use for the CardHeader root element
*/
function getReferenceId(
headerId: string | undefined,
childWithId: React.ReactElement | undefined,
generatedId: string,
): string {
if (headerId) {
return headerId;
}

if (childWithId?.props.id) {
return childWithId.props.id;
}

return generatedId;
}

/**
* Create the state required to render CardHeader.
*
Expand All @@ -21,15 +59,17 @@ export const useCardHeader_unstable = (props: CardHeaderProps, ref: React.Ref<HT
} = useCardContext_unstable();
const headerRef = React.useRef<HTMLDivElement>(null);

const hasChildId = React.useRef(false);
const generatedId = useId(cardHeaderClassNames.header, referenceId);

React.useEffect(() => {
if (header && headerRef.current) {
const { id } = headerRef.current;
const headerId = !hasChildId.current ? headerRef.current?.id : undefined;
const childWithId = getChildWithId(header);

hasChildId.current = Boolean(childWithId);

setReferenceId(id ? id : generatedId);
}
}, [header, setReferenceId, generatedId]);
setReferenceId(getReferenceId(headerId, childWithId, generatedId));
}, [generatedId, header, setReferenceId]);

return {
components: {
Expand All @@ -49,7 +89,7 @@ export const useCardHeader_unstable = (props: CardHeaderProps, ref: React.Ref<HT
required: true,
defaultProps: {
ref: headerRef,
id: referenceId,
id: !hasChildId.current ? referenceId : undefined,
},
}),
description: resolveShorthand(description),
Expand Down

0 comments on commit 1a34d5c

Please sign in to comment.