Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions apps/docs/app/docs/DocsSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { useEffect, useState } from 'react';

import NavLink from '../../components/NavLink';

interface DocsSidebarProps {
isMobileOpen?: boolean;
onMobileClose?: () => void;
}

interface HeadingItem {
id: string;
text: string;
level: number;
element: HTMLElement;
}

export function DocsSidebar({
isMobileOpen = false,
onMobileClose,
}: DocsSidebarProps) {
const [headings, setHeadings] = useState<HeadingItem[]>([]);
const [activeHeading, setActiveHeading] = useState<string>('');

// Generate ID from heading text
const generateId = (text: string): string => {
return text
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.trim();
};

// Extract headings from the page content
useEffect(() => {
const extractHeadings = () => {
const headingElements = document.querySelectorAll('h2, h3');
const headingItems: HeadingItem[] = [];

headingElements.forEach((element) => {
const text = element.textContent ?? '';
const id = generateId(text);
const level = parseInt(element.tagName.charAt(1));

// Set the ID on the element for anchor linking
element.id = id;

headingItems.push({
id,
text,
level,
element: element as HTMLElement,
});
});

setHeadings(headingItems);
};

// Extract headings after a short delay to ensure DOM is ready
const timeoutId = setTimeout(extractHeadings, 100);

return () => clearTimeout(timeoutId);
}, []);

// Handle scroll-based active heading detection
useEffect(() => {
const handleScroll = () => {
const scrollPosition = window.scrollY + 100; // Offset for better UX

for (let i = headings.length - 1; i >= 0; i--) {
const heading = headings[i];
if (heading.element.offsetTop <= scrollPosition) {
setActiveHeading(heading.id);
break;
}
}
};

if (headings.length > 0) {
window.addEventListener('scroll', handleScroll);
handleScroll(); // Check initial position

return () => window.removeEventListener('scroll', handleScroll);
}

return undefined;
}, [headings]);

return (
<>
{isMobileOpen && (
<div
className="fixed inset-0 z-40 bg-black/50 md:hidden"
onClick={onMobileClose}
/>
)}

<aside
className={`
p-5
md:px-0
md:relative md:py-7 md:block md:transform-none md:transition-none
md:shadow-none md:border-none md:bg-transparent md:z-auto
md:left-auto md:top-auto md:h-auto md:w-auto md:overflow-visible
md:translate-x-0 md:opacity-100 md:pointer-events-auto

fixed top-0 left-0 z-50 w-72 h-screen
bg-background border-r border-border
transform -translate-x-full transition-transform duration-300 ease-in-out
overflow-y-auto shadow-xl
${isMobileOpen ? 'translate-x-0' : '-translate-x-full'}
`}
>
<nav className="sticky top-0 py-5 space-y-0.5" onClick={onMobileClose}>
{headings.map((heading) => (
<NavLink
key={heading.id}
href={`#${heading.id}`}
active={activeHeading === heading.id}
className={heading.level === 3 ? 'ml-4' : ''}
>
{heading.text}
</NavLink>
))}
</nav>
</aside>
</>
);
}

export default DocsSidebar;
76 changes: 44 additions & 32 deletions apps/docs/app/docs/Overview.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { SimpleCodeBlock } from '@/components/SimpleCodeBlock';
import { IconCiWarningFill } from '@/components/icons';
import { ButtonGroup, ButtonGroupItem } from '@/components/ui/button-group';
import Link from 'next/link';
import { useState } from 'react';

import type { DocsExampleTypes } from './types';
Expand Down Expand Up @@ -153,49 +155,59 @@ export function Overview({ exampleType, setExampleType }: OverviewProps) {
return (
<section className="space-y-4">
<h2>Overview</h2>
<p className="bg-amber-500 px-4 py-2 rounded-md">
<strong>NOTE</strong>: Precision Diffs is in early active development
and many of these APIs will change or are incomplete.
<p className="flex gap-2 text-sm bg-cyan-500/10 border border-cyan-500/20 px-5 py-4 rounded-md text-cyan-600 dark:text-cyan-300">
<IconCiWarningFill className="mt-[2px]" />
Precision Diffs is in early active development—many of these APIs are
subject to change.
</p>
<p>
Precision Diffs is a library for rendering code and diffs on the web.
This includes both high level easy to use components as well as exposing
many of the internals if you want to selectively use specific pieces.
We&lsquo;ve built syntax highlighting on top of Shiki which provides a
lot of great theme and language support.
<strong>Precision Diffs</strong> is a library for rendering code and
diffs on the web. This includes both high level easy-to-use components
as well as exposing many of the internals if you want to selectively use
specific pieces. We’ve built syntax highlighting on top of{' '}
<Link href="https://shiki.dev" target="_blank">
Shiki
</Link>{' '}
which provides a lot of great theme and language support.
</p>
<p>
We&lsquo;ve taken a somewhat opinionated stance in our architecture in
that browsers are pretty effecient at rendering raw html and we lean
into this by having all the lower level APIs purely rendering strings
(raw html) that can then be consumed by higher order components and
utilities. This gives us great performance and flexibility to support
popular libraries like React as well as provide great tools if you want
to stick to vanilla js and html. The higher order components render all
this out into shadow dom and css grids.
We have an opinionated stance in our architecture:{' '}
<strong>browsers are rather efficient at rendering raw HTML</strong>. We
lean into this by having all the lower level APIs purely rendering
strings (the raw HTML) that are then consumed by higher-order components
and utilities. This gives us great performance and flexibility to
support popular libraries like React as well as provide great tools if
you want to stick to vanilla JavaScript and HTML. The higher-order
components render all this out into Shadow DOM and CSS grid layout.
</p>
<p>
Generally speaking you&lsquo;re probably going to want to use the higher
level components since they provide an easy to use API that you can get
started with rather quickly. Currently we only have components for
vanilla js and React, but will add more if there&lsquo;s demand.
Generally speaking, youre probably going to want to use the higher
level components since they provide an easy-to-use API that you can get
started with rather quickly. We currently only have components for
vanilla JavaScript and React, but will add more if theres demand.
</p>
<p>
For this overview we&lsquo;ll talk about the vanilla js components for
now but there are React equivalents for all of these.
For this overview, well talk about the vanilla JavaScript components
for now but there are React equivalents for all of these.
</p>
<h3>Rendering Diffs</h3>
<p>
It&lsquo;s in the name, it&lsquo;s probably why you&lsquo;re here. Our
goal with visualizing diffs was to provide some flexible and easy to use
APIs for <em>how</em> you might want to render diffs. For this we
provide a component called <code>FileDiff</code> (both a vanilla js
implementation and a React version).
Its in the name, its probably why youre here. Our goal with
visualizing diffs was to provide some flexible and easy to use APIs for{' '}
<em>how</em> you might want to render diffs. For this we provide a
component called <code>FileDiff</code> (available in both JavaScript and
React versions).
</p>
<p>
With <code>FileDiff</code> there are two basic ways to render diffs,
either providing 2 versions of a file or code to compare, or consuming a
patch file.
There are two ways to render diffs with <code>FileDiff</code>:
</p>
<ol>
<li>Provide two versions of a file or code to compare</li>
<li>Consume a patch file</li>
</ol>
<p>
You can see examples of both these approaches below, in both JavaScript
and React.
</p>
<div className="flex gap-2">
<ButtonGroup
Expand All @@ -213,8 +225,8 @@ export function Overview({ exampleType, setExampleType }: OverviewProps) {
setExample(value as 'single-file' | 'patch-file')
}
>
<ButtonGroupItem value="single-file">Single File</ButtonGroupItem>
<ButtonGroupItem value="patch-file">Patch File</ButtonGroupItem>
<ButtonGroupItem value="single-file">Single file</ButtonGroupItem>
<ButtonGroupItem value="patch-file">Patch file</ButtonGroupItem>
</ButtonGroup>
</div>
{exampleType === 'react' ? (
Expand Down
71 changes: 55 additions & 16 deletions apps/docs/app/docs/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
'use client';

import Footer from '@/components/Footer';
import { IconParagraph } from '@/components/icons';
import { Button } from '@/components/ui/button';
import { useEffect, useState } from 'react';

import { DocsHeader } from './DocsHeader';
import { DocsSidebar } from './DocsSidebar';
import { Installation } from './Installation';
import { Overview } from './Overview';
import { ReactAPI } from './ReactAPI';
Expand All @@ -12,37 +15,73 @@ import { VanillaAPI } from './VanillaAPI';
import type { DocsExampleTypes } from './types';

export default function DocsPage() {
const [isMobileMenuOpen] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [exampleTypes, setExampleType] = useState<DocsExampleTypes>('vanilla');

// Prevent body scroll when mobile menu is open
useEffect(() => {
if (isMobileMenuOpen) {
document.body.classList.add('mobile-menu-open');
document.body.classList.add('overflow-hidden');
} else {
document.body.classList.remove('mobile-menu-open');
document.body.classList.remove('overflow-hidden');
}

// Cleanup on unmount
return () => {
document.body.classList.remove('mobile-menu-open');
document.body.classList.remove('overflow-hidden');
};
}, [isMobileMenuOpen]);

const toggleMobileMenu = () => {
setIsMobileMenuOpen(!isMobileMenuOpen);
};

const closeMobileMenu = () => {
setIsMobileMenuOpen(false);
};

return (
<div className="min-h-screen w-5xl px-5 mx-auto">
<div className="relative min-h-screen w-5xl px-5 mx-auto max-w-full">
<DocsHeader />
<div className="docs-container prose dark:prose-invert max-w-none">
<Installation />
<Overview exampleType={exampleTypes} setExampleType={setExampleType} />
<ReactAPI />
<VanillaAPI />
<Styling />
{/* <ComponentProps /> */}
{/* <RendererOptions /> */}
{/* <EventHandlers /> */}
{/* <CompleteExample /> */}
{/* <TypescriptSupport /> */}

<Button
variant="outline"
onClick={toggleMobileMenu}
className="md:hidden sticky top-5 z-50 mt-8 bg-background dark:bg-background hover:bg-muted dark:hover:bg-muted"
>
<IconParagraph />
Menu
</Button>

<Button
variant="outline"
onClick={toggleMobileMenu}
className="md:hidden sticky top-5 z-50 mt-8 bg-background dark:bg-background hover:bg-muted dark:hover:bg-muted"
>
<IconParagraph />
Menu
</Button>

<div className="md:grid md:grid-cols-[220px_1fr] gap-6 md:gap-12">
<DocsSidebar
isMobileOpen={isMobileMenuOpen}
onMobileClose={closeMobileMenu}
/>
<div className="prose dark:prose-invert w-full min-w-0 max-w-full">
<Installation />
<Overview
exampleType={exampleTypes}
setExampleType={setExampleType}
/>
<ReactAPI />
<VanillaAPI />
<Styling />
{/* <ComponentProps /> */}
{/* <RendererOptions /> */}
{/* <EventHandlers /> */}
{/* <CompleteExample /> */}
{/* <TypescriptSupport /> */}
</div>
</div>
<Footer />
</div>
Expand Down
Loading