-
Notifications
You must be signed in to change notification settings - Fork 26
Coding guidelines~Style guide & patterns
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
.
- 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.
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 callt('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 inBaseComponent
if it's at the root of theComponents
folder. Things inInternals
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 attheme.palette
ortheme.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.
- Component in one file (
MyComponent.tsx
) - Styles in separate file (
MyComponent.styles.ts
) - Types/interfaces in a separate file (
MyComponent.types.ts
)
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.
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.
*/
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>
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
- 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
vsADT 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)
- use
- 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.
If you have a question for one of the project maintainers, please post the question here. We'll get back to you as soon as possible!