Fix scroll to hash and update next to 14.1.3 (#286)

* Update next to 14.1.3

* Scroll to hash when url changes

* Simplify tests

* Lint and format

* Fix TS

* Dummy comment

* Try async function
This commit is contained in:
Samy Pessé
2024-03-18 07:55:30 +00:00
committed by GitHub
parent 9e8cde48d4
commit a27fa68ae6
10 changed files with 80 additions and 111 deletions
BIN
View File
Binary file not shown.
+4 -88
View File
@@ -81,94 +81,6 @@ const testCases: TestsCase[] = [
{
name: 'Rocket.Chat',
baseUrl: 'https://docs.rocket.chat',
tests: [
{
name: 'Home',
url: '',
},
{
name: 'PDF',
url: '~gitbook/pdf?limit=10',
},
],
},
{
name: 'Commerce Layer',
baseUrl: 'https://docs.commercelayer.io/core/',
tests: [
{
name: 'Home',
url: '',
},
{
name: 'API Reference',
url: 'v/api-reference/',
},
],
},
{
name: 'Naviga',
baseUrl: 'https://docs.navigaglobal.com/naviga-dashboard-overview/',
tests: [
{
name: 'Home',
url: 'v/dashboard-5.4/',
},
],
},
{
name: 'Mattermost',
baseUrl: 'https://handbook.mattermost.com/',
tests: [
{
name: 'Home',
url: '',
},
],
},
{
name: 'Tile DB',
baseUrl: 'https://docs.tiledb.com/main/',
tests: [
{
name: 'Home',
url: '',
},
],
},
{
name: 'Nimbleway',
baseUrl: 'https://docs.nimbleway.com/',
tests: [
{
name: 'Home',
url: '',
},
],
},
{
name: 'Parcellab',
baseUrl: 'https://how.parcellab.works/docs/',
tests: [
{
name: 'Home',
url: '',
},
],
},
{
name: 'CitrusAd',
baseUrl: 'https://help.citrusad.com/citrus-ads/',
tests: [
{
name: 'Home',
url: '',
},
],
},
{
name: 'ThousandEyes',
baseUrl: 'https://docs.thousandeyes.com/',
tests: [
{
name: 'Home',
@@ -252,6 +164,10 @@ const testCases: TestsCase[] = [
name: 'Emojis',
url: 'blocks/emojis',
},
{
name: 'Links',
url: 'blocks/links',
},
],
},
{
+1 -1
View File
@@ -36,7 +36,7 @@
"jsontoxml": "^1.0.1",
"katex": "^0.16.9",
"memoizee": "^0.4.15",
"next": "^14.1.0",
"next": "^14.1.3",
"next-themes": "^0.2.1",
"nuqs": "^1.15.4",
"object-hash": "^3.0.0",
@@ -0,0 +1,13 @@
'use client';
import { useScrollToHash } from '@/components/hooks';
/**
* Client component to initialize interactivity for a page.
*/
export function PageClientLayout(props: {}) {
// We use this hook in the page layout to ensure the elements for the blocks
// are rendered before we scroll to the hash.
useScrollToHash();
return null;
}
@@ -10,6 +10,7 @@ import { getPagePath } from '@/lib/pages';
import { ContentRefContext } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
import { PageClientLayout } from './PageClientLayout';
import { PagePathParams, fetchPageData, getPathnameParam } from '../../fetch';
export const runtime = 'edge';
@@ -83,6 +84,9 @@ export default async function Page(props: { params: PagePathParams }) {
/>
) : null}
</div>
<React.Suspense fallback={null}>
<PageClientLayout />
</React.Suspense>
</>
);
}
+7 -6
View File
@@ -111,15 +111,16 @@ export function Block<T extends DocumentBlock>(props: BlockProps<T>) {
function BlockPlaceholder(props: { block: DocumentBlock; style: ClassValue }) {
const { block, style } = props;
const id = 'meta' in block && block.meta && 'id' in block.meta ? block.meta.id : undefined;
switch (block.type) {
case 'heading-1':
case 'heading-2':
case 'heading-3':
case 'file':
return <SkeletonHeading style={style} />;
return <SkeletonHeading id={id} style={style} />;
case 'paragraph':
return <SkeletonSmall style={style} />;
return <SkeletonSmall id={id} style={style} />;
case 'list-ordered':
case 'list-unordered':
case 'list-tasks':
@@ -129,7 +130,7 @@ function BlockPlaceholder(props: { block: DocumentBlock; style: ClassValue }) {
case 'hint':
case 'tabs':
case 'synced-block':
return <SkeletonParagraph style={style} />;
return <SkeletonParagraph id={id} style={style} />;
case 'expandable':
case 'table':
case 'swagger':
@@ -137,17 +138,17 @@ function BlockPlaceholder(props: { block: DocumentBlock; style: ClassValue }) {
case 'divider':
case 'content-ref':
case 'integration':
return <SkeletonCard style={style} />;
return <SkeletonCard id={id} style={style} />;
case 'embed':
case 'images':
case 'drawing':
return <SkeletonImage style={style} />;
return <SkeletonImage id={id} style={style} />;
case 'image':
case 'code-line':
case 'tabs-item':
throw new Error('Blocks should be directly rendered by parent');
case 'integration':
return <SkeletonCard style={style} />;
return <SkeletonCard id={id} style={style} />;
default:
assertNever(block);
}
+1
View File
@@ -1 +1,2 @@
export * from './useScrollActiveId';
export * from './useScrollToHash';
+29
View File
@@ -0,0 +1,29 @@
import { useParams } from 'next/navigation';
import React from 'react';
/**
* Scroll to the current URL hash everytime the URL changes.
*/
export function useScrollToHash() {
const params = useParams();
const scrollToHash = React.useCallback(() => {
const hash = window.location.hash;
if (hash) {
const element = document.getElementById(hash.slice(1));
if (element) {
element.scrollIntoView({
block: 'start',
behavior: 'smooth',
});
}
}
}, []);
// With next.js, the hashchange event is not triggered when the hash changes
// Instead a hack is to use the `useParams` hook to listen to changes in the hash
// https://github.com/vercel/next.js/discussions/49465#discussioncomment-5845312
React.useEffect(() => {
scrollToHash();
}, [params, scrollToHash]);
}
+20 -15
View File
@@ -6,10 +6,10 @@ import { LoadingPane } from './LoadingPane';
* Placeholder to be used when a content is not yet loaded (in a React.Suspense boundary).
* It's used when streaming the content of a page.
*/
export function SkeletonParagraph(props: { style?: ClassValue }) {
const { style } = props;
export function SkeletonParagraph(props: { id?: string; style?: ClassValue }) {
const { id, style } = props;
return (
<div role="status" aria-busy className="skeleton-paragraph">
<div id={id} role="status" aria-busy className="skeleton-paragraph">
<LoadingPane
style={[
'rounded-md',
@@ -25,10 +25,10 @@ export function SkeletonParagraph(props: { style?: ClassValue }) {
/**
* Placeholder when loading a title.
*/
export function SkeletonHeading(props: { style?: ClassValue }) {
const { style } = props;
export function SkeletonHeading(props: { id?: string; style?: ClassValue }) {
const { id, style } = props;
return (
<div role="status" aria-busy className="skeleton-heading">
<div id={id} role="status" aria-busy className="skeleton-heading">
<LoadingPane
tile={12}
style={['rounded-md', 'h-[47px]', '[max-width:calc(48rem-1px)]', style]}
@@ -40,10 +40,10 @@ export function SkeletonHeading(props: { style?: ClassValue }) {
/**
* Placeholder when loading an asset (image, video, etc.)
*/
export function SkeletonImage(props: { style?: ClassValue }) {
const { style } = props;
export function SkeletonImage(props: { id?: string; style?: ClassValue }) {
const { id, style } = props;
return (
<div role="status" aria-busy className="skeleton-image">
<div id={id} role="status" aria-busy className="skeleton-image">
<LoadingPane
tile={96}
style={[
@@ -61,10 +61,15 @@ export function SkeletonImage(props: { style?: ClassValue }) {
/**
* Placeholder when loading a card
*/
export function SkeletonCard(props: { style?: ClassValue }) {
const { style } = props;
export function SkeletonCard(props: { id?: string; style?: ClassValue }) {
const { id, style } = props;
return (
<div role="status" aria-busy className={tcls('skeleton-card', 'flex', 'gap-[25px]', style)}>
<div
id={id}
role="status"
aria-busy
className={tcls('skeleton-card', 'flex', 'gap-[25px]', style)}
>
<LoadingPane tile={24} delay={0} style={['rounded-md', 'aspect-[1/1.2]', 'w-full']} />
<LoadingPane tile={24} delay={1} style={['rounded-md', 'aspect-[1/1.2]', 'w-full']} />
<LoadingPane tile={24} delay={2} style={['rounded-md', 'aspect-[1/1.2]', 'w-full']} />
@@ -75,10 +80,10 @@ export function SkeletonCard(props: { style?: ClassValue }) {
/**
* Placeholder when loading small elements
*/
export function SkeletonSmall(props: { style?: ClassValue }) {
const { style } = props;
export function SkeletonSmall(props: { id?: string; style?: ClassValue }) {
const { id, style } = props;
return (
<div role="status" aria-busy className="skeleton-small">
<div id={id} role="status" aria-busy className="skeleton-small">
<LoadingPane
tile={12}
style={['rounded-md', 'h-[35px]', '[max-width:calc(48rem-1px)]', style]}
+1 -1
View File
@@ -12,7 +12,7 @@ export type StreamResponseChunk<T> = {
export function streamResponse<T, P extends any[]>(
createGenerator: (...args: P) => AsyncGenerator<T>,
) {
return (...args: Parameters<typeof createGenerator>) => {
return async (...args: Parameters<typeof createGenerator>) => {
const generator = createGenerator(...args);
return streamChunk<T>(generator);
};