Ana içeriğe atla
Ön uç bileşenler, Twenty’nin UI’si içinde doğrudan görüntülenen React bileşenleridir. Remote DOM kullanan izole bir Web Worker içinde çalışırlar — kodunuz izole bir ortamda (sandbox) çalışır ancak bir iframe içinde değil, sayfada yerel olarak işlenir.

Ön uç bileşenlerinin kullanılabileceği yerler

Ön uç bileşenler, Twenty içinde iki konumda işlenebilir:
  • Yan panel — Headless olmayan ön uç bileşenler, sağ taraftaki yan panelde açılır. Bir ön uç bileşeni komut menüsünden tetiklendiğinde varsayılan davranış budur.
  • Widget’lar (panolar ve kayıt sayfaları) — Ön uç bileşenler, sayfa düzenlerine widget olarak gömülebilir. Bir pano veya kayıt sayfası düzeni yapılandırılırken kullanıcılar bir ön uç bileşen widget’ı ekleyebilir.

Basit örnek

Bir ön uç bileşenini çalışırken görmenin en hızlı yolu, onu bir komut olarak kaydetmektir. isPinned: true ile bir command alanı eklemek, sayfanın sağ üst köşesinde hızlı işlem düğmesi olarak görünmesini sağlar — herhangi bir sayfa düzenine gerek yoktur:
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,
  command: {
    universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
    shortLabel: 'Hello',
    label: 'Hello World',
    icon: 'IconBolt',
    isPinned: true,
    availabilityType: 'GLOBAL',
  },
});
yarn twenty dev ile senkronize ettikten sonra (veya tek seferlik bir yarn twenty dev --once çalıştırdıktan sonra), hızlı işlem sayfanın sağ üst köşesinde görünür:
Sağ üst köşedeki hızlı işlem düğmesi
Bileşeni satır içi işlemek için üzerine tıklayın.

Yapılandırma alanları

AlanZorunluAçıklama
universalIdentifierEvetBu bileşen için kalıcı benzersiz kimlik
componentEvetBir React bileşen fonksiyonu
nameHayırGörünen Ad
descriptionHayırBileşenin ne yaptığına dair açıklama
isHeadlessHayırBileşenin görünür bir kullanıcı arayüzü yoksa true olarak ayarlayın (aşağıya bakın)
commandHayırBileşeni bir komut olarak kaydedin (aşağıda komut seçeneklerine bakın)

Bir ön uç bileşenini bir sayfaya yerleştirme

Komutların ötesinde, bir ön uç bileşenini bir sayfa düzeninde widget olarak ekleyerek doğrudan bir kayıt sayfasına gömebilirsiniz. Ayrıntılar için definePageLayout bölümüne bakın.

Headless ve headless olmayan

Ön uç bileşenler, isHeadless seçeneğiyle kontrol edilen iki işleme kipiyle gelir: Headless olmayan (varsayılan) — Bileşen görünür bir kullanıcı arayüzü (UI) oluşturur. Komut menüsünden tetiklendiğinde yan panelde açılır. isHeadless false olduğunda veya belirtilmediğinde bu varsayılan davranıştır. Headless (isHeadless: true) — Bileşen arka planda görünmez şekilde bağlanır. Yan paneli açmaz. Headless bileşenler, mantığı çalıştırıp ardından kendilerini kaldıran eylemler için tasarlanmıştır — örneğin, bir async görevi çalıştırma, bir sayfaya gitme veya bir onay modalı gösterme. Aşağıda açıklanan SDK Command bileşenleriyle doğal olarak eşleşirler.
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,
});
Bileşen null döndürdüğü için, Twenty bunun için bir kapsayıcı oluşturmayı atlar — düzende boş alan görünmez. Bileşen yine de tüm hook’lara ve host iletişim API’sine erişime sahiptir.

SDK Command bileşenleri

twenty-sdk paketi, headless ön bileşenler için tasarlanmış dört Command yardımcı bileşeni sağlar. Her bileşen bağlandığında bir eylem yürütür, hataları bir snackbar bildirimi göstererek ele alır ve tamamlandığında ön bileşeni otomatik olarak kaldırır. Bunları twenty-sdk/command içinden içe aktarın:
  • Commandexecute prop’u aracılığıyla async bir geri çağrıyı çalıştırır.
  • CommandLink — Bir uygulama yoluna gider. Props: to, params, queryParams, options.
  • CommandModal — Bir onay modalı açar. Kullanıcı onaylarsa execute geri çağrısını yürütür. Props: title, subtitle, execute, confirmButtonText, confirmButtonAccent.
  • CommandOpenSidePanelPage — Belirli bir yan panel sayfasını açar. Props: page, pageTitle, pageIcon.
Command kullanarak komut menüsünden bir eylem çalıştıran headless bir ön bileşenin tam örneği:
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,
  command: {
    universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
    label: 'Run my action',
    icon: 'IconPlayerPlay',
  },
});
Ve yürütmeden önce onay istemek için CommandModal kullanan bir örnek:
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,
  command: {
    universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567',
    label: 'Delete draft',
    icon: 'IconTrash',
  },
});

Çalışma zamanı bağlamına erişme

Bileşeninizin içinde, geçerli kullanıcıya, kayda ve bileşen örneğine erişmek için SDK hook’larını kullanın:
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,
});
Kullanılabilir hook’lar:
HookDöndürürAçıklama
useUserId()string veya nullGeçerli kullanıcının ID’si
useRecordId()string veya nullGeçerli kaydın ID’si (bir kayıt sayfasına yerleştirildiğinde)
useFrontComponentId()stringBu bileşen örneğinin ID’si
useFrontComponentExecutionContext(selector)değişirBir seçici işlevle tam yürütme bağlamına erişin

Host iletişim API’si

Ön uç bileşenleri, twenty-sdk’deki işlevleri kullanarak gezinmeyi, modalları ve bildirimleri tetikleyebilir:
FonksiyonAçıklama
navigate(to, params?, queryParams?, options?)Uygulamada bir sayfaya git
openSidePanelPage(params)Bir yan panel aç
closeSidePanel()Yan paneli kapat
openCommandConfirmationModal(params)Bir onay iletişim kutusu göster
enqueueSnackbar(params)Bir toast bildirimi göster
unmountFrontComponent()Bileşeni kaldır (unmount)
updateProgress(progress)Bir ilerleme göstergesini güncelle
Bir eylem tamamlandıktan sonra bir snackbar göstermek ve yan paneli kapatmak için host API’sini kullanan bir örnek:
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,
});

Komut seçenekleri

defineFrontComponent içine bir command alanı eklemek, bileşeni komut menüsüne (Cmd+K) kaydeder. isPinned true ise, sayfanın sağ üst köşesinde bir hızlı işlem düğmesi olarak da görünür.
AlanZorunluAçıklama
universalIdentifierEvetKomut için kalıcı benzersiz kimlik
labelEvetKomut menüsünde (Cmd+K) gösterilen tam etiket
shortLabelHayırSabitlenmiş hızlı işlem düğmesinde görüntülenen daha kısa etiket
iconHayırEtiketin yanında görüntülenen simge adı (örn. 'IconBolt', 'IconSend')
isPinnedHayırtrue olduğunda, komutu sayfanın sağ üst köşesinde bir hızlı işlem düğmesi olarak gösterir
availabilityTypeHayırKomutun nerede görüneceğini kontrol eder: 'GLOBAL' (her zaman kullanılabilir), 'RECORD_SELECTION' (yalnızca kayıtlar seçiliyken) veya 'FALLBACK' (başka hiçbir komut eşleşmediğinde gösterilir)
availabilityObjectUniversalIdentifierHayırKomutu belirli bir nesne türünün sayfalarıyla sınırlandırın (örn. yalnızca Company kayıtlarında)
conditionalAvailabilityExpressionHayırKomutun görünür olup olmadığını dinamik olarak kontrol eden bir boolean ifade (aşağıya bakın)

Koşullu kullanılabilirlik ifadeleri

conditionalAvailabilityExpression alanı, geçerli sayfa bağlamına göre bir komutun ne zaman görünür olacağını kontrol etmenizi sağlar. İfadeler oluşturmak için twenty-sdk’den türlendirilmiş değişkenleri ve operatörleri içe aktarın:
import { defineFrontComponent } from 'twenty-sdk/define';
import {
  pageType,
  numberOfSelectedRecords,
  objectPermissions,
  everyEquals,
  isDefined,
} from 'twenty-sdk/front-component';

export default defineFrontComponent({
  universalIdentifier: '...',
  name: 'bulk-action',
  component: BulkAction,
  command: {
    universalIdentifier: '...',
    label: 'Bulk Update',
    availabilityType: 'RECORD_SELECTION',
    conditionalAvailabilityExpression: everyEquals(
      objectPermissions,
      'canUpdateObjectRecords',
      true,
    ),
  },
});
Bağlam değişkenleri — bunlar sayfanın mevcut durumunu temsil eder:
DeğişkenTürAçıklama
pageTypestringGeçerli sayfa türü (örn. 'RecordIndexPage', 'RecordShowPage')
isInSidePanelbooleanBileşenin bir yan panelde oluşturulup oluşturulmadığı
numberOfSelectedRecordsnumberŞu anda seçili kayıt sayısı
isSelectAllboolean”tümünü seç” seçeneğinin etkin olup olmadığı
selectedRecordsarraySeçili kayıt nesneleri
favoriteRecordIdsarrayFavorilere eklenen kayıtların ID’leri
objectPermissionsobjectGeçerli nesne türü için izinler
targetObjectReadPermissionsobjectHedef nesne için okuma izinleri
targetObjectWritePermissionsobjectHedef nesne için yazma izinleri
featureFlagsobjectEtkin özellik bayrakları
objectMetadataItemobjectGeçerli nesne türünün üst verileri
hasAnySoftDeleteFilterOnViewbooleanGeçerli görünümde soft-delete filtresi olup olmadığı
Operatörler — değişkenleri boolean ifadelere dönüştürmek için birleştirin:
OperatörAçıklama
isDefined(value)Değer null/undefined değilse true
isNonEmptyString(value)Değer boş olmayan bir string ise true
includes(array, value)Dizi değeri içeriyorsa true
includesEvery(array, prop, value)Her bir öğenin özelliği değeri içeriyorsa true
every(array, prop)Özellik her öğede truthy ise true
everyDefined(array, prop)Özellik her öğede tanımlıysa true
everyEquals(array, prop, value)Özellik her öğede değere eşitse true
some(array, prop)Özellik en az bir öğede truthy ise true
someDefined(array, prop)Özellik en az bir öğede tanımlıysa true
someEquals(array, prop, value)Özellik en az bir öğede değere eşitse true
someNonEmptyString(array, prop)Özellik en az bir öğede boş olmayan bir string ise true
none(array, prop)Özellik her öğede falsy ise true
noneDefined(array, prop)Özellik her öğede tanımsızsa true
noneEquals(array, prop, value)Özellik hiçbir öğede değere eşit değilse true

Genel varlıklar

Ön uç bileşenleri, getPublicAssetUrl kullanarak uygulamanın public/ dizinindeki dosyalara erişebilir:
import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define';

const Logo = () => <img src={getPublicAssetUrl('logo.png')} alt="Logo" />;

export default defineFrontComponent({
  universalIdentifier: '...',
  name: 'logo',
  component: Logo,
});
Ayrıntılar için genel varlıklar bölümüne bakın.

Stil

Ön uç bileşenleri birden fazla biçimlendirme yaklaşımını destekler. Şunları kullanabilirsiniz:
  • Satır içi stillerstyle={{ color: 'red' }}
  • Twenty UI bileşenleritwenty-sdk/ui içinden içe aktarın (Button, Tag, Status, Chip, Avatar ve daha fazlası)
  • Emotion@emotion/react ile CSS-in-JS
  • Styled-componentsstyled.div kalıpları
  • Tailwind CSS — yardımcı sınıflar
  • React ile uyumlu herhangi bir CSS-in-JS kitaplığı
import { defineFrontComponent } from 'twenty-sdk/define';
import { Button, Tag, Status } from 'twenty-sdk/ui';

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,
});