├── .babelrc
├── .editorconfig
├── .github
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .npmignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .storybook
├── config.js
└── webpack.config.js
├── LICENSE
├── README.md
├── examples
└── index.html
├── index.js
├── package-lock.json
├── package.json
├── res
└── demo.gif
├── src
├── MessageModal
│ ├── colors.js
│ ├── index.js
│ ├── keyframes.js
│ └── styles.js
└── index.js
├── stories
└── index.stories.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/react"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [{package.json,*.yml}]
15 | indent_style = space
16 | indent_size = 2
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # 事象説明
2 |
3 | ## スクリーンショット
4 |
5 | ## 原因
6 |
7 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # 目的
2 |
3 | ## スクリーンショット
4 |
5 | ## やったこと
6 |
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .DS_Store
3 | lib/
4 | examples/index.js
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | LICENSE
2 | src/
3 | res/
4 | examples/
5 | README.md
6 | .babelrc
7 | .npmrc
8 | .prettierrc
9 | .prettierignore
10 | .editorconfig
11 | .keep
12 | webpack.config.js
13 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact=true
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | examples
4 | res
5 | package.json
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "semi": false,
4 | "singleQuote": true,
5 | "jsxSingleQuote": true,
6 | "trailingComma": "es5",
7 | "jsxBracketSameLine": true
8 | }
9 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/react'
2 |
3 | // automatically import all files ending in *.stories.js
4 | const req = require.context('../stories', true, /.stories.js$/)
5 | function loadStories() {
6 | req.keys().forEach(filename => req(filename))
7 | }
8 |
9 | configure(loadStories, module)
10 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | // you can use this file to add your custom webpack plugins, loaders and anything you like.
2 | // This is just the basic way to add additional webpack configurations.
3 | // For more information refer the docs: https://storybook.js.org/configurations/custom-webpack-config
4 |
5 | // IMPORTANT
6 | // When you add this file, we won't add the default configurations which is similar
7 | // to "React Create App". This only has babel loader to load JavaScript.
8 |
9 | module.exports = {
10 | plugins: [
11 | // your custom plugins
12 | ],
13 | module: {
14 | rules: [
15 | // add your custom rules.
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 yui540
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @yui540/react-message-modal
2 |
3 | 『メッセージが届いています。』
4 |
5 | デモサイトは[こちら](https://yui540-react-message-modal.netlify.com/)
6 |
7 | 
8 |
9 | - npm
10 | - [@yui540/react-message-modal](https://www.npmjs.com/package/@yui540/react-message-modal)
11 | - 開発者
12 | - yui540
13 | - [Twitter: @yui540](https://twitter.com/yui540)
14 | - [HP](https://yui540.graphics/)
15 |
16 | ## 使い方
17 |
18 | まず、自身のプロジェクトに`@yui540/react-message-modal`をインストールしてください。
19 |
20 | ```bash
21 | $ npm i @yui540/react-message-modal -S
22 | ```
23 |
24 | あとは、自身のプロジェクトの組み込みたい箇所にコンポーネントをマウントしてください。
25 |
26 | props 等の詳細は下記のソースコードのコメントをご参考ください。
27 |
28 | ```javascript
29 | import React from 'react'
30 | import { render } from 'react-dom'
31 |
32 | // MessageModalに渡すprops一覧です
33 | const className = 'message-modal'
34 | const title = 'メッセージが届いています。'
35 | const fontColor = '#5d3523'
36 | const okColor = '#90bdbd'
37 | const cancelColor = '#ea8b98'
38 | const mainColor = '#e4d6ce'
39 | const subColor = '#ffffff'
40 | const onClose = () => console.log('close')
41 | const sp = !(window.innerWidth > 760)
42 | const props = {
43 | title, // [type: string] メッセージモーダルのタイトル
44 | fontColor, // [type: string][options] モーダルのタイトル色
45 | okColor, // [type: string][options] OKボタンの色
46 | cancelColor, // [type: string][options] キャンセルボタンの色
47 | mainColor, // [type: string][options] モーダルのメインカラー
48 | subColor, // [type: string][options] モーダルのサブカラー
49 | onClose, // [type: func][options] モーダルを閉じた時に呼ばれる関数
50 | sp, // [type: bool][options] スマートフォンか否か
51 | className, // [type: string][options] class名
52 | }
53 |
54 | /*
55 | 各色のデフォルトの値は下記の通り
56 |
57 | fontColor = '#5d3523
58 | okColor = '#90bdbd'
59 | cancelColor = '#ea8b98'
60 | mainColor = '#e4d6ce'
61 | subColor = '#ffffff'
62 | */
63 |
64 | render(
65 | // MessageModalの子要素がメッセージの内容になります
66 |
67 |
はじめまして。
68 |
はじめまして。yui540です。
69 |
70 | ,
71 | document.querySelector('#root')
72 | )
73 | ```
74 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @yui540/react-message-modal
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib')
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@yui540/react-message-modal",
3 | "version": "1.0.1",
4 | "description": "『メッセージが届いています。』",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "storybook": "start-storybook -p 6006",
9 | "build": "babel src/MessageModal --out-dir lib",
10 | "serve": "cross-env MODE=development webpack-dev-server",
11 | "watch": "cross-env MODE=development webpack --watch",
12 | "webpack:dev": "cross-env MODE=development webpack",
13 | "webpack:prod": "cross-env MODE=production webpack",
14 | "webpack": "npm-run-all webpack:prod",
15 | "prepublish": "rm -rf lib && npm run build",
16 | "fmt": "prettier --write \"**/*.{js,jsx,json}\"",
17 | "build-storybook": "build-storybook"
18 | },
19 | "husky": {
20 | "hooks": {
21 | "pre-commit": "lint-staged"
22 | }
23 | },
24 | "lint-staged": {
25 | "*.{js,jsx,json}": [
26 | "prettier --write",
27 | "git add"
28 | ]
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "git+https://github.com/yui540/react-message-modal.git"
33 | },
34 | "keywords": [],
35 | "author": "yui540 (https://yui540.graphics)",
36 | "license": "MIT",
37 | "bugs": {
38 | "url": "https://github.com/yui540/react-message-modal/issues"
39 | },
40 | "homepage": "https://github.com/yui540/react-message-modal#readme",
41 | "devDependencies": {
42 | "@babel/cli": "7.2.3",
43 | "@babel/core": "7.3.3",
44 | "@babel/polyfill": "7.2.5",
45 | "@babel/preset-env": "7.3.1",
46 | "@babel/preset-react": "7.0.0",
47 | "@storybook/cli": "4.1.11",
48 | "babel-loader": "8.0.5",
49 | "cross-env": "5.2.0",
50 | "husky": "1.3.1",
51 | "lint-staged": "8.1.4",
52 | "npm-run-all": "4.1.5",
53 | "prettier": "1.16.4",
54 | "react": "16.8.2",
55 | "react-dom": "16.8.2",
56 | "webpack": "4.29.4",
57 | "webpack-cli": "3.2.3",
58 | "webpack-dev-server": "3.1.14",
59 | "@storybook/react": "^4.1.11",
60 | "@storybook/addon-actions": "^4.1.11",
61 | "@storybook/addon-links": "^4.1.11",
62 | "@storybook/addons": "^4.1.11"
63 | },
64 | "dependencies": {
65 | "proptypes": "1.1.0",
66 | "styled-components": "4.1.3"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/res/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yui540/react-message-modal/dcad21fcb3a912e30f7a633ee67197ad449ad8b6/res/demo.gif
--------------------------------------------------------------------------------
/src/MessageModal/colors.js:
--------------------------------------------------------------------------------
1 | export const $fontColor = '#5d3523'
2 | export const $okColor = '#90bdbd'
3 | export const $cancelColor = '#ea8b98'
4 | export const $mainColor = '#e4d6ce'
5 | export const $subColor = '#ffffff'
6 |
--------------------------------------------------------------------------------
/src/MessageModal/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'proptypes'
3 | import {
4 | Container,
5 | HeaderWrapper,
6 | Title,
7 | MainColumnWrapper,
8 | Inner,
9 | FooterWrapper,
10 | OkButton,
11 | CancelButton,
12 | } from './styles'
13 | import {
14 | $fontColor,
15 | $okColor,
16 | $cancelColor,
17 | $mainColor,
18 | $subColor,
19 | } from './colors'
20 |
21 | const Header = ({
22 | title,
23 | fontColor = $fontColor,
24 | mainColor = $mainColor,
25 | subColor = $subColor,
26 | isOpenMessage,
27 | }) => (
28 |
32 |
33 | {title}
34 |
35 |
36 | )
37 |
38 | const MainColumn = ({
39 | subColor = $subColor,
40 | fontColor = $fontColor,
41 | isOpenMessage,
42 | children,
43 | getMessageHeight,
44 | messageHeight,
45 | }) => (
46 |
51 | {children}
52 |
53 | )
54 |
55 | const Footer = ({
56 | fontColor = $fontColor,
57 | okColor = $okColor,
58 | cancelColor = $cancelColor,
59 | mainColor = $mainColor,
60 | subColor = $subColor,
61 | isOpenMessage,
62 | closeModal,
63 | openMessage,
64 | sp,
65 | }) => (
66 |
67 |
74 |
78 |
79 |
86 |
90 |
91 |
92 | )
93 |
94 | export default class MessageModal extends Component {
95 | constructor(props) {
96 | super(props)
97 |
98 | this.state = {
99 | messageHeight: 0,
100 | isClose: false,
101 | isOpenMessage: false,
102 | }
103 | }
104 |
105 | componentDidMount() {
106 | const height = document
107 | .querySelector('.message-modal__inner')
108 | .getBoundingClientRect().height
109 |
110 | this.setState({ messageHeight: height })
111 | }
112 |
113 | getMessageHeight(height) {
114 | this.setState({ messageHeight: height })
115 | }
116 |
117 | openMessage() {
118 | this.setState({ isOpenMessage: true })
119 | }
120 |
121 | closeModal() {
122 | if (this.state.isOpenMessage) this.setState({ isOpenMessage: false })
123 | else this.setState({ isClose: true })
124 | }
125 |
126 | onCloseModal() {
127 | this.setState({ isClose: 'delete' })
128 |
129 | this.props.onClose && this.props.onClose()
130 | }
131 |
132 | render() {
133 | const { sp = false, className = 'message-modal' } = this.props
134 | const { isClose } = this.state
135 |
136 | return (
137 | {
142 | if (e.animationName === 'message-modal__close') this.onCloseModal()
143 | }}>
144 |
145 |
150 |
156 |
157 | )
158 | }
159 | }
160 |
161 | MessageModal.propTypes = {
162 | className: PropTypes.string,
163 | title: PropTypes.string.isRequired,
164 | children: PropTypes.node.isRequired,
165 | fontColor: PropTypes.string,
166 | okColor: PropTypes.string,
167 | cancelColor: PropTypes.string,
168 | mainColor: PropTypes.string,
169 | subColor: PropTypes.string,
170 | onClose: PropTypes.func,
171 | sp: PropTypes.bool,
172 | }
173 |
--------------------------------------------------------------------------------
/src/MessageModal/keyframes.js:
--------------------------------------------------------------------------------
1 | import { keyframes } from 'styled-components'
2 |
3 | export const slideRight = keyframes`
4 | from { transform: translateX(-100%); }
5 | to { transform: translateX(0%); }
6 | `
7 |
8 | export const slideLeft = keyframes`
9 | from { transform: translateX(100%); }
10 | to { transform: translateX(0%); }
11 | `
12 |
13 | export const slideTop = keyframes`
14 | from { transform: translateY(100%); }
15 | to { transform: translateY(0%); }
16 | `
17 |
18 | export const slideBottom = keyframes`
19 | from { transform: translateY(-100%); }
20 | to { transform: translateY(0%); }
21 | `
22 |
23 | export const popupModal = keyframes`
24 | 0% { transform: translateY(50%); opacity: 0; }
25 | 50% { transform: translateY(-10%); opacity: 1; }
26 | 100% { transform: translateY(0%); opacity: 1; }
27 | `
28 |
29 | export const popupTitle = keyframes`
30 | 0% { transform: translateY(100%); opacity: 0; }
31 | 50% { transform: translateY(-25%); opacity: 1; }
32 | 100% { transform: translateY(0%); opacity: 1; }
33 | `
34 |
35 | export const popupButton = keyframes`
36 | 0% { transform: translateY(50%); opacity: 0; }
37 | 50% { transform: translateY(-70%); opacity: 1; }
38 | 100% { transform: translateY(-50%); opacity: 1; }
39 | `
40 |
41 | export const hiddenOkButton = keyframes`
42 | from { transform: translateY(-50%) scale(1); opacity: 1; }
43 | to { transform: translateY(-50%) scale(0); opacity: 0; }
44 | `
45 |
46 | export const slideCancelButton = keyframes`
47 | from { transform: translate(0, -50%); opacity: 1; }
48 | to { transform: translate(100%, -50%); opacity: 1; }
49 | `
50 |
--------------------------------------------------------------------------------
/src/MessageModal/styles.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components'
2 | import {
3 | popupModal,
4 | hiddenModal,
5 | popupTitle,
6 | popupButton,
7 | slideTop,
8 | slideLeft,
9 | slideRight,
10 | slideBottom,
11 | hiddenOkButton,
12 | slideCancelButton,
13 | } from './keyframes'
14 |
15 | const RADIUS = 14
16 | const PC_WIDTH = 380
17 | const SP_WIDTH = 90
18 | const BAR_HEIGHT = 50
19 | const LINE_WIDTH = 4
20 | const BUTTON_SIZE = 55
21 |
22 | export const Container = styled.div`
23 | width: ${props => (props.sp ? `${SP_WIDTH}%` : `${PC_WIDTH}px`)};
24 | border-radius: ${RADIUS}px;
25 | box-shadow: 5px 5px 20px rgba(0, 0, 0, 0.1);
26 | opacity: 0;
27 | transition: all 0.4s ease-out 0s;
28 |
29 |
30 | /* focus */
31 | ${props =>
32 | !props.sp
33 | ? css`
34 | &:hover {
35 | box-shadow: 10px 10px 20px rgba(0, 0, 0, 0.15);
36 | }
37 | `
38 | : ``}
39 |
40 | /* animation */
41 | animation: ${popupModal} 0.45s ease 0s forwards;
42 |
43 | /* モーダルを閉じる時 */
44 | &[data-close='true'] {
45 | animation: message-modal__close 0.45s ease 0s forwards;
46 | }
47 | @keyframes message-modal__close {
48 | from {
49 | transform: translateY(0%);
50 | opacity: 1;
51 | }
52 | to {
53 | transform: translateY(30%);
54 | opacity: 0;
55 | }
56 | }
57 |
58 | /* モーダルを非表示 */
59 | &[data-close='delete'] {
60 | display: none;
61 | }
62 | `
63 |
64 | export const HeaderWrapper = styled.div`
65 | display: flex;
66 | justify-content: center;
67 | padding: ${BAR_HEIGHT / 2}px ${BAR_HEIGHT / 2}px ${BAR_HEIGHT}px
68 | ${BAR_HEIGHT / 2}px;
69 | border-radius: ${RADIUS}px ${RADIUS}px 0 0;
70 | background-color: ${props => props.mainColor};
71 | border-bottom: dashed ${LINE_WIDTH / 2}px ${props => props.subColor};
72 | transition: all 0.4s cubic-bezier(0.68, 0.02, 0.25, 1) 0s;
73 |
74 | &[data-open='true'] {
75 | height: ${BAR_HEIGHT}px;
76 | padding: 0;
77 | }
78 | `
79 |
80 | export const Title = styled.h1`
81 | font-size: 14px;
82 | color: ${props => props.fontColor};
83 | line-height: 30px;
84 | letter-spacing: 0.2em;
85 | border-radius: 15px;
86 | padding: 0 14px;
87 | display: inline-block;
88 | background-color: ${props => props.subColor};
89 | opacity: 0;
90 | transition: all 0.4s cubic-bezier(0.68, 0.02, 0.25, 1) 0s;
91 |
92 | /* animation */
93 | animation: ${popupTitle} 0.45s ease 0.3s forwards;
94 |
95 | /* メッセージを開いた時 */
96 | &[data-open='true'] {
97 | color: transparent;
98 | background-color: transparent;
99 | }
100 | `
101 |
102 | export const MainColumnWrapper = styled.div`
103 | max-height: 0;
104 | overflow: hidden;
105 | background-color: ${props => props.subColor};
106 | transition: all 0.4s cubic-bezier(0.68, 0.02, 0.25, 1) 0s;
107 |
108 | &[data-open='true'] {
109 | max-height: ${props => props.messageHeight}px;
110 | overflow: auto;
111 | -webkit-overflow-scrolling: touch;
112 | }
113 | `
114 |
115 | export const Inner = styled.div``
116 |
117 | export const FooterWrapper = styled.div`
118 | display: flex;
119 | justify-content: center;
120 | background-color: ${props => props.mainColor};
121 | height: ${BAR_HEIGHT}px;
122 | border-radius: 0 0 ${RADIUS}px ${RADIUS}px;
123 | border-top: dashed ${LINE_WIDTH / 2}px ${props => props.subColor};
124 | `
125 |
126 | const Button = styled.button`
127 | appearance: none;
128 | border: none;
129 | padding: 0;
130 | margin: 0;
131 | position: relative;
132 | display: block;
133 | width: ${BUTTON_SIZE}px;
134 | height: ${BUTTON_SIZE}px;
135 | border-radius: 50%;
136 | transform: translateY(-50%);
137 | cursor: pointer;
138 | opacity: 0;
139 | transition: all 0.4s ease-out 0s;
140 |
141 | .line {
142 | overflow: hidden;
143 |
144 | &::before,
145 | &::after {
146 | content: '';
147 | display: block;
148 | position: absolute;
149 | top: 0;
150 | left: 0;
151 | width: 100%;
152 | height: 100%;
153 | }
154 | &::before {
155 | background-color: ${props => props.subColor};
156 | }
157 | &::after {
158 | background-color: ${props => props.fontColor};
159 | }
160 | }
161 |
162 | /* focus */
163 | &:focus {
164 | outline: none;
165 | }
166 |
167 | /* hover */
168 | ${props =>
169 | !props.sp
170 | ? css`
171 | &:hover {
172 | box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.2);
173 | }
174 | `
175 | : ``}
176 | `
177 |
178 | export const OkButton = styled(Button)`
179 | background-color: ${props => props.okColor};
180 | margin-left: ${BUTTON_SIZE}px;
181 |
182 | .line-box {
183 | position: absolute;
184 | top: 50%;
185 | left: 50%;
186 | width: 42%;
187 | height: 42%;
188 | transform: translate(-50%, -75%) rotate(45deg);
189 |
190 | .line {
191 | position: absolute;
192 | right: 0;
193 | bottom: 0;
194 | border-radius: ${LINE_WIDTH / 2}px;
195 |
196 | &:first-child {
197 | width: 80%;
198 | height: ${LINE_WIDTH}px;
199 |
200 | &::before,
201 | &::after {
202 | transform: translateX(101%);
203 | }
204 | }
205 | &:last-child {
206 | width: ${LINE_WIDTH}px;
207 | height: 100%;
208 |
209 | &::before,
210 | &::after {
211 | transform: translateY(-101%);
212 | }
213 | }
214 | }
215 | }
216 |
217 | /* hover */
218 | ${props =>
219 | !props.sp
220 | ? css`
221 | &:hover .line:first-child::after {
222 | animation: ${slideRight} 0.15s ease 0s forwards;
223 | }
224 | &:hover .line:last-child::after {
225 | animation: ${slideTop} 0.15s ease 0.15s forwards;
226 | }
227 | `
228 | : ``}
229 |
230 | /* animation */
231 | animation: ${popupButton} 0.45s ease 0.4s forwards;
232 | .line:first-child::before {
233 | animation: ${slideRight} 0.2s ease 0.8s forwards;
234 | }
235 | .line:last-child::before {
236 | animation: ${slideTop} 0.2s ease 1s forwards;
237 | }
238 |
239 | /* メッセージを開いた時 */
240 | &[data-open='true'] {
241 | pointer-events: none;
242 | animation: ${hiddenOkButton} 0.4s ease 0s;
243 | }
244 | `
245 |
246 | export const CancelButton = styled(Button)`
247 | background-color: ${props => props.cancelColor};
248 |
249 | .line {
250 | position: absolute;
251 | top: 50%;
252 | left: 50%;
253 | width: 50%;
254 | height: ${LINE_WIDTH}px;
255 | border-radius: ${LINE_WIDTH / 2}px;
256 |
257 | &::before,
258 | &::after {
259 | transform: translateX(-101%);
260 | }
261 | &:first-child {
262 | transform: translate(-50%, -50%) rotate(45deg);
263 | }
264 | &:last-child {
265 | transform: translate(-50%, -50%) rotate(-45deg);
266 | }
267 | }
268 |
269 | /* hover */
270 | ${props =>
271 | !props.sp
272 | ? css`
273 | &:hover .line:first-child::after {
274 | animation: ${slideRight} 0.15s ease 0s forwards;
275 | }
276 | &:hover .line:last-child::after {
277 | animation: ${slideRight} 0.15s ease 0.15s forwards;
278 | }
279 | `
280 | : ``}
281 |
282 | /* animation */
283 | animation: ${popupButton} 0.45s ease 0.5s forwards;
284 | .line:first-child::before {
285 | animation: ${slideRight} 0.2s ease 1.1s forwards;
286 | }
287 | .line:last-child::before {
288 | animation: ${slideRight} 0.2s ease 1.3s forwards;
289 | }
290 |
291 | /* メッセージを開いた時 */
292 | &[data-open='true'] {
293 | opacity: 1;
294 | transform: translate(100%, -50%);
295 | animation: ${slideCancelButton} 0.4s ease 0s;
296 | }
297 | `
298 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import MessageModal from './MessageModal'
4 | import styled from 'styled-components'
5 |
6 | const Container = styled.div`
7 | position: absolute;
8 | top: 0;
9 | left: 0;
10 | width: 100%;
11 | height: 100%;
12 | display: flex;
13 | justify-content: center;
14 | align-items: center;
15 | `
16 |
17 | const Wrapper = styled.div`
18 | padding: 20px;
19 | padding-bottom: 50px;
20 | padding-top: 30px;
21 | `
22 |
23 | const Title = styled.h3`
24 | font-size: 20px;
25 | margin: 0;
26 | line-height: 34px;
27 | color: #5d3523;
28 | padding: 5px;
29 | margin-bottom: 10px;
30 | text-align: center;
31 | border-bottom: dashed 1px #ccc;
32 | `
33 |
34 | const Description = styled.p`
35 | font-size: 13px;
36 | line-height: 30px;
37 | margin: 0;
38 | padding: 5px;
39 | color: #5d3523;
40 | text-align: center;
41 | white-space: pre-wrap;
42 | word-wrap: break-word;
43 | `
44 |
45 | const description = `
46 | はじめまして。yui540です。
47 | ここ最近は更新が止まっていましたが、
48 | 個人活動は続けていきます。
49 | このコンポーネントは、npmで配布しています。
50 | `.replace(/^\n/, '')
51 |
52 | // props
53 | const className = 'message-modal'
54 | const title = 'メッセージが届いています。'
55 | const fontColor = '#5d3523'
56 | const okColor = '#90bdbd'
57 | const cancelColor = '#ea8b98'
58 | const mainColor = '#e4d6ce'
59 | const subColor = '#ffffff'
60 | const onClose = () => console.log('close')
61 | const sp = !(window.innerWidth > 760)
62 | const props = {
63 | title, // [type: string] メッセージモーダルのタイトル
64 | fontColor, // [type: string][options] モーダルのタイトル色
65 | okColor, // [type: string][options] OKボタンの色
66 | cancelColor, // [type: string][options] キャンセルボタンの色
67 | mainColor, // [type: string][options] モーダルのメインカラー
68 | subColor, // [type: string][options] モーダルのサブカラー
69 | onClose, // [type: func][options] モーダルを閉じた時に呼ばれる関数
70 | sp, // [type: bool][options] スマートフォンか否か
71 | }
72 | /*
73 | 各色のデフォルトの値は下記の通り
74 |
75 | fontColor = '#5d3523
76 | okColor = '#90bdbd'
77 | cancelColor = '#ea8b98'
78 | mainColor = '#e4d6ce'
79 | subColor = '#ffffff'
80 | */
81 |
82 | render(
83 |
84 |
85 |
86 | はじめまして。
87 | {description}
88 |
89 |
90 | ,
91 | document.querySelector('#root')
92 | )
93 |
--------------------------------------------------------------------------------
/stories/index.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { storiesOf } from '@storybook/react'
4 | import { action } from '@storybook/addon-actions'
5 | import { linkTo } from '@storybook/addon-links'
6 |
7 | import MessageModal from '../src/MessageModal'
8 |
9 | const Content = () => (
10 |
11 |
はじめまして。
12 |
はじめして。yui540です。
13 |
14 | )
15 | const className = 'message-modal'
16 | const title = 'メッセージが届いています。'
17 | const fontColor = '#5d3523'
18 | const okColor = '#90bdbd'
19 | const cancelColor = '#ea8b98'
20 | const mainColor = '#e4d6ce'
21 | const subColor = '#ffffff'
22 | const onClose = () => console.log('close')
23 | const sp = true
24 | const props = {
25 | title, // [type: string] メッセージモーダルのタイトル
26 | fontColor, // [type: string][options] モーダルのタイトル色
27 | okColor, // [type: string][options] OKボタンの色
28 | cancelColor, // [type: string][options] キャンセルボタンの色
29 | mainColor, // [type: string][options] モーダルのメインカラー
30 | subColor, // [type: string][options] モーダルのサブカラー
31 | onClose, // [type: func][options] モーダルを閉じた時に呼ばれる関数
32 | sp, // [type: bool][options] スマートフォンか否か
33 | }
34 |
35 | storiesOf('MessageModal', module)
36 | .add('最低限のprops渡す', () => (
37 |
38 |
39 |
40 | ))
41 | .add('全てのpropsを付与(かつsp版)', () => (
42 |
43 |
44 |
45 | ))
46 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: process.env.MODE,
3 | entry: ['@babel/polyfill', './src/index.js'],
4 | output: {
5 | path: `${__dirname}/examples`,
6 | filename: 'index.js',
7 | },
8 | module: {
9 | rules: [
10 | {
11 | test: /\.js$/,
12 | exclude: /node_modules/,
13 | use: ['babel-loader'],
14 | },
15 | ],
16 | },
17 | devServer: {
18 | open: true,
19 | contentBase: `${__dirname}/examples`,
20 | port: 8080,
21 | historyApiFallback: true,
22 | },
23 | }
24 |
--------------------------------------------------------------------------------