npm package: @instawork/design-system
Description: The design system for Instawork's web apps
Author: Instawork
Latest version: 4.10.1

# Instawork Design System

The Instawork Design System includes a version of
[Bootstrap 4.3](https://getbootstrap.com/docs/4.3/getting-started/introduction) customized to match the Instawork UI
style guide.

It also includes several reusable UI components implemented as jQuery plugins.

This Design System can be viewed via [the demo site](http://design.instawork.com/) or via [the Storybook](https://instawork.github.io/design-system/).

```bash
yarn install @instawork/design-system 
```

## Prerequisites

### Package consumer
- jQuery ~3.3.1
- Luxon ^1.26.0 (time-related components only)

### Authoring / Development
- [nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
- [Yarn v1.x](https://classic.yarnpkg.com/en/docs/install#mac-stable)

## Development

To run the demo locally:
```
nvm install
yarn
yarn start
```

This will start an HTTP server on port 8086. Visit [http://localhost:8086/](http://localhost:8086/) in your browser to load the demo site.

To run the Storybook locally replace `yarn start` with `yarn storybook`.

## Make changes

To make changes to the design system, first start the dev server:

```
yarn start
```

### CSS / SCSS
For updating CSS styles, the main files to modify are in the [`scss`](./scss) directory:

- [`_custom-variables.scss`](./scss/_custom-variables.scss): Overrides for Bootstrap's variables. To see the full list of variables,
  see `node_modules/bootstrap/scss_variables.scss`. If you copy any of these to `_custom-variables.scss` and modify them,
  be sure to remove `!default` from your version.

- [`_custom-styles.scss`](./scss/_custom-styles.scss): Add SASS rules here to override Bootstrap's behaviors or add new behaviors.
- [`design-system.scss`](./scss/design-system.scss): Add or remove components to the final Bootstrap build here. Avoid
  creating any implementations directly in this file - it is only to be used for combining style implementations from other files.


### jQuery Component Plugins
Instawork's jQuery Component plugins can be found in [`./components`](./components).

#### Testing

`design-system` uses the following stack to provide testing functionality:
- [Karma](https://karma-runner.github.io/latest/index.html) - Test runner (loads the code, browsers, etc)
- [Mocha](https://mochajs.org/) - Test framework (define specs and tests with `describe`, `it`, etc)
- [Chai](https://www.chaijs.com/) - BDD assertion library (`expect(...)`)
- [Istanbul](https://istanbul.js.org/) - Test coverage reports

##### Authoring tests
- Create a `.spec.ts` file adjacent to the file you intend to test. For example, tests for code a file named
  `my-awesome.component.ts` would go in a file named `my-awesome.component.spec.ts`
- Use Mocha's `describe(...)` to group your tests by method / property name, or logical categorization
- When defining a test, the text passed to `it(...)` should form a descriptive sentence. Avoid using redundant
  words like "should" (e.g `it('returns a string')` instead of `it('should return a string')`)   
- Use Chai's `expect` assertion style. `expect` automatically included as a global variable for tests.
- See [@instawork/testing](./testing) for more information on DOM-based testing

##### Running tests
- From your terminal, run `yarn test:watch` - this will start the test server, and automatically run all tests.
  Tests will be re-run with every code change.
- To run the test suite once (without watching for changes), run `yarn test`
- To debug tests, open `http://localhost:9876/debug.html` and use the browser's Dev Tools
- Coverage reports are generated automatically and can be found at `.coverage/html/index.html`

_Note:_ By default, tests run in a "headless" instance of Chrome. To use other browsers, navigate to
 `http://localhost:9876`. To run other browsers automatically, or in "headless" mode, install the
  appropriate [Karma browser launcher plugin](https://www.npmjs.com/search?q=karma%20launcher).

###### Tests involving focus state
When testing code involving focus state, be aware that tests may behave differently when running in an
interactive browser compared to a headless browser, particularly if you have DevTools open.

This snippet can be used in a `before` block to ensure non-interactive tests correctly receive focus:
```javascript
$('<input>').appendTo('body').focus().remove();
```

For interactive test runs, be sure to give the body of the test document focus before refreshing / 
re-running your tests. Using the DevTools panel takes the focus away from the test document.

### Demo pages
When creating or making significant changes to components or style features, please update or add a section
to the corresponding demo page in [`./demo`](./demo). The demo pages are written in the [PugJS](https://pugjs.org)
template language.

Webpack's dev server will automatically refresh or update CSS in the demo pages as you make changes.

### Storybook
When creating or making significant changes to components or style features, you may also need to update the Storybook. This will require updating the `stories` directory and checking the results via `yarn storybook`.

### Before Submitting a PR
1. If updating JavaScript / jQuery-based components, ensure there are unit tests covering your changes
2. Ensure all changes are included in the demo artifact files by running `yarn build`
3. Bump `version` in [`./package.json`](./package.json) (see [Semantic Versioning](#semantic-versioning) below)
4. Update `CHANGELOG.md` with a summary of your changes

#### Semantic Versioning
Like most packages in the NodeJS / JavaScript ecosystem, this package uses
[Semantic Versioning](https://semver.org/) as its versioning scheme. A quick overview of what this means is:
- The version consists of three parts - major, minor, patch, separated by dots (e.g. `1.2.3`)
- The version component getting incremented is determined by the nature of public facing changes being published
- "Major" versions are incremented for incompatible or breaking changes
- "Minor" versions are incremented for new features or other changes that are backwards compatible with the currently
   published version
- "Patch" versions are incremented for backwards compatible bug fixes
- Pre-release labels can be suffixed to the version as an extension - this project uses the `-pre.#` label for
  pre-releases (e.g. 3.0.0-pre.0)

Use the `yarn next_semver` script to automatically determine the next version based on the desired increment:

```bash
# show the next major, minor, patch, or pre-release version
yarn next_semver major       # 3.0.0       -> 4.0.0
yarn next_semver minor       # 3.0.0       -> 3.1.0
yarn next_semver patch       # 3.0.0       -> 3.0.1
yarn next_semver prerelease  # 3.0.0-pre.0 -> 3.0.0-pre.1

# show the version for starting a prerelease
yarn next_semver major --prerelease  # 3.0.0 -> 4.0.0-pre.0

# add the --write flag to the above commands to also update package.json
yarn next_semver minor --write 
yarn next_semver minor --prerelease --write
yarn next_semver prerelease --write 
```

### Publishing

Publishing new versions of `@instawork/design-system` is currently done manually from a developer's machine.

#### Publishing prerequisites
In order to publish packages to the `@instawork` organization, you must be logged in using Instawork's shared
[npmjs.org](https://npmjs.org) account. See "Tools and Shared Accounts" in Confluence for authentication information.

1. Run `npm adduser --scope @instawork` (_note_: `npm`, not `yarn`)
2. Enter the username, password, and email from Confluence when prompted
3. Verify that you see the following message:
   > Logged in as _`(username)`_ to scope @instawork on <https://registry.npmjs.org/>.

If you get an error or see a different registry URL, check for stray configuration settings by running
`npm config list`.

#### Publish the package
Stable releases should only be published from `master` after a PR is approved and merged.
Pre-releases can be published at any time from any branch.

To publish the `@instawork/design-system` package.
1. Run `yarn build:design-system`.
2. At the command line, switch to the `dist/design-system` directory
3. Run `yarn publish --access public` from the `dist/design-system` directory
4. When prompted for a version, hit enter - the version should already have been incremented as part of
   your PR (see [Before Submitting a PR](#before-submitting-a-pr) above) or for a pre-release
5. The `postpublish` script should automatically give the release a
   [distribution tag](https://docs.npmjs.com/cli/v7/commands/npm-dist-tag), which ensures that anyone installing
     the package using `npm install` or `yarn install` without a specific version will only
     ever get a stable release, and not a pre-release
   - Pre-releases (from a branch other than master) use the `dev` dist-tag.
   - Stable releases (from master) use the `latest` dist-tag.
6. Verify the distribution tag in the console output of the publish command:
   Pre-releases:
   ```text
                [3/4] Publishing...
                $ ../../scripts/dist_tag
   this line -> +dev: @instawork/design-system@3.0.0-pre.3
                success Published.
   ```

   Stable releases:
   ```text
                [3/4] Publishing...
                $ ../../scripts/dist_tag
   this line -> +latest: @instawork/design-system@3.0.0
                success Published.
   ```

   If the `dist_tag` command fails for whatever reason, see
   [Manually adding distribution tags](#manually-adding-distribution-tags)

**IMPORTANT:** If you are publishing a pre-release, you the release MUST be tagging after publishing! Otherwise, the
registry may incorrectly / implicitly treat your pre-release as the "latest", which is used when resolving
versions for `yarn install` / `npm install` requests that do not include a version.

> _Note:_ The version _must_ be changed between releases, as it is not possible overwrite a previously published
> release. You will see the following error if this is attempted:
>
> ```
> Couldn't publish package: "https://registry.npmjs.org/@instawork%2fdesign-system: You cannot publish over the previously published versions: (version here)
> ```

> _Another note_: After the publish command succeeds, there may be a brief delay before you can install the newly
> released version. If you see a message like this:
> ```
> Couldn't find any versions for "@instawork/design-system" that matches "(your recently published version)"  
> ```
> ...then grab a drink of water, stand up and stretch for a moment, and then try again.

##### Manually adding distribution tags
- When publishing from `master`:
```bash
npm dist-tag add @instawork/design-system@VERSION latest  
```
Example:
```bash
npm dist-tag add @instawork/design-system@1.0.0 latest  
```
- When publishing a pre-release:
```bash
npm dist-tag add @instawork/design-system@VERSION dev  
```
Example:
```bash
npm dist-tag add @instawork/design-system@1.0.0-pre.1 dev  
```

> _Note_: Use `npm` here and not `yarn` - `yarn` [errors when trying to add tags](https://github.com/yarnpkg/yarn/issues/7823)

##### Removing previously published releases
NPM allows releases to be removed within 72 hours of publishing. This can be done using the `npm unpublish command`:

```bash
npm unpublish @instawork/design-system@3.0.0-pre.3
```

Be sure the correct version is specified, as the command will appear to succeed given a non-existent version.

#### Testing changes in another application
Before publishing a new release, it is recommended to check the changes in a client application.

##### Local testing
It is possible to test changes to the package locally without publishing a new version to yarn / npm  using
the [`yarn link`](https://classic.yarnpkg.com/en/docs/cli/link/) command:

1. Run `yarn build:design-system`
2. In the `dist/design-system` directory, run `yarn link`
3. In the project root of the test client application, run `yarn link @instawork/design-system`

This will create a symlink between the client application's `node_modules` directory for the
`@instawork/design-system` package, and the `dist/design-system` directory in the `design-system` project.

After making changes, you will need to run `yarn build:design-system` again to update the build artifacts, but you should not
need to repeat the `yarn link` commands.

Running `yarn` (`yarn install`) in the client project will restore the real package from the npm repository.
To test the local package again, re-run `yarn link @instawork/design-system`.

##### Pre-releases
A pre-release will allow you to test a release as it will be used in the real world by actually publishing
it to the npm registry, allowing it to be installed by anyone on any machine.

To create a pre-release:
- Run the appropriate `yarn next_semver` command with the `--write` flag (see
  [Semantic Versioning](#semantic-versioning) above for details)
- Follow the instructions in [Publish the package](#publish-the-package)

##### Build commands
The commands use a convention such that the shorter commands run all commands that use their name as a prefix. So
for example, `yarn build:design-system` will run all commands that start with `build:design-system:`.

- `yarn build`: Builds both the `@instawork/design-system` and `@instawork/testing` packages
- `yarn build:design-system`: Builds all bundles and files required to publish `@instawork/design-system`
- `yarn build:design-system:lib`: Builds the ES module files that go into the `/lib` subdirectory
- `yarn build:design-system:lib:ts`: Compiles TypeScript source to individual JS files in `/lib`, generates type definition files (`.d.ts`)
- `yarn build:design-system:lib:scss`: Generates empty stand-in JS files for each SCSS file (see [Package structure](#package-structure) below for details)
- `yarn build:design-system:lib:templates`: Compiles PUG templates to ES modules (see [Package structure](#package-structure) below for details)
- `yarn build:design-system:web`: Compiles SCSS to CSS, builds browser-ready JavaScript bundles
- `yarn build:design-system:cp`: Copies other required files like `package.json`, `README`, and `CHANGELOG`
- `yarn build:testing`: Compiles and collects all files required to publish the `@instawork/testing` package
- `yarn build:testing:ts`: Compiles TypeScript source to individual JS files, generates type definition files (`.d.ts`)
- `yarn build:testing:cp`: Copies other required files like `package.json`, `README`, and `CHANGELOG`
- `yarn build:storybook`: Builds the storybook.

##### Package structure
**@instawork/design-system**
- `./components.js` - browser-ready bundle for jQuery plugin components
- `./components.css` - Styles supporting jQuery plugin components
- `./design-system.css` - Themed Bootstrap styles
- `./marketing.css` - Specialized styles for the marketing website
- `./select2.css` - Styles supporting our customized `select2` implementation
- `./fonts` - font file assets
- `./lib` - Contains ES module and type declaration files for jQuery plugin components

###### ES Modules in ./lib
These files are provided to better support using the jQuery plugin components and their respective classes as
dependencies in a downstream application. They are intended to be referenced by a bundler like Webpack, or even
directly by the TypeScript compiler (as a dependency). They are published as ES modules to support
[Tree Shaking](https://webpack.js.org/guides/tree-shaking/).

Since several of the components use a PUG templates and/or define their own styles via SCSS, additional processing is
done to ensure that these aspects do not break downstream apps or force them to implement their own SCSS or PUG
template processing:

**PUG Templates**

PUG templates are compiled to ES modules that provide the compiled HTML content as their default export. Since they
are saved to the same path as the `.pug` source file, except with `.js` extension (e.g. `time-input.component.pug.js`),
they will automatically be picked up by the downstream application's bundler or compiler, regardless of it configuring
support for PUG. Since this is done without modifying either the source TypeScript file or its compiled ES module output,
it does not negatively the accuracy of the source maps.

**SCSS styles**

A similar approach is used for SCSS styles - though instead of compiling the SCSS, an empty JavaScript file is created,
effectively resulting in a noop when the downstream app loads the file. This requires that the downstream app uses a
separate method to load the bundled `components.css` file at runtime.