Skip to content

Commit

Permalink
feat(app-components): add overlay and dialog component
Browse files Browse the repository at this point in the history
  • Loading branch information
rams23 committed Jan 19, 2021
1 parent 2bd26b8 commit 6b9dcb8
Show file tree
Hide file tree
Showing 13 changed files with 292 additions and 6 deletions.
43 changes: 43 additions & 0 deletions packages/game-app/src/_shared/components/Dialog/Dialog.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import { Meta, Story } from '@storybook/react';

import Dialog from './Dialog';
import useDialog from './useDialog';
import Button from '../Button';
import Box from '../Box';

export default {
title: 'Components/Dialog',
component: Dialog,
argTypes: {},
} as Meta;

const Template: Story<React.ComponentProps<typeof Dialog>> = args => <Dialog {...args} />;

export const Default = Template.bind({});

Default.args = {
open: true,
title: 'Trigger reivew',
};

const ToggleTemplate: Story<React.ComponentProps<typeof Dialog>> = ({ title }) => {
const dialog = useDialog();

return (
<div>
<Button onClick={dialog.open} label="Open" />
<Dialog open={dialog.isOpen} title={title}>
<Box display="flex" justifyContent="center" mt={4}>
<Button label="Close" onClick={dialog.close} />
</Box>
</Dialog>
</div>
);
};

export const Toggle = ToggleTemplate.bind({});

Toggle.args = {
title: 'Share the game',
};
10 changes: 10 additions & 0 deletions packages/game-app/src/_shared/components/Dialog/Dialog.styled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import styled from 'styled-components';

export const DialogContainer = styled.div`
background: rgba(255, 255, 255, 0.6);
box-shadow: 0px 0px 6px #d7d2cb80;
border-radius: 10px;
padding: 40px;
`;

DialogContainer.displayName = 'DialogContainer';
39 changes: 39 additions & 0 deletions packages/game-app/src/_shared/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import GlassOverlay from '../GlassOverlay';
import { DialogContainer } from './Dialog.styled';
import Typography from '../Typography';

type Props = {
/**
* If the dialog is open or not.
* When you change this prop the dialog will disappear using an aniamation
*/
open?: boolean;
/**
* The dialog title
*/
title: string;
/**
* Content placed inside the dialog under the title
*/
children?: React.ReactChild;
};

/**
* Dialog component that appears at the center of the screen with animation inside a {{GlassOverlay}}
*
*/
const Dialog: React.FC<Props> = ({ open, title, children }) => {
return (
<GlassOverlay open={open}>
<DialogContainer>
<Typography variant="title">{title}</Typography>
{children}
</DialogContainer>
</GlassOverlay>
);
};

Dialog.displayName = 'Dialog';

export default Dialog;
3 changes: 3 additions & 0 deletions packages/game-app/src/_shared/components/Dialog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Dialog from './Dialog';

export default Dialog;
24 changes: 24 additions & 0 deletions packages/game-app/src/_shared/components/Dialog/useDialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useCallback, useState } from 'react';

export default function useDialog() {
const [isOpen, setIsOpen] = useState(false);

const open = useCallback(() => {
setIsOpen(true);
}, []);

const close = useCallback(() => {
setIsOpen(false);
}, []);

const toggle = useCallback(() => {
setIsOpen(s => !s);
}, []);

return {
isOpen,
close,
open,
toggle,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useState } from 'react';
import { Meta, Story } from '@storybook/react';

import GlassOverlay from './GlassOverlay';

export default {
title: 'Components/GlassOverlay',
component: GlassOverlay,
argTypes: {},
} as Meta;

const Template: Story<React.ComponentProps<typeof GlassOverlay>> = args => <GlassOverlay {...args} />;

export const Open = Template.bind({});

Open.args = {
open: true,
};

const ToggleTemplate: Story<React.ComponentProps<typeof GlassOverlay>> = args => {
const [state, setState] = useState(false);

return (
<div>
<button onClick={() => setState(s => !s)}>open</button>
<GlassOverlay open={state} />
</div>
);
};

export const Toggle = ToggleTemplate.bind({});

Toggle.args = {};

export const WithContent = Template.bind({});

WithContent.args = {
children: <div style={{ background: 'white' }}>content</div>,
open: true,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import styled, { css, keyframes } from 'styled-components';

const openingAnimation = keyframes`
0% {
opacity: 0;
}
100% {
opacity: 1;
}
`;

const closingAnimation = keyframes`
0% {
opacity: 1;
}
100% {
opacity: 0;
}
`;

export const Overlay = styled.div<{ isClosing: boolean; isOpening: boolean }>`
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 1000;
background-color: rgb(170, 180, 175, 0.4);
backdrop-filter: blur(15px);
${props =>
props.isClosing &&
css`
animation: ${closingAnimation} linear 0.3s;
animation-fill-mode: forwards;
`}
${props =>
props.isOpening &&
css`
animation: ${openingAnimation} linear 0.3s;
`}
`;

Overlay.displayName = 'Overlay';

export const OverlayContentWrapper = styled.div`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
`;
OverlayContentWrapper.displayName = 'OverlayContentWrapper';
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useEffect, useState } from 'react';
import { Overlay, OverlayContentWrapper } from './GlassOverlay.styled';
import { createPortal } from 'react-dom';

type Props = {
open?: boolean;
};

const GlassOverlay: React.FC<Props> = ({ children, open }) => {
const [internalState, setInternalState] = useState<'opening' | 'open' | 'closed' | 'closing'>(
open ? 'open' : 'closed',
);

useEffect(() => {
if (!open && internalState === 'open') {
setInternalState('closing');
setTimeout(() => {
setInternalState('closed');
}, 500);
} else if (open && internalState !== 'open') {
setInternalState('opening');
setTimeout(() => {
setInternalState('open');
}, 500);
}
}, [internalState, open]);

return internalState !== 'closed'
? createPortal(
<Overlay isClosing={internalState === 'closing'} isOpening={internalState === 'opening'}>
<OverlayContentWrapper>{children}</OverlayContentWrapper>
</Overlay>,
document.body,
)
: null;
};

GlassOverlay.displayName = 'OverlayDialog';

export default GlassOverlay;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import GlassOverlay from './GlassOverlay';

export default GlassOverlay;
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type Props = {
name: string;
label?: string;
value: string;
onChange: React.ChangeEventHandler<HTMLInputElement>;
onChange?: React.ChangeEventHandler<HTMLInputElement>;
errorMessage?: string | null;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ type Props = {
name: string;
label?: string;
value: string;
onChange: React.ChangeEventHandler<HTMLInputElement>;
onChange?: React.ChangeEventHandler<HTMLInputElement>;
errorMessage?: string | null;
disabled?: boolean;
};
Expand Down
31 changes: 27 additions & 4 deletions packages/game-app/src/_shared/components/TextInput/TextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import React from 'react';
import styled from 'styled-components';
import { variant, color, ColorProps } from 'styled-system';
import Box from '../Box';
import Typography from '../Typography';

type Props = {
name: string;
label?: string;
value: string;
onChange: React.ChangeEventHandler<HTMLInputElement>;
onChange?: React.ChangeEventHandler<HTMLInputElement>;
errorMessage?: string | null;
type?: string;
disabled?: boolean;
};

const Input = styled.input`
type InputVariants = 'default' | 'clear';

export const Input = styled.input<{ variant: InputVariants } & ColorProps>`
width: 100%;
height: 40px;
padding: 5px 10px;
box-sizing: border-box;
border-radius: 10px;
border: 1px solid #9a9a9a;
&:focus {
outline: none;
Expand All @@ -28,6 +30,18 @@ const Input = styled.input`
&:focus {
border: 1px solid #00867c;
}
${variant({
variants: {
default: {
border: '1px solid #9a9a9a',
},
clear: {
border: 'none',
},
},
})}
${color}
`;

const InputContainer = styled(Box)<React.ComponentProps<typeof Box>>`
Expand All @@ -45,7 +59,16 @@ const TextInput = React.forwardRef<HTMLInputElement, Props>(
{label}
</Typography>
) : null}
<Input ref={ref} disabled={disabled} type={type} value={value} name={name} id={name} onChange={onChange} />
<Input
ref={ref}
variant="default"
disabled={disabled}
type={type}
value={value}
name={name}
id={name}
onChange={onChange}
/>
{errorMessage ? <span className="error-message">{errorMessage}</span> : null}
</InputContainer>
);
Expand Down
6 changes: 6 additions & 0 deletions packages/game-app/src/_shared/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import * as animations from './animations';
import ExpandableTopPanel from './ExpandableTopPanel';
import Typography from './Typography';
import FabDial from './FabDial';
import useDialog from './Dialog/useDialog';
import Dialog from './Dialog';
import { Input } from './TextInput/TextInput';

export {
TextInput,
Expand All @@ -28,4 +31,7 @@ export {
ExpandableTopPanel,
Typography,
FabDial,
useDialog,
Dialog,
Input,
};

0 comments on commit 6b9dcb8

Please sign in to comment.