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#
| Alan | Tip | Açıklama |
|---|---|---|
components | Record<string, ComponentConfig> | Zorunlu. Anahtar, dokümanda type olarak saklanır |
categories | Record<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) |
templates | SectionTemplate[] | Tek bölüm şablonları (Slotlar ve şablonlar) |
pageTemplates | PageTemplate[] | Çok bölümlü sayfa şablonları |
theme | DeepPartialThemeConfig | Temanın varsayılan renk/tipografi/boşluk değerleri (Canlı tema) |
themePresets | ThemePreset[] | Tema panelinde hazır renk paletleri |
darkMode | boolean | { defaultMode?, storageKey? } | Karanlık modu açar (Canlı tema) |
permissions | Partial<Permissions> | Genel izinler (İzinler) |
migrations | MigrationConfig | Eski kayıtları yükseltme (İzinler ve migration) |
ai | { complete({ system, prompt }) } | AI ile bölüm üretme (Studio) |
nodeInfoButton | boolean | Seçim araç çubuğunda ⓘ bileşen bilgisi düğmesi; varsayılan kapalı |
ComponentConfig#
Yalnız render zorunludur.
| Alan | Tip | Açıklama |
|---|---|---|
render | (props) => ReactNode | Editörde ve yayında kullanılan render |
label | string? | Kütüphanede görünen ad |
category | string? | Kategori anahtarı |
fields | Record<string, FieldConfig> | Inspector alanları (Alanlar) |
fieldsGroups | { name, fields[] }[] | Alanları akordeon gruplarına ayırır; listelenmeyenler grupların ardından düz gösterilir |
defaultProps | object? | Yeni düğümün başlangıç değerleri |
defaultChildren | Record<slot, Array<type | { type, props? }>> | Eklenince slotlara otomatik konan çocuklar (özyinelemeli, döngü korumalı) |
variants | Record<string, { label, props }> | İsimli prop ön ayarları: Bölüm Ekle’de ayrı kart, Inspector’da çip |
inline | boolean? | Editörün sarmalayıcı div’ini kaldırır; bileşen puck.dragRef’i köke bağlamalıdır |
acceptsChildren | boolean | string[] | Record<slot, boolean | string[]> | Hangi çocuk tiplerini kabul ettiği (bileşen ya da slot bazında) |
maxItems | number | Record<slot, number> | Doğrudan çocuk üst sınırı (bileşen ya da slot bazında) |
allowedParents | string[] | Bu bileşenin bırakılabileceği üst tipler; doluysa kök içeriğe bırakılamaz |
resizable | boolean? | Boyutlandırma modunda genişlik/yükseklik tutamaçları (varsayılan true) |
permissions / resolvePermissions | — | Bileşen ve düğüm bazlı izinler |
resolveFields | (props, ctx) => fields | Prop’lara göre dinamik alan seti (sync/async) |
resolveData | (props, ctx) => { props?, readOnly? } | Türetilmiş prop’lar ve salt okunur alanlar (sync/async) |
metadata | object? | 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.
// 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:
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:
slotstek yerde tanımlanır; hemtype: "slot"alanları hemdefaultChildrenburadan 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’inep.classNameeklenir.
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ği | Açıklama |
|---|---|
label | Inspector’daki slot adı |
layout | row · row-wrap · col · contents · none; row/row-wrap slot alanını yatay (orientation: "horizontal") yapar |
gap | none · xs .25 · sm .5 · md .75 · lg 1 · xl 1.5 rem ya da ham değer ("1.25rem") |
className | Kapsayıcıya ek sınıf; fonksiyon verilirse her render’da prop’larla çağrılır |
hideIfEmpty | Yayında slot boşsa kapsayıcıyı hiç basma (editörde boş slot “Bileşen ekle” ipucuyla görünür kalır) |
default | Eklenince otomatik doğan çocuklar |
fieldExtra | Slot alanına eklenecek ek ayarlar (ör. { repeatSource: "items" }) |
allow, maxItems | Slot 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:
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>
)| Prop | Açıklama |
|---|---|
value | Motorun enjekte ettiği slot değeri (p.<slotAdı>) |
layout, gap, className, style, hideIfEmpty | Yukarıdaki tabloyla aynı |
| diğerleri | data-*, 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ı).
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.
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:
InlineButton: {
inline: true,
fields: { text: { type: "text" } },
render: ({ puck, text, className }) => (
<button ref={puck?.dragRef} className={className}>{text}</button>
),
}Varyantlar#
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ü#
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").