This commit is contained in:
Your Name committed 2026-10-10 08:00:56 +07:00
1 parent 943fd8f1a3
commit 9237c0eded
7 files changed
+776 -377

No files matched your search

+33 -27
View File
@@ -14,7 +14,7 @@ import {
} from 'lucide-react'
interface ThemeLivePreviewProps {
tokens: Record<string, string>
tokens: Record<string, any>
}
// ── WCAG Relative Luminance & Contrast Calculation ──────────────────────────
@@ -401,39 +401,45 @@ export default function ThemeLivePreview({ tokens }: ThemeLivePreviewProps) {
</div>
<div className="flex items-center gap-2">
<button
type="button"
style={{ color: 'var(--p-text-main)' }}
className="p-1 rounded-full hover:bg-black/5 transition"
title="Search"
>
<Search className="w-3.5 h-3.5" />
</button>
<div className="relative">
{tokens.headerShowSearch !== false && (
<button
type="button"
style={{ color: 'var(--p-text-main)' }}
className="p-1 rounded-full hover:bg-black/5 transition"
title="Cart"
title="Search"
>
<ShoppingCart className="w-3.5 h-3.5" />
<Search className="w-3.5 h-3.5" />
</button>
<span
style={{ backgroundColor: 'var(--p-brand-accent)', color: '#FFFFFF' }}
className="absolute -top-1 -right-1 h-3.5 w-3.5 rounded-full text-[8px] font-bold flex items-center justify-center shadow-sm"
>
2
</span>
</div>
)}
<button
type="button"
className="preview-btn-line px-2.5 py-1 text-[10px] font-bold flex items-center gap-1 cursor-pointer"
>
<MessageCircle className="w-3 h-3" />
<span>LINE</span>
</button>
{tokens.headerShowCart !== false && (
<div className="relative">
<button
type="button"
style={{ color: 'var(--p-text-main)' }}
className="p-1 rounded-full hover:bg-black/5 transition"
title="Cart"
>
<ShoppingCart className="w-3.5 h-3.5" />
</button>
<span
style={{ backgroundColor: 'var(--p-brand-accent)', color: '#FFFFFF' }}
className="absolute -top-1 -right-1 h-3.5 w-3.5 rounded-full text-[8px] font-bold flex items-center justify-center shadow-sm"
>
2
</span>
</div>
)}
{tokens.headerShowAccount !== false && (
<button
type="button"
className="preview-btn-line px-2.5 py-1 text-[10px] font-bold flex items-center gap-1 cursor-pointer"
>
<MessageCircle className="w-3 h-3" />
<span>LINE</span>
</button>
)}
</div>
</header>
+206 -2
View File
@@ -19,7 +19,13 @@ import {
ArrowUpDown,
Monitor,
Smartphone,
LayoutGrid
LayoutGrid,
Search,
Heart,
ShoppingBag,
Truck,
Globe,
User
} from 'lucide-react'
const defaultThemeTokens = {
@@ -169,6 +175,12 @@ export default function ThemeSettingsPage() {
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')
const [headerShowSearch, setHeaderShowSearch] = useState<boolean>(true)
const [headerShowWishlist, setHeaderShowWishlist] = useState<boolean>(true)
const [headerShowCart, setHeaderShowCart] = useState<boolean>(true)
const [headerShowTrackOrder, setHeaderShowTrackOrder] = useState<boolean>(true)
const [headerShowLanguage, setHeaderShowLanguage] = useState<boolean>(true)
const [headerShowAccount, setHeaderShowAccount] = useState<boolean>(true)
// JSON Quick Copy / Paste State
const [isJsonBoxOpen, setIsJsonBoxOpen] = useState(false)
@@ -201,6 +213,12 @@ export default function ThemeSettingsPage() {
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)
setHeaderShowSearch((data as any).headerShowSearch ?? (data.themeTokens as any)?.headerShowSearch ?? true)
setHeaderShowWishlist((data as any).headerShowWishlist ?? (data.themeTokens as any)?.headerShowWishlist ?? true)
setHeaderShowCart((data as any).headerShowCart ?? (data.themeTokens as any)?.headerShowCart ?? true)
setHeaderShowTrackOrder((data as any).headerShowTrackOrder ?? (data.themeTokens as any)?.headerShowTrackOrder ?? true)
setHeaderShowLanguage((data as any).headerShowLanguage ?? (data.themeTokens as any)?.headerShowLanguage ?? true)
setHeaderShowAccount((data as any).headerShowAccount ?? (data.themeTokens as any)?.headerShowAccount ?? true)
})
.catch(console.error)
.finally(() => setLoading(false))
@@ -221,6 +239,12 @@ export default function ThemeSettingsPage() {
headerBehavior,
mobileHeaderBehavior,
mobileHeaderStyle,
headerShowSearch,
headerShowWishlist,
headerShowCart,
headerShowTrackOrder,
headerShowLanguage,
headerShowAccount,
cardPreset,
},
cardPreset,
@@ -231,6 +255,12 @@ export default function ThemeSettingsPage() {
headerBehavior,
mobileHeaderStyle,
mobileHeaderBehavior,
headerShowSearch,
headerShowWishlist,
headerShowCart,
headerShowTrackOrder,
headerShowLanguage,
headerShowAccount,
}
}
@@ -325,6 +355,25 @@ export default function ThemeSettingsPage() {
} else if (extractedTokens?.mobileHeaderBehavior === 'always_sticky' || extractedTokens?.mobileHeaderBehavior === 'hide_on_scroll') {
setMobileHeaderBehavior(extractedTokens.mobileHeaderBehavior)
}
// Extract Header Action Icons Visibility
if (typeof parsed.headerShowSearch === 'boolean') setHeaderShowSearch(parsed.headerShowSearch)
else if (typeof extractedTokens?.headerShowSearch === 'boolean') setHeaderShowSearch(extractedTokens.headerShowSearch)
if (typeof parsed.headerShowWishlist === 'boolean') setHeaderShowWishlist(parsed.headerShowWishlist)
else if (typeof extractedTokens?.headerShowWishlist === 'boolean') setHeaderShowWishlist(extractedTokens.headerShowWishlist)
if (typeof parsed.headerShowCart === 'boolean') setHeaderShowCart(parsed.headerShowCart)
else if (typeof extractedTokens?.headerShowCart === 'boolean') setHeaderShowCart(extractedTokens.headerShowCart)
if (typeof parsed.headerShowTrackOrder === 'boolean') setHeaderShowTrackOrder(parsed.headerShowTrackOrder)
else if (typeof extractedTokens?.headerShowTrackOrder === 'boolean') setHeaderShowTrackOrder(extractedTokens.headerShowTrackOrder)
if (typeof parsed.headerShowLanguage === 'boolean') setHeaderShowLanguage(parsed.headerShowLanguage)
else if (typeof extractedTokens?.headerShowLanguage === 'boolean') setHeaderShowLanguage(extractedTokens.headerShowLanguage)
if (typeof parsed.headerShowAccount === 'boolean') setHeaderShowAccount(parsed.headerShowAccount)
else if (typeof extractedTokens?.headerShowAccount === 'boolean') setHeaderShowAccount(extractedTokens.headerShowAccount)
}
// ── Paste JSON Text Handlers ──────────────────────────────────────────────
@@ -450,6 +499,12 @@ export default function ThemeSettingsPage() {
headerBehavior,
mobileHeaderBehavior,
mobileHeaderStyle,
headerShowSearch,
headerShowWishlist,
headerShowCart,
headerShowTrackOrder,
headerShowLanguage,
headerShowAccount,
cardPreset,
},
cardPreset,
@@ -460,6 +515,12 @@ export default function ThemeSettingsPage() {
headerBehavior,
mobileHeaderStyle,
mobileHeaderBehavior,
headerShowSearch,
headerShowWishlist,
headerShowCart,
headerShowTrackOrder,
headerShowLanguage,
headerShowAccount,
}),
})
if (res.ok) {
@@ -1262,6 +1323,141 @@ export default function ThemeSettingsPage() {
</button>
</div>
</div>
{/* Header Action Icons (Corner Icons) */}
<div className="space-y-4 pt-6 border-t border-white/5">
<div>
<label className="text-xs uppercase font-bold tracking-widest text-slate-400 flex items-center gap-1.5">
<ShoppingBag className="w-3.5 h-3.5 text-amber-400" />
<span>Header Action Icons (ไอคอนมุมขวาบนของ Header)</span>
</label>
<p className="text-xs text-slate-400 mt-1">
กำหนดเปิด/ซ่อนไอคอนการทำงานที่มุมขวาบนของ Header (ทั้งบน PC และ Mobile) เหมาะสำหรับร้านที่ไม่มีระบบตะกร้าสินค้า หรือต้องการซ่อนบางฟังก์ชัน
</p>
</div>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{/* Cart */}
<div className={`flex items-start justify-between p-4 rounded-xl border transition-all ${headerShowCart ? 'bg-white/5 border-amber-500/40' : 'bg-night-950/40 border-white/5 opacity-70'}`}>
<div className="flex items-start gap-3 min-w-0">
<div className={`p-2 rounded-lg shrink-0 ${headerShowCart ? 'bg-amber-400/20 text-amber-300' : 'bg-white/5 text-slate-500'}`}>
<ShoppingBag className="w-4 h-4" />
</div>
<div>
<span className="text-xs font-bold text-white block">ตะกร้าสินค้า (Cart)</span>
<span className="text-[10px] text-slate-400 block mt-0.5 leading-tight">ซ่อนปุ่มตะกร้า หากเป็นร้านโชว์สินค้า/ไม่มีระบบสั่งซื้อ</span>
</div>
</div>
<button
type="button"
onClick={() => setHeaderShowCart(v => !v)}
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors cursor-pointer ml-2 ${headerShowCart ? 'bg-amber-500' : 'bg-slate-700'}`}
>
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${headerShowCart ? 'translate-x-6' : 'translate-x-1'}`} />
</button>
</div>
{/* Wishlist */}
<div className={`flex items-start justify-between p-4 rounded-xl border transition-all ${headerShowWishlist ? 'bg-white/5 border-amber-500/40' : 'bg-night-950/40 border-white/5 opacity-70'}`}>
<div className="flex items-start gap-3 min-w-0">
<div className={`p-2 rounded-lg shrink-0 ${headerShowWishlist ? 'bg-rose-500/20 text-rose-300' : 'bg-white/5 text-slate-500'}`}>
<Heart className="w-4 h-4" />
</div>
<div>
<span className="text-xs font-bold text-white block">รายการโปรด (Wishlist)</span>
<span className="text-[10px] text-slate-400 block mt-0.5 leading-tight">ไอคอนหัวใจบันทึกรายการโปรด</span>
</div>
</div>
<button
type="button"
onClick={() => setHeaderShowWishlist(v => !v)}
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors cursor-pointer ml-2 ${headerShowWishlist ? 'bg-amber-500' : 'bg-slate-700'}`}
>
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${headerShowWishlist ? 'translate-x-6' : 'translate-x-1'}`} />
</button>
</div>
{/* Search */}
<div className={`flex items-start justify-between p-4 rounded-xl border transition-all ${headerShowSearch ? 'bg-white/5 border-amber-500/40' : 'bg-night-950/40 border-white/5 opacity-70'}`}>
<div className="flex items-start gap-3 min-w-0">
<div className={`p-2 rounded-lg shrink-0 ${headerShowSearch ? 'bg-cyan-500/20 text-cyan-300' : 'bg-white/5 text-slate-500'}`}>
<Search className="w-4 h-4" />
</div>
<div>
<span className="text-xs font-bold text-white block">ค้นหา (Search)</span>
<span className="text-[10px] text-slate-400 block mt-0.5 leading-tight">ปุ่มเปิดหน้าต่างค้นหาสินค้า</span>
</div>
</div>
<button
type="button"
onClick={() => setHeaderShowSearch(v => !v)}
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors cursor-pointer ml-2 ${headerShowSearch ? 'bg-amber-500' : 'bg-slate-700'}`}
>
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${headerShowSearch ? 'translate-x-6' : 'translate-x-1'}`} />
</button>
</div>
{/* Track Order */}
<div className={`flex items-start justify-between p-4 rounded-xl border transition-all ${headerShowTrackOrder ? 'bg-white/5 border-amber-500/40' : 'bg-night-950/40 border-white/5 opacity-70'}`}>
<div className="flex items-start gap-3 min-w-0">
<div className={`p-2 rounded-lg shrink-0 ${headerShowTrackOrder ? 'bg-emerald-500/20 text-emerald-300' : 'bg-white/5 text-slate-500'}`}>
<Truck className="w-4 h-4" />
</div>
<div>
<span className="text-xs font-bold text-white block">ติดตามพัสดุ (Track Order)</span>
<span className="text-[10px] text-slate-400 block mt-0.5 leading-tight">ปุ่มตรวจสอบสถานะจัดส่งสินค้า</span>
</div>
</div>
<button
type="button"
onClick={() => setHeaderShowTrackOrder(v => !v)}
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors cursor-pointer ml-2 ${headerShowTrackOrder ? 'bg-amber-500' : 'bg-slate-700'}`}
>
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${headerShowTrackOrder ? 'translate-x-6' : 'translate-x-1'}`} />
</button>
</div>
{/* Language Switcher */}
<div className={`flex items-start justify-between p-4 rounded-xl border transition-all ${headerShowLanguage ? 'bg-white/5 border-amber-500/40' : 'bg-night-950/40 border-white/5 opacity-70'}`}>
<div className="flex items-start gap-3 min-w-0">
<div className={`p-2 rounded-lg shrink-0 ${headerShowLanguage ? 'bg-violet-500/20 text-violet-300' : 'bg-white/5 text-slate-500'}`}>
<Globe className="w-4 h-4" />
</div>
<div>
<span className="text-xs font-bold text-white block">เปลี่ยนภาษา (Language)</span>
<span className="text-[10px] text-slate-400 block mt-0.5 leading-tight">เมนูเลือกภาษา (TH, EN, JA ฯลฯ)</span>
</div>
</div>
<button
type="button"
onClick={() => setHeaderShowLanguage(v => !v)}
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors cursor-pointer ml-2 ${headerShowLanguage ? 'bg-amber-500' : 'bg-slate-700'}`}
>
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${headerShowLanguage ? 'translate-x-6' : 'translate-x-1'}`} />
</button>
</div>
{/* Account / Login */}
<div className={`flex items-start justify-between p-4 rounded-xl border transition-all ${headerShowAccount ? 'bg-white/5 border-amber-500/40' : 'bg-night-950/40 border-white/5 opacity-70'}`}>
<div className="flex items-start gap-3 min-w-0">
<div className={`p-2 rounded-lg shrink-0 ${headerShowAccount ? 'bg-lime-500/20 text-lime-300' : 'bg-white/5 text-slate-500'}`}>
<User className="w-4 h-4" />
</div>
<div>
<span className="text-xs font-bold text-white block">บัญชี / ล็อกอิน (Account)</span>
<span className="text-[10px] text-slate-400 block mt-0.5 leading-tight">ปุ่ม LINE Login หรือโปรไฟล์ผู้ใช้</span>
</div>
</div>
<button
type="button"
onClick={() => setHeaderShowAccount(v => !v)}
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors cursor-pointer ml-2 ${headerShowAccount ? 'bg-amber-500' : 'bg-slate-700'}`}
>
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${headerShowAccount ? 'translate-x-6' : 'translate-x-1'}`} />
</button>
</div>
</div>
</div>
</div>
{/* ── 5. Mobile Header Settings ── */}
@@ -1487,7 +1683,15 @@ export default function ThemeSettingsPage() {
{/* ── Right Column: Live Interactive UI Preview (Sticky) ── */}
<div className="xl:col-span-5 xl:sticky xl:top-20 space-y-6">
<ThemeLivePreview tokens={themeTokens} />
<ThemeLivePreview tokens={{
...themeTokens,
headerShowSearch,
headerShowWishlist,
headerShowCart,
headerShowTrackOrder,
headerShowLanguage,
headerShowAccount,
}} />
</div>
</div>