# API & Pattern Guidelines

Note: This repo hasn't seen a full audit, so you may find examples that contradict these guidelines.
Some of the below rules are inspired by painpoints we've encountered in this project.

- [Canvas](#canvas)
- [Naming](#naming)
  - [Props](#props)
  - [T-shirt Sizes](#t-shirt-sizes)
  - [Theme Types](#theme-types)
  - [Event Handlers](#event-handlers)
  - [Enums](#enums)
- [Patterns](#patterns)
  - [Event Handlers](#event-handlers-1)
  - [Grow Interface](#grow-interface)
  - [Input Provider](#input-provider)
  - [Prop Spread Behavior](#prop-spread-behavior)
  - [Controlled Components](#controlled-components)
  - [Accessibility](#accessibility)
  - [Child Mapping](#child-mapping)
  - [Logic Flow](#logic-flow)
  - [Server Side Rendering](#server-side-rendering)
- [Code Style](#code-style)
  - [Default Props](#default-props)
  - [Element Choice](#element-choice)
  - [Styled Components](#styled-components)
  - [Exports](#exports)
- [Documentation](#documentation)
  - [Readmes](#readmes)
  - [Storybook Structure](#storybook-structure)
  - [Prop Descriptions](#prop-descriptions)

## Canvas

- Ensure you're always using the Canvas primitives and enums wherever possible for things like:
  - Space (e.g. `canvas.space.s`)
  - Depth (e.g. `canvas.depth[2]`)
  - Type (e.g. `...canvas.type.h1`). Always start from a type hierarchy level and override if
    needed.
- Use the provided types (e.g. `CanvasSpaceValues`, `CanvasSpaceNumbers`, etc.) to restrict prop
  values
- Check out the
  [`@workday/canvas-kit-react/tokens` README](https://github.com/Workday/canvas-kit/blob/master/modules/react/tokens/README.md)
  for the latest and greatest Canvas helpers.

## Naming

#### Props

- Prop names should never include the component name (e.g. `type`, not `buttonType`)
- Use the same props for the same concepts across components
- Avoid names that reference color, position, and size. For example:
  - `blueIcon` can be bad because it may not be blue to everyone and changing colors or making
    colors variable is a breaking change.
  - `leftIcon` can be bad because we can change the position with RTL or add something to the left
    of that, then it wouldn't make sense anymore.
  - `mediumIcon` can be bad if we add another size in between... then which one is medium? Is it
    mediumLarge now?

#### T-shirt Sizes

- Always use the shortest enumeration (`xs, s, m, l, xl`, etc.)
- **Do not** use longer versions (e.g. `sm`)

#### Theme Types

- Default - normal state/color for use on light background
- Inverse - inverted colors for use on a dark background
- _Note:_ If you encounter somewhere you need another theme type, please let us know so we can
  document it

#### Event Handlers

- Always use standard `on{Descriptor}{Event}` naming (`onClick`, `onChange`, `onBreakpointChange`,
  etc.)
- Only use a descriptor if:
  - You need more context
  - There is already a handler for that type of event (e.g. `onChange`, `onValidColorChange`)

#### Enums

Use disjoint string unions instead of enums. Enums have issues with overloading or extending. They
are also more difficult to use and create a different experience from JavaScript. These union types
also have a better autocomplete experience.

```tsx
interface ButtonProps {
  size: 'small' | 'medium' | 'large';
}

// use
<PrimaryButton size="medium" />;
```

JavaScript objects can be used as enums without the downsides:

```tsx
const ButtonType = {
  Primary: 'primary',
  Secondary: 'secondary'
} as const // as const locks object values as literal

interface ButtonProps {
  type: typeof ButtonType[keyof typeof ButtonType] // returns 'primary' | 'secondary'
}

Button.Type = ButtonType

// use
<Button type={Button.Type.Primary} />
<Button type="primary" />
```

If objects are desired, the keys follow these rules:

- Singular
- PascalCase
- Include component name unless it's a generic enum shared across components. Since we export our
  enums, this prevents naming clashes
- Exclude component name in static variables (`Button.Type` vs. `Button.ButtonType`):

## Patterns

#### Event Handlers

- Use standard browser events wherever possible
- All event handlers should receive an event unless there's a good reason otherwise. This is for
  consumer predictability. In other words, always opt for `onChange: e => void` over
  `onChange: () => void` or `onChange: value => void`, etc.

#### Grow Interface

- If your component needs to grow to fill to it's container, extend `GrowthInterface` (e.g.
  `export interface MyComponentProps extends GrowthBehavior`)
- Then use the `grow` boolean prop in your styles to achieve the desired effect (e.g.
  `width: grow ? '100%' : undefined`)

#### Input Provider

- All Canvas Kit components should support an `InputProvider` component to provide the cleanest
  experience for mouse users. Read the docs
  [here](https://github.com/Workday/canvas-kit/tree/master/modules/core/react#input-provider).
- Do not use `InputProvider` within your components. It is meant to be used only once in your
  application. It does not require wrapping any children
- Make sure you provide fully accessible styling by default, and only override for mouse usage.
-

```tsx
[`[data-whatinput='mouse'] &:focus,
  [data-whatinput='touch'] &:focus,
  [data-whatinput='pointer'] &:focus`]: {
  outline: 'none',
  border: 'none',
},
```

#### Prop Spread Behavior

- Extend the interface of the primary element/component in your component (e.g.
  `export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement>`)
- Intentionally destructure your props so that every prop is assigned. This allows you to use spread
  the way it was intended.

  ```tsx
  interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
    type: ButtonType,
    size: ButtonSize,
    icon: CanvasIcon
  }

  // ...somewhere in your button render()
  const { type, size, icon, ...elemProps } = this.props
  <ButtonContainer type={type} size={size} icon={icon} {...elemProps} />
  ```

- Only spread props on one element/component

#### Controlled Components

- We opt for controlled components wherever possible.
- We aim to manage the least amount of state within our components as possible.
- For input type components:

  - Always stick with the default `value` and `onChange` if you can
  - Deviate where it makes sense and/or is required (e.g. `checked` and `onChange` for checkboxes).

#### Accessibility

- Use aria labels where required
- Ensure full keyboard navigation
- Check whether tabbing is enough or whether additional keyboard navigation is required (e.g. arrow
  keys)
- When in doubt, ask an expert!

#### Children

- We often add or augment props to React children within our components. Use `React.Children.map`
  along with `React.cloneElement()`
- Use `React.isValidElement()` if you want to make sure it's a React component and not a regular DOM
  node.
- If you're adding any event handlers to the children, make sure you also support existing ones

#### Logic Flow

- If vs. Switch: use switch statements when code branching is determined by the value of a single
  variable or expression.
- Nested Ternaries: maximum two levels and only if it's very obvious. If you have two or more
  levels, try rewriting it as if/else statements and compare the complexity & scanability.
- Opt for
  [pure functions](https://medium.com/@jamesjefferyuk/javascript-what-are-pure-functions-4d4d5392d49c)
  wherever possible. They make unit testing easier and always behave as expected. Because React can
  be a bit of a magic black box, sometimes `this.x` values are not what you expect.

```ts
foo(number, bar) => {
  return number * bar
}

foo(this.number, this.bar);

// is a much better option than

foo() => {
  return this.number * this.bar
}

foo();
```

#### Server Side Rendering

- In order to support SSR, we cannot reference global objects (`window`, `document`, etc.) before a
  component is hydrated/mounted.
- Generally, it is only safe to use these freely within `componentDidMount`, `useEffect` and
  `useLayoutEffect`.
- This means that any reference to `window` or `document` should be avoided wherever possible within
  the global scope, constructors, and render methods.
- If you need to reference these variables in these avoided places, you must check whether it's
  undefined first (e.g. `typeof window !== 'undefined'`)
- Be particularly careful when initializing default props or state with something stored on the
  `window`/`document` objects. These initializations will have to be skipped for SSR contexts
  (assign `undefined` or `null`) and updated upon mounting.

## Code Style

#### Default Props

- Use `defaultProps` whenever you find yourself checking for the existence of something before
  executing branching logic. It significantly reduces conditionals, facilitating easier testing and
  less bugs.
- We prefer to colocate our default props and destructure them which allows consumers to rename our
  components on import.
- Note: If you assign a default value to a prop, make sure to make the prop as optional in the
  interface.

```jsx
const someInterface {
  /**
   * If true, sets the Checkbox checked to true
   * @default false
   */
  checked?: boolean;
   /**
   * If true, set the Checkbox to the disabled state.
   * @default false
   */
  disabled?: boolean;
  /**
   * The value of the Checkbox.
   */
  value?: string;
}
//...
const {checked = false, disabled = false, value} = this.props;
```

#### Element Choice

- Use the correct native element wherever possible. This enables us to get as much behavior for free
  from the browser.
- For example, if something peforms an action on a click, it should generally use a `button` to get
  keypress handling for free.

#### Styled Components

- Always initialize styled components outside of your render function. Failing to do this will
  result in a big performance hit.
- When specifying the props a styled component can accept, it is up to you do define how restrictive
  you should be. You can accept any prop that the component accepts (e.g.
  `styled('div')<ComponentProps>`) or only accept a subset (e.g.
  `styled('div')<Pick<ComponentProps, 'someProp' | 'anotherProp'>>`)
- We generally prefer the use of `styled` components over using the `css` function. However, `css`
  can be handy for some basic styling.

#### Exports

- [Avoid default exports](https://basarat.gitbook.io/typescript/main-1/defaultisbad)
- Export everything else as a named export (`export * from ...`). Consider the naming of the things
  you're exporting (interfaces, enums, etc.) so you don't encounter any clashes.

```ts
// inside MyComponent/index.ts
export * from './lib/MyComponent';
export * from './lib/AnotherComponent';
```

## Documentation

#### Readmes

- Follow our README template
- Outline static properties (e.g. `Button.Type`), required props, and optional props
- Usage example should be as standalone as possible. As long as it's not too complex, this snippet
  should be a working implementation so consumers can copy/paste

#### Storybook Structure

- Always opt for the most referenceable code in your stories. Storybook helps us test, but many
  consumers use it as an example of how to implement components.
- Avoid helper functions to reduce duplication that make it harder to parse.
- Avoid sharing wrappers, components, etc. from other story files.
- Essentially, try to keep each example as standalone and referencable as possible.

#### Prop Descriptions

We use [JSDoc](https://devdocs.io/jsdoc/) standards for our prop type definitions.

The base pattern for prop descriptions is: `The <property> of the <component>.` For example:

```js
/**
  * The value of the Checkbox.
  */
value?: string;
```

Be as specific as possible. For example, suppose there is a `label` prop for `Checkbox` which
specifies the text of the label. Rather than describe `label` as `The label of the Checkbox`, the
following is preferable:

```js
/**
  * The text of the Checkbox label.
  */
label?: string;
```

Feel free to provide additional detail in the description:

```js
/**
 * The value of the Slider. Goes to 11.
 */
value: number;
```

Be sure to specify a proper `@default` for enum props. Listing the named values which are accepted
by the enum prop is encouraged:

```js
/**
  * The side from which the SidePanel opens. Accepts `Left` or `Right`.
  * @default SidePanelOpenDirection.Left
  */
openDirection?: SidePanelOpenDirection;
```

Use a modified pattern for function props: `The function called when <something happens>.` For
example:

```js
/**
  * The function called when the Checkbox state changes.
  */
onChange?: (e: React.ChangeEvent) => void;
```

The pattern for booleans is also different: `If true, <do something>.` For standard 2-state
booleans, set `@default false` in the description. For example:

```js
/**
  * If true, set the Checkbox to the disabled state.
  * @default false
  */
disabled?: boolean;
```

Provide additional detail for 2-state booleans where the `false` outcome cannot be inferred:

```js
/**
  * If true, center the Header navigation. If false, right-align the Header navigation.
  * @default false
  */
centeredNav?: boolean;
```

For 3-state booleans, you will need to describe all 3 cases:
`If true <do something>. If false <do something else>. If undefined <do yet another thing>.`

We also recommend the following pattern for errors:

```js
/**
  * The type of error associated with the Checkbox (if applicable).
  */
error?: ErrorType;
```

Occasionally, you may encounter props which don't play nicely with the suggested guidelines. Rather
than following the patterns to the letter, adjust them to provide a better description if necessary.
For example, rather than ambiguously describing `id` as `The id of the Checkbox`, provide a more
explicit description:

```js
/**
  * The HTML `id` of the underlying checkbox input element.
  */
id?: string;
```