* Added Overview page * Revised Getting Started page * Minor revision * Edited readme, minor modifications to docs * Removed sweep.yaml, .devcontainer, .ergomake * Moved security.md to .github, added contributing.md * changes as per code review * updated contributing.md * fixed broken links & added missing links in doc, improved structure * fixed link in wsl setup * fixed server link, added https cloning in yarn-setup
292 lines
8.4 KiB
Plaintext
292 lines
8.4 KiB
Plaintext
---
|
||
title: Style Guide
|
||
sidebar_position: 4
|
||
sidebar_custom_props:
|
||
icon: TbPencil
|
||
---
|
||
|
||
We define here the rules to follow when writing code.
|
||
|
||
Our goal is to have a consistent codebase, which is easy to read and easy to maintain.
|
||
|
||
For this we prefer to tend towards being a bit more verbose than being too concise.
|
||
|
||
Always keep in mind that code is read more often than it is written, especially on an open source project, where anyone can contribute.
|
||
|
||
There are a lot of rules that are not defined here, but that are automatically checked by our linters.
|
||
|
||
## React
|
||
|
||
### Use functional components
|
||
|
||
Always use TSX functional components.
|
||
|
||
Do not use default `import` with `const`, because it's harder to read and harder to import with code completion.
|
||
|
||
```tsx
|
||
// ❌ Bad, harder to read, harder to import with code completion
|
||
const MyComponent = () => {
|
||
return <div>Hello World</div>;
|
||
};
|
||
|
||
export default MyComponent;
|
||
|
||
// ✅ Good, easy to read, easy to import with code completion
|
||
export function MyComponent() {
|
||
return <div>Hello World</div>;
|
||
};
|
||
```
|
||
|
||
### Props
|
||
|
||
Create the type of the props and call it `(ComponentName)Props` if there's no need to export it.
|
||
|
||
Use props destructuring.
|
||
|
||
```tsx
|
||
// ❌ Bad, no type
|
||
export const MyComponent = (props) => <div>Hello {props.name}</div>;
|
||
|
||
// ✅ Good, type
|
||
type MyComponentProps = {
|
||
name: string;
|
||
};
|
||
|
||
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
|
||
```
|
||
|
||
#### Refrain from using `React.FC` or `React.FunctionComponent` to define prop types.
|
||
|
||
```tsx
|
||
/* ❌ - Bad, defines the component type annotations with `FC`
|
||
* - With `React.FC`, the component implicitly accepts a `children` prop
|
||
* even if it's not defined in the prop type. This might not always be
|
||
* desirable, especially if the component doesn't intend to render
|
||
* children.
|
||
*/
|
||
const EmailField: React.FC<{
|
||
value: string;
|
||
}> = ({ value }) => <TextInput value={value} disabled fullWidth />;
|
||
```
|
||
|
||
```tsx
|
||
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
|
||
* component's props
|
||
* - This method doesn't automatically include the children prop. If
|
||
* you want to include it, you have to specify it in OwnProps.
|
||
*/
|
||
type EmailFieldProps = {
|
||
value: string;
|
||
};
|
||
|
||
const EmailField = ({ value }: EmailFieldProps) => (
|
||
<TextInput value={value} disabled fullWidth />
|
||
);
|
||
```
|
||
|
||
#### No Single Variable Prop Spreading in JSX Elements
|
||
|
||
We discourage the use of single variable prop spreading in JSX elements, e.g., `{...props}`. This practice often leads to less readable and maintainable code as it's unclear what props are being passed down to the component.
|
||
|
||
```tsx
|
||
/* ❌ - Bad, spreads a single variable prop into the underlying component
|
||
*/
|
||
const MyComponent = (props: OwnProps) => {
|
||
return <OtherComponent {...props} />;
|
||
}
|
||
```
|
||
|
||
```tsx
|
||
/* ✅ - Good, Explicitly lists all props
|
||
* - Enhances readability and maintainability
|
||
*/
|
||
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
|
||
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
|
||
};
|
||
```
|
||
|
||
Rationale:
|
||
- It's clearer to see at a glance which props are being passed down, making the code easier to understand and maintain.
|
||
- It helps to prevent tight coupling between components via their props.
|
||
- It's easier to catch misspelled or unused props with linting tools when props are listed explicitly
|
||
|
||
## JavaScript
|
||
|
||
### Use nullish-coalescing operator `??`
|
||
|
||
```tsx
|
||
// ❌ Bad, can return 'default' even if value is 0 or ''
|
||
const value = process.env.MY_VALUE || 'default';
|
||
|
||
// ✅ Good, will return 'default' only if value is null or undefined
|
||
const value = process.env.MY_VALUE ?? 'default';
|
||
```
|
||
|
||
### Use optional chaining `?.`
|
||
|
||
```tsx
|
||
// ❌ Bad
|
||
onClick && onClick();
|
||
|
||
// ✅ Good
|
||
onClick?.();
|
||
```
|
||
|
||
## TypeScript
|
||
|
||
### Use `type` instead of `Interface`
|
||
|
||
We decided to always use `type` instead of `interface`, because they almost always overlap, and `type` is more flexible.
|
||
|
||
```tsx
|
||
// ❌ Bad
|
||
interface MyInterface {
|
||
name: string;
|
||
}
|
||
|
||
// ✅ Good
|
||
type MyType = {
|
||
name: string;
|
||
};
|
||
```
|
||
|
||
### Use string literals instead of enums
|
||
|
||
[String literals](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) are the go-to way to handle enum-like values in TypeScript. They are easier to extend with Pick and Omit, and offer a better developer experience, especially with code completion.
|
||
|
||
You can see why TypeScript recommend avoiding enums [here](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums).
|
||
|
||
```tsx
|
||
// ❌ Bad, utilizes an enum
|
||
enum Color {
|
||
Red = "red",
|
||
Green = "green",
|
||
Blue = "blue",
|
||
}
|
||
|
||
let color = Color.Red;
|
||
```
|
||
|
||
```tsx
|
||
// ✅ Good, utilizes a string literal
|
||
let color: "red" | "green" | "blue" = "red";
|
||
```
|
||
|
||
#### GraphQL and internal libs
|
||
|
||
We recommend using enums that are generated by GraphQL codegen.
|
||
|
||
We also recommend using an enum when using an internal lib, so the internal lib doesn't have to expose a string literal type that is not related to the internal API.
|
||
|
||
Example:
|
||
|
||
```TSX
|
||
const {
|
||
setHotkeyScopeAndMemorizePreviousScope,
|
||
goBackToPreviousHotkeyScope,
|
||
} = usePreviousHotkeyScope();
|
||
|
||
setHotkeyScopeAndMemorizePreviousScope(
|
||
RelationPickerHotkeyScope.RelationPicker,
|
||
);
|
||
```
|
||
|
||
## Styling
|
||
|
||
### Use StyledComponents
|
||
|
||
Components should be styled with [styled-components](https://emotion.sh/docs/styled).
|
||
|
||
```tsx
|
||
// ❌ Bad
|
||
<div className="my-class">Hello World</div>
|
||
```
|
||
|
||
```tsx
|
||
// ✅ Good
|
||
const StyledTitle = styled.div`
|
||
color: red;
|
||
`;
|
||
```
|
||
|
||
Styled components should be prefixed with "Styled" to differentiate them from "real" components.
|
||
|
||
```tsx
|
||
// ❌ Bad
|
||
const Title = styled.div`
|
||
color: red;
|
||
`;
|
||
```
|
||
|
||
```tsx
|
||
// ✅ Good
|
||
const StyledTitle = styled.div`
|
||
color: red;
|
||
`;
|
||
```
|
||
|
||
### Theming
|
||
|
||
Utilizing the theme for the majority of component styling is the preferred approach.
|
||
|
||
#### Units of measurement
|
||
|
||
Avoid using `px` or `rem` values directly within the styled components. The necessary values are generally already defined in the theme, so it’s recommended to make use of the theme for these purposes.
|
||
|
||
#### Colors
|
||
|
||
Refrain from introducing additional colors; instead, utilize the existing palette from the theme. Should there be a situation where the palette does not align, kindly leave a comment so that it can be rectified.
|
||
|
||
|
||
```tsx
|
||
// ❌ Bad, directly specifies style values without utilizing the theme
|
||
const StyledButton = styled.button`
|
||
color: #333333;
|
||
font-size: 1rem;
|
||
font-weight: 400;
|
||
margin-left: 4px;
|
||
border-radius: 50px;
|
||
`;
|
||
```
|
||
|
||
```tsx
|
||
// ✅ Good, utilizes the theme
|
||
const StyledButton = styled.button`
|
||
color: ${({ theme }) => theme.font.color.primary};
|
||
font-size: ${({ theme }) => theme.font.size.md};
|
||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||
border-radius: ${({ theme }) => theme.border.rounded};
|
||
`;
|
||
```
|
||
## Enforcing No-Type Imports
|
||
|
||
In our codebase, we've adopted a coding standard to disallow type imports. This helps maintain consistency and readability in our TypeScript code. To enforce this standard, we've added an ESLint rule that checks for and reports any type imports.
|
||
|
||
```tsx
|
||
// ❌ Bad
|
||
import { type Meta, type StoryObj } from '@storybook/react';
|
||
|
||
// ❌ Bad
|
||
import type { Meta, StoryObj } from '@storybook/react';
|
||
|
||
// ✅ Good
|
||
import { Meta, StoryObj } from '@storybook/react';
|
||
```
|
||
|
||
### Why No-Type Imports?
|
||
|
||
- **Consistency**: By avoiding type imports, our codebase remains consistent in its module import style. We use a single approach for both type and value imports.
|
||
|
||
- **Readability**: No-type imports improve code readability by making it clear when you're importing values or types. This reduces ambiguity and makes it easier to understand the purpose of imported symbols.
|
||
|
||
- **Maintainability**: Codebase maintainability is enhanced because developers can quickly identify and locate type-only imports when reviewing or modifying code.
|
||
|
||
### ESLint Rule
|
||
|
||
We've configured an ESLint rule, `@typescript-eslint/consistent-type-imports`, to enforce the no-type import standard. This rule will generate errors or warnings for any type import violations.
|
||
|
||
Please note that this rule is intended to address rare edge cases where type imports might be used unintentionally. TypeScript itself discourages this practice, as mentioned in the [TypeScript 3.8 release notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). In most situations, you should not need to use type-only imports.
|
||
|
||
To ensure your code complies with this rule, make sure to run ESLint as part of your development workflow.
|