This commit is contained in:
Your Name committed 2026-10-06 19:40:15 +07:00
1 parent ce4502d123
commit acee55a633
14 files changed
+634 -104

No files matched your search

@@ -126,8 +126,62 @@ export default function ProductCarouselForm({
}
className="admin-input w-full bg-slate-900 border-white/10"
>
<option value="standard">Standard (Retail with Add to Cart)</option>
<option value="minimal_swatch">Minimal Swatch (Editorial with Color Dots)</option>
<option value="standard">Standard Retail (1:1 Boxed with Add to Cart)</option>
<option value="minimal">Fashion Minimal (3:4 Portrait, Pomelo/Zara Style)</option>
<option value="swatch">Editorial Swatch (Lookbook with Swatch Dots)</option>
</select>
</div>
<div className="space-y-2">
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">ความกว้างบล็อก (Width)</label>
<select
value={editingSection.payload?.containerWidth || 'standard'}
onChange={(e) =>
setEditingSection(prev => prev ? {
...prev,
payload: { ...(prev.payload || {}), containerWidth: e.target.value }
} : null)
}
className="admin-input w-full 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-2">
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">ขนาดการ์ด (Card Size)</label>
<select
value={editingSection.payload?.cardSize || 'medium'}
onChange={(e) =>
setEditingSection(prev => prev ? {
...prev,
payload: { ...(prev.payload || {}), cardSize: e.target.value }
} : null)
}
className="admin-input w-full bg-slate-900 border-white/10"
>
<option value="medium">Medium (~4 ใบต่อหน้าจอ สไตล์ Pomelo - แนะนำ)</option>
<option value="large">Large (~3-4 ใบต่อหน้าจอ ขนาดใหญ่พิเศษ)</option>
<option value="compact">Compact (~5-6 ใบต่อหน้าจอ ขนาดกะทัดรัด)</option>
</select>
</div>
<div className="space-y-2">
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">รูปแบบการแสดง (Display Layout)</label>
<select
value={editingSection.payload?.layout || 'carousel'}
onChange={(e) =>
setEditingSection(prev => prev ? {
...prev,
payload: { ...(prev.payload || {}), layout: e.target.value }
} : null)
}
className="admin-input w-full bg-slate-900 border-white/10"
>
<option value="carousel">Carousel Slider (สไลด์เลื่อนแนวนอน)</option>
<option value="grid">Grid Layout (ตารางสินค้า แถวละ 4 ใบ แบบ Pomelo)</option>
</select>
</div>
@@ -116,6 +116,42 @@ export default function ProductsForm({
</select>
</div>
<div className="space-y-2">
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">ความกว้างบล็อก (Width)</label>
<select
value={editingSection.payload?.containerWidth || 'standard'}
onChange={(e) =>
setEditingSection(prev => prev ? {
...prev,
payload: { ...(prev.payload || {}), containerWidth: e.target.value }
} : null)
}
className="admin-input w-full 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-2">
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">ขนาดการ์ด (Card Size)</label>
<select
value={editingSection.payload?.cardSize || 'medium'}
onChange={(e) =>
setEditingSection(prev => prev ? {
...prev,
payload: { ...(prev.payload || {}), cardSize: e.target.value }
} : null)
}
className="admin-input w-full bg-slate-900 border-white/10"
>
<option value="medium">Medium (~4 ใบต่อหน้าจอ สไตล์ Pomelo - แนะนำ)</option>
<option value="large">Large (~3-4 ใบต่อหน้าจอ ขนาดใหญ่พิเศษ)</option>
<option value="compact">Compact (~5-6 ใบต่อหน้าจอ ขนาดกะทัดรัด)</option>
</select>
</div>
<div className="space-y-2 flex flex-col justify-end">
<label className="flex items-center gap-3 p-3 border border-white/5 bg-white/5 rounded-xl cursor-pointer select-none">
<input
+3
View File
@@ -935,6 +935,9 @@ export default function PageBuilderPage() {
showPrice: true,
showAddToCart: true,
cardStyle: 'standard',
containerWidth: 'standard',
cardSize: 'medium',
layout: 'carousel',
};
break;
case 'PRODUCT_MAIN':
+87 -2
View File
@@ -18,7 +18,8 @@ import {
Bot,
ArrowUpDown,
Monitor,
Smartphone
Smartphone,
LayoutGrid
} from 'lucide-react'
const defaultThemeTokens = {
@@ -167,6 +168,7 @@ export default function ThemeSettingsPage() {
const [headerBehavior, setHeaderBehavior] = useState<'always_sticky' | 'hide_on_scroll'>('hide_on_scroll')
const [mobileHeaderStyle, setMobileHeaderStyle] = useState<'logo_center' | 'logo_left' | 'search_bar_below'>('logo_center')
const [mobileHeaderBehavior, setMobileHeaderBehavior] = useState<'always_sticky' | 'hide_on_scroll'>('hide_on_scroll')
const [cardPreset, setCardPreset] = useState<'standard' | 'minimal' | 'swatch'>('standard')
// JSON Quick Copy / Paste State
const [isJsonBoxOpen, setIsJsonBoxOpen] = useState(false)
@@ -197,6 +199,8 @@ export default function ThemeSettingsPage() {
setHeaderBehavior(pcBehavior)
setMobileHeaderStyle((data as any).mobileHeaderStyle || (data.themeTokens as any)?.mobileHeaderStyle || 'logo_center')
setMobileHeaderBehavior((data as any).mobileHeaderBehavior || (data.themeTokens as any)?.mobileHeaderBehavior || pcBehavior)
const cp = (data as any).cardPreset || (data.themeTokens as any)?.cardPreset || 'standard'
setCardPreset(cp === 'minimal_swatch' ? 'swatch' : cp)
})
.catch(console.error)
.finally(() => setLoading(false))
@@ -217,7 +221,9 @@ export default function ThemeSettingsPage() {
headerBehavior,
mobileHeaderBehavior,
mobileHeaderStyle,
cardPreset,
},
cardPreset,
scrollRevealEnabled,
scrollRevealStyle,
scrollRevealDuration: parseInt(scrollRevealDuration) || 700,
@@ -444,7 +450,9 @@ export default function ThemeSettingsPage() {
headerBehavior,
mobileHeaderBehavior,
mobileHeaderStyle,
cardPreset,
},
cardPreset,
scrollRevealEnabled,
scrollRevealStyle,
scrollRevealDuration: parseInt(scrollRevealDuration) || 700,
@@ -1064,7 +1072,84 @@ export default function ThemeSettingsPage() {
</div>
</div>
{/* ── 4. Desktop (PC) Header Settings ── */}
{/* ── 4. Product Card UI Presets ── */}
<div className="admin-card p-6 md:p-8 space-y-6">
<div className="flex items-center gap-3 border-b border-white/10 pb-4">
<div className="p-2 bg-emerald-500/10 rounded-xl text-emerald-400">
<LayoutGrid className="w-5 h-5" />
</div>
<div>
<h2 className="text-lg font-bold text-white">Product Card UI Preset</h2>
<p className="text-xs text-slate-400">เลือกรูปแบบการ์ดสินค้าหลักสำหรับทั้งร้านค้า (Catalog, Search & Default)</p>
</div>
</div>
<div className="grid gap-6 md:grid-cols-3">
{[
{
id: 'standard',
title: 'Standard Retail',
badge: 'Default Retail',
desc: 'กล่องการ์ดมีขอบ, ภาพ 1:1 จัตุรัส, ปุ่ม Select Options / Add to Cart ใต้ราคา เหมาะกับร้านค้าทั่วไปหรือสินค้าอิเล็กทรอนิกส์/ของใช้',
features: ['1:1 Square Image', 'Bordered Card Container', 'Action Button (Add to Cart)', 'Category Badge'],
},
{
id: 'minimal',
title: 'Fashion Minimal',
badge: 'Pomelo / Zara Style',
desc: 'สไตล์แฟชั่นมินิมอล ภาพแนวตั้ง 3:4 ไร้กรอบแบนราบ แบรนด์ตัวหนา หัวใจ Wishlist แสดงยอดขาย คลีนหรูหรา',
features: ['3:4 Portrait Image', 'Borderless Flat Layout', 'Brand Name & Wishlist Heart', 'Price & Stock Status'],
},
{
id: 'swatch',
title: 'Editorial Swatch',
badge: 'Lookbook / New Arrivals',
desc: 'สไตล์นิตยสารมินิมอล เน้นจุดสีกลม (Color Swatch Dots) และชื่อสินค้าแบบเรียบหรู เหมาะกับสินค้าหลายเฉดสี',
features: ['Color Swatch Dots', 'Clean Typography', 'Frameless Lookbook', 'Price with Subtitle'],
},
].map((preset) => {
const isSelected = cardPreset === preset.id
return (
<div
key={preset.id}
onClick={() => setCardPreset(preset.id as any)}
className={`relative p-5 rounded-2xl border transition-all cursor-pointer flex flex-col justify-between ${
isSelected
? 'border-emerald-500/60 bg-emerald-500/10 ring-1 ring-emerald-500/40 shadow-lg'
: 'border-white/10 bg-night-900/60 hover:border-white/20 hover:bg-white/5'
}`}
>
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-full bg-white/10 text-slate-300">
{preset.badge}
</span>
<div className={`w-4 h-4 rounded-full border flex items-center justify-center ${isSelected ? 'border-emerald-400 bg-emerald-400' : 'border-slate-500'}`}>
{isSelected && <div className="w-1.5 h-1.5 rounded-full bg-black" />}
</div>
</div>
<div>
<h3 className="text-base font-bold text-white">{preset.title}</h3>
<p className="text-xs text-slate-400 mt-1 leading-relaxed">{preset.desc}</p>
</div>
<div className="pt-2 border-t border-white/5 space-y-1">
{preset.features.map((feat, idx) => (
<div key={idx} className="flex items-center gap-1.5 text-[11px] text-slate-300">
<span className="text-emerald-400 font-bold">✓</span>
<span>{feat}</span>
</div>
))}
</div>
</div>
</div>
)
})}
</div>
</div>
{/* ── 5. Desktop (PC) Header Settings ── */}
<div className="admin-card p-6 md:p-8 space-y-6">
<div className="flex items-center gap-3 border-b border-white/10 pb-4">
<div className="p-2 bg-amber-500/10 rounded-xl text-amber-400">