Skip to content

Commit

Permalink
Feature/72 view all (#86)
Browse files Browse the repository at this point in the history
* initial files

* Initial scripting

* Add styling

* Adding tests

* styling tweak

* Adding completed code to pattern

* typo

* another typo

* Updating for amnually opening all toggles

* update pattern with latest code

* Updating ACs

* Update CSS button icon, linting updates

* Update dependency form mark-up

---------

Co-authored-by: Mick Perkins <[email protected]>
  • Loading branch information
sarah-storm and mjbp authored May 27, 2024
1 parent 35d61c9 commit c39cea4
Show file tree
Hide file tree
Showing 9 changed files with 471 additions and 16 deletions.
120 changes: 120 additions & 0 deletions __tests__/patterns/view-all/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { h } from 'preact';
import { init as initViewAll } from '../../../src/js/modules/view-all';
import ViewAll from '../../../src/templates/pages/example/view-all';
import { render } from 'preact-render-to-string';

describe('View all > mark up', () => {
beforeAll(() => {
document.body.innerHTML = render(<ViewAll />);
initViewAll();
});

it('Should use a button element for the expandable section trigger', () => {
const toggleButton = document.querySelector('.js-expandable-section__btn-1');
expect(toggleButton.tagName).toEqual('BUTTON');
});

it('Should use a button element for the view all trigger', () => {
const toggleButton = document.querySelector('.js-expandable-section__btn-all');
expect(toggleButton.tagName).toEqual('BUTTON');
});

it('Buttons should be focusable', () => {
const toggleButton = document.querySelector('.js-expandable-section__btn-1');
toggleButton.focus();
expect(document.activeElement).toEqual(toggleButton);
});

it('View all button should be focusable', () => {
const toggleButton = document.querySelector('.js-expandable-section__btn-all');
toggleButton.focus();
expect(document.activeElement).toEqual(toggleButton);
});

it('Buttons should be appropriately labelled', () => {
const toggleButton = document.querySelector('.js-expandable-section__btn-1');
expect(toggleButton.textContent).toEqual('Section title');
});

it('View all button should be appropriately labelled', () => {
const toggleButtonDetail = document.querySelector('.js-expandable-section__btn-all .visually-hidden');
expect(toggleButtonDetail.textContent).toEqual('sections about Lorem Ipsum');
});

});

describe('View all > functionality', () => {
let instance;
beforeEach(() => {
document.body.innerHTML = render(<ViewAll />);
[ instance ] = initViewAll();
});

it('Should update the visible text on the button when toggled', () => {
const [ btn ] = instance.getState().btns;
expect(btn.textContent).toMatch(/(View all)/i);
btn.click();
expect(btn.textContent).toMatch(/(Hide all)/i);
});

it('View all button should toggle all inner expandable sections when used', () => {
const [ btn ] = instance.getState().btns;
const toggles = instance.getState().toggles;

const allClosed = toggles.reduce((acc, tog) => {
return acc && !tog.getState().isOpen;
}, true);
expect(allClosed).toBeTruthy();

btn.click();

const allOpen = toggles.reduce((acc, tog) => {
return acc && tog.getState().isOpen;
}, true);
expect(allOpen).toBeTruthy();
});

it('View all button should update when all toggles are manually opened', () => {
const [ btn ] = instance.getState().btns;
const toggles = instance.getState().toggles;

toggles.forEach((toggle) => {
toggle.getState().toggles[0].click();
});
expect(btn.textContent).toMatch(/(Hide all)/i);
expect(btn.classList.contains('is--open')).toBeTruthy();
expect(btn.getAttribute('aria-expanded')).toEqual("true");
});

});


describe('Expandable search > axe > ARIA', () => {
let instance;
beforeEach(() => {
document.body.innerHTML = render(<ViewAll />);
[ instance ] = initViewAll();
});

it('ARIA controls attribute should correctly associate button with search container element', () => {
const [ toggle ] = instance.getState().toggles;
const [ innerToggleBtn ] = toggle.getState().toggles;
expect(innerToggleBtn.getAttribute('aria-controls')).toEqual(toggle.node.getAttribute('id'));
});

it('ARIA expanded attribute should correctly describe shown/hidden state of individual toggles', () => {
const [ toggle ] = instance.getState().toggles;
const [ innerToggleBtn ] = toggle.getState().toggles;
expect(innerToggleBtn.getAttribute('aria-expanded')).toEqual("false");
toggle.toggle();
expect(innerToggleBtn.getAttribute('aria-expanded')).toEqual("true");
});

it('ARIA expanded attribute should correctly describe shown/hidden state of view all toggle', () => {
const [ btn ] = instance.getState().btns;
expect(btn.getAttribute('aria-expanded')).toEqual("false");
btn.click();
expect(btn.getAttribute('aria-expanded')).toEqual("true");
});

});
57 changes: 57 additions & 0 deletions src/css/components/_expandable-section.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
border-top: 1px solid var(--light-grey-3);
}
}
.expand-all .expandable-section:first-of-type {
border-top: 1px solid var(--light-grey-3);
}
.expandable-section__btn {
position: relative;
width: 100%;
Expand Down Expand Up @@ -47,4 +50,58 @@
.expandable-section__btn:after {
display: none;
}
}

.expandable-section__btn-all {
margin-bottom: var(--baseline);
background: none;
font-size: var(--font-size-plus-1);
font-weight: 500;
position: relative;
padding-left: calc(var(--gutter)*1.25);
display: flex;
align-items: center;

&:before,
&:after {
content: '';
display: block;
position: absolute;
left: 0;
width: 20px;
height: 2px;
background-color: var(--off-black);
}
&:after {
transform: rotate(90deg);
}

// &:before {
// content: "";
// display: inline-block;
// position: relative;
// top: 2px;
// width: 19px;
// margin-right: calc(var(--baseline)/2);
// aspect-ratio: 1;
// background: conic-gradient(from 90deg at 2px 2px, #0000 90deg,#000 0) calc(100% + 1px) calc(100% + 1px) / calc(50% + 2px) calc(50% + 2px);
// }

&.is--open {
&:after {
display: none;
}
// &:before {
// top: 9px;
// background: conic-gradient(from 90deg at 0px 3px, rgba(0, 0, 0, 0) 90deg, #000 0) calc(100% + 1px) calc(100% + 1px)
// }
}
}

.is--open .expandable-section__btn-all-view, .expandable-section__btn-all-hide {
display: none;
}

.is--open .expandable-section__btn-all-hide {
display: inline;
}
1 change: 1 addition & 0 deletions src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import './modules/cookie-banner';
import './modules/form-validation';
import './modules/tabs';
import './modules/show-more';
import './modules/view-all';

// Importer(`tabs`)()
// import(/* webpackChunkName: "toggle" */`./features/toggle`).then(module => module.default());
Expand Down
63 changes: 63 additions & 0 deletions src/js/modules/view-all/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import toggle from '@stormid/toggle';
const SELECTORS = {
NODE: '.js-expand-all',
TOGGLES: '.js-expandable-section-all',
BTN: '.js-expandable-section__btn-all'
};
const CLASSES = {
OPEN: 'is--open'
};

const checkAllToggles = toggles => toggles.reduce((acc, tog) => acc && tog.getState().isOpen, true);

const updateVewButton = (btns, allOpen) => {
btns.forEach(btn => {
btn.setAttribute('aria-expanded', allOpen);
btn.classList.toggle(CLASSES.OPEN);
});
};

export const init = () => {

const nodes = Array.from(document.querySelectorAll(SELECTORS.NODE));
const initialised = [];

nodes.forEach(node => {
if (!node.querySelector(SELECTORS.TOGGLES)) return;
const state = {
allOpen: false,
toggles: toggle(Array.from(node.querySelectorAll(SELECTORS.TOGGLES)), {
focus: false,
local: true,
callback: () => {
const newOpenState = checkAllToggles(state.toggles);
if (newOpenState !== state.allOpen) {
updateVewButton(state.btns, newOpenState);
state.allOpen = newOpenState;
}
} }),
btns: Array.from(node.querySelectorAll(SELECTORS.BTN))
};

if (state.toggles.length) {
state.btns.forEach(btn => {
btn.addEventListener('click', () => {
state.allOpen = !state.allOpen;
updateVewButton(state.btns, state.allOpen);
state.toggles.forEach(tog => {
if (tog.getState().isOpen !== state.allOpen) tog.toggle();
});
});
});
};

initialised.push({
node,
getState: () => state
});
});

return initialised;
};

init();
34 changes: 18 additions & 16 deletions src/templates/components/dependency-table/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { h } from 'preact';

const DependencyTable = ({ dependencies }) => <table class="table push-bottom--double">
<thead>
<tr>
<th class="th th--tight">Package</th>
<th class="th th--tight">Installation</th>
</tr>
</thead>
<tbody>
{
dependencies.map(dependency => <tr>
<td class="td td--tight"><a href={`https://www.npmjs.com/package/${dependency.package}`} rel="noopener nofollow">{dependency.package}</a></td>
<td class="td td--tight"><pre class="pre pre--inline"><code class="code">{dependency.installation}</code></pre></td>
</tr>)
}
</tbody>
</table>;
const DependencyTable = ({ dependencies }) => <div class="table__container--base">
<table class="table push-bottom--double">
<thead>
<tr>
<th class="th th--tight">Package</th>
<th class="th th--tight">Installation</th>
</tr>
</thead>
<tbody>
{
dependencies.map(dependency => <tr>
<td class="td td--tight"><a href={`https://www.npmjs.com/package/${dependency.package}`} rel="noopener nofollow">{dependency.package}</a></td>
<td class="td td--tight"><pre class="pre pre--inline"><code class="code">{dependency.installation}</code></pre></td>
</tr>)
}
</tbody>
</table>
</div>;

export default DependencyTable;
56 changes: 56 additions & 0 deletions src/templates/pages/example/view-all/code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { h, Fragment } from 'preact';

const Code = () => <Fragment>
<div class="expand-all js-expand-all">
<button type="button" class="expandable-section__btn-all js-expandable-section__btn-all" aria-expanded="false">
<span class="expandable-section__btn-all-view">View all <span class="visually-hidden">sections about Lorem Ipsum</span></span>
<span class="expandable-section__btn-all-hide">Hide all <span class="visually-hidden">sections about Lorem Ipsum</span></span>
</button>
<div class="expandable-section">
<h2 class="expandable-section__heading">
<button type="button" class="expandable-section__btn js-expandable-section__btn-1" aria-expanded="false" aria-controls="section-1">
Section title
</button>
</h2>
<div id="section-1" class="expandable-section__bd js-expandable-section-all" data-toggle="js-expandable-section__btn-1">
<p>Aenean id posuere nunc. Donec diam nisl, rhoncus vel faucibus sed, porttitor sit amet ipsum. Donec at venenatis augue. Phasellus consequat lectus non augue vestibulum, at varius diam sagittis. Morbi nec purus augue. Etiam rutrum ullamcorper arcu vitae sollicitudin. Aliquam bibendum suscipit risus, at lacinia tortor efficitur ac.</p>
<p>Praesent vitae mi nec mauris vehicula ultricies nec ut felis. Nam vel nisi id nunc efficitur fermentum. Vestibulum mollis enim nec ultrices mattis. Curabitur placerat sed nisl lobortis iaculis. Fusce porttitor massa augue, sit amet faucibus enim finibus nec. Maecenas hendrerit metus in justo commodo viverra.</p>
</div>
</div>
<div class="expandable-section">
<h2 class="expandable-section__heading">
<button type="button" class="expandable-section__btn js-expandable-section__btn-2" aria-expanded="false" aria-controls="section-2">
Section title
</button>
</h2>
<div id="section-2" class="expandable-section__bd js-expandable-section-all" data-toggle="js-expandable-section__btn-2">
<p>Aenean id posuere nunc. Donec diam nisl, rhoncus vel faucibus sed, porttitor sit amet ipsum. Donec at venenatis augue. Phasellus consequat lectus non augue vestibulum, at varius diam sagittis. Morbi nec purus augue. Etiam rutrum ullamcorper arcu vitae sollicitudin. Aliquam bibendum suscipit risus, at lacinia tortor efficitur ac.</p>
<p>Praesent vitae mi nec mauris vehicula ultricies nec ut felis. Nam vel nisi id nunc efficitur fermentum. Vestibulum mollis enim nec ultrices mattis. Curabitur placerat sed nisl lobortis iaculis. Fusce porttitor massa augue, sit amet faucibus enim finibus nec. Maecenas hendrerit metus in justo commodo viverra.</p>
</div>
</div>
<div class="expandable-section">
<h2 class="expandable-section__heading">
<button type="button" class="expandable-section__btn js-expandable-section__btn-3" aria-expanded="false" aria-controls="section-3">
Section title
</button>
</h2>
<div id="section-3" class="expandable-section__bd js-expandable-section-all" data-toggle="js-expandable-section__btn-3">
<p>Aenean id posuere nunc. Donec diam nisl, rhoncus vel faucibus sed, porttitor sit amet ipsum. Donec at venenatis augue. Phasellus consequat lectus non augue vestibulum, at varius diam sagittis. Morbi nec purus augue. Etiam rutrum ullamcorper arcu vitae sollicitudin. Aliquam bibendum suscipit risus, at lacinia tortor efficitur ac.</p>
<p>Praesent vitae mi nec mauris vehicula ultricies nec ut felis. Nam vel nisi id nunc efficitur fermentum. Vestibulum mollis enim nec ultrices mattis. Curabitur placerat sed nisl lobortis iaculis. Fusce porttitor massa augue, sit amet faucibus enim finibus nec. Maecenas hendrerit metus in justo commodo viverra.</p>
</div>
</div>
<div class="expandable-section">
<h2 class="expandable-section__heading">
<button type="button" class="expandable-section__btn js-expandable-section__btn-4" aria-expanded="false" aria-controls="section-4">
Section title
</button>
</h2>
<div id="section-4" class="expandable-section__bd js-expandable-section-all" data-toggle="js-expandable-section__btn-4">
<p>Aenean id posuere nunc. Donec diam nisl, rhoncus vel faucibus sed, porttitor sit amet ipsum. Donec at venenatis augue. Phasellus consequat lectus non augue vestibulum, at varius diam sagittis. Morbi nec purus augue. Etiam rutrum ullamcorper arcu vitae sollicitudin. Aliquam bibendum suscipit risus, at lacinia tortor efficitur ac.</p>
<p>Praesent vitae mi nec mauris vehicula ultricies nec ut felis. Nam vel nisi id nunc efficitur fermentum. Vestibulum mollis enim nec ultrices mattis. Curabitur placerat sed nisl lobortis iaculis. Fusce porttitor massa augue, sit amet faucibus enim finibus nec. Maecenas hendrerit metus in justo commodo viverra.</p>
</div>
</div>
</div>
</Fragment>;

export default Code;
14 changes: 14 additions & 0 deletions src/templates/pages/example/view-all/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { h } from 'preact';
import ExampleLayout from '@layouts/example';
import Code from './code';

export const title = 'Expandable section example with view/hide all';

const ExpandableSectionViewAll = () => <ExampleLayout>
<main class="wrap soft-top">
<h1 class="visuallyhidden">Expandable section example with view/hide all</h1>
<Code />
</main>
</ExampleLayout>;

export default ExpandableSectionViewAll;
2 changes: 2 additions & 0 deletions src/templates/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { status as tablesOverflowStatus } from './patterns/table-overflow';
import { status as tablesResponsiveStatus } from './patterns/table-responsive';
import { status as tablesRowLinksStatus } from './patterns/table-row-links';
import { status as showMoreStatus } from './patterns/show-more';
import { status as viewAllStatus } from './patterns/view-all';

export const title = 'Home';

Expand All @@ -31,6 +32,7 @@ const PATTERNS = [
{ title: 'Expandable navigation', url: '/patterns/expandable-navigation', status: expandableNavigationStatus },
{ title: 'Expandable search', url: '/patterns/expandable-search', status: expandableSearchStatus },
{ title: 'Expandable section', url: '/patterns/expandable-section', status: expandableSectionStatus },
{ title: 'Expandable section - view/hide all', url: '/patterns/view-all', status: viewAllStatus },
{ title: 'Form page headings', url: '/patterns/form-headings', status: formHeadingStatus },
{ title: 'Form validation', url: '/patterns/form-validation', status: formValidationStatus },
{ title: 'Full screen navigation', url: '/patterns/full-screen-navigation', status: fullScreenNavigationStatus },
Expand Down
Loading

0 comments on commit c39cea4

Please sign in to comment.