Skip to content

Commit 2944bb1

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into refactor/redux-toolkit
# Conflicts: # client/modules/App/App.jsx # package-lock.json
2 parents 7e5c6fc + ca74574 commit 2944bb1

File tree

206 files changed

+36329
-53318
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

206 files changed

+36329
-53318
lines changed

.babelrc

+10-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,16 @@
5151
"development": {
5252
"plugins": [
5353
"babel-plugin-styled-components",
54-
"react-hot-loader/babel"
54+
"react-refresh/babel"
55+
],
56+
"presets": [
57+
[
58+
"@babel/preset-react",
59+
{
60+
"development": true,
61+
"runtime": "automatic"
62+
}
63+
]
5564
]
5665
}
5766
},

.eslintrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"extends": ["airbnb", "prettier"],
2+
"extends": ["airbnb", "prettier", "plugin:storybook/recommended"],
33
"parser": "@babel/eslint-parser",
44
"env": {
55
"browser": true,
@@ -20,6 +20,7 @@
2020
"no-console": 0,
2121
"no-alert": 0,
2222
"no-underscore-dangle": 0,
23+
"no-useless-catch": 2,
2324
"max-len": [1, 120, 2, {"ignoreComments": true, "ignoreTemplateLiterals": true}],
2425
"quote-props": [1, "as-needed"],
2526
"no-unused-vars": [1, {"vars": "local", "args": "none"}],

.github/workflows/deploy.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ jobs:
2323
with:
2424
ref: release
2525
- name: Set up Docker Buildx
26-
uses: docker/setup-buildx-action@v1
26+
uses: docker/setup-buildx-action@v2
27+
with:
28+
platforms: linux/amd64,linux/arm64
2729
- name: Login to Docker Hub
2830
uses: docker/login-action@v1
2931
with:

.storybook/main.js

+25-19
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
1-
const path = require('path');
2-
3-
module.exports = {
1+
/** @type { import('@storybook/react-webpack5').StorybookConfig } */
2+
const config = {
43
stories: ['../client/**/*.stories.(jsx|mdx)'],
54
addons: [
6-
'@storybook/addon-actions',
7-
'@storybook/addon-docs',
8-
'@storybook/addon-knobs',
95
'@storybook/addon-links',
10-
'storybook-addon-theme-playground/dist/register'
6+
'@storybook/addon-essentials',
7+
'@storybook/addon-interactions'
118
],
12-
webpackFinal: async config => {
13-
// do mutation to the config
14-
15-
const rules = config.module.rules;
16-
17-
// modify storybook's file-loader rule to avoid conflicts with svgr
18-
const fileLoaderRule = rules.find(rule => rule.test.test('.svg'));
19-
fileLoaderRule.exclude = path.resolve(__dirname, '../client');
9+
framework: {
10+
name: '@storybook/react-webpack5',
11+
options: {}
12+
},
13+
docs: {
14+
autodocs: 'tag'
15+
},
16+
async webpackFinal(config) {
17+
// https://storybook.js.org/docs/react/builders/webpack
18+
// this modifies the existing image rule to exclude .svg files
19+
// since we want to handle those files with @svgr/webpack
20+
const imageRule = config.module.rules.find(rule => rule.test.test('.svg'))
21+
imageRule.exclude = /\.svg$/
2022

21-
// use svgr for svg files
22-
rules.push({
23+
// configure .svg files to be loaded with @svgr/webpack
24+
config.module.rules.push({
2325
test: /\.svg$/,
24-
use: ["@svgr/webpack"],
26+
use: ['@svgr/webpack']
2527
})
2628

27-
return config;
29+
return config
2830
},
2931
};
32+
33+
export default config;
34+
35+

.storybook/preview-head.html

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
// https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/176#issuecomment-683150213
3+
window.$RefreshReg$ = () => {};
4+
window.$RefreshSig$ = () => () => {};
5+
</script>

.storybook/preview.js

+19-25
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,25 @@
11
import React from 'react';
2-
import { addDecorator, addParameters } from '@storybook/react';
3-
import { withKnobs } from "@storybook/addon-knobs";
4-
import { withThemePlayground } from 'storybook-addon-theme-playground';
5-
import { ThemeProvider } from "styled-components";
2+
import { Provider } from 'react-redux';
3+
import { MemoryRouter } from 'react-router';
64

7-
import theme, { Theme } from '../client/theme';
5+
import ThemeProvider from '../client/modules/App/components/ThemeProvider';
6+
import configureStore from '../client/store';
7+
import '../client/i18n-test';
8+
import '../client/styles/storybook.css'
89

9-
addDecorator(withKnobs);
10+
const initialState = window.__INITIAL_STATE__;
1011

11-
const themeConfigs = Object.values(Theme).map(
12-
name => {
13-
return { name, theme: theme[name] };
14-
}
15-
);
12+
const store = configureStore(initialState);
1613

17-
addDecorator(withThemePlayground({
18-
theme: themeConfigs,
19-
provider: ThemeProvider
20-
}));
14+
export const decorators = [
15+
(Story) => (
16+
<Provider store={store}>
17+
<MemoryRouter>
18+
<ThemeProvider>
19+
<Story />
20+
</ThemeProvider>
21+
</MemoryRouter>
22+
</Provider>
23+
),
24+
]
2125

22-
addParameters({
23-
options: {
24-
/**
25-
* display the top-level grouping as a "root" in the sidebar
26-
*/
27-
showRoots: true,
28-
},
29-
})
30-
31-
// addDecorator(storyFn => <ThemeProvider theme={theme}>{storyFn()}</ThemeProvider>);

client/browserHistory.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { createBrowserHistory } from 'history';
2+
3+
const browserHistory = createBrowserHistory();
4+
5+
export default browserHistory;

client/common/Button.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33
import styled from 'styled-components';
4-
import { Link } from 'react-router';
4+
import { Link } from 'react-router-dom';
55

66
import { remSize, prop } from '../theme';
77

client/common/Button.stories.jsx

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import React from 'react';
22
import { action } from '@storybook/addon-actions';
3-
import { boolean, text } from '@storybook/addon-knobs';
43

54
import Button from './Button';
65
import { GithubIcon, DropdownArrowIcon, PlusIcon } from './icons';
76

87
export default {
98
title: 'Common/Button',
10-
component: Button
9+
component: Button,
10+
args: {
11+
children: 'this is the button',
12+
label: 'submit',
13+
disabled: false
14+
}
1115
};
1216

13-
export const AllFeatures = () => (
14-
<Button
15-
disabled={boolean('disabled', false)}
16-
type="submit"
17-
label={text('label', 'submit')}
18-
>
19-
{text('children', 'this is the button')}
17+
export const AllFeatures = (args) => (
18+
<Button disabled={args.disabled} type="submit" label={args.label}>
19+
{args.children}
2020
</Button>
2121
);
2222

client/common/ButtonOrLink.jsx

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import { Link } from 'react-router-dom';
3+
import PropTypes from 'prop-types';
4+
5+
/**
6+
* Helper for switching between <button>, <a>, and <Link>
7+
*/
8+
const ButtonOrLink = ({ href, children, ...props }) => {
9+
if (href) {
10+
if (href.startsWith('http')) {
11+
return (
12+
<a href={href} target="_blank" rel="noopener noreferrer" {...props}>
13+
{children}
14+
</a>
15+
);
16+
}
17+
return (
18+
<Link to={href} {...props}>
19+
{children}
20+
</Link>
21+
);
22+
}
23+
return <button {...props}>{children}</button>;
24+
};
25+
26+
/**
27+
* Accepts all the props of an HTML <a> or <button> tag.
28+
*/
29+
ButtonOrLink.propTypes = {
30+
/**
31+
* If providing an href, will render as a link instead of a button.
32+
* Can be internal or external.
33+
* Internal links will use react-router.
34+
* External links should start with 'http' or 'https' and will open in a new window.
35+
*/
36+
href: PropTypes.string,
37+
/**
38+
* Content of the button/link.
39+
* Can be either a string or a complex element.
40+
*/
41+
children: PropTypes.node.isRequired
42+
};
43+
44+
ButtonOrLink.defaultProps = {
45+
href: null
46+
};
47+
48+
export default ButtonOrLink;

client/common/ButtonOrLink.test.jsx

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
import { render, screen, fireEvent, waitFor, history } from '../test-utils';
3+
import ButtonOrLink from './ButtonOrLink';
4+
5+
describe('ButtonOrLink', () => {
6+
const clickHandler = jest.fn();
7+
8+
afterEach(() => {
9+
clickHandler.mockClear();
10+
});
11+
12+
it('can render a clickable button', () => {
13+
render(<ButtonOrLink onClick={clickHandler}>Text</ButtonOrLink>);
14+
const button = screen.getByRole('button');
15+
expect(button).toBeInstanceOf(HTMLButtonElement);
16+
expect(button).toContainHTML('<button>Text</button>');
17+
fireEvent.click(button);
18+
expect(clickHandler).toHaveBeenCalled();
19+
});
20+
21+
it('can render an external link', () => {
22+
render(<ButtonOrLink href="https://p5js.org">p5</ButtonOrLink>);
23+
const link = screen.getByRole('link');
24+
expect(link).toBeInstanceOf(HTMLAnchorElement);
25+
expect(link).toHaveAttribute('href', 'https://p5js.org');
26+
});
27+
28+
it('can render an internal link with react-router', async () => {
29+
render(<ButtonOrLink href="/about">About</ButtonOrLink>);
30+
31+
const link = screen.getByText('About');
32+
fireEvent.click(link);
33+
34+
await waitFor(() => expect(history.location.pathname).toEqual('/about'));
35+
});
36+
});

client/common/icons.jsx

+10-4
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@ import DropdownArrow from '../images/down-filled-triangle.svg';
1313
import Preferences from '../images/preferences.svg';
1414
import Play from '../images/triangle-arrow-right.svg';
1515
import More from '../images/more.svg';
16+
import Editor from '../images/editor.svg';
17+
import Account from '../images/account.svg';
1618
import Code from '../images/code.svg';
1719
import Save from '../images/save.svg';
1820
import Terminal from '../images/terminal.svg';
19-
2021
import Folder from '../images/folder-padded.svg';
21-
2222
import CircleTerminal from '../images/circle-terminal.svg';
2323
import CircleFolder from '../images/circle-folder.svg';
2424
import CircleInfo from '../images/circle-info.svg';
25+
import Add from '../images/add.svg';
26+
import Filter from '../images/filter.svg';
27+
import Cross from '../images/cross.svg';
2528

2629
// HOC that adds the right web accessibility props
2730
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
@@ -83,16 +86,19 @@ export const GoogleIcon = withLabel(Google);
8386
export const PlusIcon = withLabel(Plus);
8487
export const CloseIcon = withLabel(Close);
8588
export const ExitIcon = withLabel(Exit);
89+
export const EditorIcon = withLabel(Editor);
90+
export const AccountIcon = withLabel(Account);
8691
export const DropdownArrowIcon = withLabel(DropdownArrow);
8792
export const PreferencesIcon = withLabel(Preferences);
8893
export const PlayIcon = withLabel(Play);
8994
export const MoreIcon = withLabel(More);
9095
export const TerminalIcon = withLabel(Terminal);
9196
export const CodeIcon = withLabel(Code);
9297
export const SaveIcon = withLabel(Save);
93-
9498
export const FolderIcon = withLabel(Folder);
95-
99+
export const CrossIcon = withLabel(Cross);
96100
export const CircleTerminalIcon = withLabel(CircleTerminal);
97101
export const CircleFolderIcon = withLabel(CircleFolder);
98102
export const CircleInfoIcon = withLabel(CircleInfo);
103+
export const AddIcon = withLabel(Add);
104+
export const FilterIcon = withLabel(Filter);

client/common/icons.stories.jsx

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import React from 'react';
2-
import { select } from '@storybook/addon-knobs';
32

43
import * as icons from './icons';
54

65
export default {
76
title: 'Common/Icons',
8-
component: icons
7+
component: icons,
8+
argTypes: {
9+
variant: {
10+
options: Object.keys(icons),
11+
control: { type: 'select' },
12+
default: icons.CircleFolderIcon
13+
}
14+
}
915
};
1016

11-
export const AllIcons = () => {
12-
const names = Object.keys(icons);
13-
14-
const SelectedIcon = icons[select('name', names, names[0])];
17+
export const Icons = (args) => {
18+
const SelectedIcon = icons[args.variant || 'CircleInfoIcon'];
1519
return <SelectedIcon />;
1620
};

0 commit comments

Comments
 (0)