Skip to content

Commit

Permalink
Add NotificationModal
Browse files Browse the repository at this point in the history
  • Loading branch information
connor-baer committed Jun 14, 2021
1 parent 819eef8 commit e72cdc8
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 2 deletions.
4 changes: 3 additions & 1 deletion packages/circuit-ui/components/ButtonGroup/ButtonGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export interface ButtonGroupProps {
/**
* Buttons to group.
*/
children: ReactElement<ButtonProps>[] | ReactElement<ButtonProps>;
children:
| (ReactElement<ButtonProps> | null | undefined)[]
| ReactElement<ButtonProps>;
/**
* Direction to align the content. Either left/right
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/circuit-ui/components/Image/Image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* limitations under the License.
*/

import { HTMLProps } from 'react';
import { HTMLProps, Ref } from 'react';
import { css } from '@emotion/core';

import styled from '../../styles/styled';
Expand All @@ -30,6 +30,7 @@ export interface ImageProps
* user uses a screen reader.
*/
alt: string;
ref?: Ref<HTMLImageElement>;
}

const baseStyles = () => css`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Copyright 2019, SumUp Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React from 'react';
import { action } from '@storybook/addon-actions';

import { ModalProvider } from '../ModalContext';
import Button from '../Button';

// import docs from './Modal.docs.mdx';
import { NotificationModal, NotificationModalProps } from './NotificationModal';
import { useNotificationModal } from './useNotificationModal';

export default {
title: 'Components/NotificationModal',
component: NotificationModal,
// parameters: {
// docs: { page: docs },
// },
};

export const Base = (modal: NotificationModalProps): JSX.Element => {
const ComponentWithModal = () => {
const { setModal } = useNotificationModal();

return (
<Button type="button" onClick={() => setModal(modal)}>
Open modal
</Button>
);
};
return (
<ModalProvider>
<ComponentWithModal />
</ModalProvider>
);
};

Base.args = {
image: {
src: 'https://source.unsplash.com/TpHmEoVSmfQ/1600x900',
alt: '',
},
headline: 'Example modal',
body: 'Hello World!',
actions: {
primary: {
children: 'Primary',
onClick: action('primary'),
},
secondary: {
children: 'Secondary',
onClick: action('secondary'),
},
},
};
196 changes: 196 additions & 0 deletions packages/circuit-ui/components/NotificationModal/NotificationModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/**
* Copyright 2019, SumUp Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/** @jsx jsx */
import { jsx, css, ClassNames } from '@emotion/core';
import { ReactNode, MouseEvent, KeyboardEvent } from 'react';
import ReactModal from 'react-modal';
import { Theme } from '@sumup/design-tokens';

import useClickHandler from '../../hooks/useClickHandler';
import { ModalComponent, BaseModalProps } from '../ModalContext';
import Image, { ImageProps } from '../Image';
import Headline from '../Headline';
import Body from '../Body';
import Button, { ButtonProps } from '../Button';
import ButtonGroup from '../ButtonGroup';
import styled, { StyleProps } from '../../styles/styled';

const TRANSITION_DURATION = 200;

const imageStyles = ({ theme }: StyleProps) => css`
max-width: 232px;
height: 160px;
object-fit: cover;
margin: 0 auto ${theme.spacings.mega};
`;

const ModalImage = styled(Image)(imageStyles);

export interface NotificationModalProps extends BaseModalProps {
image: ImageProps;
headline: string;
body: string | ReactNode;
actions: {
primary: Omit<ButtonProps, 'variant'>;
secondary?: Omit<ButtonProps, 'variant'>;
};
/**
* TODO: Add description. Default true.
*/
dismissible?: boolean;
}

/**
* Circuit UI's wrapper component for ReactModal.
* http://reactcommunity.org/react-modal/accessibility/#aria
*/
export const NotificationModal: ModalComponent<NotificationModalProps> = ({
image,
headline,
body,
actions,
onClose,
dismissible = false,
tracking = {},
className,
...props
}) => {
if (process.env.NODE_ENV !== 'production' && className) {
// eslint-disable-next-line no-console
console.warn(
[
'Custom styles are not supported by the NotificationModal component.',
'If your use case requires custom styles, please open an issue at',
'https://github.com/sumup-oss/circuit-ui.',
].join(' '),
);
}

const handleClose = useClickHandler(onClose, tracking, 'modal-close');
return (
<ClassNames<Theme>>
{({ css: cssString, theme }) => {
// React Modal styles
// https://reactcommunity.org/react-modal/styles/classes/

// FIXME: Replace border-radius with theme value in v3.
const styles = {
base: cssString`
label: notification-modal;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100vw - ${theme.spacings.peta} * 2);
max-width: 420px;
max-height: calc(100vh - ${theme.spacings.mega} * 2);
outline: none;
background-color: ${theme.colors.white};
border-radius: 16px;
padding: ${theme.spacings.giga};
text-align: center;
opacity: 0;
transition: opacity ${TRANSITION_DURATION}ms ease-in-out;
overflow-y: auto;
${theme.mq.untilKilo} {
-webkit-overflow-scrolling: touch;
}
`,
afterOpen: cssString`
label: notification-modal--after-open;
opacity: 1;
`,
beforeClose: cssString`
label: notification-modal--before-close;
opacity: 0;
`,
};

const overlayStyles = {
base: cssString`
label: notification-modal__overlay;
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
opacity: 0;
transition: opacity ${TRANSITION_DURATION}ms ease-in-out;
background: ${theme.colors.overlay};
z-index: ${theme.zIndex.modal};
${theme.mq.kilo} {
-webkit-overflow-scrolling: touch;
overflow-y: auto;
}
`,
afterOpen: cssString`
label: notification-modal__overlay--after-open;
opacity: 1;
`,
beforeClose: cssString`
label: notification-modal__overlay--before-close;
opacity: 0;
`,
};

const reactModalProps = {
className: styles,
overlayClassName: overlayStyles,
onRequestClose: handleClose,
closeTimeoutMS: TRANSITION_DURATION,
shouldCloseOnOverlayClick: dismissible,
shouldCloseOnEsc: dismissible,
...props,
};

function wrapOnClick(onClick?: ButtonProps['onClick']) {
return (event: MouseEvent | KeyboardEvent) => {
handleClose?.(event);
onClick?.(event);
};
}

return (
<ReactModal {...reactModalProps}>
<ModalImage {...image} />
<Headline as="h2" size="three" noMargin>
{headline}
</Headline>
<Body>{body}</Body>
<ButtonGroup align="center">
{actions.secondary && (
<Button
{...actions.secondary}
variant="secondary"
onClick={wrapOnClick(actions.secondary.onClick)}
/>
)}
<Button
{...actions.primary}
variant="primary"
onClick={wrapOnClick(actions.primary.onClick)}
/>
</ButtonGroup>
</ReactModal>
);
}}
</ClassNames>
);
};

NotificationModal.TIMEOUT = TRANSITION_DURATION;
17 changes: 17 additions & 0 deletions packages/circuit-ui/components/NotificationModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Copyright 2021, SumUp Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export { useNotificationModal } from './useNotificationModal';
export type { NotificationModalProps } from './NotificationModal';
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright 2021, SumUp Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { createUseModal } from '../ModalContext';

import { NotificationModal } from './NotificationModal';

export const useNotificationModal = createUseModal(NotificationModal);
2 changes: 2 additions & 0 deletions packages/circuit-ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ export { ModalProvider } from './components/ModalContext';
export type { ModalProviderProps } from './components/ModalContext';
export { useModal } from './components/Modal';
export type { ModalProps } from './components/Modal';
export { useNotificationModal } from './components/NotificationModal';
export type { NotificationModalProps } from './components/NotificationModal';

export { TableRow, TableCell, TableHeader } from './components/Table';
export { default as Table } from './components/Table';
Expand Down

0 comments on commit e72cdc8

Please sign in to comment.