Back stitch is a popular stitch used for seams to give them strength & durability.
A simple wrapper pattern for react frameworks, including Material UI
It's nothing new to developers, but here's a project to quickly bootstrap your project with component wrappers (sometimes called the adapter pattern).
- Easily replace one library with another (eg replace
Material-ui
withBootstrap
), and keep the same Component API throughout your app - Quicker app setup inside whichever starter you want (CRA, Next, etc)
- Quickly generate new UI component wrappers with Hygen
- Quickly generate all of our UI components with a single shell command
- Consistently style throughout our app.
- DRYer code
Eg. Switch every Button
in our app from contained to outlined with a single line of code:
<Button
+ variant='outlined'
- variant='contained'
/>
- Backstitch is not a npm library - it's boilerplate code to clone the code into your project, and code from there
- ✔ Easily plug and play with Material UI (currently v4.10.1) ☐ (Bootstrap and more to come! Contributions welcome)
- ✔ StoryBook
- ✔ Built-in unit testing - WIP ☐ Cypress ☐ React Testing Library
- ✔ Styled components (which can be easily switched) ☐ Easily switched for css-modules ☐ Switched for emotion
- ✔ TypeScript
import React from 'react';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';
import React from 'react';
import { AppBar, Toolbar, Typography, Button, IconButton, MenuIcon } from 'common/ui';
Refactor reused components themselves using your own pattern and defaults
example from Material-ui buttons minus style
export default function IconLabelButtons() {
...
<Button
variant="contained"
disabled
color="secondary"
className={classes.button}
startIcon={<KeyboardVoiceIcon />}
>
Talk
</Button>
};
export default function IconLabelButtons() {
...
<Button
disabled
startIcon={<KeyboardVoiceIcon />}
>
Talk
</Button>
}
Here's how you can quickly boostrap with Create React App (CRA)
npx create-react-app MyApp --template typescript
cd MyApp
Clone backstitch
into the ./src/common
folder of your app:
git clone [email protected]:tgrrr/backstitch.git src/common/
Using Git Submodules (optional)
mkdir -p src/common/
cd src/common/
git submodule add https://github.com/tgrrr/backstitch
git submodule update --init --recursive
cd ../..
Important: Running git fetch
inside of the submodule folder (rather than your root) will over-ride your local changes inside the common/ui
folder.
If you want to disable this, and miss out on any updates, just run:
// warning: dangerous command ahead:
rm -rf /src/common/.gitignore
rm -rf
is a dangerous command itself, my uncle's friend's daughter lost their entire PHD using it, so be careful!
If you're wary, you might want to check out git subtree instead.
Add typescript to your project:
yarn add -D typescript @types/node @types/react @types/react-dom @types/jest
Add material-ui to your tsconfig.json
file
{
"compilerOptions": {
+ "types": ["material-ui"]
}
}
yarn add -D @storybook/react babel-loader
[Install StoryBook to your project
add the following scripts to your package.json
:
"storybook": "start-storybook",
"storybook:build": "build-storybook -c .storybook -o .out",
then:
yarn run storybook
View your stories at http://localhost:9009
import React from 'react';
import { Button } from 'common/ui';
const Foo = () => <Button>Foo who?</Button>;
export default const Foo;
OR Add your component to your App.js
file:
import React from 'react';
import { Button } from 'common/ui';
function App() {
return (
<div className="App">
<header className="App-header">
header
</header>
<Button>Foo who?</Button>
</div>
);
}
export default App;
Depending on how you've setup Webpack, we want to keep your bundle size small. There's quite a few components, and you wont need all of them. So delete the ones you don't need. You can always cherrypick them later.
To learn more, see: Minimising bundle size with tree shaking
At the moment, the components are just set up to let Material-ui deal with the props
Eg.
import * as React from 'react';
import InputStyled from './InputStyled';
import MaterialInput, {
InputProps as MaterialInputProps,
} from '@material-ui/core/Input';
interface Props
extends MaterialInputProps
// Uncomment these lines to disable the InputProps props:
// , Omit<
// MaterialInputProps,
// | 'propToDisable1'
// | 'propToDisable2'
// >
{
className?: string;
}
const Input: React.FC<Props> = ({ className, ...rest }) => (
<InputStyled>
<div className={['Input', className && className].join(' ')} data-testid='Input'>
<MaterialInput {...rest} />
</div>
</InputStyled>
);
export default Input;
This isn't my favourite pattern, it's better to be more explicit by declaring exactly which props we want to include (or exclude using Omit
).
As you customise each component, it's a good idea to remove any props being passed with {...rest}
, and limit it to the props that we know that we'll need.
We're using Hygen to template quickly
Replace NAME
with the component you want to create
Note: ensure you get your CamelCase
right!
From the home
directory (containing your package.json
)
$ yarn hygen:ui ui new --name NAME [--stateful] [--functional]
> yarn run v1.22.0
> $ HYGEN_TMPLS=src/common-ui/_templates hygen component new --name NAME > --functional
>
> Loaded templates: src/common-ui/_templates
> added: src/common/ui/NAME/NAME.story.js
> added: src/common/ui/NAME/NAME.test.js
> added: src/common/ui/NAME/index.js
> ✨ Done in 0.38s.
$ yarn hygen ui new --name NAME [--stateful] [--functional]
Note: there's an example in common/.config/.package.json
If you want a slightly different template than I've used, you're in luck.
-
Customise the
src/common/_templates/ui/new/component.js.t
file the way you want it. -
Generate a new button
$ yarn hygen ui new --name Button
> yarn run v1.22.4
> $ HYGEN_TMPLS=src/common/_templates hygen ui new --name Button
>
> Loaded templates: src/common/_templates
> added: src/common/ui/Button/Button.tsx
> added: src/common/ui/Button/index.ts
> inject: src/common/ui/index.ts
> inject: src/common/ui/index.ts
> added: src/common/ui/Button/ButtonStyled.tsx
> added: src/common/ui/Button/Button.stories.tsx
> added: src/common/ui/Button/Button.test.js
> new:
> - hygen ui new --name NAME
- That's it!
It's possible to batch generate all of the @material-ui/core
components using the ./hygen-bash.sh
script
- Customise the
src/common/_templates/ui/new/component.js.t
file the way you want it (the same as we do inGenerate UI components with Yarn)
Edit the ./hygen-bash.sh
script to only include the material-ui components you want to include
$ nano hygen-bash.sh
declare -a arr=(
"Button"
...
"Card"
)
Then from your home directory run the hygen-bash.sh
script:
$ ./src/common/hygen-bash.sh
$ HYGEN_TMPLS=src/common/_templates hygen ui new --name Button
Loaded templates: src/common/_templates
added: src/common/ui/Button/Button.tsx
added: src/common/ui/Button/index.ts
inject: src/common/ui/index.ts
inject: src/common/ui/index.ts
added: src/common/ui/Button/ButtonStyled.tsx
added: src/common/ui/Button/Button.stories.tsx
added: src/common/ui/Button/Button.test.js
new:
- hygen ui new --name NAME
✨ Done in 0.58s.
Card
yarn run v1.22.4
$ HYGEN_TMPLS=src/common/_templates hygen ui new --name Card
Loaded templates: src/common/_templates
added: src/common/ui/Card/Card.tsx
added: src/common/ui/Card/index.ts
inject: src/common/ui/index.ts
inject: src/common/ui/index.ts
added: src/common/ui/Card/CardStyled.tsx
added: src/common/ui/Card/Card.stories.tsx
added: src/common/ui/Card/Card.test.js
new:
- hygen ui new --name NAME
✨ Done in 1.38s.
Here's the current todo list
Eg.
chore
: add Oyster build scriptdocs
: explain hat wobblefeat
: add beta sequencefix
: remove broken confirmation messagerefactor
: share logic between 4d3d3d3 and flarhgunnstowstyle
: convert tabs to spacestest
: ensure Tayne retains clothing