x
This commit is contained in:
1 parent
3bbe2bb35e
commit
641cc4e42b
9 files changed
+1331
-631
No files matched your search
@@ -31,13 +31,18 @@ export default function FeaturedTilesForm({
|
||||
if (preset === 'editorial') {
|
||||
// Style 1: Clean Editorial (4 Columns, Landscape, Left aligned, Arrow link, Warm Cream)
|
||||
updatePayloadValue('layoutPreset', 'editorial');
|
||||
updatePayloadValue('headerAlign', 'left');
|
||||
updatePayloadValue('showHeading', true);
|
||||
updatePayloadValue('columns', 4);
|
||||
updatePayloadValue('containerWidth', 'wide');
|
||||
updatePayloadValue('imageAspectRatio', 'landscape');
|
||||
updatePayloadValue('textAlign', 'left');
|
||||
updatePayloadValue('ctaStyle', 'arrow_link');
|
||||
updatePayloadValue('imageRounded', 'md');
|
||||
updatePayloadValue('backgroundColor', '#F7F4EE');
|
||||
updatePayloadValue('textColor', '#1E293B');
|
||||
updatePayloadValue('paddingTop', 'compact');
|
||||
updatePayloadValue('paddingBottom', 'normal');
|
||||
|
||||
// Update tiles' aspect ratio to match
|
||||
const updatedTiles = tiles.map((t: any) => ({
|
||||
@@ -49,13 +54,18 @@ export default function FeaturedTilesForm({
|
||||
} else {
|
||||
// Style 2: Campaign Spotlight (2 Columns, Square, Center aligned, Pill button, White)
|
||||
updatePayloadValue('layoutPreset', 'spotlight');
|
||||
updatePayloadValue('headerAlign', 'center');
|
||||
updatePayloadValue('showHeading', true);
|
||||
updatePayloadValue('columns', 2);
|
||||
updatePayloadValue('containerWidth', 'standard');
|
||||
updatePayloadValue('imageAspectRatio', 'square');
|
||||
updatePayloadValue('textAlign', 'center');
|
||||
updatePayloadValue('ctaStyle', 'pill_button');
|
||||
updatePayloadValue('imageRounded', '3xl');
|
||||
updatePayloadValue('backgroundColor', '#FFFFFF');
|
||||
updatePayloadValue('textColor', '#111827');
|
||||
updatePayloadValue('paddingTop', 'normal');
|
||||
updatePayloadValue('paddingBottom', 'normal');
|
||||
|
||||
const updatedTiles = tiles.map((t: any) => ({
|
||||
...t,
|
||||
@@ -201,44 +211,74 @@ export default function FeaturedTilesForm({
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">Grid Columns (จำนวนคอลัมน์)</label>
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">การจัดวางหัวข้อ (Header Alignment)</label>
|
||||
<select
|
||||
value={payload.headerAlign || (payload.showHeading === false ? 'none' : 'center')}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value;
|
||||
updatePayloadValue('headerAlign', val);
|
||||
updatePayloadValue('showHeading', val !== 'none');
|
||||
}}
|
||||
className="admin-input w-full text-xs bg-slate-900 border-white/10"
|
||||
>
|
||||
<option value="center">กึ่งกลาง (Center - ค่าเริ่มต้น)</option>
|
||||
<option value="left">ชิดซ้าย (Left - สไตล์ Editorial แบบรูป 1)</option>
|
||||
<option value="right">ชิดขวา (Right)</option>
|
||||
<option value="none">ซ่อนหัวข้อ (Do not show - ไม่โชว์เลย)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-6 gap-3 pt-3 border-t border-white/5">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">Grid Columns (คอลัมน์)</label>
|
||||
<select
|
||||
value={payload.columns || 2}
|
||||
onChange={(e) => updatePayloadValue('columns', parseInt(e.target.value))}
|
||||
className="admin-input w-full text-xs bg-slate-900 border-white/10"
|
||||
>
|
||||
<option value={2}>2 Columns (ใหญ่เด่น 2 ช่อง)</option>
|
||||
<option value={3}>3 Columns (ปานกลาง 3 ช่อง)</option>
|
||||
<option value={4}>4 Columns (แถวยาว 4 ช่องแบบรูปที่ 1)</option>
|
||||
<option value={2}>2 Columns (ใหญ่เด่น)</option>
|
||||
<option value={3}>3 Columns (ปานกลาง)</option>
|
||||
<option value={4}>4 Columns (ยาวแบบรูป 1)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 pt-2 border-t border-white/5">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">สัดส่วนรูปภาพ (Default Aspect Ratio)</label>
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">ความกว้างบล็อก (Width)</label>
|
||||
<select
|
||||
value={payload.containerWidth || 'standard'}
|
||||
onChange={(e) => updatePayloadValue('containerWidth', e.target.value)}
|
||||
className="admin-input w-full text-xs bg-slate-900 border-white/10"
|
||||
>
|
||||
<option value="standard">Standard (กรอบ 1280px)</option>
|
||||
<option value="wide">Wide (ขยายกว้าง 1600px)</option>
|
||||
<option value="full">Full Width (เต็มพื้นที่ 100%)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">สัดส่วนรูปภาพ (Ratio)</label>
|
||||
<select
|
||||
value={payload.imageAspectRatio || 'square'}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value;
|
||||
updatePayloadValue('imageAspectRatio', val);
|
||||
// Also optionally update all tiles
|
||||
const updatedTiles = tiles.map((t: any) => ({ ...t, imageAspectRatio: val }));
|
||||
updatePayloadValue('tiles', updatedTiles);
|
||||
}}
|
||||
className="admin-input w-full text-xs bg-slate-900 border-white/10"
|
||||
>
|
||||
<option value="landscape">Landscape 16:9 (แนวนอนแบบรูป 1)</option>
|
||||
<option value="wide">Wide 4:3 (แนวนอน 4:3)</option>
|
||||
<option value="square">Square 1:1 (จัตุรัสแบบรูป 2)</option>
|
||||
<option value="portrait">Portrait 3:4 (แนวตั้ง 3:4)</option>
|
||||
<option value="tall">Tall 2:3 (แนวสูง 2:3)</option>
|
||||
<option value="auto">Auto / Natural (รูปต้นฉบับ)</option>
|
||||
<option value="landscape">Landscape 16:9 (แบบรูป 1)</option>
|
||||
<option value="wide">Wide 4:3</option>
|
||||
<option value="square">Square 1:1 (แบบรูป 2)</option>
|
||||
<option value="portrait">Portrait 3:4</option>
|
||||
<option value="tall">Tall 2:3</option>
|
||||
<option value="auto">Auto (รูปต้นฉบับ)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">การจัดวางข้อความ (Text Alignment)</label>
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">ข้อความการ์ด (Card Align)</label>
|
||||
<select
|
||||
value={payload.textAlign || 'center'}
|
||||
onChange={(e) => updatePayloadValue('textAlign', e.target.value)}
|
||||
@@ -251,30 +291,67 @@ export default function FeaturedTilesForm({
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">สไตล์ปุ่ม / CTA (Button Style)</label>
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">สไตล์ปุ่ม / CTA</label>
|
||||
<select
|
||||
value={payload.ctaStyle || 'pill_button'}
|
||||
onChange={(e) => updatePayloadValue('ctaStyle', e.target.value)}
|
||||
className="admin-input w-full text-xs bg-slate-900 border-white/10"
|
||||
>
|
||||
<option value="arrow_link">Text Link + ลูกศร (Shop Now → แบบรูป 1)</option>
|
||||
<option value="pill_button">ปุ่มแคปซูลวงรี (Outline Capsule แบบรูป 2)</option>
|
||||
<option value="solid_button">ปุ่มทึบ (Solid Button)</option>
|
||||
<option value="none">ซ่อนปุ่ม (ไม่มีปุ่ม)</option>
|
||||
<option value="arrow_link">Link + ลูกศร (แบบรูป 1)</option>
|
||||
<option value="pill_button">ปุ่มแคปซูล (แบบรูป 2)</option>
|
||||
<option value="solid_button">ปุ่มทึบ</option>
|
||||
<option value="none">ซ่อนปุ่ม</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">ความโค้งมนของภาพ (Corner Radius)</label>
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">ความโค้งมน (Radius)</label>
|
||||
<select
|
||||
value={payload.imageRounded || '3xl'}
|
||||
onChange={(e) => updatePayloadValue('imageRounded', e.target.value)}
|
||||
className="admin-input w-full text-xs bg-slate-900 border-white/10"
|
||||
>
|
||||
<option value="none">ขอบคม (None 0px)</option>
|
||||
<option value="md">มนเรียบหรู (Medium 12px แบบรูป 1)</option>
|
||||
<option value="2xl">มนเด่น (Rounded 2xl 16px)</option>
|
||||
<option value="3xl">มนกลมพิเศษ (Rounded 3xl 24px แบบรูป 2)</option>
|
||||
<option value="md">มนเรียบหรู (12px แบบรูป 1)</option>
|
||||
<option value="2xl">มนเด่น (16px)</option>
|
||||
<option value="3xl">มนกลมพิเศษ (24px แบบรูป 2)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Padding & Spacing Controls (Full Control) ── */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 pt-3 border-t border-white/5">
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">ระยะห่างด้านบน (Top Padding)</label>
|
||||
<span className="text-[10px] text-cyan-glow/80 font-mono">{payload.paddingTop || payload.sectionPadding || 'normal'}</span>
|
||||
</div>
|
||||
<select
|
||||
value={payload.paddingTop || payload.sectionPadding || 'normal'}
|
||||
onChange={(e) => updatePayloadValue('paddingTop', e.target.value)}
|
||||
className="admin-input w-full text-xs bg-slate-900 border-white/10"
|
||||
>
|
||||
<option value="none">ไม่มีระยะห่างด้านบน (None - 0px)</option>
|
||||
<option value="compact">กะทัดรัด (Compact - 24-40px)</option>
|
||||
<option value="normal">มาตรฐาน (Normal - 40-56px)</option>
|
||||
<option value="spacious">กว้างโปร่ง (Spacious - 80-96px)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">ระยะห่างด้านล่าง (Bottom Padding)</label>
|
||||
<span className="text-[10px] text-cyan-glow/80 font-mono">{payload.paddingBottom || payload.sectionPadding || 'normal'}</span>
|
||||
</div>
|
||||
<select
|
||||
value={payload.paddingBottom || payload.sectionPadding || 'normal'}
|
||||
onChange={(e) => updatePayloadValue('paddingBottom', e.target.value)}
|
||||
className="admin-input w-full text-xs bg-slate-900 border-white/10"
|
||||
>
|
||||
<option value="none">ไม่มีระยะห่างด้านล่าง (None - 0px)</option>
|
||||
<option value="compact">กะทัดรัด (Compact - 24-40px)</option>
|
||||
<option value="normal">มาตรฐาน (Normal - 40-56px)</option>
|
||||
<option value="spacious">กว้างโปร่ง (Spacious - 80-96px)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1348,27 +1348,43 @@
|
||||
"hidePayload": "Hide Payload",
|
||||
"copyPayload": "Copy Payload",
|
||||
"copied": "Copied",
|
||||
"copyLlmPrompt": "Copy LLM Prompt",
|
||||
"llmPromptCopied": "Prompt Copied",
|
||||
"schemaSpec": "Schema Spec",
|
||||
"hideSchema": "Hide Spec",
|
||||
"desktopView": "Desktop (100%)",
|
||||
"mobileView": "Mobile (375px)",
|
||||
"categories": {
|
||||
"all": "All Blocks",
|
||||
"hero": "Banner & Hero",
|
||||
"minimal": "Minimal & Luxury",
|
||||
"products": "Products & Shop",
|
||||
"editorial": "Story & Editorial",
|
||||
"articles": "Article Elements",
|
||||
"forms": "Forms & Info"
|
||||
"forms": "Forms & Info",
|
||||
"layout": "Layout & Custom"
|
||||
},
|
||||
"blocks": {
|
||||
"ANNOUNCEMENT_BAR": "Top notice bar with custom styling and scrolling announcements.",
|
||||
"HERO": "Premium full-screen cover with headline, background image, and CTA.",
|
||||
"HERO_V2": "Split Layout Hero featuring text on one side and a premium image card on the other.",
|
||||
"HERO_VIDEO": "Cinema-quality full background video banner with animated overlay typography and dual CTA buttons.",
|
||||
"HERO_CAROUSEL": "Mega promotional banner carousel with auto-sliding timer, navigation arrows, dots, and mobile aspect ratios.",
|
||||
"HERO_MINIMAL": "Quiet Luxury full-width centered typography hero with adjustable overlay darkness.",
|
||||
"HERO_SPLIT_MINIMAL": "Minimalist split layout hero with pure typography on one side and curated imagery on the other.",
|
||||
"HERO_VIDEO_MINIMAL": "Atmospheric silent video hero showcasing nature and serene motion.",
|
||||
"BANNER_MINIMAL": "Quiet luxury typographic banner with concise headline and destination link.",
|
||||
"ANNOUNCEMENT_BAR_MINIMAL": "Minimalist scrolling announcements ticker with dot separators.",
|
||||
"USP_MINIMAL": "Horizontal brand commitments and USP ticker with dot/pipe delimiters.",
|
||||
"USP_BAR": "Unique Selling Proposition Bar highlighting product perks (Icons & Labels).",
|
||||
"BANNER": "Clickable promotional block with headline overlay, ideal for campaigns.",
|
||||
"CATEGORIES": "Visual category grid showcase to guide users to specific collections.",
|
||||
"PRODUCTS": "Showcase grid or carousel for featured products, brand items, or a customized list with Quick Shop option.",
|
||||
"PRODUCT_CAROUSEL": "Swipeable horizontal card carousel displaying specific product collections.",
|
||||
"PRODUCT_LAYOUT": "Full product detail page layout template including media gallery, buy box, specs tabs, and reviews.",
|
||||
"RELATED_PRODUCTS": "Dynamic block recommending items from the same category for the product page layout.",
|
||||
"EDITORIAL_TILE": "Stunning editorial grid block presenting storytelling text alongside a curated campaign photo.",
|
||||
"FEATURED_TILES": "Side-by-side campaign grid featuring square or tall images with centered titles and outline CTA buttons (Jo Malone aesthetic).",
|
||||
"EDITORIAL_TILE": "Stunning editorial grid block presenting storytelling text alongside a curated campaign photo with adjustable aspect ratio.",
|
||||
"FEATURED_TILES": "Side-by-side campaign grid featuring square or landscape images with dual-level links (Jo Malone aesthetic).",
|
||||
"EDITORIAL_CAROUSEL": "Stories and values slider displaying image slideshows side-by-side with localized editorial statements.",
|
||||
"SCENT_NOTES_BREAKDOWN": "Fragrance profile breakdowns containing Top, Heart/Middle, and Base notes lists.",
|
||||
"QUOTE_CALLOUT": "Elegant text banner presenting callout quotations, ideal for customer testimonials.",
|
||||
@@ -1387,7 +1403,9 @@
|
||||
"NEWSLETTER": "Email subscription block to attract newsletter sign-ups.",
|
||||
"ACCORDION": "Question-answer accordions for FAQs, divided by custom categories.",
|
||||
"CONTACT_INFO": "Beautiful information box showing address, telephone, and social details.",
|
||||
"CONTACT_FORM": "General support email inquiry contact form."
|
||||
"CONTACT_FORM": "General support email inquiry contact form.",
|
||||
"DIVIDER": "Section separation line with configurable style, thickness, spacing, color, and container width.",
|
||||
"CUSTOM": "Dynamic extensible white-label block supporting custom components and flexible props."
|
||||
}
|
||||
},
|
||||
"repeatCustomers": {
|
||||
|
||||
@@ -1347,27 +1347,43 @@
|
||||
"hidePayload": "ซ่อน Payload",
|
||||
"copyPayload": "คัดลอก Payload",
|
||||
"copied": "คัดลอกแล้ว",
|
||||
"copyLlmPrompt": "คัดลอก LLM Prompt",
|
||||
"llmPromptCopied": "คัดลอก Prompt แล้ว",
|
||||
"schemaSpec": "สเปกโครงสร้าง (Schema)",
|
||||
"hideSchema": "ซ่อนสเปก",
|
||||
"desktopView": "เดสก์ท็อป (100%)",
|
||||
"mobileView": "มือถือ (375px)",
|
||||
"categories": {
|
||||
"all": "ทุกบล็อก",
|
||||
"hero": "แบนเนอร์ & Hero",
|
||||
"minimal": "มินิมอล & ลักชูรี",
|
||||
"products": "สินค้า & ร้านค้า",
|
||||
"editorial": "เรื่องราว & บทความ",
|
||||
"articles": "องค์ประกอบบทความ",
|
||||
"forms": "ฟอร์ม & ข้อมูลติดต่อ"
|
||||
"forms": "ฟอร์ม & ข้อมูลติดต่อ",
|
||||
"layout": "เลย์เอาต์ & ส่วนเสริม"
|
||||
},
|
||||
"blocks": {
|
||||
"ANNOUNCEMENT_BAR": "แถบประกาศด้านบนสุด พร้อมข้อความวิ่งและการจัดสไตล์ที่กำหนดเองได้",
|
||||
"HERO": "แบนเนอร์เต็มจอระดับพรีเมียม พร้อมหัวข้อ รูปพื้นหลัง และปุ่ม Call-to-Action",
|
||||
"HERO_V2": "แบนเนอร์ Hero แบบแยกสองฝั่ง จัดวางข้อความเด่นฝั่งหนึ่งและการ์ดภาพสินค้าอีกฝั่ง",
|
||||
"HERO_VIDEO": "แบนเนอร์วิดีโอพื้นหลังคุณภาพระดับภาพยนตร์ พร้อมตัวอักษรและปุ่ม CTA คู่",
|
||||
"HERO_CAROUSEL": "สไลด์แบนเนอร์โปรโมชันขนาดใหญ่ พร้อมตัวตั้งเวลาเลื่อนอัตโนมัติ ปุ่มลูกศร และปรับสัดส่วนจอมือถือได้",
|
||||
"HERO_MINIMAL": "แบนเนอร์ภาพเต็มจอข้อความกึ่งกลางสไตล์ Quiet Luxury พร้อมชั้นเลเยอร์สีทึบปรับความเข้มได้",
|
||||
"HERO_SPLIT_MINIMAL": "แบนเนอร์ข้อความสลับภาพครึ่งฝั่งแบบเรียบหรูระดับพรีเมียม",
|
||||
"HERO_VIDEO_MINIMAL": "แบนเนอร์วิดีโอแบบเงียบสงบ มินิมอล พร้อมสโลแกนและหัวข้อบรรยากาศธรรมชาติ",
|
||||
"BANNER_MINIMAL": "แบนเนอร์ข้อความและลิงก์แนวเรียบง่ายแต่ทรงพลัง สำหรับแจ้งสิทธิพิเศษ",
|
||||
"ANNOUNCEMENT_BAR_MINIMAL": "แถบประกาศเลื่อนข้อความสไตล์ลักชูรี พร้อมตัวคั่นจุด",
|
||||
"USP_MINIMAL": "แถบจุดเด่นสินค้าแบบ Ticker แนวนอน พร้อมตัวคั่นจุดหรือเส้น",
|
||||
"USP_BAR": "แถบจุดเด่นสินค้า (USP) แสดงไอคอนและข้อความการันตีคุณภาพ",
|
||||
"BANNER": "แบนเนอร์โปรโมชันแบบคลิกได้ พร้อมข้อความทับบนรูปภาพ เหมาะสำหรับแคมเปญ",
|
||||
"CATEGORIES": "กริดแสดงหมวดหมู่สินค้าเพื่อนำทางลูกค้าไปยังคอลเลกชันที่ต้องการ",
|
||||
"PRODUCTS": "บล็อกแสดงสินค้าแบบกริดหรือสไลด์สำหรับสินค้าแนะนำ หรือเลือกสินค้าพร้อมระบบสั่งด่วน",
|
||||
"PRODUCT_CAROUSEL": "สไลด์การ์ดสินค้าแนวนอน เลื่อนดูสินค้าในคอลเลกชันได้อย่างลื่นไหล",
|
||||
"PRODUCT_LAYOUT": "โครงสร้างหน้าแสดงรายละเอียดสินค้าแบบครบวงจร (แกลเลอรี, ตะกร้า, แท็บสเปก, รีวิว)",
|
||||
"RELATED_PRODUCTS": "บล็อกแนะนำสินค้าที่เกี่ยวข้องจากหมวดหมู่เดียวกัน สำหรับหน้ารายละเอียดสินค้า",
|
||||
"EDITORIAL_TILE": "บล็อกเล่าเรื่องสไตล์ Editorial จัดวางเนื้อหาคู่กับภาพถ่ายแคมเปญ",
|
||||
"FEATURED_TILES": "กริดแคมเปญภาพคู่แบบจัตุรัสหรือแนวตั้ง พร้อมหัวข้อกึ่งกลางและปุ่มเรียบหรู (Jo Malone Style)",
|
||||
"EDITORIAL_TILE": "บล็อกเล่าเรื่องสไตล์ Editorial จัดวางเนื้อหาคู่กับภาพถ่ายแคมเปญ พร้อมปรับสัดส่วนภาพได้",
|
||||
"FEATURED_TILES": "กริดแคมเปญภาพคู่แบบจัตุรัส/แนวนอน พร้อมหัวข้อกึ่งกลางหรือชิดซ้าย และปุ่มลิงก์สองระดับ",
|
||||
"EDITORIAL_CAROUSEL": "สไลด์เรื่องราวและคุณค่าของแบรนด์ แสดงภาพคู่กับข้อความบรรยายอย่างลงตัว",
|
||||
"SCENT_NOTES_BREAKDOWN": "โครงสร้างจำแนกระดับกลิ่นหอม แสดงโน้ต Top, Heart/Middle และ Base",
|
||||
"QUOTE_CALLOUT": "กล่องโควทข้อความและคำคมระดับพรีเมียม เหมาะสำหรับรีวิวและคำนิยม",
|
||||
@@ -1386,7 +1402,9 @@
|
||||
"NEWSLETTER": "ฟอร์มรับสมัครจดหมายข่าว เพื่อรับส่วนลดและข่าวสารโปรโมชัน",
|
||||
"ACCORDION": "แถบพับเปิด-ปิดสำหรับคำถามที่พบบ่อย (FAQs) แยกตามหมวดหมู่",
|
||||
"CONTACT_INFO": "กล่องข้อมูลติดต่อ แสดงที่อยู่ เบอร์โทร อีเมล และโซเชียลมีเดียอย่างสวยงาม",
|
||||
"CONTACT_FORM": "ฟอร์มส่งข้อความติดต่อสอบถามทั่วไปทางอีเมล"
|
||||
"CONTACT_FORM": "ฟอร์มส่งข้อความติดต่อสอบถามทั่วไปทางอีเมล",
|
||||
"DIVIDER": "เส้นคั่นแบ่งส่วน ปรับสไตล์ ความหนา ช่องไฟ สี และความกว้างได้",
|
||||
"CUSTOM": "บล็อกส่วนต่อขยายไดนามิกสำหรับแบรนด์และหมวดหมู่เฉพาะ (White-label Extensible Block)"
|
||||
}
|
||||
},
|
||||
"repeatCustomers": {
|
||||
|
||||
+1014
-586
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
// Trigger reload: support landscape and wide aspect ratios for FEATURED_TILES and EDITORIAL_TILE
|
||||
// Trigger reload: support paddingTop, paddingBottom, and sectionPadding for FEATURED_TILES
|
||||
import {
|
||||
LocalizedStringSchema,
|
||||
HeroSectionPayloadSchema,
|
||||
|
||||
@@ -199,8 +199,8 @@ const PAGE_BLOCK_METADATA: Record<string, BlockMetadata> = {
|
||||
FEATURED_TILES: {
|
||||
type: "FEATURED_TILES",
|
||||
requiredFields: ["tiles[].title", "tiles[].imageUrl"],
|
||||
optionalFields: ["title", "subtitle", "columns", "backgroundColor", "textColor", "tiles[].description", "tiles[].buttonText", "tiles[].buttonLink", "tiles[].imageAspectRatio"],
|
||||
renderedFields: ["title", "subtitle", "columns", "backgroundColor", "textColor", "tiles[].title", "tiles[].description", "tiles[].imageUrl", "tiles[].buttonText", "tiles[].buttonLink", "tiles[].imageAspectRatio"],
|
||||
optionalFields: ["title", "subtitle", "headerAlign", "showHeading", "columns", "containerWidth", "paddingTop", "paddingBottom", "sectionPadding", "layoutPreset", "textAlign", "ctaStyle", "imageRounded", "backgroundColor", "textColor", "tiles[].description", "tiles[].buttonText", "tiles[].buttonLink", "tiles[].imageAspectRatio"],
|
||||
renderedFields: ["title", "subtitle", "headerAlign", "showHeading", "columns", "containerWidth", "paddingTop", "paddingBottom", "sectionPadding", "layoutPreset", "textAlign", "ctaStyle", "imageRounded", "backgroundColor", "textColor", "tiles[].title", "tiles[].description", "tiles[].imageUrl", "tiles[].buttonText", "tiles[].buttonLink", "tiles[].imageAspectRatio"],
|
||||
storedOnlyFields: [],
|
||||
discoveryToolsNeeded: [],
|
||||
warnings: ["columns should be 2, 3, or 4."],
|
||||
|
||||
@@ -71,6 +71,47 @@ export default function DynamicFeaturedTiles({ payload }: DynamicFeaturedTilesPr
|
||||
}
|
||||
};
|
||||
|
||||
const containerWidth = payload.containerWidth || 'standard';
|
||||
|
||||
const getContainerWidthClass = (width: string) => {
|
||||
switch (width) {
|
||||
case 'full':
|
||||
return 'w-full max-w-none';
|
||||
case 'wide':
|
||||
return 'w-full max-w-[1600px] mx-auto';
|
||||
case 'standard':
|
||||
default:
|
||||
return 'ui-section-inner';
|
||||
}
|
||||
};
|
||||
|
||||
const headerAlign = payload.headerAlign || (layoutPreset === 'editorial' ? 'left' : 'center');
|
||||
const showHeading = payload.showHeading !== false && headerAlign !== 'none';
|
||||
|
||||
const getHeaderAlignClass = (align: string) => {
|
||||
switch (align) {
|
||||
case 'left':
|
||||
return 'text-left mr-auto items-start';
|
||||
case 'right':
|
||||
return 'text-right ml-auto items-end';
|
||||
case 'center':
|
||||
default:
|
||||
return 'text-center mx-auto items-center';
|
||||
}
|
||||
};
|
||||
|
||||
const getAccentLineClass = (align: string) => {
|
||||
switch (align) {
|
||||
case 'left':
|
||||
return 'mr-auto';
|
||||
case 'right':
|
||||
return 'ml-auto';
|
||||
case 'center':
|
||||
default:
|
||||
return 'mx-auto';
|
||||
}
|
||||
};
|
||||
|
||||
const isLeftAlign = textAlign === 'left';
|
||||
const isRightAlign = textAlign === 'right';
|
||||
const alignContainerClass = isLeftAlign
|
||||
@@ -79,15 +120,46 @@ export default function DynamicFeaturedTiles({ payload }: DynamicFeaturedTilesPr
|
||||
? 'items-end text-right'
|
||||
: 'items-center text-center';
|
||||
|
||||
const padTop = payload.paddingTop || payload.sectionPadding || 'normal';
|
||||
const padBottom = payload.paddingBottom || payload.sectionPadding || 'normal';
|
||||
|
||||
const getPaddingTopClass = (pad: string) => {
|
||||
switch (pad) {
|
||||
case 'none':
|
||||
return 'pt-0';
|
||||
case 'compact':
|
||||
return 'pt-6 md:pt-10';
|
||||
case 'spacious':
|
||||
return 'pt-20 md:pt-28';
|
||||
case 'normal':
|
||||
default:
|
||||
return 'pt-10 md:pt-16';
|
||||
}
|
||||
};
|
||||
|
||||
const getPaddingBottomClass = (pad: string) => {
|
||||
switch (pad) {
|
||||
case 'none':
|
||||
return 'pb-0';
|
||||
case 'compact':
|
||||
return 'pb-6 md:pb-10';
|
||||
case 'spacious':
|
||||
return 'pb-20 md:pb-28';
|
||||
case 'normal':
|
||||
default:
|
||||
return 'pb-10 md:pb-16';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
className="ui-section-band w-full overflow-hidden"
|
||||
className={`w-full overflow-hidden px-4 sm:px-6 md:px-8 ${getPaddingTopClass(padTop)} ${getPaddingBottomClass(padBottom)}`}
|
||||
style={{ backgroundColor: payload.backgroundColor, color: payload.textColor }}
|
||||
>
|
||||
<div className="ui-section-inner space-y-12">
|
||||
<div className={`${getContainerWidthClass(containerWidth)} space-y-6 md:space-y-8`}>
|
||||
{/* Optional Section Header */}
|
||||
{(sectionTitle || sectionSubtitle) && (
|
||||
<div className="text-center space-y-3 max-w-2xl mx-auto">
|
||||
{showHeading && (sectionTitle || sectionSubtitle) && (
|
||||
<div className={`max-w-2xl flex flex-col space-y-3 ${getHeaderAlignClass(headerAlign)}`}>
|
||||
{sectionTitle && (
|
||||
<h2 className="font-serif text-3xl md:text-4xl font-light tracking-wide" style={{ color: payload.textColor }}>
|
||||
{sectionTitle}
|
||||
@@ -98,7 +170,7 @@ export default function DynamicFeaturedTiles({ payload }: DynamicFeaturedTilesPr
|
||||
{sectionSubtitle}
|
||||
</p>
|
||||
)}
|
||||
<div className="w-12 h-[1px] bg-pop-pink mx-auto"></div>
|
||||
<div className={`w-12 h-[1px] bg-pop-pink ${getAccentLineClass(headerAlign)}`}></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1710,6 +1710,19 @@ function FeaturedTilesSection({ payload, lang }: { payload: Record<string, unkno
|
||||
const ctaStyle = typeof payload.ctaStyle === 'string' ? payload.ctaStyle : (layoutPreset === 'editorial' ? 'arrow_link' : 'pill_button')
|
||||
const sectionAspect = typeof payload.imageAspectRatio === 'string' ? payload.imageAspectRatio : (layoutPreset === 'editorial' ? 'landscape' : undefined)
|
||||
const imageRounded = typeof payload.imageRounded === 'string' ? payload.imageRounded : (layoutPreset === 'editorial' ? 'md' : '3xl')
|
||||
const containerWidth = typeof payload.containerWidth === 'string' ? payload.containerWidth : 'standard'
|
||||
|
||||
const getContainerWidthClass = (width: string) => {
|
||||
switch (width) {
|
||||
case 'full':
|
||||
return 'w-full max-w-none'
|
||||
case 'wide':
|
||||
return 'w-full max-w-[1600px] mx-auto'
|
||||
case 'standard':
|
||||
default:
|
||||
return 'w-full max-w-7xl mx-auto'
|
||||
}
|
||||
}
|
||||
|
||||
const getColsClass = (cols: number) => {
|
||||
switch (cols) {
|
||||
@@ -1760,6 +1773,35 @@ function FeaturedTilesSection({ payload, lang }: { payload: Record<string, unkno
|
||||
}
|
||||
}
|
||||
|
||||
const headerAlign = typeof payload.headerAlign === 'string'
|
||||
? payload.headerAlign
|
||||
: (layoutPreset === 'editorial' ? 'left' : 'center')
|
||||
const showHeading = payload.showHeading !== false && headerAlign !== 'none'
|
||||
|
||||
const getHeaderAlignClass = (align: string) => {
|
||||
switch (align) {
|
||||
case 'left':
|
||||
return 'text-left mr-auto items-start'
|
||||
case 'right':
|
||||
return 'text-right ml-auto items-end'
|
||||
case 'center':
|
||||
default:
|
||||
return 'text-center mx-auto items-center'
|
||||
}
|
||||
}
|
||||
|
||||
const getAccentLineClass = (align: string) => {
|
||||
switch (align) {
|
||||
case 'left':
|
||||
return 'mr-auto'
|
||||
case 'right':
|
||||
return 'ml-auto'
|
||||
case 'center':
|
||||
default:
|
||||
return 'mx-auto'
|
||||
}
|
||||
}
|
||||
|
||||
const isLeftAlign = textAlign === 'left'
|
||||
const isRightAlign = textAlign === 'right'
|
||||
const alignContainerClass = isLeftAlign
|
||||
@@ -1768,18 +1810,57 @@ function FeaturedTilesSection({ payload, lang }: { payload: Record<string, unkno
|
||||
? 'items-end text-right'
|
||||
: 'items-center text-center'
|
||||
|
||||
const padTop = typeof payload.paddingTop === 'string'
|
||||
? payload.paddingTop
|
||||
: (typeof payload.sectionPadding === 'string' ? payload.sectionPadding : 'normal')
|
||||
|
||||
const padBottom = typeof payload.paddingBottom === 'string'
|
||||
? payload.paddingBottom
|
||||
: (typeof payload.sectionPadding === 'string' ? payload.sectionPadding : 'normal')
|
||||
|
||||
const getPaddingTopClass = (pad: string) => {
|
||||
switch (pad) {
|
||||
case 'none':
|
||||
return 'pt-0'
|
||||
case 'compact':
|
||||
return 'pt-6 md:pt-10'
|
||||
case 'spacious':
|
||||
return 'pt-20 md:pt-28'
|
||||
case 'normal':
|
||||
default:
|
||||
return 'pt-10 md:pt-16'
|
||||
}
|
||||
}
|
||||
|
||||
const getPaddingBottomClass = (pad: string) => {
|
||||
switch (pad) {
|
||||
case 'none':
|
||||
return 'pb-0'
|
||||
case 'compact':
|
||||
return 'pb-6 md:pb-10'
|
||||
case 'spacious':
|
||||
return 'pb-20 md:pb-28'
|
||||
case 'normal':
|
||||
default:
|
||||
return 'pb-10 md:pb-16'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="overflow-hidden bg-surface-page ui-section-band" style={{ backgroundColor, color: textColor }}>
|
||||
<div className="mx-auto max-w-7xl space-y-12">
|
||||
{(title || subtitle) && (
|
||||
<div className="mx-auto max-w-2xl text-center space-y-3">
|
||||
<section
|
||||
className={`overflow-hidden bg-surface-page px-6 ${getPaddingTopClass(padTop)} ${getPaddingBottomClass(padBottom)}`}
|
||||
style={{ backgroundColor, color: textColor }}
|
||||
>
|
||||
<div className={`${getContainerWidthClass(containerWidth)} space-y-6 md:space-y-8`}>
|
||||
{showHeading && (title || subtitle) && (
|
||||
<div className={`max-w-2xl flex flex-col space-y-3 ${getHeaderAlignClass(headerAlign)}`}>
|
||||
{title && (
|
||||
<h2 className="text-3xl font-semibold leading-tight text-text-main md:text-5xl" style={{ color: textColor }}>
|
||||
{renderAccentTitle(title)}
|
||||
</h2>
|
||||
)}
|
||||
{subtitle && <p className="text-base leading-relaxed text-muted-text md:text-lg" style={{ color: textColor }}>{subtitle}</p>}
|
||||
<div className="mx-auto h-px w-14 bg-brand-primary" />
|
||||
<div className={`h-px w-14 bg-brand-primary ${getAccentLineClass(headerAlign)}`} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -729,10 +729,16 @@ export const FeaturedTilesSectionPayloadSchema = z.object({
|
||||
subtitle: LocalizedStringSchema.optional(),
|
||||
columns: z.number().optional().default(2),
|
||||
layoutPreset: z.enum(['editorial', 'spotlight', 'custom']).optional(),
|
||||
headerAlign: z.enum(['left', 'center', 'right', 'none']).optional().default('center'),
|
||||
showHeading: z.boolean().optional().default(true),
|
||||
textAlign: z.enum(['left', 'center', 'right']).optional(),
|
||||
ctaStyle: z.enum(['arrow_link', 'pill_button', 'solid_button', 'none']).optional(),
|
||||
imageAspectRatio: z.enum(['landscape', 'wide', 'square', 'portrait', 'tall', 'auto']).optional(),
|
||||
imageRounded: z.enum(['none', 'sm', 'md', 'xl', '2xl', '3xl', 'full']).optional(),
|
||||
containerWidth: z.enum(['standard', 'wide', 'full']).optional().default('standard'),
|
||||
paddingTop: z.enum(['none', 'compact', 'normal', 'spacious']).optional(),
|
||||
paddingBottom: z.enum(['none', 'compact', 'normal', 'spacious']).optional(),
|
||||
sectionPadding: z.enum(['none', 'compact', 'normal', 'spacious']).optional().default('normal'),
|
||||
backgroundColor: z.string().optional(),
|
||||
textColor: z.string().optional(),
|
||||
tiles: z.array(z.object({
|
||||
|
||||
Reference in new issue
Block a user