Skip to content

Conversation

viktorrenkema
Copy link
Contributor

Details

This PR updates the styles a bit and adds some more functionality to the toolbar displayed on CR and revision preview links.

Will likely be iterated on design-wise, but that can come later. Also, it is not yet draggable, but that is also the intent for a follow-up PR.

Preview

Change-request preview link:

CleanShot.2025-09-08.at.15.25.22.mp4

Revision preview link:
CleanShot 2025-09-08 at 15 43 51@2x

Dark mode:

CleanShot 2025-09-08 at 15 48 15@2x

Copy link

linear bot commented Sep 8, 2025

Copy link

changeset-bot bot commented Sep 8, 2025

⚠️ No Changeset found

Latest commit: dbaaf35

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

argos-ci bot commented Sep 8, 2025

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
customers-v2 (Inspect) ⚠️ Changes detected (Review) 3 changed Sep 12, 2025, 8:04 AM
v2-cloudflare (Inspect) ⚠️ Changes detected (Review) 5 changed Sep 12, 2025, 8:07 AM
v2-vercel (Inspect) ⚠️ Changes detected (Review) 5 changed Sep 12, 2025, 8:06 AM

@gregberge gregberge requested a review from Copilot September 12, 2025 06:35
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR completely overhauls the admin toolbar displayed on change request (CR) and revision preview links with an enhanced visual design, animations, and improved functionality. The toolbar now features an animated GitBook logo, interactive magnification effects, and better responsive behavior.

  • Replaces the simple static toolbar with an animated, interactive version featuring a prominently displayed GitBook logo
  • Adds comprehensive animation system using framer-motion for smooth transitions, staggered appearances, and hover effects
  • Implements a sophisticated magnification effect for toolbar buttons that scales and repositions them based on mouse proximity

Reviewed Changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
DateRelative.tsx Wraps the time element with a Tooltip component to improve UX
useMagnificationEffect.ts New custom hook implementing Mac-style dock magnification for toolbar buttons
transitions.ts Defines animation configurations and timing for toolbar elements
index.ts Updates exports to include new toolbar components
Toolbar.tsx Complete rewrite of toolbar components with animations and interactive features
RefreshChangeRequestButton.tsx Updates button to use new toolbar design system with motion values
AnimatedLogo.tsx New component for the animated GitBook logo with SVG path animation
AnimatedLogo.module.css CSS animations for the logo's segmented stroke effect
AdminToolbarClient.tsx New client component handling toolbar rendering logic
AdminToolbar.tsx Simplified server component that passes data to client component
package.json Adds motion library dependency for animations

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

const container = containerRef.current;
if (!container) return;

const buttons = Array.from(container.querySelectorAll('#toolbar-button')) as HTMLElement[];
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using ID selectors for querying multiple elements violates HTML standards where IDs should be unique. Consider using a class selector like '.toolbar-button' instead of '#toolbar-button' to avoid potential conflicts and improve maintainability.

Copilot uses AI. Check for mistakes.

Comment on lines +141 to +143
const buttons = Array.from(
container.querySelectorAll('#toolbar-button')
) as HTMLElement[];
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate use of the same problematic ID selector. This query is repeated from line 133, suggesting the need for a consistent approach using class selectors instead.

Copilot uses AI. Check for mistakes.

<motion.a
href={href}
onClick={onClick}
id="toolbar-button"
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the same ID 'toolbar-button' on multiple elements violates HTML standards. IDs must be unique within a document. Use a class name instead, such as 'className="toolbar-button"'.

Copilot uses AI. Check for mistakes.

@keyframes traceFill {
0% {
fill: transparent;
stroke:#46474c;
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded color value #46474c is used multiple times without being defined as a CSS custom property. Consider defining it as a variable like '--trace-stroke-color: #46474c;' for better maintainability.

Copilot uses AI. Check for mistakes.

}
95% {
fill: transparent;
stroke:#46474c;
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded color value #46474c is used multiple times without being defined as a CSS custom property. Consider defining it as a variable like '--trace-stroke-color: #46474c;' for better maintainability.

Copilot uses AI. Check for mistakes.

Copy link
Contributor

@conico974 conico974 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks very good, but the size of the serialized context is an issue IMO.
I've noticed another issue when I use the system theme (in dark mode), it gets confused :
image
It might be a linux thing


if (!changeRequest) {
return null;
return <AdminToolbarClient context={serializableContext} />;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can get very big, we should restrict what gets serialized as much as possible here.
On gitbook it doubles the size of the html and rsc (and we're not even the one with the biggest ones out there)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for flagging! I reduced it to just the fields we need here, lmk if you feel better about that: dbaaf35

function ChangeRequestToolbar(props: AdminToolbarClientProps) {
const { context } = props;
const { space, changeRequest, site } = context;
const reduceMotion = Boolean(useReducedMotion());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need that, it can be automatic? https://motion.dev/docs/react-accessibility#automatic

changeRequestId={changeRequest.id}
revisionId={changeRequest.revision}
updatedAt={new Date(changeRequest.updatedAt).getTime()}
key="refresh-button"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need a key?

<ToolbarButton
title="Comment in app"
href={`${changeRequest.urls.app}~/comments`}
key="comment-button"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need a key?

const [minified, setMinified] = React.useState(true);
const [showToolbarControls, setShowToolbarControls] = React.useState(false);
const [isReady, setIsReady] = React.useState(false);
const reduceMotion = Boolean(useReducedMotion());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same is it really needed with the automatic mode?

Comment on lines +27 to +30
// Small delay to ensure everything is settled
setTimeout(() => {
setIsReady(true);
}, 100);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is arbitrary, what do you mean here? It probably depends of your CPU so you are delaying users with good machine and doing it too early for users with bad machines. Not sure that's a good strategy to wait an arbitrary time like this. Bad smell.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also you need to get the timeout and clear it in the useEffect.

// After toolbar appears, wait then show the full content
React.useEffect(() => {
if (isReady) {
setTimeout(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here you need to clear the timeout in the cleanup func.

}}
>
{/* Logo with stroke segments animation in blue-tints */}
<motion.div key="logo" layout={!reduceMotion}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why key?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants