mui-bueno
TypeScript icon, indicating that this package has built-in type declarations

4.0.1-27 • Public • Published

lib

Mui Bueno

Simple Material UI library to support Form creation in React

Mui Bueno Repository

  • 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 in d.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} />

Readme

Keywords

none

Package Sidebar

Install

npm i mui-bueno

Weekly Downloads

2

Version

4.0.1-27

License

MIT

Unpacked Size

5.53 MB

Total Files

113

Last publish

Collaborators

  • lat.tran
  • simon.woo
  • jpcomputing
  • jw23
  • jeanie.lam