x
This commit is contained in:
1 parent
752451b1d4
commit
f6c188bd93
5 files changed
+27
-3
No files matched your search
@@ -1,12 +1,13 @@
|
|||||||
import { UploadCloud, Image as ImageIcon } from 'lucide-react';
|
import { UploadCloud, Image as ImageIcon } from 'lucide-react';
|
||||||
import { type PageSection } from '@homaroma/types';
|
import { type PageSection } from '@homaroma/types';
|
||||||
|
import FocalPointPicker from './FocalPointPicker';
|
||||||
|
|
||||||
interface BannerFormProps {
|
interface BannerFormProps {
|
||||||
editingSection: Partial<PageSection>;
|
editingSection: Partial<PageSection>;
|
||||||
editingLang: string;
|
editingLang: string;
|
||||||
getLocalizedValue: (payload: any, field: string) => string;
|
getLocalizedValue: (payload: any, field: string) => string;
|
||||||
updateLocalizedValue: (field: string, value: string) => void;
|
updateLocalizedValue: (field: string, value: string) => void;
|
||||||
updatePayloadValue: (field: string, value: string | number) => void;
|
updatePayloadValue: (field: string, value: any) => void;
|
||||||
setUploadTarget: (target: { type: 'main' | 'slide'; index?: number } | null) => void;
|
setUploadTarget: (target: { type: 'main' | 'slide'; index?: number } | null) => void;
|
||||||
setIsMediaModalOpen: (open: boolean) => void;
|
setIsMediaModalOpen: (open: boolean) => void;
|
||||||
}
|
}
|
||||||
@@ -47,6 +48,16 @@ export default function BannerForm({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{editingSection.payload?.imageUrl && (
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<FocalPointPicker
|
||||||
|
imageUrl={editingSection.payload.imageUrl}
|
||||||
|
focalPoint={editingSection.payload.focalPoint}
|
||||||
|
onChange={(focalPoint) => updatePayloadValue('focalPoint', focalPoint)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">Banner Title</label>
|
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">Banner Title</label>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const ADD_PAGE_SECTION_GUIDANCE: Record<string, { required: string[]; example: s
|
|||||||
},
|
},
|
||||||
BANNER: {
|
BANNER: {
|
||||||
required: ["imageUrl"],
|
required: ["imageUrl"],
|
||||||
example: `{ "imageUrl": "https://example.com/banner.jpg", "title": ${localizedStringExample}, "buttonText": ${localizedStringExample} }`
|
example: `{ "imageUrl": "https://example.com/banner.jpg", "title": ${localizedStringExample}, "buttonText": ${localizedStringExample}, "focalPoint": { "x": "50%", "y": "50%" } }`
|
||||||
},
|
},
|
||||||
HERO_ARTICLE: {
|
HERO_ARTICLE: {
|
||||||
required: ["title"],
|
required: ["title"],
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ export default function DynamicBanner({ payload }: DynamicBannerProps) {
|
|||||||
const title = getLocalizedField(payload.title, lang);
|
const title = getLocalizedField(payload.title, lang);
|
||||||
const description = getLocalizedField(payload.description, lang);
|
const description = getLocalizedField(payload.description, lang);
|
||||||
const buttonText = getLocalizedField(payload.buttonText, lang);
|
const buttonText = getLocalizedField(payload.buttonText, lang);
|
||||||
|
const focalPoint = typeof payload.focalPoint === 'object' && payload.focalPoint ? payload.focalPoint : undefined;
|
||||||
|
const imageStyle = focalPoint ? { objectPosition: `${focalPoint.x} ${focalPoint.y}` } : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="ui-section-band bg-pop-bg relative overflow-hidden">
|
<section className="ui-section-band bg-pop-bg relative overflow-hidden">
|
||||||
@@ -24,6 +26,7 @@ export default function DynamicBanner({ payload }: DynamicBannerProps) {
|
|||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-[1.03]"
|
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-[1.03]"
|
||||||
alt={title || "Banner"}
|
alt={title || "Banner"}
|
||||||
|
style={imageStyle}
|
||||||
/>
|
/>
|
||||||
<div className="ui-banner-overlay"></div>
|
<div className="ui-banner-overlay"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,6 +58,7 @@ export default function DynamicBanner({ payload }: DynamicBannerProps) {
|
|||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-[1.03]"
|
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-[1.03]"
|
||||||
alt={title || "Banner"}
|
alt={title || "Banner"}
|
||||||
|
style={imageStyle}
|
||||||
/>
|
/>
|
||||||
<div className="ui-banner-overlay"></div>
|
<div className="ui-banner-overlay"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -424,10 +424,18 @@ function BannerSection({ payload, lang }: { payload: Record<string, unknown>, la
|
|||||||
const buttonText = localizedFromRecord(payload, 'buttonText', resolvedLang)
|
const buttonText = localizedFromRecord(payload, 'buttonText', resolvedLang)
|
||||||
const imageUrl = typeof payload.imageUrl === 'string' ? payload.imageUrl : '/images/hero.png'
|
const imageUrl = typeof payload.imageUrl === 'string' ? payload.imageUrl : '/images/hero.png'
|
||||||
const linkUrl = typeof payload.linkUrl === 'string' ? payload.linkUrl : ''
|
const linkUrl = typeof payload.linkUrl === 'string' ? payload.linkUrl : ''
|
||||||
|
const focalPoint = typeof payload.focalPoint === 'object' && payload.focalPoint ? (payload.focalPoint as { x: string, y: string }) : undefined
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<div className="ui-banner-shell">
|
<div className="ui-banner-shell">
|
||||||
<Image src={imageUrl} alt={title || 'Banner'} fill unoptimized className="absolute inset-0 h-full w-full object-cover" />
|
<Image
|
||||||
|
src={imageUrl}
|
||||||
|
alt={title || 'Banner'}
|
||||||
|
fill
|
||||||
|
unoptimized
|
||||||
|
className="absolute inset-0 h-full w-full object-cover"
|
||||||
|
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
|
||||||
|
/>
|
||||||
<div className="ui-banner-overlay" />
|
<div className="ui-banner-overlay" />
|
||||||
<div className="ui-banner-content">
|
<div className="ui-banner-content">
|
||||||
{title && <h2 className="text-3xl font-semibold text-brand-primary drop-shadow-md md:text-5xl">{title}</h2>}
|
{title && <h2 className="text-3xl font-semibold text-brand-primary drop-shadow-md md:text-5xl">{title}</h2>}
|
||||||
|
|||||||
@@ -339,6 +339,7 @@ export const BannerSectionPayloadSchema = z.object({
|
|||||||
title: LocalizedStringSchema.optional(),
|
title: LocalizedStringSchema.optional(),
|
||||||
description: LocalizedStringSchema.optional(),
|
description: LocalizedStringSchema.optional(),
|
||||||
buttonText: LocalizedStringSchema.optional(),
|
buttonText: LocalizedStringSchema.optional(),
|
||||||
|
focalPoint: z.object({ x: z.string(), y: z.string() }).optional(),
|
||||||
});
|
});
|
||||||
export type BannerSectionPayload = z.infer<typeof BannerSectionPayloadSchema>;
|
export type BannerSectionPayload = z.infer<typeof BannerSectionPayloadSchema>;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user