Skip to content

Commit 4b16b8b

Browse files
committed
Restyle cr/revision preview toolbar
1 parent 193d591 commit 4b16b8b

File tree

12 files changed

+984
-192
lines changed

12 files changed

+984
-192
lines changed

bun.lock

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
"memoizee": "^0.4.17",
138138
"micromark-extension-frontmatter": "^2.0.0",
139139
"micromark-extension-gfm": "^3.0.0",
140+
"motion": "^12.23.12",
140141
"next": "15.3.5",
141142
"next-themes": "^0.2.1",
142143
"nuqs": "^2.2.3",
@@ -2445,6 +2446,12 @@
24452446

24462447
"mnemonist": ["[email protected]", "", { "dependencies": { "obliterator": "^1.6.1" } }, "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw=="],
24472448

2449+
"motion": ["[email protected]", "", { "dependencies": { "framer-motion": "^12.23.12", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-8jCD8uW5GD1csOoqh1WhH1A6j5APHVE15nuBkFeRiMzYBdRwyAHmSP/oXSuW0WJPZRXTFdBoG4hY9TFWNhhwng=="],
2450+
2451+
"motion-dom": ["[email protected]", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw=="],
2452+
2453+
"motion-utils": ["[email protected]", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="],
2454+
24482455
"mri": ["[email protected]", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
24492456

24502457
"ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@@ -4381,6 +4388,10 @@
43814388

43824389
"minizlib/minipass": ["[email protected]", "", { "dependencies": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg=="],
43834390

4391+
"motion/framer-motion": ["[email protected]", "", { "dependencies": { "motion-dom": "^12.23.12", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg=="],
4392+
4393+
"motion/tslib": ["[email protected]", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
4394+
43844395
"next/postcss": ["[email protected]", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
43854396

43864397
"normalize-package-data/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="],

packages/gitbook/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"memoizee": "^0.4.17",
4747
"micromark-extension-frontmatter": "^2.0.0",
4848
"micromark-extension-gfm": "^3.0.0",
49+
"motion": "^12.23.12",
4950
"next": "15.3.5",
5051
"next-themes": "^0.2.1",
5152
"nuqs": "^2.2.3",
Lines changed: 14 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,23 @@
11
import type { GitBookSiteContext } from '@/lib/context';
2-
import { Icon } from '@gitbook/icons';
32
import { headers } from 'next/headers';
4-
import React from 'react';
3+
import { AdminToolbarClient } from './AdminToolbarClient';
54

6-
import { tcls } from '@/lib/tailwind';
7-
8-
import { DateRelative } from '../primitives';
9-
import { RefreshChangeRequestButton } from './RefreshChangeRequestButton';
10-
import { Toolbar, ToolbarBody, ToolbarButton, ToolbarButtonGroups } from './Toolbar';
11-
12-
interface AdminToolbarProps {
5+
export interface AdminToolbarProps {
136
context: GitBookSiteContext;
147
}
158

16-
function ToolbarLayout(props: { children: React.ReactNode }) {
17-
return (
18-
<div
19-
className={tcls(
20-
'fixed',
21-
'bottom-5',
22-
'left-1/2',
23-
'z-50',
24-
'transform',
25-
'-translate-x-1/2',
26-
'rounded-full',
9+
// Serializable version of GitBookSiteContext (excludes functions)
10+
export type SerializableGitBookSiteContext = Omit<
11+
GitBookSiteContext,
12+
'linker' | 'imageResizer' | 'dataFetcher'
13+
>;
2714

28-
'bg-tint-12/9',
29-
'dark:bg-tint-1/9',
30-
31-
'shadow-lg',
32-
'min-h-10',
33-
'min-w-40',
34-
'p-2',
35-
'max-w-md',
36-
'border-tint-12/1',
37-
'backdrop-blur-md'
38-
)}
39-
>
40-
<React.Suspense fallback={null}>{props.children}</React.Suspense>
41-
</div>
42-
);
15+
export interface AdminToolbarClientProps {
16+
context: SerializableGitBookSiteContext;
4317
}
4418

4519
/**
46-
* Toolbar with information for the content admin when previewing a revision or change-request.
20+
* Server component that determines what type of toolbar to show and passes data to client component
4721
*/
4822
export async function AdminToolbar(props: AdminToolbarProps) {
4923
const { context } = props;
@@ -54,89 +28,10 @@ export async function AdminToolbar(props: AdminToolbarProps) {
5428
return null;
5529
}
5630

57-
if (context.changeRequest) {
58-
return <ChangeRequestToolbar context={context} />;
59-
}
60-
61-
if (context.revisionId !== context.space.revision) {
62-
return <RevisionToolbar context={context} />;
63-
}
64-
65-
return null;
66-
}
67-
68-
async function ChangeRequestToolbar(props: { context: GitBookSiteContext }) {
69-
const { context } = props;
70-
const { space, changeRequest } = context;
31+
if (context.changeRequest || context.revisionId !== context.space.revision) {
32+
// Create a serializable version of the context by removing function-containing objects
33+
const { linker, imageResizer, dataFetcher, ...serializableContext } = context;
7134

72-
if (!changeRequest) {
73-
return null;
35+
return <AdminToolbarClient context={serializableContext} />;
7436
}
75-
76-
return (
77-
<ToolbarLayout>
78-
<Toolbar>
79-
<ToolbarButton title="Open in application" href={changeRequest.urls.app}>
80-
<Icon icon="code-branch" className="size-4" />
81-
</ToolbarButton>
82-
<ToolbarBody>
83-
<p>
84-
#{changeRequest.number}: {changeRequest.subject ?? 'No subject'}
85-
</p>
86-
<p className="text-tint-2 text-xs dark:text-tint-11">
87-
Change request updated <DateRelative value={changeRequest.updatedAt} />
88-
</p>
89-
</ToolbarBody>
90-
<ToolbarButtonGroups>
91-
<ToolbarButton title="Open in application" href={changeRequest.urls.app}>
92-
<Icon icon="arrow-up-right-from-square" className="size-4" />
93-
</ToolbarButton>
94-
<RefreshChangeRequestButton
95-
spaceId={space.id}
96-
changeRequestId={changeRequest.id}
97-
revisionId={changeRequest.revision}
98-
updatedAt={new Date(changeRequest.updatedAt).getTime()}
99-
/>
100-
</ToolbarButtonGroups>
101-
</Toolbar>
102-
</ToolbarLayout>
103-
);
104-
}
105-
106-
async function RevisionToolbar(props: { context: GitBookSiteContext }) {
107-
const { context } = props;
108-
const { revision } = context;
109-
110-
return (
111-
<ToolbarLayout>
112-
<Toolbar>
113-
<ToolbarButton title="Open in application" href={revision.urls.app}>
114-
<Icon icon="code-commit" className="size-4" />
115-
</ToolbarButton>
116-
<ToolbarBody>
117-
<p>
118-
Revision created <DateRelative value={revision.createdAt} />
119-
</p>
120-
{revision.git ? (
121-
<p className="text-tint-2 text-xs dark:text-tint-11">
122-
{revision.git.message}
123-
</p>
124-
) : null}
125-
</ToolbarBody>
126-
<ToolbarButtonGroups>
127-
<ToolbarButton title="Open in application" href={revision.urls.app}>
128-
<Icon icon="arrow-up-right-from-square" className="size-4" />
129-
</ToolbarButton>
130-
{revision.git?.url ? (
131-
<ToolbarButton title="Open git commit" href={revision.git.url}>
132-
<Icon
133-
icon={revision.git.url.includes('github.com') ? 'github' : 'gitlab'}
134-
className="size-4"
135-
/>
136-
</ToolbarButton>
137-
) : null}
138-
</ToolbarButtonGroups>
139-
</Toolbar>
140-
</ToolbarLayout>
141-
);
14237
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
'use client';
2+
import { useReducedMotion } from 'framer-motion';
3+
import * as motion from 'motion/react-client';
4+
import { DateRelative } from '../primitives';
5+
import type { AdminToolbarClientProps } from './AdminToolbar';
6+
import { RefreshChangeRequestButton } from './RefreshChangeRequestButton';
7+
import {
8+
Toolbar,
9+
ToolbarBody,
10+
ToolbarButton,
11+
ToolbarButtonGroup,
12+
ToolbarSeparator,
13+
} from './Toolbar';
14+
import { getCopyVariants } from './transitions';
15+
16+
export function AdminToolbarClient(props: AdminToolbarClientProps) {
17+
const { context } = props;
18+
19+
if (context.changeRequest) {
20+
return <ChangeRequestToolbar context={context} />;
21+
}
22+
23+
if (context.revisionId !== context.space.revision) {
24+
return <RevisionToolbar context={context} />;
25+
}
26+
27+
return null;
28+
}
29+
30+
function ChangeRequestToolbar(props: AdminToolbarClientProps) {
31+
const { context } = props;
32+
const { space, changeRequest, site } = context;
33+
const reduceMotion = Boolean(useReducedMotion());
34+
35+
if (!changeRequest) {
36+
return null;
37+
}
38+
39+
const crLabel = changeRequest.subject || 'Untitled change request';
40+
const author = changeRequest.createdBy.displayName;
41+
42+
return (
43+
<Toolbar>
44+
<ToolbarBody>
45+
<div className="flex items-center gap-1 text-xs">
46+
<motion.span
47+
{...(reduceMotion ? undefined : { ...getCopyVariants(0) })}
48+
className="font-light text-neutral-7 dark:text-neutral-3"
49+
>
50+
#{changeRequest.number}
51+
</motion.span>
52+
<motion.span
53+
{...(reduceMotion ? undefined : { ...getCopyVariants(1) })}
54+
className="max-w-[24ch] truncate font-semibold text-neutral-3 dark:text-neutral-2"
55+
>
56+
{crLabel}
57+
</motion.span>
58+
</div>
59+
<motion.span
60+
{...(reduceMotion ? undefined : { ...getCopyVariants(2) })}
61+
className="text-[10px] text-neutral-7 text-xs dark:text-neutral-2"
62+
>
63+
<DateRelative value={changeRequest.updatedAt} /> by {author}
64+
</motion.span>
65+
</ToolbarBody>
66+
67+
<ToolbarSeparator />
68+
69+
<ToolbarButtonGroup>
70+
{/* Refresh to retrieve latest changes */}
71+
<RefreshChangeRequestButton
72+
spaceId={space.id}
73+
changeRequestId={changeRequest.id}
74+
revisionId={changeRequest.revision}
75+
updatedAt={new Date(changeRequest.updatedAt).getTime()}
76+
key="refresh-button"
77+
/>
78+
{/* Comment in app */}
79+
<ToolbarButton
80+
title="Comment in app"
81+
href={`${changeRequest.urls.app}~/comments`}
82+
key="comment-button"
83+
icon="comment"
84+
/>
85+
86+
{/* Open production site */}
87+
<ToolbarButton
88+
title="Open production site"
89+
href={`${site.urls.published}`}
90+
key="open-production-site-button"
91+
icon="globe"
92+
/>
93+
94+
{/* Open CR in GitBook */}
95+
<ToolbarButton
96+
title="View CR in GitBook"
97+
href={`${changeRequest.urls.app}`}
98+
key="view-change-request-button"
99+
icon="code-branch"
100+
/>
101+
</ToolbarButtonGroup>
102+
</Toolbar>
103+
);
104+
}
105+
106+
function RevisionToolbar(props: AdminToolbarClientProps) {
107+
const { context } = props;
108+
const { revision, site } = context;
109+
const reduceMotion = Boolean(useReducedMotion());
110+
111+
if (!revision) {
112+
return null;
113+
}
114+
115+
const gitURL = revision.git?.url;
116+
117+
return (
118+
<Toolbar>
119+
<ToolbarBody>
120+
<div className="flex items-center gap-1 text-xs">
121+
<motion.span
122+
{...(reduceMotion ? undefined : { ...getCopyVariants(0) })}
123+
className="font-light text-neutral-7 dark:text-neutral-3"
124+
>
125+
Site revision
126+
</motion.span>
127+
<motion.span
128+
{...(reduceMotion ? undefined : { ...getCopyVariants(1) })}
129+
className="max-w-[24ch] truncate font-semibold text-neutral-3 dark:text-neutral-2"
130+
>
131+
{context.site.title}
132+
</motion.span>
133+
</div>
134+
<motion.span
135+
{...(reduceMotion ? undefined : { ...getCopyVariants(2) })}
136+
className="text-[10px] text-neutral-7 text-xs dark:text-neutral-2"
137+
>
138+
Created <DateRelative value={revision.createdAt} />
139+
</motion.span>
140+
</ToolbarBody>
141+
<ToolbarButtonGroup>
142+
{/* Open commit in Git client */}
143+
<ToolbarButton
144+
title={gitURL ? 'Open commit in Git client' : 'Setup GitSync to edit using git'}
145+
href={gitURL}
146+
disabled={!gitURL}
147+
icon={gitURL ? (gitURL.includes('github.com') ? 'github' : 'gitlab') : 'github'}
148+
/>
149+
150+
{/* Open production site */}
151+
<ToolbarButton
152+
title="Open production site"
153+
href={`${site.urls.published}`}
154+
key="open-production-site-button"
155+
icon="globe"
156+
/>
157+
158+
{/* Open revision in GitBook */}
159+
<ToolbarButton
160+
title="View revision in GitBook"
161+
href={revision.urls.app}
162+
icon="code-commit"
163+
/>
164+
</ToolbarButtonGroup>
165+
</Toolbar>
166+
);
167+
}

0 commit comments

Comments
 (0)