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:
{
"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.
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:
// 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: {
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 hemTecofRender’da çözülür; host tarafında iş gerekmez. ({{ data.* }}ise CMS kaydından,cmsDataile çö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:
| Kaynak | Satırlar nereden gelir |
|---|---|
Dizi prop / createRepeaterField | Dokümanda kayıtlı satırlar |
createApiListField | Host’un fetchList fonksiyonu; editörde ve yayında (istemcide) kütüphane çağırır |
createCmsCollectionField | CMS 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) |
// 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:
card: {
type: "slot",
itemSchema: [
{ key: "name", label: "Ürün adı", type: "text" },
{ key: "image", label: "Görsel", type: "image" },
],
}| Dışa aktarım | Ne 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 |
Slider / carousel deseni#
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.
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 === falseiken 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ı.
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" } },
],
},
},
},
],| Alan | Tip | Açıklama |
|---|---|---|
id | string | Kararlı kimlik |
label | string | Kartta görünen ad |
category | string? | Kategori |
thumbnail | string? | Önizleme görseli (yoksa ikon) |
payload.node | TecofNode | Eklenecek kök düğüm |
payload.zones | Record<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.
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.