Introduce UI Components documentation (#1926)
* new contributor guide folder architecture * update content pass 1 * Prepare UI component folder to receive componentns * Add component doc example for button * Fix broken links * Fix broken links * Fix images
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"position": 3
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"label": "Advanced",
|
||||
"position": 3,
|
||||
"customProps": {
|
||||
"icon": "TbTerminal2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
sidebar_custom_props:
|
||||
icon: TbChecklist
|
||||
---
|
||||
|
||||
# Best practices
|
||||
|
||||
## State management
|
||||
|
||||
We use React and Recoil for state management.
|
||||
|
||||
### Use `useRecoilState` to store state
|
||||
|
||||
We recommend that you create as many atoms as you need to store your state.
|
||||
|
||||
Rule of thumb : It's better to be using too many atoms than trying to be too concise with props drilling.
|
||||
|
||||
```tsx
|
||||
export const myAtomState = atom({
|
||||
key: 'myAtomState',
|
||||
default: 'default value',
|
||||
});
|
||||
|
||||
export const MyComponent = () => {
|
||||
const [myAtom, setMyAtom] = useRecoilState(myAtomState);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
value={myAtom}
|
||||
onChange={(e) => setMyAtom(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Do not use `useRef` to store state
|
||||
|
||||
We do not recommend using `useRef` to store state.
|
||||
|
||||
If you want to store state, you should use `useState` or `useRecoilState`.
|
||||
|
||||
We recommend seeing [how to manage re-renders](#managing-re-renders) if you feel like you need `useRef` to prevent some re-renders from happening.
|
||||
|
||||
## Managing re-renders
|
||||
|
||||
Re-renders can be hard to manage in React.
|
||||
|
||||
We provide you with some rules that we follow to avoid unnecessary re-renders.
|
||||
|
||||
Keep in mind that re-renders can **always** be avoided by understanding the cause of the re-render.
|
||||
|
||||
### Work at the root level
|
||||
|
||||
We made it easy for you to avoid re-renders in new features by taking care of eliminating them at the root level.
|
||||
|
||||
There's only one `useEffect` in the sidecar component `PageChangeEffect` that is holding all the logic that should be executed on a page change.
|
||||
|
||||
That way you know that there's only one place that can trigger a re-render.
|
||||
|
||||
### Always think twice before adding `useEffect` in your codebase
|
||||
|
||||
Re-renders are often caused by unnecessary `useEffect`.
|
||||
|
||||
You should think whether the useEffect is really needed, or if you can move the logic in a event handler function.
|
||||
|
||||
You'll find it generally easy to move the logic in a `handleClick` or `handleChange` function.
|
||||
|
||||
You can also find them in libraries like Apollo : `onCompleted`, `onError`, etc.
|
||||
|
||||
### Use a sibling component to extract useEffect or data fetching logic
|
||||
|
||||
If you feel like you need to add a `useEffect` in your root component, you should consider extracting it in a sidecar component.
|
||||
|
||||
The same can be applied for data fetching logic, with Apollo hooks.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, will cause re-renders even if data is not changing,
|
||||
// because useEffect needs to be re-evaluated
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useRecoilState(dataState);
|
||||
const [someDependency] = useRecoilState(someDependencyState);
|
||||
|
||||
useEffect(() => {
|
||||
if(someDependency !== data) {
|
||||
setData(someDependency);
|
||||
}
|
||||
}, [someDependency]);
|
||||
|
||||
return <div>{data}</div>;
|
||||
};
|
||||
|
||||
export const App = () => (
|
||||
<RecoilRoot>
|
||||
<PageComponent />
|
||||
</RecoilRoot>
|
||||
);
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, will not cause re-renders if data is not changing,
|
||||
// because useEffect is re-evaluated in another sibling component
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useRecoilState(dataState);
|
||||
|
||||
return <div>{data}</div>;
|
||||
};
|
||||
|
||||
export const PageData = () => {
|
||||
const [data, setData] = useRecoilState(dataState);
|
||||
const [someDependency] = useRecoilState(someDependencyState);
|
||||
|
||||
useEffect(() => {
|
||||
if(someDependency !== data) {
|
||||
setData(someDependency);
|
||||
}
|
||||
}, [someDependency]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export const App = () => (
|
||||
<RecoilRoot>
|
||||
<PageData />
|
||||
<PageComponent />
|
||||
</RecoilRoot>
|
||||
);
|
||||
```
|
||||
|
||||
### Use recoil family states and recoil family selectors
|
||||
|
||||
Recoil family states and selectors are a great way to avoid re-renders.
|
||||
|
||||
They are especially useful when you need to store a list of items.
|
||||
|
||||
### You shouldn't use `React.memo(MyComponent)`
|
||||
|
||||
We do not recommend `React.memo()` usage because it does not solve the cause of the re-render, but instead breaks the re-render chain, which can lead to unexpected behavior and make the code really hard to refactor.
|
||||
|
||||
### Limit `useCallback` or `useMemo` usage
|
||||
|
||||
They are often not necessary and will make the code harder to read and maintain for a gain of performance that is unnoticeable.
|
||||
|
||||
## Console.logs
|
||||
|
||||
`console.log` statements are invaluable during development, offering real-time insights into variable values and code flow. However, leaving them in production code can lead to several issues:
|
||||
|
||||
1. **Performance**: Excessive logging can affect the runtime performance, especially on client-side applications.
|
||||
|
||||
2. **Security**: Logging sensitive data can expose critical information to anyone who inspects the browser's console.
|
||||
|
||||
3. **Cleanliness**: Filling up the console with logs can obscure important warnings or errors that developers or tools need to see.
|
||||
|
||||
4. **Professionalism**: End users or clients checking the console and seeing a myriad of log statements might question the code's quality and polish.
|
||||
|
||||
## Naming
|
||||
|
||||
### Variable Naming
|
||||
|
||||
Variable names ought to precisely depict the purpose or function of the variable.
|
||||
|
||||
#### The issue with generic names
|
||||
Generic names in programming are not ideal because they lack specificity, leading to ambiguity and reduced code readability. Such names fail to convey the variable or function's purpose, making it challenging for developers to understand the code's intent without deeper investigation. This can result in increased debugging time, higher susceptibility to errors, and difficulties in maintenance and collaboration. Descriptive naming, on the other hand, makes the code self-explanatory and easier to navigate, enhancing overall code quality and developer productivity.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, uses a generic name that doesn't communicate its
|
||||
// purpose or content clearly
|
||||
const [value, setValue] = useState('');
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, uses a descriptive name
|
||||
const [email, setEmail] = useState('');
|
||||
```
|
||||
|
||||
#### Some words to avoid in variable names
|
||||
|
||||
- dummy
|
||||
|
||||
### Event handlers
|
||||
|
||||
Event handler names should start with `handle`, `on` is a prefix used to name events in components props
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
const onEmailChange = (val: string) => {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good
|
||||
const handleEmailChange = (val: string) => {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
## Optional Props
|
||||
|
||||
Avoid supplying the default value for an optional prop, as it generally doesn’t contribute significantly.
|
||||
|
||||
EXAMPLE
|
||||
|
||||
Assume, we have the `EmailField` component defined below
|
||||
|
||||
```tsx
|
||||
type OwnProps = {
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const EmailField = ({ value, disabled = false }: OwnProps) => (
|
||||
<TextInput value={value} disabled={disabled} fullWidth />
|
||||
);
|
||||
```
|
||||
|
||||
USAGE
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, passing in the same value as the default value adds no value
|
||||
const Form = () => <EmailField value="username@email.com" disabled={false} />;
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, assumes the default value
|
||||
const Form = () => <EmailField value="username@email.com" />;
|
||||
```
|
||||
|
||||
## Component as props
|
||||
|
||||
Try as much as possible to pass uninstanciated components as props, so chilren can decide on their own of what props they need to pass.
|
||||
|
||||
The most common example for that is icon components :
|
||||
|
||||
```tsx
|
||||
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
|
||||
|
||||
// In MyComponent
|
||||
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MyIcon size={theme.icon.size.md}>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
```
|
||||
|
||||
For React to understand that the component is a component, you need to use PascalCase, to later instanciate it with `<MyIcon>`
|
||||
|
||||
## Prop Drilling: Keep It Minimal
|
||||
|
||||
Prop drilling, in the React context, refers to the practice of passing state variables and their setters through multiple component layers, even if intermediary components don't use them. While sometimes necessary, excessive prop drilling can lead to:
|
||||
|
||||
1. **Decreased Readability**: Tracing where a prop originates or where it's utilized can become convoluted in a deeply nested component structure.
|
||||
|
||||
2. **Maintenance Challenges**: Changes in one component's prop structure might necessitate adjustments in several components, even if they don't directly use the prop.
|
||||
|
||||
3. **Reduced Component Reusability**: A component receiving numerous props solely for the purpose of passing them down becomes less general-purpose and harder to reuse in different contexts.
|
||||
|
||||
If you feel that you are using excessive prop drilling, see [state management best practices](/contributor/frontend/advanced/best-practices#state-management)
|
||||
|
||||
## Imports
|
||||
|
||||
When importing, opt for the designated aliases rather than specifying complete or relative paths.
|
||||
|
||||
THE ALIASES
|
||||
|
||||
```js
|
||||
{
|
||||
alias: {
|
||||
"~": path.resolve(__dirname, "src"),
|
||||
"@": path.resolve(__dirname, "src/modules"),
|
||||
"@testing": path.resolve(__dirname, "src/testing"),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
USAGE
|
||||
```tsx
|
||||
// ❌ Bad, specifies the entire relative path
|
||||
import {
|
||||
CatalogDecorator
|
||||
} from '../../../../../testing/decorators/CatalogDecorator';
|
||||
import {
|
||||
ComponentDecorator
|
||||
} from '../../../../../testing/decorators/ComponentDecorator';
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, utilises the designated aliases
|
||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';=
|
||||
```
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
Prioritize thorough manual testing before proceeding to guarantee that modifications haven’t caused disruptions elsewhere, given that tests have not yet been extensively integrated.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
sidebar_position: 11
|
||||
sidebar_custom_props:
|
||||
icon: TbKeyboard
|
||||
---
|
||||
|
||||
# Hotkeys
|
||||
|
||||
You can intercept any hotkey combination and execute a custom action.
|
||||
|
||||
We added a thin wrapper on top of [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) to make it more performant and to avoid unnecessary re-renders.
|
||||
|
||||
We also created a wrapper hook `useScopedHotkeys` to make it easy to manage scopes.
|
||||
|
||||
```ts
|
||||
useScopedHotkeys(
|
||||
'ctrl+k,meta+k',
|
||||
() => {
|
||||
openCommandMenu();
|
||||
},
|
||||
AppHotkeyScope.CommandMenu,
|
||||
[openCommandMenu],
|
||||
);
|
||||
```
|
||||
@@ -0,0 +1,292 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
sidebar_custom_props:
|
||||
icon: TbPencil
|
||||
---
|
||||
|
||||
# Style guide
|
||||
|
||||
We define here the rules to follow when writing code.
|
||||
|
||||
Our goal is to have a consistent codebase, 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 `OwnProps` 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 OwnProps = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const MyComponent = ({ name }: OwnProps) => <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 OwnProps = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
const EmailField = ({ value }: OwnProps) => (
|
||||
<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 }: OwnProps) => {
|
||||
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.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"label": "Basics",
|
||||
"position": 1,
|
||||
"customProps": {
|
||||
"icon": "TbTerminal2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
sidebar_position: 7
|
||||
sidebar_custom_props:
|
||||
icon: TbPaint
|
||||
---
|
||||
|
||||
# Design System
|
||||
|
||||
We rely on our internal and custom design system, that is built on top of styled-components.
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
sidebar_custom_props:
|
||||
icon: TbFolder
|
||||
---
|
||||
|
||||
# Folder Architecture
|
||||
|
||||
In this guide, you will explore the details of the project directory structure and how it contributes to the organization and maintainability of Twenty.
|
||||
|
||||
By following this folder architecture convention, it is easier to find the files related to specific features and ensure that the application is scalable and maintainable.
|
||||
|
||||
```
|
||||
front
|
||||
└───modules
|
||||
│ └───module1
|
||||
│ │ └───submodule1
|
||||
│ └───module2
|
||||
│ └───ui
|
||||
│ │ └───buttons
|
||||
└───pages
|
||||
└───...
|
||||
```
|
||||
|
||||
## Pages
|
||||
|
||||
Contains the top-level components that are defined by the application routes. They import more low-level components from the modules folder (more details below).
|
||||
|
||||
## Modules
|
||||
|
||||
Each module represents a feature or a group of feature, comprising its specific components, states, and operational logic.
|
||||
They should all follow the structure below. You can nest modules within modules; We often refer to them as submodules but the same rules apply.
|
||||
|
||||
```
|
||||
module1
|
||||
└───components
|
||||
│ └───component1
|
||||
│ └───component2
|
||||
└───constants
|
||||
└───contexts
|
||||
└───graphql
|
||||
│ └───fragments
|
||||
│ └───queries
|
||||
│ └───mutations
|
||||
└───hooks
|
||||
│ └───internal
|
||||
└───states
|
||||
│ └───recoil-scope-contexts
|
||||
│ └───selectors
|
||||
└───types
|
||||
└───utils
|
||||
```
|
||||
|
||||
### Contexts
|
||||
|
||||
A context is a way to pass data through the component tree without having to pass props down manually at every level.
|
||||
|
||||
See [React Context](https://react.dev/reference/react#context-hooks) for more details
|
||||
|
||||
### States
|
||||
|
||||
Contains the state management logic. We use [RecoilJS](https://recoiljs.org) for this.
|
||||
|
||||
- Selectors
|
||||
|
||||
See [RecoilJS Selectors](https://recoiljs.org/docs/basic-tutorial/selectors) for more details.
|
||||
|
||||
- Recoil Scope Contexts
|
||||
|
||||
More details will be added soon.
|
||||
|
||||
We still use React's built-in state management for state that is only used within a component.
|
||||
|
||||
### Hooks
|
||||
|
||||
See [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) for more details.
|
||||
|
||||
### Utils
|
||||
|
||||
Should only contain reusable pure functions. Otherwise, create custom hooks in the `hooks` folder.
|
||||
|
||||
### GraphQL
|
||||
|
||||
Includes fragments, queries, and mutations.
|
||||
|
||||
See [GraphQL](https://graphql.org/learn/) for more details.
|
||||
|
||||
- Fragments
|
||||
|
||||
A fragment is a reusable piece of a query, which can be used in multiple places. By using fragments, it is easier to avoid duplicating code.
|
||||
|
||||
See [GraphQL Fragments](https://graphql.org/learn/queries/#fragments) for more details.
|
||||
|
||||
- Queries
|
||||
|
||||
See [GraphQL Queries](https://graphql.org/learn/queries/) for more details.
|
||||
|
||||
- Mutations
|
||||
|
||||
See [GraphQL Mutations](https://graphql.org/learn/queries/#mutations) for more details.
|
||||
|
||||
## UI
|
||||
|
||||
Contains all of the reusable UI components used in the application.
|
||||
|
||||
This folder can contain subfolders for specific types of components, such as `buttons`, `inputs`, or `modals`. Each component should be self-contained and reusable, so that it can be used in multiple parts of the application.
|
||||
|
||||
By separating the UI components from the other components in the `modules` folder, it is easier to maintain a consistent design and to make changes to the UI without affecting other parts (business logic) of the codebase.
|
||||
|
||||
## Interface and dependencies
|
||||
|
||||
You can import other module code from any module except for the `ui` folder. This will keep its code easy to test.
|
||||
|
||||
### Internal
|
||||
|
||||
Each part (hooks, states, ...) of a module can have an `internal` folder, which contains parts that are only used within the module.
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
sidebar_custom_props:
|
||||
icon: TbEyeglass
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
## Tech Stack
|
||||
|
||||
We took care of having a clean and simple stack, with minimal boilerplate code.
|
||||
|
||||
**App**
|
||||
|
||||
- [React](https://react.dev/)
|
||||
- [Apollo](https://www.apollographql.com/docs/)
|
||||
- [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
|
||||
- [Recoil](https://recoiljs.org/docs/introduction/core-concepts)
|
||||
- [TypeScript](https://www.typescriptlang.org/)
|
||||
|
||||
**Testing**
|
||||
|
||||
- [Jest](https://jestjs.io/)
|
||||
- [Storybook](https://storybook.js.org/)
|
||||
|
||||
**Tooling**
|
||||
|
||||
- [Yarn](https://yarnpkg.com/)
|
||||
- [Craco](https://craco.js.org/docs/)
|
||||
- [ESLint](https://eslint.org/)
|
||||
|
||||
## Architecture
|
||||
|
||||
### Routing
|
||||
|
||||
We use [React Router](https://reactrouter.com/) for routing.
|
||||
|
||||
To avoid unnecessary [re-renders](/contributor/frontend/advanced/best-practices#managing-re-renders) we handle all the routing logic in a `useEffect` in `PageChangeEffect`.
|
||||
|
||||
### State Management
|
||||
|
||||
We use [Recoil](https://recoiljs.org/docs/introduction/core-concepts) for state management.
|
||||
|
||||
See our [best practices](/contributor/frontend/advanced/best-practices#state-management) for more managing state.
|
||||
|
||||
## Testing
|
||||
|
||||
We use [Jest](https://jestjs.io/) for unit testing and [Storybook](https://storybook.js.org/) for component testing.
|
||||
|
||||
Jest is mainly used for testing utility functions, and not components themselves.
|
||||
|
||||
Storybook is used for testing the behavior of isolated components, as well as displaying our [design system](/contributor/frontend/basics/design-system).
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
sidebar_custom_props:
|
||||
icon: TbBrandFigma
|
||||
---
|
||||
|
||||
# Work with figma
|
||||
|
||||
Figma is a collaborative interface design tool that aids in bridging the communication barrier between designers and developers.
|
||||
In this guide, we'll go over how to collaborate with Twenty’s Figma.
|
||||
|
||||
## Access
|
||||
|
||||
1. **Access the shared link:** you can access the Twenty Figma file with this link: https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty
|
||||
2. **Sign in:** If you're not already signed in to Figma, you'll be prompted to do so.
|
||||
Key features are only available to logged-in users, such as the developer mode and the ability to select a dedicated frame.
|
||||
**You will not be able to collaborate effectively without an account.**
|
||||
|
||||
## Figma structure
|
||||
|
||||
On the left sidebar, you can access the different pages of our Figma. They are organised as such:
|
||||
|
||||
- **Components page:** This is the first page. The designer uses it to create and organize the reusable design elements that are used throughout the design file. For example, buttons, icons, symbols, or any other reusable components. It serves to maintain consistency across the design.
|
||||
- **Main page:** The second page is the main page, where the complete user interface of the project is shown. You can press ***Play*** to use the full app prototype.
|
||||
- **Features pages:** The subsequent pages are typically dedicated to features being worked on. They contain the design of specific features or modules of the application or website. They are usually still being worked on.
|
||||
|
||||
## Useful Tips
|
||||
|
||||
With read-only access, you can't edit the design but you can access all features that will be useful to implement the designs into code.
|
||||
|
||||
### Use the Dev mode
|
||||
|
||||
Figma's Dev Mode enhances developers' productivity by providing easy design navigation, effective asset management, efficient communication tools, toolbox integrations, quick code snippets, and key layer information, bridging the gap between design and development. learn more at https://www.figma.com/dev-mode/
|
||||
|
||||
Switch to the "Developer" mode in the right part of the toolbar to see design specs, copy CSS, and access assets.
|
||||
|
||||
### Use the Prototype
|
||||
|
||||
Click on any element on the canvas and press the “Play” button at the top right edge of the interface to access the prototype view. Prototype mode allows you to interact with the design as if it were the final product. It demonstrates the flow between screens and how interface elements like buttons, links, or menus behave when interacted with.
|
||||
|
||||
1. **Understanding transitions and animations:** In the Prototype mode, any transitions or animations added by a designer between screens or UI elements can be viewed, providing clear visual instructions to developers on the intended behavior and style.
|
||||
2. **Implementation Clarification:** A prototype can also be used to reduce ambiguities. Developers can interact with it to gain a better understanding of the functionality or appearance of particular elements.
|
||||
|
||||
For more comprehensive details and guidance on learning the Figma platform, you can visit the official Figma Documentation: https://help.figma.com/hc/en-us
|
||||
|
||||
### Measure distances
|
||||
|
||||
Select an element, hold `Option` key (Mac) or `Alt` key (Windows), then hover over another element to see the distance between them.
|
||||
|
||||
### Figma extension for VSCode (Recommended)
|
||||
|
||||
[Figma for VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
|
||||
lets you navigate and inspect design files, collaborate with designers, track changes, and speed up implementation - all without leaving your text editor.
|
||||
It is part of our recommended extensions.
|
||||
|
||||
## Collaboration
|
||||
|
||||
1. **Using Comments:** You are welcome to use the comment feature by clicking on the bubble icon in the left part of the toolbar.
|
||||
2. **Cursor chat:** A nice feature of Figma is the Cursor chat. Just press `;` on Mac and `/` on Windows to send a message if you see someone else using Figma as the same time as you.
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Contributing
|
||||
sidebar_position: 1
|
||||
sidebar_custom_props:
|
||||
icon: TbTopologyStar
|
||||
---
|
||||
|
||||
## Pre-requesites
|
||||
|
||||
Make sure that your [IDE is correctly setup](/contributor/local-setup/ide-setup) and that your backend is running on `localhost:3000`.
|
||||
|
||||
|
||||
## Starting a new feature
|
||||
|
||||
Make sure your database is running on the URL provided in your `server/.env` file.
|
||||
|
||||
```
|
||||
cd front
|
||||
yarn
|
||||
|
||||
yarn start
|
||||
```
|
||||
|
||||
## Regenerate graphql schema based on API graphql schema
|
||||
|
||||
```
|
||||
yarn graphql:generate
|
||||
```
|
||||
|
||||
## Lint
|
||||
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
```
|
||||
yarn test # run jest tests
|
||||
yarn storybook:dev # run storybook
|
||||
yarn storybook:test # run tests (needs yarn storybook:dev to be running)
|
||||
yarn storybook:coverage # run tests (needs yarn storybook:dev to be running)
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: Frontend Development
|
||||
displayed_sidebar: frontendSidebar
|
||||
sidebar_position: 0
|
||||
sidebar_custom_props:
|
||||
icon: TbTerminal2
|
||||
isSidebarRoot: true
|
||||
---
|
||||
|
||||
Welcome to the Frontend Development section of the documentation.
|
||||
Here you will find information about the frontend development process, the tools we use, and the best practices we follow.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"label": "UI Components",
|
||||
"position": 1,
|
||||
"customProps": {
|
||||
"icon": "TbTerminal2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"label": "Forms",
|
||||
"position": 2,
|
||||
"collapsible": true,
|
||||
"collapsed": true,
|
||||
"customProps": {
|
||||
"icon": "TbForms"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Button
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
import IframeResizer from 'iframe-resizer-react';
|
||||
|
||||
<IframeResizer
|
||||
checkOrigin={false}
|
||||
inPageLinks
|
||||
src="http://storybook.twenty.com/iframe.html?id=ui-button-button--docs&viewMode=docs&singleStory=true"
|
||||
style={{ width: '1px', minWidth: '100%'}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Overview
|
||||
sidebar_position: 0
|
||||
sidebar_custom_props:
|
||||
icon: TbBrandFigma
|
||||
---
|
||||
|
||||
WIP
|
||||
|
||||
Reference in New Issue
Block a user