mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-23 19:06:31 +00:00
Load the admin toolbar and its CSS lazily (RND-12524) (#4526)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Load the admin toolbar and its CSS lazily so published pages no longer ship a render-blocking stylesheet for admin-only UI.
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AdminToolbarClient } from './AdminToolbarClient';
|
||||
import { AdminToolbarLazy } from './AdminToolbarLazy';
|
||||
import { getToolbarChangedPages } from './changedPages';
|
||||
import type { AdminToolbarContext } from './types';
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
@@ -62,5 +62,5 @@ export async function AdminToolbar(props: AdminToolbarProps) {
|
||||
changedPages,
|
||||
};
|
||||
|
||||
return <AdminToolbarClient context={minimalContext} />;
|
||||
return <AdminToolbarLazy context={minimalContext} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import type { AdminToolbarClientProps } from './types';
|
||||
|
||||
// The toolbar only ever appears after hydration (`useToolbarVisibility` reads cookies and local
|
||||
// storage), so nothing is lost by deferring it — and it takes its CSS modules out of the route's
|
||||
// render-blocking stylesheets, where every published page was paying for them.
|
||||
const AdminToolbarClient = dynamic(
|
||||
() => import('./AdminToolbarClient').then((mod) => mod.AdminToolbarClient),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export function AdminToolbarLazy(props: AdminToolbarClientProps) {
|
||||
return <AdminToolbarClient {...props} />;
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
// Only the server entrypoint: re-exporting the client modules would pull their CSS modules back
|
||||
// into the route's render-blocking stylesheets, defeating the lazy boundary.
|
||||
export * from './AdminToolbar';
|
||||
export * from './AdminToolbarClient';
|
||||
export * from './IframeWrapper';
|
||||
export * from './Toolbar';
|
||||
export * from './transitions';
|
||||
export * from './utils';
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
} from '../primitives/DropdownMenu';
|
||||
import { SiteAuthLoginDropdownMenuItem } from '../SiteAuth/SiteAuthLoginLink';
|
||||
import { HeaderLinkMoreDropdown } from './HeaderLinkMoreClient';
|
||||
import styles from './headerLinks.module.css';
|
||||
import { isSiteAuthLoginHref } from '@/lib/auth-login-link';
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
import { resolveContentRef } from '@/lib/references';
|
||||
@@ -34,7 +33,7 @@ export function HeaderLinkMore(props: {
|
||||
const { label, links, context, socialAccounts } = props;
|
||||
|
||||
return (
|
||||
<div className={`${styles.linkEllipsis} z-20 items-center`}>
|
||||
<div className="header-links-ellipsis z-20 items-center">
|
||||
<HeaderLinkMoreDropdown
|
||||
label={label}
|
||||
dropdownClassName={tcls(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type React from 'react';
|
||||
|
||||
import styles from './headerLinks.module.css';
|
||||
import { type ClassValue, tcls } from '@/lib/tailwind';
|
||||
|
||||
interface HeaderLinksProps {
|
||||
@@ -12,7 +11,7 @@ export async function HeaderLinks({ children, style }: HeaderLinksProps) {
|
||||
return (
|
||||
<div
|
||||
className={tcls(
|
||||
styles.containerHeaderlinks,
|
||||
'header-links',
|
||||
'@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
|
||||
)}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
@container header (width < 1280px) {
|
||||
.containerHeaderlinks {
|
||||
container-type: inline-size;
|
||||
container-name: headerlinks;
|
||||
}
|
||||
}
|
||||
|
||||
.linkEllipsis {
|
||||
display: none;
|
||||
& div > a {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@container headerlinks (width < 150px) {
|
||||
.containerHeaderlinks > :nth-child(n + 1) {
|
||||
display: none;
|
||||
}
|
||||
.containerHeaderlinks > :nth-child(n + 1) ~ .linkEllipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 1) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 300px) {
|
||||
.containerHeaderlinks > :nth-child(n + 2) {
|
||||
display: none;
|
||||
}
|
||||
.containerHeaderlinks > :nth-child(n + 2) ~ .linkEllipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 2) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 450px) {
|
||||
.containerHeaderlinks > :nth-child(n + 3) {
|
||||
display: none;
|
||||
}
|
||||
.containerHeaderlinks > :nth-child(n + 3) ~ .linkEllipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 3) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 550px) {
|
||||
.containerHeaderlinks > :nth-child(n + 4) {
|
||||
display: none;
|
||||
}
|
||||
.containerHeaderlinks > :nth-child(n + 4) ~ .linkEllipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 4) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 650px) {
|
||||
.containerHeaderlinks > :nth-child(n + 5) {
|
||||
display: none;
|
||||
}
|
||||
.containerHeaderlinks > :nth-child(n + 5) ~ .linkEllipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 5) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 750px) {
|
||||
.containerHeaderlinks > :nth-child(n + 6) {
|
||||
display: none;
|
||||
}
|
||||
.containerHeaderlinks > :nth-child(n + 6) ~ .linkEllipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 6) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -552,3 +552,107 @@ html.dark .highlight-line.diff-deleted .highlight-line-content::before {
|
||||
.button-group > :is(button, a):not(:first-child):has(~ :is(button, a)) {
|
||||
@apply rounded-none!;
|
||||
}
|
||||
|
||||
/* Zoomable images */
|
||||
html:has(.zoom-modal) {
|
||||
/* stylelint-disable-next-line plugin/no-unsupported-browser-features -- single-value overflow is universal; doiuse flags the whole css-overflow feature */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.zoom-img {
|
||||
/* stylelint-disable-next-line plugin/no-unsupported-browser-features -- zoom cursors are irrelevant on the touch browsers doiuse flags */
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.zoom-image-active {
|
||||
view-transition-name: zoom-image;
|
||||
}
|
||||
|
||||
.zoom-modal img {
|
||||
view-transition-name: zoom-image;
|
||||
/* stylelint-disable-next-line plugin/no-unsupported-browser-features -- zoom cursors are irrelevant on the touch browsers doiuse flags */
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
/* Header links collapse into the "more" dropdown one by one as the header narrows. Each breakpoint
|
||||
hides one more link and reveals the ellipsis holding the ones that no longer fit. */
|
||||
@container header (width < 1280px) {
|
||||
.header-links {
|
||||
container-type: inline-size;
|
||||
container-name: headerlinks;
|
||||
}
|
||||
}
|
||||
|
||||
.header-links-ellipsis {
|
||||
display: none;
|
||||
& div > a {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@container headerlinks (width < 150px) {
|
||||
.header-links > :nth-child(n + 1) {
|
||||
display: none;
|
||||
}
|
||||
.header-links > :nth-child(n + 1) ~ .header-links-ellipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 1) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 300px) {
|
||||
.header-links > :nth-child(n + 2) {
|
||||
display: none;
|
||||
}
|
||||
.header-links > :nth-child(n + 2) ~ .header-links-ellipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 2) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 450px) {
|
||||
.header-links > :nth-child(n + 3) {
|
||||
display: none;
|
||||
}
|
||||
.header-links > :nth-child(n + 3) ~ .header-links-ellipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 3) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 550px) {
|
||||
.header-links > :nth-child(n + 4) {
|
||||
display: none;
|
||||
}
|
||||
.header-links > :nth-child(n + 4) ~ .header-links-ellipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 4) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 650px) {
|
||||
.header-links > :nth-child(n + 5) {
|
||||
display: none;
|
||||
}
|
||||
.header-links > :nth-child(n + 5) ~ .header-links-ellipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 5) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@container headerlinks (width < 750px) {
|
||||
.header-links > :nth-child(n + 6) {
|
||||
display: none;
|
||||
}
|
||||
.header-links > :nth-child(n + 6) ~ .header-links-ellipsis {
|
||||
display: flex;
|
||||
& div > *:nth-child(n + 6) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
html:has(.zoomModal) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.zoomImg {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.zoomImageActive {
|
||||
view-transition-name: zoom-image;
|
||||
}
|
||||
|
||||
.zoomModal {
|
||||
/** Unstyled */
|
||||
}
|
||||
|
||||
.zoomModal img {
|
||||
view-transition-name: zoom-image;
|
||||
cursor: zoom-out;
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import ReactDOM from 'react-dom';
|
||||
|
||||
import { Icon } from '@gitbook/icons';
|
||||
|
||||
import styles from './ZoomImage.module.css';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
const PADDING = 32; // Padding around the image in the modal, in pixels
|
||||
@@ -159,8 +158,8 @@ export function ZoomImage(
|
||||
}}
|
||||
className={classNames(
|
||||
props.className,
|
||||
zoomable ? styles.zoomImg : null,
|
||||
active ? styles.zoomImageActive : null
|
||||
zoomable ? 'zoom-img' : null,
|
||||
active ? 'zoom-image-active' : null
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
@@ -200,7 +199,7 @@ function ZoomImageModal(props: {
|
||||
<div
|
||||
data-testid="zoom-image-modal"
|
||||
className={classNames(
|
||||
styles.zoomModal,
|
||||
'zoom-modal',
|
||||
tcls(
|
||||
'fixed',
|
||||
'inset-0',
|
||||
|
||||
Reference in New Issue
Block a user