├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .storybook ├── main.js └── preview.js ├── LICENSE ├── README.md ├── __mocks__ ├── file-mock.js └── gatsby.js ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── jest-loadershim.ts ├── jest-preprocess.ts ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── @types │ └── index.d.ts ├── components │ ├── button │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── button.test.tsx.snap │ │ │ └── button.test.tsx │ │ ├── button.tsx │ │ └── index.ts │ ├── code │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── code.test.tsx.snap │ │ │ └── code.test.tsx │ │ ├── code.tsx │ │ └── index.ts │ ├── css-debugger │ │ ├── css-debugger.tsx │ │ └── index.tsx │ ├── footer │ │ ├── footer.tsx │ │ └── index.ts │ ├── github-icon │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── github-icon.test.tsx.snap │ │ │ └── github-icon.test.tsx │ │ ├── github-icon.tsx │ │ └── index.ts │ ├── layout │ │ ├── index.ts │ │ └── layout.tsx │ ├── link │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── link.test.tsx.snap │ │ │ └── link.test.tsx │ │ ├── index.ts │ │ └── link.tsx │ └── seo │ │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── seo.test.tsx.snap │ │ └── seo.test.tsx │ │ ├── index.ts │ │ └── seo.tsx ├── images │ ├── github-icon.png │ └── icon.png ├── pages │ ├── 404.tsx │ ├── about.tsx │ └── index.tsx ├── stories │ └── index.stories.tsx └── styles │ ├── global-styles.tsx │ ├── index.ts │ └── theme.tsx ├── static ├── favicon.ico └── icons │ ├── apple-touch-icon.png │ ├── icon_192x192.png │ └── icon_512x512.png └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 80 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = 0 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # gatsby files 2 | .cache/* 3 | node_modules/* 4 | public/* 5 | .storybook/* 6 | @types 7 | __mocks__ 8 | __tests__ 9 | 10 | gatsby-*.js 11 | loadershim.ts 12 | stories/* 13 | 14 | jest*.js 15 | gatsby*.ts 16 | jest*.ts 17 | *.test.* 18 | *.spec.* 19 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "react-app", // See below 4 | "plugin:functional/recommended", 5 | "plugin:prettier/recommended", // Should always be last. Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 6 | ], 7 | plugins: ["functional"], 8 | rules: { 9 | "@typescript-eslint/explicit-function-return-type": "off", // This rule prevents you from letting React component return types be inferred. 10 | "functional/no-expression-statement": "off", // This rule causes an error with ReactDOM.render() 11 | "functional/prefer-readonly-type": "off", // Off for now. Do we want to have to annotate everything with readonly? To be discussed. 12 | "functional/functional-parameters": "off", // This rule breaks things like the useEffect, which takes a callback with no parameters. 13 | "functional/no-mixed-type": "off", // This rules doesn't allow you to create a type alias for a component that mixes methods and values. 14 | "functional/prefer-type-literal": "off", // Interfaces are fine. Some people prefer to extend. 15 | "functional/no-conditional-statement": "off", // if statements are useful and quite nice for conditional component rendering logic. 16 | "functional/no-return-void": "off", // In React, you are often returning void. i.e. useState setters 17 | "functional/no-try-statement": "off", // What's wrong with a try/catch? They are very useful with async/await. 18 | }, 19 | }; 20 | 21 | /** 22 | * eslint-config-react-app 23 | * Docs: 24 | * https://www.npmjs.com/package/eslint-config-react-app 25 | * 26 | * Source: 27 | * https://github.com/facebook/create-react-app/blob/master/packages/eslint-config-react-app/index.js 28 | * This is a default eslint configuration created and maintained the Facebook team, 29 | * primarily for use with create-react-app. 30 | * This default configuration is well thought-out and actively maintained. It includes sensible rules for 31 | * React-specific apps, including hooks usage. It also includes accessibility rules via react-a11y as 32 | * well as rules for import statements and a few other niceties. 33 | */ 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # dotenv environment variables file 55 | .env 56 | 57 | # gatsby files 58 | .cache/ 59 | public 60 | 61 | # Mac files 62 | .DS_Store 63 | 64 | .pnp/ 65 | .pnp.js 66 | # Yarn Integrity file 67 | .yarn-integrity 68 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": true, 4 | "singleQuote": false, 5 | "tabWidth": 2, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | stories: ['../**/*.stories.tsx'], 4 | addons: [ 5 | "@storybook/addon-links", 6 | "@storybook/addon-knobs", 7 | "@storybook/addon-actions" 8 | ], 9 | webpackFinal: async (config, { configType }) => { 10 | // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION' 11 | // You can change the configuration based on that. 12 | // 'PRODUCTION' is used when building the static version of storybook. 13 | 14 | // Make whatever fine-grained changes you need 15 | // Transpile Gatsby module because Gatsby includes un-transpiled ES6 code. 16 | config.module.rules[0].exclude = [/node_modules\/(?!(gatsby)\/)/]; 17 | 18 | // use installed babel-loader which is v8.0-beta (which is meant to work with @babel/core@7) 19 | config.module.rules[0].use[0].loader = require.resolve("babel-loader"); 20 | 21 | // use @babel/preset-react for JSX and env (instead of staged presets) 22 | config.module.rules[0].use[0].options.presets = [ 23 | require.resolve("babel-preset-gatsby"), 24 | require.resolve("@babel/preset-react"), 25 | require.resolve("@babel/preset-env"), 26 | ]; 27 | 28 | // use @babel/plugin-proposal-class-properties for class arrow functions 29 | config.module.rules[0].use[0].options.plugins = [ 30 | require.resolve("@babel/plugin-proposal-class-properties"), 31 | ]; 32 | 33 | // Prefer Gatsby ES6 entrypoint (module) over commonjs (main) entrypoint 34 | config.resolve.mainFields = ["browser", "module", "main"]; 35 | config.module.rules.push({ 36 | test: /\.(ts|tsx)$/, 37 | loader: require.resolve("babel-loader"), 38 | options: { 39 | presets: [["react-app", { flow: false, typescript: true }]], 40 | plugins: ["babel-plugin-styled-components"], 41 | }, 42 | }); 43 | 44 | config.resolve.extensions.push(".ts", ".tsx"); 45 | return config; 46 | // Return the altered config 47 | return config; 48 | }, 49 | } 50 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import { action } from "@storybook/addon-actions"; 2 | 3 | // Gatsby's Link overrides: 4 | // Gatsby defines a global called ___loader to prevent its method calls from creating console errors you override it here 5 | global.___loader = { 6 | enqueue: () => {}, 7 | hovering: () => {}, 8 | }; 9 | // Gatsby internal mocking to prevent unnecessary errors in storybook testing environment 10 | global.__PATH_PREFIX__ = ""; 11 | // This is to utilized to override the window.___navigate method Gatsby defines and uses to report what path a Link would be taking us to if it wasn't inside a storybook 12 | window.___navigate = pathname => { 13 | action("NavigateTo:")(pathname); 14 | }; 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 gatsbyjs 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 | # gatsby-starter-typescript-deluxe 🌟 2 | 3 | ### An opinionated starter library for creating React applications with Gatsby and TypeScript. 4 | 5 | ### [View Demo](https://gatsby-starter-typescript-deluxe.netlify.com) [![Netlify Status](https://api.netlify.com/api/v1/badges/741aaab2-8497-431c-8b59-7f675856de77/deploy-status)](https://app.netlify.com/sites/gatsby-starter-typescript-deluxe/deploys) 6 | 7 | This starter library is pre-configured with the following integrations: 8 | 9 | - **TypeScript** for type-safe code. 10 | - **Styled-Components** for all your styles. 11 | - **modern-css-reset** for a reset of sensible default styles. 12 | - **Framer Motion** for awesome animations. 13 | - **gatsby-image and gatsby-transformer-sharp** for optimized images. 14 | - **gatsby-plugin-manifest / SEO component** for an SEO-friendly PWA. 15 | - **Storybook with add-ons** for showing off your awesome components. 16 | - **Jest and React Testing library** for snapshots and unit tests. 17 | - **ESLint with an emphasis on functional patterns (with Prettier and TypeScript integration)** to make your code look its best. 18 | - **React Axe and React A11y for accessibility** so that your site is awesome for everyone. 19 | 20 | ## Installation 21 | 22 | You will need to have `node` and `npm` installed on your computer. 23 | 24 | You can either use `npx` or install the `gatsby-cli` globally. 25 | 26 | The `npx` way: 27 | 28 | ```sh 29 | npx gatsby new my-site https://github.com/gojutin/gatsby-starter-typescript-deluxe 30 | ``` 31 | 32 | or the global way: 33 | 34 | ```sh 35 | npm i -g gatsby-cli 36 | gatsby new my-site https://github.com/gojutin/gatsby-starter-typescript-deluxe 37 | ``` 38 | 39 | ## Usage 40 | 41 | To start the development servers: 42 | 43 | ```sh 44 | npm run develop 45 | ``` 46 | 47 | If all was successful, you should see links to two development servers in the Node terminal. You can open these url in any browser that you would like. 48 | 49 | 1. [http://localhost:8080](http://localhost:8080): 50 | 51 | This is the development server that allows you to preview your website. It comes with hot-module reloading, which means that you should see your changes almost immediately without having to refresh the browser tab. 52 | 53 | 2. [http://localhost:8000/\_\_\_graphql](http://localhost:8000/___graphql): 54 | 55 | This is the development server that allows you to interact with the your site's GraphQL data via the GraphiQL IDE. 56 | 57 | ### Available Scripts 58 | 59 | | Script | Description | 60 | | ----------------- | :---------------------------------------------------------------------------------- | 61 | | `develop` | Start the development server with hot module reloading. | 62 | | `dev` | Alias for `develop`. | 63 | | `format` | Format your code with Prettier. | 64 | | `clean` | Delete the `.cache` and `public` directories. | 65 | | `test` | Run your Jest tests once. | 66 | | `test:watch` | Run your Jest tests in watch mode. | 67 | | `lint` | Lint your code with ESLint. | 68 | | `lint:watch` | Lint your code with ESLint in watch mode. | 69 | | `lint:fix` | Lint your code with ESLint and attempt to fix linting issues. | 70 | | `serve` | Serve the production build of your site for testing. | 71 | | `build` | Compile your application and make it ready for deployment | 72 | | `storybook` | Starts Storybook. | 73 | | `build-storybook` | Compiles your stories and makes them ready for deployment. | 74 | | `update` | Updates the package.json to the latest dependency versions using npm-check-updates. | 75 | 76 | ## Styling 77 | 78 | This library is pre-configured with [styled-components](https://www.styled-components.com/). 79 | 80 | #### Global Styles 81 | 82 | Global styles are defined in the `src/styles/global-styles.tsx` file using the `createGlobalStyle` function provided by styled-components. The global styles are injected in the `Layout` component via the component that is provided from the `createGlobalStyle` function. 83 | 84 | The global style also includes the styles from [css-modern-reset](https://github.com/hankchizljaw/modern-css-reset), which aims to provide a sensible reset of browser styles. 85 | 86 | #### Theme 87 | 88 | You can define your theme styles in the `/src/styles/theme` file. The theme will be available in any styled-component via `props.theme` and to any other component via the `useTheme` hook. 89 | 90 | #### Handling Media Queries 91 | 92 | The theme utilizes the [use-media](https://github.com/streamich/use-media) library, which allows you to track the state of a CSS media queries. This works by passing a boolean for each screen size that you defined in your theme. Just define your screen sizes in `src/styles/theme`. 93 | 94 | #### Styling Examples 95 | 96 | **`src/pages/about.tsx` includes various examples (with comments) of using styled-components and framer-motion with the theme provider.** 97 | 98 | #### The CSS Prop 99 | 100 | This starter is also preconfigured to work with the `css` prop: 101 | 102 | ```jsx 103 | import styled from "styled-components"; 104 | 105 | const MyComponent = () => ( 106 |
107 |

112 | Hello World! 113 |

114 |
115 | ); 116 | ``` 117 | 118 | _Note: The `css` prop does not play nicely with the `jsx-no-multiline-js` ESLint rule. You may want to disable the rule if you plan on using the `css` prop. This can be done in the `.eslintrc.js` file._ 119 | 120 | I personally do not use the `css` prop and prefer to define styled-components outside of the component definition. My general rule is if the component that is using a styled-component is the only component that uses it, I define the styled-component in the same file. Otherwise, I will move it out to a `components/common` directory. 121 | 122 | ```tsx 123 | import styled from "styled-components"; 124 | 125 | const Heading = styled.h1` 126 | color: #333; 127 | `; 128 | 129 | const MyComponent = () => ( 130 |
131 | Hello World! 132 |
133 | ); 134 | ``` 135 | 136 | ## CSS Debugger 137 | 138 | This starter also includes a `CSSDebugger` component. This component allows you to easily debug your styles by drawing outlines around all elements and applying a grid in the background. It also includes a toggle button that you can optionally use during debugging. 139 | 140 | **_Note: You can drag the toggle button around if it gets in your way._** 141 | 142 | The `CSSDebugger` component is used in the `layout.tsx` component. 143 | 144 | GIF of css debugger 145 | 146 | ## Linting 147 | 148 | This project includes a combination of ESLint and React-A11y rules for React and TypeScript code, which are extended from the [eslint-config-gojutin](https://github.com/gojutin/eslint-config-gojutin) npm package. Many of the rules favor a functional approach with a strong emphasis on immutability and strong type definitions. Since all of the rules and dependencies are included in this package, you can easily remove it if you prefer to wire up your own linting configuration. 149 | 150 | The rules are listed as key/value pairs. The key represents the rule name and the value (number) represents the setting of the rule: 151 | 152 | | | | 153 | | --- | :---- | 154 | | `0` | off | 155 | | `1` | warn | 156 | | `2` | error | 157 | 158 | Here is an example of a rule: 159 | 160 | ``` 161 | "immutable/no-this": 2 162 | ``` 163 | 164 | This particular rule disallows the use of the `this` keyword, which will result in an error. 165 | 166 | ## Storybook 167 | 168 | Storybook is available by creating stories in the `src/stories` directory and running the `npm run storybook` script. Your storybook will be availble at [http://localhost:6006](http://localhost:6006). 169 | 170 | You can also compile a production build of your Storybook by running `npm run build:storybook`. The compiled production build will be located in a `/storybook-static` directory. 171 | 172 | ## Deployment 173 | 174 | Lint your files and fix all linting issues. 175 | 176 | ```sh 177 | npm run lint 178 | ``` 179 | 180 | Run your test suite and fix any broken tests. 181 | 182 | ```sh 183 | npm run test 184 | ``` 185 | 186 | Compile a production build to the `/public` directory. 187 | 188 | ```sh 189 | npm run build 190 | ``` 191 | 192 | ## Lighthouse Audit Score 💯 193 | 194 | Lighthouse Score 195 | 196 | ## TODOS 197 | 198 | - Write more robust unit tests for all components and custom hook. 199 | - Possibly add support for MDX and markdown. 200 | - Add more Storybook add-ons with more component demos. 201 | 202 | That's about it. Now, build something awesome 😀 203 | -------------------------------------------------------------------------------- /__mocks__/file-mock.js: -------------------------------------------------------------------------------- 1 | module.exports = "test-file-stub" -------------------------------------------------------------------------------- /__mocks__/gatsby.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | const gatsby = jest.requireActual("gatsby"); 3 | 4 | module.exports = { 5 | ...gatsby, 6 | graphql: jest.fn(), 7 | Link: jest.fn().mockImplementation( 8 | // these props are invalid for an `a` tag 9 | ({ 10 | activeClassName, 11 | activeStyle, 12 | getProps, 13 | innerRef, 14 | ref, 15 | replace, 16 | to, 17 | ...rest 18 | }) => 19 | React.createElement("a", { 20 | ...rest, 21 | href: to, 22 | }) 23 | ), 24 | StaticQuery: jest.fn(), 25 | useStaticQuery: jest.fn(), 26 | }; 27 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Browser APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/browser-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | const siteTitle = "gatsby-starter-typescript-deluxe"; 2 | const siteDescription = 3 | "A Gatsby starter with TypeScript, Storybook, Styled Components, Framer Motion, Jest, and more."; 4 | const siteAuthor = "@gojutin"; 5 | const siteUrl = "https://gatsby-starter-typescript-deluxe.netlify.com"; 6 | const siteImage = `${siteUrl}/icons/icon_512x512.png`; 7 | const siteKeywords = ["gatsby", "typescript", "starter", "javascript", "react"]; 8 | 9 | module.exports = { 10 | siteMetadata: { 11 | title: siteTitle, 12 | description: siteDescription, 13 | author: siteAuthor, 14 | url: siteUrl, 15 | keywords: siteKeywords, 16 | image: siteImage, 17 | }, 18 | plugins: [ 19 | { 20 | resolve: `gatsby-source-filesystem`, 21 | options: { 22 | path: `${__dirname}/src/images`, 23 | name: "images", 24 | }, 25 | }, 26 | { 27 | resolve: "gatsby-plugin-react-axe", 28 | options: { 29 | showInProduction: false, 30 | // Options to pass to axe-core. 31 | // See: https://github.com/dequelabs/axe-core/blob/master/doc/API.md#api-name-axeconfigure 32 | axeOptions: { 33 | // Your axe-core options. 34 | }, 35 | }, 36 | }, 37 | `gatsby-plugin-styled-components`, 38 | `gatsby-transformer-sharp`, 39 | `gatsby-plugin-sharp`, 40 | "gatsby-plugin-react-helmet", 41 | { 42 | resolve: `gatsby-plugin-manifest`, 43 | options: { 44 | name: siteTitle, 45 | short_name: siteTitle, 46 | description: siteDescription, 47 | start_url: `/`, 48 | background_color: `#663399`, 49 | theme_color: `#663399`, 50 | display: `minimal-ui`, 51 | icon: "src/images/icon.png", 52 | icons: [ 53 | { 54 | src: "icons/icon_512x512.png", 55 | sizes: "512x512", 56 | 57 | }, 58 | { 59 | src: "icons/icon_192x192.png", 60 | sizes: "192x192", 61 | 62 | }, 63 | ], 64 | }, 65 | }, 66 | `gatsby-plugin-offline`, 67 | ], 68 | }; 69 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Node APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/node-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /jest-loadershim.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | global.___loader = { 3 | enqueue: jest.fn(), 4 | }; 5 | -------------------------------------------------------------------------------- /jest-preprocess.ts: -------------------------------------------------------------------------------- 1 | const babelOptions = { 2 | presets: ["babel-preset-gatsby"], 3 | }; 4 | 5 | module.exports = require("babel-jest").createTransformer(babelOptions); 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | "^.+\\.tsx?$": "ts-jest", 4 | "^.+\\.jsx?$": "/jest-preprocess.ts", 5 | }, 6 | testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.([tj]sx?)$", 7 | moduleNameMapper: { 8 | ".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy", 9 | ".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": 10 | "/__mocks__/file-mock.js", 11 | }, 12 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], 13 | testPathIgnorePatterns: ["node_modules", ".cache"], 14 | transformIgnorePatterns: ["node_modules/(?!(gatsby)/)"], 15 | globals: { 16 | __PATH_PREFIX__: "", 17 | }, 18 | testURL: "http://localhost", 19 | setupFiles: ["/jest-loadershim.ts"], 20 | }; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-starter-typescript-deluxe", 3 | "description": "A Gatsby starter with TypeScript, Storybook, Styled Components, Jest, and more.", 4 | "version": "0.0.4", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/gojutin/gatsby-starter-typescript-deluxe" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/gojutin/gatsby-starter-typescript-deluxe/issues" 12 | }, 13 | "scripts": { 14 | "develop": "gatsby develop", 15 | "dev": "npm run develop", 16 | "format": "prettier --write src/**/*.{js,jsx,ts,tsx}", 17 | "lint": "eslint . --ext ts --ext tsx --ext js --ext jsx", 18 | "lint:watch": "esw -w . --ext ts --ext tsx --ext js --ext jsx", 19 | "lint:fix": "npm run lint --fix", 20 | "test": "jest", 21 | "test:watch": "jest --watch", 22 | "serve": "gatsby serve", 23 | "storybook": "start-storybook -p 6006", 24 | "clean": "gatsby clean", 25 | "build": "gatsby build", 26 | "build:storybook": "build-storybook", 27 | "update": "ncu -u" 28 | }, 29 | "dependencies": { 30 | "framer-motion": "^2.9.4", 31 | "gatsby": "^2.25.3", 32 | "gatsby-image": "^2.4.21", 33 | "gatsby-plugin-manifest": "^2.5.2", 34 | "gatsby-plugin-offline": "^3.3.3", 35 | "gatsby-plugin-react-axe": "^0.5.0", 36 | "gatsby-plugin-react-helmet": "^3.3.14", 37 | "gatsby-plugin-sharp": "^2.7.1", 38 | "gatsby-plugin-styled-components": "^3.3.14", 39 | "gatsby-source-filesystem": "^2.4.2", 40 | "gatsby-transformer-sharp": "^2.5.21", 41 | "modern-css-reset": "^1.3.0", 42 | "react": "^17.0.1", 43 | "react-dom": "^17.0.1", 44 | "react-helmet": "^6.1.0", 45 | "styled-components": "^5.2.1", 46 | "use-media": "^1.4.0" 47 | }, 48 | "devDependencies": { 49 | "@babel/core": "^7.12.3", 50 | "@storybook/addon-actions": "^6.0.28", 51 | "@storybook/addon-knobs": "^6.0.28", 52 | "@storybook/addon-links": "^6.0.28", 53 | "@storybook/addons": "^6.0.28", 54 | "@storybook/react": "^6.0.28", 55 | "@testing-library/jest-dom": "^5.11.5", 56 | "@testing-library/react": "^11.1.1", 57 | "@types/jest": "^26.0.15", 58 | "@types/react": "^16.9.56", 59 | "@types/react-dom": "^16.9.9", 60 | "@types/react-helmet": "^6.1.0", 61 | "@types/styled-components": "^5.1.4", 62 | "@typescript-eslint/eslint-plugin": "^4.7.0", 63 | "@typescript-eslint/parser": "^4.7.0", 64 | "babel-jest": "^26.6.3", 65 | "babel-loader": "^8.2.0", 66 | "babel-plugin-styled-components": "^1.11.1", 67 | "babel-preset-gatsby": "^0.5.16", 68 | "babel-preset-react-app": "^10.0.0", 69 | "eslint": "^7.13.0", 70 | "eslint-config-gojutin": "0.1.5", 71 | "eslint-config-prettier": "^6.15.0", 72 | "eslint-plugin-immutable": "^1.0.0", 73 | "eslint-plugin-import": "^2.22.1", 74 | "eslint-plugin-jest": "^24.1.0", 75 | "eslint-plugin-jsx-a11y": "^6.4.1", 76 | "eslint-plugin-prettier": "^3.1.4", 77 | "eslint-plugin-react": "^7.21.5", 78 | "eslint-plugin-react-hooks": "^4.2.0", 79 | "eslint-watch": "^7.0.0", 80 | "identity-obj-proxy": "^3.0.0", 81 | "jest": "^26.6.3", 82 | "jest-styled-components": "^7.0.3", 83 | "npm-check-updates": "^10.0.0", 84 | "prettier": "^2.1.2", 85 | "ts-jest": "^26.4.4", 86 | "tslint-immutable": "^6.0.1", 87 | "typescript": "^4.0.5" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/@types/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import "styled-components"; 5 | 6 | // Add support for css prop 7 | declare namespace React { 8 | interface DOMAttributes { 9 | css?: any; 10 | } 11 | } 12 | 13 | declare module "styled-components" { 14 | export interface DefaultTheme { 15 | [key: string]: any | DefaultTheme; 16 | } 17 | } 18 | 19 | // Add support for svg imports 20 | declare module "*.svg" { 21 | const content: any; 22 | export default content; 23 | } 24 | 25 | // Add support for Jest configuration 26 | declare global { 27 | namespace NodeJS { 28 | export interface Global { 29 | ___loader: any; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/button/__tests__/__snapshots__/button.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`matches snapshot 1`] = ` 4 | 5 | 10 | 11 | `; 12 | -------------------------------------------------------------------------------- /src/components/button/__tests__/button.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button } from "../button"; 3 | import { render } from "@testing-library/react"; 4 | 5 | it("matches snapshot", () => { 6 | const { asFragment } = render(); 7 | expect(asFragment()).toMatchSnapshot(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/button/button.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | const Button = styled.button` 4 | height: 30px; 5 | min-width: 100px; 6 | border: none; 7 | border-radius: 5px; 8 | font-size: 16px; 9 | cursor: pointer; 10 | :disabled { 11 | background: lightgray; 12 | } 13 | `; 14 | 15 | export { Button }; 16 | -------------------------------------------------------------------------------- /src/components/button/index.ts: -------------------------------------------------------------------------------- 1 | export { Button } from "./button"; 2 | -------------------------------------------------------------------------------- /src/components/code/__tests__/__snapshots__/code.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`matches snapshot 1`] = ` 4 | 5 | 8 | const gatsby = "awesome" 9 | 10 | 11 | `; 12 | -------------------------------------------------------------------------------- /src/components/code/__tests__/code.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Code } from "../code"; 3 | import { render } from "@testing-library/react"; 4 | 5 | it("matches snapshot", () => { 6 | const { asFragment } = render(const gatsby = "awesome"); 7 | expect(asFragment()).toMatchSnapshot(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/code/code.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | const Code = styled.code` 4 | display: inline-block; 5 | color: ${(props) => props.theme.colors.darkblue}; 6 | font-size: 1.2rem; 7 | background: #f5f5f5; 8 | margin-top: 1rem; 9 | border-radius: 5px; 10 | padding: 0.5rem; 11 | @media (max-width: 800px) { 12 | font-size: 1rem; 13 | } 14 | ::before { 15 | content: "$ "; 16 | } 17 | `; 18 | 19 | export { Code }; 20 | -------------------------------------------------------------------------------- /src/components/code/index.ts: -------------------------------------------------------------------------------- 1 | export { Code } from "./code"; 2 | -------------------------------------------------------------------------------- /src/components/css-debugger/css-debugger.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import styled, { createGlobalStyle } from "styled-components"; 3 | import { motion } from "framer-motion"; 4 | 5 | const ToggleDebugButton = styled(motion.button).attrs(() => ({ 6 | drag: true, 7 | dragMomentum: false, 8 | whileHover: { scale: 1.05 }, 9 | }))<{ debug?: boolean }>` 10 | position: fixed; 11 | top: 20px; 12 | right: 20px; 13 | border: none; 14 | cursor: pointer; 15 | padding: 5px; 16 | border-radius: 5px; 17 | border: 2px solid orange; 18 | background: orange; 19 | color: #333; 20 | z-index: 5000; 21 | `; 22 | 23 | const getDebugStyles = ({ debug, color }: ComponentProps): string => { 24 | if (!debug) { 25 | return ""; 26 | } 27 | return ` 28 | background: rgba(211,211,211, 0.2) !important; 29 | outline-style: solid !important; 30 | outline-color: ${color} !important; 31 | outline-width: thin !important; 32 | 33 | `; 34 | }; 35 | 36 | const gridStyles = ` 37 | background-size: 10px 10px !important; 38 | background-image: linear-gradient(to right, lightgray 1px, transparent 1px), linear-gradient(to bottom, lightgray 1px, transparent 1px) !important; 39 | `; 40 | 41 | interface CSSDebuggerProps { 42 | debug?: boolean; 43 | showToggle?: boolean; 44 | color?: string; 45 | showGrid?: boolean; 46 | buttonStyle?: React.CSSProperties; 47 | } 48 | 49 | const CSSDebugger: React.FC = ({ 50 | debug = false, 51 | showToggle = true, 52 | showGrid = true, 53 | buttonStyle, 54 | color = "rgba(255, 0, 0, .75)", 55 | }) => { 56 | const [isDebug, setIsDebug] = useState(debug); 57 | const GlobalStyle = createGlobalStyle<{ 58 | debug: boolean; 59 | showGrid?: boolean; 60 | color?: string; 61 | }>` 62 | html, * { 63 | ${(props) => getDebugStyles(props)}; 64 | } 65 | 66 | html { 67 | ${(props) => props.debug && props.showGrid && gridStyles}; 68 | } 69 | `; 70 | 71 | useEffect(() => { 72 | setIsDebug(debug); 73 | }, [debug]); 74 | 75 | const toggle = () => { 76 | setIsDebug((v) => !v); 77 | }; 78 | 79 | const maybeRenderToggleButton = showToggle && ( 80 | 81 | Debug CSS 82 | 83 | ); 84 | 85 | return ( 86 | <> 87 | 88 | {maybeRenderToggleButton} 89 | 90 | ); 91 | }; 92 | 93 | export { CSSDebugger }; 94 | -------------------------------------------------------------------------------- /src/components/css-debugger/index.tsx: -------------------------------------------------------------------------------- 1 | export { CSSDebugger } from "./css-debugger"; 2 | -------------------------------------------------------------------------------- /src/components/footer/footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { GithubIcon } from "../github-icon"; 4 | import { useTheme } from "../../styles"; 5 | 6 | const StyledFooter = styled.footer` 7 | position: absolute; 8 | bottom: 0; 9 | left: 0; 10 | width: 100%; 11 | `; 12 | 13 | const StyledSvg = styled.svg` 14 | position: absolute; 15 | bottom: 0; 16 | `; 17 | 18 | const FooterContainer = styled.div` 19 | text-align: center; 20 | padding-bottom: 10px; 21 | `; 22 | 23 | const Footer: React.FC = () => { 24 | const { blue } = useTheme().colors; 25 | return ( 26 | 27 | {/* This svg was created at https://getwaves.io/ */} 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | export { Footer }; 43 | -------------------------------------------------------------------------------- /src/components/footer/index.ts: -------------------------------------------------------------------------------- 1 | export { Footer } from "./footer"; 2 | -------------------------------------------------------------------------------- /src/components/github-icon/__tests__/__snapshots__/github-icon.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`matches snapshot 1`] = ` 4 | 5 | 31 | 32 | `; 33 | -------------------------------------------------------------------------------- /src/components/github-icon/__tests__/github-icon.test.tsx: -------------------------------------------------------------------------------- 1 | // TODO: Figure out why this test is failing. 2 | 3 | import React from "react"; 4 | import { GithubIcon } from "../github-icon"; 5 | import { render, cleanup } from "@testing-library/react"; 6 | import * as Gatsby from "gatsby"; 7 | const useStaticQuery = jest.spyOn(Gatsby, "useStaticQuery"); 8 | 9 | beforeEach(() => { 10 | useStaticQuery.mockImplementationOnce(() => { 11 | return { 12 | icon: { 13 | childImageSharp: { 14 | fixed: "gatsby-logo.png", 15 | }, 16 | }, 17 | }; 18 | }); 19 | }); 20 | 21 | afterEach(cleanup); 22 | 23 | it("matches snapshot", () => { 24 | const { asFragment } = render(); 25 | expect(asFragment()).toMatchSnapshot(); 26 | }); 27 | -------------------------------------------------------------------------------- /src/components/github-icon/github-icon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useStaticQuery, graphql } from "gatsby"; 3 | import styled from "styled-components"; 4 | import Img from "gatsby-image"; 5 | import { motion } from "framer-motion"; 6 | 7 | const Wrapper = styled.div``; 8 | 9 | const GithubIcon: React.FC = () => { 10 | const data = useStaticQuery(graphql` 11 | query { 12 | icon: file(relativePath: { eq: "github-icon.png" }) { 13 | childImageSharp { 14 | fixed(height: 30, width: 30) { 15 | ...GatsbyImageSharpFixed_withWebp 16 | } 17 | } 18 | } 19 | } 20 | `); 21 | 22 | const imageData = data.icon.childImageSharp.fixed; 23 | return ( 24 | 25 | 34 | GitHub Icon 35 | 36 | 37 | ); 38 | }; 39 | 40 | export { GithubIcon }; 41 | -------------------------------------------------------------------------------- /src/components/github-icon/index.ts: -------------------------------------------------------------------------------- 1 | export { GithubIcon } from "./github-icon"; 2 | -------------------------------------------------------------------------------- /src/components/layout/index.ts: -------------------------------------------------------------------------------- 1 | export { Layout } from "./layout"; 2 | -------------------------------------------------------------------------------- /src/components/layout/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { ThemeProvider } from "styled-components"; 3 | import { useStaticQuery, graphql } from "gatsby"; 4 | import { theme, GlobalStyles } from "../../styles"; 5 | // Components 6 | import { CSSDebugger } from "../css-debugger"; 7 | import { Link } from "../link"; 8 | import { Footer } from "../footer"; 9 | 10 | const Container = styled.main` 11 | margin: 0 auto; 12 | max-width: 1080px; 13 | padding: 2rem; 14 | `; 15 | 16 | const Title = styled.h1` 17 | font-size: ${(props) => (props.theme.screens.sm ? "1.8rem" : "2.2rem")}; 18 | margin: 20px 0px; 19 | color: white; 20 | `; 21 | 22 | const Tagline = styled.h2` 23 | font-size: 1.1rem; 24 | font-weight: 400; 25 | color: ${(props) => props.theme.colors.blue}; 26 | `; 27 | 28 | const Layout: React.FC = ({ children }) => { 29 | const data = useStaticQuery(graphql` 30 | query SiteTitleQuery { 31 | site { 32 | siteMetadata { 33 | title 34 | description 35 | } 36 | } 37 | } 38 | `); 39 | 40 | const { title, description } = data.site.siteMetadata; 41 | 42 | return ( 43 | 44 | 45 | 46 | 47 | 48 | {title.toUpperCase()} 49 | 50 | {description} 51 |
52 |
{children}
53 |