├── .babelrc
├── .gitignore
├── .storybook
├── addons.js
├── config.js
├── presets.js
└── webpack.config.js
├── README.md
├── package.json
├── rollup.config.js
├── src
├── Button
│ ├── Button.stories.tsx
│ └── Button.tsx
├── ButtonGroup
│ ├── ButtonGroup.stories.tsx
│ └── ButtonGroup.tsx
├── Dialog
│ ├── Dialog.stories.tsx
│ └── Dialog.tsx
├── Icon
│ ├── Icon.stories.tsx
│ ├── Icon.tsx
│ └── svg
│ │ ├── exit.svg
│ │ ├── heart.svg
│ │ ├── index.ts
│ │ └── pencil.svg
├── index.ts
└── typings.d.ts
├── tsconfig.json
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["react-app", { "flow": false, "typescript": true }]]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | storybook-static
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-links/register';
3 | import '@storybook/addon-knobs/register';
4 | import '@storybook/addon-viewport/register';
5 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/react';
2 |
3 | // automatically import all files ending in *.stories.js
4 | configure(require.context('../src', true, /\.stories\.(js|mdx|tsx)$/), module);
5 |
--------------------------------------------------------------------------------
/.storybook/presets.js:
--------------------------------------------------------------------------------
1 | module.exports = ['@storybook/addon-docs/react/preset'];
2 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = ({ config, mode }) => {
2 | config.module.rules.push({
3 | test: /\.(ts|tsx)$/,
4 | use: [
5 | {
6 | loader: require.resolve('babel-loader'),
7 | options: {
8 | presets: [['react-app', { flow: false, typescript: true }]],
9 | plugins: [
10 | [
11 | require.resolve('babel-plugin-named-asset-import'),
12 | {
13 | loaderMap: {
14 | svg: {
15 | ReactComponent: '@svgr/webpack?-svgo,+titleProp,+ref![path]'
16 | }
17 | }
18 | }
19 | ]
20 | ]
21 | }
22 | },
23 | require.resolve('react-docgen-typescript-loader')
24 | ]
25 | });
26 | config.resolve.extensions.push('.ts', '.tsx');
27 | return config;
28 | };
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-uikit-sample
2 |
3 | A Sample React UI Kit.
4 |
5 | [Storybook](https://react-uikit-sample.surge.sh)
6 |
7 | Used tools:
8 |
9 | - TypeScript
10 | - Storybook
11 | - Rollup
12 |
13 | ## Tags
14 |
15 | 각 섹션별로 Git Repository 의 태그가 생성되어 있습니다.
16 |
17 | - [2-1. Knobs Addon](https://github.com/velopert/storybook-tutorial-code/tree/02a)
18 | - [2-2. Actions 애드온](https://github.com/velopert/storybook-tutorial-code/tree/02b)
19 | - [2-3. Docs 애드온](https://github.com/velopert/storybook-tutorial-code/tree/02c)
20 | - [3. TypeScript 사용하기](https://github.com/velopert/storybook-tutorial-code/tree/03)
21 | - [4-1. Button 만들기](https://github.com/velopert/storybook-tutorial-code/tree/04a)
22 | - [4-2. ButtonGroup 만들기](https://github.com/velopert/storybook-tutorial-code/tree/04b)
23 | - [4-3. Icon 만들기](https://github.com/velopert/storybook-tutorial-code/tree/04c)
24 | - [4-4. Dialog 만들기](https://github.com/velopert/storybook-tutorial-code/tree/04d)
25 | - [5-1. Rollup으로 라이브러리 번들하기](https://github.com/velopert/storybook-tutorial-code/tree/05a)
26 | - [5-2. npm에 publish 하기](https://github.com/velopert/storybook-tutorial-code/tree/05b)
27 |
28 | 프로젝트를 clone 했을 경우 다음 명령어를 사용하여 특정 태그의 코드를 볼 수 있습니다.
29 |
30 | ```
31 | git checkout 02a
32 | ```
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-uikit-sample",
3 | "version": "1.0.3",
4 | "module": "dist/index.js",
5 | "license": "MIT",
6 | "types": "dist/types/index.d.ts",
7 | "files": [
8 | "/dist"
9 | ],
10 | "dependencies": {
11 | "react-spring": "^8.0.27"
12 | },
13 | "devDependencies": {
14 | "@babel/core": "^7.7.2",
15 | "@storybook/addon-actions": "^5.2.6",
16 | "@storybook/addon-docs": "^5.2.6",
17 | "@storybook/addon-knobs": "^5.2.6",
18 | "@storybook/addon-links": "^5.2.6",
19 | "@storybook/addon-viewport": "^5.2.6",
20 | "@storybook/addons": "^5.2.6",
21 | "@storybook/react": "^5.2.6",
22 | "@svgr/rollup": "^4.3.3",
23 | "babel-loader": "^8.0.6",
24 | "babel-plugin-named-asset-import": "^0.3.4",
25 | "babel-preset-react-app": "^9.0.2",
26 | "react-docgen-typescript-loader": "^3.4.0",
27 | "rollup": "^1.27.2",
28 | "rollup-plugin-babel": "^4.3.3",
29 | "rollup-plugin-commonjs": "^10.1.0",
30 | "rollup-plugin-node-resolve": "^5.2.0",
31 | "rollup-plugin-peer-deps-external": "^2.2.0",
32 | "rollup-plugin-url": "^3.0.0",
33 | "typescript": "^3.7.2"
34 | },
35 | "scripts": {
36 | "storybook": "start-storybook -p 6006",
37 | "build-storybook": "build-storybook",
38 | "build": "rollup -c",
39 | "build:types": "tsc --emitDeclarationOnly"
40 | },
41 | "peerDependencies": {
42 | "@emotion/core": "^10.0.22",
43 | "react": "^16.12.0",
44 | "react-dom": "^16.12.0",
45 | "react-spring": "^8.0.27"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from 'rollup-plugin-commonjs';
2 | import resolve from 'rollup-plugin-node-resolve';
3 | import babel from 'rollup-plugin-babel';
4 | import pkg from './package.json';
5 | import external from 'rollup-plugin-peer-deps-external';
6 | import svgr from '@svgr/rollup';
7 | import url from 'rollup-plugin-url';
8 | import peerDepsExternal from 'rollup-plugin-peer-deps-external';
9 |
10 | const extensions = ['.js', '.jsx', '.ts', '.tsx']; // 어떤 확장자를 처리 할 지 정함
11 |
12 | // babel-preset-react-app를 사용한다면 BABEL_ENV를 필수로 설정해야함.
13 | process.env.BABEL_ENV = 'production';
14 |
15 | export default {
16 | input: './src/index.ts', // 어떤 파일부터 불러올지 정함.
17 | plugins: [
18 | peerDepsExternal() /* peerDependencies로 설치한 라이브러리들을 external 모듈로 설정
19 | 즉, 번들링된 결과에 포함시키지 않음 */,
20 | resolve({ extensions }), // node_modules 에서 모듈을 불러올 수 있게 해줌. ts/tsx 파일도 불러올 수 있게 해줌
21 | commonjs({
22 | include: 'node_modules/**'
23 | }), // CommonJS 형태로 만들어진 모듈도 불러와서 사용 할 수 있게 해줌. 현재 프로젝트 상황에서는 없어도 무방함
24 | babel({ extensions, include: ['src/**/*'], runtimeHelpers: true }), // Babel을 사용 할 수 있게 해줌
25 | url(), // 미디어 파일을 dataURI 형태로 불러와서 사용 할 수 있게 해줌.
26 | svgr() // SVG를 컴포넌트로 사용 할 수 있게 해줌.
27 | ],
28 | output: [
29 | {
30 | file: pkg.module, // 번들링한 파일을 저장 할 경로
31 | format: 'es' // ES Module 형태로 번들링함
32 | }
33 | ]
34 | };
35 |
--------------------------------------------------------------------------------
/src/Button/Button.stories.tsx:
--------------------------------------------------------------------------------
1 | /** @jsx jsx */
2 | import Button from './Button';
3 | import { jsx, css } from '@emotion/core';
4 | import { withKnobs, text, boolean, select } from '@storybook/addon-knobs';
5 | import { action } from '@storybook/addon-actions';
6 | import ButtonGroup from '../ButtonGroup/ButtonGroup';
7 | import Icon from '../Icon/Icon';
8 |
9 | export default {
10 | title: 'components|Button',
11 | component: Button,
12 | decorators: [withKnobs]
13 | };
14 |
15 | export const button = () => {
16 | const label = text('children', 'BUTTON');
17 | const size = select('size', ['small', 'medium', 'big'], 'medium');
18 | const theme = select(
19 | 'theme',
20 | ['primary', 'secondary', 'tertiary'],
21 | 'primary'
22 | );
23 | const disabled = boolean('disabled', false);
24 | const width = text('width', '');
25 |
26 | return (
27 |
36 | );
37 | };
38 |
39 | button.story = {
40 | name: 'Default'
41 | };
42 |
43 | export const primaryButton = () => {
44 | return ;
45 | };
46 |
47 | export const secondaryButton = () => {
48 | return ;
49 | };
50 |
51 | export const tertiaryButton = () => {
52 | return ;
53 | };
54 |
55 | const buttonWrapper = css`
56 | .description {
57 | margin-bottom: 0.5rem;
58 | }
59 | & > div + div {
60 | margin-top: 2rem;
61 | }
62 | `;
63 |
64 | export const sizes = () => {
65 | return (
66 |
67 |
68 |
Small
69 |
70 |
71 |
72 |
Medium
73 |
74 |
75 |
76 |
Big
77 |
78 |
79 |
80 | );
81 | };
82 |
83 | export const disabled = () => {
84 | return (
85 |
86 |
87 |
88 |
89 |
90 |
93 |
94 |
95 |
98 |
99 |
100 | );
101 | };
102 |
103 | export const customSized = () => {
104 | return (
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | );
114 | };
115 |
116 | export const withIcon = () => {
117 | return (
118 |
119 |
120 |
123 |
126 |
129 |
130 |
131 | );
132 | };
133 |
134 | export const iconOnly = () => {
135 | return (
136 |
137 |
138 |
141 |
144 |
147 |
148 |
149 | );
150 | };
151 |
--------------------------------------------------------------------------------
/src/Button/Button.tsx:
--------------------------------------------------------------------------------
1 | /** @jsx jsx */
2 | import { jsx, css } from '@emotion/core';
3 |
4 | type ButtonProps = {
5 | /** 버튼 안의 내용 */
6 | children: React.ReactNode;
7 | /** 클릭했을 때 호출할 함수 */
8 | onClick?: (e?: React.MouseEvent) => void;
9 | /** 버튼의 생김새를 설정합니다. */
10 | theme: 'primary' | 'secondary' | 'tertiary';
11 | /** 버튼의 크기를 설정합니다. */
12 | size: 'small' | 'medium' | 'big';
13 | /** 버튼을 비활성화 시킵니다. */
14 | disabled?: boolean;
15 | /** 버튼의 너비를 임의로 설정합니다. */
16 | width?: string | number;
17 | /** 버튼에서 아이콘만 보여줄 때 이 값을 `true`로 설정하세요. */
18 | iconOnly?: boolean;
19 | };
20 |
21 | /** `Button` 컴포넌트는 어떠한 작업을 트리거 할 때 사용합니다. */
22 | const Button = ({
23 | children,
24 | theme,
25 | size,
26 | disabled,
27 | width,
28 | iconOnly,
29 | onClick
30 | }: ButtonProps) => {
31 | return (
32 |
45 | );
46 | };
47 |
48 | Button.defaultProps = {
49 | theme: 'primary',
50 | size: 'medium'
51 | };
52 |
53 | const style = css`
54 | outline: none;
55 | border: none;
56 | box-sizing: border-box;
57 | border-radius: 0.25rem;
58 | line-height: 1;
59 | font-weight: 600;
60 | display: inline-flex;
61 | align-items: center;
62 | justify-content: center;
63 | &:focus {
64 | box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.2);
65 | }
66 | &:disabled {
67 | cursor: not-allowed;
68 | }
69 | svg {
70 | width: 1em;
71 | margin-right: 1em;
72 | }
73 | `;
74 |
75 | const themes = {
76 | primary: css`
77 | background: #20c997;
78 | color: white;
79 | svg {
80 | fill: white;
81 | }
82 | &:hover:enabled {
83 | background: #38d9a9;
84 | }
85 | &:active:enabled {
86 | background: #12b886;
87 | }
88 | &:disabled {
89 | background: #aed9cc;
90 | }
91 | `,
92 | secondary: css`
93 | background: #e9ecef;
94 | color: #343a40;
95 | svg {
96 | fill: #343a40;
97 | }
98 | &:hover:enabled {
99 | background: #f1f3f5;
100 | }
101 | &:active:enabled {
102 | background: #dee2e6;
103 | }
104 | &:disabled {
105 | color: #c6d3e1;
106 | svg {
107 | fill: #c6d3e1;
108 | }
109 | }
110 | `,
111 | tertiary: css`
112 | background: none;
113 | color: #20c997;
114 | svg {
115 | fill: #20c997;
116 | }
117 | &:hover:enabled {
118 | background: #e6fcf5;
119 | }
120 | &:active:enabled {
121 | background: #c3fae8;
122 | }
123 | &:disabled {
124 | color: #bcd9d0;
125 | svg {
126 | fill: #bcd9d0;
127 | }
128 | }
129 | `
130 | };
131 |
132 | const sizes = {
133 | small: css`
134 | height: 1.75rem;
135 | font-size: 0.75rem;
136 | padding: 0 0.875rem;
137 | `,
138 | medium: css`
139 | height: 2.5rem;
140 | font-size: 1rem;
141 | padding: 0 1rem;
142 | `,
143 | big: css`
144 | height: 3rem;
145 | font-size: 1.125rem;
146 | padding: 0 1.5rem;
147 | `
148 | };
149 |
150 | const iconOnlyStyle = css`
151 | padding: 0;
152 | border-radius: 50%;
153 | svg {
154 | margin: 0;
155 | }
156 | `;
157 |
158 | const iconOnlySizes = {
159 | small: css`
160 | width: 1.75rem;
161 | `,
162 | medium: css`
163 | width: 2.5rem;
164 | `,
165 | big: css`
166 | width: 3rem;
167 | `
168 | };
169 |
170 | export default Button;
171 |
--------------------------------------------------------------------------------
/src/ButtonGroup/ButtonGroup.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ButtonGroup from './ButtonGroup';
3 | import Button from '../Button/Button';
4 | import { withKnobs, text, radios, boolean } from '@storybook/addon-knobs';
5 |
6 | export default {
7 | title: 'components|ButtonGroup',
8 | component: ButtonGroup,
9 | decorators: [withKnobs]
10 | };
11 |
12 | export const buttonGroup = () => {
13 | const direction = radios(
14 | 'direction',
15 | { Row: 'row', Column: 'column' },
16 | 'row'
17 | );
18 | const rightAlign = boolean('rightAlign', false);
19 | const gap = text('gap', '0.5rem');
20 |
21 | return (
22 |
23 |
24 |
25 |
26 | );
27 | };
28 |
29 | buttonGroup.story = {
30 | name: 'Default'
31 | };
32 |
33 | export const rightAlign = () => {
34 | return (
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | export const column = () => {
43 | return (
44 |
45 |
46 |
47 |
48 | );
49 | };
50 |
51 | export const customGap = () => {
52 | return (
53 |
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | export const customGapColumn = () => {
61 | return (
62 |
63 |
64 |
65 |
66 | );
67 | };
68 |
--------------------------------------------------------------------------------
/src/ButtonGroup/ButtonGroup.tsx:
--------------------------------------------------------------------------------
1 | /** @jsx jsx */
2 | import { css, jsx } from '@emotion/core';
3 |
4 | export type ButtonGroupProps = {
5 | /** 버튼을 보여줄 방향 */
6 | direction: 'row' | 'column';
7 | /** 버튼을 우측에 보여줍니다. */
8 | rightAlign?: boolean;
9 | /** 버튼과 버튼사이의 간격을 설정합니다. */
10 | gap: number | string;
11 | /** 버튼 그룹에서 보여줄 버튼들 */
12 | children: React.ReactNode;
13 | /* 스타일 커스터마이징 하고싶을 때 사용 */
14 | className?: string;
15 | };
16 |
17 | /**
18 | * 여러개의 `Button` 컴포넌트를 보여주고 싶거나, 버튼을 우측에 정렬하고 싶을 땐 `ButtonGroup` 컴포넌트를 사용하세요.
19 | */
20 | const ButtonGroup = ({
21 | direction,
22 | rightAlign,
23 | children,
24 | gap,
25 | className
26 | }: ButtonGroupProps) => {
27 | return (
28 |
39 | {children}
40 |
41 | );
42 | };
43 |
44 | ButtonGroup.defaultProps = {
45 | direction: 'row',
46 | gap: '0.5rem'
47 | };
48 |
49 | // direction 에 따라 margin-left 또는 margin-top 설정
50 | const gapStyle = (direction: 'row' | 'column', gap: number | string) => {
51 | const marginType = direction === 'row' ? 'marginLeft' : 'marginTop';
52 | return css({
53 | 'button + button': {
54 | [marginType]: gap
55 | }
56 | });
57 | };
58 |
59 | const rightAlignStyle = css`
60 | justify-content: flex-end;
61 | `;
62 |
63 | export default ButtonGroup;
64 |
--------------------------------------------------------------------------------
/src/Dialog/Dialog.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Dialog from './Dialog';
3 | import { withKnobs, text, boolean } from '@storybook/addon-knobs';
4 |
5 | export default {
6 | title: 'components|Dialog',
7 | component: Dialog,
8 | parameters: {
9 | docs: {
10 | inlineStories: false
11 | }
12 | },
13 | decorators: [withKnobs]
14 | };
15 |
16 | export const dialog = () => {
17 | const title = text('title', '결제 성공');
18 | const description = text('description', '결제가 성공적으로 이루어졌습니다.');
19 | const visible = boolean('visible', true);
20 | const confirmText = text('confirmText', '확인');
21 | const cancelText = text('cancelText', '취소');
22 | const cancellable = boolean('cancellable', false);
23 |
24 | return (
25 |
33 | );
34 | };
35 |
36 | dialog.story = {
37 | name: 'Default'
38 | };
39 |
40 | export const cancellable = () => {
41 | return (
42 |
49 | );
50 | };
51 |
52 | export const customContent = () => {
53 | return (
54 |
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/src/Dialog/Dialog.tsx:
--------------------------------------------------------------------------------
1 | /** @jsx jsx */
2 | import { Fragment } from 'react';
3 | import { css, jsx } from '@emotion/core';
4 | import ButtonGroup from '../ButtonGroup/ButtonGroup';
5 | import Button from '../Button/Button';
6 | import { useTransition, animated } from 'react-spring';
7 |
8 | export type DialogProps = {
9 | visible: boolean;
10 | title?: string;
11 | description?: string;
12 | children?: React.ReactNode;
13 | hideButtons?: boolean;
14 | cancellable?: boolean;
15 | cancelText: string;
16 | confirmText: string;
17 | onCancel?: () => void;
18 | onConfirm?: () => void;
19 | };
20 |
21 | const Dialog = ({
22 | visible,
23 | title,
24 | description,
25 | hideButtons,
26 | cancellable,
27 | cancelText,
28 | confirmText,
29 | children,
30 | onCancel,
31 | onConfirm
32 | }: DialogProps) => {
33 | const fadeTransition = useTransition(visible, null, {
34 | from: { opacity: 0 },
35 | enter: { opacity: 1 },
36 | leave: { opacity: 0 }
37 | });
38 |
39 | const slideUpTransition = useTransition(visible, null, {
40 | from: {
41 | transform: `translateY(200px) scale(0.8)`,
42 | opacity: 0
43 | },
44 | enter: {
45 | transform: `translateY(0px) scale(1)`,
46 | opacity: 1
47 | },
48 | leave: {
49 | transform: `translateY(200px) scale(0.8)`,
50 | opacity: 0
51 | },
52 | config: {
53 | tension: 200,
54 | friction: 15
55 | }
56 | });
57 |
58 | return (
59 |
60 | {fadeTransition.map(({ item, key, props }) =>
61 | item ? (
62 |
67 | ) : null
68 | )}
69 |
70 | {slideUpTransition.map(({ item, key, props }) =>
71 | item ? (
72 |
77 |
78 | {title &&
{title}
}
79 | {description &&
{description}
}
80 | {children}
81 | {!hideButtons && (
82 |
83 | {cancellable && (
84 |
87 | )}
88 |
89 |
90 | )}
91 |
92 |
93 | ) : null
94 | )}
95 |
96 | );
97 | };
98 |
99 | Dialog.defaultProps = {
100 | cancelText: '취소',
101 | confirmText: '확인'
102 | };
103 |
104 | const breakpoints = [576, 768, 992, 1200];
105 |
106 | const mq = breakpoints.map(bp => `@media (max-width: ${bp}px)`);
107 |
108 | const fullscreen = css`
109 | position: fixed;
110 | top: 0;
111 | left: 0;
112 | width: 100%;
113 | height: 100%;
114 | `;
115 |
116 | const darkLayer = css`
117 | z-index: 10;
118 | background: rgba(0, 0, 0, 0.5);
119 | `;
120 |
121 | const whiteBoxWrapper = css`
122 | z-index: 15;
123 | display: flex;
124 | align-items: center;
125 | justify-content: center;
126 | `;
127 |
128 | const whiteBox = css`
129 | box-sizing: border-box;
130 | border-radius: 4px;
131 | width: 25rem;
132 | background: white;
133 | box-shadow: 0px 4px 8px 8px rgba(0, 0, 0, 0.05);
134 | padding: 2rem;
135 |
136 | h3 {
137 | font-size: 1.5rem;
138 | color: #343a40;
139 | margin-top: 0;
140 | margin-bottom: 1rem;
141 | }
142 |
143 | p {
144 | font-size: 1.125rem;
145 | margin: 0;
146 | color: #868e96;
147 | }
148 |
149 | ${mq[1]} {
150 | width: calc(100% - 2rem);
151 | padding: 1.5rem;
152 | h3 {
153 | font-size: 1.3125rem;
154 | }
155 | p {
156 | font-size: 0.875rem;
157 | }
158 | }
159 | `;
160 |
161 | export default Dialog;
162 |
--------------------------------------------------------------------------------
/src/Icon/Icon.stories.tsx:
--------------------------------------------------------------------------------
1 | /** @jsx jsx */
2 | import { jsx, css } from '@emotion/core';
3 | import Icon, { iconTypes } from './Icon';
4 |
5 | export default {
6 | component: Icon,
7 | title: 'components|Icon'
8 | };
9 |
10 | export const icon = () => ;
11 | icon.story = {
12 | name: 'Default'
13 | };
14 |
15 | export const customSize = () => ;
16 |
17 | export const customColor = () => ;
18 |
19 | export const customizedWithStyle = () => (
20 |
21 | );
22 |
23 | export const listOfIcons = () => {
24 | return (
25 |
26 | {iconTypes.map(icon => (
27 | -
28 |
29 | {icon}
30 |
31 | ))}
32 |
33 | );
34 | };
35 |
36 | const iconListStyle = css`
37 | list-style: none;
38 | display: flex;
39 | flex-wrap: wrap;
40 | li {
41 | box-sizing: border-box;
42 | width: 25%;
43 | padding: 1rem;
44 | display: flex;
45 | align-items: center;
46 | svg {
47 | margin-right: 1rem;
48 | }
49 | }
50 | `;
51 |
--------------------------------------------------------------------------------
/src/Icon/Icon.tsx:
--------------------------------------------------------------------------------
1 | /** @jsx jsx */
2 | import { jsx } from '@emotion/core';
3 | import * as icons from './svg';
4 |
5 | type IconType = keyof typeof icons;
6 | export const iconTypes: IconType[] = Object.keys(icons) as any[]; // 스토리에서 불러오기 위함
7 |
8 | export type IconProps = {
9 | /** 사용 할 아이콘 타입 */
10 | icon: IconType;
11 | /** 아이콘 색상 */
12 | color?: string;
13 | /** 아이콘 크기 */
14 | size?: string | number;
15 | className?: string;
16 | };
17 |
18 | /** 아이콘을 보여주고 싶을 땐 `Icon` 컴포넌트를 사용하세요.
19 | *
20 | * 이 컴포넌트는 svg 형태로 아이콘을 보여주며, props 또는 스타일을 사용하여 아이콘의 색상과 크기를 정의 할 수 있습니다.
21 | *
22 | * 스타일로 모양새를 설정 할 때에는 `color`로 색상을 설정하고 `width`로 크기를 설정하세요.
23 | */
24 | const Icon = ({ icon, color, size, className }: IconProps) => {
25 | const SVGIcon = icons[icon];
26 | return (
27 |
31 | );
32 | };
33 |
34 | export default Icon;
35 |
--------------------------------------------------------------------------------
/src/Icon/svg/exit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Icon/svg/heart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Icon/svg/index.ts:
--------------------------------------------------------------------------------
1 | export { ReactComponent as exit } from './exit.svg';
2 | export { ReactComponent as heart } from './heart.svg';
3 | export { ReactComponent as pencil } from './pencil.svg';
4 |
--------------------------------------------------------------------------------
/src/Icon/svg/pencil.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Button } from './Button/Button';
2 | export { default as ButtonGroup } from './ButtonGroup/ButtonGroup';
3 | export { default as Dialog } from './Dialog/Dialog';
4 | export { default as Icon } from './Icon/Icon';
5 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.mdx';
2 |
3 | declare module '*.svg' {
4 | import * as React from 'react';
5 |
6 | export const ReactComponent: React.FunctionComponent>;
9 |
10 | const src: string;
11 | export default src;
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "skipLibCheck": true,
6 | "esModuleInterop": true,
7 | "allowSyntheticDefaultImports": true,
8 | "strict": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "jsx": "react",
14 | "declaration": true,
15 | "declarationDir": "dist/types"
16 | },
17 | "include": ["src"],
18 | "exclude": ["**/*.stories.tsx"]
19 | }
20 |
--------------------------------------------------------------------------------