From be68b4d63c0a85da726bb5f367f612d1a71fa8d9 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Thu, 11 Sep 2025 17:18:11 +0200 Subject: [PATCH 01/11] simple navigation indicator --- .../gitbook/src/components/Header/Header.tsx | 2 + .../SpaceLayout/NavigationLoader.tsx | 37 +++++++++++ .../gitbook/src/components/hooks/useHash.tsx | 61 +++++++++++++++++-- packages/gitbook/tailwind.config.ts | 5 ++ 4 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx diff --git a/packages/gitbook/src/components/Header/Header.tsx b/packages/gitbook/src/components/Header/Header.tsx index b952f5d598..3d99dd78d6 100644 --- a/packages/gitbook/src/components/Header/Header.tsx +++ b/packages/gitbook/src/components/Header/Header.tsx @@ -5,6 +5,7 @@ import { getSpaceLanguage, t } from '@/intl/server'; import { tcls } from '@/lib/tailwind'; import { SearchContainer } from '../Search'; import { SiteSectionTabs, encodeClientSiteSections } from '../SiteSections'; +import { NavigationLoader } from '../SpaceLayout/NavigationLoader'; import { HeaderLink } from './HeaderLink'; import { HeaderLinkMore } from './HeaderLinkMore'; import { HeaderLinks } from './HeaderLinks'; @@ -196,6 +197,7 @@ export function Header(props: { ) : null} + ); } diff --git a/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx b/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx new file mode 100644 index 0000000000..dd30782b35 --- /dev/null +++ b/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx @@ -0,0 +1,37 @@ +'use client'; + +import { tcls } from '@/lib/tailwind'; +import { useIsNavigating } from '../hooks'; + +export function NavigationLoader() { + const isNavigating = useIsNavigating(); + if (!isNavigating) { + return null; + } + return ( +
+ +
+ ); +} diff --git a/packages/gitbook/src/components/hooks/useHash.tsx b/packages/gitbook/src/components/hooks/useHash.tsx index efcb8249a2..bdbd25dddd 100644 --- a/packages/gitbook/src/components/hooks/useHash.tsx +++ b/packages/gitbook/src/components/hooks/useHash.tsx @@ -1,4 +1,5 @@ 'use client'; +import { usePathname } from 'next/navigation'; import React from 'react'; export const HashContext = React.createContext<{ @@ -9,9 +10,17 @@ export const HashContext = React.createContext<{ * URL can be relative or absolute. */ updateHashFromUrl: (href: string) => void; + /** + * Indicates if a link has been clicked recently. + * Becomes true after a click and resets to false when pathname changes. + * It is debounced to avoid flickering on fast navigations. + * Debounce time is 400ms (= doherty threshold for responsiveness). + */ + isNavigating: boolean; }>({ hash: null, updateHashFromUrl: () => {}, + isNavigating: false, }); function getHash(): string | null { @@ -21,32 +30,74 @@ function getHash(): string | null { return window.location.hash.slice(1); } -export const HashProvider: React.FC> = ({ children }) => { +export const HashProvider: React.FC = ({ children }) => { const [hash, setHash] = React.useState(getHash); + const [isNavigating, setIsNavigating] = React.useState(false); + const timeoutRef = React.useRef(null); + const pathname = usePathname(); + const pathnameRef = React.useRef(pathname); + + // Reset isNavigating when pathname changes + React.useEffect(() => { + if (pathnameRef.current !== pathname) { + setIsNavigating(false); + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + pathnameRef.current = pathname; + } + }, [pathname]); + + // Cleanup timeout on unmount + React.useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + const updateHashFromUrl = React.useCallback((href: string) => { const url = new URL( href, typeof window !== 'undefined' ? window.location.origin : 'http://localhost' ); setHash(url.hash.slice(1)); + + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + if (pathnameRef.current !== url.pathname) { + timeoutRef.current = window.setTimeout(() => { + setIsNavigating(true); + timeoutRef.current = null; + return; + }, 400); // 400ms timeout - doherty threshold for responsiveness + } }, []); + const memoizedValue = React.useMemo( - () => ({ hash, updateHashFromUrl }), - [hash, updateHashFromUrl] + () => ({ hash, updateHashFromUrl, isNavigating }), + [hash, updateHashFromUrl, isNavigating] ); return {children}; }; /** - * Hook to get the current hash from the URL. + * Hook to get the current hash from the URL and click state. * @see https://github.com/vercel/next.js/discussions/49465 * We use a different hack than this one, because for same page link it don't work * We can't use the `hashChange` event because it doesn't fire for `replaceState` and `pushState` which are used by Next.js. * Since we have a single Link component that handles all links, we can use a context to share the hash. */ export function useHash() { - // const params = useParams(); const { hash } = React.useContext(HashContext); return hash; } + +export function useIsNavigating() { + const { isNavigating: hasBeenClicked } = React.useContext(HashContext); + return hasBeenClicked; +} diff --git a/packages/gitbook/tailwind.config.ts b/packages/gitbook/tailwind.config.ts index 87d42fdeaf..a66424f623 100644 --- a/packages/gitbook/tailwind.config.ts +++ b/packages/gitbook/tailwind.config.ts @@ -329,6 +329,7 @@ const config: Config = { exitToRight: 'exitToRight 250ms cubic-bezier(0.83, 0, 0.17, 1) both', heightIn: 'heightIn 200ms ease both', + progressLoader: 'progressLoader 1.5s ease-in-out infinite alternate', }, keyframes: { bounceSmall: { @@ -498,6 +499,10 @@ const config: Config = { from: { height: '0' }, to: { height: 'max-content' }, }, + progressLoader: { + from: { transform: 'translateX(-1%)', left: 0 }, + to: { transform: 'translateX(-99%)', left: '100%' }, + }, }, boxShadow: { thinbottom: '0px 1px 0px rgba(0, 0, 0, 0.05)', From 3fb7451af8349295fa33ab487446e29188e0b324 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Thu, 11 Sep 2025 18:09:04 +0200 Subject: [PATCH 02/11] renaming --- .../RootLayout/RootLayoutClientContexts.tsx | 6 ++--- .../gitbook/src/components/hooks/useHash.tsx | 25 +++++++++++-------- .../src/components/primitives/Link.tsx | 6 ++--- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/gitbook/src/components/RootLayout/RootLayoutClientContexts.tsx b/packages/gitbook/src/components/RootLayout/RootLayoutClientContexts.tsx index 68a95dde55..1ac89b8176 100644 --- a/packages/gitbook/src/components/RootLayout/RootLayoutClientContexts.tsx +++ b/packages/gitbook/src/components/RootLayout/RootLayoutClientContexts.tsx @@ -5,7 +5,7 @@ import type React from 'react'; import { TranslateContext } from '@/intl/client'; import type { TranslationLanguage } from '@/intl/translations'; import { TooltipProvider } from '@radix-ui/react-tooltip'; -import { HashProvider } from '../hooks'; +import { NavigationStatusProvider } from '../hooks'; import { LoadingStateProvider } from '../primitives/LoadingStateProvider'; /** @@ -20,9 +20,9 @@ export function RootLayoutClientContexts(props: { return ( - + {children} - + ); diff --git a/packages/gitbook/src/components/hooks/useHash.tsx b/packages/gitbook/src/components/hooks/useHash.tsx index bdbd25dddd..c33b5dd6e9 100644 --- a/packages/gitbook/src/components/hooks/useHash.tsx +++ b/packages/gitbook/src/components/hooks/useHash.tsx @@ -1,15 +1,16 @@ 'use client'; + import { usePathname } from 'next/navigation'; import React from 'react'; -export const HashContext = React.createContext<{ +export const NavigationStatusContext = React.createContext<{ hash: string | null; /** * Updates the hash value from the URL provided here. * It will then be used by the `useHash` hook. * URL can be relative or absolute. */ - updateHashFromUrl: (href: string) => void; + onNavigationClick: (href: string) => void; /** * Indicates if a link has been clicked recently. * Becomes true after a click and resets to false when pathname changes. @@ -19,7 +20,7 @@ export const HashContext = React.createContext<{ isNavigating: boolean; }>({ hash: null, - updateHashFromUrl: () => {}, + onNavigationClick: () => {}, isNavigating: false, }); @@ -30,7 +31,7 @@ function getHash(): string | null { return window.location.hash.slice(1); } -export const HashProvider: React.FC = ({ children }) => { +export const NavigationStatusProvider: React.FC = ({ children }) => { const [hash, setHash] = React.useState(getHash); const [isNavigating, setIsNavigating] = React.useState(false); const timeoutRef = React.useRef(null); @@ -58,7 +59,7 @@ export const HashProvider: React.FC = ({ children }) => }; }, []); - const updateHashFromUrl = React.useCallback((href: string) => { + const onNavigationClick = React.useCallback((href: string) => { const url = new URL( href, typeof window !== 'undefined' ? window.location.origin : 'http://localhost' @@ -78,10 +79,14 @@ export const HashProvider: React.FC = ({ children }) => }, []); const memoizedValue = React.useMemo( - () => ({ hash, updateHashFromUrl, isNavigating }), - [hash, updateHashFromUrl, isNavigating] + () => ({ hash, onNavigationClick, isNavigating }), + [hash, onNavigationClick, isNavigating] + ); + return ( + + {children} + ); - return {children}; }; /** @@ -92,12 +97,12 @@ export const HashProvider: React.FC = ({ children }) => * Since we have a single Link component that handles all links, we can use a context to share the hash. */ export function useHash() { - const { hash } = React.useContext(HashContext); + const { hash } = React.useContext(NavigationStatusContext); return hash; } export function useIsNavigating() { - const { isNavigating: hasBeenClicked } = React.useContext(HashContext); + const { isNavigating: hasBeenClicked } = React.useContext(NavigationStatusContext); return hasBeenClicked; } diff --git a/packages/gitbook/src/components/primitives/Link.tsx b/packages/gitbook/src/components/primitives/Link.tsx index c09180173b..722b7fdb80 100644 --- a/packages/gitbook/src/components/primitives/Link.tsx +++ b/packages/gitbook/src/components/primitives/Link.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { tcls } from '@/lib/tailwind'; import { SiteExternalLinksTarget } from '@gitbook/api'; import { type TrackEventInput, useTrackEvent } from '../Insights'; -import { HashContext } from '../hooks'; +import { NavigationStatusContext } from '../hooks'; import { isExternalLink } from '../utils/link'; import { type DesignTokenName, useClassnames } from './StyleProvider'; @@ -72,7 +72,7 @@ export const Link = React.forwardRef(function Link( ) { const { href, prefetch, children, insights, classNames, className, ...domProps } = props; const { externalLinksTarget } = React.useContext(LinkSettingsContext); - const { updateHashFromUrl } = React.useContext(HashContext); + const { onNavigationClick } = React.useContext(NavigationStatusContext); const trackEvent = useTrackEvent(); const forwardedClassNames = useClassnames(classNames || []); const isExternal = isExternalLink(href); @@ -81,7 +81,7 @@ export const Link = React.forwardRef(function Link( const onClick = (event: React.MouseEvent) => { const isExternalWithOrigin = isExternalLink(href, window.location.origin); if (!isExternal) { - updateHashFromUrl(href); + onNavigationClick(href); } if (insights) { From a49ed879a482164eaffc74570a402e2b5df423c1 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Thu, 11 Sep 2025 18:58:49 +0200 Subject: [PATCH 03/11] update comment --- packages/gitbook/src/components/hooks/useHash.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/gitbook/src/components/hooks/useHash.tsx b/packages/gitbook/src/components/hooks/useHash.tsx index c33b5dd6e9..1c3957ab2e 100644 --- a/packages/gitbook/src/components/hooks/useHash.tsx +++ b/packages/gitbook/src/components/hooks/useHash.tsx @@ -6,8 +6,7 @@ import React from 'react'; export const NavigationStatusContext = React.createContext<{ hash: string | null; /** - * Updates the hash value from the URL provided here. - * It will then be used by the `useHash` hook. + * Updates the navigation state from the URL provided here. * URL can be relative or absolute. */ onNavigationClick: (href: string) => void; @@ -90,7 +89,7 @@ export const NavigationStatusProvider: React.FC = ({ ch }; /** - * Hook to get the current hash from the URL and click state. + * Hook to get the current hash from the URL. * @see https://github.com/vercel/next.js/discussions/49465 * We use a different hack than this one, because for same page link it don't work * We can't use the `hashChange` event because it doesn't fire for `replaceState` and `pushState` which are used by Next.js. @@ -102,6 +101,10 @@ export function useHash() { return hash; } +/** + * Hook to get the current navigation state. + * @returns True if a navigation has been triggered recently. False otherwise, it also resets to false when the navigation is complete. + */ export function useIsNavigating() { const { isNavigating: hasBeenClicked } = React.useContext(NavigationStatusContext); return hasBeenClicked; From 000634516f5cd1c679a40dbd6ff4fbd50950a2aa Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Thu, 18 Sep 2025 15:04:42 +0200 Subject: [PATCH 04/11] Try another navigation loader --- .../gitbook/src/components/Header/Header.tsx | 2 - .../src/components/Search/SearchInput.tsx | 5 +- .../SpaceLayout/NavigationLoader.tsx | 55 +++++++++++-------- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/packages/gitbook/src/components/Header/Header.tsx b/packages/gitbook/src/components/Header/Header.tsx index 3d99dd78d6..b952f5d598 100644 --- a/packages/gitbook/src/components/Header/Header.tsx +++ b/packages/gitbook/src/components/Header/Header.tsx @@ -5,7 +5,6 @@ import { getSpaceLanguage, t } from '@/intl/server'; import { tcls } from '@/lib/tailwind'; import { SearchContainer } from '../Search'; import { SiteSectionTabs, encodeClientSiteSections } from '../SiteSections'; -import { NavigationLoader } from '../SpaceLayout/NavigationLoader'; import { HeaderLink } from './HeaderLink'; import { HeaderLinkMore } from './HeaderLinkMore'; import { HeaderLinks } from './HeaderLinks'; @@ -197,7 +196,6 @@ export function Header(props: { ) : null} - ); } diff --git a/packages/gitbook/src/components/Search/SearchInput.tsx b/packages/gitbook/src/components/Search/SearchInput.tsx index 8f25c6fa2a..3cfcea7222 100644 --- a/packages/gitbook/src/components/Search/SearchInput.tsx +++ b/packages/gitbook/src/components/Search/SearchInput.tsx @@ -7,6 +7,7 @@ import { tcls } from '@/lib/tailwind'; import { Icon } from '@gitbook/icons'; import { Button, variantClasses } from '../primitives'; import { useClassnames } from '../primitives/StyleProvider'; +import { NavigationLoader } from '../SpaceLayout/NavigationLoader'; interface SearchInputProps { onChange: (value: string) => void; @@ -46,7 +47,7 @@ export const SearchInput = React.forwardRef( }, [isOpen, value]); return ( -
+ {/* biome-ignore lint/a11y/useKeyWithClickEvents: this div needs an onClick to show the input on mobile, where it's normally hidden. Normally you'd also need to add a keyboard trigger to do the same without a pointer, but in this case the input already be focused on its own. */}
( /> {!isOpen ? : null}
-
+
); } ); diff --git a/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx b/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx index dd30782b35..c13915df15 100644 --- a/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx +++ b/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx @@ -3,35 +3,42 @@ import { tcls } from '@/lib/tailwind'; import { useIsNavigating } from '../hooks'; -export function NavigationLoader() { +interface NavigationLoaderProps { + children: React.ReactNode; + className?: string; +} + +export function NavigationLoader({ children, className }: NavigationLoaderProps) { const isNavigating = useIsNavigating(); - if (!isNavigating) { - return null; - } + return ( -
- + {/* Children content */} + {children} + + {/* Animated loading overlay */} + {isNavigating && ( +
+ > + {/* Animated circular gradient background */} +
+
)}
); } From 3f9ec66b9542f2dd31fa6c1ef36769c2a32edfe8 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Mon, 22 Sep 2025 00:18:30 +0200 Subject: [PATCH 05/11] use outline animation instead --- .../src/components/Search/SearchInput.tsx | 8 +-- .../SpaceLayout/NavigationLoader.tsx | 54 ++++++++----------- packages/gitbook/tailwind.config.ts | 21 ++++++++ 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/packages/gitbook/src/components/Search/SearchInput.tsx b/packages/gitbook/src/components/Search/SearchInput.tsx index 3cfcea7222..9682182ce4 100644 --- a/packages/gitbook/src/components/Search/SearchInput.tsx +++ b/packages/gitbook/src/components/Search/SearchInput.tsx @@ -47,10 +47,10 @@ export const SearchInput = React.forwardRef( }, [isOpen, value]); return ( - +
{/* biome-ignore lint/a11y/useKeyWithClickEvents: this div needs an onClick to show the input on mobile, where it's normally hidden. Normally you'd also need to add a keyboard trigger to do the same without a pointer, but in this case the input already be focused on its own. */} -
( ref={inputRef} /> {!isOpen ? : null} -
- + +
); } ); diff --git a/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx b/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx index c13915df15..ce06bdeaba 100644 --- a/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx +++ b/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx @@ -1,44 +1,32 @@ 'use client'; +import React from 'react'; import { tcls } from '@/lib/tailwind'; import { useIsNavigating } from '../hooks'; -interface NavigationLoaderProps { +interface NavigationLoaderProps extends React.HTMLAttributes { children: React.ReactNode; - className?: string; } -export function NavigationLoader({ children, className }: NavigationLoaderProps) { - const isNavigating = useIsNavigating(); - - return ( -
- {/* Children content */} - {children} - - {/* Animated loading overlay */} - {isNavigating && ( -
( + ({ children, className, ...props }, ref) => { + const isNavigating = useIsNavigating(); + + return ( +
- {/* Animated circular gradient background */} -
-
)} -
- ); -} + {/* Children content */} + {children} +
+ ); + } +); + +NavigationLoader.displayName = 'NavigationLoader'; diff --git a/packages/gitbook/tailwind.config.ts b/packages/gitbook/tailwind.config.ts index a66424f623..fcb9c26ef1 100644 --- a/packages/gitbook/tailwind.config.ts +++ b/packages/gitbook/tailwind.config.ts @@ -330,6 +330,7 @@ const config: Config = { heightIn: 'heightIn 200ms ease both', progressLoader: 'progressLoader 1.5s ease-in-out infinite alternate', + animateOutline: 'animateOutline 1s ease-in-out infinite', }, keyframes: { bounceSmall: { @@ -503,6 +504,26 @@ const config: Config = { from: { transform: 'translateX(-1%)', left: 0 }, to: { transform: 'translateX(-99%)', left: '100%' }, }, + animateOutline: { + '0%': { + outlineWidth: '1px', + outlineOffset: '0', + outlineColor: 'rgb(var(--primary-original) / 0)', + }, + '10%': { + outlineColor: 'rgb(var(--primary-original) / 0.75)', + }, + '50%': { + outlineWidth: '7px', + outlineOffset: '4px', + outlineColor: 'rgb(var(--primary-original) / 0)', + }, + '100%': { + outlineWidth: '7px', + outlineOffset: '4px', + outlineColor: 'rgb(var(--primary-original) / 0)', + }, + }, }, boxShadow: { thinbottom: '0px 1px 0px rgba(0, 0, 0, 0.05)', From 1bbbd9167cbe1df2758400d8d12e5c75149a2e34 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Mon, 22 Sep 2025 09:53:00 +0200 Subject: [PATCH 06/11] refactor --- .../src/components/SpaceLayout/NavigationLoader.tsx | 2 +- packages/gitbook/tailwind.config.ts | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx b/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx index ce06bdeaba..885d3953d1 100644 --- a/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx +++ b/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx @@ -18,7 +18,7 @@ export const NavigationLoader = React.forwardRef diff --git a/packages/gitbook/tailwind.config.ts b/packages/gitbook/tailwind.config.ts index fcb9c26ef1..ad4ed5fa12 100644 --- a/packages/gitbook/tailwind.config.ts +++ b/packages/gitbook/tailwind.config.ts @@ -329,8 +329,7 @@ const config: Config = { exitToRight: 'exitToRight 250ms cubic-bezier(0.83, 0, 0.17, 1) both', heightIn: 'heightIn 200ms ease both', - progressLoader: 'progressLoader 1.5s ease-in-out infinite alternate', - animateOutline: 'animateOutline 1s ease-in-out infinite', + outline: 'animateOutline 1s ease-in-out infinite', }, keyframes: { bounceSmall: { @@ -500,10 +499,6 @@ const config: Config = { from: { height: '0' }, to: { height: 'max-content' }, }, - progressLoader: { - from: { transform: 'translateX(-1%)', left: 0 }, - to: { transform: 'translateX(-99%)', left: '100%' }, - }, animateOutline: { '0%': { outlineWidth: '1px', From bd327ced4b7af199a214c304b581d72e47e92e6d Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Mon, 22 Sep 2025 09:59:39 +0200 Subject: [PATCH 07/11] rename --- packages/gitbook/src/components/Search/SearchInput.tsx | 6 +++--- .../NavigationOutlineIndicator.tsx} | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) rename packages/gitbook/src/components/{SpaceLayout/NavigationLoader.tsx => primitives/NavigationOutlineIndicator.tsx} (71%) diff --git a/packages/gitbook/src/components/Search/SearchInput.tsx b/packages/gitbook/src/components/Search/SearchInput.tsx index 9682182ce4..22b8e044c4 100644 --- a/packages/gitbook/src/components/Search/SearchInput.tsx +++ b/packages/gitbook/src/components/Search/SearchInput.tsx @@ -7,7 +7,7 @@ import { tcls } from '@/lib/tailwind'; import { Icon } from '@gitbook/icons'; import { Button, variantClasses } from '../primitives'; import { useClassnames } from '../primitives/StyleProvider'; -import { NavigationLoader } from '../SpaceLayout/NavigationLoader'; +import { NavigationOutlineInndicator } from '../primitives/NavigationOutlineIndicator'; interface SearchInputProps { onChange: (value: string) => void; @@ -50,7 +50,7 @@ export const SearchInput = React.forwardRef(
{/* biome-ignore lint/a11y/useKeyWithClickEvents: this div needs an onClick to show the input on mobile, where it's normally hidden. Normally you'd also need to add a keyboard trigger to do the same without a pointer, but in this case the input already be focused on its own. */} - ( ref={inputRef} /> {!isOpen ? : null} - +
); } diff --git a/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx b/packages/gitbook/src/components/primitives/NavigationOutlineIndicator.tsx similarity index 71% rename from packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx rename to packages/gitbook/src/components/primitives/NavigationOutlineIndicator.tsx index 885d3953d1..f1f57becb6 100644 --- a/packages/gitbook/src/components/SpaceLayout/NavigationLoader.tsx +++ b/packages/gitbook/src/components/primitives/NavigationOutlineIndicator.tsx @@ -4,11 +4,11 @@ import React from 'react'; import { tcls } from '@/lib/tailwind'; import { useIsNavigating } from '../hooks'; -interface NavigationLoaderProps extends React.HTMLAttributes { +interface NavigationOutlineIndicatorProps extends React.HTMLAttributes { children: React.ReactNode; } -export const NavigationLoader = React.forwardRef( +export const NavigationOutlineInndicator = React.forwardRef( ({ children, className, ...props }, ref) => { const isNavigating = useIsNavigating(); @@ -29,4 +29,4 @@ export const NavigationLoader = React.forwardRef Date: Mon, 22 Sep 2025 09:59:53 +0200 Subject: [PATCH 08/11] linting --- .../src/components/Search/SearchInput.tsx | 2 +- .../primitives/NavigationOutlineIndicator.tsx | 39 +++++++++---------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/packages/gitbook/src/components/Search/SearchInput.tsx b/packages/gitbook/src/components/Search/SearchInput.tsx index 22b8e044c4..5ad9494090 100644 --- a/packages/gitbook/src/components/Search/SearchInput.tsx +++ b/packages/gitbook/src/components/Search/SearchInput.tsx @@ -6,8 +6,8 @@ import { tString, useLanguage } from '@/intl/client'; import { tcls } from '@/lib/tailwind'; import { Icon } from '@gitbook/icons'; import { Button, variantClasses } from '../primitives'; -import { useClassnames } from '../primitives/StyleProvider'; import { NavigationOutlineInndicator } from '../primitives/NavigationOutlineIndicator'; +import { useClassnames } from '../primitives/StyleProvider'; interface SearchInputProps { onChange: (value: string) => void; diff --git a/packages/gitbook/src/components/primitives/NavigationOutlineIndicator.tsx b/packages/gitbook/src/components/primitives/NavigationOutlineIndicator.tsx index f1f57becb6..af4e8e03b0 100644 --- a/packages/gitbook/src/components/primitives/NavigationOutlineIndicator.tsx +++ b/packages/gitbook/src/components/primitives/NavigationOutlineIndicator.tsx @@ -1,32 +1,29 @@ 'use client'; -import React from 'react'; import { tcls } from '@/lib/tailwind'; +import React from 'react'; import { useIsNavigating } from '../hooks'; interface NavigationOutlineIndicatorProps extends React.HTMLAttributes { children: React.ReactNode; } -export const NavigationOutlineInndicator = React.forwardRef( - ({ children, className, ...props }, ref) => { - const isNavigating = useIsNavigating(); - - return ( -
- {/* Children content */} - {children} -
- ); - } -); +export const NavigationOutlineInndicator = React.forwardRef< + HTMLDivElement, + NavigationOutlineIndicatorProps +>(({ children, className, ...props }, ref) => { + const isNavigating = useIsNavigating(); + + return ( +
+ {/* Children content */} + {children} +
+ ); +}); NavigationOutlineInndicator.displayName = 'NavigationLoader'; From abec90e82a96e9aeef5c4f05f3df79b070fd0f0c Mon Sep 17 00:00:00 2001 From: Zeno Kapitein Date: Tue, 23 Sep 2025 12:42:05 +0200 Subject: [PATCH 09/11] Change to animated line at the top --- .../src/components/Search/SearchInput.tsx | 5 ++-- .../components/SpaceLayout/SpaceLayout.tsx | 2 ++ .../primitives/NavigationLoader.tsx | 18 ++++++++++++ .../primitives/NavigationOutlineIndicator.tsx | 29 ------------------- packages/gitbook/tailwind.config.ts | 28 +++++++++--------- 5 files changed, 35 insertions(+), 47 deletions(-) create mode 100644 packages/gitbook/src/components/primitives/NavigationLoader.tsx delete mode 100644 packages/gitbook/src/components/primitives/NavigationOutlineIndicator.tsx diff --git a/packages/gitbook/src/components/Search/SearchInput.tsx b/packages/gitbook/src/components/Search/SearchInput.tsx index 5ad9494090..8f25c6fa2a 100644 --- a/packages/gitbook/src/components/Search/SearchInput.tsx +++ b/packages/gitbook/src/components/Search/SearchInput.tsx @@ -6,7 +6,6 @@ import { tString, useLanguage } from '@/intl/client'; import { tcls } from '@/lib/tailwind'; import { Icon } from '@gitbook/icons'; import { Button, variantClasses } from '../primitives'; -import { NavigationOutlineInndicator } from '../primitives/NavigationOutlineIndicator'; import { useClassnames } from '../primitives/StyleProvider'; interface SearchInputProps { @@ -50,7 +49,7 @@ export const SearchInput = React.forwardRef(
{/* biome-ignore lint/a11y/useKeyWithClickEvents: this div needs an onClick to show the input on mobile, where it's normally hidden. Normally you'd also need to add a keyboard trigger to do the same without a pointer, but in this case the input already be focused on its own. */} - ( ref={inputRef} /> {!isOpen ? : null} - +
); } diff --git a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx index a9be339da8..f1376f1a7b 100644 --- a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx +++ b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx @@ -25,6 +25,7 @@ import { InsightsProvider, VisitorSessionProvider } from '../Insights'; import { SearchContainer } from '../Search'; import { SiteSectionList, encodeClientSiteSections } from '../SiteSections'; import { CurrentContentProvider } from '../hooks'; +import { NavigationLoader } from '../primitives/NavigationLoader'; import { SpaceLayoutContextProvider } from './SpaceLayoutContext'; type SpaceLayoutProps = { @@ -125,6 +126,7 @@ export function SpaceLayout(props: SpaceLayoutProps) {
+ {customization.ai?.mode === CustomizationAIMode.Assistant ? ( ) : null} diff --git a/packages/gitbook/src/components/primitives/NavigationLoader.tsx b/packages/gitbook/src/components/primitives/NavigationLoader.tsx new file mode 100644 index 0000000000..f6f5920c46 --- /dev/null +++ b/packages/gitbook/src/components/primitives/NavigationLoader.tsx @@ -0,0 +1,18 @@ +'use client'; +import { tcls } from '@/lib/tailwind'; +import { useIsNavigating } from '../hooks'; + +export const NavigationLoader = () => { + const isNavigating = useIsNavigating(); + + return ( +
+
+
+ ); +}; diff --git a/packages/gitbook/src/components/primitives/NavigationOutlineIndicator.tsx b/packages/gitbook/src/components/primitives/NavigationOutlineIndicator.tsx deleted file mode 100644 index af4e8e03b0..0000000000 --- a/packages/gitbook/src/components/primitives/NavigationOutlineIndicator.tsx +++ /dev/null @@ -1,29 +0,0 @@ -'use client'; - -import { tcls } from '@/lib/tailwind'; -import React from 'react'; -import { useIsNavigating } from '../hooks'; - -interface NavigationOutlineIndicatorProps extends React.HTMLAttributes { - children: React.ReactNode; -} - -export const NavigationOutlineInndicator = React.forwardRef< - HTMLDivElement, - NavigationOutlineIndicatorProps ->(({ children, className, ...props }, ref) => { - const isNavigating = useIsNavigating(); - - return ( -
- {/* Children content */} - {children} -
- ); -}); - -NavigationOutlineInndicator.displayName = 'NavigationLoader'; diff --git a/packages/gitbook/tailwind.config.ts b/packages/gitbook/tailwind.config.ts index ad4ed5fa12..c8ee64c663 100644 --- a/packages/gitbook/tailwind.config.ts +++ b/packages/gitbook/tailwind.config.ts @@ -329,7 +329,7 @@ const config: Config = { exitToRight: 'exitToRight 250ms cubic-bezier(0.83, 0, 0.17, 1) both', heightIn: 'heightIn 200ms ease both', - outline: 'animateOutline 1s ease-in-out infinite', + crawl: 'crawl 2s ease-in-out infinite', }, keyframes: { bounceSmall: { @@ -499,24 +499,22 @@ const config: Config = { from: { height: '0' }, to: { height: 'max-content' }, }, - animateOutline: { + crawl: { '0%': { - outlineWidth: '1px', - outlineOffset: '0', - outlineColor: 'rgb(var(--primary-original) / 0)', + scale: '0 1', + translate: '0 0', }, - '10%': { - outlineColor: 'rgb(var(--primary-original) / 0.75)', - }, - '50%': { - outlineWidth: '7px', - outlineOffset: '4px', - outlineColor: 'rgb(var(--primary-original) / 0)', + '40%': { + scale: '1 1', + translate: '100% 0', }, + // '60%': { + // scale: '1 1', + // translate: '0 0', + // }, '100%': { - outlineWidth: '7px', - outlineOffset: '4px', - outlineColor: 'rgb(var(--primary-original) / 0)', + scale: '0 1', + translate: '100% 0', }, }, }, From e714a564014ed04d5be60e330e448f8000a73e8d Mon Sep 17 00:00:00 2001 From: Zeno Kapitein Date: Tue, 23 Sep 2025 12:52:29 +0200 Subject: [PATCH 10/11] Update NavigationLoader.tsx --- .../gitbook/src/components/primitives/NavigationLoader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gitbook/src/components/primitives/NavigationLoader.tsx b/packages/gitbook/src/components/primitives/NavigationLoader.tsx index f6f5920c46..eca15e2fda 100644 --- a/packages/gitbook/src/components/primitives/NavigationLoader.tsx +++ b/packages/gitbook/src/components/primitives/NavigationLoader.tsx @@ -8,8 +8,8 @@ export const NavigationLoader = () => { return (
From 05734836a5829757a041535dce4b62f601fd24dc Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Tue, 23 Sep 2025 14:47:44 +0200 Subject: [PATCH 11/11] delete commented part --- packages/gitbook/tailwind.config.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/gitbook/tailwind.config.ts b/packages/gitbook/tailwind.config.ts index c8ee64c663..c6b075ef20 100644 --- a/packages/gitbook/tailwind.config.ts +++ b/packages/gitbook/tailwind.config.ts @@ -508,10 +508,6 @@ const config: Config = { scale: '1 1', translate: '100% 0', }, - // '60%': { - // scale: '1 1', - // translate: '0 0', - // }, '100%': { scale: '0 1', translate: '100% 0',