Compare commits

...

5 Commits

8 changed files with 108 additions and 37 deletions

View File

@ -0,0 +1,80 @@
import type { JSX } from "solid-js";
import { createSignal, onCleanup } from "solid-js";
type SmoothScrollProps = JSX.IntrinsicElements["div"] & {
children: JSX.Element;
};
export function SmoothScroll(props: SmoothScrollProps) {
const [isScrolling, setIsScrolling] = createSignal(false);
let animationFrameId: number | null = null;
const easeOutQuad = (t: number, b: number, c: number, d: number) => {
const time = t / d;
return -c * time * (time - 2) + b;
};
const smoothScroll = (deltaY: number) => {
const scrollSpeed = 3;
const currentScroll = window.scrollY;
const targetScroll = deltaY * scrollSpeed;
const duration = 300;
const startTime = performance.now();
const animateScroll = (currentTime: number) => {
const elapsedTime = currentTime - startTime;
const ease = easeOutQuad(
elapsedTime,
currentScroll,
targetScroll,
duration,
);
window.scrollTo(0, ease);
if (elapsedTime < duration) {
animationFrameId = requestAnimationFrame(animateScroll);
} else {
setIsScrolling(false);
}
};
animationFrameId = requestAnimationFrame(animateScroll);
};
const isMobile = () => {
const regex =
/Mobi|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
return regex.test(navigator.userAgent);
};
const handleWheel = (event: WheelEvent) => {
if (isMobile()) return;
event.preventDefault();
if (isScrolling()) {
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
}
}
requestAnimationFrame(() => {
smoothScroll(event.deltaY);
setIsScrolling(false);
});
setIsScrolling(true);
};
onCleanup(() => {
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
}
});
return (
<div {...props} onWheel={handleWheel} style={{ "touch-action": "auto" }}>
{props.children}
</div>
);
}

View File

@ -68,7 +68,7 @@ export function Snippet(props: SnippetProps) {
<div class="pt-4 pl-4"> <div class="pt-4 pl-4">
<TrafficLightsIcon class="h-2.5 w-auto stroke-slate-500/30" /> <TrafficLightsIcon class="h-2.5 w-auto stroke-slate-500/30" />
<div class="mt-4 flex space-x-2 text-xs"> <div class="mt-4 flex space-x-2 text-xs overflow-x-auto">
<For each={props.snippets}> <For each={props.snippets}>
{(tab) => ( {(tab) => (
<div <div

View File

@ -1,6 +1,5 @@
import type { JSXElement } from "solid-js"; import type { JSXElement } from "solid-js";
// import { TableOfContents } from "@/partials/TableOfContents";
import { PrevNextLinks } from "@/components/PrevNextLinks"; import { PrevNextLinks } from "@/components/PrevNextLinks";
import { usePageContext } from "vike-solid/usePageContext"; import { usePageContext } from "vike-solid/usePageContext";
import { clientOnly } from "vike-solid/clientOnly"; import { clientOnly } from "vike-solid/clientOnly";
@ -22,8 +21,11 @@ export function DocsLayout(props: DocsLayoutProps) {
return ( return (
<> <>
<main class="max-w-2xl min-w-0 flex-auto px-4 py-16 lg:max-w-none lg:pr-0 lg:pl-8 xl:px-16 grow"> <main
<article id="article-content"> id="article-content"
class="max-w-2xl min-w-0 flex-auto px-4 py-16 lg:max-w-none lg:pr-0 lg:pl-8 xl:px-16 grow"
>
<article>
<DocsHeader <DocsHeader
title={pageContext.exports.frontmatter?.title} title={pageContext.exports.frontmatter?.title}
estimatedReadingTime={pageContext.exports.readingTime?.text} estimatedReadingTime={pageContext.exports.readingTime?.text}

View File

@ -1,6 +1,7 @@
import type { JSXElement } from "solid-js"; import type { JSXElement } from "solid-js";
import { usePageContext } from "vike-solid/usePageContext"; import { usePageContext } from "vike-solid/usePageContext";
import { SmoothScroll } from "@/components/SmoothScroll";
import { HeroSection } from "@/partials/HeroSection"; import { HeroSection } from "@/partials/HeroSection";
import { Navigation } from "@/partials/Navigation"; import { Navigation } from "@/partials/Navigation";
import { clientOnly } from "vike-solid/clientOnly"; import { clientOnly } from "vike-solid/clientOnly";
@ -24,7 +25,7 @@ export default function DefaultLayout(props: DefaultLayoutProps) {
return ( return (
<> <>
<div class="flex w-full flex-col font-sans"> <SmoothScroll class="flex w-full flex-col font-sans">
<Header /> <Header />
<ReadProgressBar /> <ReadProgressBar />
@ -43,7 +44,7 @@ export default function DefaultLayout(props: DefaultLayoutProps) {
</div> </div>
<Footer /> <Footer />
</div> </SmoothScroll>
<Toaster /> <Toaster />
</> </>

View File

@ -4,7 +4,7 @@ import { Link } from "@/components/Link";
export function Footer() { export function Footer() {
return ( return (
<footer class="bg-slate-50 text-slate-700"> <footer class="bg-slate-50 text-slate-700">
<div class="mx-auto w-full flex flex-col max-w-8xl sm:px-6 lg:px-8 py-8"> <div class="mx-auto w-full flex flex-col max-w-8xl px-4 sm:px-6 lg:px-8 py-8">
<div> <div>
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
<Logo class="h-8 w-auto" /> <Logo class="h-8 w-auto" />

View File

@ -1,7 +1,6 @@
import type { JSX } from "solid-js"; import type { JSX } from "solid-js";
import { MobileNavigation } from "@/partials/MobileNavigation"; import { MobileNavigation } from "@/partials/MobileNavigation";
import { createEffect, createSignal } from "solid-js";
import { Search } from "@/components/Search"; import { Search } from "@/components/Search";
import { Link } from "@/components/Link"; import { Link } from "@/components/Link";
import { Logo } from "@/components/Logo"; import { Logo } from "@/components/Logo";
@ -16,19 +15,6 @@ function GitHubIcon(props: JSX.IntrinsicElements["svg"]) {
} }
export function Header() { export function Header() {
const [isScrolled, setIsScrolled] = createSignal(false);
createEffect(() => {
function onScroll() {
setIsScrolled(window.scrollY > 0);
}
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => {
window.removeEventListener("scroll", onScroll);
};
}, []);
return ( return (
<header <header
class={clsx( class={clsx(
@ -49,11 +35,7 @@ export function Header() {
</div> </div>
<div class="-my-5 mr-6 sm:mr-8 md:mr-0"> <div class="-my-5 mr-6 sm:mr-8 md:mr-0">
<Search <Search />
// fallback={
// <div class="h-6 w-6 animate-pulse rounded-full bg-slate-200" />
// }
/>
</div> </div>
<div class="relative flex basis-0 justify-end gap-6 sm:gap-8 md:grow"> <div class="relative flex basis-0 justify-end gap-6 sm:gap-8 md:grow">

View File

@ -8,35 +8,42 @@ export function ReadProgressBar() {
const [width, setWidth] = createSignal("0%"); const [width, setWidth] = createSignal("0%");
const handleScroll = () => { const handleScroll = () => {
const scrollTop = window.scrollY; const articleHeight = articleContentElement.scrollHeight;
const pageHeight = document.documentElement.scrollHeight; const topPosition = articleContentElement.getBoundingClientRect().top;
const scrollHeight = articleContentElement.scrollHeight; const windowHeight = window.innerHeight;
const gapWithBottom = pageHeight - scrollHeight; if (articleHeight <= windowHeight) {
const scrollableHeight = pageHeight - gapWithBottom; setWidth("100%");
return;
}
const scrollPercentage = Math.round((scrollTop / scrollableHeight) * 100); const elementScrollTop = -topPosition;
const scrollableHeight = articleHeight - windowHeight;
const percentage = (elementScrollTop / scrollableHeight) * 100;
const clampedPercentage = Math.max(0, Math.min(100, percentage));
setWidth(`${scrollPercentage}%`); setWidth(`${clampedPercentage}%`);
}; };
createEffect(() => { createEffect(() => {
window.addEventListener("scroll", handleScroll); window.addEventListener("scroll", handleScroll);
window.addEventListener("resize", handleScroll);
handleScroll(); handleScroll();
return () => { return () => {
window.removeEventListener("scroll", handleScroll); window.removeEventListener("scroll", handleScroll);
window.removeEventListener("resize", handleScroll);
}; };
}); });
return ( return (
<div <div
aria-hidden aria-hidden="true"
class="sticky inset-x-0 top-20 z-50 h-1 w-full bg-violet-50" class="fixed inset-x-0 bottom-0 lg:top-0 z-50 h-1 w-full bg-violet-50 pointer-events-none"
> >
<div <div
class="bg-violet-300 h-full transition-all duration-75" class="bg-violet-300 h-full transition-all duration-300 ease-out"
style={{ width: width() }} style={{ width: width() }}
/> />
</div> </div>

View File

@ -21,7 +21,6 @@ export function TableOfContents() {
if (!section.hash) return null; if (!section.hash) return null;
const el = document.getElementById(section.hash); const el = document.getElementById(section.hash);
console.log(section.hash, el);
if (!el) return null; if (!el) return null;
const style = window.getComputedStyle(el); const style = window.getComputedStyle(el);