snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,329 @@
---
title: Лучшие практики
icon: star
---
Этот документ описывает лучшие практики, которых следует придерживаться при работе с фронтендом.
## Управление состоянием
React и Jotai отвечают за управление состоянием в коде.
### Используйте атомы Jotai для хранения состояния
Полезно создавать столько атомов, сколько вам нужно для хранения состояния.
<Warning>
Лучше использовать дополнительные атомы, чем пытаться быть слишком кратким с передачей свойств.
</Warning>
```tsx
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
export const myAtomState = createAtomState<string>({
key: 'myAtomState',
defaultValue: 'default value',
});
export const MyComponent = () => {
const [myAtom, setMyAtom] = useAtomState(myAtomState);
return (
<div>
<input
value={myAtom}
onChange={(e) => setMyAtom(e.target.value)}
/>
</div>
);
}
```
### Не используйте `useRef` для хранения состояния
Избегайте использования `useRef` для хранения состояния.
Если вы хотите сохранять состояние, используйте `useState` или атомы Jotai с `useAtomState`.
Смотрите [как управлять повторными рендерами](#managing-re-renders), если вы считаете, что вам нужен `useRef`, чтобы предотвратить их.
## Управление повторными рендерами
Управлять повторными рендерами в React может быть сложно.
Вот некоторые правила, которые стоит соблюдать, чтобы избегать ненужных повторных рендеров.
Помните, что вы всегда можете избежать повторных рендеров, если поймете причину их возникновения.
### Работа на корневом уровне
Избежать повторных рендеров в новых функциях стало проще, устранив их на корневом уровне.
Сайдкар-компонент `PageChangeEffect` содержит всего один `useEffect`, который реализует всю логику при изменении страницы.
Таким образом, вы знаете, что есть только одно место, которое может вызвать повторный рендер.
### Всегда думайте дважды, прежде чем добавлять `useEffect` в ваш код
Повторные рендеры часто вызываются ненужными `useEffect`.
Подумайте, нужно ли вам использовать `useEffect`, или же вы можете перенести логику в функцию обработчика событий.
Как правило, несложно перенести логику в функции `handleClick` или `handleChange`.
Вы также можете найти их в библиотеках, таких как Apollo: `onCompleted`, `onError` и т. д.
### Используйте дополнительный компонент для извлечения `useEffect` или логики получения данных
Если вы считаете, что нужно добавить `useEffect` в корневой компонент, стоит рассмотреть возможность его извлечения в сайдкар-компонент.
Вы можете применять то же самое для логики получения данных, с хуками Apollo.
```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] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <div>{data}</div>;
};
export const App = () => (
<PageComponent />
);
```
```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] = useAtomState(dataState);
return <div>{data}</div>;
};
export const PageData = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <></>;
};
export const App = () => (
<>
<PageData />
<PageComponent />
</>
);
```
### Используйте состояния семейства атомов и селекторы
Состояния семейства атомов и селекторы — отличный способ избежать повторных рендеров.
Они полезны, когда нужно хранить список элементов.
### Не следует использовать `React.memo(MyComponent)`
Избегайте использования `React.memo()`, так как это не решает причину повторного рендера, но разрывает цепочку рендера, что может привести к неожиданному поведению и усложнить рефакторинг кода.
### Ограничьте использование `useCallback` или `useMemo`
Часто это не нужно и делает код труднее читаемым и поддерживаемым для улучшения производительности, которую сложно заметить.
## Console.logs
`console.log` полезны во время разработки, предоставляя информацию в реальном времени о значениях переменных и потоке кода. Однако оставление их в коде для продакшена может привести к нескольким проблемам:
1. **Производительность**: Избыточное логирование может повлиять на производительность, особенно в клиентских приложениях.
2. **Безопасность**: Логирование конфиденциальных данных может раскрыть критическую информацию тем, кто может просматривать консоль браузера.
3. **Чистота**: Заполнение консоли логами может скрыть важные предупреждения или ошибки, которые нужно увидеть разработчикам или инструментам.
4. **Профессионализм**: Конечные пользователи или клиенты, проверяющие консоль и видящие множество логов, могут усомниться в качестве и обработке кода.
Убедитесь, что все `console.logs` удалены перед загрузкой кода в продакшен.
## Называние
### Наименование переменных
Названия переменных должны точно описывать их цель или функцию.
#### Проблемы с универсальными именами
Универсальные имена в программировании не идеальны, так как они не хватает определенности, что ведет к неясности и ухудшению читаемости кода. Такие имена не передают цель переменной или функции, делая трудным понимать намерение кода без более глубокого исследования. Это может привести к увеличению времени отладки, повышению вероятности ошибок и трудностям в поддержке и сотрудничестве. Между тем, описательные имена делают код самодокументирующимся и более простым для навигации, улучшая качество кода и продуктивность разработчиков.
```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('');
```
#### Некоторые слова, которых стоит избегать в названиях переменных
* dummy
### Обработчики событий
Названия обработчиков событий должны начинаться с `handle`, в то время как `on` используется как префикс для наименования событий в пропсах компонентов.
```tsx
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
```
## Необязательные пропсы
Избегайте передачи значения по умолчанию для необязательного пропса.
**ПРИМЕР**
Возьмите компонент `EmailField`, определенный ниже:
```tsx
type EmailFieldProps = {
value: string;
disabled?: boolean;
};
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
<TextInput value={value} disabled={disabled} fullWidth />
);
```
**Использование**
```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" />;
```
## Компонент как пропсы
Постарайтесь максимально передавать неинстанцированные компоненты как пропсы, чтобы дети могли самостоятельно определять, какие пропсы им нужно передать.
Наиболее распространенный пример этого — иконки компонентов:
```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>
)
};
```
Чтобы React понял, что компонент является компонентом, нужно использовать PascalCase, чтобы потом инстанцировать его с `<MyIcon>`
## Сверление пропсов: минимизируйте его
Сверление пропсов в контексте React — это практика передачи переменных состояния и их сеттеров через многие уровни компонентов, даже если промежуточные компоненты их не используют. Хотя иногда это необходимо, чрезмерное сверление пропсов может привести к:
1. **Уменьшение читаемости**: Отслеживание происхождения пропса или мест, где он используется, может стать запутанным в глубоко вложенной структуре компонентов.
2. **Трудности в обслуживании**: Изменения в структуре пропсов одного компонента могут потребовать изменений в нескольких компонентах, даже если они их не используют напрямую.
3. **Снижение повторного использования компонентов**: Компонент, получающий много пропсов только для передачи их дальше, становится менее универсальным и сложным для повторного использования в разных контекстах.
Если вы считаете, что чрезмерно используете сверление пропсов, обратите внимание на [лучшие практики управления состоянием](#state-management).
## Импорт
При импорте предпочтение стоит отдать указанным псевдонимам, а не полным или относительным путям.
**Псевдонимы**
```js
{
alias: {
"~": path.resolve(__dirname, "src"),
"@": path.resolve(__dirname, "src/modules"),
"@testing": path.resolve(__dirname, "src/testing"),
},
}
```
**Использование**
```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 'twenty-ui/testing';
```
## Проверка схемы
[Zod](https://github.com/colinhacks/zod) — это проверка схемы для нетипизированных объектов:
```js
const validationSchema = z
.object({
exist: z.boolean(),
email: z
.string()
.email('Email must be a valid email'),
password: z
.string()
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
})
.required();
type Form = z.infer<typeof validationSchema>;
```
## Изменения с нарушением совместимости
Всегда проводите тщательное ручное тестирование перед тем, как продолжить, чтобы гарантировать, что изменения не вызвали перебоев в других местах, поскольку тесты еще не были широко интегрированы.

View File

@@ -0,0 +1,110 @@
---
title: Архитектура папок
icon: folder-tree
info: Подробное рассмотрение нашей архитектуры папок
---
В этом руководстве вы изучите детали структуры каталога проекта и то, как она способствует организации и сопровождаемости Twenty.
Следуя этому соглашению об архитектуре папок, легче находить файлы, связанные с конкретными функциями, и обеспечивать масштабируемость и поддерживаемость приложения.
```
front
└───modules
│ └───module1
│ │ └───submodule1
│ └───module2
│ └───ui
│ │ └───display
│ │ └───inputs
│ │ │ └───buttons
│ │ └───...
└───pages
└───...
```
## Страницы
Включает компоненты высокого уровня, определенные маршрутами приложения. Они импортируют более низкоуровневые компоненты из папки модулей (подробности ниже).
## Модули
Каждый модуль представляет собой функционал или группу функций, содержащий свои специфические компоненты, состояния и операционную логику.
Все они должны следовать структуре ниже. Вы можете вложить модули в модули (называются подмодулями), и те же правила будут применяться.
```
module1
└───components
│ └───component1
│ └───component2
└───constants
└───contexts
└───graphql
│ └───fragments
│ └───queries
│ └───mutations
└───hooks
│ └───internal
└───states
│ └───selectors
└───types
└───utils
```
### Контексты
Контекст — это способ передачи данных через дерево компонентов без необходимости передавать props вручную на каждом уровне.
См. [React Context](https://react.dev/reference/react#context-hooks) для более подробной информации.
### GraphQL
Включает фрагменты, запросы и мутации.
См. [GraphQL](https://graphql.org/learn/) для более подробной информации.
* Фрагменты
Фрагмент — это переиспользуемая часть запроса, которую можно использовать в разных местах. Используя фрагменты, легче избежать дублирования кода.
См. [GraphQL Fragments](https://graphql.org/learn/queries/#fragments) для более подробной информации.
* Запросы
См. [GraphQL Queries](https://graphql.org/learn/queries/) для более подробной информации.
* Мутации
См. [GraphQL Mutations](https://graphql.org/learn/queries/#mutations) для более подробной информации.
### Хуки
См. [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) для более подробной информации.
### Состояния
Содержит логику управления состоянием. [Jotai](https://jotai.org) этим управляет.
* Селекторы: Производные атомы (с использованием `createAtomSelector`) вычисляют значения из других атомов и автоматически мемоизируются.
Встроенное управление состоянием в React все еще управляет состоянием внутри компонента.
### Утилиты
Должны содержать только переиспользуемые чистые функции. В противном случае создайте пользовательские хуки в папке `hooks`.
## Пользовательский интерфейс
Содержит все переиспользуемые компоненты пользовательского интерфейса, используемые в приложении.
Эта папка может содержать подкаталоги, такие как `data`, `display`, `feedback` и `input` для конкретных типов компонентов. Каждый компонент должен быть автономным и переиспользуемым, чтобы вы могли использовать его в разных частях приложения.
Разделяя компоненты пользовательского интерфейса от других компонентов в папке `modules`, легче поддерживать согласованность дизайна и вносить изменения в пользовательский интерфейс, не затрагивая другие части (бизнес-логику) кодовой базы.
## Интерфейс и зависимости
Вы можете импортировать код других модулей из любого модуля, кроме папки `ui`. Это позволит упростить тестирование его кода.
### Внутренний
Каждая часть (хуки, состояния, ...) модуля может иметь внутреннюю папку, содержащую части, которые используются только внутри модуля.

View File

@@ -0,0 +1,91 @@
---
title: Команды фронтенда
icon: terminal
---
## Полезные команды
### Запуск приложения
```bash
npx nx start twenty-front
```
### Перегенерировать схему graphql на основе схемы API graphql
```bash
npx nx run twenty-front:graphql:generate --configuration=metadata
```
ИЛИ
```bash
npx nx run twenty-front:graphql:generate
```
### Линт
```bash
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## Переводы
```bash
npx nx run twenty-front:lingui:extract
npx nx run twenty-front:lingui:compile
```
### Тест
```bash
npx nx run twenty-front:test # run jest tests
npx nx run twenty-front:storybook:serve:dev # run storybook
npx nx run twenty-front:storybook:test # run tests # (needs yarn storybook:serve:dev to be running)
npx nx run twenty-front:storybook:coverage # (needs yarn storybook:serve:dev to be running)
```
## Технологический стек
Проект имеет чистый и простой стек с минимальным кодом.
**Приложение**
* [React](https://react.dev/)
* [Apollo](https://www.apollographql.com/docs/)
* [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
* [Jotai](https://jotai.org/)
* [TypeScript](https://www.typescriptlang.org/)
**Тестирование**
* [Jest](https://jestjs.io/)
* [Storybook](https://storybook.js.org/)
**Инструменты**
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
## Архитектура
### Маршрутизация
[React Router](https://reactrouter.com/) обрабатывает маршрутизацию.
Чтобы избежать ненужных [перерисовок](/l/ru/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders), вся логика маршрутизации находится в `useEffect` в `PageChangeEffect`.
### Управление состоянием
[Jotai](https://jotai.org/) отвечает за управление состоянием.
[лучшие практики](/l/ru/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) для получения дополнительной информации об управлении состоянием.
## Тестирование
[Jest](https://jestjs.io/) служит инструментом для модульного тестирования, а [Storybook](https://storybook.js.org/) — для тестирования компонентов.
Jest в основном используется для тестирования утилит, а не самих компонентов.
Storybook предназначен для тестирования поведения изолированных компонентов, а также для отображения системы дизайна.

View File

@@ -0,0 +1,178 @@
---
title: Горячие клавиши
---
## Введение
Когда вам нужно прослушивать горячую клавишу, вы обычно используете обработчик событий `onKeyDown`.
Однако в `twenty-front` могут возникнуть конфликты между одинаковыми горячими клавишами, которые используются в разных компонентах, смонтированных одновременно.
Например, если у вас есть страница, которая реагирует на клавишу Enter, и модальное окно, реагирующее на ту же клавишу Enter, с компонентом Select внутри этого окна, то может возникнуть конфликт, когда все они смонтированы одновременно.
## Хук `useScopedHotkeys`
Для решения этой проблемы у нас есть индивидуальный хук, который позволяет мониторить горячие клавиши без конфликтов.
Вы размещаете его в компоненте, и он будет реагировать на горячие клавиши только тогда, когда компонент смонтирован И заданные **области горячих клавиш** активны.
## Как прослушивать горячие клавиши на практике?
Настройка мониторинга горячих клавиш включает в себя два шага :
1. Установить [область горячих клавиш](#what-is-a-hotkey-scope-), которая будет реагировать на горячие клавиши
2. Использовать хук `useScopedHotkeys` для мониторинга горячих клавиш
Настройка областей горячих клавиш необходима даже на простых страницах, так как другие элементы пользовательского интерфейса, такие как меню слева или меню команд, также могут реагировать на горячие клавиши.
## Кейсы использования горячих клавиш
В общем, у вас будет два случая использования, требующих горячих клавиш :
1. На странице или в компоненте, смонтированном на странице
2. В компоненте типа модального окна, который фокусируется из-за действия пользователя
Второй случай использования может происходить рекурсивно : например, раскрывающееся меню в модальном окне.
### Прослушивание горячих клавиш на странице
Пример :
```tsx
const PageListeningEnter = () => {
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
// 1. Set the hotkey scope in a useEffect
useEffect(() => {
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleEnterPage,
);
// Revert to the previous hotkey scope when the component is unmounted
return () => {
goBackToPreviousHotkeyScope();
};
}, [goBackToPreviousHotkeyScope, setHotkeyScopeAndMemorizePreviousScope]);
// 2. Use the useScopedHotkeys hook
useScopedHotkeys(
Key.Enter,
() => {
// Some logic executed on this page when the user presses Enter
// ...
},
ExampleHotkeyScopes.ExampleEnterPage,
);
return <div>My page that listens for Enter</div>;
};
```
### Прослушивание горячих клавиш в компоненте типа модального окна
Для этого примера мы используем модальный компонент, который прослушивает ключ Escape, чтобы сообщить своему родителю закрыть его.
В этом случае взаимодействие пользователя меняет область операций.
```tsx
const ExamplePageWithModal = () => {
const [showModal, setShowModal] = useState(false);
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
const handleOpenModalClick = () => {
// 1. Set the hotkey scope when user opens the modal
setShowModal(true);
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleModal,
);
};
const handleModalClose = () => {
// 1. Revert to the previous hotkey scope when the modal is closed
setShowModal(false);
goBackToPreviousHotkeyScope();
};
return <div>
<h1>My page with a modal</h1>
<button onClick={handleOpenModalClick}>Open modal</button>
{showModal && <MyModalComponent onClose={handleModalClose} />}
</div>;
};
```
Затем в компоненте модального окна :
```tsx
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
// 2. Use the useScopedHotkeys hook to listen for Escape.
// Note that escape is a common hotkey that could be used by many other components
// So it's important to use a hotkey scope to avoid conflicts
useScopedHotkeys(
Key.Escape,
() => {
onClose()
},
ExampleHotkeyScopes.ExampleModal,
);
return <div>My modal component</div>;
};
```
Важно использовать этот шаблон, когда вы не уверены, что просто использование useEffect при монтировании/размонтировании будет достаточным для избегания конфликтов.
Эти конфликты могут быть сложными для отладки, и они могут возникать чаще, чем ожидалось, с использованием useEffects.
## Что такое область горячих клавиш?
Область горячих клавиш — это строка, представляющая собой контекст, в котором активны горячие клавиши. Она обычно кодируется в виде перечисления (enum).
При изменении области горячих клавиш, горячие клавиши, слушающие эту область, будут включены, а слушающие другие области — отключены.
Вы можете установить только одну область за раз.
Например, области горячих клавиш для каждой страницы определены в enum `PageHotkeyScope`:
```tsx
export enum PageHotkeyScope {
Settings = 'settings',
CreateWorkspace = 'create-workspace',
SignInUp = 'sign-in-up',
CreateProfile = 'create-profile',
PlanRequired = 'plan-required',
ShowPage = 'show-page',
PersonShowPage = 'person-show-page',
CompanyShowPage = 'company-show-page',
CompaniesPage = 'companies-page',
PeoplePage = 'people-page',
OpportunitiesPage = 'opportunities-page',
ProfilePage = 'profile-page',
WorkspaceMemberPage = 'workspace-member-page',
TaskPage = 'task-page',
}
```
Внутренне текущая выбранная область действия хранится в атоме Jotai, который используется по всему приложению:
```tsx
export const currentHotkeyScopeState = createState<HotkeyScope>({
key: 'currentHotkeyScopeState',
defaultValue: INITIAL_HOTKEYS_SCOPE,
});
```
Но этот атом никогда не следует обрабатывать вручную! Мы увидим, как использовать это в следующем разделе.
## Как это работает внутренне?
Мы сделали тонкую обертку поверх [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro), которая делает его более производительным и избегает ненужных повторных рендеров.
Мы также создаем атом Jotai, чтобы управлять состоянием области действия горячих клавиш и сделать его доступным везде в приложении.

View File

@@ -0,0 +1,8 @@
---
title: Сторибук
description: Просмотр библиотеки компонентов UI Twenty
---
Посмотрите нашу полную библиотеку компонентов и документацию в Сторибуке.
[Открыть Сторибук →](https://storybook.twenty.com)

View File

@@ -0,0 +1,291 @@
---
title: Руководство по стилю
icon: paintbrush
---
Этот документ включает правила, которые нужно соблюдать при написании кода.
Цель заключается в обеспечении однородности кода, который будет легко читать и поддерживать.
Для этого лучше быть немного более многословными, чем слишком краткими.
Всегда помните, что код читают чаще, чем пишут, особенно в проекте с открытым исходным кодом, в который может внести вклад кто угодно.
Существует много правил, которые здесь не описаны, но автоматически проверяются линтерами.
## React
### Используйте функциональные компоненты.
Всегда используйте функциональные компоненты TSX.
Не используйте стандартный `import` с `const`, так как это сложнее для чтения и импорта с автозаполнением кода.
```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>;
};
```
### Свойства
Создайте тип пропсов и назовите его `(ComponentName)Props`, если нет необходимости экспортировать его.
Используйте деструктуризацию пропсов.
```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>;
```
#### Воздержитесь от использования `React.FC` или `React.FunctionComponent` для определения типов пропсов.
```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 />
);
```
#### Нет разворачивания пропсов одиночной переменной в JSX-элементах.
Избегайте спреда пропсов из одной переменной в JSX-элементах, например `{...props}`. Подобная практика часто приводит к менее читаемому и сложному в поддержке коду, так как непонятно, какие пропсы принимает компонент.
```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 }} />;
};
```
Обоснование:
* С первого взгляда становится яснее, какие пропсы передаются в коде, что делает его легче понятным и поддерживаемым.
* Это помогает предотвратить жесткое связывание между компонентами через их пропсы.
* Инструменты для анализа кода облегчают обнаружение опечаток или неиспользуемых пропсов при явном перечислении пропсов.
## JavaScript
### Используйте оператор нулевого слияния `??`
```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';
```
### Используйте опциональную цепочку `?.`
```tsx
// ❌ Bad
onClick && onClick();
// ✅ Good
onClick?.();
```
## TypeScript
### Используйте `type` вместо `interface`
Всегда используйте `type` вместо `interface`, так как они почти всегда пересекаются, а `type` более гибок.
```tsx
// ❌ Bad
interface MyInterface {
name: string;
}
// ✅ Good
type MyType = {
name: string;
};
```
### Используйте строковые литералы вместо перечислений.
[Строковые литералы](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) - это основной способ обработки значений, напоминающих перечисление, в TypeScript. Их проще расширять с помощью Pick и Omit, и они обеспечивают более удобную работу разработчика, особенно благодаря автодополнению кода.
Вы можете увидеть, почему TypeScript рекомендует избегать перечислений [здесь](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 и внутренние библиотеки
Вы должны использовать перечисления, которые генерирует кодогенератор GraphQL.
Также лучше использовать перечисление при использовании внутренней библиотеки, чтобы она не требовала явного указания типа строкового литерала, не связанного с внутренним API.
Пример:
```TSX
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
```
## Стилизация
### Использование StyledComponents
Стилизуйте компоненты с помощью [Linaria styled](https://github.com/callstack/linaria).
```tsx
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
Добавляйте префикс "Styled" к стилизованным компонентам, чтобы отличать их от "реальных" компонентов.
```tsx
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
### Темизация
Использование темы для большинства стилевых компонентов является предпочтительным подходом.
#### Единицы измерения
Избегайте использования значений `px` или `rem` напрямую в стилизованных компонентах. Необходимые значения обычно уже определены в теме, поэтому рекомендуется использовать тему для этих целей.
#### Цвета
Избегайте введения новых цветов; используйте существующую палитру из темы. Если палитра не соответствует, оставьте комментарий, чтобы команда могла это исправить.
```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};
`;
```
## Запрещение импорта типов
Избегайте импорта типов. Чтобы обеспечить соблюдение этого стандарта, правило Oxlint проверяет и сообщает о любых импортах с ключевым словом type. Это помогает сохранить согласованность и читаемость кода TypeScript.
```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';
```
### Почему избегать импорта типов
* **Согласованность**: Избегая импорта типов и используя единый подход для импорта как типов, так и значений, кодовая база остается согласованной в стиле импорта модулей.
* **Читаемость**: Избегающий импорт типов улучшает читаемость кода, делая ясным, когда вы импортируете значения или типы. Это снижает двусмысленность и облегчает понимание назначения импортируемых символов.
* **Поддерживаемость**: Это повышает поддерживаемость кодовой базы, поскольку разработчики могут идентифицировать и находить импорты только типов при просмотре или изменении кода.
### Правило Oxlint
Правило Oxlint `typescript/consistent-type-imports` обеспечивает соблюдение стандарта импортов без ключевого слова type. Это правило создаст ошибки или предупреждения для всех нарушений импорта типов.
Обратите внимание, что это правило касается редких крайних случаев, когда случаются непреднамеренные импорты типов. Сам TypeScript не рекомендует эту практику, как указано в [примечаниях к выпуску TypeScript 3.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). В большинстве случаев вам не нужно использовать импорты только типов.
Чтобы гарантировать соответствие вашего кода этому правилу, убедитесь, что вы запускаете Oxlint как часть вашего рабочего процесса разработки.

View File

@@ -0,0 +1,61 @@
---
title: Работа с Figma
info: Узнайте, как можно сотрудничать с Figma компании Twenty
---
Figma - это совместный инструмент для проектирования пользовательских интерфейсов, который помогает преодолеть барьер в коммуникации между дизайнерами и разработчиками.
Это руководство объясняет, как вы можете работать с Figma.
## Доступ
1. **Получите доступ к общей ссылке:** Вы можете открыть файл проекта Figma [здесь](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty).
2. **Войдите в систему:** Если вы ещё не вошли, Figma предложит вам это сделать.
Ключевые функции доступны только для авторизованных пользователей, такие как режим разработчика и возможность выбора конкретного фрейма.
<Warning>
Вы не сможете эффективно сотрудничать без учетной записи.
</Warning>
## Структура Figma
На левой боковой панели вы можете получить доступ к разным страницам Figma компании Twenty. Вот как они организованы:
* **Страница компонентов:** Это первая страница. Дизайнер использует её для создания и организации элементов дизайна, которые могут быть переиспользованы в течение всего файла дизайна. Например, кнопки, иконки, символы или любые другие переиспользуемые компоненты. Она служит для поддержания консистентности дизайна.
* **Главная страница:** Вторая страница — это главная страница, которая показывает полный пользовательский интерфейс проекта. Вы можете нажать ***Play***, чтобы использовать полный прототип приложения.
* **Страницы с функциями:** Другие страницы обычно посвящены функциям в процессе разработки. Они содержат дизайн определённых функций или модулей приложения или веб-сайта. Они, как правило, все еще в процессе разработки.
## Полезные советы
С доступом только для чтения, вы не можете редактировать дизайн, но вы можете получить доступ ко всем функциям, которые будут полезны для преобразования дизайнов в код.
### Используйте режим разработчика
Dev Mode Figma повышает производительность разработчиков, обеспечивая легкую навигацию по дизайну, эффективное управление ресурсами, инструменты связи, интеграции с инструментами для разработки, быстрые фрагменты кода и информацию о ключевых слоях, устраняя разрыв между дизайном и разработкой. Подробнее о режиме Dev можно узнать [здесь](https://www.figma.com/dev-mode/).
Переключитесь в режим "Разработчик" в правой части панели инструментов, чтобы посмотреть спецификации дизайна, скопировать CSS и получить доступ к ресурсам.
### Использование прототипа
Кликните на любой элемент на холсте и нажмите кнопку “Play” в верхней правой части интерфейса, чтобы открыть просмотр прототипа. Режим прототипа позволяет взаимодействовать с дизайном, как если бы это был финальный продукт. Он демонстрирует переходы между экранами и то, как элементы интерфейса, такие как кнопки, ссылки или меню, ведут себя при взаимодействии.
1. **Понимание переходов и анимаций:** В режиме Прототипа вы можете увидеть любые переходы или анимации, добавленные дизайнером между экранами или элементами интерфейса, предоставляя четкие визуальные инструкции разработчикам о предположительном поведении и стиле.
2. **Уточнение реализации:** Прототип также может помочь уменьшить неясности. Разработчики могут взаимодействовать с ним, чтобы лучше понять функциональность или внешний вид определенных элементов.
Для получения более подробной информации и руководств о платформе Figma, вы можете посетить официальную [Документацию Figma](https://help.figma.com/hc/en-us).
### Измерение расстояний
Выберите элемент, удерживая клавишу `Option` (Mac) или `Alt` (Windows), затем наведите курсор на другой элемент, чтобы увидеть расстояние между ними.
### Расширение Figma для VSCode (Рекомендуется)
[Figma для VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
позволяет вам просматривать и инспектировать файлы дизайна, сотрудничать с дизайнерами, отслеживать изменения и ускорять внедрение - все это, не покидая текстового редактора.
Это часть наших рекомендуемых расширений.
## Сотрудничество
1. **Использование комментариев:** Вы можете воспользоваться функцией комментариев, кликнув по значку пузыря в левой части панели инструментов.
2. **Чат курсора:** Приятная особенность Figma - это чат курсора. Просто нажмите `;` на Mac и `/` на Windows, чтобы отправить сообщение, если вы видите, что кто-то еще использует Figma одновременно с вами.