Skip to content

Commit 593b7a2

Browse files
committed
Creates PopupOKCancel component
1 parent d077b49 commit 593b7a2

16 files changed

+311
-83
lines changed

.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"typescript.tsdk": "node_modules/typescript/lib"
3+
}

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"@types/redux-devtools-extension": "^2.13.2",
3434
"@types/redux-mock-store": "^1.0.0",
3535
"@types/snakecase-keys": "^2.1.0",
36-
"@types/styled-components": "^4.1.8",
36+
"@types/styled-components": "^4.1.14",
3737
"@types/turndown": "^5.0.0",
3838
"apollo-boost": "^0.1.28",
3939
"axios": "^0.18.0",
@@ -108,7 +108,7 @@
108108
"sass-loader": "7.1.0",
109109
"snakecase-keys": "^2.1.0",
110110
"style-loader": "0.23.1",
111-
"styled-components": "^4.1.3",
111+
"styled-components": "^4.2.0",
112112
"terser-webpack-plugin": "1.2.2",
113113
"tslint": "^5.12.1",
114114
"tslint-config-airbnb": "^5.11.1",

src/components/auth/AuthModal.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ const AuthModal: React.SFC<AuthModalProps> = ({
9292
}) => {
9393
const [closed, setClosed] = useState(true);
9494
useEffect(() => {
95-
let timeoutId: NodeJS.Timeout | null = null;
95+
let timeoutId: number | null = null;
9696
if (visible) {
9797
setClosed(false);
9898
} else {

src/components/common/Button.tsx

+20-22
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import * as React from 'react';
22
import styled, { css } from 'styled-components';
3-
import palette from '../../lib/styles/palette';
3+
import palette, { buttonColorMap } from '../../lib/styles/palette';
44

5-
const ButtonBlock = styled.button<{ theme: string }>`
5+
type ColorType = 'teal' | 'gray' | 'darkgray';
6+
7+
const ButtonBlock = styled.button<{ color: ColorType }>`
68
display: inline-flex;
79
align-items: center;
810
height: 2rem;
@@ -13,36 +15,32 @@ const ButtonBlock = styled.button<{ theme: string }>`
1315
cursor: pointer;
1416
outline: none;
1517
border: none;
16-
17-
${props =>
18-
props.theme === 'default' &&
19-
css`
20-
background: ${palette.gray8};
21-
color: white;
22-
border-radius: 1rem;
23-
24-
&:hover,
25-
&:focus {
26-
background: ${palette.gray6};
27-
}
28-
`}
18+
color: white;
19+
background: ${props => buttonColorMap[props.color].background};
20+
color: ${props => buttonColorMap[props.color].color};
21+
&:hover,
22+
&:focus {
23+
background: ${props => buttonColorMap[props.color].hoverBackground};
24+
}
25+
border-radius: 4px;
2926
`;
3027

3128
interface ButtonProps extends React.HTMLProps<HTMLButtonElement> {
32-
theme?: string;
29+
color?: ColorType;
3330
}
3431

35-
const Button: React.SFC<ButtonProps> = ({ theme, children, ref, ...rest }) => {
32+
const Button: React.SFC<ButtonProps> = ({
33+
children,
34+
ref,
35+
color = 'teal',
36+
...rest
37+
}) => {
3638
const htmlProps = rest as any;
3739
return (
38-
<ButtonBlock theme={theme} {...htmlProps}>
40+
<ButtonBlock color={color} {...htmlProps}>
3941
{children}
4042
</ButtonBlock>
4143
);
4244
};
4345

44-
Button.defaultProps = {
45-
theme: 'default', // black button
46-
};
47-
4846
export default Button;

src/components/common/OpaqueLayer.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const { useState, useEffect, useRef } = React;
3333

3434
const OpaqueLayer: React.SFC<OpaqueLayerProps> = ({ visible }) => {
3535
const [animate, setAnimate] = useState(false);
36-
const timeoutId = useRef<NodeJS.Timeout | null>(null);
36+
const timeoutId = useRef<number | null>(null);
3737
const mounted = useRef(false);
3838
const [closed, setClosed] = useState(true);
3939

src/components/common/PopupBase.tsx

+41-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as React from 'react';
2-
import styled from 'styled-components';
2+
import styled, { css } from 'styled-components';
33
import OpaqueLayer from './OpaqueLayer';
44
import zIndexes from '../../lib/styles/zIndexes';
5+
import transitions from '../../lib/styles/transitions';
56

67
const PopupBaseBlock = styled.div`
78
position: fixed;
@@ -13,24 +14,55 @@ const PopupBaseBlock = styled.div`
1314
display: flex;
1415
align-items: center;
1516
justify-content: center;
16-
& > .wrapper {
17-
width: 25rem;
18-
background: white;
19-
padding: 2rem 1.5rem;
20-
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.09);
21-
}
17+
`;
18+
19+
const PopupWrapper = styled.div<{ visible: boolean }>`
20+
width: 25rem;
21+
border-radius: 4px;
22+
background: white;
23+
padding: 2rem 1.5rem;
24+
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.09);
25+
${props =>
26+
props.visible
27+
? css`
28+
animation: ${transitions.popInFromBottom} 0.4s forwards ease-in-out;
29+
`
30+
: css`
31+
animation: ${transitions.popOutToBottom} 0.2s forwards ease-in-out;
32+
`}
2233
`;
2334

2435
interface PopupBaseProps {
2536
visible: boolean;
2637
}
2738

28-
const PopupBase: React.SFC<PopupBaseProps> = ({ visible }) => {
39+
const { useState, useEffect } = React;
40+
41+
const PopupBase: React.SFC<PopupBaseProps> = ({ visible, children }) => {
42+
const [closed, setClosed] = useState(true);
43+
useEffect(() => {
44+
let timeoutId: number | null = null;
45+
if (visible) {
46+
setClosed(false);
47+
} else {
48+
timeoutId = setTimeout(() => {
49+
setClosed(true);
50+
}, 200);
51+
}
52+
return () => {
53+
if (timeoutId) {
54+
clearTimeout(timeoutId);
55+
}
56+
};
57+
}, [visible]);
58+
59+
if (!visible && closed) return null;
60+
2961
return (
3062
<>
3163
<OpaqueLayer visible={visible} />
3264
<PopupBaseBlock>
33-
<div className="wrapper">Hey tehre!</div>
65+
<PopupWrapper visible={visible}>{children}</PopupWrapper>
3466
</PopupBaseBlock>
3567
</>
3668
);
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as React from 'react';
2+
import styled from 'styled-components';
3+
import PopupBase from './PopupBase';
4+
import Button from './Button';
5+
import palette from '../../lib/styles/palette';
6+
7+
const PopupOKCancelBlock = styled.div`
8+
h3 {
9+
margin: 0;
10+
font-size: 1.5rem;
11+
color: ${palette.gray8};
12+
line-height: 1.5;
13+
font-weight: bold;
14+
}
15+
p {
16+
line-height: 1.5;
17+
font-size: 1.125rem;
18+
color: ${palette.gray7};
19+
}
20+
.button-area {
21+
display: flex;
22+
justify-content: flex-end;
23+
button + button {
24+
margin-left: 0.75rem;
25+
}
26+
}
27+
`;
28+
29+
export interface PopupOKCancelProps {
30+
visible: boolean;
31+
title?: string;
32+
onConfirm?: () => any;
33+
onCancel?: () => any;
34+
children: React.ReactNode;
35+
}
36+
37+
const PopupOKCancel: React.FC<PopupOKCancelProps> = ({
38+
visible,
39+
title,
40+
children,
41+
onConfirm,
42+
onCancel,
43+
}) => {
44+
return (
45+
<PopupBase visible={visible}>
46+
<PopupOKCancelBlock>
47+
{title && <h3>{title}</h3>}
48+
<p>{children}</p>
49+
<div className="button-area">
50+
<Button color="gray" onClick={onCancel}>
51+
취소
52+
</Button>
53+
<Button onClick={onConfirm}>확인</Button>
54+
</div>
55+
</PopupOKCancelBlock>
56+
</PopupBase>
57+
);
58+
};
59+
60+
export default PopupOKCancel;

src/components/common/RoundButton.tsx

+7-31
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,10 @@
11
import * as React from 'react';
22
import styled, { css } from 'styled-components';
3-
import palette from '../../lib/styles/palette';
3+
import palette, { buttonColorMap } from '../../lib/styles/palette';
44
import { Route } from 'react-router';
55

66
type ButtonSize = 'SMALL' | 'DEFAULT' | 'LARGE';
77

8-
const colorMap: {
9-
[color: string]: {
10-
background: string;
11-
color: string;
12-
hoverBackground: string;
13-
};
14-
} = {
15-
teal: {
16-
background: palette.teal6,
17-
color: 'white',
18-
hoverBackground: palette.teal5,
19-
},
20-
gray: {
21-
background: palette.gray2,
22-
color: palette.gray7,
23-
hoverBackground: palette.gray1,
24-
},
25-
darkGray: {
26-
background: palette.gray8,
27-
color: 'white',
28-
hoverBackground: palette.gray6,
29-
},
30-
};
31-
328
type RoundButtonBlockProps = {
339
inline?: boolean;
3410
color: string;
@@ -77,20 +53,20 @@ const RoundButtonBlock = styled.button<RoundButtonBlockProps>`
7753
border: none;
7854
outline: none;
7955
font-weight: bold;
80-
background: ${props => colorMap[props.color].background};
81-
color: ${props => colorMap[props.color].color};
56+
background: ${props => buttonColorMap[props.color].background};
57+
color: ${props => buttonColorMap[props.color].color};
8258
&:hover {
83-
background: ${props => colorMap[props.color].hoverBackground};
59+
background: ${props => buttonColorMap[props.color].hoverBackground};
8460
}
8561
8662
${props =>
8763
props.border &&
8864
css<RoundButtonBlockProps>`
8965
background: transparent;
90-
border: 1px solid ${props => colorMap[props.color].background};
91-
color: ${props => colorMap[props.color].background};
66+
border: 1px solid ${props => buttonColorMap[props.color].background};
67+
color: ${props => buttonColorMap[props.color].background};
9268
&:hover {
93-
background: ${props => colorMap[props.color].background};
69+
background: ${props => buttonColorMap[props.color].background};
9470
color: white;
9571
}
9672
`}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as React from 'react';
2+
import { render, fireEvent } from 'react-testing-library';
3+
import PopupOKCancel, { PopupOKCancelProps } from '../PopupOKCancel';
4+
5+
describe('PopupOKCancel', () => {
6+
const setup = (props: Partial<PopupOKCancelProps> = {}) => {
7+
const initialProps: PopupOKCancelProps = {
8+
visible: true,
9+
title: '제목',
10+
children: '내용',
11+
};
12+
const utils = render(<PopupOKCancel {...initialProps} {...props} />);
13+
return {
14+
...utils,
15+
};
16+
};
17+
it('renders properly', () => {
18+
const utils = setup();
19+
utils.getByText('제목');
20+
utils.getByText('내용');
21+
});
22+
it('matches snapshot', () => {
23+
const { container } = setup();
24+
expect(container).toMatchSnapshot();
25+
});
26+
describe('visible property works properly', () => {
27+
it('false', () => {
28+
const utils = setup({ visible: false });
29+
const title = utils.queryByText('제목');
30+
expect(title).toBe(null);
31+
});
32+
it('true', () => {
33+
const utils = setup({ visible: true });
34+
utils.getByText('제목');
35+
});
36+
});
37+
it('button works properly', () => {
38+
const [onConfirm, onCancel] = [jest.fn(), jest.fn()];
39+
const utils = setup({
40+
onConfirm,
41+
onCancel,
42+
});
43+
const confirm = utils.getByText('확인');
44+
const cancel = utils.getByText('취소');
45+
fireEvent.click(confirm);
46+
expect(onConfirm).toBeCalled();
47+
fireEvent.click(cancel);
48+
expect(onCancel).toBeCalled();
49+
});
50+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`PopupOKCancel matches snapshot 1`] = `
4+
<div>
5+
<div
6+
class="sc-bdVaJa kNCPvv"
7+
/>
8+
<div
9+
class="sc-bwzfXH fIoYbF"
10+
>
11+
<div
12+
class="sc-htpNat rpSHm"
13+
>
14+
<div
15+
class="sc-ifAKCX jgCToZ"
16+
>
17+
<h3>
18+
제목
19+
</h3>
20+
<p>
21+
내용
22+
</p>
23+
<div
24+
class="button-area"
25+
>
26+
<button
27+
class="sc-bxivhb gSCPGr"
28+
color="gray"
29+
>
30+
취소
31+
</button>
32+
<button
33+
class="sc-bxivhb iVzCmN"
34+
color="teal"
35+
>
36+
확인
37+
</button>
38+
</div>
39+
</div>
40+
</div>
41+
</div>
42+
</div>
43+
`;

0 commit comments

Comments
 (0)