347 lines
9.9 KiB
TypeScript
347 lines
9.9 KiB
TypeScript
import type {CSSProperties, ReactNode} from 'react';
|
|
import {Link} from 'react-router';
|
|
import {Image} from '@shopify/hydrogen';
|
|
import {Instagram, Youtube} from 'lucide-react';
|
|
import type {FooterQuery, HeaderQuery} from 'storefrontapi.generated';
|
|
import {pickAbiirLogoData, type AbiirHeaderCms} from '~/components/Header';
|
|
import type {
|
|
AbiirFooterCms,
|
|
AbiirFooterColumn,
|
|
AbiirFooterLink,
|
|
} from '~/lib/footer';
|
|
import {MOCK_ABIIR_FOOTER_CMS} from '~/lib/footer';
|
|
|
|
/**
|
|
* GROQ: `settings` singleton → ABiiR footer module.
|
|
* Use with `context.sanity.loadQuery(FOOTER_SECTION_GROQ)` or `loadFooterFromSanity`.
|
|
*/
|
|
export const FOOTER_SECTION_GROQ = `*[_type == "settings" && _id == "settings"][0].abiirFooter{
|
|
tagline,
|
|
brandLinkPath,
|
|
emblem{
|
|
alt,
|
|
asset->{
|
|
url,
|
|
metadata{dimensions{width,height}}
|
|
}
|
|
},
|
|
wordmark{
|
|
alt,
|
|
asset->{
|
|
url,
|
|
metadata{dimensions{width,height}}
|
|
}
|
|
},
|
|
certificationBadges{
|
|
alt,
|
|
asset->{
|
|
url,
|
|
metadata{dimensions{width,height}}
|
|
}
|
|
},
|
|
socialInstagramUrl,
|
|
socialYoutubeUrl,
|
|
socialXUrl,
|
|
columnGroups[]{
|
|
title,
|
|
links[]{ label, path }
|
|
},
|
|
copyrightLine,
|
|
legalLinks[]{ label, path }
|
|
}` as const;
|
|
|
|
export {MOCK_ABIIR_FOOTER_CMS};
|
|
|
|
const CREAM = '#faf8f3';
|
|
const LINK_MUTED = 'rgba(250, 248, 243, 0.8)';
|
|
const BODY_MUTED = 'rgba(250, 248, 243, 0.7)';
|
|
const LEGAL_MUTED = 'rgba(250, 248, 243, 0.6)';
|
|
const BORDER_TOP = 'rgba(250, 248, 243, 0.15)';
|
|
|
|
function SocialX({className, size}: {className?: string; size?: number}) {
|
|
const s = size ?? 19;
|
|
return (
|
|
<svg
|
|
aria-hidden
|
|
className={className}
|
|
fill="none"
|
|
height={s}
|
|
viewBox="0 0 24 24"
|
|
width={s}
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
>
|
|
<path
|
|
d="M13.522 10.774 20.48 3h-1.646l-6.04 6.748L8.158 3H3l7.304 10.12L3 21h1.646l6.388-7.142L15.842 21H21l-7.478-10.226ZM11.66 13.02l-.732-1.01L5.22 4.2h2.513l4.71 6.488.732 1.01 6.11 8.42h-2.514l-4.99-6.878Z"
|
|
fill="currentColor"
|
|
/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
function isExternalHref(href: string) {
|
|
return /^https?:\/\//i.test(href);
|
|
}
|
|
|
|
function FooterNavLink({
|
|
href,
|
|
className,
|
|
style,
|
|
children,
|
|
}: {
|
|
href: string;
|
|
className?: string;
|
|
style?: CSSProperties;
|
|
children: ReactNode;
|
|
}) {
|
|
if (isExternalHref(href)) {
|
|
return (
|
|
<a
|
|
className={className}
|
|
href={href}
|
|
rel="noopener noreferrer"
|
|
style={style}
|
|
target="_blank"
|
|
>
|
|
{children}
|
|
</a>
|
|
);
|
|
}
|
|
return (
|
|
<Link className={className} style={style} to={href}>
|
|
{children}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
function isRemoteImageUrl(url: string) {
|
|
return /^https?:\/\//i.test(url);
|
|
}
|
|
|
|
function FooterRemoteImage({
|
|
url,
|
|
alt,
|
|
width,
|
|
height,
|
|
className,
|
|
style,
|
|
sizes,
|
|
}: {
|
|
url: string;
|
|
alt: string;
|
|
width: number;
|
|
height: number;
|
|
className?: string;
|
|
style?: CSSProperties;
|
|
sizes?: string;
|
|
}) {
|
|
if (isRemoteImageUrl(url)) {
|
|
return (
|
|
<Image
|
|
alt={alt}
|
|
className={className}
|
|
data={{
|
|
url,
|
|
altText: alt,
|
|
width,
|
|
height,
|
|
}}
|
|
loader={({src}) => src ?? ''}
|
|
sizes={sizes}
|
|
style={style}
|
|
/>
|
|
);
|
|
}
|
|
return (
|
|
<Image
|
|
alt={alt}
|
|
className={className}
|
|
height={height}
|
|
sizes={sizes}
|
|
src={url}
|
|
style={style}
|
|
width={width}
|
|
/>
|
|
);
|
|
}
|
|
|
|
const linkClass =
|
|
'font-["Raleway",sans-serif] text-[15.938px] font-light leading-[23.906px] transition-opacity hover:opacity-80 no-underline';
|
|
const linkStyle: CSSProperties = {color: LINK_MUTED};
|
|
|
|
const columnHeadingClass =
|
|
'm-0 font-["Raleway",sans-serif] text-[11.688px] font-semibold leading-[15.778px] tracking-[2.5712px] uppercase';
|
|
const columnHeadingStyle: CSSProperties = {color: CREAM};
|
|
|
|
const legalLinkClass =
|
|
'font-["Raleway",sans-serif] text-[13.813px] font-light leading-[20.719px] tracking-[0.5525px] transition-opacity hover:opacity-60 whitespace-nowrap no-underline';
|
|
const legalLinkStyle: CSSProperties = {color: LEGAL_MUTED};
|
|
|
|
function FooterNavColumn({column}: {column: AbiirFooterColumn}) {
|
|
return (
|
|
<div
|
|
className="flex min-w-0 flex-col"
|
|
style={{gap: '29.75px'}}
|
|
>
|
|
<h4 className={columnHeadingClass} style={columnHeadingStyle}>
|
|
{column.title}
|
|
</h4>
|
|
<ul className="m-0 flex list-none flex-col p-0" style={{gap: '17px'}}>
|
|
{column.links.map((link: AbiirFooterLink) => (
|
|
<li key={link.label}>
|
|
<FooterNavLink
|
|
className={linkClass}
|
|
href={link.href}
|
|
style={linkStyle}
|
|
>
|
|
{link.label}
|
|
</FooterNavLink>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export type AbiirFooterProps = {
|
|
/** Shopify menu query (optional; reserved for future use). */
|
|
footer: FooterQuery | null;
|
|
footerCms: AbiirFooterCms;
|
|
header: HeaderQuery;
|
|
/** Same source as `Header` — drives footer brand mark (`pickAbiirLogoData`). */
|
|
headerCms: AbiirHeaderCms;
|
|
publicStoreDomain: string;
|
|
};
|
|
|
|
export function Footer({
|
|
footer: _footer,
|
|
footerCms,
|
|
header,
|
|
headerCms,
|
|
publicStoreDomain: _publicStoreDomain,
|
|
}: AbiirFooterProps) {
|
|
const cms = footerCms;
|
|
const logoData = pickAbiirLogoData(headerCms, header.shop.brand?.logo?.image);
|
|
const columnGroups = cms.columnGroups;
|
|
const firstNavPair = columnGroups.slice(0, 2);
|
|
const restNavColumns = columnGroups.slice(2);
|
|
|
|
return (
|
|
<footer className="w-full" style={{background: '#1a1a18', color: CREAM}}>
|
|
<div className="abiir-footer-main mx-auto max-w-[1710px] px-6 pt-[72px] sm:px-10 sm:pt-[88px] lg:px-16 lg:pt-[102px] 2xl:px-[206px]">
|
|
<div className="mx-auto grid max-w-[1298px] grid-cols-1 gap-x-10 gap-y-12 lg:grid-cols-[minmax(0,493.695px)_repeat(3,minmax(0,225.602px))] lg:gap-x-[42.5px] lg:gap-y-0">
|
|
{/* Brand */}
|
|
<div className="lg:max-w-[493.695px]">
|
|
<FooterNavLink
|
|
aria-label="ABiiR — Home"
|
|
className="mb-5 inline-flex items-center transition-opacity hover:opacity-90"
|
|
href={cms.brandLinkPath}
|
|
style={{textDecoration: 'none'}}
|
|
>
|
|
<Image
|
|
alt={logoData.altText || 'ABiiR Skincare'}
|
|
className="block h-auto w-[148px] max-w-[148px]"
|
|
data={logoData}
|
|
sizes="148px"
|
|
width={148}
|
|
/>
|
|
</FooterNavLink>
|
|
<p
|
|
className="m-0 max-w-[340px] font-['Raleway',sans-serif] text-[15.938px] font-light leading-[30.281px]"
|
|
style={{color: BODY_MUTED}}
|
|
>
|
|
{cms.tagline}
|
|
</p>
|
|
<div
|
|
className="mt-8 flex items-center"
|
|
style={{gap: '21.25px'}}
|
|
>
|
|
<a
|
|
aria-label="Instagram"
|
|
className="flex h-[19px] w-[19px] items-center justify-center transition-opacity hover:opacity-60"
|
|
href={cms.socialInstagramUrl}
|
|
rel="noopener noreferrer"
|
|
style={{color: CREAM}}
|
|
target="_blank"
|
|
>
|
|
<Instagram aria-hidden size={19} strokeWidth={1.5} />
|
|
</a>
|
|
<a
|
|
aria-label="YouTube"
|
|
className="flex h-[19px] w-[19px] items-center justify-center transition-opacity hover:opacity-60"
|
|
href={cms.socialYoutubeUrl}
|
|
rel="noopener noreferrer"
|
|
style={{color: CREAM}}
|
|
target="_blank"
|
|
>
|
|
<Youtube aria-hidden size={19} strokeWidth={1.5} />
|
|
</a>
|
|
<a
|
|
aria-label="X"
|
|
className="flex h-[19px] w-[19px] items-center justify-center transition-opacity hover:opacity-60"
|
|
href={cms.socialXUrl}
|
|
rel="noopener noreferrer"
|
|
style={{color: CREAM}}
|
|
target="_blank"
|
|
>
|
|
<SocialX size={19} />
|
|
</a>
|
|
</div>
|
|
<div className="mt-8 min-w-0 max-w-full">
|
|
<FooterRemoteImage
|
|
alt={cms.certificationBadgesAlt}
|
|
className="block h-auto w-full max-w-[320px] object-contain opacity-90"
|
|
height={cms.certificationBadgesHeight ?? 80}
|
|
sizes="(max-width: 1024px) min(100vw, 520px), 320px"
|
|
url={cms.certificationBadgesUrl}
|
|
width={cms.certificationBadgesWidth ?? 320}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{firstNavPair.length >= 2 ? (
|
|
<div className="grid min-w-0 grid-cols-2 gap-x-6 gap-y-0 sm:gap-x-10 lg:contents">
|
|
{firstNavPair.map((column) => (
|
|
<FooterNavColumn key={column.title} column={column} />
|
|
))}
|
|
</div>
|
|
) : (
|
|
firstNavPair.map((column) => (
|
|
<FooterNavColumn key={column.title} column={column} />
|
|
))
|
|
)}
|
|
{restNavColumns.map((column) => (
|
|
<FooterNavColumn key={column.title} column={column} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
className="abiir-footer-legal mx-auto max-w-[1400px] px-6 sm:px-10 lg:px-[51px]"
|
|
style={{borderTop: `1px solid ${BORDER_TOP}`}}
|
|
>
|
|
<p
|
|
className="m-0 text-center font-['Raleway',sans-serif] text-[13.813px] font-light leading-[24.862px] tracking-[0.5525px] sm:text-left"
|
|
style={{color: LEGAL_MUTED}}
|
|
>
|
|
{cms.copyrightLine}
|
|
</p>
|
|
<div
|
|
className="flex flex-wrap items-center justify-center sm:justify-end"
|
|
style={{gap: '29.75px'}}
|
|
>
|
|
{cms.legalLinks.map((item) => (
|
|
<FooterNavLink
|
|
key={item.label}
|
|
className={legalLinkClass}
|
|
href={item.href}
|
|
style={legalLinkStyle}
|
|
>
|
|
{item.label}
|
|
</FooterNavLink>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
);
|
|
}
|