Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/sharp-years-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudfour/patterns": patch
---

Update Card and Overview to only use `header` and `footer` elements if the containing element is an `article` or `section`.
54 changes: 54 additions & 0 deletions src/components/card/card.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import path from 'path';

import type { ElementHandle } from 'pleasantest';
import { getAccessibilityTree, withBrowser } from 'pleasantest';

import { loadTwigTemplate } from '../../../test-utils.js';

/** Helper to load the Twig template file */
const template = loadTwigTemplate(path.join(__dirname, './demo/single.twig'));
const divTemplate = loadTwigTemplate(path.join(__dirname, './demo/div.twig'));

describe('Card component', () => {
test(
'should use header/footer with article',
withBrowser(async ({ utils, page }) => {
await utils.injectHTML(
await template({
show_heading: true,
show_footer: true,
})
);

const body = await page.evaluateHandle<ElementHandle>(
() => document.body
);
expect(await getAccessibilityTree(body, { includeText: false }))
.toMatchInlineSnapshot(`
article
banner
heading "Lorem ipsum dolor sit amet" (level=2)
contentinfo
`);
})
);

test(
'should not use header/footer with div',
withBrowser(async ({ utils, page }) => {
await utils.injectHTML(
await divTemplate({
show_heading: true,
show_footer: true,
})
);

const body = await page.evaluateHandle<ElementHandle>(
() => document.body
);
expect(
await getAccessibilityTree(body, { includeText: false })
).toMatchInlineSnapshot(`heading "Lorem ipsum dolor sit amet" (level=2)`);
})
);
});
34 changes: 21 additions & 13 deletions src/components/card/card.twig
Original file line number Diff line number Diff line change
@@ -1,32 +1,40 @@
{% set tag_name = tag_name|default('article') %}
{% set header_tag_name = header_tag_name|default('header') %}
{% set footer_tag_name = footer_tag_name|default('footer') %}

{% set heading_level = heading_level|default(2) %}
{% set _tag_name = tag_name|default('article') %}
{#
Using `header` inside a `div` causes pointless "banner" landmarks in
the VoiceOver rotor. As a result, we set the default header/footer
element to `div` if the `tag_name` is anything but `article` or `section`.
#}
{% set _is_sectioning = _tag_name in ['article', 'section'] %}
{% set _default_header_tag = _is_sectioning ? 'header' : 'div' %}
{% set _default_footer_tag = _is_sectioning ? 'footer' : 'div' %}
{% set _header_tag_name = header_tag_name|default(_default_header_tag) %}
{% set _footer_tag_name = footer_tag_name|default(_default_footer_tag) %}

{% set _heading_level = heading_level|default(2) %}

{% set _heading_block %}{% block heading %}{% endblock %}{% endset %}
{% set _cover_block %}{% block cover %}{% endblock %}{% endset %}
{% set _content_block %}{% block content %}{% endblock %}{% endset %}
{% set _footer_block %}{% block footer %}{% endblock %}{% endset %}

<{{ tag_name }} class="
<{{ _tag_name }} class="
c-card
{% if href %}c-card--with-link{% endif %}
{% if class %}{{ class }}{% endif %}"
{% if heading_id and _heading_block is not empty %}aria-labelledby="{{heading_id}}"{% endif %}>

{% if _heading_block is not empty %}
<{{ header_tag_name }} class="c-card__header">
<h{{ heading_level }} class="c-card__heading"{% if heading_id %} id="{{heading_id}}"{% endif %}>
<{{ _header_tag_name }} class="c-card__header">
<h{{ _heading_level }} class="c-card__heading"{% if heading_id %} id="{{heading_id}}"{% endif %}>
{% if href %}
<a href="{{ href }}" class="c-card__link">
{{ _heading_block }}
</a>
{% else %}
{{ _heading_block }}
{% endif %}
</h{{ heading_level }}>
</{{ header_tag_name }}>
</h{{ _heading_level }}>
</{{ _header_tag_name }}>
{% endif %}

{% if _cover_block is not empty %}
Expand All @@ -42,9 +50,9 @@
{% endif %}

{% if _footer_block is not empty %}
<{{ footer_tag_name }} class="c-card__footer">
<{{ _footer_tag_name }} class="c-card__footer">
{{ _footer_block }}
</{{ footer_tag_name }}>
</{{ _footer_tag_name }}>
{% endif %}

</{{ tag_name }}>
</{{ _tag_name }}>
23 changes: 23 additions & 0 deletions src/components/card/demo/div.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{# Used for tests #}
{% embed '@cloudfour/components/card/card.twig' with { tag_name: 'div' } %}
{% block heading %}
{%- if show_heading -%}
Lorem ipsum dolor sit amet
{%- endif -%}
{% endblock %}
{% block cover %}
{%- if show_cover -%}
<img src="https://placeimg.com/800/450/animals" alt="">
{%- endif -%}
{% endblock %}
{% block content %}
{%- if show_content -%}
<p>Consectetur adipiscing elit. Fusce tempor ut ex nec scelerisque. Quisque dui tortor, tempus et tempor in, rhoncus eu massa. Vestibulum dolor erat, vestibulum eget velit eu, dignissim hendrerit tortor.</p>
{%- endif -%}
{% endblock %}
{% block footer %}
{%- if show_footer -%}
<p>{{'now'|date('M j, Y')}}</p>
{%- endif -%}
{% endblock %}
{% endembed %}
12 changes: 12 additions & 0 deletions src/objects/overview/demo/div.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{# Used for tests #}
{% embed '@cloudfour/objects/overview/overview.twig' with { overview_tag: 'div' } %}
{% block header %}
Header
{% endblock %}
{% block actions %}
Actions
{% endblock %}
{% block content %}
Content
{% endblock %}
{% endembed %}
56 changes: 56 additions & 0 deletions src/objects/overview/overview.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import path from 'path';

import type { ElementHandle } from 'pleasantest';
import { getAccessibilityTree, withBrowser } from 'pleasantest';

import { loadTwigTemplate } from '../../../test-utils.js';

/** Helper to load the Twig template file */
const template = loadTwigTemplate(path.join(__dirname, './demo/basic.twig'));
const divTemplate = loadTwigTemplate(path.join(__dirname, './demo/div.twig'));

describe('Overview object', () => {
test(
'should use header with section',
withBrowser(async ({ utils, page }) => {
await utils.injectHTML(
await template({
show_heading: true,
show_footer: true,
})
);

const body = await page.evaluateHandle<ElementHandle>(
() => document.body
);
expect(await getAccessibilityTree(body)).toMatchInlineSnapshot(`
region
banner
text "Header"
text "Actions"
text "Content"
`);
})
);

test(
'should not use header with div',
withBrowser(async ({ utils, page }) => {
await utils.injectHTML(
await divTemplate({
show_heading: true,
show_footer: true,
})
);

const body = await page.evaluateHandle<ElementHandle>(
() => document.body
);
expect(await getAccessibilityTree(body)).toMatchInlineSnapshot(`
text "Header"
text "Actions"
text "Content"
`);
})
);
});
18 changes: 14 additions & 4 deletions src/objects/overview/overview.twig
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
<{{overview_tag|default('section')}}
{% set _overview_tag = overview_tag|default('section') %}
{#
Using `header` inside a `div` causes pointless "banner" landmarks in
the VoiceOver rotor. As a result, we set the default header element
to `div` if the `overview_tag` is anything but `article` or `section`.
#}
{% set _is_sectioning = _overview_tag in ['article', 'section'] %}
{% set _default_header_tag = _is_sectioning ? 'header' : 'div' %}
{% set _header_tag_name = header_tag_name|default(_default_header_tag) %}

<{{ _overview_tag }}
class="o-overview"
{% if labelledby_id %}aria-labelledby="{{labelledby_id}}"{% endif %}
>
<header class="o-overview__header">
<{{ _header_tag_name }} class="o-overview__header">
{% block header %}{% endblock %}
</header>
</{{ _header_tag_name }}>
<div class="o-overview__actions">
{% block actions %}{% endblock %}
</div>
<div class="o-overview__content">
{% block content %}{% endblock %}
</div>
</{{overview_tag|default('section')}}>
</{{ _overview_tag }}>