A description of the structure of the codebase, conventions being followed, and patterns.
- Component structure and standards
- Building components
- Writing tests
The TDS codebase maintains a set of organizational and syntactical standards. We utilize the following tools for the development, release, and distribution processes:
- Styleguidist: isolated React component development environment with a living style guide
- Jest and Enzyme: we use jest as our test runner and enzyme to test our React components
- Linters and Prettier: standardize code style and format
- CSS Modules: facilitates the buildup of scoped CSS while maintaining the familiar interface of SCSS
- NPM: we use NPM as our node package manager
- Openshift and Docker: the CI pipeline is largely based on the TELUS isomorphic starter kit pipeline, using Docker as the build artifact
Component structure and standards
All TDS components have a common directory structure and set of standards. Where you have a
ButtonLink, the files are organized like this:
/packages/ │ └─── ButtonLink │ ButtonLink.md │ ButtonLink.jsx │ ButtonLink.modules.scss | index.cjs.js │ index.es.js | package.json | README.md | rollup.config.js | └─── __tests__ │ ButtonLink.spec.jsx | └─── __snapshots__ | ButtonLink.spec.jsx.snap
Here you may notice some of our standards:
- Use PascalCase for all file names
- Every component must include a set of unit tests and snapshot
- If a component requires custom styling, use CSS Modules and suffix your scss file with
- To include custom documentation with a component, use
- To include documentation for the npm registry page, use
Building React components for TDS involves a set of coding standards in order to maintain consistent syntax and form across the codebase. Though the majority of syntax is automatically formatted with our linter rules and with the aid of Prettier (see Developer Guide for setup), this guide covers the more subjective rules we uphold.
Creating new components from scratch
When starting fresh, you can use the scaffolding script to generate a component directory structure:
npm run scaffold [ComponentName]
This will output a set of files in the aforementioned structure.
React & JSX practices
Though the following practices are not strictly enforced, they are strongly encouraged:
Code style and conventions
Here are some dos and don'ts to consider when writing code.
- When creating components, consider this order of preference:
- Functional components, optionally with hooks and effects
- Pure components
- Class components
- Accessibility and responsiveness is a must for every component
- Provide a flexible and predictable interface via props
- Prefer containment over specialization. See React docs on composition vs inheritance
- Components should only be aware of themselves or their direct children
- Reuse component logic and shared assets as much as possible
- DO make components self-contained. A component should not know anything about other components, except for its direct children
- DO provide a clear, prop-based API to the component. Avoid allowing consumers to customize styles by passing in
styleas this is not a clear API. Use a prop with known values instead
- DO use React component state for ephemeral UI state within components, while avoiding redux or other application state containers
- DO use dependencies when needed, such as open source React components so long as they do not bloat the component final component build
- DO use tds-core components and other tds-community components judiciously
- DO make components compatible with React 15 or greater
- DO forward
restprops onto the primary HTML element of the component so that consumers can still attach global HTML attributes such as
- DON'T hardcode content without giving consumers the ability to override it with other copy or languages
- De-structure props in the function signature to make it easy to see all of the used props
- Always accept "rest" props (except for className and style) to allow for common global HTML attributes such as
id. Spread these onto the main HTML element of the component before other props. This is to prevent accidental or purposeful overriding.
styleare also not available as a prop interface in most components because TDS strictly enforces the TELUS brand through styles, and as such there should be no potential inaccurate brand representations
- If a parent component does not use props and only passes them down to its children, pass components as props
Styling (CSS Modules, Sass)
- DO scope component class names
- DON'T specify external margins. Components must be able to fit into various layouts
- DON'T use HTML elements or IDs as CSS selectors. Only use class names
- DON'T hardcode pixel values unless an absolute pixel value is required. Use tds-core components or relative values such as rem instead.
A utility module is a shared package that can provide common functionality to multiple components. Utility modules are not published to NPM, but are versioned in order for lerna to know it should upgrade components consuming one that has been modified. For an example of a utility module, look at [util-generate-id][tds-util-example] on tds-core.
- DO add utility modules to the
/shareddirectory with its own
- DO configure
private: truewithin a utility module's
- DO add utility modules as a devDependency to components that consume one
- DON'T create utility modules that cannot be reused in any other component
TDS components use CSS Modules with Sass. You can learn about its usage and design from the CSS Modules GitHub repository. TDS components derive CSS modules from their respective ComponentName.modules.scss file. The following patterns are strongly encouraged:
- Use the
composesproperty rather than SCSS
@extendor comma-separated classes. Mixins are acceptable
- Use camelCase class names
- Use flexbox, but be aware of cross-browser limitations
- Components should make effective use of 'layout' components such as Box or Responsive, rather than styles
composes example above:
<!-- the 'class' attribute contains the 'primary' and 'base' classes since 'primary' composes' base --> <a class="primary base" href="#">Find out how</a>
Tests utilize Jest and Enzyme matchers. Tests are treated as a first-class citizen. Tests should clearly outline the features and expected output for a component. For some inspiration, have a look at how pre-existing TDS components' tests are written.
Use assertions that produce helpful error messages, enzyme-matchers is useful for this.
expect(myComponent).toHaveProp('someBoolean', true) // => Expected myComponent to have the prop "someBoolean" // with the value of true, but it was false.
expect(myComponent.props().someBoolean).toBeTruthy() // => Expected false to be truthy.
Always prefer "shallow" rendering, then "render", then "mount". Only "mount" if you are testing the lifecycle methods.
Use a snapshot test for components that do not have any logic and to increase confidence in the structure of the component. Snapshot tests do not replace unit tests.