mui-bueno
v4.0.1-27
Published
Simple Material UI library to support Form creation in React
Downloads
31
Keywords
Readme
lib
Mui Bueno
Simple Material UI library to support Form creation in React
- Library
- Contains Mui Bueno library's exported components.
- Documentation
- Contains detailed documentation on each library component and types.
- Demo
- Contains implementation of demonstrative webpages showcasing each feature.
- Demo Website
Table of Contents
How I Built This
I created a blank repository and built up from there to more easily configure this library to our needs. The process is adapted from an existing tutorial. It provides great additional explanation for each step.
Create Project
# it is required to house both lib and demo in a common folder (i.e. mui-bueno/ as on gitlab)
mkdir mui-bueno
cd mui-bueno
# make repo directory
mkdir lib
cd lib
# initialize project (create package.json)
yarn init
# answer questions
question name (lib): [enter]
question version (1.0.0): 0.1.0
question description: Simple Material UI library to support Form creation in React
question entry point (index.js): index.ts
question repository url: [enter]
question author: [enter]
question license (MIT): [enter]
question private: true
Create remaining files and directories
This will be the basic file structure after the setup process is complete. I will go in depth on each file in the following steps.
lib
├── src/
│ ├── components/
│ │ ├── Component1/
│ │ ├── Component2/
│ │ ...
│ │
│ ├── styles/
│ ├── index.ts
│ ├── react-app-env.d.ts
│ └── setupTests.ts
│
├── .gitignore
├── package.json
├── README.md
├── rollup.config.js
└── tsconfig.json
Update package.json
and add standard app dependencies
# react
yarn add react react-dom @types/react react-scripts
# typescript
yarn add typescript
# material ui
yarn add @mui/material @emotion/react @emotion/styled @mui/lab @mui/icons-material
Once installed, need to add some of these dependencies as peer dependencies. This is to guarantee that applications using this library have compatible versions. Add the following section to package.json:
"peerDependencies": {
"@emotion/react": ">=11.10.4",
"@emotion/styled": ">=11.10.4",
"@mui/icons-material": ">=5.10.3",
"@mui/lab": ">=5.0.0-alpha.97",
"@mui/material": ">=5.10.3",
"react": ">=18.2.0",
"react-dom": ">=18.2.0"
}
Update typescript files
First, we need to update tsconfig.json
:
{
"compilerOptions": {
"declaration": true,
"declarationDir": ".",
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
],
"exclude": [
"node_modules",
"dist",
]
}
Next, we need to update react-app-env.d.ts
. This is needed for the library to understand types and imports.
/// <reference types="react-scripts" />
Set up eslint
yarn eslint --init
# answer style questions
Need to install the following packages:
@eslint/create-config
Ok to proceed? (y) y
✔ How would you like to use ESLint? · To check syntax, find problems, and enforce code style
✔ What type of modules does your project use? · JavaScript modules (import/export)
✔ Which framework does your project use? · React
✔ Does your project use TypeScript? · Yes
✔ Where does your code run? · Browser
✔ How would you like to define a style for your project? · Answer questions about your style
✔ What format do you want your config file to be in? · JSON
✔ What style of indentation do you use? · Spaces
✔ What quotes do you use for strings? · Single
✔ What line endings do you use? · Unix
? Do you require semicolons? › No
Local ESLint installation not found.
The config that you\'ve selected requires the following dependencies:
eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest eslint@latest
? Would you like to install them now with npm? › Yes
.eslintrc.json
will be generated in the root directory. Update with the following information:
...
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"warn",
"single"
],
"no-unused-vars": "off",
"react/prop-types": "off",
"react/display-name": "off",
"@typescript-eslint/no-explicit-any": "off"
},
"settings": {
"react": {
"version": "16.12.0"
}
}
...
Add lint script to package.json
"scripts": {
"lint": "eslint src/**/*.{ts,tsx} --fix"
}
Configure Rollup
Rollup is "a module bundler for JavaScript which compiles small pieces of code into something larger and more complex, such as a library or application". It will be used to bundle our library into a package that can be used by another application locally or published to npm as a public library.
Install plugins
yarn add -D rollup @rollup/plugin-typescript @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup-plugin-peer-deps-external rollup-plugin-terser rollup-plugin-delete rollup-plugin-copy
@rollup/plugin-json
Update rollup.config.js
import commonjs from '@rollup/plugin-commonjs'; // enables transpilation into CommonJS (CJS) format
import del from 'rollup-plugin-delete'; // delete files and folders using Rollup
import peerDepsExternal from 'rollup-plugin-peer-deps-external'; // prevents Rollup from bundling the peer dependencies
import resolve from '@rollup/plugin-node-resolve'; // bundles third party dependencies
import { terser } from 'rollup-plugin-terser'; // minify Rollup bundle
import typescript from '@rollup/plugin-typescript'; // transpiles TypeScript into JavaScript
import pkg from './package.json'
import copy from 'rollup-plugin-copy';
import json from '@rollup/plugin-json';
export default {
input: pkg.source,
output: [
{
file: pkg.main,
format: 'cjs',
sourcemap: true
},
{
file: pkg.module,
format: 'esm',
sourcemap: true
}
],
plugins: [
del({
targets: ['dist/*'] // clear dist/ folder contents on new bundle creation
}),
peerDepsExternal(),
resolve(),
commonjs(),
typescript({
tsconfig: './tsconfig.json', // use options specified in tsconfig
exclude: ['**/*.test.tsx'], // exclude test files from bundle
}),
terser(),
copy({
targets: [{ src: 'package.json', dest: 'dist' }]
}),
json({
compact: true,
})
]
};
Update package.json
In package.json
add the following:
"source": "src/index.ts",
"module": "dist/index.es.js",
"files": [
"dist"
],
...
"scripts": {
...
"build": "rollup -c"
}
Add custom scripts for quick actions as needed.
Set up testing
A note on Jest testing in the library that are different from in the demo app: Because there is no store, you do not need to wrap the component being testing with <Provider></Provider>
. So, as you port over existing test files, they will need to be updated and rerun to ensure they still pass.
Add required imports
yarn add @testing-library/jest-dom @testing-library/react @types/jest
Alternatively, you could choose to import this line at the top of all test files, but this is an import that will be run prior to tests.
Add test script(s)
In package.json
add the following script:
"scripts": {
...
"test": "react-scripts test",
"test:coverage": "react-scripts test --coverage --watchAll=false"
}
The first script will run the test environment. The second script will run tests and monitor code coverage.
Update .gitignore
Add the following to the file:
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/dist
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
Getting started
Now, you are able to build the library so it can be used in other applications.
Run the command yarn build
to create a Rollup bundle in dist/
. This bundle is what will be published when we choose to publish the library. For our current method of development, we will instead link this directory locally to import the library.
To add this library as a dependency to an application locally, add the following to the application's package.json
:
"dependencies": {
"mui-bueno": "file:<relative-path-to-mue-bueno-lib>/dist",
...
}
Component structure
Here, I will outline how we should structure and implement components for consistency.
Directory Contents
A sample component directory looks like this:
ComponentA
├── ComponentB/
│ ├── ChildComponentB.test.tsx
│ └── ChildComponentB.tsx
│
├── ComponentC/
│ ├── ChildComponentC.test.tsx
│ └── ChildComponentC.tsx
│
├── Utils/
│ ├── util.test.tsx
│ └── util.tsx
│
├── ComponentA.types.ts
├── ComponentA.styles.ts
├── ComponentA.test.tsx
└── ComponentA.tsx
Let's break this down:
- ComponentA is an example data visualization component, a parent to ComponentB and ComponentC.
- The root directory for ComponentA contains a types file (
ComponentA.types.ts
), styles file (ComponentA.styles.ts
), a test file (ComponentA.test.tsx
), and the component itself (ComponentA.tsx
).
Important Notes:
- Only create
ComponentA.types.ts
when there are common types and interfaces shared between the parent and child components, else types can be stored directly in a component file. IMPORTANT NOTE ABOUT TYPE FILES With the current configuration of the rollup bundler, if a file ends ind.ts
it will not be bundled properly and will cause errors. Name all types files with the.types.ts
suffix. - Only create
ComponentA.styles.ts
to help reduce the amount of code within the component itself or if these are common styles shared between parent and child components.
Sample Component File
Here is a sample of what ComponentA.tsx
may look like:
import * as React from 'react';
import ComponentB from './ComponentB/ComponentB';
import ComponentC from './ComponentC/ComponentC';
/** title type */
type ComponentATitle = string;
export interface ComponentAProps {
title: ComponentATitle;
}
export const ComponentA: React.FC<ComponentAProps> = props => {
const { title } = props;
return (
<div>
<h1>{title}</h1>
<ComponentB />
<ComponentC />
</div>
);
};
export default ComponentA;
Exporting
For components and types that you wish to export in the library package, add the export
keyword to the beginning of each declaration. Also add the export default to the bottom of the component. This way you are able to export both the component and its type together in index.ts
.
Else, to just export a component to be used by another component within the library, the standard format for exporting a component applies.
Documentation
Throughout the development and maintenance of this library, documentation will be upkept in 3 locations.
- API Documentation (Markdown Files in mui-bueno/docs Repository)
- JSDocs (Source Code Documentation)
- In-Line Documentation (Developer Notes on Logic etc.)
API Documentation
The full documentation of a component's API will be written into a markdown (.md) file inside of the documentation repository (mui-bueno/docs).
The template for the full API docs can be found at the root of the repository. Every exported library component should have a descriptive outline of it's design and usage.
JSDocs
JSDocs is an extremely useful tool when building a component library. It generates formatted documentation from source code to describe the purpose, parameters, return/default values, etc. of a piece of the library. While developing components, it is important to write organized and descriptive JSDocs so that users of the library can reference it for help (accessible on hover in most modern IDEs).
We write JSDocs for all exported components, subcomponents, interfaces, types, and functions.
Component Documentation
/**
* **Component**
*
* Description of component's purpose, usage,
* and other general information.
*
* **Required Props:**
* - prop name
* - description
* - prop name
* - description
*
* **Other Props:**
* - prop name
* - description
* - prop name
* - description
*
* **Usage:**
* ```tsx
* <Component />
* ```
*
* **Resources:**
* [API Documentation](http://link.com)
* [Demo](http://link.com)
*/
export const Component: React.FC<Props> = (props) => {...}
Other Documentation
export interface Props {
/**
* Description of prop.
*
* @required
*/
propName: Data;
/**
* Description of prop.
*
* @default false
*/
propName?: number;
}
/**
* Description of type.
*/
export type Data = (string | number)[] | null;
In-Line Documentation
In-line documentation refers to the freeform comments throughout developer source code. The intention of this is to increase readability and provide context to all source code. This is to decrease onboarding time of new library maintainer/developers and preserve the intent of code or logic choice.
//*********************************************************
// Heading (used as section titles for readability)
//*********************************************************
// One line comment to add context or provide background on logic
/**
* Generate additional hoverable JSDocs comments on code.
* Recommended for anything exported as well as
* functions, interfaces, types, etc.
*
* @param a description of parameter a
* @param b description of parameter b
* @returns description of what the function returns
*/
Styling
Because we want to have this library accept different color modes easily from the parent application, we needed to modify how we handled styling. Sass and Material UI handle styling based on mode individually, making for a complicated process handling style changes. Additionally, Sass styling within the component library had no way of knowing the parent app's mode simply. So, for this library we will be using a combination of Styled Components and in-line styles.
When to use styled components? When you need to access theme variables.
When to use in-line styles? For simple css styling, without accessing theme.
Another key practice to do when styling components is to rely on the provided Theme's styling. That means, using their established color palette, typography, etc when styling a library component. This will allow the user developer to more easily match the style to whichever application they are using this library.
Styled Components
A styled component creates a custom component of the type you specify with its own styling. Documentation from material ui can be found here. Let's run through some example uses:
import { Button, Paper, styled } from '@mui/material';
// html styled component
const StyledDiv = styled('div')(({ theme }) => ({
width: 400,
height: '100%,
border: `solid 1px ${theme.palette.mode=='light' ? 'red' : 'blue'}`
}));
// material ui styled component
const StyledPaper = styled(Paper)(({ theme }) => ({
borderRadius: 3,
padding: '10px 2px 5px',
margin: 10,
[theme.breakpoints.down('sm')]: {
width: 'calc(100vw - 150px)',
}
}));
// material ui styled component
const StyledButton = styled(Button)(({ theme }) => ({
'&:hover': {
cursor: 'pointer',
}
'&.MuiButton-outlined': {
borderColor: theme.palette.primary.main,
}
'.custom-button-class': {
color: 'red',
}
}));
In-line Styles
When you need to apply styles that don't rely on theme, it is simplest to just use in-line styles. For example:
<div styles={{ backgroundColor: 'green' }} />
// or
import { CSSProperties } from 'react';
const divStyles: CSSProperties = {
backgroundColor: 'green'
};
<div styles={divStyles} />