Layout fixes

This commit is contained in:
Zeno Kapitein
2025-05-07 12:56:08 +02:00
parent c3bde7d989
commit c38d403f77
10 changed files with 71 additions and 35 deletions
@@ -13,11 +13,12 @@ export function AIPageSummary() {
const visitedPagesRef = useRef(visitedPages);
const [summary, setSummary] = useState<{
pageSummary?: string;
keyFacts?: string;
bigPicture?: string;
}>({});
useEffect(() => {
if (!summary.keyFacts) setLoading(true);
if (!visitedPages?.length) return;
// Skip if the visited pages haven't changed
@@ -47,12 +48,6 @@ export function AIPageSummary() {
}
})().finally(() => {
setLoading(false);
if (!toggle.manual) {
setToggle({
open: true,
manual: false,
});
}
});
return () => {
@@ -64,13 +59,13 @@ export function AIPageSummary() {
return (
toggle.open && (
<div className="flex animate-fadeIn flex-col gap-4">
{summary.pageSummary ? (
<div className="flex min-w-64 animate-fadeIn flex-col gap-4">
{summary.keyFacts ? (
<div>
<h5 className="mb-0.5 font-semibold text-tint-subtle text-xs uppercase">
Key facts
</h5>
{summary.pageSummary}
{summary.keyFacts}
</div>
) : (
<div className="flex w-full flex-wrap gap-2">
@@ -19,11 +19,39 @@ export const AdaptiveContext = React.createContext<AdaptiveContextType | null>(n
*/
export function AdaptiveContextProvider({ children }: { children: React.ReactNode }) {
const [loading, setLoading] = React.useState(true);
// Start with a default state that works for SSR
const [toggle, setToggle] = React.useState({
open: true,
open: false, // Default to open for SSR
manual: false,
});
// Update the toggle state on the client side only
React.useEffect(() => {
// Check for mobile only on the client
const handleResize = () => {
if (!toggle.manual) {
const isMobile = window.innerWidth < 1280;
setToggle((prev) => ({
...prev,
open: !isMobile,
}));
}
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [toggle.manual]);
React.useEffect(() => {
if (toggle.open) {
document.body.classList.add('adaptive-pane');
} else {
document.body.classList.remove('adaptive-pane');
}
}, [toggle.open]);
return (
<AdaptiveContext.Provider value={{ loading, setLoading, toggle, setToggle }}>
{children}
@@ -10,8 +10,8 @@ export function AdaptivePane() {
return (
<div
className={tcls(
'flex flex-col gap-4 rounded-md straight-corners:rounded-none bg-tint-subtle ring-1 ring-tint-subtle ring-inset transition-all duration-300',
toggle.open ? 'w-72 px-4 py-4' : 'w-56 px-4 py-3'
'flex shrink-0 flex-col gap-4 overflow-x-hidden rounded-md straight-corners:rounded-none bg-tint-subtle ring-1 ring-tint-subtle ring-inset transition-all duration-300',
toggle.open ? 'max-h px-4 py-4 xl:w-72' : 'px-4 py-3 xl:w-56'
)}
>
<AdaptivePaneHeader />
@@ -21,6 +21,7 @@ export function AdaptivePaneHeader() {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
className="text-tint-subtle text-xs"
>
{loading ? 'Basing on your context...' : 'Based on your context'}
@@ -40,7 +40,7 @@ export async function* streamPageSummary({
},
{
schema: z.object({
pageSummary: z
keyFacts: z
.string()
.describe(
'A collection of key facts from the page that together form a comprehensive summary. Keep it under 30 words.'
@@ -201,13 +201,13 @@ export async function* streamPageSummary({
]);
for await (const value of stream) {
const pageSummary = value.pageSummary;
const keyFacts = value.keyFacts;
const bigPicture = value.bigPicture;
if (!pageSummary) continue;
if (!keyFacts) continue;
yield {
pageSummary,
keyFacts,
bigPicture,
};
}
@@ -106,12 +106,16 @@ export function Header(props: { context: GitBookSiteContext; withTopHeader?: boo
'lg:max-w-lg',
'lg:ml-[max(calc((100%-18rem-48rem-3rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - margin (3rem)
'xl:ml-[max(calc((100%-18rem-48rem-14rem-3rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - outline (14rem) - margin (3rem)
'adaptive-pane:xl:ml-[max(calc((100%-18rem-48rem-18rem-3rem)/2),1.5rem)]',
'page-no-toc:lg:ml-[max(calc((100%-18rem-48rem-18rem-3rem)/2),0rem)]',
'page-full-width:lg:ml-[max(calc((100%-18rem-103rem-3rem)/2),1.5rem)]',
'page-full-width:2xl:ml-[max(calc((100%-18rem-96rem-14rem+3rem)/2),1.5rem)]',
'[body.adaptive-pane:has(.page-full-width)_&]:2xl:ml-[max(calc((100%-18rem-96rem-18rem+3rem)/2),1.5rem)]',
'md:mr-auto',
'order-last',
'md:order-[unset]',
'transition-[margin-left]',
'duration-300',
]
: ['order-last']
)}
@@ -35,10 +35,11 @@ export function PageAside(props: {
<aside
className={tcls(
'group/aside',
'hidden',
'flex',
// 'hidden',
'xl:flex',
'flex-col',
'basis-56',
'xl:basis-56',
'grow-0',
'shrink-0',
'break-anywhere', // To prevent long words in headings from breaking the layout
@@ -46,7 +47,14 @@ export function PageAside(props: {
'text-tint',
'contrast-more:text-tint-strong',
'text-sm',
'sticky',
'xl:sticky',
'lg:px-12',
'xl:px-0',
'mx-auto',
'xl:mx-0',
'w-full',
'max-w-3xl',
// Without header
'lg:top-0',
@@ -79,22 +87,22 @@ export function PageAside(props: {
'page-api-block:p-2'
)}
>
<div className="lg:top:0 sticky flex grow flex-col gap-6 overflow-y-auto overflow-x-visible border-none py-8 *:border-tint-subtle site-header-sections:lg:top-[6.75rem] site-header:lg:top-16 [&>*:not(:first-child)]:border-t [&>*:not(:first-child)]:pt-6">
<div className="lg:top:0 sticky flex grow flex-col gap-6 overflow-y-auto overflow-x-visible border-none pt-8 *:border-tint-subtle site-header-sections:lg:top-[6.75rem] site-header:lg:top-16 xl:pb-8 [&>*:not(:first-child)]:border-t [&>*:not(:first-child)]:pt-6">
{useAdaptivePane ? <AdaptivePane /> : null}
{page.layout.outline ? (
<>
<div className="hidden flex-col gap-6 xl:flex">
<PageOutline document={document} context={context} />
<PageActions
page={page}
context={context}
withPageFeedback={withPageFeedback}
/>
</>
</div>
) : null}
</div>
<div
className={tcls(
'sticky bottom-0 z-10 mt-auto flex flex-col bg-tint-base theme-gradient-tint:bg-gradient-tint theme-gradient:bg-gradient-primary theme-muted:bg-tint-subtle pb-4 page-api-block:xl:max-2xl:hidden page-api-block:xl:max-2xl:pb-0 page-api-block:xl:max-2xl:group-hover/aside:flex [html.sidebar-filled.theme-bold.tint_&]:bg-tint-subtle',
'sticky bottom-0 z-10 mt-auto hidden flex-col bg-tint-base theme-gradient-tint:bg-gradient-tint theme-gradient:bg-gradient-primary theme-muted:bg-tint-subtle pb-4 xl:flex page-api-block:xl:max-2xl:hidden page-api-block:xl:max-2xl:pb-0 page-api-block:xl:max-2xl:group-hover/aside:flex [html.sidebar-filled.theme-bold.tint_&]:bg-tint-subtle',
'page-api-block:xl:max-2xl:bg-transparent'
)}
>
@@ -70,7 +70,7 @@ export async function SitePage(props: SitePageProps) {
<PageCover as="full" page={page} cover={page.cover} context={context} />
) : null}
{/* We use a flex row reverse to render the aside first because the page is streamed. */}
<div className="flex grow flex-row-reverse justify-end">
<div className="flex grow flex-col xl:flex-row-reverse xl:justify-end">
<PageAside
page={page}
document={document}
@@ -1,7 +1,5 @@
import type { SVGProps } from 'react';
import { tcls } from '@/lib/tailwind';
export const Loading = ({
busy = true,
...props
@@ -13,29 +11,26 @@ export const Loading = ({
preserveAspectRatio="xMaxYMid meet"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-busy
aria-busy={busy}
{...props}
>
<title>{busy ? 'Loading' : 'Loaded'}</title>
<path
className={tcls(
busy
? 'animate-[pathLoading_2s_ease_infinite_forwards]'
: 'animate-[pathLoading_2s_ease_forwards]'
)}
d="M6 59.5V56.291C6 45.8865 11.5194 36.263 20.5 31.0091V31.0091L60.9857 7.32407C63.4452 5.88525 66.4843 5.86317 68.9643 7.26611L116 33.8734L70.4183 60.2148C67.9468 61.6431 64.9014 61.6462 62.4269 60.223L29.9772 41.5592C19.3106 35.4242 6 43.1236 6 55.4288V64.8776C6 73.4486 10.5708 81.3691 17.9918 85.6575L54.59 106.807C62.0198 111.1 71.1766 111.1 78.6064 106.807L116.364 84.9874C120.074 82.8432 122.36 78.883 122.36 74.5975V59.2647C122.36 57.7248 120.692 56.7626 119.359 57.5331L72.6023 84.5529C68.8874 86.6996 64.309 86.6996 60.5941 84.5529L26 64.5617"
stroke="currentColor"
pathLength="100"
strokeOpacity={busy ? 0.24 : 1}
className="transition-[stroke-opacity] duration-500"
fill="none"
strokeWidth="11"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
className="animate-[pathLoading_2s_ease_infinite_forwards] transition-opacity delay-500 duration-500"
d="M6 59.5V56.291C6 45.8865 11.5194 36.263 20.5 31.0091V31.0091L60.9857 7.32407C63.4452 5.88525 66.4843 5.86317 68.9643 7.26611L116 33.8734L70.4183 60.2148C67.9468 61.6431 64.9014 61.6462 62.4269 60.223L29.9772 41.5592C19.3106 35.4242 6 43.1236 6 55.4288V64.8776C6 73.4486 10.5708 81.3691 17.9918 85.6575L54.59 106.807C62.0198 111.1 71.1766 111.1 78.6064 106.807L116.364 84.9874C120.074 82.8432 122.36 78.883 122.36 74.5975V59.2647C122.36 57.7248 120.692 56.7626 119.359 57.5331L72.6023 84.5529C68.8874 86.6996 64.309 86.6996 60.5941 84.5529L26 64.5617"
stroke="currentColor"
pathLength="100"
strokeOpacity={busy ? 0.24 : 1}
className="transition-opacity duration-1000"
fill="none"
strokeWidth="11"
strokeLinecap="round"
+5
View File
@@ -465,6 +465,11 @@ const config: Config = {
'body:has(.page-no-toc):has(#site-header:not(.mobile-only) #variants) &',
]);
/**
* Variant when the adaptive pane is open.
*/
addVariant('adaptive-pane', 'body.adaptive-pane &');
const customisationVariants = {
// Sidebar styles
sidebar: ['sidebar-default', 'sidebar-filled'],