Skip to content

Commit 6bac6ad

Browse files
committed
first commit 🎉
0 parents  commit 6bac6ad

17 files changed

+14612
-0
lines changed

.gitignore

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
.pnpm-debug.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# random
32+
.vercel
33+
.next
34+
.github
35+
.husky

.prettierignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Ignore artifacts:
2+
node_modules
3+
build
4+
coverage
5+
.next
6+
.vscode
7+
8+
# Ignore all HTML files:
9+
*.html

.prettierrc.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"tabWidth": 2,
3+
"semi": false,
4+
"singleQuote": true,
5+
"endOfLine": "auto",
6+
"jsxSingleQuote": true,
7+
"trailingComma": "none",
8+
"printWidth": 130,
9+
"bracketSameLine": true
10+
}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Se-Gl
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.

README.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# SSR Modal Component
2+
3+
[![Prettier](https://github.com/Se-Gl/modal-react/actions/workflows/prettier.yml/badge.svg)](https://github.com/Se-Gl/modal-react/actions/workflows/prettier.yml)
4+
[![Downloads p/week](https://badgen.net/npm/dw/modal-react)](https://badgen.net/npm/dw/modal-react)
5+
[![NPM version](https://badgen.net/npm/v/modal-react)](https://badgen.net/npm/v/modal-react)
6+
[![minified + gzip](https://badgen.net/bundlephobia/minzip/modal-react)](https://badgen.net/bundlephobia/minzip/modal-react)
7+
[![License](https://badgen.net/npm/license/modal-react)](https://badgen.net/npm/license/modal-react)
8+
9+
This is a sustainable React modal component project based on [greenCSS](https://github.com/Se-Gl/greenCSS), which also works SSR with Next.js. greenCSS is an animated, responsive, lightweight and sustainable CSS library. It is recommended that you also use greenCSS in your project. If you don't want to, just style your modal with your own classes.
10+
11+
![modal preview](/img/modal-preview.gif)
12+
13+
## Features
14+
15+
- Press CTRL + k to open the modal
16+
- Press ESC key to close the modal
17+
- On Chrome browser the background is blurred. In Firefox this feature is not available, there the background has an opacity of 75%.
18+
- Click on the background or the close icon (top right corner) to close the modal.
19+
- The Modal component use a default greenCSS fade in animation with a duration of 500ms `fade-in animation-duration-500ms animation-forwards`. If you want to animate the Modal.Header or Modal.Body, just add your greenCSS or your custom animation as a class to the `className`. Here can find all [greenCSS animations](https://www.greencss.dev/examples/animation). p.e. `<Modal.Body className='clip-circle-in-left animation-duration-800ms animation-forwards'>`
20+
- Do you want to use your own close icon on the top right corner? Add `closeIcon` with your own (svg-) component. `<Modal toggle={toggleModal} setToggle={setToggleModal} closeIcon={<div>X</div>}>` The recommended size for an svg is 20x20px.
21+
- People who do not want animations will automatically not be shown any animations. As it will be blocked by default `prefers-reduced-motion: reduce`
22+
23+
## Getting Started
24+
25+
First, install the Modal dependency:
26+
27+
```bash
28+
npm i modal-react
29+
```
30+
31+
### Next.js
32+
33+
In the pages directory, add `_document.js`. It is important to add `<div id='modal-portal' />` below the `<Main />` component. Otherwise your SSR Modal will not work. Learn more about the custom [document](https://nextjs.org/docs/advanced-features/custom-document).
34+
35+
```js
36+
// pages/_document.js
37+
import Document, { Html, Head, Main, NextScript } from 'next/document'
38+
39+
class MyDocument extends Document {
40+
static async getInitialProps(ctx) {
41+
const initialProps = await Document.getInitialProps(ctx)
42+
return { ...initialProps }
43+
}
44+
45+
render() {
46+
return (
47+
<Html>
48+
<Head />
49+
<body>
50+
<Main />
51+
<div id='modal-portal' />
52+
<NextScript />
53+
</body>
54+
</Html>
55+
)
56+
}
57+
}
58+
export default MyDocument
59+
```
60+
61+
### Example Page
62+
63+
The following jsx file is based on greenCSS. If you don't want to use it, you can add your own `classNames`.
64+
65+
```js
66+
import React, { useState } from 'react'
67+
import { Modal } from 'modal-react'
68+
69+
export default function Home() {
70+
const [toggleModal, setToggleModal] = useState(false)
71+
return (
72+
<div className='min-h-100vh bg-gray-9'>
73+
{/* Modal Toggle Button */}
74+
<button onClick={() => setToggleModal((prev) => !prev)} className='bg-red-9 px-20px py-10px rounded-10px hover:bg-red-7'>
75+
Toggle Modal
76+
</button>
77+
78+
{/* Modal */}
79+
<Modal toggle={toggleModal} setToggle={setToggleModal}>
80+
<Modal.Header className='sans font-900 text-30px fade-in-left animation-duration-500ms animation-forwards'>
81+
<h3>👋 Hi, I'm your modal</h3>
82+
</Modal.Header>
83+
<Modal.Body className='sans font-400 text-15px text-gray fade-in animation-duration-800ms animation-forwards'>
84+
<p>
85+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
86+
aliqua. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae auctor. Quis vel eros donec ac. Mauris
87+
pellentesque pulvinar pellentesque habitant morbi tristique senectus.
88+
</p>
89+
<p>
90+
Nunc non blandit massa enim nec dui nunc. Sed elementum tempus egestas sed sed risus. Senectus et netus et malesuada
91+
fames ac turpis egestas maecenas. Urna nec tincidunt praesent semper feugiat. Est ante in nibh mauris cursus mattis
92+
molestie. Vel elit scelerisque mauris pellentesque pulvinar pellentesque habitant.
93+
</p>
94+
</Modal.Body>
95+
<Modal.Footer className='sans font-400 text-10px'>
96+
<h3>copyright</h3>
97+
</Modal.Footer>
98+
</Modal>
99+
</div>
100+
)
101+
}
102+
```
103+
104+
### Summary
105+
106+
1. Import the modal.
107+
2. Next.js: Adjust pages/\_document.js
108+
3. In your page/component:
109+
110+
- Set the react useState to toggle the modal.
111+
- Create a button to activate the modal with one click.
112+
- Create and adjust your personal modal.
113+
114+
4. Have fun with the sustainable Next.js Modal
115+
116+
## Props
117+
118+
| Name | Default Value | Description |
119+
| --------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
120+
| toggle | - | useState toggle state |
121+
| setToggle | - | useState toggle setToggle |
122+
| className | "" | Use your own className to style the modal content |
123+
| backgroundAnimation | 'fade-in animation-duration-500ms animation-forwards' | Add your custom animation className in order to overwrite the default fade in animation for the background |
124+
| modalContentAnimation | 'fade-in animation-duration-500ms animation-forwards' | Add your custom animation className in order to overwrite the default fade in animation for the modal content component |
125+
| closeIcon | `<CloseIcon /> ` | Add your own close icon on the top right, may be an svg or your custom component |
126+
| useKeyInput | true | Use the default key inputs "CTRL + k" to open the modal and "ESC" to close the modal |
127+
| `<Modal>` | `<Modal>{children}</Modal>` | This is the Modal component. Add your own child element(s) or use the `<Modal.Header>` and `<Modal.Body>` inside. |
128+
| `<Modal.Header>` | `<Modal.Header>{children}</Modal.Header>` | Add a header text |
129+
| `<Modal.Body>` | `<Modal.Body>{children}</Modal.Body>` | Add body elements |
130+
| `<Modal.Footer>` | `<Modal.Footer>{children}</Modal.Footer>` | Add footer elements |

dist/index.es.js

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { createPortal } from 'react-dom';
2+
import React, { useEffect, useRef } from 'react';
3+
4+
function _extends() {
5+
_extends = Object.assign ? Object.assign.bind() : function (target) {
6+
for (var i = 1; i < arguments.length; i++) {
7+
var source = arguments[i];
8+
9+
for (var key in source) {
10+
if (Object.prototype.hasOwnProperty.call(source, key)) {
11+
target[key] = source[key];
12+
}
13+
}
14+
}
15+
16+
return target;
17+
};
18+
return _extends.apply(this, arguments);
19+
}
20+
21+
function styleInject(css, ref) {
22+
if ( ref === void 0 ) ref = {};
23+
var insertAt = ref.insertAt;
24+
25+
if (!css || typeof document === 'undefined') { return; }
26+
27+
var head = document.head || document.getElementsByTagName('head')[0];
28+
var style = document.createElement('style');
29+
style.type = 'text/css';
30+
31+
if (insertAt === 'top') {
32+
if (head.firstChild) {
33+
head.insertBefore(style, head.firstChild);
34+
} else {
35+
head.appendChild(style);
36+
}
37+
} else {
38+
head.appendChild(style);
39+
}
40+
41+
if (style.styleSheet) {
42+
style.styleSheet.cssText = css;
43+
} else {
44+
style.appendChild(document.createTextNode(css));
45+
}
46+
}
47+
48+
var css_248z = "@media(prefers-reduced-motion:reduce){*,:after,:before{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.01ms!important}}*,:after,:before{box-sizing:border-box}*{margin:0}::-moz-focus-inner{border:0}.fade-in{-webkit-animation-name:fadeIn;animation-name:fadeIn;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden}@keyframes fadeIn{0%{opacity:0}45%{opacity:.33}to{opacity:1}}.font-bold{font-weight:700}.text-20px{font-size:20px}.text-black{color:#101010}.bg-white{background-color:#fdfdfd}.overflow-x-hidden{overflow-x:hidden}.absolute{position:absolute}.fixed{position:fixed}.top-0per{top:0}.top-50per{top:50%}.right-0per{right:0}.bottom-0per{bottom:0}.left-0per{left:0}.left-50per{left:50%}.z-1{z-index:1}.max-w-90per{max-width:90%}.max-w-50vw{max-width:50vw}.min-w-50rem{min-width:50rem}.max-h-75vh{max-height:75vh}.p-20px{padding:20px}.mt-20px{margin-top:20px}.mb-20px{margin-bottom:20px}.animation-forwards{animation-fill-mode:forwards;-webkit-animation-fill-mode:forwards;-moz-animation-fill-mode:forwards}.animation-duration-500ms{animation-duration:.5s;-webkit-animation-duration:.5s}.shadow-small-black-5{box-shadow:5px 5px 10px -1px #4d4d4d;-webkit-box-shadow:5px 5px 10px -1px #4d4d4d;-moz-box-shadow:5px 5px 10px -1px #4d4d4d;-ms-box-shadow:5px 5px 10px -1px #4d4d4d}.backdrop-blur-30px{backdrop-filter:blur(30px);-webkit-backdrop-filter:blur(30px)}.rounded-20px{border-radius:20px}.cursor-pointer{cursor:pointer}.fill-gray{fill:#7d7d7d}@media(max-width:480px){.sm\\:min-w-90vw{min-width:90vw}}@media(min-width:481px)and (max-width:768px){.md\\:min-w-90vw{min-width:90vw}}";
49+
styleInject(css_248z);
50+
51+
function CloseIcon({
52+
width = '20',
53+
height = '20'
54+
}) {
55+
return /*#__PURE__*/React.createElement("svg", {
56+
className: "cursor-pointer",
57+
xmlns: "http://www.w3.org/2000/svg",
58+
width: width,
59+
height: height,
60+
fill: "none",
61+
viewBox: "0 0 20 20"
62+
}, /*#__PURE__*/React.createElement("path", {
63+
className: "fill-gray",
64+
d: "M5.187 4.01A.833.833 0 004.01 5.187L8.822 10l-4.814 4.813a.835.835 0 101.179 1.178L10 11.178l4.813 4.813a.833.833 0 001.178-1.178L11.178 10l4.813-4.812a.833.833 0 00-1.178-1.179L10 8.822 5.187 4.008v.001z"
65+
}));
66+
}
67+
68+
const Modal = ({
69+
children,
70+
toggle,
71+
setToggle,
72+
className,
73+
backgroundAnimation = 'fade-in animation-duration-500ms animation-forwards',
74+
closeIcon = /*#__PURE__*/React.createElement(CloseIcon, null),
75+
modalContentAnimation = 'fade-in animation-duration-500ms animation-forwards',
76+
useKeyInput = true
77+
}) => {
78+
{
79+
useKeyInput === true ? useEffect(() => {
80+
// ESC key to close the modal
81+
const close = e => {
82+
if (e.keyCode === 27) {
83+
setToggle();
84+
}
85+
};
86+
87+
window.addEventListener('keydown', close); // CTRL + k key to open the modal
88+
89+
const opener = e => {
90+
if (e.ctrlKey && e.key === 'k') {
91+
e.preventDefault();
92+
setToggle(true);
93+
}
94+
};
95+
96+
document.addEventListener('keydown', opener);
97+
return () => window.removeEventListener('keydown', close) || document.removeEventListener('keydown', opener);
98+
}, []) : null;
99+
}
100+
return typeof window !== 'undefined' && /*#__PURE__*/createPortal( /*#__PURE__*/React.createElement(React.Fragment, null, toggle && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
101+
onClick: setToggle.bind(undefined, false),
102+
className: `fixed left-0per top-0per bottom-0per right-0per backdrop-blur-30px text-black ${backgroundAnimation}`,
103+
style: {
104+
backgroundColor: 'rgba(16,16,16,0.75)'
105+
}
106+
}), /*#__PURE__*/React.createElement(ModalContent, {
107+
className: className,
108+
setToggle: setToggle,
109+
closeIcon: closeIcon,
110+
modalContentAnimation: modalContentAnimation
111+
}, children))), document.querySelector('#modal-portal'));
112+
};
113+
114+
const ModalContent = ({
115+
children,
116+
className,
117+
modalContentAnimation,
118+
closeIcon,
119+
setToggle,
120+
...restProps
121+
}) => {
122+
const modalRef = useRef();
123+
return /*#__PURE__*/React.createElement("div", _extends({
124+
ref: modalRef,
125+
tabIndex: 0
126+
}, restProps, {
127+
className: `text-black overflow-x-hidden fixed left-50per top-50per rounded-20px bg-white min-w-50rem max-w-50vw sm:min-w-90vw md:min-w-90vw max-h-75vh shadow-small-black-5 ${className} ${modalContentAnimation}`,
128+
style: {
129+
transform: 'translate(-50%, -50%)'
130+
}
131+
}), /*#__PURE__*/React.createElement("button", {
132+
onClick: setToggle.bind(undefined, false),
133+
className: "absolute z-1",
134+
style: {
135+
top: '20px',
136+
right: '20px'
137+
}
138+
}, closeIcon), /*#__PURE__*/React.createElement("div", {
139+
className: "p-20px"
140+
}, children));
141+
}; // Modal header
142+
143+
144+
Modal.Header = ({
145+
children,
146+
className,
147+
...restProps
148+
}) => {
149+
return /*#__PURE__*/React.createElement("header", _extends({
150+
className: `text-20px font-bold mb-20px max-w-90per ${className}`
151+
}, restProps), children);
152+
}; // Modal body
153+
154+
155+
Modal.Body = ({
156+
children,
157+
className,
158+
...restProps
159+
}) => {
160+
return /*#__PURE__*/React.createElement("div", _extends({
161+
className: `${className}`
162+
}, restProps), children);
163+
}; // Modal footer
164+
165+
166+
Modal.Footer = ({
167+
children,
168+
className,
169+
...restProps
170+
}) => {
171+
return /*#__PURE__*/React.createElement("footer", _extends({
172+
className: `mt-20px ${className}`
173+
}, restProps), children);
174+
}; // import React from 'react'
175+
// export function Modal() {
176+
// return <div>Modal</div>
177+
// }
178+
179+
export { Modal };

0 commit comments

Comments
 (0)