@actinc/dls
Version:
Design Language System (DLS) for ACT & Encoura front-end projects.
417 lines (310 loc) • 13.5 kB
Markdown
# Design Language System (DLS)
<!-- markdownlint-disable no-inline-html -->
<a href="https://www.npmjs.com/package/@actinc/dls">
<img alt="Version" src="https://img.shields.io/npm/v/@actinc/dls"/>
</a> <a href="https://github.com/act-org/dls/actions/workflows/ci.yml">
<img alt="Build" src="https://github.com/act-org/dls/actions/workflows/ci.yml/badge.svg"/>
</a> <a href="https://github.com/act-org/dls/blob/master/LICENSE">
<img alt="License" src="https://img.shields.io/badge/license-MIT-green"/>
</a> <a href="https://www.npmjs.com/package/@actinc/dls">
<img alt="Downloads" src="https://img.shields.io/npm/dw/@actinc/dls?color=orange"/>
</a>
<!-- markdownlint-enable no-inline-html -->
The Design Language System for ACT & Encoura front-end projects. View the UI components
[here](https://act-org.github.io/dls).
## Project Setup
### Installation
In order to use the DLS, you must install it along with
[Material UI](https://mui.com/) version `5.x` and
[React](https://reactjs.org/) version `17.x` or `18.x`.
```sh
npm install --save /dls@latest @mui/material /lab @mui/x-data-grid /styled @emotion/react react react-dom
```
### Choosing a Theme
This DLS is built on top of the
[theme engine](https://mui.com/material-ui/customization/theming/) from
Material UI, and ships with several themes out of the box:
1. `"ACT"`: for ACT's "traditional" look and feel
2. `"ACT_ET"`: for ACT's "Emerging Technology" look and feel
3. `"ENCOURA_CLASSIC"`: for Encoura's "Classic" look and feel
4. `"ENCOURAGE"`: for the Encoura's "Encourage" look and feel
To apply one of these themes to your components, simply wrap your application
in the `ThemeProvider` component and specify a theme!
```jsx
import { ThemeProvider } from '/dls/components';
...
const MyApp = () => (
// specify a theme here!
<ThemeProvider theme="ACT_ET">
<App />
</ThemeProvider>
);
```
#### Extending Themes
You can exend the core DLS themes using using our variation on the
[`createTheme`](https://mui.com/material-ui/customization/theming/#createtheme-options-args-theme)
generator from Material UI:
```jsx
import deepMerge from 'deepmerge';
import { createTheme } from '/dls/styles/createTheme';
import { THEME_ACT } from '/dls/styles/themeAct';
import { ThemeProvider } from '/dls/components';
const myExtendedTheme = createTheme(deepMerge(THEME_ACT, {
// theme customizations go here!
}));
const MyApp = () => (
<ThemeProvider theme={myExtendedTheme}>
<App />
</ThemeProvider>
);
```
#### Custom Themes
Alternatively, you can build your own theme from scratch using our variation on the
[`createTheme`](https://mui.com/material-ui/customization/theming/#createtheme-options-args-theme)
generator from Material UI. Our version takes the same parameters, but will
return a strongly typed version of the theme with any customizations you may
have added.
```jsx
import { ThemeProvider } from '/dls/components';
import { createTheme } from '/dls/styles/createTheme';
const myCustomTheme = createTheme({
// build your theme here!
});
const MyApp = () => (
<ThemeProvider theme={myCustomTheme}>
<App />
</ThemeProvider>
);
```
#### Custom Themes And Styled Components
Within your styled components, if you need to access custom a theme variable
that is not present in the default MUI `Theme` type, we provide a helper
function to generate a `styled` function that is strongly typed to your theme:
```jsx
import { createThemeStyled } from '/dls/helpers/styled';
import { THEME_ACT } from '/dls/styles/themeAct';
import TableCell from '/material/TableCell';
const styled = createThemeStyled(THEME_ACT);
const StyledTypography = styled(TableCell)(({ theme }) => ({
// `customDims` is not available on the default theme
height: theme.customDims.heights.tableHeader,
}))
```
### Load Fonts
#### Montserrat
The `ACT` and `ACT_ET` themes assume that the
[Montserrat](https://fonts.google.com/specimen/Montserrat) font is available in
the browser. Therefore, it is recommended that you include the following font
reference in the `head` of your React app:
```html
<link
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,500,600,700&display=swap"
rel="stylesheet"
/>
```
#### Museo Sans
The `ENCOURA_CLASSIC` theme assumes that the
[Museo Sans](https://fonts.adobe.com/fonts/museo-sans) font
is available in the browser. Include this embed code in the `head`
of your React app after obtaining the licensed font URL from Marketing:
```html
<style>
url("licensed-font-url");
</style>
```
#### Work Sans, Roboto, Roboto Mono
The `ENCOURAGE` theme assumes that the
[Work Sans](https://fonts.google.com/specimen/Work+Sans),
[Roboto](https://fonts.google.com/specimen/Roboto), and the
[Roboto Mono](https://fonts.google.com/specimen/Roboto+Mono) fonts are available
in the browser. Therefore, it is recommended that you include the following font
reference in the `head` of your React app:
```html
<link
href="https://fonts.googleapis.com/css2?display=swap&family=Work+Sans:wght@200..800"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?display=swap&family=Roboto:wght@300;400;500;700"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?display=swap&family=Roboto+Mono:wght@200..700"
rel="stylesheet"
/>
```
### CSS Baseline
It is recommended to inject the `CssBaseline` component from Material UI near
the root of your component tree in order to reset and normalize browser styles
for your project:
```jsx
import { CssBaseline } from '/material';
...
const MyApp = () => (
...
<CssBaseline />
...
);
```
### Server-Side Rendering
If your project's React framework supports SSR, you can configure the DLS
components for server-side rendering. See the official Next.js example
[here](https://github.com/mui/material-ui/tree/master/examples/nextjs-with-typescript).
### Icons
The DLS re-exports all icons that are provided by the
[`mdi-material-ui`](https://www.npmjs.com/package/mdi-material-ui) package.
You can search for a specific icon to use on
[materialdesignicons.com](https://materialdesignicons.com). Once you've found
the perfect icon, you can use it in your project like so:
```jsx
import { PollBox } from '/dls/icons';
...
const MyComponent = () => (
...
<PollBox />
...
);
```
### Import Stuff
That's it! You're ready to use the DLS. Simply import the components,
constants, context, helpers, hooks, icons, styles, and types that you need:
```jsx
// components
import { Alert } from '/dls/components';
// constants
import { SORT_DIRECTION_TYPES } from '/dls/constants';
// context
import { AlertContext } from '/dls/context';
// helpers
import { search } from '/dls/helpers';
// hooks
import { useLocalStorage } from '/dls/hooks';
// icons
import { ChevronDown } from '/dls/icons';
// styles & themes
import { THEME_ACT } from '/dls/styles/themeAct';
// types
import { SortObject } from '/dls/types';
```
### Transient Props and Styled Components
The DLS provides a customized styled helper which omits transient props
(those starting with $) from the rendered HTML, while still being able to use
those parameters in styled components. Use as a drop in replacement of the
`styled` function that exists in `/material/styles`:
```jsx
import { styled } from '/dls/helpers/styled';
import Button, { ButtonProps } from '/material/Button';
import * as React from 'react';
const StyledButton = styled(Button)<ButtonProps & { $ultraWide: boolean }>(
({ $ultraWide, theme }) => ({
paddingLeft: $ultraWide ? theme.spacing(8) : theme.spacing(4),
paddingRight: $ultraWide ? theme.spacing(8) : theme.spacing(4),
}),
);
const MyComponent: React.FC = () => {
return <StyledButton $ultraWide />;
};
```
* This implementation is how Styled Components does it: <https://styled-components.com/docs/api#transient-props>
* Unfortunately emotion (the default styling engine in React) doesn't seem to
care: <https://github.com/emotion-js/emotion/issues/2193#issuecomment-1178372803>
## ES Modules & Tree Shaking
Version <= 6 of the DLS were built and exported as
[CommonJS modules](https://nodejs.org/api/modules.html#modules-commonjs-modules).
While this allowed the simplest integration of the DLS into any
project, it also resulted in project bundles being
[larger than desired](https://web.dev/commonjs-larger-bundles/) due to
the inability of bundlers to
[tree-shake](https://www.smashingmagazine.com/2021/05/tree-shaking-reference-guide/)
the DLS.
In version >= 7 of the DLS, we are now building and exporting the library as
[ECMAScript modules](https://nodejs.org/api/esm.html#modules-ecmascript-modules).
This allows your project's bundler to much more easily read and
[tree-shake](https://www.smashingmagazine.com/2021/05/tree-shaking-reference-guide/)
the DLS right out of the box. (No more need for
[`babel-plugin-transform-imports`](https://www.npmjs.com/package/babel-plugin-transform-imports)!)
Furthermore, the DLS's `package.json` is also setting:
```json
"sideEffects": false,
```
to instruct builders to enable even deeper tree-shaking. This should make
bundle sizes significantly smaller with less effort. However, the tradeoff
is that in certain scenarios, like
[Lazy Loading](https://nextjs.org/docs/advanced-features/dynamic-import),
if you are expecting a dependency to be there that is now removed from
tree-shaking, things will break and you may need to import that dependency
directly in a parent bundle.
### SyntaxError: Unexpected token 'export'
One downside of exporting the DLS as ECMAScript modules is that the `import` and
`export` keywords are preserved, which may cause your packager/runner to throw:
```sh
SyntaxError: Unexpected token 'export'
```
If you see this error, you'll need to instruct your packager/runner to transpile
the DLS on-the-fly.
#### Next.js
You can do this in a [Next.js](https://nextjs.org/) app by adding the DLS to the
[`transpilePackages`](https://beta.nextjs.org/docs/api-reference/next.config.js#transpilepackages)
option in your `next.config.js` file.
```js
transpilePackages: ['/dls'],
```
#### Jest
You can do this in the [Jest](https://jestjs.io/) test runner by omitting the
DLS from the
[`transformIgnorePatterns`](https://jestjs.io/docs/tutorial-react-native#transformignorepatterns-customization)
option in your `jest.config.js` file.
```js
transformIgnorePatterns: ['/node_modules/(?!(/dls)/)'],
```
## Local Development
Check out the [developer guide](./docs/GUIDE.md) to learn how to build
effectively for the DLS.
To run the DLS locally:
1. Install node modules: `npm install`
2. Start the [Storybook](https://storybook.js.org) component visualizer:
`npm start`
### How to Iterate Locally
For rapid iteration, you can run a local build of this library in your
downstream project:
1. In this library, run the watch script: `npm run watch`
2. In your project, install a local build of this library: `npm install <path-to-this-repo>/dist`
Under the hood, this creates a symlink between the local build and your project.
When changes are detected under the `src` folder, a new build will be output
into the `dist` folder and picked up by your project.
Once you are happy with the changes, you'll want to destroy this symlink
and replace the local build with a formal/hosted version of this library.
You can do that by running the following commands in your project:
1. `npm uninstall /dls`
2. `npm install /dls@<pick-your-version> --save --save-exact`
### npm Scripts
There are lots of npm scripts at your disposal during local development.
Here are some of the more important ones:
| Script | Description |
|:------------------------- |:----------------------------------------------------------------------- |
| npm run build | Transpiles DLS from TypeScript (`./src`) into ES5 (`./dist`). |
| npm run build-storybook | Creates a static website for deployment (`./storybook-static`). |
| npm start | Starts the [Storybook](https://storybook.js.org) component visualizer. |
| npm test | Runs all tests. |
| npm run release | Publishes a new release of the DLS. |
| npm run release:storybook | Publishes a new release of Storybook (make sure to pull latest `main`). |
| npm run watch | Watch the `src` folder for changes and transpile on-the-fly to `dist`. |
### Committing Code
semantic-release scans commits to manage package.json versions and `CHANGELOG.MD`
It is important that we accurately capture what type of development we are doing.
* For changes to storybook (i.e. no change to components), use the `docs` tag:
```bash
git commit -m "docs: Added stories for Alert"
```
* For patches to existing components, use `fix`:
```bash
git commit -m "fix: Fixed Snackbar not appear in center of screen"
```
* For new functionality, use `feat`:
```bash
git commit -m "feat: Added Carousel component"
```
## Pinned Packages
Some npm packages are pinned to non-current versions for a specific reason:
| Package | Version | Reason |
|:------------ |:------- |:-------|