Alanlar Inspector’da düzenlenen prop’ları tanımlar. Yerleşik tipler doğrudan { type: "…" } olarak yazılır; gelişmiş alanlar create*Field() fabrikalarıyla üretilir. Fabrikalar { type: "custom", _fieldType, label, render } döndürür, yani Puck uyumlu config’e doğrudan konur.
Yerleşik tipler#
type | Değer | Önemli seçenekler |
|---|---|---|
text, textarea | string | bindable (CMS bağlama düğmesi, varsayılan true), readOnly |
select, radio | seçilen value | options: [{ label, value }] |
number | number | — |
boolean, toggle | boolean | onLabel, offLabel (varsayılan Açık / Kapalı) |
range | number | min (0), max (100), step (1), unit (ör. "px") |
array | object[] | arrayFields |
object | object | objectFields |
slot | (zone) | orientation, repeatSource, itemSchema — bkz. Slotlar |
Her alan label, defaultValue ve readOnly alabilir. Dinamik salt okunurluk resolveData().readOnly ile gelir; ikisi VEYA’lanır.
Ortak seçenekler#
Tüm fabrikalar şunları destekler:
| Seçenek | Tip | Açıklama |
|---|---|---|
label | string | Inspector etiketi |
labelIcon | ReactElement | Etiketin yanındaki ikon (ör. <Globe size={16} />) |
visible | boolean | Alanın panelde görünüp görünmediği |
LanguageField — çok dilli metin#
Merchant dillerini merchant-info’dan alır ve dil başına değer tutar. Studio içinde üst çubuktaki global dil kullanılır ve alan içindeki dil sekmeleri gizlenir; Studio dışında sekmeli moda döner.
import { createLanguageField } from "@tecof/theme-editor";
fields: {
title: createLanguageField({ label: "Başlık" }),
description: createLanguageField({ label: "Açıklama", isTextarea: true, textareaRows: 4 }),
html: createLanguageField({ label: "HTML içerik", isHtml: true }),
}| Seçenek | Varsayılan | Açıklama |
|---|---|---|
isTextarea | false | Çok satırlı giriş |
textareaRows | 3 | Satır sayısı |
placeholder | '' | Yer tutucu |
isHtml | false | Çeviride HTML etiketlerini korur |
Değer: [{ code: "tr", value: "Merhaba" }, { code: "en", value: "Hello" }]. Temada aktif dilin değerini seçin; değer yoksa varsayılan dile düşmek temanın sorumluluğundadır:
const pick = (value: Array<{ code: string; value: string }> | string | undefined, locale: string, fallback = "tr") =>
typeof value === "string" ? value : (value?.find((v) => v.code === locale)?.value || value?.find((v) => v.code === fallback)?.value || "");Dil araçları: Birden fazla dil varken alan üstünde Hızlı Doldur (aktif sekmedeki metni tüm dillere kopyalar) ve Çevir (API üzerinden diğer dillere çevirir; isHtml ile etiketler korunur) düğmeleri görünür. Aynı çubuk EditorField, LinkField (yalnız Hızlı Doldur, yalnız boş diller) ve LocalizedUploadField’da da vardır.
Kendi çok dilli alanınızda aynı araçları kullanabilirsiniz:
import {
LanguageToolsBar, useLanguageToolsStatus, normalizeLocalizedValues, fillLanguages,
translateLanguages, isEmptyText, LANGUAGE_TOOL_MESSAGES,
} from "@tecof/theme-editor";
const { status, translating, setTranslating, flash } = useLanguageToolsStatus();
const values = normalizeLocalizedValues<string>(value, languages, () => "");
const fill = () => {
const res = fillLanguages(values, languages, activeTab, { isEmpty: isEmptyText });
if (!res) return; // kaynak boş ya da doldurulacak dil yok
onChange(res.values);
flash({ text: LANGUAGE_TOOL_MESSAGES.filledAll, type: "success" }, 2000);
};
const translate = async () => {
setTranslating(true);
try {
const out = await translateLanguages({
values: () => valuesRef.current, // getter: yanıt gelene kadar yazılanlar korunur
languages,
sourceCode: activeTab,
translate: apiClient.translate.bind(apiClient),
});
if (out.ok) onChange(out.values);
flash(out.ok ? { text: LANGUAGE_TOOL_MESSAGES.translated, type: "success" } : { text: out.message, type: "error" }, 3000);
} finally {
setTranslating(false);
}
};
<LanguageToolsBar onFill={fill} onTranslate={translate} translating={translating} status={status} />;fillLanguages(values, languages, sourceCode, { onlyEmpty, isEmpty, clone }) → { values, filled } | null. onlyEmpty: true dolu dilleri ezmez ve listede olmayan dil kodlarını korur. Boşluk tanımları: isEmptyText, isEmptyHtml (medya etiketi dolu sayılır), isEmptyLink.
EditorField — zengin metin#
TipTap tabanlı, çok dilli WYSIWYG editör (kalın, italik, bağlantı, liste, başlık…). Hızlı Doldur boş <p></p> içerikte pasiftir; Çevir yalnız TecofProvider varken görünür. Araçlar aktif dilin içeriğini değiştirmediği için imleç korunur.
fields: { content: createEditorField({ label: "İçerik" }) }UploadField — medya#
Seçili medya kare önizleme kutuları olarak görünür; “ekle” kutusu sekmeli bir medya penceresi açar: Kütüphane (sunucudaki dosyalar), Yükle (FilePond), Referans (CMS değişkeni, {{ data.alan }}). Doka görsel düzenleyici (kırp, döndür, ayarlar), istemci tarafı WebP sıkıştırma ve odak noktası seçimi içerir.
fields: {
gallery: createUploadField({
label: "Ürün galerisi",
allowMultiple: true,
maxFiles: 10,
maxFileSize: "50MB",
acceptedTypes: ["image/*"],
folder: "/products",
}),
document: createUploadField({ label: "Katalog", allowMultiple: false, acceptedTypes: ["application/pdf"] }),
}| Seçenek | Varsayılan | Açıklama |
|---|---|---|
allowMultiple | true | Çoklu dosya |
maxFiles | 100 | En çok dosya |
maxFileSize | "100MB" | Tek dosya sınırı |
maxTotalFileSize | "200MB" | Toplam sınır |
acceptedTypes | tümü | MIME türleri (["image/*"], ["application/pdf"]) |
folder | "/" | Yükleme klasörü |
allowReorder | true | Sürükleyerek sıralama |
imageCompressionEnabled | true | Yüklemeden önce sıkıştırma |
imageCompressionOptions | { maxSizeMB: 1, maxWidthOrHeight: 1920 } | Sıkıştırma ayarları (fileType de verilebilir) |
imagePreviewHeight | — | FilePond önizleme yüksekliği |
showUploadedFiles | false | Kullanımdan kalktı; yeni arayüzde etkisiz |
Değer: UploadedFile[] — { _id, name, size, type, mimeType?, url?, folder?, provider?, focalPoint?, meta? }. Görseli temada TecofPicture ile basın (Yayın render’ı).
Odak noktası: görsel kutusundaki nişangâh düğmesi odak penceresini açar (tıkla/sürükle, ok tuşlarıyla 1’er, Shift ile 10’ar adım; 16:9, 1:1, 9:16 kırpma önizlemeleri). Seçim dosyanın içine focalPoint: { x, y } (yüzde, 50/50 merkez) olarak yazılır; TecofPicture bunu object-position olarak uygular. Backend’de şema değişikliği gerekmez.
LocalizedUploadField — dile göre medya#
UploadField’ın dil sekmeli sürümü; seçenekleri aynıdır. Her dil için ayrı dosya listesi tutar.
fields: {
banner: createLocalizedUploadField({ label: "Kampanya görseli", allowMultiple: false }),
}
defaultProps: { banner: [] }Değer: [{ code: "tr", value: UploadedFile[] }, { code: "en", value: [] }]. Yayında istenen dil boşsa varsayılan dile, o da boşsa ilk dolu dile düşülür:
import { resolveLocalizedUploadFile, TecofPicture } from "@tecof/theme-editor";
const file = resolveLocalizedUploadFile(props.banner, locale, "tr"); // UploadedFile | null
return file ? <TecofPicture data={file} alt="" /> : null;Çoklu dosya için resolveLocalizedUpload(value, locale, defaultLanguage) → UploadedFile[]. Eski düz UploadedFile[] kayıtlar varsayılan dil altında açılır ve ilk düzenlemede yeni biçimde kaydedilir. Hızlı Doldur yalnız boş dillere kopyalar; Çevir yoktur. Ters yön (yerelleştirilmiş alanı tekrar createUploadField’a çevirmek) desteklenmez.
LinkField — sayfa veya URL#
Mağaza sayfalarından seçim (aranabilir pencere, yayın durumu göstergesi) ya da elle URL girişi. Dil sekmelidir; değer dil başına saklanır.
fields: {
cta: createLinkField({ label: "CTA bağlantısı", showTarget: true, placeholder: "https://…" }),
}| Seçenek | Varsayılan | Açıklama |
|---|---|---|
showTarget | true | Aynı / yeni sekme seçimi |
placeholder | https://... | URL yer tutucusu |
Değer: [{ code: "tr", value: LinkFieldValue }, …]; LinkFieldValue = { url, label?, target?: "_self" | "_blank", type?: "page" | "custom" | "category" | "brand" | "product" | "cms" }. Render yalnız url ve target okur. Eski düz LinkFieldValue kayıtlar mağaza dillerine göre normalize edilir.
ColorField — renk#
Popover renk seçici: doygunluk/parlaklık alanı, ton ve opaklık kaydırıcıları, HEX/RGB/HSL girdileri, ekrandan renk seçme (EyeDropper), canlı tema paleti, 22 tonluk Tailwind paleti, son kullanılanlar ve WCAG kontrast önizlemesi. Tam klavye erişimi vardır.
fields: {
bgColor: createColorField({ label: "Arka plan" }),
overlay: createColorField({ label: "Kaplama", showOpacity: true, defaultColor: "#00000080" }),
accent: createColorField({ label: "Vurgu", themeVars: true }),
brand: createColorField({ label: "Marka", swatches: ["#18181b", "#74b500", "#ffffff"], palette: false }),
}| Seçenek | Varsayılan | Açıklama |
|---|---|---|
showOpacity | false | Opaklık; değer #rrggbbaa |
defaultColor | '' | Sıfırlama hedefi |
placeholder | '#000000' | HEX girdisi yer tutucusu |
showReset | true | Değer varsayılandan farklıyken sıfırla düğmesi |
swatches | [] | “Hızlı seçim” satırı |
themeColors | true | Canlı tema renkleri (yalnız Studio içinde) |
themeVars | false | Tema rengini var(--theme-color-<anahtar>) olarak bağlar; kapalıyken açık paletteki hex kopyalanır |
palette | true | Tailwind paleti |
contrast | true | Kontrast çipleri (WCAG AA/AAA) |
contrastAgainst | — | Kontrast zemini (hex) |
Değer sözleşmesi: '', #rrggbb, #rrggbbaa (yalnız showOpacity) ya da var(--theme-color-*) (yalnız themeVars). rgb()/hsl()/transparent girdileri hex’e çevrilir; tanınmayan değerler korunur. Hex matematiği yapan tema kodu var() beklememelidir. Saf yardımcılar da dışa aktarılır: parseColor, formatHex, normalizeColorValue, classifyColorValue, contrastRatio, wcagLevel, themeColorVar, parseThemeColorVar, THEME_COLOR_KEYS, toThemeCssKey.
CodeEditorField — kod#
Monaco tabanlı kod editörü (ilk kullanımda tembel yüklenir).
fields: {
customHtml: createCodeEditorField({ label: "Özel kod", defaultLanguage: "html" }),
json: createCodeEditorField({ label: "Yapılandırma", defaultLanguage: "json", height: "240px" }),
}Seçenekler: defaultLanguage, height, theme.
RepeaterField — tekrar eden satırlar#
Alt alan setini (subFields) satırlar hâlinde düzenler: ekle, sil, çoğalt, sürükleyerek sırala, satırı katla.
fields: {
items: createRepeaterField({
label: "Öğeler",
subFields: {
title: createLanguageField({ label: "Başlık" }),
link: createLinkField({ label: "Bağlantı" }),
},
minItems: 1,
maxItems: 6,
defaultRow: { title: "", link: null },
}),
}Değer: Record<string, unknown>[]. Bir slot’un repeatSource’u olarak kullanılabilir (Repeat zone).
IconField — ikon#
lucide-react setinden aranabilir ikon seçici (arama yoksa ilk 120 ikon). Değer ikonun adıdır ("ShoppingCart"); bileşeni render’da siz çözersiniz:
import { icons } from "lucide-react";
const Icon = icons[props.icon as keyof typeof icons];
return Icon ? <Icon className="size-5" /> : null;ExternalField — harici tek kayıt#
Kendi API’nizden aranabilir bir pencerede tek kayıt seçtirir.
fields: {
product: createExternalField({
label: "Ürün",
fetchList: async ({ query }) => (await fetch(`/api/products?q=${query ?? ""}`)).json(),
mapProp: (row) => ({ id: row.id, name: row.name }),
mapRow: (row) => ({ Ad: row.name, SKU: row.sku }),
getItemSummary: (value) => value?.name ?? "",
placeholder: "Ürün seçin",
}),
}| Seçenek | Açıklama |
|---|---|
fetchList({ query?, filters? }) | Zorunlu. Satırları getirir |
mapProp(row) | Prop’a yazılacak değer (varsayılan satırın kendisi) |
mapRow(row) | Pencere tablosunun sütunları |
getItemSummary(value) | Seçili değerin etiketi |
showSearch | Arama kutusu (varsayılan true) |
ApiListField — API’den liste#
Kendi API’nizden liste akıtır; bir slot’un repeatSource’una bağlanınca öğe şablonu her satır için tekrarlanır.
fields: {
source: createApiListField({
label: "Ürünler",
fetchList: async ({ query, limit }) => (await fetch(`/api/products?q=${query ?? ""}&limit=${limit ?? 8}`)).json(),
itemSchema: [{ key: "name", label: "Ad", type: "text" }, { key: "image", label: "Görsel", type: "image" }],
defaultLimit: 8,
}),
card: { type: "slot", orientation: "horizontal", repeatSource: "source" },
}Değer: { query?: string; limit?: number } — dokümana yalnız sorgu parametreleri yazılır; satırlar render anında çözülür (oturum içi önbellek, eşzamanlı istek tekleme). Seçenekler: showQuery, showLimit (varsayılan true), defaultLimit.
E-ticaret seçicileri#
Mağazanın kendi kayıtlarından seçim yaptırır; slug ya da ObjectId elle yazdırmaz.
import {
createCategoryField, createCategoryListField, createProductField, createProductListField,
createBrandField, createBrandListField, createTagField, createTagListField,
createAttributeField, createAttributeListField, createVariantTypeField, createVariantField,
createFlashSaleField, createCampaignField, createDiscountField,
} from "@tecof/theme-editor";
fields: {
category: createCategoryField({ label: "Kategori" }),
featured: createProductField({ label: "Öne çıkan ürün" }),
tags: createTagListField({ label: "Etiketler", max: 6 }),
coupon: createDiscountField({ label: "Kupon" }),
}Saklanan değer seçim anında donan bir anlık görüntüdür; yayın ek istek atmadan render edebilir:
| Alan | Değer |
|---|---|
createCategoryField | { id, slug, name, path } |
createProductField | { id, slug, name, image } |
createBrandField | { id, slug, name, image } |
createTagField | { id, slug, name } |
createAttributeField | { id, slug, name, type, isFilterable, options: [{ value, label, colorCode }] } |
createVariantTypeField | { id, name, selectionType, values: [{ label, colorCode }] } |
createVariantField | { productId, productSlug, productName, variantId, sku, title, price } |
createFlashSaleField | { id, name, startDate, endDate, showCountdown, bannerText, highlightColor, discountType, discountValue } |
createCampaignField | { id, name, status, startDate, endDate, discountCode } |
createDiscountField | { id, code, title, type, value, minCartAmount, maxDiscountAmount, startDate, endDate } |
*ListFieldvaryantlarında değer bu nesnelerin dizisidir; editördeki sıra korunur. Seçenekler:label,placeholder,visible, listelerdemax.- Neden hem
idhemslug: ürün listeleme ucu markayı ve etiketi ObjectId, kategoriyi slug ile filtreler. Temada filtreyi kimlikle gönderin:getProducts({ brand: props.brand?.id, tag: props.tag?.id }). - Veri kaynağı: kategori/ürün/marka vitrin uçlarından (
/api/store/ecommerce/*); etiket, özellik, varyant tipi, flaş satış, kampanya ve kupon panel uçlarından (/api/merchant/ecommerce/*) gelir. Panel uçları JWT’ye ek olarakx-merchant-idister;TecofApiClientbunumerchant-info’dan bir kez çözer. Kimlik çözülemezse liste boş değil hata gösterir. - Anlık görüntü donar: markanın adı ya da kampanyanın tarihi panelde sonradan değişirse sayfa eski değeri gösterir; alanı yeniden seçin.
- Boş durum: liste boşsa “Panelde ürün ekle” gibi bir bağlantı, doluysa başlıkta “Panelde yönet” görünür; adresler
panelUrlüzerine kurulur ve yeni sekmede açılır. Panelde kayıt ekledikten sonra seçicideki Yenile listeyi tazeler. - Geriye uyum: var olan bir metin alanının yerine koyarken eski prop’u okumaya devam edin:
props.brand?.id ?? props.brandIdText ?? "".
CmsCollectionField — koleksiyon bağlama#
Bir bileşeni CMS koleksiyonuna bağlar: koleksiyon, adet ve sıralama seçilir; bileşenin veri yuvaları koleksiyon alanlarına eşlenir.
fields: {
source: createCmsCollectionField({
label: "Veri kaynağı",
defaultLimit: 6,
slots: {
title: { label: "Başlık", fieldTypes: ["text"] },
image: { label: "Görsel", fieldTypes: ["image"] },
link: { label: "Bağlantı" },
},
}),
}| Seçenek | Varsayılan | Açıklama |
|---|---|---|
defaultLimit | 10 | Varsayılan öğe sayısı |
showLimit, showSort | true | Adet ve sıralama (özel / yeni → eski / eski → yeni) kontrolleri |
slots | — | Bileşenin ihtiyaç duyduğu veri yuvaları |
Değer: { collectionSlug, collectionName?, limit?, sort?: "newest" | "oldest" | "custom", fieldMap?: Record<yuva, cmsAlanKısaKodu> }. Bir repeat slot’a kaynak olarak bağlanırsa satırlar koleksiyondan otomatik çekilir (ağaçta TecofProvider gerekir).
CMS veri bağlama (metin alanları)#
text ve textarea alanlarının yanında bir { } düğmesi görünür; koleksiyon → alan seçilince alana {{ data.<kısaKod> }} token’ı eklenir. Yayında token, TecofRender’a verilen cmsData kaydıyla çözülür. Kapatmak için bindable: false:
fields: {
title: { type: "text", label: "Başlık" }, // bağlama açık
staticLabel: { type: "text", label: "Sabit etiket", bindable: false },
}Repeat zone şablonunun içindeyken aynı düğme Öğe alanları bölümünü de gösterir ({{ item.<alan> }}).
Hata yalıtımı#
Fabrikaların ürettiği alanlar FieldErrorBoundary içinde render edilir; bir alanın hatası Inspector’ı düşürmez. Kendi render alanlarınızda da aynı bileşeni kullanabilirsiniz.