├── .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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Gatsby + TypeScript + Emotion + Storybook
19 |
20 |
21 | (and React Intl + SVGR + Jest)
22 |
23 |
24 |
25 | [](https://app.netlify.com/sites/gatsby-typescript-emotion-storybook/deploys) 
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 | [](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 | ,
52 | ];
53 |
54 | setHeadComponents([...hrefLangLinks, ...typeKitScript]);
55 | };
56 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | // all ts or tsx files need to be transformed using jest-preprocess.js
3 | // Set up Babel config in jest-preprocess.js
4 | transform: {
5 | // Allow tests in TypeScript using the .ts or .tsx
6 | '^.+\\.[jt]sx?$': '/test-utils/jest-preprocess.js',
7 | },
8 | testRegex: '(/__tests__/.*(test|spec))\\.([tj]sx?)$',
9 | moduleDirectories: ['node_modules', __dirname],
10 | // Works like webpack rules. Tells Jest how to handle imports
11 | moduleNameMapper: {
12 | // Mock static file imports and assets which Jest can’t handle
13 | // stylesheets use the package identity-obj-proxy
14 | '.+\\.(css|styl|less|sass|scss)$': 'identity-obj-proxy',
15 | // Manual mock other files using file-mock.js
16 | '.+\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
17 | '/__mocks__/file-mock.js',
18 | // Mock SVG
19 | '\\.svg': '/__mocks__/svgr-mock.js',
20 | '^~/(.*)$': '/src/$1',
21 | '@theme/styled': '/src/styled',
22 | },
23 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
24 | testPathIgnorePatterns: ['node_modules', '.cache', 'public'],
25 | // Gatsby includes un-transpiled ES6 code. Exclude the gatsby module.
26 | transformIgnorePatterns: ['node_modules/(?!(gatsby)/)'],
27 | globals: {
28 | __PATH_PREFIX__: '',
29 | },
30 | collectCoverageFrom: [
31 | 'src/**/*.{js,jsx,ts,tsx}',
32 | '!/src/**/*.stories.{ts,tsx}',
33 | '!/src/**/__tests__/**/*',
34 | '!/src/components/**/index.ts',
35 | '!/node_modules/',
36 | '!/test-utils/',
37 | ],
38 | testURL: 'http://localhost',
39 | setupFiles: ['/test-utils/loadershim.js', 'jest-localstorage-mock'],
40 | setupFilesAfterEnv: ['/test-utils/setup-test-env.ts'],
41 | };
42 |
43 | module.exports = config;
44 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "~/*": ["src/*"],
6 | "@theme/styled": ["src/styled"]
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-typescript-emotion-storybook-starter",
3 | "author": "Duncan Leung (https://www.duncanleung.com)",
4 | "description": "Gatsby + TypeScript + Emotion + Storybook + React Intl + SVGR + Jest - Starter",
5 | "version": "1.0.0",
6 | "homepage": "https://github.com/duncanleung/gatsby-storybook-typescript-emotion-react-intl-svgr",
7 | "bugs": "https://github.com/duncanleung/gatsby-storybook-typescript-emotion-react-intl-svgr",
8 | "keywords": [
9 | "gatsby",
10 | "typescript",
11 | "emotion",
12 | "storybook",
13 | "react-intl",
14 | "svgr",
15 | "jest"
16 | ],
17 | "license": "MIT",
18 | "jest": {
19 | "setupTestFrameworkScriptFile": "/setup-test-env.js"
20 | },
21 | "scripts": {
22 | "build": "gatsby build",
23 | "clean": "rm -rf public && rm -rf .cache",
24 | "dev": "yarn clean && node --max-http-header-size=16385 node_modules/.bin/gatsby develop",
25 | "debug": "yarn clean && node --nolazy --inspect-brk node_modules/.bin/gatsby develop",
26 | "serve": "node --max-http-header-size=16385 node_modules/.bin/gatsby serve",
27 | "lint": "yarn lint:js && yarn lint:ts",
28 | "lint:js": "./node_modules/.bin/eslint --color --ext .js,.jsx .",
29 | "lint:ts": "./node_modules/.bin/eslint --color --ext .ts,.tsx .",
30 | "eslint-prettier-check-all": "yarn eslint-prettier-check-ts && yarn eslint-prettier-check-js",
31 | "eslint-prettier-check-ts": "eslint --print-config src/pages/index.tsx | eslint-config-prettier-check",
32 | "eslint-prettier-check-js": "eslint --print-config gatsby-browser.js | eslint-config-prettier-check",
33 | "test": "jest",
34 | "test:coverage": "yarn test --coverage",
35 | "test:watch": "jest --watch",
36 | "format": "yarn lint:js --fix && yarn lint:ts --fix",
37 | "storybook": "start-storybook",
38 | "storybook:build": "build-storybook -c .storybook -o .out"
39 | },
40 | "dependencies": {
41 | "@emotion/babel-preset-css-prop": "^10.0.27",
42 | "@emotion/core": "^10.0.27",
43 | "@emotion/styled": "^10.0.27",
44 | "@reach/router": "^1.3.1",
45 | "@svgr/webpack": "^5.1.0",
46 | "babel-plugin-module-resolver": "^4.0.0",
47 | "dotenv": "^8.2.0",
48 | "emotion-theming": "^10.0.27",
49 | "gatsby": "^2.19.19",
50 | "gatsby-image": "^2.2.41",
51 | "gatsby-plugin-emotion": "^4.1.22",
52 | "gatsby-plugin-google-fonts": "^1.0.1",
53 | "gatsby-plugin-intl": "0.3.3",
54 | "gatsby-plugin-react-helmet": "^3.1.22",
55 | "gatsby-plugin-remove-serviceworker": "^1.0.0",
56 | "gatsby-plugin-sharp": "^2.4.5",
57 | "gatsby-plugin-svgr": "^2.0.2",
58 | "gatsby-plugin-typescript": "^2.1.27",
59 | "gatsby-transformer-sharp": "^2.3.14",
60 | "polished": "^3.4.4",
61 | "react": "^16.12.0",
62 | "react-dom": "^16.12.0",
63 | "react-helmet": "^5.2.1"
64 | },
65 | "devDependencies": {
66 | "@babel/core": "^7.8.4",
67 | "@storybook/addon-actions": "^5.3.13",
68 | "@storybook/addon-info": "^5.3.13",
69 | "@storybook/addon-viewport": "^5.3.13",
70 | "@storybook/preset-typescript": "^1.2.0",
71 | "@storybook/react": "^5.3.13",
72 | "@testing-library/jest-dom": "^5.1.1",
73 | "@testing-library/react": "^9.4.0",
74 | "@types/jest": "^25.1.3",
75 | "@types/node": "^13.7.4",
76 | "@types/react": "^16.9.50",
77 | "@types/react-dom": "^16.9.5",
78 | "@types/react-helmet": "^5.0.15",
79 | "@types/react-intl": "2.3.18",
80 | "@types/storybook__react": "^5.2.1",
81 | "@typescript-eslint/eslint-plugin": "^2.34.0",
82 | "@typescript-eslint/parser": "^2.20.0",
83 | "babel-eslint": "^10.1.0",
84 | "babel-jest": "^25.1.0",
85 | "babel-loader": "^8.0.6",
86 | "babel-plugin-emotion": "^10.0.27",
87 | "babel-plugin-jsx-remove-data-test-id": "^2.1.3",
88 | "babel-plugin-remove-graphql-queries": "^2.7.23",
89 | "babel-preset-gatsby": "^0.2.29",
90 | "babel-preset-react-app": "^9.1.1",
91 | "eslint": "^6.8.0",
92 | "eslint-config-airbnb": "^18.0.1",
93 | "eslint-config-airbnb-typescript": "^7.0.0",
94 | "eslint-config-prettier": "^6.10.0",
95 | "eslint-import-resolver-alias": "^1.1.2",
96 | "eslint-plugin-import": "^2.20.1",
97 | "eslint-plugin-jest": "^23.7.0",
98 | "eslint-plugin-jsx-a11y": "^6.2.3",
99 | "eslint-plugin-no-only-tests": "^2.4.0",
100 | "eslint-plugin-prettier": "^3.1.2",
101 | "eslint-plugin-react": "^7.18.3",
102 | "eslint-plugin-react-hooks": "^2.4.0",
103 | "gatsby-plugin-webpack-bundle-analyser-v2": "^1.1.8",
104 | "husky": "^4.2.3",
105 | "identity-obj-proxy": "^3.0.0",
106 | "jest": "^25.1.0",
107 | "jest-emotion": "^10.0.27",
108 | "jest-localstorage-mock": "^2.4.0",
109 | "precise-commits": "^1.0.2",
110 | "prettier": "^1.19.1",
111 | "react-docgen-typescript-loader": "^3.6.0",
112 | "react-test-renderer": "^16.12.0",
113 | "storybook-addon-intl": "^2.4.1",
114 | "ts-essentials": "^6.0.1",
115 | "ts-jest": "^25.2.1",
116 | "ts-loader": "^6.2.1",
117 | "typescript": "^3.8.2"
118 | },
119 | "husky": {
120 | "hooks": {
121 | "pre-commit": "precise-commits"
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/@types/assets/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg' {
2 | import React = require('react');
3 | export const ReactComponent: React.SFC>;
4 | const src: string;
5 | export default src;
6 | }
7 |
8 | declare module '*.jpg' {
9 | const jpgContent: string;
10 | export { jpgContent };
11 | }
12 |
13 | declare module '*.png' {
14 | const pngContent: string;
15 | export { pngContent };
16 | }
17 |
18 | declare module '*.json' {
19 | const jsonContent: string;
20 | export { jsonContent };
21 | }
22 |
--------------------------------------------------------------------------------
/src/@types/gatsby-plugin-intl/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'gatsby-plugin-intl' {
2 | /* eslint-disable-next-line import/no-extraneous-dependencies */
3 | import ReactIntl from 'react-intl';
4 | import Gatsby from 'gatsby';
5 |
6 | // Type mapping from react-intl
7 | export import injectIntl = ReactIntl.injectIntl;
8 | export import InjectedIntl = ReactIntl.InjectedIntl;
9 | export import InjectedIntlProps = ReactIntl.InjectedIntlProps;
10 | export import FormattedMessage = ReactIntl.FormattedMessage;
11 | export import FormattedHTMLMessage = ReactIntl.FormattedHTMLMessage;
12 | export import FormattedNumber = ReactIntl.FormattedNumber;
13 |
14 | // Type mapping from Gatsby
15 | export import Link = Gatsby.Link;
16 | export import navigate = Gatsby.navigate;
17 | }
18 |
--------------------------------------------------------------------------------
/src/@types/index.d.ts:
--------------------------------------------------------------------------------
1 | export type PartialBy = Omit & Partial>;
2 |
--------------------------------------------------------------------------------
/src/components/App/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import { ThemeProvider } from 'emotion-theming';
3 |
4 | import theme from '../../theme';
5 |
6 | /**
7 | * This component exists to provide a reusable application wrapper for use in Gatsby API's, testing, etc.
8 | */
9 | const App = ({ element }: { element: ReactNode }) => {
10 | return {element};
11 | };
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/src/components/App/index.ts:
--------------------------------------------------------------------------------
1 | export { default as App } from './App';
2 |
--------------------------------------------------------------------------------
/src/components/Button/Button.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-props-no-spreading */
2 | import React from 'react';
3 | import styled from '@emotion/styled';
4 | import { css } from '@emotion/core';
5 | import { Theme } from '@theme/styled';
6 | import { rem } from 'polished';
7 | import { Link } from 'gatsby-plugin-intl';
8 |
9 | import { LoadingSpinner } from '~/components/LoadingIndicators';
10 |
11 | import { above, spacer } from '~/utils/styles';
12 | import { GetRenderComponentProps } from '~/utils/types';
13 |
14 | type Color = 'primary' | 'secondary';
15 | type Variant = 'default' | 'outline';
16 | export type Size = 'large' | 'medium' | 'small';
17 |
18 | type RenderComponent = React.ComponentType | typeof Link | 'a';
19 |
20 | type Props = {
21 | as?: E;
22 | name?: string;
23 | size?: Size;
24 | color?: Color;
25 | variant?: Variant;
26 | isLoading?: boolean;
27 | disabled?: boolean;
28 | /* Strips button styles for a button that looks like a regular anchor tag */
29 | stripButtonStyles?: boolean;
30 | } & React.ComponentProps<'button'> &
31 | GetRenderComponentProps;
32 |
33 | // These colors are not part of the design system color palette (defined in the Theme)
34 | const interactionColors = {
35 | hover: {
36 | primary: {
37 | default: '#ffD840',
38 | outline: '#ffcc00',
39 | },
40 | secondary: {
41 | default: '#40a2a6',
42 | outline: '#008489',
43 | },
44 | },
45 | active: {
46 | primary: {
47 | default: '#e6b800',
48 | outline: '#ffcc00',
49 | },
50 | secondary: {
51 | default: '#00696d',
52 | outline: '#00696D',
53 | },
54 | },
55 | };
56 |
57 | const createStyles = (
58 | theme: Theme,
59 | size: Size,
60 | color: Color,
61 | variant: Variant
62 | ) => {
63 | return css`
64 | /* Defaults (size - medium, color - primary, variant - default) */
65 | position: relative;
66 | display: inline-block;
67 | font-size: ${rem('18px')};
68 | font-weight: 400;
69 | text-align: center;
70 | padding: ${spacer(2)} ${spacer(4)};
71 | border-radius: ${theme.shape.borderRadius.large}px;
72 | color: ${theme.color.text.heading};
73 | background-color: ${theme.color[color]};
74 | transition: 150ms;
75 | cursor: pointer;
76 | line-height: 1.25;
77 |
78 | &:hover {
79 | background-color: ${interactionColors.hover[color][variant]};
80 | }
81 |
82 | &:active {
83 | background-color: ${interactionColors.active[color][variant]};
84 | }
85 |
86 | &:disabled {
87 | background-color: ${theme.color.disabled};
88 | }
89 |
90 | ${above(
91 | 'sm',
92 | css`
93 | padding: ${spacer(2)} ${spacer(6)};
94 | `
95 | )}
96 |
97 | /* Size modifiers */
98 | ${size === 'small' &&
99 | css`
100 | padding: ${spacer()} ${spacer(3)};
101 | font-size: ${rem('16px')};
102 |
103 | ${above(
104 | 'sm',
105 | css`
106 | padding: ${spacer(1.5)} ${spacer(4)};
107 | `
108 | )}
109 | `}
110 |
111 | /* Color modifiers */
112 | ${color === 'secondary' &&
113 | css`
114 | color: #fff;
115 | background-color: ${theme.color.secondary};
116 | `}
117 |
118 | /* Variant - outline */
119 | ${variant === 'outline' &&
120 | css`
121 | color: ${color === 'primary'
122 | ? theme.color.text.heading
123 | : theme.color.secondary};
124 | background-color: transparent;
125 | border: 1px solid
126 | ${color === 'primary'
127 | ? theme.color.text.heading
128 | : theme.color.secondary};
129 |
130 | &:hover,
131 | &:active {
132 | color: #fff;
133 | }
134 | `}
135 | `;
136 | };
137 |
138 | const loadingStyles = css`
139 | display: block;
140 | position: absolute;
141 | top: 50%;
142 | right: 15px;
143 | transform: translateY(-50%);
144 | `;
145 |
146 | // For buttons that should look like links
147 | const stripStyles = css`
148 | background: none;
149 | color: inherit;
150 | border: none;
151 | padding: 0;
152 | font: inherit;
153 | cursor: pointer;
154 | outline: inherit;
155 | `;
156 |
157 | const StyledButton = styled.button``;
158 |
159 | const Button = ({
160 | as,
161 | size = 'medium',
162 | color = 'primary',
163 | variant = 'default',
164 | isLoading = false,
165 | stripButtonStyles = false,
166 | children,
167 | ...props
168 | }: Props): ReturnType>> => {
169 | const buttonProps = {
170 | css: (theme: Theme) =>
171 | !stripButtonStyles
172 | ? createStyles(theme, size, color, variant)
173 | : stripStyles,
174 | ...props,
175 | };
176 |
177 | return (
178 |
179 | {children}
180 | {isLoading && }
181 |
182 | );
183 | };
184 |
185 | export default Button;
186 |
--------------------------------------------------------------------------------
/src/components/Button/__tests__/Button.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'test-utils';
3 | import { rem } from 'polished';
4 | import { Link } from 'gatsby-plugin-intl';
5 | import theme from '~/theme';
6 |
7 | import Button from '../Button';
8 | import { spacer } from '~/utils/styles';
9 |
10 | describe('', () => {
11 | describe('default button with style modifiers', () => {
12 | test('should render default button styles', () => {
13 | const { getByText } = render();
14 |
15 | const button = getByText('Click Me');
16 |
17 | expect(button).toHaveStyleRule('font-size', rem('18px'));
18 | expect(button).toHaveStyleRule('padding', `${spacer(2)} ${spacer(4)}`);
19 | expect(button).toHaveStyleRule('color', theme.color.text.heading);
20 | expect(button).toHaveStyleRule('background-color', theme.color.primary);
21 | });
22 |
23 | test('should render button with small modifier styles', () => {
24 | const { getByText } = render();
25 |
26 | const button = getByText('Click Me');
27 |
28 | expect(button).toHaveStyleRule('font-size', rem('16px'));
29 | expect(button).toHaveStyleRule('padding', `${spacer()} ${spacer(3)}`);
30 | });
31 |
32 | test('should render button with secondary color modifier styles', () => {
33 | const { getByText } = render();
34 |
35 | const button = getByText('Click Me');
36 |
37 | expect(button).toHaveStyleRule('background-color', theme.color.secondary);
38 | });
39 | });
40 |
41 | describe('outline variant button with style modifiers', () => {
42 | test('should render outline button styles', () => {
43 | const { getByText } = render();
44 |
45 | const button = getByText('Click Me');
46 |
47 | expect(button).toHaveStyleRule('color', theme.color.text.heading);
48 | expect(button).toHaveStyleRule('background-color', 'transparent');
49 | expect(button).toHaveStyleRule(
50 | 'border',
51 | `1px solid ${theme.color.text.heading}`
52 | );
53 | });
54 |
55 | test('should render outline button with small modifier styles', () => {
56 | const { getByText } = render(
57 |
60 | );
61 |
62 | const button = getByText('Click Me');
63 |
64 | expect(button).toHaveStyleRule('font-size', rem('16px'));
65 | expect(button).toHaveStyleRule('padding', `${spacer()} ${spacer(3)}`);
66 | });
67 |
68 | test('should render outline button with secondary color modifier styles', () => {
69 | const { getByText } = render(
70 |
73 | );
74 |
75 | const button = getByText('Click Me');
76 |
77 | expect(button).toHaveStyleRule('color', theme.color.secondary);
78 | expect(button).toHaveStyleRule(
79 | 'border',
80 | `1px solid ${theme.color.secondary}`
81 | );
82 | });
83 | });
84 |
85 | test('should render as anchor tag if type is "link"', () => {
86 | const { container } = render(
87 |
90 | );
91 |
92 | const linkAsButton = container.querySelector('a');
93 |
94 | expect(linkAsButton).toBeInTheDocument();
95 | });
96 |
97 | test('should render as anchor tag if type is "a"', () => {
98 | const { container } = render(
99 |
102 | );
103 |
104 | const linkAsButton = container.querySelector('a');
105 |
106 | expect(linkAsButton).toBeInTheDocument();
107 | });
108 | });
109 |
--------------------------------------------------------------------------------
/src/components/Button/button.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { FormattedMessage } from 'gatsby-plugin-intl';
4 |
5 | import Button from './Button';
6 |
7 | export default {
8 | title: 'Button',
9 | };
10 |
11 | export const Link = () => (
12 |
15 | );
16 |
17 | export const Loading = () => (
18 |
21 | );
22 |
23 | export const Outline = () => (
24 |
27 | );
28 |
29 | export const Secondary = () => (
30 |
33 | );
34 |
--------------------------------------------------------------------------------
/src/components/Button/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Button } from './Button';
2 |
--------------------------------------------------------------------------------
/src/components/Layout/DefaultLayout.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-props-no-spreading */
2 | import React from 'react';
3 | import { css } from '@emotion/core';
4 |
5 | import GlobalStyles from './GlobalStyles';
6 |
7 | type Props = {};
8 |
9 | const DefaultLayout: React.FC = ({
10 | children,
11 |
12 | ...props
13 | }) => {
14 | return (
15 | <>
16 |
17 |
23 | {children}
24 |
25 | >
26 | );
27 | };
28 |
29 | export default DefaultLayout;
30 |
--------------------------------------------------------------------------------
/src/components/Layout/GlobalStyles.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Global, css } from '@emotion/core';
3 | import { normalize, rem } from 'polished';
4 |
5 | import { above, spacer } from '~/utils/styles';
6 |
7 | const GlobalStyles: React.FC = () => {
8 | return (
9 | css`
11 | ${normalize()}
12 |
13 | html,
14 | body {
15 | font-family: 'neue-haas-unica', serif;
16 | font-weight: 400;
17 | color: ${theme.color.text.body};
18 | line-height: 1.5;
19 | box-sizing: border-box;
20 | }
21 |
22 | *,
23 | *:before,
24 | *:after {
25 | box-sizing: inherit;
26 | }
27 |
28 | /* HTML selector defaults */
29 | h1,
30 | h2,
31 | h3,
32 | h4,
33 | h5,
34 | h6 {
35 | font-family: 'Lora', serif;
36 | font-weight: 600;
37 | line-height: 1.3;
38 | color: ${theme.color.text.heading};
39 | margin-top: 0;
40 | margin-bottom: ${theme.spacing.unit * 3}px;
41 | }
42 |
43 | a {
44 | color: ${theme.color.secondary};
45 | text-decoration: none;
46 | }
47 |
48 | p {
49 | margin-bottom: ${spacer(2.5)}px;
50 |
51 | ${above(
52 | 'md',
53 | css`
54 | font-size: ${rem('18px')};
55 | `
56 | )};
57 | }
58 |
59 | img {
60 | max-width: 100%;
61 | margin-bottom: ${theme.spacing.unit * 3}px;
62 | }
63 |
64 | button {
65 | color: ${theme.color.secondary};
66 | font-weight: 500;
67 | background: none;
68 | border: 0;
69 | }
70 | `}
71 | />
72 | );
73 | };
74 |
75 | export default GlobalStyles;
76 |
--------------------------------------------------------------------------------
/src/components/Layout/index.ts:
--------------------------------------------------------------------------------
1 | export { default as GlobalStyles } from './GlobalStyles';
2 | export { default as Layout } from './DefaultLayout';
3 |
--------------------------------------------------------------------------------
/src/components/LoadingIndicators/ContentLoading.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-props-no-spreading */
2 | import React from 'react';
3 | import { rgba } from 'polished';
4 |
5 | import styled from '@theme/styled';
6 | import { spacer } from '~/utils/styles';
7 |
8 | const Wrapper = styled.div`
9 | height: 300px;
10 | margin-top: ${spacer(5)};
11 |
12 | /*
13 | Each linear gradient represents a placeholder element in the skeleton UI
14 | @link https://css-tricks.com/building-skeleton-screens-css-custom-properties/
15 | */
16 | background: linear-gradient(
17 | 90deg,
18 | ${rgba('#ffffff', 0)} 0,
19 | ${rgba('#ffffff', 0.8)} 50%,
20 | ${rgba('#ffffff', 0)} 100%
21 | ) -100% 0,
22 | linear-gradient(#f3f1f0 12px, transparent) 0 0,
23 | linear-gradient(#f3f1f0 12px, transparent) 0 40px,
24 | linear-gradient(#f3f1f0 12px, transparent) 0 80px,
25 | linear-gradient(#f3f1f0 12px, transparent) 0 140px,
26 | linear-gradient(#f3f1f0 12px, transparent) 0 180px;
27 |
28 | background-size: 35% 100%, 33% 12px, 100% 12px, 66% 12px, 100% 12px, 50% 12px;
29 |
30 | background-repeat: no-repeat;
31 | animation: content-loading 2s infinite;
32 |
33 | /* Note, only first background-image values change for this animation */
34 | @keyframes content-loading {
35 | to {
36 | background-position: 200% 0, 0 0, 0 40px, 0 80px, 0 140px, 0 180px;
37 | }
38 | }
39 | `;
40 |
41 | /**
42 | * This component provided a general skeleton UI loading experience that can be used in
43 | * multiple component types to indicate asynchronous content loading.
44 | */
45 | const ContentLoading: React.FC = props => {
46 | return ;
47 | };
48 |
49 | export default ContentLoading;
50 |
--------------------------------------------------------------------------------
/src/components/LoadingIndicators/LoadingSpinner.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-props-no-spreading */
2 | import React from 'react';
3 | import { em } from 'polished';
4 |
5 | import styled from '@theme/styled';
6 | import { ReactComponent as SpinnerIcon } from '~/icons/loading-spinner.svg';
7 |
8 | type Props = {
9 | /** A size value to apply to the height and width of the spinner, Example: '18'. Will be set as pixel units */
10 | size?: string;
11 | };
12 |
13 | const Wrapper = styled.div`
14 | position: relative;
15 | width: ${({ size }) => em(size as string)};
16 | height: ${({ size }) => em(size as string)};
17 |
18 | > * {
19 | width: ${({ size }) => em(size as string)};
20 | height: ${({ size }) => em(size as string)};
21 | }
22 | `;
23 |
24 | const Spinner = styled(SpinnerIcon)`
25 | position: absolute;
26 | top: 0;
27 | left: 0;
28 | animation: spinner 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
29 | will-change: transform;
30 |
31 | * {
32 | fill: currentColor;
33 | }
34 |
35 | @keyframes spinner {
36 | 0% {
37 | transform: rotate(0deg);
38 | }
39 | 100% {
40 | transform: rotate(360deg);
41 | }
42 | }
43 | `;
44 |
45 | /**
46 | * A general use loading spinner. Can be used in multiple component types.
47 | */
48 | const LoadingSpinner: React.FC = ({ size = '18', ...props }) => {
49 | return (
50 |
51 |
52 |
53 | );
54 | };
55 |
56 | export default LoadingSpinner;
57 |
--------------------------------------------------------------------------------
/src/components/LoadingIndicators/index.ts:
--------------------------------------------------------------------------------
1 | export { default as LoadingSpinner } from './LoadingSpinner';
2 | export { default as ContentLoading } from './ContentLoading';
3 |
--------------------------------------------------------------------------------
/src/components/SEO/SEO.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Helmet, { HelmetProps } from 'react-helmet';
3 | import { injectIntl, InjectedIntlProps } from 'gatsby-plugin-intl';
4 | import { Location } from '@reach/router';
5 |
6 | type Props = {
7 | /** Description text for the description meta tags */
8 | description?: string;
9 | } & HelmetProps &
10 | InjectedIntlProps;
11 |
12 | /**
13 | * An SEO component that handles all element in the head that can accept
14 | */
15 | const SEO: React.FC = ({ children, description = '', title, intl }) => {
16 | const metaDescription = description || 'Welcome to my website';
17 |
18 | return (
19 |
20 | {({ location }) => (
21 |
28 |
29 |
30 | {/* OG tags */}
31 |
35 |
36 |
37 |
38 |
39 |
40 | {children}
41 |
42 | )}
43 |
44 | );
45 | };
46 |
47 | export default injectIntl(SEO);
48 |
--------------------------------------------------------------------------------
/src/components/SEO/index.ts:
--------------------------------------------------------------------------------
1 | export { default as SEO } from './SEO';
2 |
--------------------------------------------------------------------------------
/src/icons/gatsby.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/icons/github.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/icons/loading-spinner.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/icons/storybook.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/icons/styled-components.svg:
--------------------------------------------------------------------------------
1 |
46 |
--------------------------------------------------------------------------------
/src/icons/tailwind.svg:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/src/icons/typescript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/locales/en-us.json:
--------------------------------------------------------------------------------
1 | {
2 | "homepage.title": "Gatsby + TypeScript + Emotion + Storybook Starter",
3 | "greeting": "Welcome to the Gatsby + TypeScript + Emotion + Storybook Starter.",
4 | "auth.login": "Login"
5 | }
6 |
--------------------------------------------------------------------------------
/src/locales/es-es.json:
--------------------------------------------------------------------------------
1 | {
2 | "homepage.title": "Gatsby + TypeScript + Emotion + Storybook Starter",
3 | "greeting": "Welcome to the Gatsby + TypeScript + Emotion + Storybook Starter.",
4 | "auth.login": "Login"
5 | }
6 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import { css } from '@emotion/core';
4 | import {
5 | injectIntl,
6 | FormattedMessage,
7 | InjectedIntlProps,
8 | } from 'gatsby-plugin-intl';
9 |
10 | import { Layout } from '~/components/Layout';
11 | import { SEO } from '~/components/SEO';
12 |
13 | import { ReactComponent as GatsbyIcon } from '~/icons/gatsby.svg';
14 | import { ReactComponent as TypeScriptIcon } from '~/icons/typescript.svg';
15 | import { ReactComponent as StorybookIcon } from '~/icons/storybook.svg';
16 | import { ReactComponent as GithubIcon } from '~/icons/github.svg';
17 |
18 | const Card = styled.div`
19 | min-width: 570px;
20 | padding: 1.5rem;
21 | text-align: center;
22 | background-color: #f7fafc;
23 | border-radius: 0.5rem;
24 | box-shadow: 2px 4px 12px 3px rgba(249,249,249,0.25);
25 | }`;
26 |
27 | const Container = styled.div`
28 | display: flex;
29 | align-items: center;
30 | justify-content: center;
31 | flex-direction: column;
32 | height: 100vh;
33 | background-color: #201b21;
34 | `;
35 |
36 | const iconStyles = css`
37 | width: 75px;
38 | `;
39 |
40 | const Logo = styled.img`
41 | max-width: 75px;
42 | margin: 0;
43 | `;
44 |
45 | const Index: React.FC = ({ intl }) => {
46 | return (
47 |
48 |
49 |
50 |
51 |
57 |
58 |
64 |
65 |
70 |
71 |
76 |
77 |
78 |
83 |
97 |
98 |
99 |
100 |
101 | );
102 | };
103 |
104 | export default injectIntl(Index);
105 |
--------------------------------------------------------------------------------
/src/styled/index.ts:
--------------------------------------------------------------------------------
1 | import styled, { CreateStyled } from '@emotion/styled';
2 |
3 | type Shades = { light: string; medium: string; dark: string };
4 |
5 | export type Theme = {
6 | color: {
7 | primary: string;
8 | secondary: string;
9 | tertiary: string;
10 | gray: {
11 | lightest: string;
12 | light: string;
13 | medium: string;
14 | dark: string;
15 | darkest: string;
16 | };
17 | status: {
18 | error: {
19 | light: string;
20 | medium: string;
21 | dark: string;
22 | };
23 | [index: string]: Shades;
24 | };
25 | text: {
26 | heading: string;
27 | body: string;
28 | };
29 | disabled: string;
30 | };
31 | spacing: {
32 | unit: number;
33 | };
34 | shape: {
35 | borderRadius: {
36 | small: number;
37 | medium: number;
38 | large: number;
39 | };
40 | };
41 | };
42 |
43 | export default styled as CreateStyled;
44 |
--------------------------------------------------------------------------------
/src/theme.ts:
--------------------------------------------------------------------------------
1 | import { Theme } from '@theme/styled';
2 |
3 | const theme: Theme = {
4 | color: {
5 | primary: '#ffcc00',
6 | secondary: '#008489',
7 | tertiary: '#f39b84',
8 | gray: {
9 | lightest: '#faf8f5',
10 | light: '#e2dedc',
11 | medium: '#aaa5a3',
12 | dark: '#75706b',
13 | darkest: '#463b36',
14 | },
15 | status: {
16 | error: {
17 | light: '#ffeeea',
18 | medium: '#ed6c4a',
19 | dark: '#ba3816',
20 | },
21 | },
22 | text: {
23 | heading: '#463b36',
24 | body: '#75706b',
25 | },
26 | disabled: '#aaa5a3',
27 | },
28 | spacing: {
29 | unit: 8,
30 | },
31 | shape: {
32 | borderRadius: {
33 | small: 4,
34 | medium: 6,
35 | large: 32,
36 | },
37 | },
38 | };
39 |
40 | export default theme;
41 |
--------------------------------------------------------------------------------
/src/utils/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import supportedLanguages from './supportedLanguages';
2 |
3 | export { supportedLanguages };
4 |
--------------------------------------------------------------------------------
/src/utils/i18n/supportedLanguages.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 |
3 | /* IMPORTANT, this file is consumed by node files as well as
4 | es6 modules, so it needs to stay in js common module format until further configuration
5 | and dependancies can be put in place to allow the node files to import Typescript modules */
6 |
7 | // NOTE, we don't translate the language labels, they should appear translated for their target audience.
8 |
9 | const supportedLanguages = [
10 | {
11 | label: 'English',
12 | languageTag: 'en-us',
13 | },
14 | {
15 | label: 'Español',
16 | languageTag: 'es-es',
17 | },
18 | ];
19 |
20 | module.exports = supportedLanguages;
21 |
--------------------------------------------------------------------------------
/src/utils/polyfills/toBlob.ts:
--------------------------------------------------------------------------------
1 | import { isBrowser } from '~/utils/system';
2 |
3 | // eslint-disable-next-line @typescript-eslint/unbound-method
4 | if (isBrowser() && !window.HTMLCanvasElement.prototype.toBlob) {
5 | Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
6 | value(callback: BlobCallback, type?: string | undefined, quality?: number) {
7 | setTimeout(() => {
8 | const binStr = atob(this.toDataURL(type, quality).split(',')[1]);
9 | const len = binStr.length;
10 | const arr = new Uint8Array(len);
11 |
12 | for (let i = 0; i < len; i += 1) {
13 | arr[i] = binStr.charCodeAt(i);
14 | }
15 |
16 | callback(new Blob([arr], { type: type || 'image/png' }));
17 | });
18 | },
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/src/utils/styles/breakpoints.ts:
--------------------------------------------------------------------------------
1 | interface BreakpointsShape {
2 | [sm: string]: number;
3 | md: number;
4 | lg: number;
5 | xl: number;
6 | }
7 |
8 | const breakpoints: BreakpointsShape = {
9 | sm: 576,
10 | md: 768,
11 | lg: 992,
12 | xl: 1200,
13 | };
14 |
15 | export default breakpoints;
16 |
--------------------------------------------------------------------------------
/src/utils/styles/index.ts:
--------------------------------------------------------------------------------
1 | export { default as spacer } from './spacer';
2 | export { above } from './mediaQueries';
3 |
--------------------------------------------------------------------------------
/src/utils/styles/mediaQueries.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 |
3 | import { css, SerializedStyles } from '@emotion/core';
4 | import breakpoints from './breakpoints';
5 |
6 | const getBreakpoint = (key: string) => breakpoints[key];
7 |
8 | /**
9 | * @function above
10 | * @param {string} breakpoint
11 | * @param {string} className - generated className from emotion. In practice this will be a template string containing the styles.
12 | *
13 | * @returns {string} mediaQueryStyles
14 | */
15 | export const above = (breakpoint: string, className: SerializedStyles) => css`
16 | @media (min-width: ${getBreakpoint(breakpoint)}px) {
17 | ${className};
18 | }
19 | `;
20 |
--------------------------------------------------------------------------------
/src/utils/styles/spacer.ts:
--------------------------------------------------------------------------------
1 | import { rem, em } from 'polished';
2 | import theme from '~/theme';
3 |
4 | type Unit = 'rem' | 'em' | 'px';
5 |
6 | export default function spacer(multiplier = 1, unit: Unit = 'rem') {
7 | const spacerValue = theme.spacing.unit * multiplier;
8 |
9 | if (unit === 'px') {
10 | return `${spacerValue}px`;
11 | }
12 | if (unit === 'em') {
13 | return em(`${spacerValue}px`);
14 | }
15 |
16 | return rem(`${spacerValue}px`);
17 | }
18 |
--------------------------------------------------------------------------------
/src/utils/system/index.ts:
--------------------------------------------------------------------------------
1 | export { default as isBrowser } from './isBrowser';
2 |
--------------------------------------------------------------------------------
/src/utils/system/isBrowser.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Checks if window is available. Helps determine wether the code being executed
3 | * is in a node context or a browser context.
4 | *
5 | * @export
6 | * @returns
7 | */
8 | export default function isBrowser() {
9 | return typeof window !== 'undefined';
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/types/index.tsx:
--------------------------------------------------------------------------------
1 | import { Link } from 'gatsby';
2 |
3 | export type GetRenderComponentProps = T extends
4 | | React.ComponentType
5 | | typeof Link
6 | ? React.ComponentProps
7 | : T extends 'a'
8 | ? React.HTMLProps
9 | : {};
10 |
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanleung/gatsby-typescript-emotion-storybook/fa8c78c319d8352791240db51a6c5765dda30786/static/favicon.ico
--------------------------------------------------------------------------------
/static/logos/emotion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duncanleung/gatsby-typescript-emotion-storybook/fa8c78c319d8352791240db51a6c5765dda30786/static/logos/emotion.png
--------------------------------------------------------------------------------
/test-utils/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement, ComponentProps, FunctionComponent } from 'react';
2 |
3 | import { ThemeProvider } from 'emotion-theming';
4 | import { render } from '@testing-library/react';
5 | import { IntlProvider } from 'react-intl';
6 |
7 | import * as messages from '~/locales/en-us.json';
8 | import theme from '../src/theme';
9 |
10 | const AllTheProviders: React.FC = ({ children }) => {
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | };
17 |
18 | const customRender = (
19 | ui: ReactElement>,
20 | options?: object
21 | ) => render(ui, { wrapper: AllTheProviders, ...options });
22 |
23 | // re-export everything
24 | export * from '@testing-library/react';
25 |
26 | // override render method
27 | export { customRender as render };
28 |
--------------------------------------------------------------------------------
/test-utils/jest-preprocess.js:
--------------------------------------------------------------------------------
1 | // Set up Babel config
2 | const babelOptions = {
3 | plugins: ['emotion'],
4 | presets: ['babel-preset-gatsby', '@babel/preset-typescript'],
5 | };
6 |
7 | module.exports = require('babel-jest').createTransformer(babelOptions);
8 |
--------------------------------------------------------------------------------
/test-utils/loadershim.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-underscore-dangle */
2 |
3 | // Related to jest.config.js globals
4 | // Load in this loadershim into `setupFiles` for all files that will be included before all tests are run
5 | global.___loader = {
6 | enqueue: jest.fn(),
7 | };
8 |
--------------------------------------------------------------------------------
/test-utils/setup-test-env.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom/extend-expect';
2 |
3 | import { matchers } from 'jest-emotion';
4 |
5 | expect.extend(matchers);
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "target": "es5",
5 | "module": "es6",
6 | "types": ["node"],
7 | "moduleResolution": "node",
8 | "esModuleInterop": true,
9 | "typeRoots": ["./src/@types", "./node_modules/@types"],
10 | "lib": ["dom", "es2015", "es2017"],
11 | "jsx": "react",
12 | "sourceMap": true,
13 | "strict": true,
14 | "resolveJsonModule": true,
15 | "noUnusedLocals": true,
16 | "noImplicitAny": true,
17 | "noUnusedParameters": true,
18 | "noFallthroughCasesInSwitch": true,
19 | "allowSyntheticDefaultImports": true,
20 | "downlevelIteration": true,
21 | "baseUrl": "./",
22 | "paths": {
23 | "~/*": ["src/*"],
24 | "@theme/styled": ["src/styled"]
25 | }
26 | },
27 | "include": ["./src/**/*", "./test-utils/**/*", "./__mocks__/**/*"],
28 | "exclude": ["node_modules", "plugins"]
29 | }
30 |
--------------------------------------------------------------------------------