Где можно использовать фронт-компоненты
Фронт-компоненты могут отображаться в трёх местах внутри Twenty:- Боковая панель — фронт-компоненты с интерфейсом открываются в правой боковой панели. Это поведение по умолчанию, когда фронт-компонент запускается из меню команд.
- Виджеты (дашборды и страницы записей) — фронт-компоненты можно встраивать как виджеты в макеты страниц. При настройке дашборда или макета страницы записи пользователи могут добавить виджет фронт-компонента.
- Настройки приложения (App settings) — если определить фронт-компонент с помощью
defineSettingsFrontComponent(), он будет отображаться как раздел на вкладке Settings приложения, заменяя стандартный интерфейс настройки переменных.
- Связать его с элементом командного меню — регистрирует его в командном меню (Cmd+K) и, при необходимости, как закреплённое быстрое действие.
- Встроить его как виджет в макет страницы — размещает его на странице деталей записи или на дашборде.
- Определить его с помощью
defineSettingsFrontComponent()— отображает его как раздел на вкладке Settings приложения, заменяя стандартный интерфейс настройки переменных.
Простой пример
Самый быстрый способ увидеть фронт-компонент в действии — связать его сdefineCommandMenuItem, чтобы он появился как кнопка быстрого действия в правом верхнем углу страницы:
src/front-components/hello-world.tsx
src/command-menu-items/hello-world.command-menu-item.ts
yarn twenty dev (или однократного запуска yarn twenty apply) быстрое действие появится в правом верхнем углу страницы:
Нажмите её, чтобы отобразить компонент инлайн.
Поля конфигурации
Размещение фронт-компонента на странице
Помимо команд, вы можете встроить фронт-компонент непосредственно на страницу записи, добавив его как виджет в макет страницы. См. макеты страниц для подробностей.Пользовательский компонент настроек
Чтобы заменить автоматически сгенерированный интерфейс настройки переменных на вкладке Settings вашего приложения собственным компонентом, определите его с помощьюdefineSettingsFrontComponent вместо defineFrontComponent. Он использует те же поля конфигурации (за исключением isHeadless, которое не принимается, поскольку компонент настроек всегда отрисовывает видимый интерфейс пользователя) и дополнительно помечает компонент как интерфейс настроек приложения.
Компонент отрисовывается как раздел внутри вкладки Settings, а не как замена всей вкладки. Управляемые системой Twenty разделы — автообновление, App URL и подключения — всегда отрисовываются над ним и не могут быть переопределены приложением.
src/front-components/app-settings.tsx
Headless и non-headless
Фронт-компоненты поддерживают два режима отображения, управляемых опциейisHeadless:
Non-headless (по умолчанию) — компонент отображает видимый интерфейс. При запуске из меню команд он открывается в боковой панели. Это поведение по умолчанию, когда isHeadless имеет значение false или опущен.
Headless (isHeadless: true) — компонент монтируется невидимо в фоновом режиме. Он не открывает боковую панель. Компоненты headless предназначены для действий, которые выполняют логику и затем размонтируются — например, запуск асинхронной задачи, переход на страницу или показ модального окна подтверждения. Они естественно сочетаются с компонентами SDK Command, описанными ниже.
src/front-components/sync-tracker.tsx
null, Twenty пропускает рендеринг контейнера для него — в макете не появляется пустое место. Компонент по-прежнему имеет доступ ко всем хукам и API взаимодействия с хостом.
Компоненты SDK Command
Пакетtwenty-sdk предоставляет четыре вспомогательных компонента Command, предназначенных для headless фронт-компонентов. Каждый компонент выполняет действие при монтировании, обрабатывает ошибки, показывая уведомление snackbar, и автоматически размонтирует фронт-компонент по завершении.
Импортируйте их из twenty-sdk/front-component:
Command— запускает асинхронный колбэк через пропexecute.CommandLink— переходит по пути внутри приложения. Пропы:to,params,queryParams,options.CommandModal— открывает модальное окно подтверждения. Если пользователь подтвердит, выполняет колбэкexecute. Пропы:title,subtitle,execute,confirmButtonText,confirmButtonAccent.CommandOpenSidePanelPage— открывает страницу боковой панели. Пропсы зависят отpage— например,ViewRecordпринимаетrecordId+objectNameSingular(а также необязательный idtab, чтобы открыть запись на определённой вкладке), другие страницы принимаютpageTitle+pageIcon.
Command для запуска действия из меню команд:
src/front-components/run-action.tsx
src/command-menu-items/run-action.command-menu-item.ts
CommandModal для запроса подтверждения перед выполнением:
src/front-components/delete-draft.tsx
CommandOpenSidePanelPage для открытия текущей записи в боковой панели на определённой вкладке. tab — это id вкладки в макете страницы (в стандартных макетах используются id вроде company-tab-emails или company-tab-timeline; в пользовательских макетах используется собственный id вкладки). Если такого id нет в макете записи, откроется вкладка по умолчанию:
src/front-components/open-company-emails.tsx
Вызов логической функции
Front-компоненты выполняются в браузере в Web Worker, изолированном внутри iframe с непрозрачным источником (opaque-origin), в то время как логические функции выполняются на стороне сервера. Между ними нет прямого внутрипроцессного вызова — вместо этого front-компонент обращается к логической функции по HTTP. Логическая функция, объявленная сhttpRouteTriggerSettings, доступна по HTTP по своему пути маршрута. RestApiClient рассматривает пути, начинающиеся с /s/, как маршруты приложения, разрешает их в URL, по которому обслуживаются ваши функции, и аутентифицирует их с помощью TWENTY_APP_ACCESS_TOKEN.
В Twenty Cloud логические функции с HTTP-триггером обслуживаются на выделенном домене для каждого рабочего пространства по адресу https://\<your-workspace-subdomain>.withtwenty.com\<path>. Для внешних вызовов скопируйте точный URL из настроек HTTP trigger функции или на вкладке Settings приложения.
Безголовый front-компонент может выполнить вызов при монтировании через компонент Command, а затем автоматически размонтироваться:
src/front-components/sync-prs.tsx
RestApiClient, — это значение httpRouteTriggerSettings.path логической функции с префиксом /s. Сохраните isAuthRequired: true; TWENTY_APP_ACCESS_TOKEN, который Twenty выпускает для вашего компонента, аутентифицирует запрос:
src/logic-functions/fetch-prs.logic-function.ts
TWENTY_APP_ACCESS_TOKEN внедряется автоматически — см. переменные приложения. Поскольку секретные переменные приложения никогда не раскрываются front-компонентам, храните ключи API и другую конфиденциальную логику в логической функции, а не во front-компоненте.Вызов REST API Twenty
Чтобы вызывать HTTP-маршруты приложения или читать и изменять записи Twenty из фронт-компонента, используйтеRestApiClient из twenty-client-sdk/rest. Он отправляет пути вида /s/... на базовый URL функций вашего рабочего пространства, а все остальные пути, включая /rest/..., — на TWENTY_API_URL.
Он всегда действует от имени человека, просматривающего страницу. runAs: 'application' — это параметр только для логической функции: компонент никогда не получает собственный токен вашего приложения, поэтому его запрос здесь вызывает ошибку. Поместите работу, требующую собственного доступа приложения, за логическую функцию и вызывайте вместо этого её.
В
options принимаются headers, query (объект с параметрами строки запроса; значения, равные null или undefined, пропускаются) и AbortSignal через signal. Объект body, не являющийся FormData, автоматически сериализуется в JSON. При получении 401 клиент один раз обновляет токен доступа через хост и повторяет запрос.
Базовый URL и токен по умолчанию берутся из окружения. При необходимости передавайте переопределения в конструктор — например, в тестах:
RestApiClientError, который содержит status, statusText, url и распарсенное body:
Доступ к контексту времени выполнения
Внутри вашего компонента используйте хуки SDK для доступа к текущему пользователю, записи и экземпляру компонента:src/front-components/record-info.tsx
Переменные приложения
Переменные приложения, определенные вdefineApplication() с isSecret: false, доступны внутри фронтенд-компонентов через утилиту getApplicationVariable:
src/front-components/greeting.tsx
getApplicationVariable всегда возвращает строку (или undefined), независимо от объявленного для переменной type. Строка сериализуется единообразно в зависимости от типа (логические значения как "true" / "false", числа как десятичные строки, массивы / объекты как JSON) в том же формате, который используется для логической функции process.env — разбирайте её самостоятельно (Number(...), JSON.parse(...), === 'true'). См. типы переменных.
Следующие системные переменные всегда доступны через process.env:
TWENTY_FUNCTIONS_URL
Twenty также внедряет TWENTY_FUNCTIONS_URL во фронт-компоненты и логические функции: это базовый URL, по которому обслуживаются HTTP-триггерные логические функции вашего приложения.
Он существует, потому что этот URL не всегда совпадает с самим сервером Twenty. В Twenty Cloud маршруты приложения обслуживаются на выделенном домене для каждого рабочего пространства (https://\<your-workspace-subdomain>.withtwenty.com или основном общедоступном домене приложения, если он настроен), чтобы ответы, сформированные приложением, отдавались с изолированного источника, а не с источника приложения Twenty. Самостоятельно развёрнутые и локальные экземпляры обслуживают маршруты приложения с префиксом /s на самом сервере и могут вовсе не задавать эту переменную. Поскольку базовый URL различается для каждого рабочего пространства и экземпляра, ваш код не может жёстко прописать его — сервер внедряет правильное значение во время выполнения.
Вам редко нужно читать его напрямую. Вызывайте свои маршруты через RestApiClient с путём, начинающимся с /s/, и клиент разрешит URL за вас: он убирает префикс /s и обращается к TWENTY_FUNCTIONS_URL, а если переменная не задана, использует \<TWENTY_API_URL>/s. Используйте resolveUrl('/s/\<path>'), чтобы получить абсолютный URL без отправки запроса, например для ссылки. Читайте переменную напрямую только при ручной сборке URL:
API взаимодействия с хостом
Компоненты фронтенда могут вызывать навигацию, модальные окна и уведомления с помощью функций изtwenty-sdk:
Пример, который использует API хоста для показа snackbar и закрытия боковой панели после завершения действия:
src/front-components/archive-record.tsx
Хранилище
localStorage и sessionStorage работают так же, как на обычной странице, с использованием стандартного синхронного API. Ваши ключи ограничены рамками установки приложения и учетной записью вошедшего пользователя: ни одно другое приложение не может их прочитать, а другой пользователь, вошедший в тот же браузер, начинает с пустого хранилища. Значения, записанные в localStorage, остаются на устройстве при перезагрузках; sessionStorage действует в течение браузерной сессии.
src/front-components/note-draft.tsx
QuotaExceededError, как и браузерный API.
Работа с несколькими записями
ИспользуйтеuseSelectedRecordIds() для обработки нескольких выбранных записей. Это полезно для массовых операций:
src/front-components/bulk-export.tsx
src/command-menu-items/bulk-export.command-menu-item.ts
Публичные ресурсы
Компоненты фронтенда могут получать доступ к файлам из каталога приложенияpublic/ с помощью getPublicAssetUrl:
Распространение зависимостей между фронт-компонентами
По умолчанию, каждая передняя часть объединяет свою собственную копию импортируемых библиотек, так что приложение с пятью компонентами поставляет React пять раз. Объявление общих зависимостей в пакете вашего приложения. son`, чтобы собрать эти библиотеки один раз и загрузить каждый компонент приложения из одного кэшированного файла:package.json
src/front-components/counter.tsx
- Один общий набор зависимостей для каждого приложения. Пакет построен из зависимостей вашего приложения, так что вы сохраняете полный контроль над версиями, которые вы поставляете.
- Перечислите точные спецификаторы, которые вы импортируете.
twenty-ui/inputиtwenty-ui/displayявляются двумя записями; только имя пакета не содержит подпусков. Списокreactавтоматически охватываетreact/jsx-runtime. - Поделиться
react-dom/clientвместе сreact. Каждый компонент проделываетcreateRoot, поэтому оставив его, значит каждый компонент всё ещё объединяет React DOM. - **Набор кэширован. * Он обслуживается под content-hash URL с длительным неизменяемым кэшем, так что загружается один раз и повторно используется всеми компонентами приложения до тех пор, пока одна из зависимостей не изменится.
- Компоненты, импортирующие ни один из общих пакетов никогда не загружают его.
Стилизация
Компоненты фронтенда поддерживают несколько подходов к стилизации. Вы можете использовать:- Встроенные стили —
style={{ color: 'red' }} - Twenty UI components — собственная библиотека компонентов Twenty; см. раздел 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. Компоненты фронтенда могут использовать его для кнопок, тегов, статусных плашек, чипов, аватаров, иконок, типографики и токенов темы, которые автоматически соответствуют светлой и тёмной теме рабочего пространства.
Установка
Добавьте пакет в своё приложение, зафиксировав его на версии, с которой поставляется ваш экземпляр Twenty:twenty-ui включается в ваш компонент фронтенда на этапе сборки, поэтому его достаточно иметь в зависимостях вашего приложения — во время выполнения ничего настраивать не нужно.
Импорт компонентов
Импортируйте из соответствующего подпути, а не из корня пакета, чтобы в ваш бандл попали только те компоненты, которые вы используете:Иконки
Импортируйте отдельные иконки изtwenty-ui/icon:
IconsProvider, useIcons и iconsState — они подключают весь набор иконок Tabler (несколько мегабайт).
Темизация и токены темы
Компоненты Twenty UI автоматически подстраиваются под светлую и тёмную темы рабочего пространства — рендерер применяет активную цветовую схему на хосте, а компоненты вычисляют свои цвета относительно неё. Чтобы использовать те же дизайн‑токены в собственных встроенных стилях, вызовите хукuseTheme(). Он возвращает токены темы Twenty (отступы, цвета, радиусы, шрифты), привязанные к активной теме, без необходимости настраивать ThemeProvider в вашем компоненте:
useTheme() — это хук, вы читаете токены внутри тела компонента, поэтому значения всегда соответствуют активной теме. Та же карта токенов также экспортируется как константа themeCssVariables, но в компонентах фронтенда предпочтительнее использовать useTheme() — модульная константа, разыменующая themeCssVariables, может быть undefined, пока извлекается манифест приложения.
Чтобы явно разветвлять логику по активной цветовой схеме, считайте её с помощью useColorScheme() из twenty-sdk/front-component, который возвращает 'light' или 'dark'.
Нынешние ограничения
Активно разрабатываются передние компоненты. Рендеринг, стилизация, обработка событий, измерение элементов и работа с хранилищем браузера работают хорошо. Все, что выходит за рамки этих возможностей (вызов DOM‑метода на ref, отслеживание изменения размеров элементов, порталирование за пределы вашего дерева), сегодня отсутствует или реализовано не полностью, и в большинстве случаев это тихо ломается: ни исключения, ни ошибки TypeScript, поскольку обвязка типизирована под полный DOM браузера. Если один из этих блоков вас, откройте задачу, чтобы получить приоритет.Макет и измерение
Элементы могут измерять себя сами: хост отражает геометрию в песочницу, поэтому чтения выполняются локально, но могут отставать до одного кадра, а первое чтение никогда ранее не измеренного элемента возвращает нули. После записи выполните повторное чтение в колбэкеrequestAnimationFrame или в эффекте.
Позиционирование с использованием
getBoundingClientRect теперь работает, но всё, что отслеживает изменение размеров через ResizeObserver (recharts ResponsiveContainer, autoUpdate из Floating UI), по‑прежнему не работает. В любом случае отдавайте предпочтение CSS для верстки: ваша таблица стилей применяется к реальной странице, поэтому flexbox, grid, aspect-ratio, clamp() и @container работают как обычно, без задержки кадра.
requestAnimationFrame, fetch, setTimeout и queueMicrotask работают без префикса window.. Только window.requestAnimationFrame(...) и брошенные друзья.DOM access
ref дает вам элемент песочницы, а не HTMLElement.
Портал зазор поэтому всплывающие окна Radix, Headless UI, MUI и react-select не отображаются по умолчанию. Большинство из них принимают реквизиты контейнера; указывайте их на отображаемый элемент.
События
События мыши, указателя, касания, перетаскивания, клавиатуры, фокуса,input/change/submit, scroll/wheel/contextmenu и animationend/transitionend передаются хосту, плюс несколько на элемент: load/error на img, буфер обмена и композиция на input/textarea, медиа на video/audio, toggle на details/dialog. Все остальное (onAuxClick, onSelect, onInvalid, onReset, onAnimationStart, захват указателя, onLoad у img) отбрасывается без предупреждения.
document.addEventListener() и window. ddEventListener() регистрируется без ошибок и никогда не стреляет, поэтому перетаскивание останавливается, как только указатель покидает начатый элемент. event.preventDefault() тоже не пересекается; форма представления, dragover/drop и ссылки уже охраняются для вас.
Атрибуты и стиль
Каждый элемент передает свои собственные свойства хостовому DOM (href на a, src/alt на img, value/placeholder/disabled на input и так далее), плюс общий набор на каждом элементе: id, className, style, title, tabIndex, role, draggable и любой атрибут aria-* / data-* (через дефис, поэтому ariaLabel отбрасывается). Всё, что за пределами тихо отбрасывается, поэтому выражать пользовательское состояние как data-*.
CSS компонента, будь то из import './styles.css', CSS-in-JS или элемента style, внедряется в head хостовой страницы без области видимости. Таким образом, имена классов конфликтуют с собственным Twenty’s (префикс их), и никогда не пишут пустые div { ... } селекторы), а @media совпадает с вашим окном браузера вместо виджета (используйте @container с вашим собственным container-type). Встроенные свойства не затронуты.
Хранилище и сеть
localStorage и sessionStorage предоставляются Twenty, а не браузером: компонент выполняется в воркере с непрозрачным источником (origin), поэтому хост хранит значения от имени вашего приложения. См. раздел storage для получения информации об области действия и ограничениях. IndexedDB, cookies, Cache API и BroadcastChannel по-прежнему недоступны. Чтобы сохранять состояние между устройствами, вызовите логическую функцию и используйте ее хранилище ключ-значение.
fetch работает с сохранениями:
- Звонки к Twenty API и маршрутам вашего приложения проксируются узлом, поэтому предпочитайте
RestApiClient. В случае проксируемых вызововAbortSignalи другие опцииRequestInitудаляются, и поддерживаются только телаstringиURLSearchParams. - Другие источники оставляют песчаник с
Origin: null, так что сторонний API отвечает, только если он посылаетAccess-Control-Allow-Origin: *. Вызовите его из логической функции. fetch('/rest/people')никогда не совпадает с двадцать API, потому что песочница не имеет URL страницы для разрешения относительного пути.
Захват мультимедиа
navigator.mediaDevices.getUserMedia() и MediaRecorder работают внутри фронт-компонентов благодаря полифиллам песочницы, поэтому стандартный код записи выполняется без изменений, а MediaRecorder.isTypeSupported возвращает true для распространенных комбинаций контейнер/кодек. Подробные объекты ограничений getUserMedia принимаются, но не передаются дальше — хост выполняет захват с настройками по умолчанию для запрошенных типов — и одновременно во всех приложениях может быть активен только один захват. Сохраните записанный Blob с помощью хост-функции uploadFile.
Другие пробелы
- Содержимое файла. Элемент
inputс типомfileпредоставляет вашему обработчику только метаданные файла, а не байты, поэтомуFileReaderнедоступен. Чтобы загрузитьBlob, который уже есть в вашем коде (например, созданныйMediaRecorder), используйте хост-функциюuploadFile. - Перетащите приложения. Перетаскивайте события, но
event.dataTransferнеопределённый. - Узел встроенный
fs,pathиnode:cryptoне срабатывают сборки, так что переместитесь с помощью logic функции. Веб-криптовалюты,fetch,TextEncoderиURL. iframeвсегда повторно изолируется безallow-same-origin, поэтому встраиваемый контент, зависящий от собственной сессии, отображается как неавторизованный. У него также нетonLoad.