Skip to content

Commit

Permalink
♿ Add tab-lock, scroll-lock, screenreader-lock
Browse files Browse the repository at this point in the history
Move from scrolllock to react-focus-on as that does everything a11y related!
  • Loading branch information
davwheat committed Apr 10, 2020
1 parent 37a97a6 commit c602719
Show file tree
Hide file tree
Showing 10 changed files with 2,705 additions and 1,612 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

A mobile-friendly, highly customizable, carousel component for displaying media in ReactJS.

### Browser support

Should work in every major browser... maybe even IE10 and IE11?

### Getting Started

Start by installing `react-images`
Expand Down
4,207 changes: 2,621 additions & 1,586 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-images",
"version": "1.1.0-beta.5",
"version": "1.1.1",
"description": "A mobile-friendly, highly customizable, carousel component for displaying media in ReactJS",
"main": "lib/index.js",
"jsnext:main": "dist/react-images.es.js",
Expand Down Expand Up @@ -41,7 +41,7 @@
"eslint-plugin-react": "^7.17.0",
"flow-bin": "^0.63.1",
"gh-pages": "^2.2.0",
"html-webpack-plugin": "^2.30.1",
"html-webpack-plugin": "^4.2.0",
"istanbul": "^0.4.5",
"lint-staged": "^6.1.1",
"nps": "^5.9.12",
Expand All @@ -58,16 +58,19 @@
"rollup-plugin-uglify": "^2.0.1",
"style-loader": "^0.20.3",
"uglify-es": "^3.3.9",
"webpack": "^4.41.5",
"webpack-dev-server": "^2.11.5"
"webpack": "^4.42.1",
"webpack-dev-server": "^3.10.3",
"webpack-cli": "^3.3.11",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"dependencies": {
"a11y-focus-store": "^1.0.0",
"cross-env": "^6.0.3",
"cross-env": "^7.0.2",
"glam": "^5.0.1",
"raf-schd": "^2.1.2",
"react-focus-on": "^3.3.0",
"react-full-screen": "^0.2.2",
"react-scrolllock": "^4.0.1",
"react-transition-group": "^2.9.0",
"react-view-pager": "^0.6.0"
},
Expand Down
2 changes: 1 addition & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ const globals = {
'raf-schd': 'rafScheduler',
'react-view-pager': 'PageView',
'react-full-screen': 'Fullscreen',
'react-scrolllock': 'ScrollLock',
'a11y-focus-store': 'focusStore',
'react-transition-group': 'Transition',
'react-focus-on': 'FocusOn',
react: 'React',
};
import createEnv from 'dotenv';
Expand Down
20 changes: 16 additions & 4 deletions src/components/Carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import glam from 'glam';
import rafScheduler from 'raf-schd';
import { ViewPager, Frame, Track, View as PageView } from 'react-view-pager';

const viewPagerStyles = { flex: '1 1 auto', position: 'relative' };
const viewPagerStyles = {
flex: '1 1 auto',
position: 'relative',
height: '100vh',
width: '100vw',
};
const frameStyles = { outline: 0 };

import {
Expand All @@ -21,7 +26,7 @@ import { type ViewsType } from '../types';
import componentBaseClassNames from './componentBaseClassNames';

type SpringConfig = { [key: string]: number };
export type fn = any => void;
export type fn = (any) => void;
export type IndicesType = Array<number>;
export type CarouselProps = {
/* Replace any of the carousel components */
Expand Down Expand Up @@ -60,7 +65,7 @@ export type CarouselProps = {
onSwipeEnd: fn,
onSwipeMove: fn,
onSwipeStart: fn,
onViewChange: number => void,
onViewChange: (number) => void,
springConfig: SpringConfig,
swipe: true | false | 'mouse' | 'touch',
swipeThreshold: number,
Expand Down Expand Up @@ -338,7 +343,13 @@ class Carousel extends Component<CarouselProps, CarouselState> {
};

getCommonProps() {
const { frameProps, trackProps, modalProps, views, showNavigationOnTouchDevice } = this.props;
const {
frameProps,
trackProps,
modalProps,
views,
showNavigationOnTouchDevice,
} = this.props;
const isModal = Boolean(modalProps);
const isFullscreen = Boolean(modalProps && modalProps.isFullscreen);
const { currentIndex, interactionIsIdle } = this.state;
Expand Down Expand Up @@ -379,6 +390,7 @@ class Carousel extends Component<CarouselProps, CarouselState> {
ref={this.getFrame}
className={className('frame')}
style={frameStyles}
tabIndex="-1"
>
<Track
{...this.getTrackProps(this.props)}
Expand Down
4 changes: 4 additions & 0 deletions src/components/Footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export const footerCSS = ({ isModal, interactionIsIdle }: State) => ({
transition: 'opacity 300ms, transform 300ms',
zIndex: isModal ? 1 : null,

'& *:focus': {
outline: '1.5px solid orange',
},

[smallDevice]: {
padding: isModal ? '20px 15px 15px' : '5px 0',
},
Expand Down
3 changes: 3 additions & 0 deletions src/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export const headerCSS = ({ interactionIsIdle }: State) => ({
left: 0,
right: 0,
zIndex: 1,
'& *:focus': {
outline: '1.5px solid orange',
},
});

const headerBaseClassName = componentBaseClassNames.Header;
Expand Down
22 changes: 13 additions & 9 deletions src/components/Modal/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import React, { cloneElement, Component } from 'react';
import glam from 'glam';
import Fullscreen from 'react-full-screen';
import ScrollLock from 'react-scrolllock';
import focusStore from 'a11y-focus-store';
import {
defaultModalComponents,
Expand All @@ -14,6 +13,7 @@ import { type CarouselType } from '../Carousel';
import { defaultModalStyles, type ModalStylesConfig } from '../../styles';
import { isTouch, className } from '../../utils';
import componentBaseClassNames from '../componentBaseClassNames';
import { FocusOn } from 'react-focus-on';

type MouseOrKeyboardEvent = MouseEvent | KeyboardEvent;
export type CloseType = (event: MouseOrKeyboardEvent) => void;
Expand All @@ -22,7 +22,7 @@ export type ModalProps = {
isFullscreen: boolean,
onClose: CloseType,
preventScroll: boolean,
toggleFullscreen: any => void,
toggleFullscreen: (any) => void,
};

export type Props = {
Expand Down Expand Up @@ -64,7 +64,7 @@ const backdropClassNames = new Set(
componentBaseClassNames.Header,
componentBaseClassNames.Footer,
componentBaseClassNames.Track,
].map(className),
].map(className)
);

class Modal extends Component<Props, State> {
Expand All @@ -78,7 +78,7 @@ class Modal extends Component<Props, State> {

this.cacheComponents(props.components);

this.state = { isFullscreen: false };
this.state = { isFullscreen: false, isClosing: false };
}

componentDidUpdate(prevProps: Props) {
Expand All @@ -96,6 +96,7 @@ class Modal extends Component<Props, State> {
modalWillUnmount = () => {
document.removeEventListener('keyup', this.handleKeyUp);
focusStore.restoreFocus();
this.setState({ isClosing: false });
};

cacheComponents = (comps?: ModalComponents) => {
Expand Down Expand Up @@ -132,12 +133,14 @@ class Modal extends Component<Props, State> {
this.handleClose(event);
};
toggleFullscreen = () => {
this.setState(state => ({ isFullscreen: !state.isFullscreen }));
this.setState((state) => ({ isFullscreen: !state.isFullscreen }));
};
handleClose = (event: MouseOrKeyboardEvent) => {
const { onClose } = this.props;
const { isFullscreen } = this.state;

this.setState({ isClosing: true });

// force exit fullscreen mode on close
if (isFullscreen) {
this.toggleFullscreen();
Expand All @@ -164,7 +167,7 @@ class Modal extends Component<Props, State> {
}
render() {
const { Blanket, Positioner, Dialog } = this.components;
const { allowFullscreen, children, preventScroll } = this.props;
const { allowFullscreen, children } = this.props;
const { isFullscreen } = this.state;
const commonProps = (this.commonProps = this.getCommonProps());

Expand Down Expand Up @@ -195,13 +198,14 @@ class Modal extends Component<Props, State> {
component={Positioner}
in={transitionIn}
innerProps={{
onClick: this.handleBackdropClick,
onClick: this.state.isClosing ? null : this.handleBackdropClick,
}}
onEntered={this.modalDidMount}
onExited={this.modalWillUnmount}
>
<Dialog {...commonProps}>{carouselComponent}</Dialog>
{preventScroll && <ScrollLock />}
<Dialog removeFocusOn={this.state.isClosing} {...commonProps}>
{carouselComponent}
</Dialog>
</SlideUp>
</Fullscreen>
);
Expand Down
27 changes: 23 additions & 4 deletions src/components/Modal/styled.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import glam from 'glam';
import { Div } from '../../primitives';
import { type PropsWithStyles } from '../../types';
import { className } from '../../utils';
import { FocusOn } from 'react-focus-on';

// ==============================
// Blanket
Expand Down Expand Up @@ -81,22 +82,40 @@ type DialogState = { isFullscreen: boolean };
type DialogProps = DialogState &
PropsWithStyles & {
children: Node,
innerProps: Object, // TODO
innerProps: Object,
removeFocusOn: Boolean,
};

export const dialogCSS = () => ({
width: '100%'
width: '100%',
});

export const Dialog = (props: DialogProps) => {
const { children, getStyles, innerProps, isFullscreen } = props;
return (
const {
children,
getStyles,
innerProps,
isFullscreen,
removeFocusOn,
} = props;

return removeFocusOn ? (
<Div
css={getStyles('dialog', props)}
className={className('dialog', { isFullscreen })}
{...innerProps}
>
{children}
</Div>
) : (
<FocusOn>
<Div
css={getStyles('dialog', props)}
className={className('dialog', { isFullscreen })}
{...innerProps}
>
{children}
</Div>
</FocusOn>
);
};
13 changes: 11 additions & 2 deletions src/components/Navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,19 @@ export const navigationCSS = ({ interactionIsIdle }: NavState) => ({
justifyContent: 'space-between',
opacity: interactionIsIdle ? 0 : 1,
transition: 'opacity 300ms',
'& *:focus': {
outline: '1.5px solid orange',
},
});

export const Navigation = (props: NavProps) => {
const { children, getStyles, isFullscreen, isModal, showNavigationOnTouchDevice } = props;
const {
children,
getStyles,
isFullscreen,
isModal,
showNavigationOnTouchDevice,
} = props;
return !isTouch() || (isTouch() && showNavigationOnTouchDevice) ? (
<Nav
css={getStyles('navigation', props)}
Expand All @@ -51,7 +60,7 @@ type ItemProps = ItemState &
PropsWithStyles & {
children: Node,
innerProps: {
onClick: any => void,
onClick: (any) => void,
title: string,
},
};
Expand Down

0 comments on commit c602719

Please sign in to comment.