This commit is contained in:
Your Name committed 2026-10-04 19:56:23 +07:00
1 parent e349ef5b84
commit 8d415ecef7
14 files changed
+372 -119

No files matched your search

@@ -265,6 +265,10 @@ export default function CategoryHeroCarouselBlockForm({
mobilePosition={getString(slide.mobileButtonPosition) || undefined}
onChangeDesktop={(pos) => updateSlide(slideIndex, { buttonPosition: pos })}
onChangeMobile={(pos) => updateSlide(slideIndex, { mobileButtonPosition: pos || '' })}
contentWidth={getString(slide.contentWidth, 'standard')}
onChangeContentWidth={(w) => updateSlide(slideIndex, { contentWidth: w })}
textColorScheme={getString(slide.textColorScheme, 'light')}
onChangeTextColorScheme={(scheme) => updateSlide(slideIndex, { textColorScheme: scheme })}
title="ตำแหน่งปุ่ม & ข้อความของสไลด์นี้ (Slide Content Position)"
/>
</article>
@@ -106,6 +106,10 @@ export default function BannerForm({
mobilePosition={editingSection.payload?.mobileButtonPosition}
onChangeDesktop={(pos) => updatePayloadValue('buttonPosition', pos)}
onChangeMobile={(pos) => updatePayloadValue('mobileButtonPosition', pos)}
contentWidth={editingSection.payload?.contentWidth}
onChangeContentWidth={(w) => updatePayloadValue('contentWidth', w)}
textColorScheme={editingSection.payload?.textColorScheme}
onChangeTextColorScheme={(scheme) => updatePayloadValue('textColorScheme', scheme)}
/>
</div>
</div>
@@ -1,4 +1,4 @@
import { Monitor, Smartphone, RotateCcw } from 'lucide-react';
import { Monitor, Smartphone, RotateCcw, Maximize2, Sun, Moon, Palette } from 'lucide-react';
export const GRID_POSITIONS = [
{ id: 'top-left', label: 'Top Left (บนซ้าย)', short: 'TL', icon: '↖' },
@@ -12,12 +12,39 @@ export const GRID_POSITIONS = [
{ id: 'bottom-right', label: 'Bottom Right (ล่างขวา)', short: 'BR', icon: '↘' },
];
export const WIDTH_OPTIONS = [
{ id: 'full', label: 'ขอบนอกสุด (Full Bleed)', desc: 'เต็มความกว้างจอ ชิดขอบซ้าย/ขวา' },
{ id: 'standard', label: 'กรอบมาตรฐาน (1400px)', desc: 'ตามระยะเว็บหลัก max-w-7xl (แนะนำ)' },
{ id: 'compact', label: 'กรอบกระชับ (768px)', desc: 'กล่องกะทัดรัด max-w-3xl' },
] as const;
export const COLOR_SCHEME_OPTIONS = [
{
id: 'light',
label: 'สว่าง (Light / ตัวอักษรสีขาว)',
desc: 'สำหรับภาพพื้นหลังโทนเข้ม หรือมี Overlay มืด (ค่าเริ่มต้น)',
icon: Sun,
badgeBg: 'bg-white/10 text-white border-white/20',
},
{
id: 'dark',
label: 'มืด (Dark / ตัวอักษรสีเข้ม)',
desc: 'สำหรับภาพพื้นหลังโทนขาว หรือภาพสว่างสดใส',
icon: Moon,
badgeBg: 'bg-neutral-800 text-slate-200 border-white/10',
},
] as const;
export interface ContentPositionPickerProps {
desktopPosition?: string;
mobilePosition?: string;
defaultDesktop?: string;
onChangeDesktop: (position: string) => void;
onChangeMobile: (position: string | undefined) => void;
contentWidth?: string;
onChangeContentWidth?: (width: string) => void;
textColorScheme?: 'light' | 'dark' | string;
onChangeTextColorScheme?: (scheme: 'light' | 'dark') => void;
title?: string;
subtitle?: string;
compact?: boolean;
@@ -29,6 +56,10 @@ export default function ContentPositionPicker({
defaultDesktop = 'center',
onChangeDesktop,
onChangeMobile,
contentWidth,
onChangeContentWidth,
textColorScheme,
onChangeTextColorScheme,
title = 'ตำแหน่งปุ่ม & เนื้อหา (Button & Content Position)',
subtitle = 'สามารถแยกตำแหน่งระหว่าง Desktop (PC) และ Mobile (มือถือ) ได้อิสระ',
compact = false,
@@ -148,6 +179,82 @@ export default function ContentPositionPicker({
</div>
</div>
</div>
{/* Container Width / Outer Boundary Selection */}
{onChangeContentWidth && (
<div className="bg-night-950/40 border border-white/5 p-3 rounded-2xl space-y-2">
<div className="flex items-center justify-between">
<span className="text-[11px] font-bold uppercase tracking-wider text-slate-300 flex items-center gap-1.5">
<Maximize2 className="w-3.5 h-3.5 text-cyan-glow" />
ความกว้างกรอบเนื้อหา (Content Width / Boundary)
</span>
<span className="px-2 py-0.5 rounded-md bg-cyan-glow/10 text-cyan-glow font-mono font-bold text-[10px] border border-cyan-glow/20">
{contentWidth || 'standard'}
</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2">
{WIDTH_OPTIONS.map((opt) => {
const isSelected = (contentWidth || 'standard') === opt.id;
return (
<button
key={opt.id}
type="button"
onClick={() => onChangeContentWidth(opt.id)}
className={`px-3 py-2 rounded-xl text-left transition-all border cursor-pointer ${
isSelected
? 'bg-cyan-glow/15 border-cyan-glow/50 text-white shadow-sm ring-1 ring-cyan-glow/30'
: 'bg-night-900 border-white/5 text-slate-400 hover:text-white hover:border-white/10'
}`}
>
<div className="text-xs font-bold leading-tight">{opt.label}</div>
<div className="text-[9px] text-slate-500 mt-0.5 leading-tight">{opt.desc}</div>
</button>
);
})}
</div>
</div>
)}
{/* Text Color Scheme Preset Selection */}
{onChangeTextColorScheme && (
<div className="bg-night-950/40 border border-white/5 p-3 rounded-2xl space-y-2">
<div className="flex items-center justify-between">
<span className="text-[11px] font-bold uppercase tracking-wider text-slate-300 flex items-center gap-1.5">
<Palette className="w-3.5 h-3.5 text-pop-pink" />
โทนสีตัวอักษร (Text Color Scheme Preset)
</span>
<span className={`px-2 py-0.5 rounded-md font-mono font-bold text-[10px] border ${(textColorScheme || 'light') === 'dark' ? 'bg-amber-400/10 text-amber-300 border-amber-400/20' : 'bg-cyan-glow/10 text-cyan-glow border-cyan-glow/20'}`}>
{(textColorScheme || 'light').toUpperCase()}
</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{COLOR_SCHEME_OPTIONS.map((opt) => {
const isSelected = (textColorScheme || 'light') === opt.id;
const Icon = opt.icon;
return (
<button
key={opt.id}
type="button"
onClick={() => onChangeTextColorScheme(opt.id as 'light' | 'dark')}
className={`px-3 py-2.5 rounded-xl text-left transition-all border cursor-pointer flex items-start gap-2.5 ${
isSelected
? 'bg-pop-pink/15 border-pop-pink/50 text-white shadow-sm ring-1 ring-pop-pink/30'
: 'bg-night-900 border-white/5 text-slate-400 hover:text-white hover:border-white/10'
}`}
>
<div className={`p-1.5 rounded-lg border shrink-0 mt-0.5 ${opt.badgeBg}`}>
<Icon className="w-4 h-4" />
</div>
<div>
<div className="text-xs font-bold leading-tight">{opt.label}</div>
<div className="text-[9px] text-slate-500 mt-0.5 leading-tight">{opt.desc}</div>
</div>
</button>
);
})}
</div>
</div>
)}
</div>
);
}
@@ -381,6 +381,10 @@ export default function HeroCarouselForm({
mobilePosition={slide.mobileButtonPosition}
onChangeDesktop={(pos) => updateSlidePayloadValue(idx, 'buttonPosition', pos)}
onChangeMobile={(pos) => updateSlidePayloadValue(idx, 'mobileButtonPosition', pos || '')}
contentWidth={slide.contentWidth}
onChangeContentWidth={(w) => updateSlidePayloadValue(idx, 'contentWidth', w)}
textColorScheme={slide.textColorScheme}
onChangeTextColorScheme={(scheme) => updateSlidePayloadValue(idx, 'textColorScheme', scheme)}
title="ตำแหน่งปุ่ม & ข้อความของสไลด์นี้ (Slide Content Position)"
/>
</div>
@@ -225,13 +225,17 @@ export default function HeroForm({
/>
</div>
{/* 9-Box Grid Button Position Selector (Desktop & Mobile) */}
{/* 9-Box Grid Button Position & Content Width & Text Color Scheme Selector */}
<div className="md:col-span-2">
<ContentPositionPicker
desktopPosition={editingSection.payload?.buttonPosition}
mobilePosition={editingSection.payload?.mobileButtonPosition}
onChangeDesktop={(pos) => updatePayloadValue('buttonPosition', pos)}
onChangeMobile={(pos) => updatePayloadValue('mobileButtonPosition', pos)}
contentWidth={editingSection.payload?.contentWidth}
onChangeContentWidth={(w) => updatePayloadValue('contentWidth', w)}
textColorScheme={editingSection.payload?.textColorScheme}
onChangeTextColorScheme={(scheme) => updatePayloadValue('textColorScheme', scheme)}
/>
</div>
</div>
@@ -293,12 +293,16 @@ export default function HeroVideoForm({
</div>
</div>
{/* Button & Content Position (9-Box Grid for Desktop & Mobile) */}
{/* Button & Content Position (9-Box Grid for Desktop & Mobile) & Color Scheme */}
<ContentPositionPicker
desktopPosition={payload.buttonPosition}
mobilePosition={payload.mobileButtonPosition}
onChangeDesktop={(pos) => updatePayloadValue('buttonPosition', pos)}
onChangeMobile={(pos) => updatePayloadValue('mobileButtonPosition', pos)}
contentWidth={payload.contentWidth}
onChangeContentWidth={(w) => updatePayloadValue('contentWidth', w)}
textColorScheme={payload.textColorScheme}
onChangeTextColorScheme={(scheme) => updatePayloadValue('textColorScheme', scheme)}
/>
</div>
);
@@ -265,6 +265,10 @@ export default function ProductHeroCarouselBlockForm({
mobilePosition={getString(slide.mobileButtonPosition) || undefined}
onChangeDesktop={(pos) => updateSlide(slideIndex, { buttonPosition: pos })}
onChangeMobile={(pos) => updateSlide(slideIndex, { mobileButtonPosition: pos || '' })}
contentWidth={getString(slide.contentWidth, 'standard')}
onChangeContentWidth={(w) => updateSlide(slideIndex, { contentWidth: w })}
textColorScheme={getString(slide.textColorScheme, 'light')}
onChangeTextColorScheme={(scheme) => updateSlide(slideIndex, { textColorScheme: scheme })}
title="ตำแหน่งปุ่ม & ข้อความของสไลด์นี้ (Slide Content Position)"
/>
</article>