├── .babelrc
├── .browserlistrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── components
└── Box
│ ├── index.test.tsx
│ └── index.tsx
├── custom.d.ts
├── hooks
├── useAxios.js
├── useBeforeLeave.js
├── useClick.js
├── useConfirm.js
├── useFadeIn.js
├── useFullscreen.js
├── useInput.js
├── useNetwork.js
├── useNotification.js
├── usePreventLeave.js
├── useScroll.js
├── useTabs.js
└── useTitle.js
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages
├── _app.tsx
├── _document.tsx
└── index.tsx
├── shared
└── const.ts
├── styles
├── global-style.ts
├── styled.d.ts
└── theme.ts
├── tsconfig.json
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "next/babel"
4 | ],
5 | "plugins": [
6 | [
7 | "babel-plugin-styled-components",
8 | {
9 | "fileName": true,
10 | "displayName": true,
11 | "pure": true
12 | }
13 | ]
14 | ]
15 | }
--------------------------------------------------------------------------------
/.browserlistrc.js:
--------------------------------------------------------------------------------
1 | export const browserslist = ["defaults"];
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Config
2 | !config/default.json
3 | !config/development.json.sample
4 | config/*.json
5 |
6 | # Next
7 | .next
8 | out
9 |
10 | # Logs
11 | npm-debug.log*
12 | coverage
13 |
14 | # NPM
15 | node_modules/
16 |
17 | # Transpiled code
18 | dist
19 | out
20 | .out
21 |
22 | # Dev tools
23 | .DS_Store
24 | .vscode
25 | .idea
26 | *.swp
27 | *.bak
28 |
29 | node_modules.nosync/
30 | *.env.*
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "prettier.semi": false,
4 | "prettier.trailingComma": "all",
5 | "prettier.singleQuote": true,
6 | "prettier.tslintIntegration": true,
7 | "prettier.tabWidth": 2,
8 | "prettier.printWidth": 120
9 | }
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | next-styled
2 |
--------------------------------------------------------------------------------
/components/Box/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from "@testing-library/react";
3 | import "@testing-library/jest-dom/extend-expect";
4 | import Box from "./index";
5 |
6 | it("renders the icon button", () => {
7 | render();
8 | expect(screen.queryByText("box"));
9 | });
10 |
--------------------------------------------------------------------------------
/components/Box/index.tsx:
--------------------------------------------------------------------------------
1 | import { BoxProps } from "shared/const";
2 |
3 | const Box = ({ name }: BoxProps) =>
{name}
;
4 |
5 | export default Box;
6 |
--------------------------------------------------------------------------------
/custom.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.svg" {
2 | const content: any;
3 | export default content;
4 | }
5 |
--------------------------------------------------------------------------------
/hooks/useAxios.js:
--------------------------------------------------------------------------------
1 | import defaultAxios from "axios";
2 |
3 | const useAxios = (opts, axiosInstance = defaultAxios) => {
4 | const [state, setState] = useStste({
5 | loading: true,
6 | error: null,
7 | data: null
8 | });
9 | const [trigger, setTrigger] = useState(0);
10 | if (!opts.url) {
11 | return;
12 | }
13 | const refetch = () => {
14 | setState({
15 | ...state,
16 | loading: true
17 | })
18 | setTrigger(new Date.now());
19 | }
20 | useEffect(() => {
21 | axiosInstance(opts)
22 | .then(data => {
23 | setState({
24 | ...state,
25 | loading: false,
26 | data
27 | });
28 | })
29 | .catch(error => {
30 | setState({ ...state, loading: false, error });
31 | });
32 | }, [trigger]);
33 | return {...state, refetch}
34 | };
35 |
36 | export default useAxios;
37 |
--------------------------------------------------------------------------------
/hooks/useBeforeLeave.js:
--------------------------------------------------------------------------------
1 | export const useBeforeLeave = onBefore => {
2 | if (typeof onBefore !== "function") {
3 | return;
4 | }
5 | const handle = (event) => {
6 | const { clientY } = event;
7 | if(clientY <= 0) {onBefore()};
8 | };
9 | useEffect(() => {
10 | document.addEventListener("mouseleave", handle);
11 | return () => document.removeEventListener("mouseleave", handle);
12 | }, []);
13 | };
--------------------------------------------------------------------------------
/hooks/useClick.js:
--------------------------------------------------------------------------------
1 | export const useClick = onClick => {
2 | if (typeof onClick !== "function") {
3 | return;
4 | }
5 | const element = useRef();
6 | useEffect(() => {
7 | if (element.current) {
8 | element.current.addEventListener("click", onClick);
9 | }
10 | return () => {
11 | if (element.current) {
12 | element.current.removeEventListener("click", onClick);
13 | }
14 | };
15 | }, []);
16 | return element;
17 | };
18 |
--------------------------------------------------------------------------------
/hooks/useConfirm.js:
--------------------------------------------------------------------------------
1 | export const useConfirm = (message = "", onConfirm, onCancel) => {
2 | if (!onConfirm || typeof onConfirm !== "function") {
3 | return;
4 | }
5 | if (onCancel && typeof onCancel !== "function") {
6 | return;
7 | }
8 | const confirmAction = () => {
9 | if (confirm(message)) {
10 | onConfirm();
11 | } else {
12 | onCancel();
13 | }
14 | };
15 | return confirmAction;
16 | };
17 |
--------------------------------------------------------------------------------
/hooks/useFadeIn.js:
--------------------------------------------------------------------------------
1 | export const useFadeIn = (duration = 1, delay = 0) => {
2 | if (typeof duration !== "number" || typeof delay !== "number")
3 | const element = useRef();
4 | useEffect(() => {
5 | if (element.current) {
6 | const { current } = element;
7 | current.style.transition = `opacity ${duration}s ease-in-out ${delay}s`;
8 | current.style.opacity = 1;
9 | }
10 | }, []);
11 | return { ref: element, style: { opacity: 0 } };
12 | };
13 |
--------------------------------------------------------------------------------
/hooks/useFullscreen.js:
--------------------------------------------------------------------------------
1 | export const useFullscreen = callback => {
2 | const element = useRef();
3 | const runCb = isFull => {
4 | if (callback && typeof callback === "function") {
5 | callback(isFull);
6 | }
7 | };
8 | const triggerFull = () => {
9 | if (element.current) {
10 | if (element.current.requestFullscreen) {
11 | element.current.requestFullscreen();
12 | } else if (element.current.mozRequestFullScreen) {
13 | element.current.mozRequestFullScreen();
14 | } else if (element.current.webkitRequestFullscreen) {
15 | element.current.webkitRequestFullscreen();
16 | } else if (element.current.msRequestFullscreen) {
17 | element.current.msRequestFullscreen();
18 | }
19 | runCb(true);
20 | }
21 | };
22 | const exitFull = () => {
23 | if (document.exidocumenttFullscreen) {
24 | document.exitFullscreen();
25 | } else if (document.mozCancelFullScreen) {
26 | document.mozCancelFullScreen();
27 | } else if (document.webkitExitFullscreen) {
28 | document.webkitExitFullscreen();
29 | } else if (document.msExitFullscreen) {
30 | document.msExitFullscreen();
31 | }
32 | runCb(false);
33 | };
34 | return { element, triggerFull, exitFull };
35 | };
36 |
--------------------------------------------------------------------------------
/hooks/useInput.js:
--------------------------------------------------------------------------------
1 | export const useInput = (initialValue, validator) => {
2 | const [value, setValue] = useState(initialValue);
3 | const onChange = event => {
4 | const {
5 | target: { value }
6 | } = event;
7 | let willUpdate = true;
8 | if (typeof validator === "fuction") {
9 | willUpdate = validator(value);
10 | }
11 | if (willUpdate) {
12 | setValue(value);
13 | }
14 | };
15 | return { value, onChange };
16 | };
17 |
--------------------------------------------------------------------------------
/hooks/useNetwork.js:
--------------------------------------------------------------------------------
1 | export const useNetwork = onChange => {
2 | const [status, setStatus] = useState(navigator.onLine);
3 | const handleChange = () => {
4 | if (typeof onChange === "function") {
5 | onChange(navigator.onLine);
6 | }
7 | setStatus(navigator.onLine);
8 | };
9 | useEffect(() => {
10 | window.addEventListener("online", handleChange);
11 | window.addEventListener("offline", handleChange);
12 | return () => {
13 | window.removeEventListener("online", handleChange);
14 | window.removeEventListener("offline", handleChange);
15 | };
16 | }, []);
17 | return status;
18 | };
19 |
--------------------------------------------------------------------------------
/hooks/useNotification.js:
--------------------------------------------------------------------------------
1 | export const useNotification = (title, options) => {
2 | if (!("Notification" in window)) {
3 | return;
4 | }
5 | const fireNotif = () => {
6 | if (Notification.permission !== "granted") {
7 | Notification.requestPermission().then(permission => {
8 | if (permission === "granted") {
9 | new Notification(title, options);
10 | } else {
11 | return;
12 | }
13 | });
14 | } else {
15 | new Notification(title, options);
16 | }
17 | };
18 | return fireNotif;
19 | j;
20 | };
21 |
--------------------------------------------------------------------------------
/hooks/usePreventLeave.js:
--------------------------------------------------------------------------------
1 | export const usePreventLeave = () => {
2 | const listener = event => {
3 | event.preventDefault();
4 | event.returnValue = "";
5 | };
6 | const enablePrevent = () =>
7 | window.addEventListener("beforeunload", listener);
8 | const disablePrevent = () =>
9 | window.removeEventListener("beforeunload", listener);
10 | return { enablePrevent, disablePrevent };
11 | };
--------------------------------------------------------------------------------
/hooks/useScroll.js:
--------------------------------------------------------------------------------
1 | export const useScroll = () => {
2 | const [state, setState] = useState({
3 | x: 0,
4 | y: 0
5 | });
6 | const onScroll = () => {
7 | setState({
8 | x: window.scrollX,
9 | y: window.scrollY
10 | });
11 | };
12 | useEffect(() => {
13 | window.addEventListener("scroll", onScroll);
14 | return () => window.removeEventListener("scroll", onScroll);
15 | }, []);
16 | return state;
17 | };
18 |
--------------------------------------------------------------------------------
/hooks/useTabs.js:
--------------------------------------------------------------------------------
1 | export const useTabs = (initialTab, allTabs) => {
2 | if (!allTabs || !Array.isArray(allTabs)) {
3 | return;
4 | }
5 | const [currentIndex, setCurrentIndex] = useState(initialTab);
6 | return {
7 | currentItem: allTabs[currentIndex],
8 | changeItem: setCurrentIndex
9 | };
10 | };
11 |
--------------------------------------------------------------------------------
/hooks/useTitle.js:
--------------------------------------------------------------------------------
1 | export const useTitle = initialTitle => {
2 | const [title, setTitle] = useState(initialTitle);
3 | const updateTitle = () => {
4 | const htmlTitle = document.querySelector("title");
5 | htmlTitle.innerText = title;
6 | };
7 | useEffect(updateTitle, [title]);
8 | return setTitle;
9 | };
10 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withBundleAnalyzer = require("@next/bundle-analyzer")({
2 | enabled: process.env.ANALYZE === "true",
3 | });
4 |
5 | module.exports = withBundleAnalyzer({
6 | target: "serverless",
7 | env: {
8 | BASE_URL: process.env.BASE_URL,
9 | },
10 |
11 | webpack(conf) {
12 | conf.module.rules.push({
13 | test: /\.svg$/,
14 | use: [
15 | {
16 | loader: "@svgr/webpack",
17 | options: {
18 | svgoConfig: {
19 | plugins: [
20 | {
21 | // Enable figma's wrong mask-type attribute work
22 | removeRasterImages: false,
23 | removeStyleElement: false,
24 | removeUnknownsAndDefaults: false,
25 | // Enable svgr's svg to fill the size
26 | removeViewBox: false,
27 | },
28 | ],
29 | },
30 | },
31 | },
32 | ],
33 | });
34 | // 절대경로
35 | conf.resolve.modules.push(__dirname);
36 | return conf;
37 | },
38 | });
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "intern-2021-03",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "repository": "https://oss.navercorp.com/booking/intern-2021-03.git",
6 | "author": "Lee Jeong Min ",
7 | "license": "MIT",
8 | "scripts": {
9 | "dev": "next",
10 | "debug": "NODE_OPTIONS='--inspect' next dev",
11 | "build": "next build",
12 | "start": "next start",
13 | "export": "next export",
14 | "type-check": "tsc",
15 | "eslint": "eslint .",
16 | "analyze": "ANALYZE=true next build",
17 | "test": "jest",
18 | "storybook": "start-storybook -p 6006",
19 | "build-storybook": "build-storybook"
20 | },
21 | "dependencies": {
22 | "@next/bundle-analyzer": "^10.0.7",
23 | "axios": "^0.21.1",
24 | "babel-plugin-styled-components": "^1.12.0",
25 | "css-loader": "^5.0.2",
26 | "file-loader": "^6.2.0",
27 | "next": "^10.0.5",
28 | "query-string": "^7.0.0",
29 | "react": "^17.0.1",
30 | "react-dom": "^17.0.1",
31 | "react-ripples": "^2.2.1",
32 | "react-virtualized-auto-sizer": "^1.0.4",
33 | "react-window": "^1.8.6",
34 | "styled-components": "^5.2.1",
35 | "styled-reset": "^4.3.4"
36 | },
37 | "devDependencies": {
38 | "@babel/core": "^7.13.10",
39 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
40 | "@svgr/webpack": "^5.5.0",
41 | "@testing-library/jest-dom": "^5.11.9",
42 | "@testing-library/react": "^11.2.5",
43 | "@types/axios": "^0.14.0",
44 | "@types/node": "^14.14.22",
45 | "@types/react": "^17.0.0",
46 | "@types/react-dom": "^17.0.0",
47 | "@types/react-window": "^1.8.2",
48 | "@types/styled-components": "^5.1.7",
49 | "@typescript-eslint/eslint-plugin": "^4.14.1",
50 | "@typescript-eslint/eslint-plugin-tslint": "^4.14.1",
51 | "@typescript-eslint/parser": "^4.14.1",
52 | "babel-jest": "^26.6.3",
53 | "babel-loader": "^8.2.2",
54 | "eslint": "^7.18.0",
55 | "eslint-config-airbnb-typescript": "^12.3.1",
56 | "eslint-config-prettier": "^8.1.0",
57 | "eslint-plugin-import": "^2.22.1",
58 | "eslint-plugin-jsx-a11y": "^6.4.1",
59 | "eslint-plugin-react": "^7.22.0",
60 | "eslint-plugin-react-hooks": "^4.2.0",
61 | "jest": "^26.6.3",
62 | "prettier": "^2.2.1",
63 | "typescript": "^4.1.3"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import type { AppProps } from "next/app";
2 | import Head from "next/head";
3 | import { ThemeProvider } from "styled-components";
4 | import { GlobalStyle } from "../styles/global-style";
5 | import { theme } from "../styles/theme";
6 |
7 | function MyApp({ Component, pageProps }: AppProps) {
8 | return (
9 | <>
10 |
11 |
12 | boilerplate
13 |
14 |
15 |
16 |
17 |
18 | >
19 | );
20 | }
21 |
22 | export default MyApp;
23 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import Document, {
2 | Html,
3 | Head,
4 | Main,
5 | NextScript,
6 | DocumentContext,
7 | } from "next/document";
8 | import { ServerStyleSheet } from "styled-components";
9 |
10 | class MyDocument extends Document {
11 | static async getInitialProps(ctx: DocumentContext) {
12 | const sheet = new ServerStyleSheet();
13 | const originalRenderPage = ctx.renderPage;
14 | try {
15 | ctx.renderPage = () =>
16 | originalRenderPage({
17 | enhanceApp: (App) => (props) =>
18 | sheet.collectStyles(),
19 | });
20 |
21 | const initialProps = await Document.getInitialProps(ctx);
22 | return {
23 | ...initialProps,
24 | styles: (
25 | <>
26 | {initialProps.styles}
27 | {sheet.getStyleElement()}
28 | >
29 | ),
30 | };
31 | } finally {
32 | sheet.seal();
33 | }
34 | }
35 |
36 | render() {
37 | return (
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
54 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | export default MyDocument;
69 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | const Index = () => {
2 | return index
;
3 | };
4 |
5 | export default Index;
6 |
--------------------------------------------------------------------------------
/shared/const.ts:
--------------------------------------------------------------------------------
1 | export interface BoxProps {
2 | name: string;
3 | }
4 |
--------------------------------------------------------------------------------
/styles/global-style.ts:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from "styled-components";
2 | import { reset } from "styled-reset";
3 | import { media } from "./theme";
4 |
5 | export const GlobalStyle = createGlobalStyle`
6 | ${reset}
7 | :focus {
8 | outline: none;
9 | border: none;
10 | }
11 | ::-webkit-scrollbar {
12 | display: none;
13 | }
14 | html{
15 | font-size: 11px;
16 | -webkit-text-size-adjust: none;
17 | font-family: -apple-system,BlinkMacSystemFont,helvetica,Apple SD Gothic Neo,sans-serif;
18 | font-display: fallback;
19 | ${media.tablet}{
20 | font-size: 10px;
21 | }
22 | -ms-overflow-style: none;
23 | scrollbar-width: none;
24 | }
25 | button {
26 | background: none;
27 | padding: 0;
28 | border: none;
29 | cursor: pointer;
30 | &:disabled {
31 | cursor: default;
32 | fill: #f2f3f4;
33 | }
34 | }
35 |
36 | .pc-tablet-only {
37 | display: block;
38 | ${media.mobile} {
39 | display: none;
40 | }
41 | }
42 | .tablet-mobile-only{
43 | display: none;
44 | ${media.tablet}{
45 | display:block;
46 | }
47 | }
48 | .mobile-only {
49 | display: none;
50 | ${media.mobile} {
51 | display: block;
52 | }
53 | }
54 | `;
55 |
--------------------------------------------------------------------------------
/styles/styled.d.ts:
--------------------------------------------------------------------------------
1 | import "styled-components";
2 |
3 | declare module "styled-components" {
4 | export interface DefaultTheme {
5 | color: {
6 | purple: "#8661de";
7 | blue: "#00bac7";
8 | gray: "#f6f6f6";
9 | green: "#07b495";
10 | lightGreen: "#99ecdd";
11 | darkGray: "#54595d";
12 | };
13 | boxShadow: {
14 | normal: "0 3px 8px 0 rgb(0 0 0 / 10%)";
15 | purple: "0 3px 8px 0 #d6c9ff";
16 | blue: "0 3px 8px 0 #b3e2e6";
17 | };
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/styles/theme.ts:
--------------------------------------------------------------------------------
1 | import { DefaultTheme } from "styled-components";
2 |
3 | export const theme: DefaultTheme = {
4 | color: {
5 | purple: "#8661de",
6 | blue: "#00bac7",
7 | gray: "#f6f6f6",
8 | green: "#07b495",
9 | lightGreen: "#99ecdd",
10 | darkGray: "#54595d",
11 | },
12 | boxShadow: {
13 | normal: "0 3px 8px 0 rgb(0 0 0 / 10%)",
14 | purple: "0 3px 8px 0 #d6c9ff",
15 | blue: "0 3px 8px 0 #b3e2e6",
16 | },
17 | };
18 |
19 | const customMediaQuery = (maxWidth: number): string =>
20 | `@media (max-width: ${maxWidth}px)`;
21 |
22 | export const media = {
23 | custom: customMediaQuery,
24 | pc: customMediaQuery(1440),
25 | tablet: customMediaQuery(768),
26 | mobile: customMediaQuery(576),
27 | };
28 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "baseUrl": ".",
21 | "rootDir": ".",
22 | },
23 | "include": [
24 | "next-env.d.ts",
25 | "**/*.ts",
26 | "**/*.tsx",
27 | "next.config.js",
28 | "custom.d.ts",
29 | "styles",
30 | "pages",
31 | "public",
32 | ],
33 | "exclude": [
34 | "node_modules",
35 | ]
36 | }
--------------------------------------------------------------------------------