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
159 lines
9.2 KiB
Plaintext
159 lines
9.2 KiB
Plaintext
---
|
|
title: Layouts de Página
|
|
description: Personalize páginas de detalhes de registros — abas, widgets e onde os front components são renderizados — usando `definePageLayout` e `definePageLayoutTab`.
|
|
icon: table-columns
|
|
---
|
|
|
|
Um **layout de página** controla como a página de detalhes de um registro é organizada: quais abas aparecem e quais widgets elas contêm. Use `definePageLayout()` para declarar um layout para um objeto que você possui ou `definePageLayoutTab()` para adicionar uma única aba a um layout que já existe (seu ou um padrão da Twenty).
|
|
|
|
| Caso de uso | Entidade |
|
|
| ------------------------------------------------------------------------------ | --------------------- |
|
|
| Definir todo o layout para uma página de registro em um objeto que você possui | `definePageLayout` |
|
|
| Adicionar uma aba a um layout existente (seu próprio objeto ou um padrão) | `definePageLayoutTab` |
|
|
|
|
## definePageLayout
|
|
|
|
Use isto quando você possuir toda a página de detalhes — normalmente para um objeto personalizado que você próprio definiu.
|
|
|
|
```ts src/page-layouts/example-record-page-layout.ts
|
|
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define';
|
|
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
|
|
import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world';
|
|
|
|
export default definePageLayout({
|
|
universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134',
|
|
name: 'Example Record Page',
|
|
type: 'RECORD_PAGE',
|
|
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
|
tabs: [
|
|
{
|
|
universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5',
|
|
title: 'Hello World',
|
|
position: 50,
|
|
icon: 'IconWorld',
|
|
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
|
|
widgets: [
|
|
{
|
|
universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d',
|
|
title: 'Hello World',
|
|
type: 'FRONT_COMPONENT',
|
|
configuration: {
|
|
configurationType: 'FRONT_COMPONENT',
|
|
frontComponentUniversalIdentifier:
|
|
HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
```
|
|
|
|
### Pontos-chave
|
|
|
|
* `type` é um dos seguintes: `'RECORD_INDEX'`, `'RECORD_PAGE'`, `'DASHBOARD'` ou `'STANDALONE_PAGE'`. Use `'RECORD_PAGE'` para personalizar a visualização de detalhes de um objeto específico.
|
|
* `objectUniversalIdentifier` especifica a qual objeto este layout se aplica.
|
|
* Cada `tab` define uma seção da página com um `title`, `position` e `layoutMode`: `VERTICAL_LIST` para páginas de registro e páginas independentes, `GRID` para dashboards e `CANVAS` para um único widget que deve preencher a janela de visualização da aba. Uma aba `VERTICAL_LIST` empilha widgets verticalmente. Widgets integrados que gerenciam sua própria rolagem, como linhas do tempo, arquivos, notas, tarefas e fluxos de trabalho, preenchem uma janela de visualização; campos, componentes de front, gráficos e outros widgets fit-content são renderizados na altura de seu conteúdo ou na altura configurada. Uma aba `GRID` sempre organiza seus widgets como cartões em uma grade de 12 colunas. Um widget `CANVAS` não tem posição explícita; se uma aba de canvas contiver vários widgets, eles serão renderizados na altura do conteúdo, em vez de preencherem a viewport.
|
|
* Defina `layoutMode` explicitamente. Omití-lo faz com que você obtenha `VERTICAL_LIST` em uma `STANDALONE_PAGE` e `GRID` em todos os outros casos, o que raramente é o que você deseja em uma página de registro.
|
|
* Cada `widget` dentro de uma aba pode renderizar um [front component](/l/pt/developers/extend/apps/layout/front-components), uma lista de relações ou outros tipos de widget nativos.
|
|
* Um widget `FRONT_COMPONENT` pode definir `headerCommandMenuItemUniversalIdentifiers` como uma matriz ordenada de identificadores universais de itens de menu de comandos do mesmo app. Essas ações aparecem como botões de ícone no cabeçalho do cartão do widget e mantêm suas verificações de disponibilidade e permissões no nível do comando. Os identificadores devem ser exclusivos e devem ser resolvidos quando o app for instalado.
|
|
* `position` nas abas controla sua ordem. Use valores mais altos (por exemplo, 50) para colocar abas personalizadas após as nativas.
|
|
|
|
### Widgets de campos
|
|
|
|
Um widget `FIELD` renderiza um campo do registro. Para campos de relação, ele também pode incorporar uma lista de registros relacionados:
|
|
|
|
```ts
|
|
{
|
|
universalIdentifier: 'c1c2c3c4-c5c6-4000-8000-000000000003',
|
|
title: 'People → Opportunities',
|
|
type: 'FIELD',
|
|
configuration: {
|
|
configurationType: 'FIELD',
|
|
fieldMetadataId: PEOPLE_FIELD_UNIVERSAL_IDENTIFIER,
|
|
fieldDisplayMode: 'TABLE',
|
|
nestedRelationFieldMetadataId: OPPORTUNITIES_FIELD_UNIVERSAL_IDENTIFIER,
|
|
},
|
|
}
|
|
```
|
|
|
|
* `fieldMetadataId` recebe o identificador universal de um campo no objeto do layout.
|
|
* `fieldDisplayMode` é um dentre `'FIELD'`, `'CARD'`, `'EDITOR'`, `'VIEW'` ou `'TABLE'`. `TABLE` incorpora uma visualização que lista os registros de um campo de relação um-para-muitos.
|
|
* `nestedRelationFieldMetadataId` é opcional e recebe o identificador universal de um campo de relação um-para-muitos no objeto de destino da relação, para listar registros a dois saltos de relação (por exemplo, uma página de Empresa listando as oportunidades das pessoas da empresa, ou uma página de Pessoa listando as oportunidades da empresa da pessoa). O primeiro salto pode ser um campo de relação um-para-muitos ou muitos-para-um, o segundo deve ser um-para-muitos (relações de junção não são compatíveis) e isso requer `fieldDisplayMode: 'TABLE'` — combiná-lo com qualquer outro modo de exibição é um erro de validação, já que um widget aninhado sempre é renderizado como uma visualização incorporada.
|
|
|
|
## definePageLayoutTab
|
|
|
|
Use isto quando você quiser apenas **adicionar** uma aba a um layout existente — por exemplo, uma aba de analytics na página padrão de Company ou uma aba de resumo de IA anexada ao layout do seu próprio objeto.
|
|
|
|
```ts src/page-layouts/example-extra-tab.ts
|
|
import {
|
|
definePageLayoutTab,
|
|
PageLayoutTabLayoutMode,
|
|
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS,
|
|
} from 'twenty-sdk/define';
|
|
import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world';
|
|
|
|
export default definePageLayoutTab({
|
|
universalIdentifier: 'b1b2b3b4-b5b6-4000-8000-000000000001',
|
|
pageLayoutUniversalIdentifier:
|
|
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.companyRecordPage
|
|
.universalIdentifier,
|
|
title: 'Hello World',
|
|
position: 1000,
|
|
icon: 'IconWorld',
|
|
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
|
|
widgets: [
|
|
{
|
|
universalIdentifier: 'b1b2b3b4-b5b6-4000-8000-000000000002',
|
|
title: 'Hello World',
|
|
type: 'FRONT_COMPONENT',
|
|
configuration: {
|
|
configurationType: 'FRONT_COMPONENT',
|
|
frontComponentUniversalIdentifier:
|
|
HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
```
|
|
|
|
### Pontos-chave
|
|
|
|
* `pageLayoutUniversalIdentifier` é **obrigatório** e deve apontar para um layout de página que já exista no momento da instalação — seja um layout padrão da Twenty ou um definido pelo seu próprio aplicativo. Referências entre aplicativos para layouts pertencentes a outro aplicativo instalado não são compatíveis atualmente. Quando o layout pai estiver ausente, a instalação falha com um erro de validação claro.
|
|
|
|
* Para layouts padrão do Twenty, importe identificadores de `twenty-sdk/define`:
|
|
|
|
```ts
|
|
import { STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
|
|
|
|
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.companyRecordPage.universalIdentifier
|
|
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.personRecordPage.universalIdentifier
|
|
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.universalIdentifier
|
|
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.opportunityRecordPage.universalIdentifier
|
|
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.noteRecordPage.universalIdentifier
|
|
// …
|
|
```
|
|
|
|
Cada entrada de layout também expõe suas `tabs` e seus `widgets`, para que você possa fazer referência a qualquer nível:
|
|
|
|
```ts
|
|
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.tabs.home
|
|
.universalIdentifier;
|
|
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.tabs.home.widgets
|
|
.fields.universalIdentifier;
|
|
```
|
|
|
|
Um alias abreviado `STANDARD_PAGE_LAYOUT` também está disponível:
|
|
|
|
```ts
|
|
import { STANDARD_PAGE_LAYOUT } from 'twenty-sdk/define';
|
|
|
|
STANDARD_PAGE_LAYOUT.companyRecordPage.universalIdentifier;
|
|
```
|
|
|
|
* `widgets` têm escopo apenas para esta aba — eles referenciam [front components](/l/pt/developers/extend/apps/layout/front-components), visualizações etc., exatamente como widgets definidos inline em `definePageLayout`.
|
|
|
|
* `position` controla a ordenação em relação às abas existentes no layout de destino. Escolha um valor que posicione sua aba onde você deseja em relação às abas nativas.
|
|
|
|
* Use isto em vez de `definePageLayout` quando você quiser apenas adicionar a um layout existente. Use `definePageLayout` quando você possuir todo o layout.
|