mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-24 11:26:31 +00:00
Start theming header
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import shadesOf from 'tailwind-shades';
|
||||
import colors from 'tailwindcss/colors';
|
||||
import { Inter } from 'next/font/google';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { PagePathParams, fetchPageData } from '../fetch';
|
||||
import assertNever from 'assert-never';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
@@ -11,6 +13,7 @@ export default async function SpaceRootLayout(props: {
|
||||
}) {
|
||||
const { children, params } = props;
|
||||
const { customization } = await fetchPageData(params);
|
||||
const headerTheme = generateHeaderTheme(customization);
|
||||
|
||||
return (
|
||||
<html lang={customization.internationalization.locale}>
|
||||
@@ -21,12 +24,22 @@ export default async function SpaceRootLayout(props: {
|
||||
'primary-color',
|
||||
customization.styling.primaryColor.light,
|
||||
)}
|
||||
${generateCSSVariable(
|
||||
'header-background',
|
||||
headerTheme.backgroundColor.light,
|
||||
)}
|
||||
${generateCSSVariable('header-link', headerTheme.linkColor.light)}
|
||||
}
|
||||
.dark {
|
||||
${generateCSSVariable(
|
||||
'primary-color',
|
||||
customization.styling.primaryColor.dark,
|
||||
)}
|
||||
${generateCSSVariable(
|
||||
'header-background',
|
||||
headerTheme.backgroundColor.dark,
|
||||
)}
|
||||
${generateCSSVariable('header-link', headerTheme.linkColor.dark)}
|
||||
}
|
||||
`}</style>
|
||||
</head>
|
||||
@@ -37,8 +50,9 @@ export default async function SpaceRootLayout(props: {
|
||||
);
|
||||
}
|
||||
|
||||
function generateCSSVariable(name: string, color: string) {
|
||||
const shades: Record<number, string> = shadesOf(color);
|
||||
type ColorInput = string | Record<string, string>;
|
||||
function generateCSSVariable(name: string, color: ColorInput) {
|
||||
const shades: Record<string, string> = typeof color === 'string' ? shadesOf(color) : color;
|
||||
|
||||
return Object.entries(shades)
|
||||
.map(([key, value]) => {
|
||||
@@ -46,3 +60,67 @@ function generateCSSVariable(name: string, color: string) {
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
function generateHeaderTheme(customization: any): {
|
||||
backgroundColor: { light: ColorInput; dark: ColorInput };
|
||||
linkColor: { light: ColorInput; dark: ColorInput };
|
||||
} {
|
||||
switch (customization.header.preset) {
|
||||
case 'default': {
|
||||
return {
|
||||
backgroundColor: {
|
||||
light: colors.white,
|
||||
dark: colors.black,
|
||||
},
|
||||
linkColor: {
|
||||
light: customization.styling.primaryColor.light,
|
||||
dark: customization.styling.primaryColor.dark,
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'bold': {
|
||||
return {
|
||||
backgroundColor: {
|
||||
light: customization.styling.primaryColor.light,
|
||||
dark: customization.styling.primaryColor.dark,
|
||||
},
|
||||
linkColor: {
|
||||
// TODO: should depend on the color of the background
|
||||
light: colors.white,
|
||||
dark: colors.black,
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'contrast': {
|
||||
return {
|
||||
backgroundColor: {
|
||||
light: customization.styling.primaryColor.dark,
|
||||
dark: customization.styling.primaryColor.light,
|
||||
},
|
||||
linkColor: {
|
||||
light: colors.white,
|
||||
dark: colors.black,
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'custom': {
|
||||
return {
|
||||
backgroundColor: {
|
||||
light: customization.header.backgroundColor?.light ?? colors.white,
|
||||
dark: customization.header.backgroundColor?.dark ?? colors.black,
|
||||
},
|
||||
linkColor: {
|
||||
light:
|
||||
customization.header.linkColor?.light ??
|
||||
customization.styling.primaryColor.light,
|
||||
dark:
|
||||
customization.header.linkColor?.dark ??
|
||||
customization.styling.primaryColor.dark,
|
||||
},
|
||||
};
|
||||
}
|
||||
default: {
|
||||
assertNever(customization.header.preset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,6 @@ export async function fetchPageData(params: PagePathParams | PageIdParams) {
|
||||
getSpaceCustomization(spaceId),
|
||||
]);
|
||||
|
||||
console.log(customization);
|
||||
|
||||
const page =
|
||||
'pageId' in params && params.pageId
|
||||
? resolvePageId(revision, params.pageId)
|
||||
|
||||
@@ -31,7 +31,7 @@ export function Header(props: { space: Space; asFullWidth: boolean; customizatio
|
||||
'lg:border-b',
|
||||
'lg:border-slate-900/10',
|
||||
'dark:border-slate-50/[0.06]',
|
||||
'bg-white/95',
|
||||
'bg-header-background-500',
|
||||
'supports-backdrop-blur:bg-white/60',
|
||||
'dark:bg-transparent',
|
||||
)}
|
||||
@@ -46,10 +46,19 @@ export function Header(props: { space: Space; asFullWidth: boolean; customizatio
|
||||
asFullWidth ? null : [CONTAINER_MAX_WIDTH_NORMAL, 'mx-auto'],
|
||||
)}
|
||||
>
|
||||
<HeaderLogo space={space} customization={customization} />
|
||||
<HeaderLogo
|
||||
space={space}
|
||||
customization={customization}
|
||||
textStyle={[
|
||||
'text-header-link-500',
|
||||
'group-headerlogo/hover:text-header-link-700',
|
||||
]}
|
||||
/>
|
||||
<div className={tcls('flex', 'basis-56', 'grow-0', 'shrink-0')}>
|
||||
<Suspense fallback={null}>
|
||||
<SearchButton>{t({ space }, 'search')}</SearchButton>
|
||||
<SearchButton style={['bg-header-background-300']}>
|
||||
{t({ space }, 'search')}
|
||||
</SearchButton>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { absoluteHref } from '@/lib/links';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { ClassValue, tcls } from '@/lib/tailwind';
|
||||
import { Space } from '@gitbook/api';
|
||||
import Link from 'next/link';
|
||||
|
||||
@@ -9,12 +9,20 @@ import Link from 'next/link';
|
||||
* - Image logo
|
||||
* - Or fallback to text with icon before
|
||||
*/
|
||||
export function HeaderLogo(props: { space: Space; customization: any }) {
|
||||
const { space } = props;
|
||||
export function HeaderLogo(props: {
|
||||
space: Space;
|
||||
customization: any;
|
||||
|
||||
/** Style applied when the logo is a text one */
|
||||
textStyle?: ClassValue;
|
||||
}) {
|
||||
const { space, textStyle } = props;
|
||||
|
||||
return (
|
||||
<Link href={absoluteHref('')} className={tcls('flex-1')}>
|
||||
<h1 className={tcls('text-lg', 'text-slate-800', 'font-semibold')}>{space.title}</h1>
|
||||
<Link href={absoluteHref('')} className={tcls('flex-1', 'group/headerlogo')}>
|
||||
<h1 className={tcls('text-lg', 'text-slate-800', 'font-semibold', textStyle)}>
|
||||
{space.title}
|
||||
</h1>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
+56
-7
@@ -68,13 +68,62 @@ export const getPageDocument = unstable_cache(
|
||||
export const getSpaceCustomization = unstable_cache(
|
||||
async (spaceId: string) => {
|
||||
// TODO: use function from API client once updated
|
||||
const { data } = await api().request({
|
||||
method: 'GET',
|
||||
path: `/spaces/${spaceId}/publishing/customization`,
|
||||
secure: true,
|
||||
format: 'json',
|
||||
});
|
||||
return data;
|
||||
try {
|
||||
throw new Error('test');
|
||||
const { data } = await api().request({
|
||||
method: 'GET',
|
||||
path: `/spaces/${spaceId}/publishing/customization`,
|
||||
secure: true,
|
||||
format: 'json',
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
// TODO: remove hardcoded value as soon as we ship the PAI
|
||||
return {
|
||||
inherit: false,
|
||||
internationalization: { inherit: false, locale: 'en' },
|
||||
styling: {
|
||||
font: 'Inter',
|
||||
corners: 'rounded',
|
||||
primaryColor: { light: '#b93d92', dark: '#346DDB' },
|
||||
},
|
||||
favicon: {},
|
||||
header: {
|
||||
preset: 'bold',
|
||||
links: [
|
||||
{
|
||||
links: [
|
||||
{
|
||||
to: { kind: 'url', url: 'https://www.google.com' },
|
||||
title: 'Sub-link',
|
||||
},
|
||||
{
|
||||
to: { kind: 'url', url: 'https://www.google.com' },
|
||||
title: 'Sub-link 2',
|
||||
},
|
||||
],
|
||||
to: { kind: 'url', url: 'https://www.google.fr' },
|
||||
title: 'Link 1',
|
||||
},
|
||||
{
|
||||
links: [],
|
||||
to: { kind: 'url', url: 'https://www.google.com' },
|
||||
title: 'Link 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: { groups: [] },
|
||||
themes: { default: 'light', toggeable: false },
|
||||
trademark: { enabled: true },
|
||||
feedback: { enabled: false },
|
||||
pdf: { enabled: false },
|
||||
aiSearch: { enabled: false },
|
||||
pagination: { enabled: false },
|
||||
privacyPolicy: {},
|
||||
socialPreview: {},
|
||||
git: { showEditLink: false },
|
||||
};
|
||||
}
|
||||
},
|
||||
['api', 'customization'],
|
||||
{
|
||||
|
||||
+3
-1
@@ -22,8 +22,10 @@ const config: Config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// Dynamic color to present the space primary color
|
||||
// Dynamic colors matching the customization settings
|
||||
primary: generateShades('primary-color'),
|
||||
'header-background': generateShades('header-background'),
|
||||
'header-link': generateShades('header-link'),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user