Files
twenty/packages/twenty-docs/l/ru/developers/extend/apps/layout/front-components.mdx
T
github-actions[bot] 3752247b73 i18n - docs translations (#22352)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22352?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-30 13:29:16 +02:00

620 lines
34 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Компоненты фронтенда
description: Создавайте компоненты React, которые отображаются внутри интерфейса Twenty в изолированной песочнице.
icon: window-maximize
---
Фронтенд-компоненты — это компоненты React, которые отображаются непосредственно внутри интерфейса Twenty. Они выполняются в изолированном Web Worker с использованием Remote DOM — ваш код изолирован (sandboxed), но рендерится нативно на странице, а не в iframe.
## Где можно использовать фронт-компоненты
Фронт-компоненты могут отображаться в двух местах внутри Twenty:
* **Боковая панель** — фронт-компоненты с интерфейсом открываются в правой боковой панели. Это поведение по умолчанию, когда фронт-компонент запускается из меню команд.
* **Виджеты (дашборды и страницы записей)** — фронт-компоненты можно встраивать как виджеты в [макеты страниц](/l/ru/developers/extend/apps/layout/page-layouts). При настройке дашборда или макета страницы записи пользователи могут добавить виджет фронт-компонента.
Сам по себе фронт-компонент недоступен из интерфейса — его нужно *сделать доступным*. Сделать это можно двумя способами:
* **Связать его с [элементом командного меню](/l/ru/developers/extend/apps/layout/command-menu-items)** — регистрирует его в командном меню (Cmd+K) и, при необходимости, как закреплённое быстрое действие.
* **Встроить его как виджет в [макет страницы](/l/ru/developers/extend/apps/layout/page-layouts)** — размещает его на странице деталей записи или на дашборде.
## Простой пример
Самый быстрый способ увидеть фронт-компонент в действии — связать его с [`defineCommandMenuItem`](/l/ru/developers/extend/apps/layout/command-menu-items), чтобы он появился как кнопка быстрого действия в правом верхнем углу страницы:
```tsx src/front-components/hello-world.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
const HelloWorld = () => {
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>Hello from my app!</h1>
<p>This component renders inside Twenty.</p>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
name: 'hello-world',
description: 'A simple front component',
component: HelloWorld,
});
```
```ts src/command-menu-items/hello-world.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
shortLabel: 'Hello',
label: 'Hello World',
icon: 'IconBolt',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
});
```
После синхронизации с помощью `yarn twenty dev` (или однократного запуска `yarn twenty dev --once`) быстрое действие появится в правом верхнем углу страницы:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Кнопка быстрого действия в правом верхнем углу" />
</div>
Нажмите её, чтобы отобразить компонент инлайн.
## Поля конфигурации
| Поле | Обязательно | Описание |
| --------------------- | ----------- | -------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | Да | Стабильный уникальный идентификатор для этого компонента |
| `component` | Да | Функция компонента React |
| `name` | Нет | Отображаемое имя |
| `description` | Нет | Описание того, что делает компонент |
| `isHeadless` | Нет | Установите значение `true`, если у компонента нет видимого пользовательского интерфейса (см. ниже) |
## Размещение фронт-компонента на странице
Помимо команд, вы можете встроить фронт-компонент непосредственно на страницу записи, добавив его как виджет в **макет страницы**. См. [макеты страниц](/l/ru/developers/extend/apps/layout/page-layouts) для подробностей.
## Headless и non-headless
Фронт-компоненты поддерживают два режима отображения, управляемых опцией `isHeadless`:
**Non-headless (по умолчанию)** — компонент отображает видимый интерфейс. При запуске из меню команд он открывается в боковой панели. Это поведение по умолчанию, когда `isHeadless` имеет значение `false` или опущен.
**Headless (`isHeadless: true`)** — компонент монтируется невидимо в фоновом режиме. Он не открывает боковую панель. Компоненты headless предназначены для действий, которые выполняют логику и затем размонтируются — например, запуск асинхронной задачи, переход на страницу или показ модального окна подтверждения. Они естественно сочетаются с компонентами SDK Command, описанными ниже.
```tsx src/front-components/sync-tracker.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useEffect } from 'react';
const SyncTracker = () => {
const recordId = useRecordId();
useEffect(() => {
enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
}, [recordId]);
return null;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'sync-tracker',
description: 'Tracks record views silently',
isHeadless: true,
component: SyncTracker,
});
```
Поскольку компонент возвращает `null`, Twenty пропускает рендеринг контейнера для него — в макете не появляется пустое место. Компонент по-прежнему имеет доступ ко всем хукам и API взаимодействия с хостом.
## Компоненты SDK Command
Пакет `twenty-sdk` предоставляет четыре вспомогательных компонента Command, предназначенных для headless фронт-компонентов. Каждый компонент выполняет действие при монтировании, обрабатывает ошибки, показывая уведомление snackbar, и автоматически размонтирует фронт-компонент по завершении.
Импортируйте их из `twenty-sdk/command`:
* **`Command`** — запускает асинхронный колбэк через проп `execute`.
* **`CommandLink`** — переходит по пути внутри приложения. Пропы: `to`, `params`, `queryParams`, `options`.
* **`CommandModal`** — открывает модальное окно подтверждения. Если пользователь подтвердит, выполняет колбэк `execute`. Пропы: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`.
* **`CommandOpenSidePanelPage`** — открывает страницу боковой панели. Пропсы зависят от `page` — например, `ViewRecord` принимает `recordId` + `objectNameSingular`, другие страницы принимают `pageTitle` + `pageIcon`.
Полный пример headless фронт-компонента, использующего `Command` для запуска действия из меню команд:
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { CoreApiClient } from 'twenty-sdk/clients';
const RunAction = () => {
const execute = async () => {
const client = new CoreApiClient();
await client.mutation({
createTask: {
__args: { data: { title: 'Created by my app' } },
id: true,
},
});
};
return <Command execute={execute} />;
};
export default defineFrontComponent({
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
name: 'run-action',
description: 'Creates a task from the command menu',
component: RunAction,
isHeadless: true,
});
```
```ts src/command-menu-items/run-action.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
А также пример с использованием `CommandModal` для запроса подтверждения перед выполнением:
```tsx src/front-components/delete-draft.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { CommandModal } from 'twenty-sdk/command';
const DeleteDraft = () => {
const execute = async () => {
// perform the deletion
};
return (
<CommandModal
title="Delete draft?"
subtitle="This action cannot be undone."
execute={execute}
confirmButtonText="Delete"
confirmButtonAccent="danger"
/>
);
};
export default defineFrontComponent({
universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456',
name: 'delete-draft',
description: 'Deletes a draft with confirmation',
component: DeleteDraft,
isHeadless: true,
});
```
## Вызов логической функции
Front-компоненты выполняются в браузере в изолированном Web Worker, в то время как [логические функции](/l/ru/developers/extend/apps/logic/logic-functions) выполняются на стороне сервера. Между ними нет прямого внутрипроцессного вызова — вместо этого front-компонент обращается к логической функции по HTTP.
Логическая функция, объявленная с `httpRouteTriggerSettings`, доступна по HTTP по своему пути маршрута. Twenty внедряет в воркер базовый URL, с которого обслуживаются ваши функции, в виде `TWENTY_FUNCTIONS_URL` вместе с `TWENTY_APP_ACCESS_TOKEN`, который аутентифицирует вызов. Пока что нет отдельного клиентского SDK для вызова ваших собственных функций, поэтому вызывайте их с помощью обычного `fetch`:
> **В Twenty Cloud логические функции с HTTP-триггером обслуживаются на выделенном домене для каждого рабочего пространства** по адресу `https://\<your-workspace-subdomain>.twenty.com\<path>` — именно к этому и разрешается `TWENTY_FUNCTIONS_URL`. Для внешних вызовов скопируйте точный URL из настроек **HTTP trigger** функции или на вкладке **Settings** приложения.
<Warning>
Устаревший маршрут функции `/s/` **не рекомендуется к использованию** и будет **деактивирован 2026-07-24**. Вместо этого используйте `TWENTY_FUNCTIONS_URL` (выше) и перенесите все жестко заданные URL вида `/s/` до этой даты. Маршрут `/s/` по-прежнему доступен при самостоятельном размещении (self-hosting).
</Warning>
Безголовый front-компонент может выполнить вызов при монтировании через компонент `Command`, а затем автоматически размонтироваться:
```tsx src/front-components/sync-prs.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
const SyncPrs = () => {
const execute = async () => {
await fetch(`${process.env.TWENTY_FUNCTIONS_URL}/github/fetch-prs`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TWENTY_APP_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ owner: 'twentyhq', repo: 'twenty' }),
});
};
return <Command execute={execute} />;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'sync-prs',
description: 'Triggers the fetch-prs logic function',
isHeadless: true,
component: SyncPrs,
});
```
Путь, добавляемый к `TWENTY_FUNCTIONS_URL`, — это значение `httpRouteTriggerSettings.path` логической функции. Сохраните `isAuthRequired: true`; `TWENTY_APP_ACCESS_TOKEN`, который Twenty выпускает для вашего компонента, аутентифицирует запрос:
```ts src/logic-functions/fetch-prs.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/logic-function';
const handler = async (event: RoutePayload) => {
const { owner, repo } = (event.body ?? {}) as { owner: string; repo: string };
// ...fetch from GitHub and persist records...
return { ok: true };
};
export default defineLogicFunction({
universalIdentifier: '...',
name: 'fetch-prs',
handler,
httpRouteTriggerSettings: {
path: '/github/fetch-prs',
httpMethod: 'POST',
isAuthRequired: true,
},
});
```
<Note>
`TWENTY_FUNCTIONS_URL` и `TWENTY_APP_ACCESS_TOKEN` внедряются автоматически — см. [переменные приложения](#application-variables). Поскольку секретные переменные приложения никогда не раскрываются front-компонентам, храните ключи API и другую конфиденциальную логику в логической функции, а не во front-компоненте.
</Note>
### Вызов REST API Twenty
Чтобы читать или изменять записи Twenty из фронт-компонента, используйте `RestApiClient` из `twenty-client-sdk/rest`. Он принадлежит к тому же семейству клиентов, что и `CoreApiClient` и `MetadataApiClient`, но нацелен на REST API Twenty (`/rest/...`) вместо GraphQL API, считывая базовый URL из `TWENTY_API_URL`.
| Метод | Описание |
| --------------------------------- | ----------------------------------------- |
| `get(path, options?)` | Отправляет запрос `GET` |
| `post(path, body?, options?)` | Отправляет запрос `POST` |
| `put(path, body?, options?)` | Отправляет запрос `PUT` |
| `patch(path, body?, options?)` | Отправляет запрос `PATCH` |
| `delete(path, options?)` | Отправляет запрос `DELETE` |
| `request(method, path, options?)` | Универсальный запрос с любым HTTP-методом |
В `options` принимаются `headers`, `query` (объект с параметрами строки запроса; значения, равные null или undefined, пропускаются) и `AbortSignal` через `signal`. Объект `body`, не являющийся `FormData`, автоматически сериализуется в JSON. При получении `401` клиент один раз обновляет токен доступа через хост и повторяет запрос.
Базовый URL и токен по умолчанию берутся из окружения. При необходимости передавайте переопределения в конструктор — например, в тестах:
```ts
const client = new RestApiClient({
baseUrl: 'https://myworkspace.twenty.com',
token: 'my-token',
});
```
Неудачные запросы выбрасывают `RestApiClientError`, который содержит `status`, `statusText`, `url` и распарсенное `body`:
```tsx
import { RestApiClient, RestApiClientError } from 'twenty-client-sdk/rest';
const client = new RestApiClient();
try {
const people = await client.get('/rest/people', {
query: { limit: 10 },
});
} catch (error) {
if (error instanceof RestApiClientError) {
console.error(error.status, error.body);
}
}
```
## Доступ к контексту времени выполнения
Внутри вашего компонента используйте хуки SDK для доступа к текущему пользователю, записи и экземпляру компонента:
```tsx src/front-components/record-info.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import {
useUserId,
useRecordId,
useFrontComponentId,
} from 'twenty-sdk/front-component';
const RecordInfo = () => {
const userId = useUserId();
const recordId = useRecordId();
const componentId = useFrontComponentId();
return (
<div>
<p>User: {userId}</p>
<p>Record: {recordId ?? 'No record context'}</p>
<p>Component: {componentId}</p>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012',
name: 'record-info',
component: RecordInfo,
});
```
Доступные хуки:
| Хук | Возвращает | Описание |
| --------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------- |
| `useUserId()` | `string` или `null` | ID текущего пользователя |
| `useSelectedRecordIds()` | `string[]` | Все выбранные идентификаторы записей (пустой массив, если ничего не выбрано) |
| `useRecordId()` | `string` или `null` | **Устарело.** Используйте `useSelectedRecordIds()` вместо этого |
| `useFrontComponentId()` | `string` | ID этого экземпляра компонента |
| `useColorScheme()` | `'light'` или `'dark'` | Активная цветовая схема интерфейса хоста (значение `System` уже определено) |
| `useFrontComponentExecutionContext(selector)` | различается | Доступ к полному контексту выполнения с помощью функции-селектора |
## Переменные приложения
Переменные приложения, определенные в [`defineApplication()`](/l/ru/developers/extend/apps/config/application) с `isSecret: false`, доступны внутри фронтенд-компонентов через утилиту `getApplicationVariable`:
```tsx src/front-components/greeting.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getApplicationVariable } from 'twenty-sdk/front-component';
const Greeting = () => {
const recipientName = getApplicationVariable('DEFAULT_RECIPIENT_NAME') ?? 'World';
return <p>Hello, {recipientName}!</p>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'greeting',
component: Greeting,
});
```
<Warning>
Секретные переменные (`isSecret: true`) **не** доступны фронтенд-компонентам. Они доступны только в [логических функциях](/l/ru/developers/extend/apps/logic/logic-functions), которые выполняются на стороне сервера. Это предотвращает отправку в браузер конфиденциальных значений, таких как ключи API.
</Warning>
Следующие системные переменные всегда доступны через `process.env`:
| Переменная | Описание |
| ------------------------- | ----------------------------------------------------------------------------- |
| `TWENTY_FUNCTIONS_URL` | Базовый URL, с которого обслуживаются функции HTTP-логики вашего приложения |
| `TWENTY_API_URL` | Базовый URL основного API Twenty |
| `TWENTY_APP_ACCESS_TOKEN` | Краткоживущий токен с областью действия, ограниченной ролью вашего приложения |
## API взаимодействия с хостом
Компоненты фронтенда могут вызывать навигацию, модальные окна и уведомления с помощью функций из `twenty-sdk`:
| Функция | Описание |
| ----------------------------------------------- | -------------------------------- |
| `navigate(to, params?, queryParams?, options?)` | Перейти на страницу в приложении |
| `openSidePanelPage(params)` | Открыть боковую панель |
| `closeSidePanel()` | Закрыть боковую панель |
| `openCommandConfirmationModal(params)` | Показать диалог подтверждения |
| `enqueueSnackbar(params)` | Показать всплывающее уведомление |
| `unmountFrontComponent()` | Размонтировать компонент |
| `updateProgress(progress)` | Обновить индикатор прогресса |
Пример, который использует API хоста для показа snackbar и закрытия боковой панели после завершения действия:
```tsx src/front-components/archive-record.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
const ArchiveRecord = () => {
const recordId = useRecordId();
const handleArchive = async () => {
const client = new CoreApiClient();
await client.mutation({
updateTask: {
__args: { id: recordId, data: { status: 'ARCHIVED' } },
id: true,
},
});
await enqueueSnackbar({
message: 'Record archived',
variant: 'success',
});
await closeSidePanel();
};
return (
<div style={{ padding: '20px' }}>
<p>Archive this record?</p>
<button onClick={handleArchive}>Archive</button>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678',
name: 'archive-record',
description: 'Archives the current record',
component: ArchiveRecord,
});
```
### Работа с несколькими записями
Используйте `useSelectedRecordIds()` для обработки нескольких выбранных записей. Это полезно для массовых операций:
```tsx src/front-components/bulk-export.tsx
import { defineFrontComponent, numberOfSelectedRecords } from 'twenty-sdk/define';
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
const BulkExport = () => {
const selectedRecordIds = useSelectedRecordIds();
const handleExport = async () => {
const client = new CoreApiClient();
for (const recordId of selectedRecordIds) {
await client.mutation({
updateTask: {
__args: { id: recordId, data: { exported: true } },
id: true,
},
});
}
await enqueueSnackbar({
message: `Exported ${selectedRecordIds.length} records`,
variant: 'success',
});
await closeSidePanel();
};
return (
<div style={{ padding: '20px' }}>
<p>Export {selectedRecordIds.length} selected record(s)?</p>
<button onClick={handleExport}>Export</button>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678901',
name: 'bulk-export',
description: 'Export selected records',
component: BulkExport,
command: {
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
conditionalAvailabilityExpression: numberOfSelectedRecords > 0,
},
});
```
## Публичные ресурсы
Компоненты фронтенда могут получать доступ к файлам из каталога приложения `public/` с помощью `getPublicAssetUrl`:
```tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';
const Logo = () => <img src={getPublicAssetUrl('logo.png')} alt="Logo" />;
export default defineFrontComponent({
universalIdentifier: '...',
name: 'logo',
component: Logo,
});
```
См. [раздел о публичных ресурсах](/l/ru/developers/extend/apps/config/public-assets) для подробностей.
## Стилизация
Компоненты фронтенда поддерживают несколько подходов к стилизации. Вы можете использовать:
* **Встроенные стили** — `style={{ color: 'red' }}`
* **Twenty UI components** — собственная библиотека компонентов Twenty; см. раздел [Using Twenty UI components](#using-twenty-ui-components) ниже
* **Emotion** — CSS-in-JS с `@emotion/react`
* **Styled-components** — паттерны `styled.div`
* **Tailwind CSS** — утилитарные классы
* **Любая библиотека CSS-in-JS**, совместимая с React
## Использование компонентов Twenty UI
Twenty поставляет свою библиотеку компонентов как пакет [`twenty-ui`](https://www.npmjs.com/package/twenty-ui/v/1.0.0-alpha.1). Компоненты фронтенда могут использовать его для кнопок, тегов, статусных плашек, чипов, аватаров, иконок, типографики и токенов темы, которые автоматически соответствуют светлой и тёмной теме рабочего пространства.
### Установка
Добавьте пакет в своё приложение, зафиксировав его на версии, с которой поставляется ваш экземпляр Twenty:
```bash
yarn add twenty-ui@1.0.0-alpha.1
```
`twenty-ui` включается в ваш компонент фронтенда на этапе сборки, поэтому его достаточно иметь в зависимостях вашего приложения — во время выполнения ничего настраивать не нужно.
### Импорт компонентов
Импортируйте из соответствующего подпути, а не из корня пакета, чтобы в ваш бандл попали только те компоненты, которые вы используете:
| Подпуть | Что экспортирует |
| --------------------------- | ------------------------------------------------- |
| `twenty-ui/input` | `Button` и элементы ввода формы |
| `twenty-ui/data-display` | `Tag`, `Status`, `Chip`, `Avatar` и другие |
| `twenty-ui/feedback` | `Callout`, `Banner`, `Info` и другие |
| `twenty-ui/typography` | `H1Title`, `H2Title`, `H3Title`, `Label` и другие |
| `twenty-ui/icon` | Компоненты `Icon*` (например, `IconCheck`) |
| `twenty-ui/theme-constants` | `ThemeProvider`, `themeCssVariables` |
```tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Status, Tag } from 'twenty-ui/data-display';
import { Button } from 'twenty-ui/input';
const StyledWidget = () => {
return (
<div style={{ padding: '16px', display: 'flex', gap: '8px' }}>
<Button title="Click me" onClick={() => alert('Clicked!')} />
<Tag text="Active" color="green" />
<Status color="green" text="Online" />
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456',
name: 'styled-widget',
component: StyledWidget,
});
```
### Иконки
Импортируйте отдельные иконки из `twenty-ui/icon`:
```tsx
import { IconBox, IconCheck } from 'twenty-ui/icon';
```
Каждая именованная иконка участвует в tree-shaking, поэтому импорт нескольких иконок почти не увеличит размер вашего бандла. Избегайте `IconsProvider`, `useIcons` и `iconsState` — они подключают весь набор иконок Tabler (несколько мегабайт).
### Темизация и токены темы
Компоненты Twenty UI автоматически подстраиваются под светлую и тёмную темы рабочего пространства — рендерер применяет активную цветовую схему на хосте, а компоненты вычисляют свои цвета относительно неё.
Чтобы использовать те же дизайн‑токены в собственных встроенных стилях, вызовите хук `useTheme()`. Он возвращает токены темы Twenty (отступы, цвета, радиусы, шрифты), привязанные к активной теме, без необходимости настраивать `ThemeProvider` в вашем компоненте:
```tsx
import { useTheme } from 'twenty-ui/theme-constants';
const Card = () => {
const theme = useTheme();
return (
<div
style={{
padding: theme.spacing[4],
background: theme.background.secondary,
color: theme.font.color.primary,
}}
>
Themed card
</div>
);
};
```
Поскольку `useTheme()` — это хук, вы читаете токены внутри тела компонента, поэтому значения всегда соответствуют активной теме. Та же карта токенов также экспортируется как константа `themeCssVariables`, но в компонентах фронтенда предпочтительнее использовать `useTheme()` — модульная константа, разыменующая `themeCssVariables`, может быть `undefined`, пока извлекается манифест приложения.
Чтобы явно разветвлять логику по активной цветовой схеме, считайте её с помощью `useColorScheme()` из `twenty-sdk/front-component`, который возвращает `'light'` или `'dark'`.