Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
24 changes: 21 additions & 3 deletions packages/modal/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Modal

Accessible modal dialog
Accessible modal dialog.

Example implementations of specific types of modal are available for reference at https://storm-ui-patterns.netlify.app/.

---

Expand Down Expand Up @@ -57,7 +59,7 @@ const [ instance ] = modal(elements);
```

CSS
The className 'is--modal' added to the document.body when the modal is open. This can be used to prevent the body from scrolling
The className 'is--modal' added to the document.body when the modal is open. This can be used to prevent the body from scrolling and to use CSS to modify other parts of the document.

```
.is--modal {
Expand All @@ -66,7 +68,7 @@ The className 'is--modal' added to the document.body when the modal is open. Thi
```

## Options
Options can be set during initialising in an Object passed as the second argument to the modal function, e.g. `modal('.js-modal', { startOpen: true })`, or as data-attributes on the modal element (the element passed to the modal function), e.g. `data-start-open="true"`
Options can be set during initialising in an Object passed as the second argument to the modal function, e.g. `modal('.js-modal', { startOpen: true })`, or as data-attributes on the modal element (the element passed to the initiaisation function), e.g. `data-start-open="true"`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small typo - *initialisation.

```
{
onClassName: 'is--active', //className added to node when modal is open
Expand All @@ -87,6 +89,22 @@ modal() returns an array of instances. Each instance exposes the interface
}
```

## Events
There are two custom events that an instance of the cookie banner dispatches:
- `modal.open` when the modal is opened
- `modal.close` when it is closed

The events are dispatched on the document. A reference to the getState function of the instance is contained in the custom event detail.

```
const [instance] = modal('.js-modal', options);

document.addEventListener('modal.open', e => {
const state = e.detail.getState();
// do something with state if we want to
});


## Tests
```
npm t
Expand Down
14 changes: 7 additions & 7 deletions packages/modal/__tests__/unit/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ describe(`Modal > Store`, () => {
it('createStore should return an Object with an API', async () => {
expect(Store).not.toBeNull();
expect(Store.getState).not.toBeNull();
expect(Store.dispatch).not.toBeNull();
expect(Store.update).not.toBeNull();
});

it('should have a getState function that returns a private state Object', async () => {
expect(Store.state).toBeUndefined();
expect(Store.getState()).toEqual({});
});

it('should have a dispatch function that updates state', async () => {
it('should have a update function that updates state', async () => {
const nextState = { isOpen: true };
Store.dispatch(nextState);
Store.update(nextState);
expect(Store.getState()).toEqual(nextState);
});

it('should have a dispatch function that does not update state if nextState is not passed', async () => {
it('should have a update function that does not update state if nextState is not passed', async () => {
const Store = createStore();
Store.dispatch();
Store.update();
expect(Store.getState()).toEqual({});
});

it('should have a dispatch function that invokes any side effect functions passed after the state change, with new state as only argument', async () => {
Store.dispatch({}, [sideEffect]);
it('should have a update function that invokes any side effect functions passed after the state change, with new state as only argument', async () => {
Store.update({}, [sideEffect]);
expect(effect).toEqual(true);
});

Expand Down
25 changes: 25 additions & 0 deletions packages/modal/__tests__/unit/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createStore } from '../../src/lib/store';
import { broadcast } from '../../src/lib/utils';
import { EVENTS } from '../../src/lib/constants';
import defaults from '../../src/lib/defaults';


describe(`Modal > Utils > broadcast`, () => {

it('should dispatch a custom event with a detail Object with a reference to Store.getState', async () => {
const store = createStore();
const state = {
settings: defaults
};
store.update(state);
const listener = jest.fn();
document.addEventListener(EVENTS.OPEN, listener);
document.addEventListener(EVENTS.OPEN, e => {
expect(e.detail).toEqual({ getState: store.getState });
});

broadcast(EVENTS.OPEN, store)(state);
expect(listener).toHaveBeenCalled();
});

});
77 changes: 44 additions & 33 deletions packages/modal/example/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,12 @@
visibility:hidden;
}
.modal__inner {
width:50%;
height:50%;
background:#fff;
margin:20px auto;
margin:30vh auto;
position: relative;
transform:scale(0.1);
transform-origin: center;
transition: transform 200ms ease, opacity 160ms ease;
transform:scale(1);
z-index:3;
max-width: 600px;
padding: 24px;
}
.modal__close {
position:fixed;
Expand Down Expand Up @@ -71,15 +67,39 @@
z-index: 9;
}
*:focus {
outline: 1px solid red;
outline: 3px solid red;
}
button {
background:#191919;
color:#fff;
padding:8px;
display:inline-block;
font-size:1rem;
background-color:#191919;
padding:8px 16px;
color: #fff;
border:0 none;
cursor: pointer;
display: inline-block;
&:hover {
background-color: blue
}
}
button + button {
margin-left: 12px;
}
.modal-confirmation__form {
margin-top: 24px;
}
.modal-confirmation__row {
margin-top: 32px;
}
.modal-confirmation__title {
font-size: 1.2rem;
font-weight: 500;
}
.modal-confirmation__cancel {
background-color: transparent;
color: #191919;
&:hover {
background-color: transparent;
color: blue;
}
}
.visually-hidden {
clip: rect(0 0 0 0);
Expand All @@ -96,27 +116,18 @@
</style>
</head>
<body>
<div style="height: 300px;background-color: #ddd"></div>
<div style="height: 200px;background-color: #666"></div>
<div>
<button class="js-modal-toggle">Open modal</button>
<div id="modal-1" class="js-modal modal" data-modal-toggle="js-modal-toggle" hidden>
<div class="modal__inner" role="dialog" aria-labelledby="modal-label">
<h1 id="modal-label" class="visually-hidden">Test modal</h1>
<button>Focusable element</button>
<input type="text">
<input type="text">
<button class="modal__close-btn js-modal-toggle" aria-label="close" >
<svg focusable="false" fill="#fff" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>
</button>
</div>
<button class="js-modal__btn" aria-haspopup="dialog">Open modal</button>
<div id="modal-confirmation" aria-labelledby="modal-label" role="region" class="js-modal modal-confirmation modal" data-modal-toggle="js-modal__btn" hidden="hidden">
<div class="modal__inner" role="dialog" aria-labelledby="modal-label" aria-describedby="modal-description">
<h1 class="modal-confirmation__title" id="modal-label">Are you sure?</h1>
<form class="modal__form modal-confirmation__form" action="#" novalidate="novalidate">
<p id="modal-description">This will permanently remove this item</p>
<div class="modal-confirmation__row">
<button type="button" class="modal-confirmation__confirm">Delete</button>
<button type="button" class="modal-confirmation__cancel js-modal__btn">Cancel</button>
</div>
</form>
</div>
</div>
<div style="height: 1000px;background-color: #aaa"></div>
<div></div>
<div></div>
</body>
</html>
7 changes: 1 addition & 6 deletions packages/modal/example/src/js/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import modal from '../../../src';

window.addEventListener('DOMContentLoaded', () => {
window.__m1__ = modal('.js-modal', {
// startOpen: true,
callback(){
console.log('callback called');
}
});
window.__m1__ = modal('.js-modal');

});
5 changes: 2 additions & 3 deletions packages/modal/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import { getSelection } from './lib/utils';
*
* @param selector, Can be a string, Array of DOM nodes, a NodeList or a single DOM element.
* @params options, Object, to be merged with defaults to become the settings propery of each returned object
*
* @return Array of modal Objects, one for each DOM node found
*/
export default (selector, options) => {
//Array.from isnt polyfilled
//https://github.com/babel/babel/issues/5682
let nodes = getSelection(selector);

//no DOM nodes found, return with warning
if (nodes.length === 0) return console.warn(`Modal not initialised, no elements found for selector '${selector}'`);

//return array of Objects, one for each DOM node found
Expand Down
8 changes: 6 additions & 2 deletions packages/modal/src/lib/constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
/* istanbul ignore file */
export const ACCEPTED_TRIGGERS = ['button', 'a'];

//Array of focusable child elements
export const FOCUSABLE_ELEMENTS = ['a[href]', 'area[href]', 'input:not([disabled]):not([type=hidden])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex="-1"])'];
export const FOCUSABLE_ELEMENTS = ['a[href]', 'area[href]', 'input:not([disabled]):not([type=hidden])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex="-1"])'];

export const EVENTS = {
OPEN: 'modal.open',
CLOSE: 'modal.close'
};
1 change: 1 addition & 0 deletions packages/modal/src/lib/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* @property onClassName, String,
* @property toggleSelectorAttribute, String
* @property callback, Function
* @property startOpen, Boolean
* @property delay, Number
*/
export default {
Expand Down
Loading