Skip to content

Commit

Permalink
Merge pull request #2684 from ONSdigital/enhancement/2372/improve-tab…
Browse files Browse the repository at this point in the history
…-layout

Enhancement/2372/improve tab layout
  • Loading branch information
willcAND authored Jun 26, 2023
2 parents 72f1349 + 4399f43 commit e7a0765
Show file tree
Hide file tree
Showing 14 changed files with 129 additions and 54 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 7 additions & 6 deletions src/components/tabs/_macro-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@

## Tab

| Name | Type | Required | Description |
| ---------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| id | string | false | Sets the HTML `id` of the tab |
| title | string | true | The title for the tab |
| hiddenSpan | string | false | Sets a visually hidden span after the title to distinguish the tab from others if multiple tabs with same title are displayed in the same page |
| content | string | true | The contents of the tab. This can contain HTML. |
| Name | Type | Required | Description |
| ---------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| id | string | false | Sets the HTML `id` of the tab |
| title | string | true | The title for the tab |
| showTitle | boolean | false | Sets an optional `h2` which will be displayed only in toc view and visually hidden in tab view. |
| hiddenSpan | string | false | Sets a visually hidden span after the title to distinguish the tab from others if multiple tabs with same title are displayed in the same page |
| content | string | true | The contents of the tab. This can contain HTML. |
7 changes: 4 additions & 3 deletions src/components/tabs/_macro.njk
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@
>
{% set titleTag = params.titleTag | default("h2") %}
<{{ titleTag }} class="ons-tabs__title ons-u-fs-r--b ons-u-mt-no">{{ params.title }}</{{ titleTag }}>

<div>
<ul class="ons-tabs__list">
{%- for tab in params.tabs -%}
<li class="ons-tab__list-item"><a href="#{{ tab.id if tab.id else tab.title|trim|replace(' ','-')|lower}}" class="ons-tab" data-ga="click" data-ga-category="tabs" data-ga-action="Show: {{ tab.title }}" data-ga-label="Show: {{ tab.title }}">{{ tab.title }}{% if tab.hiddenSpan %}<span class='ons-u-d-no'>{{tab.hiddenSpan}}</span>{% endif %}</a></li>
{%- endfor -%}
</ul>

</div>
{% for tab in params.tabs %}
<section id="{{ tab.id if tab.id else tab.title|trim|replace(' ','-')|lower }}" class="ons-tabs__panel">
{% if tab.showTitle %}<h2 id="{{ tab.title|trim|replace(' ','-')|lower + '-content-title' }}" class="ons-u-vh">{{ tab.title }}</h2>{% endif %}
{{ tab.content | safe }}
</section>
{% endfor %}
{% endfor %}
</section>
{% endmacro %}
23 changes: 23 additions & 0 deletions src/components/tabs/_macro.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,23 @@ const EXAMPLE_TABS = {
],
};

const EXAMPLE_TABS_WITH_SHOWTITLE = {
title: 'Example tabs',
tabs: [
{
id: 'first-tab',
title: 'Tab 1',
showTitle: true,
content: 'Example content...',
},
{
id: 'second-tab',
title: 'Tab 2',
content: 'Some nested <strong>strong element</strong>...',
},
],
};

const EXAMPLE_TABS_WITHOUT_TAB_IDS = {
title: 'Example tabs',
tabs: [
Expand Down Expand Up @@ -127,4 +144,10 @@ describe('macro: tabs', () => {
.trim(),
).toBe('Some nested <strong>strong element</strong>...');
});

it('displays a h2 when showTitle set to true', () => {
const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS_WITH_SHOWTITLE));

expect($('.ons-tabs__panel:first').find('h2').length).toBe(1);
});
});
26 changes: 11 additions & 15 deletions src/components/tabs/_tabs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,19 @@
margin: 0 0 1rem;
overflow: visible;
padding: 0;

width: max-content;
// Tabs
&--row {
margin: 0;
position: relative;

&::after {
background: var(--ons-color-borders);
bottom: 0;
box-shadow: 0 1px 0 0 var(--ons-color-page-light);
content: '';
height: 1px;
left: 0;
position: absolute;
width: 100%;
}
}
}
&__container {
border-bottom: 1px solid var(--ons-color-borders);
margin: 0;
padding: 0;
z-index: -2;
}
}

.ons-tab__list-item {
Expand Down Expand Up @@ -81,10 +76,12 @@
// Tab when selected
&[aria-selected='true'] {
background-color: var(--ons-color-page-light);
border-bottom: none;
border-bottom: 2px white;
border-color: var(--ons-color-borders);
border-radius: 3px 3px 0 0;
position: relative;
text-decoration: none;
top: 1px;
z-index: 1;

&:focus {
Expand All @@ -109,7 +106,7 @@
}

&:focus {
box-shadow: 0 0 0 3px var(--ons-color-page-light), 0 0 0 5px var(--ons-color-text-link-focus), 0 0 0 8px var(--ons-color-focus);
box-shadow: 0 0 0 0 var(--ons-color-page-light), 0 0 0 2px var(--ons-color-text-link-focus), 0 0 0 6px var(--ons-color-focus);
outline: 3px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show box-shadows
z-index: 1;
}
Expand Down Expand Up @@ -137,7 +134,6 @@
}

.ons-tabs__list--row {
margin-bottom: -1px;
padding: 0;
}

Expand Down
12 changes: 6 additions & 6 deletions src/components/tabs/example-tabs.njk
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
{
"id": "ukis",
"title": 'UKIS',
"showTitle": true,
"hiddenSpan": 'for UKIS',
"content": '<h2 class="ons-u-d-no@m">UKIS</h2>
<h3>Aim of this survey</h3>
"content": '<h3>Aim of this survey</h3>
<p class="ons-u-fs-r">The aim of the UK Innovation Survey (UKIS) is to collect data from businesses about various aspects of their innovation related activities. Using this data we can measure the level, types and trends in innovation.</p>
<h3>How we’ll use this data</h3>
<p class="ons-u-fs-r">The UKIS data is a major source of evidence to inform government policy. It is used to promote innovation activities among businesses to boost economic growth. It is an important contribution to the European-wide Community Innovation Survey (CIS). The CIS is used for international benchmarking and comparison purposes.</p>
Expand All @@ -18,16 +18,16 @@
{
"id": "vacancy-survey",
"title": 'Vacancy survey',
"content": '<h2 class="ons-u-d-no@m">Vacancy survey</h2>
<h3>Purpose</h3>
"showTitle": true,
"content": '<h3>Purpose</h3>
<p class="ons-u-fs-r">The Vacancy Survey is a regular survey of businesses, which provides an accurate and comprehensive measure of the total number of vacancies across the economy and fills a gap in the information available regarding the demand for labour. Before the Vacancy Survey was introduced, the only information available nationally about vacancies was from records of vacancies notified to Job Centres by employers. This provided only a partial picture, possibly less than half of all vacancies, because employers are under no obligation to notify vacancies to Job Centres. This business based survey has a more complete coverage and is included in the monthly ONS Labour Market Statistical Bulletin.</p>
<p>You can <a href="https://www.ons.gov.uk/surveys/informationforbusinesses/businesssurveys/vacancysurvey">find more information on the Vacancy Survey on the ONS website</a>.</p>'
},
{
"id": "monthly-business-survey",
"title": 'Monthly Business Survey',
"content": '<h2 class="ons-u-d-no@m">Monthly Business Survey</h2>
<h3>Aim of this survey</h3>
"showTitle": true,
"content": '<h3>Aim of this survey</h3>
<p class="ons-u-fs-r">The Monthly Business Survey (MBS) collects monthly information on employment of businesses in Great Britain. Your response contributes to Labour Market Statistics.</p>
<h4>What you need to know</h4>
<p class="ons-u-fs-r">To complete the survey, you will need the following information to answer the survey questions:</p>
Expand Down
32 changes: 24 additions & 8 deletions src/components/tabs/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const classTabTitle = 'ons-tabs__title';
const classTabList = 'ons-tabs__list';
const classTabListItems = 'ons-tab__list-item';
const classTabsPanel = 'ons-tabs__panel';

const matchMediaUtil = matchMedia;

export default class Tabs {
Expand All @@ -22,6 +21,7 @@ export default class Tabs {
this.tabsTitle = component.querySelector(`.${classTabTitle}`);
this.tabs = [...component.getElementsByClassName(classTab)];
this.tabList = component.getElementsByClassName(classTabList);
this.tabListContainer = this.tabList[0].parentElement;
this.tabListItems = [...component.getElementsByClassName(classTabListItems)];
this.tabPanels = [...component.getElementsByClassName(classTabsPanel)];

Expand All @@ -31,7 +31,6 @@ export default class Tabs {
this.jsTabAsListClass = 'ons-tab--row';

this.noInitialActiveTab = this.component.getAttribute('data-no-initial-active-tab');

if (matchMediaUtil.hasMatchMedia()) {
this.setupViewportChecks();
} else {
Expand All @@ -40,11 +39,22 @@ export default class Tabs {
}

// Set up checks for responsive functionality
// The tabs will display as tabs for >40rem viewports
// Tabs will display as a TOC list and show full content for <740px viewports
// Aria tags are added only for >740px viewports
// The tabs will display as tabs up until this.breakpoint is reached
// Tabs will display as a TOC list and show full content for <this.breakpoint viewports
// Aria tags are added only in toc view
setupViewportChecks() {
this.viewport = matchMediaUtil('(min-width: 740px)');
const breakpoint = () => {
let finalBreakpoint = 0;
this.tabListItems.forEach(tab => {
finalBreakpoint += tab.offsetWidth;
});
if (finalBreakpoint < 450) {
return (finalBreakpoint = 450);
} else {
return finalBreakpoint;
}
};
this.viewport = matchMediaUtil(`(min-width: ${breakpoint()}px)`);
this.viewport.addListener(this.checkViewport.bind(this));
this.checkViewport();
}
Expand All @@ -62,9 +72,12 @@ export default class Tabs {
this.tabList[0].classList.add(this.jsTabListAsRowClass);

this.tabsTitle.classList.add('ons-u-vh');

this.tabListContainer.classList.add('ons-tabs__container');
this.tabPanels.forEach(panel => {
panel.setAttribute('tabindex', '0');
if (panel.querySelector('[id*="content-title"]')) {
panel.firstElementChild.classList.add('ons-u-vh');
}
});

this.tabListItems.forEach(item => {
Expand Down Expand Up @@ -96,11 +109,14 @@ export default class Tabs {
makeList() {
this.tabList[0].removeAttribute('role');
this.tabList[0].classList.remove(this.jsTabListAsRowClass);

this.tabListContainer.classList.remove('ons-tabs__container');
this.tabsTitle.classList.remove('ons-u-vh');

this.tabPanels.forEach(panel => {
panel.removeAttribute('tabindex', '0');
if (panel.firstElementChild.classList.contains('ons-u-vh')) {
panel.firstElementChild.classList.remove('ons-u-vh');
}
});

this.tabListItems.forEach(item => {
Expand Down
42 changes: 40 additions & 2 deletions src/components/tabs/tabs.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const EXAMPLE_TABS = {
{
id: 'tab.id.1',
title: 'Tab 1',
showTitle: true,
content: 'First content...',
},
{
Expand All @@ -24,6 +25,38 @@ const EXAMPLE_TABS = {
],
};

const EXAMPLE_TABS_LONGER = {
title: 'Example tabs',
tabs: [
{
id: 'tab.id.1',
title: 'Tab 1',
showTitle: true,
content: 'First content...',
},
{
id: 'tab.id.2',
title: 'Tab 2',
content: 'Second content...',
},
{
id: 'tab.id.3',
title: 'Tab 3',
content: 'Third content...',
},
{
id: 'tab.id.4',
title: 'Tab 4',
content: 'Fourth content...',
},
{
id: 'tab.id.5',
title: 'Tab 5',
content: 'Fifth content...',
},
],
};

const EXAMPLE_TABS_WITH_NO_INITIAL_ACTIVE_TAB = {
...EXAMPLE_TABS,
noInitialActiveTab: true,
Expand Down Expand Up @@ -175,7 +208,7 @@ describe('script: tabs', () => {
beforeEach(async () => {
await page.emulate(puppeteer.devices['iPhone X']);

await setTestPage('/test', renderComponent('tabs', EXAMPLE_TABS));
await setTestPage('/test', renderComponent('tabs', EXAMPLE_TABS_LONGER));
});

it('has no aria attributes on tabs', async () => {
Expand All @@ -194,11 +227,16 @@ describe('script: tabs', () => {

it('has no hidden tab panels', async () => {
const panelCount = await page.$$eval('.ons-tabs__panel', nodes => nodes.length);
expect(panelCount).toBe(3);
expect(panelCount).toBe(5);

const hiddenPanelCount = await page.$$eval('.ons-tabs__panel--hidden', nodes => nodes.length);
expect(hiddenPanelCount).toBe(0);
});

it('displays a h2 element with a unique id', async () => {
const panelCount = await page.$$eval('#tab-1-content-title', nodes => nodes.length);
expect(panelCount).toBe(1);
});
});

describe('when `data-no-initial-active-tab` is present', () => {
Expand Down

0 comments on commit e7a0765

Please sign in to comment.