Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UX-565 Takeover component migration #214

Merged
merged 50 commits into from
Nov 15, 2019
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
1721b81
UX-565 Takeover component
Oct 1, 2019
2a46201
UX-565 Animation; portal & inline; close by ESC; focus trap
Oct 1, 2019
60f1335
UX-565 Fix Portal/Inline switching
Oct 4, 2019
62d5ebc
UX-565 Lock body scroll
Oct 4, 2019
6415db7
UX-565 Ability to extend FocusTrap options
Oct 4, 2019
8e32525
UX-565 Compound components: Header, Content
Oct 4, 2019
4f5a37d
UX-565 Comment
Oct 4, 2019
e9e54e4
UX-565 Remove unnecessary dependencies
Oct 4, 2019
ea9dc83
UX-565 Story with nested SidePanel
Oct 4, 2019
a15f557
UX-565 Add @paprika/sidepanel to root
Oct 11, 2019
4cb26ff
UX-565 Fix: Takeover closing on esc press while overlapped sidepanel …
Oct 11, 2019
afea7b3
UX-565 Refactor Takeover.Content
Oct 11, 2019
b6babe2
UX-565 Remove console.log
Oct 11, 2019
4f75cc1
UX-565 Add spec
Nov 1, 2019
c4d8400
UX-565 SidePanel: always stop propagation
Nov 1, 2019
7b18725
UX-565 Takeover: always stop propagation
Nov 1, 2019
28b39d3
UX-565 SidePanel: always stop propagation
Nov 1, 2019
1d53aca
UX-565 Update yarn.lock
Nov 4, 2019
f7e9015
UX-565 Fix ESC key handling
Nov 4, 2019
fbd96f0
UX-565 Use data-pka-anchor
Nov 4, 2019
212f9f3
UX-565 Use displayName instead of componentType
Nov 4, 2019
e8c023f
UX-565 Use value from Tokens
Nov 4, 2019
37c9399
UX-565 Add "given" as allowed global to ESLint
Nov 4, 2019
370a322
UX-565 Sort style rules
Nov 4, 2019
3d6d4b0
UX-565 ESLint: ignore line with hack
Nov 4, 2019
5e05d8b
UX-565 README.md
Nov 4, 2019
749c9b7
UX-565 Using `css` prop instead of `styled.` helper function
Nov 4, 2019
9ef77fa
Merge branch 'master' into UX-565-migrate-takeover
Nov 8, 2019
a43184c
UX-565 Move LockBodyScroll to helpers package
Nov 8, 2019
9daa631
UX-565 Move Portal to helpers package
Nov 8, 2019
9d54ac1
UX-565 Remove an ability to inline Takeover
Nov 8, 2019
5dffdab
UX-565 Ignore ESLint warning
Nov 8, 2019
b6e1e68
UX-565 Update takeover deps
Nov 8, 2019
9e37434
UX-565 Format
Nov 8, 2019
0f0a413
UX-565 Update FocusTrap prop types
Nov 8, 2019
65724c5
UX-565 Update FocusTrap prop types
Nov 8, 2019
a1e044e
UX-565 Fix ButtonIcon styles
Nov 8, 2019
7e618a6
UX-565 Format
Nov 8, 2019
90b1550
UX-565 Get rid of `flex` shorthand
Nov 8, 2019
fe97cb3
UX-565 Move extractChildren to helpers package; move tokens.js
Nov 8, 2019
f9443c6
UX-565 Change defaultProps for FocusTrap
Nov 8, 2019
ab6a6a9
UX-565 Get rid of `css` attr
Nov 11, 2019
99c0be9
UX-565 Remove ESLint ignore comments
Nov 11, 2019
b3abde6
UX-565 Format
Nov 11, 2019
0b857ef
Merge branch 'master' into UX-565-migrate-takeover
Nov 11, 2019
dd568a2
UX-565 Downgrade @babel/runtime-corejs2
Nov 15, 2019
90ce4b0
UX-565 Fix ListBox
Nov 15, 2019
7405712
Merge branch 'master' into UX-565-migrate-takeover
Nov 15, 2019
fe5b4fb
UX-565 Fix SidePanel breaking change
Nov 15, 2019
8c62a0c
UX-565 Format code
Nov 15, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@
},
{
"files": ["packages/**/*.spec.js"],
"rules": { "import/no-extraneous-dependencies": "off" }
"rules": { "import/no-extraneous-dependencies": "off" },
"globals": {
"given": true
}
},
{
"files": ["packages/helpers/**/*.js"],
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module.exports = {
setupFilesAfterEnv: [
"jest-dom/extend-expect",
"@testing-library/react/cleanup-after-each",
"given2/setup",
"./testingHelpers/config.js",
],
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@paprika/popover": "^0.1.0",
"@paprika/raw-button": "^0.1.0",
"@paprika/select": "^0.1.0",
"@paprika/sidepanel": "^0.1.0",
"@paprika/sortable": "^0.1.0",
"@paprika/spinner": "^0.1.0",
"@paprika/stylers": "^0.1.0",
Expand Down Expand Up @@ -85,6 +86,7 @@
"eslint-plugin-jsx-a11y": "^6.1.1",
"eslint-plugin-react": "^7.11.1",
"eslint-plugin-react-hooks": "^1.6.0",
"given2": "^2.1.7",
"husky": "^2.5.0",
"jest": "^24.1.0",
"jest-cli": "^24.1.0",
Expand Down
12 changes: 10 additions & 2 deletions packages/SidePanel/src/SidePanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import FocusTrap from "./components/FocusTrap";
import LockBodyScroll from "./components/LockBodyScroll";

import { extractChildren } from "./helpers";
import { useOffsetScroll, useEscapeKey } from "./hooks";
import { useOffsetScroll } from "./hooks";

const propTypes = {
/** The content for the SidePanel. */
Expand Down Expand Up @@ -64,7 +64,6 @@ function SidePanel(props) {
// Hooks
const [isVisible, setIsVisible] = React.useState(props.isOpen);
const offsetScroll = useOffsetScroll(offsetY);
useEscapeKey(isOpen, onClose);

// Refs
const refTrigger = React.useRef(null);
Expand Down Expand Up @@ -118,6 +117,14 @@ function SidePanel(props) {
...extendedFocusTrapOptions,
};

function handleEscKey(event) {
if (event.key === "Escape") {
DanilAgafonov marked this conversation as resolved.
Show resolved Hide resolved
event.stopPropagation();

onClose();
}
}

let sidePanel = null;

if (isVisible) {
Expand All @@ -134,6 +141,7 @@ function SidePanel(props) {
refHeader={refHeader}
offsetY={offsetScroll}
isOpen={isOpen}
onKeyDown={handleEscKey}
{...moreProps}
>
{children}
Expand Down
1 change: 0 additions & 1 deletion packages/SidePanel/src/hooks/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { default as useEscapeKey } from "./useEscapeKey";
export { default as useOffsetScroll } from "./useOffsetScroll";
export { default as useFooterOffset } from "./useFooterOffset";
19 changes: 0 additions & 19 deletions packages/SidePanel/src/hooks/useEscapeKey.js

This file was deleted.

4 changes: 4 additions & 0 deletions packages/Takeover/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*

!readme.md
!lib/**
36 changes: 36 additions & 0 deletions packages/Takeover/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Takeover

Takeover component can toggle a full-screen view to help the user focus on complex UI tasks.
More information at the design system site: https://design.wegalvanize.com/p/components/takeover

## Components

You can use any of the following components to compose the Takeover:

```jsx
<Takeover.Header />
<Takeover.Content />
<Takeover.FocusTrap />
```

## Basic example

```jsx
<Takeover isOpen={isOpen} onClose={toggle}>
<Takeover.Header>
<Heading level={2}>Header</Heading>
</Takeover.Header>
<Takeover.Content>
My content
</Takeover.Content>
</Takeover>
```

## Props

- `children` (required)
- `isOpen` (required)
- `onClose`
- `onAfterOpen`
- `onAfterClose`
- `isInline`
30 changes: 30 additions & 0 deletions packages/Takeover/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@paprika/takeover",
"version": "0.1.0",
"description": "Takeover component",
"author": "@paprika",
"main": "lib/index.js",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/acl-services/paprika/tree/master/packages/Takeover"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@babel/runtime-corejs2": "^7.3.1",
"@paprika/button": "^0.1.13",
DanilAgafonov marked this conversation as resolved.
Show resolved Hide resolved
"@paprika/heading": "^0.1.6",
"@paprika/stylers": "^0.1.5",
"@paprika/tokens": "^0.1.4",
"focus-trap-react": "^6.0.0",
"prop-types": "^15.7.2",
"react-transition-group": "^4.3.0"
},
"peerDependencies": {
"react": "^16.8.4",
"react-dom": "^16.8.4",
"styled-components": "^4.2.0"
DanilAgafonov marked this conversation as resolved.
Show resolved Hide resolved
}
}
119 changes: 119 additions & 0 deletions packages/Takeover/src/Takeover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";
import { Transition } from "react-transition-group";
import FocusTrapLibrary from "focus-trap-react";
import * as styles from "./Takeover.styles";
import Header from "./components/Header";
import LockBodyScroll from "./components/LockBodyScroll";
import { animationDuration } from "./tokens";
import extractChildren from "./helpers/extractChildren";

const propTypes = {
/** The content for the Takeover. */
children: PropTypes.node.isRequired,

/** Control the visibility of the takeover */
isOpen: PropTypes.bool.isRequired,

/** Callback triggered when the takeover needs to be close */
onClose: PropTypes.func,

/** Callback once the takeover has been opened event */
onAfterOpen: PropTypes.func,

/** Callback once the takeover has been closed event */
onAfterClose: PropTypes.func,

/** Render the takeover inline */
isInline: PropTypes.bool,
};

const defaultProps = {
isInline: false,
onAfterClose: () => {},
onClose: () => {},
onAfterOpen: () => {},
};

const Portal = ({ children, active }) =>
DanilAgafonov marked this conversation as resolved.
Show resolved Hide resolved
active ? ReactDOM.createPortal(children, document.body) : <React.Fragment>{children}</React.Fragment>;
DanilAgafonov marked this conversation as resolved.
Show resolved Hide resolved

const Takeover = props => {
const { isOpen, onClose, isInline, onAfterClose, onAfterOpen, ...moreProps } = props;

function handleTransitionEnter(node) {
// https://github.com/reactjs/react-transition-group/blob/6dbadb594c7c2a2f15bc47afc6b4374cfd73c7c0/src/CSSTransition.js#L44
// eslint-disable-next-line no-unused-expressions
node.scrollTop;
}

const {
DanilAgafonov marked this conversation as resolved.
Show resolved Hide resolved
"Takeover.FocusTrap": focusTrapExtracted,
"Takeover.Header": headerExtracted,
"Takeover.Content": contentExtracted,
children,
} = extractChildren(moreProps.children, ["Takeover.FocusTrap", "Takeover.Header", "Takeover.Content"]);

const extendedFocusTrapOptions = focusTrapExtracted ? focusTrapExtracted.props : {};

const focusTrapOptions = {
fallbackFocus: () => document.createElement("div"),
...extendedFocusTrapOptions,
};

function handleEscKey(event) {
if (event.key === "Escape") {
event.stopPropagation();

onClose();
}
}

return (
<>
{isOpen && <LockBodyScroll />}
<Portal active={!isInline}>
<Transition
DanilAgafonov marked this conversation as resolved.
Show resolved Hide resolved
mountOnEnter
unmountOnExit
in={isOpen}
timeout={animationDuration}
onEnter={handleTransitionEnter}
onEntered={onAfterOpen}
onExited={onAfterClose}
>
{state => (
<FocusTrapLibrary active={!isInline} focusTrapOptions={focusTrapOptions}>
DanilAgafonov marked this conversation as resolved.
Show resolved Hide resolved
<div
css={styles.wrapper}
state={state}
role="dialog"
tabIndex="-1"
onKeyDown={handleEscKey}
data-pka-anchor="takeover"
>
{headerExtracted && <Header css={styles.header} {...headerExtracted.props} onClose={onClose} />}
{contentExtracted && (
<div
role="region"
css={styles.contentWrapper}
tabIndex="0" // eslint-disable-line jsx-a11y/no-noninteractive-tabindex
>
{contentExtracted}
</div>
)}
{children}
</div>
</FocusTrapLibrary>
)}
</Transition>
</Portal>
</>
);
};

Takeover.propTypes = propTypes;
Takeover.defaultProps = defaultProps;

export default Takeover;
40 changes: 40 additions & 0 deletions packages/Takeover/src/Takeover.styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { css } from "styled-components";
import tokens from "@paprika/tokens";
import { animationDuration } from "./tokens";

const openedCss = css`
opacity: 1;
`;

const closedCss = css`
opacity: 0;
`;

const states = {
entering: openedCss,
entered: openedCss,
exiting: closedCss,
exited: closedCss,
};

export const wrapper = css`
background-color: ${tokens.backgroundColor.level0};
bottom: 0;
display: flex;
flex-direction: column;
left: 0;
position: fixed;
right: 0;
top: 0;
transition: all ${animationDuration}ms ease;
${({ state }) => states[state]};
`;

export const header = css`
flex: none;
`;

export const contentWrapper = css`
flex: 1 1 auto;
overflow-y: auto;
`;
16 changes: 16 additions & 0 deletions packages/Takeover/src/components/Content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import styled from "styled-components";
import { spacer } from "@paprika/stylers/lib/helpers";
import tokens from "@paprika/tokens/lib/tokens";

const Content = styled.div`
background-color: ${tokens.color.white};
border: 1px solid ${tokens.border.color};
border-radius: ${tokens.border.radius};
box-shadow: ${tokens.shadow};
margin: ${spacer(2)};
padding: ${spacer(2)};
`;

Content.displayName = "Takeover.Content";

export default Content;
36 changes: 36 additions & 0 deletions packages/Takeover/src/components/FocusTrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import PropTypes from "prop-types";
nahumzs marked this conversation as resolved.
Show resolved Hide resolved
import FocusTrapLibrary from "focus-trap-react";

// properties copy from https://github.com/davidtheclark/focus-trap

const propTypes = {
/** A function that will be called when the focus trap activates. */
onActivate: PropTypes.func,
/** A function that will be called when the focus trap deactivates */
onDeactivate: PropTypes.func,

/** By default, when a focus trap is activated the first element in the focus trap's tab order will receive focus. With this option you can specify a different element to receive that initial focus. Can be a DOM node, or a selector string (which will be passed to document.querySelector() to find the DOM node), or a function that returns a DOM node. */
initialFocus: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.func]),

/**
By default, an error will be thrown if the focus trap contains no elements in its tab order. With this option you can specify a fallback element to programmatically receive focus if no other tabbable elements are found. For example, you may want a popover's <div> to receive focus if the popover's content includes no tabbable elements. Make sure the fallback element has a negative tabindex so it can be programmatically focused. The option value can be a DOM node, a selector string (which will be passed to document.querySelector() to find the DOM node), or a function that returns a DOM node.
escapeDeactivates {boolean}: Default: true. If false, the Escape key will not trigger deactivation of the focus trap. This can be useful if you want to force the user to make a decision instead of allowing an easy way out.
DanilAgafonov marked this conversation as resolved.
Show resolved Hide resolved
*/
fallbackFocus: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.func]), //

/** Default: false. If true, a click outside the focus trap will deactivate the focus trap and allow the click event to do its thing. */
clickOutsideDeactivates: PropTypes.bool,

/** Default: true. If false, when the trap is deactivated, focus will not return to the element that had focus before activation. */
returnFocusOnDeactivate: PropTypes.bool,
DanilAgafonov marked this conversation as resolved.
Show resolved Hide resolved
};

const defaultProps = FocusTrapLibrary.defaultProps.focusTrapOptions;
DanilAgafonov marked this conversation as resolved.
Show resolved Hide resolved

const FocusTrap = () => null;

FocusTrap.propTypes = propTypes;
FocusTrap.defaultProps = defaultProps;
FocusTrap.displayName = "Takeover.FocusTrap";

export default FocusTrap;