Skip to content

Coding guidelines~Style guide & patterns

Cory Crowley edited this page Mar 22, 2022 · 17 revisions

We use prettier for code formatting. It should auto format on save. If it's not, make sure you set it as your default formatter.

We use eslint for code quality. You shouldn't need to do anything here but if it complains, you can run npm run lint and it should auto fix most things.

We use Storybook for testing and Chromatic for snapshot validations. All tests need to work without connection to real endpoints so make sure you've got mock data in the MockAdapter.

File structure

  • Adapters - data connectors
  • Components - React components
  • Cards - older react components [deprecated]
  • Models - classes, constants, contexts, hooks, services, & types
  • Pages - the high level apps/pages that we export to consumers
  • Resources - localization & static files
  • Theming - color palettes and theme definitions

Under Components, the root files should be the component that is used in the app. Any subcomponents that are very specific to your component should go in an Internals folder. Look at ADT3DSceneBuilder as an example since it's a pretty big component.

Patterns

A few general patterns we follow in the code

  • We default export components at the bottom of the file
  • Components at the root of the Components folder should be wrapped in <BaseComponent>
  • No hard coded display strings in components, localize everything. Use the useTranslation hook and call t('string name') to get your resource from the translation.json
  • We use BaseComponent in our components only when the component might need to be used as a standalone component outside of the main tree. If it's specific to the 3D work, then it doesn't need it. As a general rule, only wrap components in BaseComponent if it's at the root of the Components folder. Things in Internals should not be wrapped.
  • There should only be 1 component per file. If you have sub components, pull them out to their own file
  • Bias towards using Fluent UI components over building using raw <button> or <input> elements.
  • Styles should use the theme provided by the useTheme hook and you can look at theme.palette or theme.semanticColors to get your colors. This is important as we should NOT have any hard coded colors in the code since that will break when the theme is changed.
  • If for some reason you still need a custom color. Please add it as a constant in the Palette.ts file so it's in one place and we can audit the app at some point.
  • Test decorators can be used to wrap all the components in a test file so you don't have to copy over and over. Currently, CardboardList and ADT3DSceneBuilder.Bheaviors.stories.tsx have some of the more robust tests in the repo so you can look there for examples of how to use storybook
  • Maybe a bit OCD but sort your props, imports and style properties alphabetically. It's just nice on the eyes.

File divisions

  • Component in one file (MyComponent.tsx)
  • Styles in separate file (MyComponent.styles.ts)
  • Types/interfaces in a separate file (MyComponent.types.ts)

Styling

We are moving away from SCSS and towards the mergeStyles package from Fluent. All new components should be using the mergeStyles format for styling and not SCSS. For an example of how to do this you can look at CardboardListItem (here)

See more details for this on the Using mergeStyles page.

Comments

All exported functions and components should have public comments with arguments annotated using JS Comment notation Example:

 /**
 * Gets a decorator that wraps the story with the global theme & localization controls
 * <T> is the type for the props of the component
 * @param cardStyle Styles for the wrapping component around the story control
 * @returns Decorator for the story to wrap the control being tested.
 */

Localizations

Every string in the UI needs to have a localized string. They are stored in Locales/en/translation.json You can nest objects in that translation file like {"3DSceneBuilder":{"SubComponent":{"MyString":"display string here"}}} You can access those strings in the component using the useLocalization and then calling the t() function with the string key (ex: 3DSceneBuilder.SubComponent.MyString)

A new pattern that we are starting to adopt is to pull out localization strings to a common spot at the top of each component to make it easier to find them and see all the resources a component uses. Example: StatesTab

const ROOT_LOC = '3dSceneBuilder.behaviorAlertForm';
const LOC_KEYS = {
    colorPickerLabel: `${ROOT_LOC}.colorPickerLabel`,
    expressionLabel: `${ROOT_LOC}.expressionLabel`,
    expressionPlaceholder: `${ROOT_LOC}.expressionPlaceholder`,
    notice: `${ROOT_LOC}.notice`,
    notificationLabel: `${ROOT_LOC}.notificationLabel`,
    notificationPlaceholder: `${ROOT_LOC}.notificationPlaceholder`
};
...
<Text styles={{ root: { color: theme.palette.neutralSecondary } }}>
    {t(LOC_KEYS.notice)}
</Text>

Theming & error handling

To get theming and error boundaries you should use <BaseComponent> (here). All components at the root of the Components folder should be wrapped in this HOC which provides the Theming & Localization providers used by fluent as well as the SCSS variables needed for styling.

The <BaseComponent> will also provide an error boundary around the control to encapsulate errors. In addition, it can handle loading states as well if the optional props are provided.

One example of using <BaseComponent> is SceneList

Tests

  • In storybook, name your test starting with Mock if you have it with mock data and there is a connected version. Ex: Mock 3D Builder vs ADT 3D Builder
  • File names - include local for stories that talk to real data
    • use MyComponent.stories.tsx for mocked out stories (ex: BlobDropdown)
    • use MyComponent.stories.local.tsx for stories that call out to APIs (ex: BlobDropdown)
  • Every component should have a story no matter how small.
  • Every story will get a baseline visual test through Chromatic, you'll have to accept changes to the baseline when you raise the PR.
  • If you want particular conditions to get a snapshot test added, you can use the play feature to orchestrate interactions with the UI. See ADT3DSceneBuilder.Bheaviors.stories.tsx for examples of what you can do here.