diff --git a/.changeset/sour-cobras-grab.md b/.changeset/sour-cobras-grab.md new file mode 100644 index 0000000000..e66a5e50a0 --- /dev/null +++ b/.changeset/sour-cobras-grab.md @@ -0,0 +1,5 @@ +--- +"gitbook": minor +--- + +Update site layout and blocks with container queries and better transitions diff --git a/packages/gitbook/src/components/AIChat/AIChat.tsx b/packages/gitbook/src/components/AIChat/AIChat.tsx index 90484bfd5c..fce68f4520 100644 --- a/packages/gitbook/src/components/AIChat/AIChat.tsx +++ b/packages/gitbook/src/components/AIChat/AIChat.tsx @@ -2,6 +2,7 @@ import { t, tString, useLanguage } from '@/intl/client'; import type { TranslationLanguage } from '@/intl/translations'; +import { tcls } from '@/lib/tailwind'; import { Icon } from '@gitbook/icons'; import React from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; @@ -65,16 +66,17 @@ export function AIChat(props: { trademark: boolean }) { } }, [chat.opened, trackEvent]); - if (!chat.opened) { - return null; - } - return (
- + diff --git a/packages/gitbook/src/components/AIChat/AIChatButton.tsx b/packages/gitbook/src/components/AIChat/AIChatButton.tsx index a990adbbdc..abe996cece 100644 --- a/packages/gitbook/src/components/AIChat/AIChatButton.tsx +++ b/packages/gitbook/src/components/AIChat/AIChatButton.tsx @@ -23,7 +23,7 @@ export function AIChatButton(props: { iconOnly={!showLabel} size="medium" variant="header" - className="h-9 px-2.5 max-md:[&_.button-content]:hidden" + className="h-9 px-2.5 @max-2xl:[&_.button-content]:hidden" label={
{t(language, 'ai_chat_ask', assistant.label)} diff --git a/packages/gitbook/src/components/DocumentView/Columns/Columns.tsx b/packages/gitbook/src/components/DocumentView/Columns/Columns.tsx index 5e7ef3cdc8..97f64a2ab4 100644 --- a/packages/gitbook/src/components/DocumentView/Columns/Columns.tsx +++ b/packages/gitbook/src/components/DocumentView/Columns/Columns.tsx @@ -80,6 +80,7 @@ export function Column(props: {
div:first-child]:hidden', '[&_.heading>div]:text-[.8em]', - 'md:[&_.heading>div]:text-[1em]', + '@xl:[&_.heading>div]:text-[1em]', '[&_.blocks:first-child_.heading]:pt-0', // Remove padding-top on first heading in card // On mobile, check if we can display the cover responsively or not: @@ -69,10 +69,10 @@ export async function RecordCard( lightCoverIsSquareOrPortrait || darkCoverIsSquareOrPortrait ? [ lightCoverIsSquareOrPortrait - ? 'grid-cols-[40%__1fr] min-[432px]:grid-cols-none min-[432px]:grid-rows-[auto_1fr]' + ? '@sm:grid-cols-none grid-cols-[40%__1fr] @sm:grid-rows-[auto_1fr]' : '', darkCoverIsSquareOrPortrait - ? 'dark:grid-cols-[40%__1fr] dark:min-[432px]:grid-cols-none dark:min-[432px]:grid-rows-[auto_1fr]' + ? 'dark:@sm:grid-cols-none dark:grid-cols-[40%__1fr] dark:@sm:grid-rows-[auto_1fr]' : '', ].filter(Boolean) : 'grid-rows-[auto_1fr]' @@ -112,11 +112,9 @@ export async function RecordCard( 'bg-tint-subtle', lightCoverIsSquareOrPortrait || darkCoverIsSquareOrPortrait ? [ - lightCoverIsSquareOrPortrait - ? 'min-[432px]:aspect-video min-[432px]:h-auto' - : '', + lightCoverIsSquareOrPortrait ? '@sm:aspect-video @sm:h-auto' : '', darkCoverIsSquareOrPortrait - ? 'dark:min-[432px]:aspect-video dark:min-[432px]:h-auto' + ? 'dark:@sm:aspect-video dark:@sm:h-auto' : '', ].filter(Boolean) : ['h-auto', 'aspect-video'], diff --git a/packages/gitbook/src/components/DocumentView/Table/ViewCards.tsx b/packages/gitbook/src/components/DocumentView/Table/ViewCards.tsx index b24ed146b2..761078f986 100644 --- a/packages/gitbook/src/components/DocumentView/Table/ViewCards.tsx +++ b/packages/gitbook/src/components/DocumentView/Table/ViewCards.tsx @@ -15,8 +15,8 @@ export function ViewCards(props: TableViewProps) { 'inline-grid', 'gap-4', 'grid-cols-1', - 'min-[432px]:grid-cols-2', - view.cardSize === 'large' ? 'md:grid-cols-2' : 'md:grid-cols-3', + '@sm:grid-cols-2', + view.cardSize === 'large' ? '@xl:grid-cols-2' : '@xl:grid-cols-3', block.data.fullWidth ? 'large:flex-column' : null )} > diff --git a/packages/gitbook/src/components/DocumentView/spacing.ts b/packages/gitbook/src/components/DocumentView/spacing.ts index b77d18cb98..097a30e641 100644 --- a/packages/gitbook/src/components/DocumentView/spacing.ts +++ b/packages/gitbook/src/components/DocumentView/spacing.ts @@ -19,19 +19,19 @@ export function getBlockTextStyle(block: DocumentBlock): { }; case 'heading-1': return { - textSize: 'text-3xl font-semibold', + textSize: 'text-xl @xs:text-2xl @lg:text-3xl font-semibold', lineHeight: 'leading-tight', marginTop: 'column-first-of-type:pt-0 pt-[1em]', }; case 'heading-2': return { - textSize: 'text-2xl font-semibold', + textSize: 'text-lg @xs:text-xl @lg:text-2xl font-semibold', lineHeight: 'leading-snug', marginTop: 'column-first-of-type:pt-0 pt-[0.75em]', }; case 'heading-3': return { - textSize: 'text-xl font-semibold', + textSize: 'text-base @xs:text-lg @lg:text-xl font-semibold', lineHeight: 'leading-snug', marginTop: 'column-first-of-type:pt-0 pt-[0.5em]', }; diff --git a/packages/gitbook/src/components/Embeddable/EmbeddableFrame.tsx b/packages/gitbook/src/components/Embeddable/EmbeddableFrame.tsx index 9b233da756..438470fb3f 100644 --- a/packages/gitbook/src/components/Embeddable/EmbeddableFrame.tsx +++ b/packages/gitbook/src/components/Embeddable/EmbeddableFrame.tsx @@ -17,7 +17,7 @@ export const EmbeddableFrame = React.forwardRef -
+
- { - // Footer Logo - customization.footer.logo ? ( -
- Logo -
- ) : null - } +
+ { + // Footer Logo + customization.footer.logo ? ( +
+ Logo +
+ ) : null + } - { - // Theme Toggle - customization.themes.toggeable ? ( -
- - - -
- ) : null - } + { + // Theme Toggle + customization.themes.toggeable ? ( +
+ + + +
+ ) : null + } - { - // Navigation groups (split into equal columns) - customization.footer.groups?.length > 0 ? ( -
-
- {partition(customization.footer.groups, FOOTER_COLUMNS).map( - (column, columnIndex) => ( -
- {column.map((group, groupIndex) => ( - - ))} -
- ) + { + // Navigation groups (split into equal columns) + customization.footer.groups?.length > 0 ? ( +
+
+ {partition(customization.footer.groups, FOOTER_COLUMNS).map( + (column, columnIndex) => ( +
+ {column.map((group, groupIndex) => ( + + ))} +
+ ) + )} +
-
- ) : null - } + ) : null + } - { - // Legal - customization.footer.copyright ? ( -
-

{customization.footer.copyright}

-
- ) : null - } + { + // Legal + customization.footer.copyright ? ( +
+

{customization.footer.copyright}

+
+ ) : null + } +
diff --git a/packages/gitbook/src/components/Header/Header.tsx b/packages/gitbook/src/components/Header/Header.tsx index 9253aa5049..fa0089bc27 100644 --- a/packages/gitbook/src/components/Header/Header.tsx +++ b/packages/gitbook/src/components/Header/Header.tsx @@ -67,7 +67,7 @@ export function Header(props: { 'theme-bold:shadow-tint-12/2' )} > -
+
1 - ? 'xl:hidden' + ? 'lg:hidden' : 'page-no-toc:hidden lg:hidden' )} /> @@ -108,22 +114,23 @@ export function Header(props: { 'flex', 'grow-0', 'shrink-0', - 'md:basis-56', + '@2xl:basis-56', 'justify-self-end', 'items-center', 'gap-2', + 'transition-[margin] duration-300', 'search' in customization.styling && customization.styling.search === 'prominent' ? [ - 'md:grow-[0.8]', - 'lg:basis-40', - 'md:max-w-[40%]', - 'lg:max-w-lg', - 'lg:ml-[max(calc((100%-18rem-48rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - 'xl:ml-[max(calc((100%-18rem-48rem-14rem-3rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - outline (14rem) - margin (3rem) - 'md:mr-auto', + '@2xl:grow-[0.8]', + '@4xl:basis-40', + '@2xl:max-w-[40%]', + '@4xl:max-w-lg', + 'lg:@2xl:ml-[max(calc((100%-18rem-48rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) + 'not-chat-open:xl:ml-[max(calc((100%-18rem-48rem-14rem-3rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - outline (14rem) - margin (3rem) + '@2xl:mr-auto', 'order-last', - 'md:order-[unset]', + '@2xl:order-[unset]', ] : ['order-last'] )} diff --git a/packages/gitbook/src/components/Header/HeaderLinks.tsx b/packages/gitbook/src/components/Header/HeaderLinks.tsx index a96467c20d..da2e01e831 100644 --- a/packages/gitbook/src/components/Header/HeaderLinks.tsx +++ b/packages/gitbook/src/components/Header/HeaderLinks.tsx @@ -14,7 +14,7 @@ export async function HeaderLinks({ children, style }: HeaderLinksProps) {
.button+.button]:-ml-2 z-20 ml-auto flex min-w-9 shrink grow items-center justify-end gap-x-4 lg:gap-x-6 xl:grow-0', + '@4xl:[&>.button+.button]:-ml-2 z-20 ml-auto flex min-w-9 shrink grow @7xl:grow-0 items-center justify-end @4xl:gap-x-6 gap-x-4', style )} > diff --git a/packages/gitbook/src/components/Header/headerLinks.module.css b/packages/gitbook/src/components/Header/headerLinks.module.css index 2a5736b43e..8384648e3b 100644 --- a/packages/gitbook/src/components/Header/headerLinks.module.css +++ b/packages/gitbook/src/components/Header/headerLinks.module.css @@ -1,4 +1,4 @@ -@media (max-width: 1279px) { +@container header (width < 1280px) { .containerHeaderlinks { container-type: inline-size; container-name: headerlinks; diff --git a/packages/gitbook/src/components/PageAside/PageAside.tsx b/packages/gitbook/src/components/PageAside/PageAside.tsx index 6b14991f94..5bf96aeaf4 100644 --- a/packages/gitbook/src/components/PageAside/PageAside.tsx +++ b/packages/gitbook/src/components/PageAside/PageAside.tsx @@ -37,24 +37,32 @@ export function PageAside(props: { 'group/aside', 'order-last', 'hidden', + 'max-w-0', 'pt-8', 'pb-4', + 'opacity-0', 'xl:flex', - 'xl:max-3xl:chat-open:hidden', - 'xl:max-3xl:chat-open:opacity-0', - 'max-w-56', - // Animate the width of the aside when the chat is open - 'xl:max-3xl:*:chat-open:w-56', + 'overflow-hidden', + + 'xl:max-w-56', + 'xl:opacity-11', + 'xl:ml-12', + + 'xl:max-3xl:chat-open:hidden', 'xl:max-3xl:chat-open:max-w-0', + 'xl:max-3xl:chat-open:opacity-0', 'xl:max-3xl:chat-open:ml-0', - 'motion-safe:xl:transition-[width,max-width,margin,opacity,display] motion-safe:xl:duration-300', - 'motion-safe:transition-discrete', + 'hydrated:starting:ml-0', + 'hydrated:starting:max-w-0', + 'hydrated:starting:opacity-0', + + 'transition-[margin,max-width,opacity,display] duration-300', + 'transition-discrete', 'basis-56', - 'xl:ml-12', 'grow-0', 'shrink-0', 'break-anywhere', // To prevent long words in headings from breaking the layout @@ -93,6 +101,7 @@ export function PageAside(props: {
( variantClasses.header, sizeClasses, // Additional custom styles - 'has-[input:focus]:-translate-y-px h-9 grow cursor-pointer px-2.5 has-[input:focus]:bg-tint-base has-[input:focus]:depth-subtle:shadow-lg has-[input:focus]:depth-subtle:shadow-primary-subtle has-[input:focus-visible]:ring-2 has-[input:focus-visible]:ring-primary-hover md:cursor-text', + 'has-[input:focus]:-translate-y-px h-9 grow @2xl:cursor-text cursor-pointer px-2.5 has-[input:focus]:bg-tint-base has-[input:focus]:depth-subtle:shadow-lg has-[input:focus]:depth-subtle:shadow-primary-subtle has-[input:focus-visible]:ring-2 has-[input:focus-visible]:ring-primary-hover', 'theme-bold:border-header-link/3 has-[input:focus-visible]:theme-bold:border-header-link/5 has-[input:focus-visible]:theme-bold:bg-header-link/3 has-[input:focus-visible]:theme-bold:ring-header-link/5', 'theme-bold:before:absolute theme-bold:before:inset-0 theme-bold:before:bg-header-background/7 theme-bold:before:backdrop-blur-xl ', // Special overlay to make the transparent colors of theme-bold visible. - 'relative z-30 max-w-none shrink grow justify-start max-md:absolute max-md:right-0', - isOpen ? 'max-md:w-56' : 'max-md:w-[38px]' + '@max-2xl:absolute relative @max-2xl:right-0 z-30 max-w-none shrink grow justify-start', + isOpen ? '@max-2xl:w-56' : '@max-2xl:w-[38px]' )} > {value && isOpen ? ( @@ -111,7 +111,7 @@ export const SearchInput = React.forwardRef( data-testid="search-input" className={tcls( 'peer z-10 min-w-0 grow bg-transparent py-0.5 text-tint-strong theme-bold:text-header-link outline-hidden transition-[width] duration-300 contain-paint placeholder:text-tint theme-bold:placeholder:text-current theme-bold:placeholder:opacity-7', - isOpen ? '' : 'max-md:opacity-0' + isOpen ? '' : '@max-2xl:opacity-0' )} role="combobox" autoComplete="off" diff --git a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx index 48f0d66f0d..aa963284da 100644 --- a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx +++ b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx @@ -129,7 +129,8 @@ export function SpaceLayout(props: SpaceLayoutProps) { 'lg:flex-row', 'lg:justify-center', CONTAINER_STYLE, - 'site-width-wide:max-w-full', + 'site-width-wide:max-w-screen-4xl', + 'hydrated:transition-[max-width] duration-300', // Ensure the footer is display below the viewport even if the content is not enough withFooter && [ diff --git a/packages/gitbook/src/components/primitives/KeyboardShortcut.tsx b/packages/gitbook/src/components/primitives/KeyboardShortcut.tsx index 891fc5efd9..49705139ac 100644 --- a/packages/gitbook/src/components/primitives/KeyboardShortcut.tsx +++ b/packages/gitbook/src/components/primitives/KeyboardShortcut.tsx @@ -28,7 +28,7 @@ export function KeyboardShortcut(props: { keys: string[]; className?: ClassValue className={tcls( 'shortcut hidden justify-end gap-0.5 whitespace-nowrap text-tint text-xs [font-feature-settings:"calt","case"] contrast-more:text-tint-strong md:flex', operatingSystem - ? 'motion-safe:animate-fade-in motion-reduce:opacity-100' + ? 'motion-safe:animate-fade-in motion-reduce:opacity-11' : 'opacity-0' )} > diff --git a/packages/gitbook/src/components/primitives/NavigationLoader.tsx b/packages/gitbook/src/components/primitives/NavigationLoader.tsx index 1ce47ad164..76c63f880f 100644 --- a/packages/gitbook/src/components/primitives/NavigationLoader.tsx +++ b/packages/gitbook/src/components/primitives/NavigationLoader.tsx @@ -1,9 +1,31 @@ 'use client'; import { tcls } from '@/lib/tailwind'; +import { usePathname } from 'next/navigation'; +import { useEffect, useLayoutEffect } from 'react'; import { useIsNavigating } from '../hooks'; export const NavigationLoader = () => { const isNavigating = useIsNavigating(); + const pathname = usePathname(); + + // Mark client hydration so initial paint doesn't animate. + useEffect(() => { + document.documentElement.classList.add('hydrated'); + }, []); + + // On route changes, add a transient class for the first paint of the new page. + useLayoutEffect(() => { + void pathname; + const root = document.documentElement; + root.classList.add('route-change'); + const raf1 = requestAnimationFrame(() => { + const raf2 = requestAnimationFrame(() => { + root.classList.remove('route-change'); + }); + return () => cancelAnimationFrame(raf2); + }); + return () => cancelAnimationFrame(raf1); + }, [pathname]); return (
&:first-of-type'); // optional for group-based variants