Skip to content

Commit

Permalink
feat: implement <Modal>
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Feb 20, 2018
1 parent 472b240 commit a8c2d07
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 3 deletions.
126 changes: 126 additions & 0 deletions src/Modal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {Component} from 'react';
import {render} from 'react-universal-interface';
import {h, isClient, on, off, noop} from '../util';
import {Overlay} from '../Overlay';

let cnt = 0;
let id = 0;

const ESC = 27;

export interface IModalProps {
onElement?: (el: HTMLDivElement) => void;
onEsc?: (event) => void;
}

export interface IModalState {
}

class Modal extends Component<IModalProps, IModalState> {
id: number;
el: HTMLElement = null;
activeEl: Element; // Previous active element;

constructor (props, context) {
super(props, context);

cnt++;
this.id = id++;

this.state = {
bindTitle: {
id: 'dialog-title-' + this.id
},
bindDescr: {
id: 'dialog-descr-' + this.id
}
};

this.activeEl = isClient ? document.activeElement : null;
}

componentDidMount () {
on(document, 'keydown', this.onKey);

setTimeout(() => {
const firstFocusableElement = this.el.querySelector('button, [href], input, select, textarea, [tabindex]:not(tabindex="-1"]') as HTMLElement;

if (firstFocusableElement && firstFocusableElement.focus) {
firstFocusableElement.focus();
}
});
}

componentWillUnmount () {
cnt--;

off(document, 'keydown', this.onKey);

const siblings = Array.from(document.body.children);

for (let i = 0; i < siblings.length; i++) {
const sibling = siblings[i] as HTMLElement;

if (sibling === this.el) {
continue;
}

if ((sibling as any).__modal_lock !== this) {
continue;
}

delete (sibling as any).__modal_lock;
(sibling as any).inert = false;
sibling.style.removeProperty('pointer-events');
sibling.removeAttribute('aria-hidden');
}

// Focus previously active element.
if (this.activeEl && (this.activeEl as any).focus) {
(this.activeEl as any).focus();
}
}

onElement = (el) => {
this.el = el;

el.setAttribute('role', 'dialog');
el.classList.add('dialog');

el.setAttribute('aria-labelledby', 'dialog-title-' + this.id);
el.setAttribute('aria-describedby', 'dialog-descr-' + this.id);

const siblings = Array.from(document.body.children);

for (let i = 0; i < siblings.length; i++) {
const sibling = siblings[i] as HTMLElement;

if (sibling === el) {
continue;
}

if ((sibling as any).__modal_lock) {
continue;
}

(sibling as any).__modal_lock = this;
(sibling as any).inert = true;
sibling.style.setProperty('pointer-events', 'none');
sibling.setAttribute('aria-hidden', 'true');
}

(this.props.onElement || noop)(el);
};

onKey = (event) => {
if (event.keyCode === ESC) {
(this.props.onEsc || noop)(event);
}
};

render () {
return h(Overlay, {
onElement: this.onElement
}, render(this.props, this.state));
}
}
7 changes: 4 additions & 3 deletions src/Overlay/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {Component} from 'react';
import {Portal} from '../Portal';
import {h} from '../util';
import {h, noop} from '../util';

export interface IOverlayProps {
color?: string;
time?: number;
onElement?: (div: HTMLElement) => void;
}

export interface IOverlayState {
Expand All @@ -19,8 +20,6 @@ export class Overlay extends Component<IOverlayProps, IOverlayState> {
onElement = (el) => {
const {style} = el;

el.setAttribute('role', 'modal');

style.zIndex = 2147483647; // Max z-index.
style.position = 'fixed';
style.width = '100%';
Expand All @@ -41,6 +40,8 @@ export class Overlay extends Component<IOverlayProps, IOverlayState> {
setTimeout(() => {
style.opacity = 1;
}, 35);

(this.props.onElement || noop)(el);
};

render () {
Expand Down

0 comments on commit a8c2d07

Please sign in to comment.