├── .babelrc ├── .editorconfig ├── .env.sample ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .storybook ├── decorators │ ├── EmotionThemeProvider.js │ ├── GatsbyIntlProvider.js │ └── index.js ├── main.js ├── manager.js ├── preview-head.html ├── preview.js └── webpack.config.js ├── .svgo.yml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── __mocks__ ├── file-mock.js ├── gatsby-plugin-intl.ts ├── gatsby.ts └── svgr-mock.js ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── jest.config.js ├── jsconfig.json ├── package.json ├── src ├── @types │ ├── assets │ │ └── index.d.ts │ ├── gatsby-plugin-intl │ │ └── index.d.ts │ └── index.d.ts ├── components │ ├── App │ │ ├── App.tsx │ │ └── index.ts │ ├── Button │ │ ├── Button.tsx │ │ ├── __tests__ │ │ │ └── Button.test.tsx │ │ ├── button.stories.tsx │ │ └── index.ts │ ├── Layout │ │ ├── DefaultLayout.tsx │ │ ├── GlobalStyles.tsx │ │ └── index.ts │ ├── LoadingIndicators │ │ ├── ContentLoading.tsx │ │ ├── LoadingSpinner.tsx │ │ └── index.ts │ └── SEO │ │ ├── SEO.tsx │ │ └── index.ts ├── icons │ ├── gatsby.svg │ ├── github.svg │ ├── loading-spinner.svg │ ├── storybook.svg │ ├── styled-components.svg │ ├── tailwind.svg │ └── typescript.svg ├── locales │ ├── en-us.json │ └── es-es.json ├── pages │ └── index.tsx ├── styled │ └── index.ts ├── theme.ts └── utils │ ├── i18n │ ├── index.ts │ └── supportedLanguages.js │ ├── polyfills │ └── toBlob.ts │ ├── styles │ ├── breakpoints.ts │ ├── index.ts │ ├── mediaQueries.ts │ └── spacer.ts │ ├── system │ ├── index.ts │ └── isBrowser.ts │ └── types │ └── index.tsx ├── static ├── favicon.ico └── logos │ └── emotion.png ├── test-utils ├── index.tsx ├── jest-preprocess.js ├── loadershim.js └── setup-test-env.ts ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "production": { 4 | "plugins": ["babel-plugin-jsx-remove-data-test-id"] 5 | }, 6 | // Jest needs @emotion/babel-preset-css-prop 7 | // Gatsby enables css-prop with gatsby-plugin-emotion 8 | // Storybook enables css-prop in /.storybook/webpack.config.js 9 | "test": { 10 | "presets": ["@emotion/babel-preset-css-prop"] 11 | } 12 | }, 13 | "plugins": [ 14 | [ 15 | "module-resolver", 16 | { 17 | "root": ["./src"], 18 | "alias": { 19 | "~": "./src", 20 | "@theme/styled": "./src/styled" 21 | } 22 | } 23 | ] 24 | ], 25 | "presets": [ 26 | [ 27 | "babel-preset-gatsby", 28 | { 29 | "targets": { 30 | "browsers": [">0.25%", "not dead"] 31 | } 32 | } 33 | ] 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | tab_width = 2 6 | indent_size = 2 7 | end_of_line = lf 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | # General 2 | GATSBY_SITE_URL = 'http://localhost:8000' # The base site URL that the application runs on, used for some compile time tasks 3 | GATSBY_ENVIRONMENT = 'development' 4 | 5 | # Profiling 6 | ENABLE_BUNDLE_ANALYZER = 0 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .cache 2 | public 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { 2 | rules: baseImportsRules, 3 | } = require('eslint-config-airbnb-base/rules/imports'); 4 | 5 | module.exports = { 6 | globals: { 7 | // Gatsby Config 8 | __PATH_PREFIX__: true, 9 | }, 10 | env: { 11 | // Allow `window` global 12 | browser: true, 13 | }, 14 | // Global ESLint Settings 15 | // ================================= 16 | settings: { 17 | 'import/resolver': { 18 | node: { 19 | paths: ['./', 'src'], 20 | extensions: ['.js', '.jsx', '.ts', '.tsx', 'json'], 21 | }, 22 | // Resolve Aliases 23 | // ================================= 24 | alias: { 25 | map: [ 26 | ['~', './src'], 27 | ['@theme/styled', './src/styled'], 28 | ], 29 | extensions: ['.js', '.jsx', '.ts', '.tsx', 'json', '.d.ts'], 30 | }, 31 | }, 32 | }, 33 | 34 | // =========================================== 35 | // Set up ESLint for .js / .jsx files 36 | // =========================================== 37 | // .js / .jsx uses babel-eslint 38 | parser: 'babel-eslint', 39 | 40 | // Plugins 41 | // ================================= 42 | plugins: ['no-only-tests'], 43 | 44 | // Extend Other Configs 45 | // ================================= 46 | extends: [ 47 | 'eslint:recommended', 48 | 'airbnb', 49 | // Disable rules that conflict with Prettier 50 | // !!! Prettier must be last to override other configs 51 | 'prettier/react', 52 | 'plugin:prettier/recommended', 53 | ], 54 | rules: { 55 | // This project uses TS. Disable prop-types check 56 | 'react/prop-types': 0, 57 | // Allow snake_case due to inconsistent APIs 58 | camelcase: 0, 59 | // Prevents exclusion of tests from passing lint check 60 | 'no-only-tests/no-only-tests': 'error', 61 | }, 62 | 63 | // https://eslint.org/docs/user-guide/configuring#report-unused-eslint-disable-comments 64 | reportUnusedDisableDirectives: true, 65 | 66 | // ================================= 67 | // Overrides for Specific Files 68 | // ================================= 69 | overrides: [ 70 | // ================================= 71 | // TypeScript Files 72 | // ================================= 73 | { 74 | files: ['**/*.{ts,tsx}'], 75 | // allow ESLint to understand TypeScript syntax 76 | // https://github.com/iamturns/eslint-config-airbnb-typescript/blob/master/lib/shared.js#L10 77 | parserOptions: { 78 | // Lint with Type Information 79 | // https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/TYPED_LINTING.md 80 | tsconfigRootDir: __dirname, 81 | project: './tsconfig.json', 82 | }, 83 | 84 | extends: [ 85 | // ESLint's inbuilt 'recommended' config 86 | 'eslint:recommended', 87 | // Disables rules from the 'eslint:recommended' that are already covered by TypeScript's typechecker 88 | 'plugin:@typescript-eslint/eslint-recommended', 89 | // Turns on rules from @typescript-eslint/eslint-plugin 90 | 'plugin:@typescript-eslint/recommended', 91 | // Lint with Type Information 92 | // https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/TYPED_LINTING.md 93 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 94 | 'airbnb-typescript', 95 | // Disable rules that conflict with Prettier 96 | // !!! Prettier must be last to override other configs 97 | 'prettier/react', 98 | 'prettier/@typescript-eslint', 99 | 'plugin:prettier/recommended', 100 | ], 101 | rules: { 102 | // This project uses TS. Disable prop-types check 103 | 'react/prop-types': 'off', 104 | // Allow snake_case due to inconsistent APIs 105 | '@typescript-eslint/camelcase': 0, 106 | // Makes no sense to allow type inferrence for expression parameters, but require typing the response 107 | '@typescript-eslint/explicit-function-return-type': 0, 108 | // Reduce props spreading rule to a warning, not an error 109 | 'react/jsx-props-no-spreading': 1, 110 | 'no-restricted-imports': [ 111 | 'warn', 112 | { 113 | paths: [ 114 | { 115 | name: '@emotion/css', 116 | message: 117 | 'Import from "@emotion/core" instead. import { css } from "@emotion/core"', 118 | }, 119 | ], 120 | }, 121 | ], 122 | }, 123 | }, 124 | // ================================= 125 | // index.ts Files (Re-exporting a directory's files) 126 | // ================================= 127 | { 128 | files: ['**/index.{js,ts,tsx}'], 129 | rules: { 130 | // Allow named exports in a directory's index files 131 | 'import/prefer-default-export': 0, 132 | }, 133 | }, 134 | // ================================= 135 | // Gatsby Files 136 | // ================================= 137 | { 138 | files: ['**/**/gatsby-*.js'], 139 | rules: { 140 | 'no-console': 0, 141 | // Allow import devDependencies in Gatsby files. 142 | 'import/no-extraneous-dependencies': [ 143 | 2, 144 | { 145 | devDependencies: true, 146 | // Tells ESLint where the path to the folder containing package.json is for nested files like /plugin/**/gatsby-*.js 147 | packageDir: './', 148 | }, 149 | ], 150 | 'react/no-danger': 0, 151 | 'react/jsx-props-no-spreading': 0, 152 | // Allow 'jsx' in .js files 153 | 'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }], 154 | 'import/prefer-default-export': 0, 155 | // Append 'ts' and 'tsx' when importing files from a folder/index.ts 156 | 'import/extensions': [ 157 | baseImportsRules['import/extensions'][0], 158 | baseImportsRules['import/extensions'][1], 159 | { 160 | ...baseImportsRules['import/extensions'][2], 161 | ts: 'never', 162 | tsx: 'never', 163 | }, 164 | ], 165 | }, 166 | }, 167 | // ================================= 168 | // Test Files 169 | // ================================= 170 | { 171 | files: ['**/test-utils/*.{js,ts,tsx}', '**/**/*.test.{js,ts,tsx}'], 172 | // Allow `jest` global 173 | extends: ['plugin:jest/recommended'], 174 | rules: { 175 | // Allow import devDependencies in tests 176 | 'import/no-extraneous-dependencies': 0, 177 | 'react/jsx-props-no-spreading': 0, 178 | 'jsx-a11y/alt-text': 0, 179 | }, 180 | }, 181 | // ================================= 182 | // Storybook Files 183 | // ================================= 184 | { 185 | files: ['**/*.stories.{js,ts,tsx}'], 186 | rules: { 187 | // Allow import devDependencies in stories 188 | 'import/no-extraneous-dependencies': 0, 189 | 'react/jsx-props-no-spreading': 0, 190 | 'jsx-a11y/alt-text': 0, 191 | }, 192 | }, 193 | ], 194 | }; 195 | -------------------------------------------------------------------------------- /.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 | !.env.sample 57 | 58 | # gatsby files 59 | .cache/ 60 | public 61 | 62 | # Mac files 63 | .DS_Store 64 | 65 | # Yarn 66 | yarn-error.log 67 | .pnp/ 68 | .pnp.js 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # Storybook 73 | .out 74 | 75 | # Ensure automated processes do not mix package manager artifacts 76 | package-lock.json 77 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | public 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/prettierrc", 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /.storybook/decorators/EmotionThemeProvider.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ThemeProvider } from 'emotion-theming'; 3 | import theme from '~/theme'; 4 | 5 | const EmotionThemeProvider = storyFn => ( 6 | {storyFn()} 7 | ); 8 | 9 | export default EmotionThemeProvider; 10 | -------------------------------------------------------------------------------- /.storybook/decorators/GatsbyIntlProvider.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { IntlContextProvider } from 'gatsby-plugin-intl/intl-context'; 3 | 4 | import { locales, messages } from '../preview'; 5 | 6 | const intlConfig = { 7 | language: 'en-us', 8 | languages: locales, 9 | messages: messages, 10 | originalPath: '/', 11 | redirect: true, 12 | routed: true, 13 | }; 14 | 15 | const GatsbyIntlProvider = storyFn => ( 16 | {storyFn()} 17 | ); 18 | 19 | export default GatsbyIntlProvider; 20 | -------------------------------------------------------------------------------- /.storybook/decorators/index.js: -------------------------------------------------------------------------------- 1 | export { default as EmotionThemeProvider } from './EmotionThemeProvider'; 2 | export { default as GatsbyIntlProvider } from './GatsbyIntlProvider'; 3 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../src/**/*.stories.tsx'], 3 | addons: [ 4 | '@storybook/addon-actions/register', 5 | '@storybook/addon-viewport/register', 6 | 'storybook-addon-intl/register', 7 | { 8 | name: '@storybook/preset-typescript', 9 | options: { 10 | tsLoaderOptions: { 11 | transpileOnly: true, 12 | }, 13 | }, 14 | }, 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `manager.js` replaces `addons.js` and allows you to customize how Storybook’s app UI renders. 3 | * That is, everything outside of the Canvas (preview iframe). 4 | * In common cases, you probably won’t need this file except when you’re theming Storybook. 5 | * 6 | * https://medium.com/storybookjs/declarative-storybook-configuration-49912f77b78 7 | */ 8 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { configure, addDecorator, addParameters } from '@storybook/react'; 4 | import { action } from '@storybook/addon-actions'; 5 | import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport'; 6 | import { setIntlConfig, withIntl } from 'storybook-addon-intl'; 7 | 8 | import { addLocaleData } from 'gatsby-plugin-intl'; 9 | import enLocaleData from 'react-intl/locale-data/en'; 10 | import esLocaleData from 'react-intl/locale-data/es'; 11 | 12 | import { EmotionThemeProvider, GatsbyIntlProvider } from './decorators'; 13 | import GlobalStyles from '../src/components/Layout/GlobalStyles'; 14 | 15 | // Gatsby Setup 16 | // ============================================ 17 | // Gatsby's Link overrides: 18 | // Gatsby defines a global called ___loader to prevent its method calls from creating console errors you override it here 19 | global.___loader = { 20 | enqueue: () => {}, 21 | hovering: () => {}, 22 | }; 23 | // Gatsby internal mocking to prevent unnecessary errors in storybook testing environment 24 | global.__PATH_PREFIX__ = ''; 25 | // 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 26 | window.___navigate = pathname => { 27 | action('NavigateTo:')(pathname); 28 | }; 29 | 30 | // Storybook Addons 31 | // ============================================ 32 | // TODO: Add our breakpoints to the list of viewport options 33 | addParameters({ 34 | viewport: { 35 | viewports: INITIAL_VIEWPORTS, 36 | defaultViewport: 'responsive', 37 | }, 38 | options: { 39 | panelPosition: 'right', 40 | }, 41 | }); 42 | 43 | // Storybook Decorators 44 | // ============================================ 45 | // Global Styles ============================== 46 | addDecorator(story => ( 47 | <> 48 | 49 |
{story()}
50 | 51 | )); 52 | 53 | // Emotion Theme Provider ===================== 54 | addDecorator(EmotionThemeProvider); 55 | 56 | // gatsby-plugin-intl Provider ================ 57 | // Set supported locales 58 | export const locales = ['en-us', 'es-es']; 59 | 60 | // Import translation messages 61 | export const messages = locales.reduce((acc, locale) => { 62 | return { 63 | ...acc, 64 | [locale]: require(`../src/locales/${locale}.json`), 65 | }; 66 | }, {}); 67 | 68 | const getMessages = locale => messages[locale]; 69 | 70 | // Set `storybook-addon-intl` configuration (handles `react-intl`) 71 | setIntlConfig({ 72 | locales, 73 | defaultLocale: 'en-us', 74 | getMessages, 75 | }); 76 | 77 | // Load the locale data for all your supported locales 78 | addLocaleData(enLocaleData); 79 | addLocaleData(esLocaleData); 80 | 81 | // Register decorators 82 | // Adds gatsby-plugin-intl IntlContextProvider which wraps the Gatsby Link component 83 | addDecorator(GatsbyIntlProvider); 84 | // Adds react-intl 85 | addDecorator(withIntl); 86 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = ({ config }) => { 4 | // Transpile Gatsby module because Gatsby includes un-transpiled ES6 code. 5 | // ======================================================== 6 | config.module.rules[0].exclude = [/node_modules\/(?!(gatsby)\/)/]; 7 | 8 | // Add Babel rules 9 | // ======================================================== 10 | // use installed babel-loader which is v8.0-beta (which is meant to work with @babel/core@7) 11 | config.module.rules[0].use[0].loader = require.resolve('babel-loader'); 12 | 13 | // use @babel/preset-react for JSX and env (instead of staged presets) 14 | config.module.rules[0].use[0].options.presets = [ 15 | require.resolve('@babel/preset-react'), 16 | require.resolve('@babel/preset-env'), 17 | // Emotion preset must run BEFORE reacts preset to properly convert css-prop. 18 | // Babel preset-ordering runs reversed (from last to first). Emotion has to be after React preset. 19 | require.resolve('@emotion/babel-preset-css-prop'), 20 | ]; 21 | 22 | config.module.rules[0].use[0].options.plugins = [ 23 | // use @babel/plugin-proposal-class-properties for class arrow functions 24 | require.resolve('@babel/plugin-proposal-class-properties'), 25 | // use babel-plugin-remove-graphql-queries to remove static queries from components when rendering in storybook 26 | require.resolve('babel-plugin-remove-graphql-queries'), 27 | ]; 28 | 29 | // Prefer Gatsby ES6 entrypoint (module) over commonjs (main) entrypoint 30 | // ======================================================== 31 | config.resolve.mainFields = ['browser', 'module', 'main']; 32 | 33 | // Add Webpack rules for TypeScript 34 | // ======================================================== 35 | config.module.rules.push({ 36 | test: /\.(ts|tsx)$/, 37 | loader: require.resolve('babel-loader'), 38 | options: { 39 | presets: [ 40 | ['react-app', { flow: false, typescript: true }], 41 | // Emotion preset must run BEFORE reacts preset to properly convert css-prop. 42 | // Babel preset-ordering runs reversed (from last to first). Emotion has to be after React preset. 43 | require.resolve('@emotion/babel-preset-css-prop'), 44 | ], 45 | plugins: [ 46 | require.resolve('@babel/plugin-proposal-class-properties'), 47 | // use babel-plugin-remove-graphql-queries to remove static queries from components when rendering in storybook 48 | require.resolve('babel-plugin-remove-graphql-queries'), 49 | ], 50 | }, 51 | }); 52 | 53 | config.resolve.extensions.push('.ts', '.tsx'); 54 | 55 | // Add SVGR Loader 56 | // ======================================================== 57 | // Remove svg rules from existing webpack rule 58 | const assetRule = config.module.rules.find(({ test }) => test.test('.svg')); 59 | 60 | const assetLoader = { 61 | loader: assetRule.loader, 62 | options: assetRule.options || assetRule.query, 63 | }; 64 | 65 | config.module.rules.unshift({ 66 | test: /\.svg$/, 67 | use: ['@svgr/webpack', assetLoader], 68 | }); 69 | 70 | // Mirror project aliases for some reason (should be picked up by .babelrc) 71 | // ======================================================== 72 | config.resolve.alias['~/utils'] = path.resolve(__dirname, '../src/utils'); 73 | config.resolve.alias['~/theme'] = path.resolve(__dirname, '../src/theme'); 74 | config.resolve.alias['~/components'] = path.resolve( 75 | __dirname, 76 | '../src/components' 77 | ); 78 | config.resolve.alias['~/images'] = path.resolve(__dirname, '../src/images'); 79 | config.resolve.alias['~/icons'] = path.resolve(__dirname, '../src/icons'); 80 | config.resolve.alias['@theme/styled'] = path.resolve( 81 | __dirname, 82 | '../src/styled' 83 | ); 84 | 85 | return config; 86 | }; 87 | -------------------------------------------------------------------------------- /.svgo.yml: -------------------------------------------------------------------------------- 1 | # multipass: true 2 | # full: true 3 | 4 | plugins: 5 | - cleanupIDs: true 6 | minify: true 7 | - cleanupListOfValues: true 8 | - convertColors: true 9 | - convertStyleToAttrs: true 10 | - convertTransform: true 11 | - cleanupNumericValues: true 12 | floatPrecision: 3 13 | - mergePaths: true 14 | - minifyStyles: true 15 | - moveElemesAttrsToGroup: true 16 | - removeAttrs: true 17 | attrs: 'fill-rule' 18 | - removeComments: true 19 | - removeDesc: true 20 | removeAny: true 21 | - removeDimensions: true 22 | - removeViewBox: false 23 | - removeDoctype: true 24 | - removeEditorsNSData: true 25 | - removeEmptyAttrs: true 26 | - removeEmptyContainers: true 27 | - removeEmptyText: true 28 | - removeNonInheritableGroupAttrs: true 29 | - removeTitle: false 30 | - removeUnknownsAndDefaults: true 31 | - removeUnusedNS: true 32 | - removeUselessDefs: true 33 | - removeUselessStrokeAndFill: true 34 | - removeXMLProcInst: true 35 | - sortAttrs: true 36 | # - addAttributesToSVGElement: 37 | # attributes: 38 | # - aria-hidden: true 39 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 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 |

2 | 3 | Gatsby 4 | 5 | 6 | TypeScript 7 | 8 | 9 | Emotion JS 10 | 11 | 12 | Storybook JS 13 | 14 |

15 | 16 |

17 |
18 | Gatsby + TypeScript + Emotion + Storybook 19 |
20 |
21 | (and React Intl + SVGR + Jest) 22 |
23 |

24 | 25 | [![Netlify Status](https://api.netlify.com/api/v1/badges/e97163d4-66e5-4abb-b6a9-27c4a278c7c2/deploy-status)](https://app.netlify.com/sites/gatsby-typescript-emotion-storybook/deploys) ![Dependabot](https://flat.badgen.net/dependabot/duncanleung/gatsby-typescript-emotion-storybook?icon=dependabot) 26 | 27 | ## ✨ About 28 | 29 | This repo is a starter to get a **Gatsby** + **TypeScript** + **Emotion** project (with React Intl + SVGR + Jest) working with **Storybook**. 30 | 31 | You can use this starter as a launch point or reference the `gatsby-config.js` and `/.storybook/webpack.config.js` config to see how to get the libraries working with Storybook. 32 | 33 | ### Integrated Libraries 34 | 35 | - [React.js](https://reactjs.org/) 36 | - [Gatsby.js](https://www.gatsbyjs.org/) 37 | - [Typescript](https://www.typescriptlang.org/) 38 | - [Emotion.js](https://emotion.sh/) 39 | - [gatsby-plugin-intl](https://github.com/wiziple/gatsby-plugin-intl/) 40 | - [gatsby-plugin-svgr](https://github.com/zabute/gatsby-plugin-svgr/) 41 | - [Jest](https://jestjs.io/) 42 | 43 | ## 📖 Related Reading 44 | 45 | I wrote some blog posts that document my learnings from setting up this starter. 46 | 47 | - [TypeScript Module Declaration for SVG Assets](https://duncanleung.com/typescript-module-declearation-svg-img-assets/) 48 | - [Use Emotion CSS Prop in Storybook](https://duncanleung.com/emotion-css-prop-jsx-pragma-storybook/) 49 | - [Import SVG Components in Storybook](https://duncanleung.com/import-svg-storybook-webpack-loader/) 50 | - [How to Setup ESLint .eslintrc Config](https://duncanleung.com/how-to-setup-eslint-eslintrc-config-difference-eslint-config-plugin/) 51 | 52 | ## 🚀 Quickstart 53 | 54 | There are few dependencies to install on your local machine to begin. 55 | 56 | 1. **Install Node** 57 | 58 | Install node. Using [Node Version Manager](https://github.com/nvm-sh/nvm) and Node.js [version >= 8.2.1](https://nodejs.org/en/download/releases/) is recommended (so that [npx](https://www.npmjs.com/package/npx) comes bundled). 59 | 60 | After Node is set up locally, check out the repo to your local machine and install the rest of the project dependencies by running: 61 | 62 | 2. **Create a Gatsby Site** 63 | 64 | Use the Gatsby CLI to create a new site, specifying this starter. 65 | 66 | ```sh 67 | # create a new Gatsby site using the blog starter 68 | npx gatsby new gatsby-typescript-emotion-storybook https://github.com/duncanleung/gatsby-typescript-emotion-storybook 69 | ``` 70 | 71 | 3. **Local Development** 72 | 73 | Navigate into your new site’s directory and start the dev server and start developing locally, run: 74 | 75 | ```sh 76 | cd gatsby-typescript-emotion-storybook 77 | 78 | yarn dev 79 | ``` 80 | 81 | Your site is now running at `http://localhost:8000`! 82 | 83 | Note: You'll also see a second link: `http://localhost:8000/___graphql`. This is a tool you can use to experiment with querying your data. Learn more about using this tool in the [Gatsby tutorial](https://www.gatsbyjs.org/tutorial/part-five/#introducing-graphiql). 84 | 85 | Open the gatsby-typescript-emotion-storybook directory in your code editor of choice and edit `src/pages/index.tsx`. Save your changes and the browser will update in real-time! 86 | 87 | **Environment Variables** 88 | 89 | Environment variables can be used to control various features or configurations. These environment variables are accessed at build time. 90 | 91 | Locally, [dotenv](https://www.npmjs.com/package/dotenv) allows storing env vars in a `.env` file. 92 | 93 | If you are just starting, rename the `.env.sample` to `.env` to get the base variables to run the project locally. 94 | 95 | Read the docs on how [Gatsby handles env vars](https://www.gatsbyjs.org/docs/environment-variables/). 96 | 97 | ### Typescript 98 | 99 | Types for this project are declared in `/src/@types`. 100 | 101 | The config is located in the `tsconfig.json` file in the root. 102 | 103 | 4. **Open the source code and start editing!** 104 | 105 | Your site is now running at `http://localhost:8000`! 106 | 107 | _Note: You'll also see a second link: _`http://localhost:8000/___graphql`_. This is a tool you can use to experiment with querying your data. Learn more about using this tool in the [Gatsby tutorial](https://www.gatsbyjs.org/tutorial/part-five/#introducing-graphiql)._ 108 | 109 | Open the `gatsby-starter-typescript-storybook` directory in your code editor of choice and edit `src/pages/index.tsx`. Save your changes and the browser will update in real-time! 110 | 111 | 5. **Run Storybook** 112 | 113 | [Storybook](https://storybook.js.org) allows for Component Driven Development. Start up the storybook runtime by running this script: 114 | 115 | Start running Storybook's local development environment. 116 | 117 | ```bash 118 | yarn storybook 119 | ``` 120 | 121 | 5) **Run Tests** 122 | 123 | Jest and React Testing Library provide test running and rendering. 124 | 125 | Start running Jest 126 | 127 | ```bash 128 | yarn test // runs jest CLI 129 | yarn test:watch // runs jest with --watch flag 130 | ``` 131 | 132 | The Jest config will look for test files with the naming convention `*.test.ts` or `*.test.tsx`. 133 | 134 | The convention in this project is to co-locate tests in a sub-directory called `__tests__`, in the same directory that the component or functions live. 135 | 136 | Global mocks are located in the `__mocks__` directory in the root of the project, while local mocks are co-located where they are used. 137 | Mock test data should be added under sub-directory `data` within the `__tests__` directory, ex: `__tests__/data/component-test-data.ts` 138 | 139 | > Jest automatically creates a `__snapshots__` directory if you happen to be using snapshot testing. 140 | 141 | To find out more about testing, look at the following resources: 142 | 143 | - [Jest docs](https://jestjs.io/) 144 | - [React Testing Library](https://github.com/testing-library/react-testing-library) 145 | 146 | ## 🧐 What's inside? 147 | 148 | A quick look at the top-level files and directories you'll see in a Gatsby project. 149 | 150 | . 151 | ├── node_modules 152 | ├── src 153 | ├── .gitignore 154 | ├── .prettierrc 155 | ├── gatsby-browser.js 156 | ├── gatsby-config.js 157 | ├── gatsby-node.js 158 | ├── gatsby-ssr.js 159 | ├── LICENSE 160 | ├── package-lock.json 161 | ├── package.json 162 | └── README.md 163 | 164 | 1. **`/node_modules`**: This directory contains all of the modules of code that your project depends on (npm packages) are automatically installed. 165 | 166 | 2. **`/src`**: This directory will contain all of the code related to what you will see on the front-end of your site (what you see in the browser) such as your site header or a page template. `src` is a convention for “source code”. 167 | 168 | 3. **`.gitignore`**: This file tells git which files it should not track / not maintain a version history for. 169 | 170 | 4. **`.prettierrc`**: This is a configuration file for [Prettier](https://prettier.io/). Prettier is a tool to help keep the formatting of your code consistent. 171 | 172 | 5. **`gatsby-browser.js`**: This file is where Gatsby expects to find any usage of the [Gatsby browser APIs](https://www.gatsbyjs.org/docs/browser-apis/) (if any). These allow customization/extension of default Gatsby settings affecting the browser. 173 | 174 | 6. **`gatsby-config.js`**: This is the main configuration file for a Gatsby site. This is where you can specify information about your site (metadata) like the site title and description, which Gatsby plugins you’d like to include, etc. (Check out the [config docs](https://www.gatsbyjs.org/docs/gatsby-config/) for more detail). 175 | 176 | 7. **`gatsby-node.js`**: This file is where Gatsby expects to find any usage of the [Gatsby Node APIs](https://www.gatsbyjs.org/docs/node-apis/) (if any). These allow customization/extension of default Gatsby settings affecting pieces of the site build process. 177 | 178 | 8. **`gatsby-ssr.js`**: This file is where Gatsby expects to find any usage of the [Gatsby server-side rendering APIs](https://www.gatsbyjs.org/docs/ssr-apis/) (if any). These allow customization of default Gatsby settings affecting server-side rendering. 179 | 180 | 9. **`LICENSE`**: Gatsby is licensed under the MIT license. 181 | 182 | 10. **`package-lock.json`** (See `package.json` below, first). This is an automatically generated file based on the exact versions of your npm dependencies that were installed for your project. **(You won’t change this file directly).** 183 | 184 | 11. **`package.json`**: A manifest file for Node.js projects, which includes things like metadata (the project’s name, author, etc). This manifest is how npm knows which packages to install for your project. 185 | 186 | 12. **`README.md`**: A text file containing useful reference information about your project. 187 | 188 | ## 🎓 Learning Gatsby 189 | 190 | Looking for more guidance? Full documentation for Gatsby lives [on the website](https://www.gatsbyjs.org/). Here are some places to start: 191 | 192 | - **For most developers, we recommend starting with our [in-depth tutorial for creating a site with Gatsby](https://www.gatsbyjs.org/tutorial/).** It starts with zero assumptions about your level of ability and walks through every step of the process. 193 | 194 | - **To dive straight into code samples, head [to our documentation](https://www.gatsbyjs.org/docs/).** In particular, check out the _Guides_, _API Reference_, and _Advanced Tutorials_ sections in the sidebar. 195 | 196 | ## 💫 Deploy 197 | 198 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/duncanleung/gatsby-typescript-emotion-storybook) 199 | -------------------------------------------------------------------------------- /__mocks__/file-mock.js: -------------------------------------------------------------------------------- 1 | // Related to jest.config.js `moduleNameMapper` on how to handle imports. 2 | // Use this stub to mock static file imports which Jest can’t handle 3 | module.exports = 'test-file-stub'; 4 | -------------------------------------------------------------------------------- /__mocks__/gatsby-plugin-intl.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const gatsbyPluginIntl = jest.requireActual('gatsby-plugin-intl'); 4 | 5 | module.exports = { 6 | ...gatsbyPluginIntl, 7 | Link: jest 8 | .fn() 9 | .mockImplementation( 10 | ({ 11 | activeClassName, 12 | activeStyle, 13 | getProps, 14 | innerRef, 15 | partiallyActive, 16 | ref, 17 | replace, 18 | to, 19 | language, 20 | ...rest 21 | }) => 22 | React.createElement('a', { 23 | ...rest, 24 | href: to, 25 | }) 26 | ), 27 | }; 28 | -------------------------------------------------------------------------------- /__mocks__/gatsby.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const gatsby = jest.requireActual('gatsby'); 4 | 5 | // Mocks graphql() function, Link component, and StaticQuery component 6 | module.exports = { 7 | ...gatsby, 8 | graphql: jest.fn(), 9 | Link: jest.fn().mockImplementation( 10 | // these props are invalid for an `a` tag 11 | ({ 12 | activeClassName, 13 | activeStyle, 14 | getProps, 15 | innerRef, 16 | partiallyActive, 17 | ref, 18 | replace, 19 | to, 20 | ...rest 21 | }) => 22 | React.createElement('a', { 23 | ...rest, 24 | href: to, 25 | }) 26 | ), 27 | StaticQuery: jest.fn(), 28 | useStaticQuery: jest.fn(), 29 | }; 30 | -------------------------------------------------------------------------------- /__mocks__/svgr-mock.js: -------------------------------------------------------------------------------- 1 | module.exports = { ReactComponent: 'icon-mock' }; 2 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { App } from './src/components/App'; 4 | 5 | // Duplicated in gatsby-ssr.js for server side rendering during the build 6 | export const wrapRootElement = props => ; 7 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config({ 2 | path: '.env', 3 | }); 4 | 5 | const supportedLanguages = require('./src/utils/i18n/supportedLanguages'); 6 | 7 | const languages = supportedLanguages.map(language => language.languageTag); 8 | 9 | const plugins = [ 10 | 'gatsby-plugin-react-helmet', 11 | 'gatsby-transformer-sharp', 12 | 'gatsby-plugin-sharp', 13 | 'gatsby-plugin-typescript', 14 | 'gatsby-plugin-emotion', 15 | 'gatsby-plugin-remove-serviceworker', 16 | 'gatsby-plugin-svgr', 17 | { 18 | resolve: 'gatsby-plugin-intl', 19 | options: { 20 | path: `${__dirname}/src/locales`, 21 | languages, 22 | defaultLanguage: 'en-us', 23 | redirect: true, 24 | }, 25 | }, 26 | { 27 | resolve: 'gatsby-plugin-google-fonts', 28 | options: { 29 | fonts: [`Lora:400,700`], 30 | display: 'swap', 31 | }, 32 | }, 33 | ]; 34 | 35 | // Bundle analyzer, dev only 36 | if (process.env.ENABLE_BUNDLE_ANALYZER === '1') { 37 | plugins.push('gatsby-plugin-webpack-bundle-analyser-v2'); 38 | } 39 | 40 | module.exports = { 41 | plugins, 42 | }; 43 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | require('dotenv').config({ 4 | path: '.env', 5 | }); 6 | 7 | exports.onCreateWebpackConfig = function addPathMapping({ 8 | stage, 9 | actions, 10 | getConfig, 11 | }) { 12 | actions.setWebpackConfig({ 13 | resolve: { 14 | alias: { 15 | '~': path.resolve(__dirname, 'src'), 16 | }, 17 | }, 18 | }); 19 | 20 | // Attempt to improve webpack vender code splitting 21 | if (stage === 'build-javascript') { 22 | const config = getConfig(); 23 | 24 | config.optimization.splitChunks.cacheGroups = { 25 | ...config.optimization.splitChunks.cacheGroups, 26 | vendors: { 27 | test: /[\\/]node_modules[\\/]/, 28 | enforce: true, 29 | chunks: 'all', 30 | priority: 1, 31 | }, 32 | }; 33 | 34 | // Ensure Gatsby does not do any css code splitting 35 | config.optimization.splitChunks.cacheGroups.styles.priority = 10; 36 | 37 | actions.replaceWebpackConfig(config); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { App } from './src/components/App'; 3 | 4 | const supportedLanguages = require('./src/utils/i18n/supportedLanguages'); 5 | 6 | require('dotenv').config({ 7 | path: '.env', 8 | }); 9 | 10 | // Duplicated in gatsby-browser.js for client side rendering 11 | export const wrapRootElement = props => ; 12 | 13 | export const onRenderBody = ({ pathname, setHeadComponents }) => { 14 | // Create a string to allow a regex replacement for SEO hreflang links: https://support.google.com/webmasters/answer/189077?hl=en 15 | const supportedLocaleRegexGroups = supportedLanguages 16 | .map(language => language.languageTag) 17 | .join('|'); 18 | 19 | const hrefLangLinks = [ 20 | ...supportedLanguages.map(language => { 21 | // Must be a fully qualified site URL 22 | const href = `${process.env.GATSBY_SITE_URL}/${language.languageTag + 23 | pathname.replace(new RegExp(`^/(${supportedLocaleRegexGroups})`), '')}`; 24 | 25 | return ( 26 | 31 | ); 32 | }), 33 | ]; 34 | 35 | // Async embed code from Typekit/Adobe @link https://helpx.adobe.com/fonts/using/embed-codes.html#JavaScriptembedcode 36 | const typeKitScript = [ 37 |