mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-23 11:03:39 +00:00
Refactor StructurePreview and HeaderLogo components to utilize HeaderLogoContent for improved logo rendering and fallback handling
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
|
||||
import { Image } from '@/components/utils';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { resolveContentRef } from '@/lib/references';
|
||||
import { Link } from '../primitives';
|
||||
import { CurrentContentIcon } from './CurrentContentIcon';
|
||||
import {
|
||||
HEADER_LOGO_CONTAINER_CLASS,
|
||||
HEADER_LOGO_IMAGE_CLASS,
|
||||
HEADER_LOGO_IMAGE_SIZES,
|
||||
HeaderLogoContent,
|
||||
} from './HeaderLogoContent';
|
||||
|
||||
interface HeaderLogoProps {
|
||||
context: GitBookSiteContext;
|
||||
@@ -26,83 +31,56 @@ export async function HeaderLogo(props: HeaderLogoProps) {
|
||||
return (
|
||||
<Link
|
||||
href={primaryLink?.href ?? linker.toPathInSite('')}
|
||||
className={tcls('group/headerlogo', 'min-w-0', 'shrink', 'flex', 'items-center')}
|
||||
className={HEADER_LOGO_CONTAINER_CLASS}
|
||||
>
|
||||
{customization.header.logo ? (
|
||||
<Image
|
||||
alt="Logo"
|
||||
resize={context.imageResizer}
|
||||
sources={{
|
||||
light: {
|
||||
src: customization.header.logo.light,
|
||||
},
|
||||
dark: customization.header.logo.dark
|
||||
? {
|
||||
src: customization.header.logo.dark,
|
||||
}
|
||||
: null,
|
||||
}}
|
||||
sizes={[
|
||||
{
|
||||
media: '(max-width: 1024px)',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
width: 260,
|
||||
},
|
||||
]}
|
||||
preload
|
||||
style={tcls(
|
||||
'overflow-hidden',
|
||||
'shrink',
|
||||
'min-w-0',
|
||||
'max-w-40',
|
||||
'lg:max-w-64',
|
||||
'lg:site-header-none:page-no-toc:max-w-56',
|
||||
'max-h-8',
|
||||
'h-full',
|
||||
'w-full',
|
||||
'object-contain',
|
||||
'object-left'
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<LogoFallback {...props} />
|
||||
)}
|
||||
<HeaderLogoContent
|
||||
logo={customization.header.logo ? <LogoImage context={context} /> : null}
|
||||
fallbackIcon={<LogoFallbackIcon context={context} />}
|
||||
title={context.site.title}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function LogoFallback(props: HeaderLogoProps) {
|
||||
function LogoImage(props: HeaderLogoProps) {
|
||||
const { context } = props;
|
||||
const { site } = context;
|
||||
const { customization } = context;
|
||||
|
||||
if (!customization.header.logo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<CurrentContentIcon
|
||||
context={context}
|
||||
alt=""
|
||||
sizes={[{ width: 32 }]}
|
||||
style={['object-contain', 'size-8']}
|
||||
fetchPriority="high"
|
||||
/>
|
||||
<div
|
||||
className={tcls(
|
||||
'text-pretty',
|
||||
'line-clamp-2',
|
||||
'tracking-tight',
|
||||
'max-w-[18ch]',
|
||||
'lg:max-w-[24ch]',
|
||||
'font-semibold',
|
||||
'ms-3',
|
||||
'text-base/tight',
|
||||
'lg:text-lg/tight',
|
||||
'text-tint-strong',
|
||||
'theme-bold:text-header-link'
|
||||
)}
|
||||
>
|
||||
{site.title}
|
||||
</div>
|
||||
</>
|
||||
<Image
|
||||
alt="Logo"
|
||||
resize={context.imageResizer}
|
||||
sources={{
|
||||
light: {
|
||||
src: customization.header.logo.light,
|
||||
},
|
||||
dark: customization.header.logo.dark
|
||||
? {
|
||||
src: customization.header.logo.dark,
|
||||
}
|
||||
: null,
|
||||
}}
|
||||
sizes={HEADER_LOGO_IMAGE_SIZES}
|
||||
preload
|
||||
style={HEADER_LOGO_IMAGE_CLASS}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function LogoFallbackIcon(props: HeaderLogoProps) {
|
||||
const { context } = props;
|
||||
|
||||
return (
|
||||
<CurrentContentIcon
|
||||
context={context}
|
||||
alt=""
|
||||
sizes={[{ width: 32 }]}
|
||||
style={['object-contain', 'size-8']}
|
||||
fetchPriority="high"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
export const HEADER_LOGO_IMAGE_SIZES = [
|
||||
{
|
||||
media: '(max-width: 1024px)',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
width: 260,
|
||||
},
|
||||
];
|
||||
|
||||
export const HEADER_LOGO_CONTAINER_CLASS = tcls(
|
||||
'group/headerlogo',
|
||||
'min-w-0',
|
||||
'shrink',
|
||||
'flex',
|
||||
'items-center'
|
||||
);
|
||||
|
||||
export const HEADER_LOGO_IMAGE_CLASS = tcls(
|
||||
'overflow-hidden',
|
||||
'shrink',
|
||||
'min-w-0',
|
||||
'max-w-40',
|
||||
'lg:max-w-64',
|
||||
'lg:site-header-none:page-no-toc:max-w-56',
|
||||
'max-h-8',
|
||||
'h-full',
|
||||
'w-full',
|
||||
'object-contain',
|
||||
'object-left'
|
||||
);
|
||||
|
||||
interface HeaderLogoContentProps {
|
||||
logo: ReactNode | null;
|
||||
fallbackIcon: ReactNode;
|
||||
title: ReactNode;
|
||||
}
|
||||
|
||||
export function HeaderLogoContent(props: HeaderLogoContentProps) {
|
||||
const { logo, fallbackIcon, title } = props;
|
||||
|
||||
if (logo) {
|
||||
return logo;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{fallbackIcon}
|
||||
<HeaderLogoTitle>{title}</HeaderLogoTitle>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function HeaderLogoTitle(props: { children: ReactNode }) {
|
||||
return (
|
||||
<div
|
||||
className={tcls(
|
||||
'text-pretty',
|
||||
'line-clamp-2',
|
||||
'tracking-tight',
|
||||
'max-w-[18ch]',
|
||||
'lg:max-w-[24ch]',
|
||||
'font-semibold',
|
||||
'ms-3',
|
||||
'text-base/tight',
|
||||
'lg:text-lg/tight',
|
||||
'text-tint-strong',
|
||||
'theme-bold:text-header-link'
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,11 @@ import { getLocalizedTitle } from '@/lib/sites';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { tString, useLanguage } from '@/intl/client';
|
||||
import {
|
||||
HEADER_LOGO_CONTAINER_CLASS,
|
||||
HEADER_LOGO_IMAGE_CLASS,
|
||||
HeaderLogoContent,
|
||||
} from '../Header/HeaderLogoContent';
|
||||
import headerLinksStyles from '../Header/headerLinks.module.css';
|
||||
import { CONTAINER_STYLE, HEADER_HEIGHT_DESKTOP } from '../layout';
|
||||
import { Button, ToggleChevron } from '../primitives';
|
||||
@@ -255,72 +260,49 @@ function StructurePreviewLogo(props: { snapshot: StructurePreviewSnapshot }) {
|
||||
const { customization } = snapshot;
|
||||
|
||||
return (
|
||||
<div className={tcls('group/headerlogo', 'min-w-0', 'shrink', 'flex', 'items-center')}>
|
||||
{customization.header.logo ? (
|
||||
<picture>
|
||||
{customization.header.logo.dark ? (
|
||||
<source
|
||||
srcSet={customization.header.logo.dark}
|
||||
media="(prefers-color-scheme: dark)"
|
||||
/>
|
||||
) : null}
|
||||
<img
|
||||
alt="Logo"
|
||||
src={customization.header.logo.light}
|
||||
className={tcls(
|
||||
'block overflow-hidden',
|
||||
'shrink',
|
||||
'min-w-0',
|
||||
'max-w-40',
|
||||
'lg:max-w-64',
|
||||
'max-h-8',
|
||||
'h-full',
|
||||
'w-full',
|
||||
'object-contain',
|
||||
'object-left'
|
||||
)}
|
||||
/>
|
||||
</picture>
|
||||
) : (
|
||||
<>
|
||||
{'emoji' in customization.favicon && customization.favicon.emoji ? (
|
||||
<span className="text-xl">{customization.favicon.emoji}</span>
|
||||
) : (
|
||||
<picture>
|
||||
<source
|
||||
srcSet={snapshot.icons.large.dark}
|
||||
media="(prefers-color-scheme: dark)"
|
||||
/>
|
||||
<img
|
||||
alt=""
|
||||
src={snapshot.icons.large.light}
|
||||
className="size-8 object-contain"
|
||||
/>
|
||||
</picture>
|
||||
)}
|
||||
<div
|
||||
className={tcls(
|
||||
'text-pretty',
|
||||
'line-clamp-2',
|
||||
'tracking-tight',
|
||||
'max-w-[18ch]',
|
||||
'lg:max-w-[24ch]',
|
||||
'font-semibold',
|
||||
'ms-3',
|
||||
'text-base/tight',
|
||||
'lg:text-lg/tight',
|
||||
'text-tint-strong',
|
||||
'theme-bold:text-header-link'
|
||||
)}
|
||||
>
|
||||
{snapshot.site.title}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className={HEADER_LOGO_CONTAINER_CLASS}>
|
||||
<HeaderLogoContent
|
||||
logo={
|
||||
customization.header.logo ? (
|
||||
<StructurePreviewLogoImage logo={customization.header.logo} />
|
||||
) : null
|
||||
}
|
||||
fallbackIcon={<StructurePreviewLogoFallbackIcon snapshot={snapshot} />}
|
||||
title={snapshot.site.title}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StructurePreviewLogoImage(props: {
|
||||
logo: NonNullable<StructurePreviewSnapshot['customization']['header']['logo']>;
|
||||
}) {
|
||||
const { logo } = props;
|
||||
|
||||
return (
|
||||
<picture>
|
||||
{logo.dark ? <source srcSet={logo.dark} media="(prefers-color-scheme: dark)" /> : null}
|
||||
<img alt="Logo" src={logo.light} className={tcls('block', HEADER_LOGO_IMAGE_CLASS)} />
|
||||
</picture>
|
||||
);
|
||||
}
|
||||
|
||||
function StructurePreviewLogoFallbackIcon(props: { snapshot: StructurePreviewSnapshot }) {
|
||||
const { snapshot } = props;
|
||||
const { customization } = snapshot;
|
||||
|
||||
if ('emoji' in customization.favicon && customization.favicon.emoji) {
|
||||
return <span className="text-xl">{customization.favicon.emoji}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<picture>
|
||||
<source srcSet={snapshot.icons.large.dark} media="(prefers-color-scheme: dark)" />
|
||||
<img alt="" src={snapshot.icons.large.light} className="size-8 object-contain" />
|
||||
</picture>
|
||||
);
|
||||
}
|
||||
|
||||
function StructurePreviewSearch(props: { style: CustomizationSearchStyle }) {
|
||||
const language = useLanguage();
|
||||
const label = tString(language, 'search');
|
||||
|
||||
Reference in New Issue
Block a user