Skip to content

Commit

Permalink
Add ability to change DialogModal background color (#767)
Browse files Browse the repository at this point in the history
* Add ability to change DialogModal background color

* Address PR feedback

* Implement and use `withPortalContainer` helper

* Set close icon background color dynamically; update stories
  • Loading branch information
joeyquarters authored Feb 11, 2021
1 parent fa54089 commit 3380390
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 9 deletions.
4 changes: 3 additions & 1 deletion src/constants/themes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ type BoxShadows =
| typeof primaryTheme['BOX_SHADOWS']
| typeof secondaryTheme['BOX_SHADOWS'];

type Colors = typeof primaryTheme['COLORS'] | typeof secondaryTheme['COLORS'];
export type Colors =
| typeof primaryTheme['COLORS']
| typeof secondaryTheme['COLORS'];

type Fonts = typeof primaryTheme['FONTS'] | typeof secondaryTheme['FONTS'];

Expand Down
121 changes: 121 additions & 0 deletions src/shared-components/dialogModal/__snapshots__/test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<DialogModal /> renders dialog modal with custom color 1`] = `
.emotion-2 {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 999999;
background-color: rgba(58,55,75,0.7);
-webkit-transition: opacity 250ms cubic-bezier(0.075,0.82,0.165,1);
transition: opacity 250ms cubic-bezier(0.075,0.82,0.165,1);
}
.emotion-2.entering,
.emotion-2.exiting,
.emotion-2.exited {
opacity: 0;
}
.emotion-2.entered {
opacity: 1;
}
@media (min-width:768px) {
.emotion-2 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-flow: row nowrap;
-ms-flex-flow: row nowrap;
flex-flow: row nowrap;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
}
.emotion-0 {
width: 100%;
margin: 0 auto;
position: absolute;
bottom: 0;
left: 0;
right: 0;
border-top-left-radius: 32px;
border-top-right-radius: 32px;
box-shadow: 0px -8px 24px rgba(52,51,82,0.05);
background: #F8F8FA;
padding: 3.5rem 1.5rem 2rem;
overflow-y: auto;
max-height: 700px;
-webkit-transition: -webkit-transform 250ms cubic-bezier(0.075,0.82,0.165,1);
-webkit-transition: transform 250ms cubic-bezier(0.075,0.82,0.165,1);
transition: transform 250ms cubic-bezier(0.075,0.82,0.165,1);
}
.emotion-0.entered {
-webkit-transform: translateY(0%);
-ms-transform: translateY(0%);
transform: translateY(0%);
}
.emotion-0.entering,
.emotion-0.exiting,
.emotion-0.exited {
-webkit-transform: translateY(100%);
-ms-transform: translateY(100%);
transform: translateY(100%);
}
.emotion-0 p {
margin-bottom: 1.5rem;
}
.emotion-0 p:last-of-type {
margin-bottom: 2rem;
}
@media (min-width:768px) {
.emotion-0 {
position: relative;
width: 456px;
border-radius: 8px;
padding: 3.5rem;
}
.emotion-0.entering,
.emotion-0.exiting,
.emotion-0.exited {
-webkit-transform: translateY(40%);
-ms-transform: translateY(40%);
transform: translateY(40%);
}
}
<div
class="entering emotion-2 emotion-3"
>
<span
hidden=""
/>
<div
class="entering emotion-0 emotion-1"
>
<div>
Dialog Modal Children Content
</div>
</div>
<span
hidden=""
/>
</div>
`;
15 changes: 15 additions & 0 deletions src/shared-components/dialogModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useRef, useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import { Transition } from 'react-transition-group';
import { FocusScope } from '@react-aria/focus';
import { useTheme } from 'emotion-theming';

import { CrossIcon } from '../../icons';
import {
Expand All @@ -11,8 +12,13 @@ import {
ModalTitle,
CrossIconContainer,
} from './style';
import { Colors, primaryTheme, secondaryTheme } from '../../constants';

export interface DialogModalProps {
/**
* DialogModal background color. Defaults to the current theme's `white` if not specified.
*/
backgroundColor?: Colors['background'];
/**
* Dialog Modal content.
* Must contain at least 1 button and is responsible for closing the modal.
Expand Down Expand Up @@ -41,11 +47,14 @@ const getDomNode = () =>
* Dialog Modals should always contain at least 1 button and the logic should close the modal at some point.
*/
export const DialogModal = ({
backgroundColor,
children,
onCloseIconClick,
title = '',
...rest
}: DialogModalProps) => {
const theme = useTheme();
const backgroundColorWithTheme = backgroundColor || theme.COLORS.white;
const [isClosing, setIsClosing] = useState(false);

const domNode = useRef<HTMLElement>(getDomNode());
Expand Down Expand Up @@ -87,11 +96,13 @@ export const DialogModal = ({
<Overlay className={transitionState} {...rest}>
<FocusScope contain restoreFocus autoFocus>
<ModalContainer
backgroundColor={backgroundColorWithTheme}
className={transitionState}
onKeyDown={handleKeyDown}
>
{onCloseIconClick && (
<CrossIconContainer
backgroundColor={backgroundColorWithTheme}
onClick={handleCloseIntent}
role="button"
tabIndex={0}
Expand All @@ -112,6 +123,10 @@ export const DialogModal = ({
};

DialogModal.propTypes = {
backgroundColor: PropTypes.oneOf([
primaryTheme.COLORS.background,
secondaryTheme.COLORS.background,
]),
children: PropTypes.node.isRequired,
onCloseIconClick: PropTypes.func,
title: PropTypes.string,
Expand Down
14 changes: 9 additions & 5 deletions src/shared-components/dialogModal/style.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styled from '@emotion/styled';

import Typography from '../typography';
import { MEDIA_QUERIES, SPACER, Z_SCALE } from '../../constants';
import { Colors, MEDIA_QUERIES, SPACER, Z_SCALE } from '../../constants';

export const Overlay = styled.div`
position: fixed;
Expand Down Expand Up @@ -31,7 +31,9 @@ export const Overlay = styled.div`
}
`;

export const ModalContainer = styled.div`
export const ModalContainer = styled.div<{
backgroundColor: Colors['background'] | Colors['white'];
}>`
width: 100%;
margin: 0 auto;
position: absolute;
Expand All @@ -41,7 +43,7 @@ export const ModalContainer = styled.div`
border-top-left-radius: ${({ theme }) => theme.BORDER_RADIUS.large};
border-top-right-radius: ${({ theme }) => theme.BORDER_RADIUS.large};
box-shadow: ${({ theme }) => theme.BOX_SHADOWS.modal};
background: ${({ theme }) => theme.COLORS.white};
background: ${({ backgroundColor }) => backgroundColor};
padding: ${SPACER.x4large} ${SPACER.large} ${SPACER.xlarge};
overflow-y: auto;
max-height: 700px;
Expand Down Expand Up @@ -84,15 +86,17 @@ export const ModalTitle = styled(Typography.Title)`
margin-bottom: ${SPACER.small};
`;

export const CrossIconContainer = styled.div`
export const CrossIconContainer = styled.div<{
backgroundColor: Colors['background'] | Colors['white'];
}>`
position: absolute;
top: 8px;
right: 12px;
z-index: ${Z_SCALE.e2000};
width: 40px;
height: 40px;
border-radius: 50%;
background: ${({ theme }) => theme.COLORS.white};
background: ${({ backgroundColor }) => backgroundColor};
display: flex;
flex-flow: row nowrap;
justify-content: center;
Expand Down
12 changes: 12 additions & 0 deletions src/shared-components/dialogModal/test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { primaryTheme } from 'src/constants/themes';
import { render } from 'src/tests/testingLibraryHelpers';

import { DialogModal } from './index';
Expand All @@ -17,4 +18,15 @@ describe('<DialogModal />', () => {
getAllByText(modalTitle);
getAllByText(modalBody);
});

it('renders dialog modal with custom color', () => {
const { container } = render(
<DialogModal backgroundColor={primaryTheme.COLORS.background}>
<div>{modalBody}</div>
</DialogModal>,
{ withPortalContainer: true },
);

expect(container.firstElementChild).toMatchSnapshot();
});
});
24 changes: 23 additions & 1 deletion src/tests/testingLibraryHelpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,24 @@ import { primaryTheme, ThemeType } from '../constants';

interface RenderOptions extends ReactTestingLibrary.RenderOptions {
theme?: ThemeType;
withPortalContainer?: boolean;
}

/**
* Many of our pages rely on components that make use of portals.
*
* Unit tests for components that do that **do not include such a portal
* container in their unit test** will have test failures on CI due to
* serialization order being off.
*/
const usePortalContainer = () => {
const portalContainer = document.createElement('div');
portalContainer.setAttribute('id', 'reactPortalSection');
document.body.appendChild(portalContainer);

return portalContainer;
};

// We customize @testing-library methods to bake-in theming and keep unit tests DRY.
// We do not use ReactTestingLibrary.render(Component, { wrapper }) option because
// `@testing-library/react` is (somehow) overwriting React with its own API when used.
Expand All @@ -17,11 +33,17 @@ const customRender = (
Component: React.ReactElement,
options: RenderOptions = {},
): ReactTestingLibrary.RenderResult => {
const { theme = primaryTheme, ...rest } = options;
const {
theme = primaryTheme,
container,
withPortalContainer,
...rest
} = options;

return ReactTestingLibrary.render(
<ThemeProvider theme={theme}>{Component}</ThemeProvider>,
{
container: withPortalContainer ? usePortalContainer() : container,
...rest,
},
);
Expand Down
Loading

0 comments on commit 3380390

Please sign in to comment.