Skip to main content
I componenti front-end sono componenti React che vengono renderizzati direttamente all’interno della UI di Twenty. Vengono eseguiti in un Web Worker isolato utilizzando Remote DOM — il tuo codice viene eseguito all’interno di un iframe con origine opaca e in sandbox, ma la sua interfaccia utente continua a essere renderizzata in modo nativo nella pagina invece di essere confinata in quell’iframe.
I componenti Front sono ancora in fase di sviluppo attivo. Il tuo codice viene eseguito su un DOM parziale, non su una vera pagina del browser, quindi gli utilizzi avanzati possono fallire, spesso in modo silenzioso. Vedi Limitazioni attuali.

Dove possono essere utilizzati i componenti front-end

I componenti front-end possono essere renderizzati in tre posizioni all’interno di Twenty:
  • Pannello laterale — I componenti front-end non headless si aprono nel pannello laterale destro. Questo è il comportamento predefinito quando un componente front-end viene avviato dal menu comandi.
  • Widget (dashboard e pagine dei record) — I componenti front possono essere incorporati come widget all’interno dei layout di pagina. Quando si configura una dashboard o il layout di una pagina record, gli utenti possono aggiungere un widget del componente front-end.
  • Impostazioni dell’app — Definito con defineSettingsFrontComponent(), il componente front-end viene renderizzato come una sezione all’interno della scheda Settings dell’app, al posto dell’interfaccia utente predefinita per la configurazione delle variabili.
Un componente front da solo non è raggiungibile dall’interfaccia utente: devi renderlo visibile. I tre modi per farlo sono:
  • Associarlo a un elemento di menu dei comandi — lo registra nel menu dei comandi (Cmd+K) e, facoltativamente, come azione rapida fissata.
  • Incorporarlo come widget in un layout di pagina — lo posiziona nella pagina dei dettagli di un record o in una dashboard.
  • Definirlo con defineSettingsFrontComponent() — lo renderizza come una sezione all’interno della scheda Settings dell’app, al posto dell’interfaccia utente predefinita per la configurazione delle variabili.

Esempio di base

Il modo più rapido per vedere un componente front in azione è associarlo a defineCommandMenuItem, così appare come pulsante di azione rapida nell’angolo in alto a destra della pagina:
src/front-components/hello-world.tsx
src/command-menu-items/hello-world.command-menu-item.ts
Dopo la sincronizzazione con yarn twenty dev (o eseguendo una volta sola yarn twenty apply), l’azione rapida appare nell’angolo in alto a destra della pagina:
Fai clic per renderizzare il componente in linea.

Campi di configurazione

Posizionare un componente front-end su una pagina

Oltre ai comandi, puoi incorporare un componente front-end direttamente in una pagina record aggiungendolo come widget in un layout di pagina. Vedi Layout di pagina per i dettagli.

Componente delle impostazioni personalizzato

Per sostituire l’interfaccia utente di configurazione delle variabili generata automaticamente nella scheda Settings della tua app con il tuo componente, definiscilo con defineSettingsFrontComponent invece di defineFrontComponent. Utilizza gli stessi campi di configurazione (tranne isHeadless, che non è accettato poiché un componente delle impostazioni renderizza sempre un’interfaccia utente visibile) e inoltre contrassegna il componente come l’interfaccia delle impostazioni dell’app. Il componente viene renderizzato come una sezione all’interno della scheda Settings, non come una sostituzione dell’intera scheda. Le sezioni gestite dal sistema di Twenty — aggiornamento automatico, URL dell’app e connessioni — vengono sempre renderizzate sopra di essa e non possono essere sovrascritte dall’app.
src/front-components/app-settings.tsx
È consentito un solo componente di front-end delle impostazioni per app; dichiararne più di uno fa fallire la build. Quando presente, la scheda Settings dell’app renderizza questo componente al posto dell’interfaccia utente di configurazione delle variabili predefinita.

Headless vs non headless

I componenti front-end prevedono due modalità di rendering controllate dall’opzione isHeadless: Non headless (predefinito) — Il componente renderizza un’interfaccia utente visibile. Quando viene avviato dal menu comandi, si apre nel pannello laterale. Questo è il comportamento predefinito quando isHeadless è false o omesso. Headless (isHeadless: true) — Il componente viene montato in modo invisibile in background. Non apre il pannello laterale. I componenti headless sono pensati per azioni che eseguono una logica e poi si smontano — ad esempio, eseguire un’attività asincrona, navigare a una pagina o mostrare una finestra modale di conferma. Si abbinano naturalmente ai componenti Command dell’SDK descritti di seguito.
src/front-components/sync-tracker.tsx
Poiché il componente restituisce null, Twenty evita di renderizzare un contenitore per esso — non appare alcuno spazio vuoto nel layout. Il componente ha comunque accesso a tutti gli hook e all’API di comunicazione con l’host.

Componenti Command dell’SDK

Il pacchetto twenty-sdk fornisce quattro componenti di supporto Command progettati per i componenti front-end headless. Ogni componente esegue un’azione al montaggio, gestisce gli errori mostrando una notifica snackbar e smonta automaticamente il componente front-end al termine. Importali da twenty-sdk/front-component:
  • Command — Esegue una callback asincrona tramite la prop execute.
  • CommandLink — Naviga verso un percorso dell’app. Props: to, params, queryParams, options.
  • CommandModal — Apre una finestra modale di conferma. Se l’utente conferma, esegue la callback execute. Props: title, subtitle, execute, confirmButtonText, confirmButtonAccent.
  • CommandOpenSidePanelPage — Apre una pagina del pannello laterale. Le props dipendono da page — ad esempio ViewRecord accetta recordId + objectNameSingular (più un id tab opzionale per aprire il record in una scheda specifica), altre pagine accettano pageTitle + pageIcon.
Ecco un esempio completo di componente front-end headless che usa Command per eseguire un’azione dal menu comandi:
src/front-components/run-action.tsx
src/command-menu-items/run-action.command-menu-item.ts
E un esempio che usa CommandModal per chiedere conferma prima di eseguire:
src/front-components/delete-draft.tsx
Ed ecco un esempio che usa CommandOpenSidePanelPage per aprire il record corrente nel pannello laterale in una scheda specifica. tab è un id di scheda di layout della pagina (i layout predefiniti usano id come company-tab-emails o company-tab-timeline; i layout personalizzati usano l’id della scheda stessa). Se l’id non esiste nel layout del record, viene invece aperta la scheda predefinita:
src/front-components/open-company-emails.tsx

Chiamare una funzione logica

I componenti front vengono eseguiti lato browser in un Web Worker in sandbox all’interno di un iframe con origine opaca, mentre le funzioni logiche vengono eseguite lato server. Non esiste una chiamata diretta in-process tra i due; invece, un front component chiama una funzione logica tramite HTTP. Una funzione logica dichiarata con httpRouteTriggerSettings è raggiungibile tramite HTTP al relativo percorso della route. RestApiClient tratta i percorsi che iniziano con /s/ come route dell’app, li risolve nell’URL da cui vengono servite le tue funzioni e li autentica con TWENTY_APP_ACCESS_TOKEN.
Su Twenty Cloud, le funzioni logiche attivate tramite HTTP sono servite su un dominio dedicato per ogni workspace in https://\<your-workspace-subdomain>.withtwenty.com\<path>. Per i chiamanti esterni, copia l’URL esatto dalle impostazioni del trigger HTTP della funzione o dalla scheda Settings dell’applicazione.
Un front component headless può eseguire la chiamata al mount tramite il componente Command, quindi smontarsi automaticamente:
src/front-components/sync-prs.tsx
Il percorso passato a RestApiClient è la proprietà httpRouteTriggerSettings.path della funzione di logica, con prefisso /s. Mantieni isAuthRequired: true; il TWENTY_APP_ACCESS_TOKEN che Twenty genera per il tuo componente autentica la richiesta:
src/logic-functions/fetch-prs.logic-function.ts
TWENTY_APP_ACCESS_TOKEN viene inserito automaticamente — vedi Variabili dell’applicazione. Poiché le variabili di applicazione segrete non vengono mai esposte ai front component, mantieni le chiavi API e altra logica sensibile all’interno della funzione logica, non nel front component.

Chiamare le Twenty REST API

Per chiamare le route HTTP dell’app o leggere e scrivere record di Twenty da un front component, usa RestApiClient da twenty-client-sdk/rest. Invia i percorsi /s/... alla base URL delle funzioni del tuo workspace e tutti gli altri percorsi, inclusi /rest/..., a TWENTY_API_URL. Agisce sempre come la persona che visualizza la pagina. runAs: 'application' è un’opzione esclusiva delle funzioni logiche: un componente non riceve mai il token della tua applicazione, quindi richiederlo qui genera un errore. Inserisci il lavoro che richiede l’accesso dell’app dietro una funzione logica e chiama invece quella. options accetta headers, query (un record di parametri della query string; i valori nullish vengono ignorati) e un AbortSignal tramite signal. Un oggetto body non-FormData viene serializzato automaticamente in JSON. In caso di 401, il client aggiorna il token di accesso una volta tramite l’host e ritenta la richiesta. L’URL di base e il token vengono risolti dall’ambiente per impostazione predefinita. Passa gli override al costruttore quando necessario — per esempio nei test:
Le richieste non riuscite generano un RestApiClientError che espone status, statusText, url e il body analizzato:

Accesso al contesto di runtime

All’interno del tuo componente, usa gli hook dell’SDK per accedere all’utente corrente, al record e all’istanza del componente:
src/front-components/record-info.tsx
Hook disponibili:

Variabili dell’applicazione

Le variabili dell’applicazione definite in defineApplication() con isSecret: false sono disponibili all’interno dei componenti front-end tramite l’utility getApplicationVariable:
src/front-components/greeting.tsx
Le variabili segrete (isSecret: true) non sono esposte ai componenti front-end. Sono disponibili solo nelle funzioni logiche, che vengono eseguite lato server. Questo impedisce che valori sensibili come le chiavi API vengano inviati al browser.
getApplicationVariable restituisce sempre una stringa (o undefined), indipendentemente dal type dichiarato della variabile. La stringa viene serializzata in modo coerente in base al tipo (booleani come "true" / "false", numeri come stringhe decimali, array / oggetti come JSON), lo stesso formato usato per la logic-function process.env — esegui il parsing manualmente (Number(...), JSON.parse(...), === 'true'). Vedi Tipi di variabili. Le seguenti variabili di sistema sono sempre disponibili tramite process.env:

TWENTY_FUNCTIONS_URL

Twenty inserisce anche TWENTY_FUNCTIONS_URL nei front component e nelle funzioni di logica: la base URL da cui vengono servite le funzioni di logica attivate tramite HTTP della tua app. Esiste perché quell’URL non coincide sempre con il server di Twenty stesso. Su Twenty Cloud, le route dell’app sono servite su un dominio dedicato per ogni workspace (https://\<your-workspace-subdomain>.withtwenty.com, oppure il dominio pubblico primario dell’applicazione quando è configurato) in modo che le risposte create dall’app vengano eseguite in un’origine isolata invece che nell’origine dell’app Twenty. Le istanze self-hosted e locali servono le route dell’app con il prefisso /s direttamente sul server e potrebbero non impostare affatto la variabile. Dato che la base URL varia per workspace e per istanza, il tuo codice non può codificarla in modo statico — il server inserisce il valore corretto a runtime. Raramente devi leggerla direttamente. Chiama le tue route tramite RestApiClient con un percorso con prefisso /s/ e il client risolve l’URL per te: rimuove il prefisso /s e punta a TWENTY_FUNCTIONS_URL, usando \<TWENTY_API_URL>/s come fallback quando la variabile non è impostata. Usa resolveUrl('/s/\<path>') per ottenere l’URL assoluto senza inviare una richiesta, ad esempio per un link. Leggi direttamente la variabile solo quando costruisci manualmente un URL:

API di comunicazione con l’host

I componenti front-end possono attivare navigazione, modali e notifiche utilizzando funzioni da twenty-sdk: Ecco un esempio che usa l’API host per mostrare una snackbar e chiudere il pannello laterale dopo il completamento di un’azione:
src/front-components/archive-record.tsx

Archiviazione

localStorage e sessionStorage funzionano come in una pagina normale, con l’API sincrona standard. Le tue chiavi sono limitate all’installazione della tua app e all’utente connesso: nessun’altra app può leggerle e un altro utente che effettua l’accesso dallo stesso browser parte da un archivio vuoto. I valori scritti in localStorage rimangono sul dispositivo tra un ricaricamento e l’altro; sessionStorage dura per l’intera sessione del browser.
src/front-components/note-draft.tsx
Twenty memorizza i valori per conto della tua app, quindi una scrittura viene applicata immediatamente in locale e salvata in background. Le letture non attendono mai l’host. Non viene sincronizzato nulla: i valori non seguono l’utente su un altro browser o macchina, quindi utilizza il key-value store di una logic function per qualsiasi cosa che debba sopravvivere a un cambio di dispositivo. Le scritture sono limitate e ogni limite conta i caratteri anziché i byte: le chiavi sono lunghe al massimo 512 caratteri, un singolo valore al massimo 262.144 caratteri e ogni archivio al massimo 1.048.576 caratteri per app e utente. Una scrittura che supera un limite genera un QuotaExceededError, come nell’API del browser.

Lavorare con più record

Usa useSelectedRecordIds() per gestire più record selezionati. Questo è utile per operazioni in blocco:
src/front-components/bulk-export.tsx
Mostralo con una voce del menu dei comandi limitata alle selezioni di record:
src/command-menu-items/bulk-export.command-menu-item.ts

Asset pubblici

I componenti front-end possono accedere ai file dalla directory public/ dell’app utilizzando getPublicAssetUrl:
Vedi la sezione sugli asset pubblici per i dettagli.

Condivisione delle dipendenze tra front component

Per impostazione predefinita, ogni front component include nel bundle la propria copia delle librerie che importa, quindi un’app con cinque componenti distribuisce React cinque volte. Dichiara le dipendenze condivise nel package.json della tua app per creare il bundle di queste librerie una sola volta e fare in modo che ogni componente dell’app le carichi da un unico file in cache:
package.json
Ogni componente importa quindi le sue dipendenze esattamente come prima — non cambia nulla nel codice del tuo componente:
src/front-components/counter.tsx
Alcune cose da sapere:
  • Un solo bundle di dipendenze condivise per app. Il bundle è costruito a partire dalle dipendenze della tua app, così mantieni il pieno controllo delle versioni che distribuisci.
  • Elenca gli specificatori esatti che importi. twenty-ui/input e twenty-ui/display sono due voci; il solo nome del pacchetto non copre i suoi sottopercorsi. L’elenco di react copre automaticamente react/jsx-runtime.
  • Condividi react-dom/client insieme a react. Ogni componente effettua il rendering tramite createRoot, quindi ometterlo significa che ogni componente include ancora nel bundle React DOM.
  • Il bundle è in cache. È servito tramite un URL con content-hash con una cache immutabile a lunga durata, quindi viene scaricato una sola volta e riutilizzato in tutti i componenti dell’app finché una delle sue dipendenze non cambia.
  • I componenti che non importano nessuno dei pacchetti condivisi non lo scaricano mai.

Stile

I componenti front-end supportano diversi approcci di styling. Puoi usare:
  • Stili inlinestyle={{ color: 'red' }}
  • Componenti di Twenty UI — la libreria di componenti di Twenty; vedi Uso dei componenti di Twenty UI di seguito
  • Emotion — CSS-in-JS con @emotion/react
  • Styled-components — pattern styled.div
  • Tailwind CSS — classi di utilità
  • Qualsiasi libreria CSS-in-JS compatibile con React

Uso dei componenti di Twenty UI

Twenty distribuisce la sua libreria di componenti come pacchetto twenty-ui. I componenti front-end possono usarlo per pulsanti, tag, pillole di stato, chip, avatar, icone, tipografia e token del tema che si adattano automaticamente al tema chiaro e scuro dell’area di lavoro.

Installazione

Aggiungi il pacchetto alla tua app, bloccato alla versione con cui la tua istanza di Twenty viene distribuita:
twenty-ui è incluso nel tuo componente front-end in fase di build, quindi deve essere solo una dipendenza della tua app: non c’è nulla da configurare a runtime.

Importare i componenti

Importa dal sottopercorso corrispondente invece che dalla root del pacchetto, in modo che solo i componenti che usi finiscano nel tuo bundle:

Icone

Importa le singole icone da twenty-ui/icon:
Ogni icona nominata è sottoposta a tree-shaking, quindi importarne alcune aggiunge poco al tuo bundle. Evita IconsProvider, useIcons e iconsState: includono l’intero set di icone Tabler (diversi MB).

Temi e token del tema

I componenti Twenty UI si adattano automaticamente al tema chiaro e scuro dell’area di lavoro: il renderer applica lo schema di colori attivo sull’host e i componenti risolvono i loro colori rispetto ad esso. Per usare gli stessi design token nei tuoi stili inline, chiama l’hook useTheme(). Restituisce i token del tema di Twenty (spaziatura, colori, raggi, font) collegati al tema attivo, senza bisogno di configurare ThemeProvider nel tuo componente:
Poiché useTheme() è un hook, leggi i token all’interno del corpo del componente, così i valori riflettono sempre il tema attivo in tempo reale. La stessa mappa di token è anche esportata come costante themeCssVariables, ma nei componenti front-end è preferibile usare useTheme(): una costante a livello di modulo che dereferenzia themeCssVariables può essere undefined mentre il manifest dell’app viene estratto. Per diramare esplicitamente in base allo schema attivo, leggilo con useColorScheme() da twenty-sdk/front-component, che restituisce 'light' o 'dark'.

Limitazioni attuali

I componenti Front sono in fase di sviluppo attivo. Il rendering, lo styling, la gestione degli eventi, la misurazione degli elementi e lo storage del browser funzionano bene. Qualsiasi cosa che vada oltre questi aspetti (chiamare un metodo del DOM su un ref, osservare il ridimensionamento degli elementi, creare un portale al di fuori del tuo albero) oggi manca o è incompleta, e la maggior parte di queste operazioni fallisce silenziosamente: nessuna eccezione e nessun errore TypeScript, poiché lo scaffold è tipizzato contro il DOM completo del browser. Se una di queste limitazioni ti blocca, apri una issue così la sua priorità aumenta.

Layout e misurazione

Gli elementi possono misurare sé stessi: l’host rispecchia la geometria nella sandbox, quindi le letture vengono gestite localmente ma possono essere non aggiornate fino a un frame, e la prima lettura di un elemento mai misurato restituisce zeri. Dopo la scrittura, rileggi in una callback di requestAnimationFrame o in un effetto. Il posizionamento tramite getBoundingClientRect ora funziona, ma qualsiasi cosa che osservi i cambi di dimensione tramite ResizeObserver (il ResponsiveContainer di recharts, l’autoUpdate di Floating UI) continua a non funzionare. Preferisci comunque il CSS per il layout: il tuo stylesheet raggiunge la pagina reale, quindi flexbox, grid, aspect-ratio, clamp() e @container si comportano normalmente, senza lag del frame.
requestAnimationFrame, fetch, setTimeout e queueMicrotask funzionano senza il prefisso window.. Solo window.requestAnimationFrame(...) e simili generano un’eccezione.

Accesso al DOM

Un ref ti restituisce un elemento sandbox, non un HTMLElement. Il gap del portale è il motivo per cui i popover di Radix, Headless UI, MUI e react-select non renderizzano nulla per impostazione predefinita. La maggior parte accetta una prop container; indirizzala a un elemento che hai renderizzato.

Eventi

Mouse, puntatore, touch, drag, tastiera, focus, input/change/submit, scroll/wheel/contextmenu e animationend/transitionend passano all’host, più alcuni per elemento: load/error su img, appunti e composizione su input/textarea, media su video/audio, toggle su details/dialog. Tutto il resto (onAuxClick, onSelect, onInvalid, onReset, onAnimationStart, acquisizione del puntatore, onLoad su img) viene scartato senza alcun avviso. document.addEventListener() e window.addEventListener() si registrano senza errori e non vengono mai attivati, motivo per cui un drag si interrompe non appena il puntatore lascia l’elemento da cui è partito. event.preventDefault() non viene propagato nemmeno; l’invio dei form, dragover/drop e i clic sui link sono già protetti per te.

Attributi e stile

Ogni elemento inoltra le proprie proprietà al DOM host (href su a, src/alt su img, value/placeholder/disabled su input, e così via), più un insieme comune su ogni elemento: id, className, style, title, tabIndex, role, draggable e qualsiasi attributo aria-* / data-* (con trattino, quindi ariaLabel viene scartato). Qualsiasi cosa al di fuori di ciò viene silenziosamente scartata, quindi esprimi lo stato personalizzato come data-*. Il CSS del componente, che provenga da import './styles.css', da CSS-in-JS o da un elemento style, viene iniettato nell’head della pagina host senza ambito. Quindi i nomi delle classi entrano in conflitto con quelli di Twenty (aggiungi un prefisso e non scrivere mai div { ... } come selettori), e @media corrisponde alla finestra del browser piuttosto che al tuo widget (usa @container con il tuo container-type). Le prop style inline non sono interessate.

Storage e rete

localStorage e sessionStorage sono forniti da Twenty anziché dal browser: il componente viene eseguito in un worker con un’origine opaca, quindi l’host memorizza i valori per conto della tua app. Vedi storage per il loro ambito e i relativi limiti. IndexedDB, i cookie, la Cache API e BroadcastChannel restano non disponibili. Per rendere persistente lo stato tra i dispositivi, chiama una logic function e utilizza il suo key-value store. fetch funziona, con alcune avvertenze:
  • Le chiamate alla Twenty API e alle route della tua app sono proxyate dall’host, quindi preferisci RestApiClient. Nelle chiamate proxyate, AbortSignal e le altre opzioni di RequestInit vengono scartate, e sono supportati solo body di tipo string e URLSearchParams.
  • Le altre origini escono dalla sandbox con Origin: null, quindi un’API di terze parti risponde solo se invia Access-Control-Allow-Origin: *. Invece, chiamala da una logic function.
  • fetch('/rest/people') non viene mai associato alla Twenty API, perché la sandbox non ha un URL di pagina rispetto a cui risolvere un percorso relativo.

Acquisizione multimediale

navigator.mediaDevices.getUserMedia() e MediaRecorder funzionano all’interno dei front components tramite sandbox polyfill, quindi il codice di registrazione standard viene eseguito senza modifiche e MediaRecorder.isTypeSupported restituisce risultati per le combinazioni comuni di contenitore/codec. Gli oggetti di constraint dettagliati per getUserMedia vengono accettati ma non inoltrati — l’host acquisisce con i propri valori predefiniti per i tipi richiesti — e può essere attiva una sola acquisizione alla volta tra le applicazioni. Archivia un Blob registrato con la funzione host uploadFile.

Altre lacune

  • Contenuti dei file. Un input di tipo file fornisce al tuo gestore solo i metadati del file, non i byte, quindi FileReader non è disponibile. Per caricare un Blob già a disposizione del tuo codice — ad esempio uno prodotto da MediaRecorder — usa la funzione host uploadFile.
  • Payload di drag-and-drop. Gli eventi di drag vengono attivati, ma event.dataTransfer è undefined.
  • Built-in di Node. fs, path e node:crypto fanno fallire la build, quindi sposta quel lavoro in una logic function. Web Crypto, fetch, TextEncoder e URL sono disponibili.
  • iframe viene sempre nuovamente messo in sandbox senza allow-same-origin, quindi un embed che dipende dalla propria sessione viene renderizzato come disconnesso. Non ha nemmeno onLoad.