İçeriğe geç
Docs

Slotlar ve Şablonlar

Zone modeli, dikey ve yatay slotlar, repeat zone (öğe şablonu) ve veri kaynakları, slider deseni, bölüm ve sayfa şablonları, id güvenliği — canlı repeat zone önizlemesiyle.

Son güncelleme:

Slotlar bir bileşenin içine başka bileşenlerin bırakılabildiği bölgelerdir. Bu sayfa slotların saklanma biçimini, yatay/dikey yerleşimi, verinin her satırı için tekrarlanan repeat zone’ları ve tek tıkla eklenen şablonları anlatır.

Zone modeli#

Kök düğümler dokümanın content dizisinde, iç içe düğümler zones nesnesinde tutulur. Zone anahtarı parentId:slotAdı biçimindedir:

JSON
{
  "content": [{ "type": "Hero", "props": { "id": "hero-1", "title": "Yeni sezon" } }],
  "zones": {
    "hero-1:actions": [
      { "type": "Button", "props": { "id": "btn-1", "text": "Keşfet" } },
      { "type": "Button", "props": { "id": "btn-2", "text": "İncele" } }
    ]
  },
  "root": { "props": {} }
}

Üst düğüm silindiğinde alt zone ağacı da temizlenir. Zone anahtarı düğüm id’sine bağlı olduğu için bir bileşen tipinin adını değiştirmek zone anahtarlarını etkilemez.

Dikey ve yatay slotlar#

Slot alanları varsayılan olarak çocukları alt alta dizer. orientation: "horizontal" verildiğinde editör bırakma eksenini sol/sağ olarak hesaplar ve yayın aynı düzeni korur.

TSX
fields: {
  columns: { type: "slot", label: "Kolonlar", orientation: "horizontal" },
  blocks: { type: "slot", label: "Bloklar" }, // dikey
}

Slot’u render’da çizmenin üç yolu vardır; yeni kodda ilk ikisini kullanın:

TSX
// 1) defineSection: hazır <Slot> elemanı
render: (p, slots) => <section className="py-16">{slots.columns}</section>

// 2) <Slot> bileşeni: yerleşim tek kaynaktan
render: (p) => (
  <section className={cn("py-16", p.className)}>
    <Slot value={p.columns} layout="row-wrap" gap="lg" />
  </section>
)

// 3) Düşük seviye: puck.renderDropZone
render: ({ puck, className }) => (
  <section className={className}>{puck.renderDropZone({ zone: "columns", orientation: "horizontal" })}</section>
)

Düz renderDropZone ile yatay slot yayında inline display:flex; flex-wrap:wrap; gap:8px alır; <Slot> ise bu varsayılanı kapatıp yerleşimi layout / gap / className’den kurar (Slot bileşeni).

Repeat zone (öğe şablonu)#

Bir slot alanına repeatSource verildiğinde slot bir öğe şablonuna dönüşür: kart bir kez tasarlanır ve veri listesindeki her satır için tekrarlanır (ürün ızgarası, koleksiyon listesi, SSS…). Şablondaki prop’lar satıra {{ item.<alan> }} token’larıyla bağlanır.

ProductGrid.tsx
ProductGrid: {
  label: "Ürün ızgarası",
  fields: {
    items: createRepeaterField({
      label: "Ürünler",
      subFields: { name: { type: "text", label: "Ad" }, price: { type: "text", label: "Fiyat" }, image: createUploadField({ label: "Görsel", allowMultiple: false }) },
    }),
    card: { type: "slot", label: "Kart şablonu", orientation: "horizontal", repeatSource: "items" },
  },
  render: ({ card, className }) => <section className={className}>{card}</section>,
}

Şablondaki bir Text düğümünün text prop’u "{{ item.name }}", fiyat satırı "Fiyat: {{ item.price }} TL" olabilir.

  • Tam token ham değeri geçirir: prop’un değeri yalnız "{{ item.image }}" ise görsel/bağlantı nesnesi olduğu gibi iletilir; metne gömülü token metne çevrilir.
  • Özel yollar: {{ item._index }} (0 tabanlı), {{ item._position }} (1 tabanlı).
  • Token’ları kütüphane çözer: {{ item.* }} hem editör kanvasında hem TecofRender’da çözülür; host tarafında iş gerekmez. ({{ data.* }} ise CMS kaydından, cmsData ile çözülür.)
  • Editörde şablon ilk satırın verisiyle bir kez düzenlenir; kalan satırlar etkileşimsiz, soluk kopyalar olarak aynı grid/flex akışında görünür.
  • Yayında döngü gerçek veriyle döner; satır yoksa şablon hiç render edilmez — canlı sitede ham token görünmez.

Veri kaynakları#

repeatSource’un gösterdiği prop’un değerine göre satırlar otomatik çözülür:

KaynakSatırlar nereden gelir
Dizi prop / createRepeaterFieldDokümanda kayıtlı satırlar
createApiListFieldHost’un fetchList fonksiyonu; editörde ve yayında (istemcide) kütüphane çağırır
createCmsCollectionFieldCMS koleksiyonu; ağaçta TecofProvider gerekir (yoksa satırlar boş kalır)
renderDropZone({ zone, repeatItems })Bileşen satırları render anında kendisi verir (tam kontrol)
TSX
// Manuel yol: veriyi bileşen kendisi getirir
render: ({ puck }) => {
  const { rows } = useMyProducts();
  return puck.renderDropZone({ zone: "card", repeatItems: rows });
},

Bağlama penceresindeki Öğe alanları listesi sırasıyla slot’un itemSchema’sından, kaynak alanın itemSchema’sından ya da çözülen ilk satırdan çıkarılır. Yalnız manuel repeatItems yolunda itemSchema bildirmeniz gerekir:

TSX
card: {
  type: "slot",
  itemSchema: [
    { key: "name", label: "Ürün adı", type: "text" },
    { key: "image", label: "Görsel", type: "image" },
  ],
}
Dışa aktarımNe işe yarar
useRepeatItem()Şablon bileşeni içinde { item, index, count }; şablon dışında null
resolveItemTokens(value, item, index)Renderer’ların kullandığı token çözücü (host’un kendi render’ı için)
resolveRepeatRows(value, sourceFieldDef, apiClient?)Satırları React dışında çözer (ör. SSR ısıtma)
useRepeatRows(value, sourceFieldDef)Aynı çözümleme, React kancası olarak
clearRepeatRowsCache()API/CMS satır önbelleğini temizler
findRepeatScope(doc, config, nodeId)Bir düğümün içinde bulunduğu repeat şablonunu bulur

Slaytlar ayrı bir bileşen olarak slota bırakılır. Kritik nokta editMode ayrımıdır: editörde tüm slaytlar yan yana görünüp sürükle-bırak almalı, yayında aynı zone kaydırılabilir bir carousel olmalıdır.

Slider.tsx
const Slider = {
  label: "Slider",
  acceptsChildren: { slides: ["Slide"] },
  maxItems: { slides: 8 },
  fields: { slides: { type: "slot", label: "Slaytlar", orientation: "horizontal" } },
  render: ({ puck, editMode, className }) => (
    <section className={className}>
      {puck.renderDropZone({
        zone: "slides",
        orientation: "horizontal",
        className: editMode ? "gap-4" : "snap-x snap-mandatory overflow-x-auto gap-4",
        style: editMode ? undefined : { flexWrap: "nowrap" },
      })}
    </section>
  ),
};

const Slide = {
  label: "Slayt",
  allowedParents: ["Slider"],
  fields: { image: createUploadField({ label: "Görsel", allowMultiple: false }), caption: { type: "text", label: "Başlık" } },
  render: ({ image, caption, className }) => (
    <figure className={cn("w-80 shrink-0 snap-center", className)}>
      <TecofPicture data={image?.[0]} alt={caption ?? ""} size="large" />
      {caption && <figcaption className="mt-2 text-sm">{caption}</figcaption>}
    </figure>
  ),
};
  • snap-* sınıfları slaydın kendi kökündedir; yayında zone çocukları doğrudan render edildiği için scroll-snap birebir çalışır.
  • Swiper/Embla gibi DOM’u klonlayan ya da ölçen kütüphaneleri yalnız editMode === false iken bağlayın; editörde zone düz düzende kalmalıdır ki seçim ve bırakma hedefleri doğru çalışsın. Çoğu senaryoda CSS scroll-snap yeterlidir.

Bölüm şablonları#

config.templates, Bölüm Ekle penceresinde ayrı sekmede gösterilen hazır bölümlerdir. Şablon bir alt ağaçtır: kök düğüm ve alt zone’ları.

TSX
templates: [
  {
    id: "hero-cta",
    label: "Hero + iki buton",
    category: "Dönüşüm",
    thumbnail: "/templates/hero-cta.webp",
    payload: {
      node: { type: "Hero", props: { id: "t-hero", title: "Başlık" } },
      zones: {
        "t-hero:actions": [
          { type: "Button", props: { id: "t-b1", text: "Başla" } },
          { type: "Button", props: { id: "t-b2", text: "İncele" } },
        ],
      },
    },
  },
],
AlanTipAçıklama
idstringKararlı kimlik
labelstringKartta görünen ad
categorystring?Kategori
thumbnailstring?Önizleme görseli (yoksa ikon)
payload.nodeTecofNodeEklenecek kök düğüm
payload.zonesRecord<string, TecofNode[]>?Kök düğümün alt zone’ları (nodeId:slotAdı)

Sayfa şablonları#

config.pageTemplates tüm sayfayı kuran bölüm dizileridir (Ana Sayfa, Hakkımızda, İletişim…). Sol paneldeki Sayfa şablonları bölümünde ve Bölüm Ekle penceresinde görünür.

TSX
pageTemplates: [
  {
    id: "core-home",
    label: "Ana sayfa",
    description: "Hero, hizmetler, referanslar ve iletişim çağrısı",
    keywords: ["kurumsal", "landing"],
    previewUrl: "/preview-template/core-home",
    sections: [
      { node: { type: "Hero", props: { id: "t-hero", title: "Başlık" } } },
      { node: { type: "Services", props: { id: "t-svc" } }, zones: { "t-svc:items": [] } },
    ],
  },
],

Şablona tıklamak bölümleri doğrudan eklemez; önce bir onay penceresi açılır. previewUrl varsa sayfa 1280 px referans genişlikte bir iframe’de (tembel, sandbox’lı, tıklanamaz) gösterilir; göreli adres editörün çalıştığı origin’e göre çözülür. Yoksa bölümlerin canlı önizlemesi üst üste dizilir. Onaydan sonra mevcut içerik silinmez; bölümler eklenir ve tamamı tek geri al adımıdır.

Id güvenliği#

Şablondaki id’ler yalnız ilişki kurmak için örnek kimliklerdir. Studio her eklemede kök ve tüm alt düğümler için yeni id üretir ve zone anahtarlarını buna göre yeniden yazar; aynı şablonu güvenle birden çok kez ekleyebilirsiniz. Kopyala/yapıştır ve çoğaltma için de aynı kural geçerlidir.