İçeriğe geç
Docs

Bileşen Yapılandırması

StudioConfig ve ComponentConfig başvurusu, defineSection/defineElement, <Slot> ve <TecofRoot>, kök className sözleşmesi, dinamik alanlar, sürükle-bırak kuralları, varyantlar ve varsayılan çocuklar.

Son güncelleme:

Studio’nun hangi blokları göstereceğini, blokların hangi alanlarla düzenleneceğini ve nasıl render edileceğini StudioConfig belirler. Editör ve yayın renderer’ı aynı nesneyi kullanmalıdır; kayıtlı bir type yayındaki config’te yoksa o düğüm sessizce render edilmez.

StudioConfig#

AlanTipAçıklama
componentsRecord<string, ComponentConfig>Zorunlu. Anahtar, dokümanda type olarak saklanır
categoriesRecord<string, { title?, components? }>Bileşen kütüphanesindeki kategoriler ve sırası
root{ fields?, render? }Sayfa geneli alanlar (Inspector → Sayfa) ve sayfa sarmalayıcısı (render, children alır)
templatesSectionTemplate[]Tek bölüm şablonları (Slotlar ve şablonlar)
pageTemplatesPageTemplate[]Çok bölümlü sayfa şablonları
themeDeepPartialThemeConfigTemanın varsayılan renk/tipografi/boşluk değerleri (Canlı tema)
themePresetsThemePreset[]Tema panelinde hazır renk paletleri
darkModeboolean | { defaultMode?, storageKey? }Karanlık modu açar (Canlı tema)
permissionsPartial<Permissions>Genel izinler (İzinler)
migrationsMigrationConfigEski kayıtları yükseltme (İzinler ve migration)
ai{ complete({ system, prompt }) }AI ile bölüm üretme (Studio)
nodeInfoButtonbooleanSeçim araç çubuğunda ⓘ bileşen bilgisi düğmesi; varsayılan kapalı

ComponentConfig#

Yalnız render zorunludur.

AlanTipAçıklama
render(props) => ReactNodeEditörde ve yayında kullanılan render
labelstring?Kütüphanede görünen ad
categorystring?Kategori anahtarı
fieldsRecord<string, FieldConfig>Inspector alanları (Alanlar)
fieldsGroups{ name, fields[] }[]Alanları akordeon gruplarına ayırır; listelenmeyenler grupların ardından düz gösterilir
defaultPropsobject?Yeni düğümün başlangıç değerleri
defaultChildrenRecord<slot, Array<type | { type, props? }>>Eklenince slotlara otomatik konan çocuklar (özyinelemeli, döngü korumalı)
variantsRecord<string, { label, props }>İsimli prop ön ayarları: Bölüm Ekle’de ayrı kart, Inspector’da çip
inlineboolean?Editörün sarmalayıcı div’ini kaldırır; bileşen puck.dragRef’i köke bağlamalıdır
acceptsChildrenboolean | string[] | Record<slot, boolean | string[]>Hangi çocuk tiplerini kabul ettiği (bileşen ya da slot bazında)
maxItemsnumber | Record<slot, number>Doğrudan çocuk üst sınırı (bileşen ya da slot bazında)
allowedParentsstring[]Bu bileşenin bırakılabileceği üst tipler; doluysa kök içeriğe bırakılamaz
resizableboolean?Boyutlandırma modunda genişlik/yükseklik tutamaçları (varsayılan true)
permissions / resolvePermissions—Bileşen ve düğüm bazlı izinler
resolveFields(props, ctx) => fieldsProp’lara göre dinamik alan seti (sync/async)
resolveData(props, ctx) => { props?, readOnly? }Türetilmiş prop’lar ve salt okunur alanlar (sync/async)
metadataobject?Host’a özel veri; render’da puck.metadata içinde

Render fonksiyonuna gelen prop’lar: düğüm prop’ları, editörün birleştirdiği className, slot alanları için hazır slot elemanları, editMode ve puck (renderDropZone, isEditing, metadata, düzenlemede registerOverlayPortal / dragRef).

Kök className sözleşmesi#

Editör seçim, hover ve stil sınıflarını bileşenin kök DOM elemanına className üzerinden ulaştırır. Bir bileşen props.className’i kökte basmazsa kanvasta seçilemez ve Stil sekmesinde verilen stiller görünmez.

TSX
// Yanlış: className kaybolur, düğüm seçilemez
render: (p) => <section className="py-16">{p.title}</section>

// Doğru
render: (p) => <section className={cn("py-16", p.className)}>{p.title}</section>

Bileşenin her dalı (ör. erken return ile boş durum) aynı sözleşmeye uymalıdır. defineSection / defineElement bunu otomatik yapar; elle yazarken TecofRoot kullanabilirsiniz:

TSX
import { TecofRoot } from "@tecof/theme-editor";

render: (p) => (
  <TecofRoot as="section" className={cn("py-16", p.className)}>
    {p.title}
  </TecofRoot>
)

defineSection ve defineElement#

Bildirimsel bir tanımdan düz bir ComponentConfig üretir:

  • slots tek yerde tanımlanır; hem type: "slot" alanları hem defaultChildren buradan türetilir,
  • render’a ikinci argüman olarak hazır <Slot> elemanları (slots.<ad>) verilir,
  • render’ın döndürdüğü kök elemanın className’ine p.className eklenir.
TSX
import { createLanguageField, defineSection } from "@tecof/theme-editor";

export const FAQSection = defineSection({
  label: "Sıkça Sorulan Sorular",
  category: "sections",
  slots: {
    content: { label: "Başlık ve açıklama", layout: "col", gap: "md", default: ["Title", "Paragraph"] },
    items: {
      label: "Sorular",
      layout: "col",
      gap: "sm",
      hideIfEmpty: true,
      className: (p) => p.compact && "max-w-2xl mx-auto",
      default: [{ type: "FAQItem" }, { type: "FAQItem" }],
    },
  },
  fields: {
    compact: { type: "toggle", label: "Dar düzen" },
    eyebrow: createLanguageField({ label: "Üst başlık" }),
  },
  defaultProps: { compact: false },
  render: (p, slots) => (
    <section className="py-16 lg:py-24">
      <div className="mx-auto max-w-4xl px-4">
        {slots.content}
        <div className="mt-10">{slots.items}</div>
      </div>
    </section>
  ),
});
slots.<ad> seçeneğiAçıklama
labelInspector’daki slot adı
layoutrow · row-wrap · col · contents · none; row/row-wrap slot alanını yatay (orientation: "horizontal") yapar
gapnone · xs .25 · sm .5 · md .75 · lg 1 · xl 1.5 rem ya da ham değer ("1.25rem")
classNameKapsayıcıya ek sınıf; fonksiyon verilirse her render’da prop’larla çağrılır
hideIfEmptyYayında slot boşsa kapsayıcıyı hiç basma (editörde boş slot “Bileşen ekle” ipucuyla görünür kalır)
defaultEklenince otomatik doğan çocuklar
fieldExtraSlot alanına eklenecek ek ayarlar (ör. { repeatSource: "items" })
allow, maxItemsSlot alanına yazılır — aşağıdaki nota bakın

Slot bileşeni#

<Slot> bir slot değerini tek satırda çizer ve yerleşimi tek kaynaktan (layout + gap) yönetir. defineSection kullanmayan bileşenlerde doğrudan kullanılır:

TSX
import { Slot } from "@tecof/theme-editor";

render: (p) => (
  <section className={cn("py-16", p.className)}>
    <Slot value={p.itemsSlot} layout="row-wrap" gap="xl" className="justify-center" data-reveal-group />
  </section>
)
PropAçıklama
valueMotorun enjekte ettiği slot değeri (p.<slotAdı>)
layout, gap, className, style, hideIfEmptyYukarıdaki tabloyla aynı
diğerleridata-*, aria-*, id gibi prop’lar slot kapsayıcısına aynen geçer

<Slot> motorun varsayılan yatay yerleşimini (inline flex/gap) basmasını kapatır; böylece temanın Tailwind sınıfları ezilmez. Yerleşim sınıfları (.tecof-slot-row, .tecof-slot-row-wrap, .tecof-slot-col, .tecof-slot-contents) @layer base içindedir: editörde paketin styles.css’inden gelir, yayında ise temanın global CSS’inde bulunmalıdır (yayın sayfası paket CSS’ini yüklemez).

Dinamik alanlar ve türetilmiş veri#

resolveFields prop’lara göre alan setini değiştirir; resolveData türetilmiş prop yazar ve alanları salt okunur yapar. İkisi de senkron ya da asenkron olabilir; geç gelen eski sonuçlar elenir, geri yazım yalnız gerçek farkı commit eder (idempotent olmalı).

TSX
ProductCard: {
  fields: {
    source: { type: "select", options: [{ label: "Elle", value: "manual" }, { label: "CMS", value: "cms" }] },
    title: { type: "text" },
    slug: { type: "text" },
  },
  resolveFields: (props, { fields }) => ({
    ...fields,
    ...(props.source === "cms" ? { collection: createCmsCollectionField({ label: "Koleksiyon" }) } : {}),
  }),
  resolveData: (props) => ({
    props: { slug: slugify(props.title ?? "") },
    readOnly: { slug: true },
  }),
  render: (props) => <article className={props.className}>{props.title}</article>,
}

resolveFields hata fırlatırsa statik fields kullanılır.

Sürükle-bırak kuralları#

Kurallar hem kanvasta hem katman panelinde, dokunmatik sürüklemede de aynı şekilde uygulanır. Hiçbiri tanımlı değilse her bırakma serbesttir.

TSX
const config: StudioConfig = {
  components: {
    Slider: {
      acceptsChildren: { slides: ["Slide"] },   // slides slotu yalnız Slide alır
      maxItems: { slides: 8 },
      fields: { slides: { type: "slot", orientation: "horizontal" } },
      render: ({ slides, className }) => <section className={className}>{slides}</section>,
    },
    Slide: {
      allowedParents: ["Slider"],                // yalnız Slider içine bırakılabilir
      render: ({ className }) => <figure className={className} />,
    },
  },
};

Inline bileşenler#

Sarmalayıcı div düzeni bozuyorsa inline: true verin ve puck.dragRef’i kök elemana bağlayın:

TSX
InlineButton: {
  inline: true,
  fields: { text: { type: "text" } },
  render: ({ puck, text, className }) => (
    <button ref={puck?.dragRef} className={className}>{text}</button>
  ),
}

Varyantlar#

TSX
Button: {
  fields: { text: { type: "text" }, tone: { type: "radio", options: [{ label: "Ana", value: "primary" }, { label: "Çizgili", value: "outline" }] } },
  variants: {
    primary: { label: "Ana buton", props: { tone: "primary" } },
    outline: { label: "Çizgili buton", props: { tone: "outline" } },
  },
  render: (p) => <a className={cn(p.tone === "primary" ? "bg-black text-white" : "border", p.className)}>{p.text}</a>,
}

Her varyant Bölüm Ekle penceresinde kendi önizlemeli kartı olarak görünür; Inspector’daki çip varyantı mevcut düğüme uygular (geri alınabilir, _variant işaretiyle).

Kategoriler ve sayfa kökü#

TSX
export const editorConfig: StudioConfig = {
  components,
  categories: {
    sections: { title: "Sayfa bölümleri", components: ["Header", "Hero", "Footer"] },
    elements: { title: "Elementler", components: ["Title", "Paragraph", "Button"] },
  },
  root: {
    fields: {
      enableEcommerce: { type: "radio", label: "E-ticaret özellikleri", options: [{ label: "Aktif", value: "yes" }, { label: "Pasif", value: "no" }] },
    },
  },
};

Sayfa geneli değerler dokümanın root.props alanında saklanır; tema yayın tarafında bunları data.root.props üzerinden okur. Editör 0.0.96 root.defaultProps alanını okumaz: değer hiç kaydedilmemiş sayfalar için varsayılanı temada verin (ör. data.root.props.enableEcommerce !== "no").