-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use wouter
for routing
#969
Changes from all commits
72aabc0
ab5e79a
69c25eb
e9dae4b
8fbe6c7
b701a0b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,9 @@ import classnames from 'classnames'; | |
import { toChildArray } from 'preact'; | ||
import type { ComponentChildren, JSX } from 'preact'; | ||
import { useMemo, useState } from 'preact/hooks'; | ||
import { Link as RouteLink } from 'wouter-preact'; | ||
|
||
import { Scroll, ScrollContainer } from '../../'; | ||
import { Link as UILink, Scroll, ScrollContainer } from '../../'; | ||
import { jsxToHTML } from '../util/jsx-to-string'; | ||
|
||
/** | ||
|
@@ -48,7 +49,10 @@ export type LibraryPageProps = { | |
function Page({ children, intro, title }: LibraryPageProps) { | ||
return ( | ||
<section className="max-w-6xl pb-16 space-y-8 text-slate-7"> | ||
<div className="sticky top-0 z-4 h-16 flex items-center bg-slate-0 border-b"> | ||
<div | ||
className="sticky top-0 z-4 h-16 flex items-center bg-slate-0 border-b" | ||
id="page-header" | ||
> | ||
<h1 className="px-4 text-4xl font-light">{title}</h1> | ||
</div> | ||
{intro && ( | ||
|
@@ -365,12 +369,30 @@ function Usage({ componentName, size = 'md' }: LibraryUsageProps) { | |
); | ||
} | ||
|
||
export type LinkProps = { | ||
children: ComponentChildren; | ||
href: string; | ||
}; | ||
|
||
/** | ||
* Render an internal link to another pattern-library page. | ||
* TODO: Support external links | ||
*/ | ||
function Link({ children, href }: LinkProps) { | ||
return ( | ||
<RouteLink href={href}> | ||
<UILink underline="always">{children}</UILink> | ||
</RouteLink> | ||
); | ||
} | ||
Comment on lines
+381
to
+387
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So many things named There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should call this component |
||
|
||
export default { | ||
Changelog, | ||
ChangelogItem, | ||
Code, | ||
Demo, | ||
Example, | ||
Link, | ||
Page, | ||
Pattern, | ||
Section, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,85 @@ | ||
import classnames from 'classnames'; | ||
import type { ComponentChildren } from 'preact'; | ||
import { useEffect } from 'preact/hooks'; | ||
import { | ||
Route, | ||
Router, | ||
Switch, | ||
Link as RouteLink, | ||
useLocation, | ||
useRoute, | ||
} from 'wouter-preact'; | ||
|
||
import type { PlaygroundAppProps } from '../'; | ||
import { Link, LogoIcon } from '../../'; | ||
import { useRoute } from '../router'; | ||
import { componentGroups, getRoutes } from '../routes'; | ||
import type { PlaygroundRoute } from '../routes'; | ||
import Library from './Library'; | ||
|
||
/** | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No meaningful changes to |
||
* Header for a primary section of nav | ||
*/ | ||
function NavHeader({ children }: { children: ComponentChildren }) { | ||
return <h2 className="border-b px-2 py-1 font-light text-lg">{children}</h2>; | ||
} | ||
|
||
/** | ||
* Secondary section of navigation, with header | ||
*/ | ||
function NavSection({ | ||
title, | ||
children, | ||
}: { | ||
title: string; | ||
children: ComponentChildren; | ||
}) { | ||
return ( | ||
<div className="space-y-4"> | ||
<h3 className="mx-2 text-slate-7 font-semibold text-sm">{title}</h3> | ||
{children} | ||
</div> | ||
); | ||
} | ||
|
||
/** | ||
* Render a list of navigation items | ||
*/ | ||
function NavList({ children }: { children: ComponentChildren }) { | ||
return ( | ||
<ul className="ml-2 space-y-2 border-l-2 border-slate-0">{children}</ul> | ||
); | ||
} | ||
|
||
/** | ||
* A single navigation link | ||
*/ | ||
function NavLink({ route }: { route: PlaygroundRoute }) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But there are (obviously) changes here to use the |
||
const [isActive] = useRoute(route.route ?? ''); | ||
return ( | ||
<li className="-ml-[2px]"> | ||
{route.route && ( | ||
<RouteLink href={route.route ?? ''}> | ||
<Link | ||
classes={classnames( | ||
'pl-4 w-full border-l-2 hover:border-l-brand', | ||
|
||
{ | ||
'border-l-2 border-brand font-semibold': isActive, | ||
'border-transparent': !isActive, | ||
} | ||
)} | ||
> | ||
{route.title} | ||
</Link> | ||
</RouteLink> | ||
)} | ||
{!route.route && ( | ||
<div className="pl-4 w-full text-slate-5 font-light">{route.title}</div> | ||
)} | ||
</li> | ||
); | ||
} | ||
|
||
/** | ||
* Render web content for the playground application. This includes the "frame" | ||
* around the page and a navigation channel, as well as the content rendered | ||
|
@@ -19,130 +91,125 @@ export default function PlaygroundApp({ | |
extraRoutesTitle = 'Playground', | ||
}: PlaygroundAppProps) { | ||
const routes = getRoutes(); | ||
const [pathname] = useLocation(); | ||
|
||
useEffect( | ||
/** | ||
* Support hash-based navigation and reset scroll when `wouter` path | ||
* changes. | ||
* - For locations without hash, reset scroll to top of page | ||
* - For locations with hash, scroll to top of fragment-indicated element, | ||
* and ensure it's not obscured by the sticky `#page-header` element. | ||
*/ | ||
() => { | ||
const hash = window.location.hash.replace(/^#/, ''); | ||
if (hash) { | ||
const fragElement = document.getElementById(hash); | ||
if (fragElement) { | ||
fragElement.scrollIntoView(); | ||
const pageHeaderElement = document.getElementById('page-header'); | ||
// Height taken up by sticky header on page. Add 8 pixels to give | ||
// some visual padding. | ||
const headerOffset = pageHeaderElement | ||
? pageHeaderElement.getBoundingClientRect().height + 8 | ||
: 0; | ||
const fragTop = fragElement.getBoundingClientRect().top; | ||
if (fragTop <= headerOffset) { | ||
// Adjustment to accommodate sticky header (only if fragment is at or | ||
// near top of viewport) | ||
window.scrollBy({ top: -1 * (headerOffset - fragTop) }); | ||
} | ||
} | ||
} else { | ||
window.scrollTo(0, 0); | ||
} | ||
}, | ||
[pathname] | ||
); | ||
|
||
// Put all of the custom routes into the "custom" group | ||
const customRoutes = extraRoutes.map((route): PlaygroundRoute => { | ||
return { ...route, group: 'custom' }; | ||
}); | ||
const allRoutes = routes.concat(customRoutes); | ||
const [activeRoute] = useRoute(baseURL, allRoutes); | ||
const content = | ||
activeRoute && activeRoute.component ? ( | ||
<activeRoute.component /> | ||
) : ( | ||
<Library.Page title=":( Sorry"> | ||
<h1 className="text-2xl">Page not found</h1> | ||
</Library.Page> | ||
); | ||
|
||
/** | ||
* Header for a primary section of nav | ||
*/ | ||
function NavHeader({ children }: { children: ComponentChildren }) { | ||
return ( | ||
<h2 className="border-b px-2 py-1 font-light text-lg">{children}</h2> | ||
); | ||
} | ||
|
||
/** | ||
* A single navigation link | ||
*/ | ||
function NavLink({ route }: { route: PlaygroundRoute }) { | ||
return ( | ||
<li className="-ml-[2px]"> | ||
{route.route && ( | ||
<Link | ||
classes={classnames('pl-4 w-full border-l-2 hover:border-l-brand', { | ||
'border-l-2 border-brand font-semibold': | ||
activeRoute?.route === route.route, | ||
'border-transparent': activeRoute?.route !== route.route, | ||
})} | ||
href={`${baseURL}${route.route.toString()}`} | ||
> | ||
{route.title} | ||
</Link> | ||
)} | ||
{!route.route && ( | ||
<div className="pl-4 w-full text-slate-5 font-light"> | ||
{route.title} | ||
</div> | ||
)} | ||
</li> | ||
); | ||
} | ||
|
||
/** | ||
* Render a list of navigation items | ||
*/ | ||
function NavList({ routes }: { routes: PlaygroundRoute[] }) { | ||
return ( | ||
<ul className="ml-2 space-y-2 border-l-2 border-slate-0"> | ||
{routes.map(route => ( | ||
<NavLink key={route.title} route={route} /> | ||
const pageRoutes = ( | ||
<> | ||
{allRoutes | ||
.filter(route => !!route.route) | ||
.map(aRoute => ( | ||
<Route key={aRoute.title} path={aRoute.route}> | ||
{aRoute.component ?? aRoute.title} | ||
</Route> | ||
))} | ||
</ul> | ||
); | ||
} | ||
</> | ||
); | ||
|
||
/** | ||
* Secondary section of navigation, with header | ||
*/ | ||
function NavSection({ | ||
title, | ||
children, | ||
}: { | ||
title: string; | ||
children: ComponentChildren; | ||
}) { | ||
return ( | ||
<div className="space-y-4"> | ||
<h3 className="mx-2 text-slate-7 font-semibold text-sm">{title}</h3> | ||
{children} | ||
</div> | ||
); | ||
} | ||
const groupKeys = Object.keys(componentGroups) as Array< | ||
keyof typeof componentGroups | ||
>; | ||
return ( | ||
<main className="max-w-[1200px] mx-auto"> | ||
<div className="md:grid md:grid-cols-[2fr_5fr]"> | ||
<div className="md:h-screen md:sticky md:top-0 overflow-scroll"> | ||
<div className="md:sticky md:top-0 h-16 px-6 flex items-center bg-slate-0 border-b"> | ||
<h1 className="text-lg"> | ||
<Link href={baseURL + '/'} classes="grow flex gap-x-2"> | ||
<LogoIcon /> | ||
Component Library | ||
</Link> | ||
</h1> | ||
</div> | ||
<nav id="nav" className="pt-8 pb-16 space-y-4 mr-4"> | ||
<NavHeader>Foundations</NavHeader> | ||
<NavList routes={getRoutes('foundations')} /> | ||
<Router base={baseURL}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NB: Except for wrapping the whole thing in a |
||
<main className="max-w-[1200px] mx-auto"> | ||
<div className="md:grid md:grid-cols-[2fr_5fr]"> | ||
<div className="md:h-screen md:sticky md:top-0 overflow-scroll"> | ||
<div className="md:sticky md:top-0 h-16 px-6 flex items-center bg-slate-0 border-b"> | ||
<h1 className="text-lg"> | ||
<Link href={baseURL + '/'} classes="grow flex gap-x-2"> | ||
<LogoIcon /> | ||
Component Library | ||
</Link> | ||
</h1> | ||
</div> | ||
<nav id="nav" className="pt-8 pb-16 space-y-4 mr-4"> | ||
<NavHeader>Foundations</NavHeader> | ||
<NavList> | ||
{getRoutes('foundations').map(route => ( | ||
<NavLink key={route.title} route={route} /> | ||
))} | ||
</NavList> | ||
|
||
<NavHeader>Components</NavHeader> | ||
<NavHeader>Components</NavHeader> | ||
|
||
{groupKeys.map(group => { | ||
return ( | ||
<NavSection | ||
key={group} | ||
title={componentGroups[group] as string} | ||
> | ||
<NavList routes={getRoutes(group)} /> | ||
</NavSection> | ||
); | ||
})} | ||
{groupKeys.map(group => { | ||
return ( | ||
<NavSection | ||
key={group} | ||
title={componentGroups[group] as string} | ||
> | ||
<NavList> | ||
{getRoutes(group).map(route => ( | ||
<NavLink key={route.title} route={route} /> | ||
))} | ||
</NavList> | ||
</NavSection> | ||
); | ||
})} | ||
|
||
{extraRoutes.length > 0 && ( | ||
<> | ||
<NavHeader>{extraRoutesTitle}</NavHeader> | ||
<NavList routes={customRoutes} /> | ||
</> | ||
)} | ||
</nav> | ||
{extraRoutes.length > 0 && ( | ||
<> | ||
<NavHeader>{extraRoutesTitle}</NavHeader> | ||
<NavList> | ||
{customRoutes.map(route => ( | ||
<NavLink key={route.title} route={route} /> | ||
))} | ||
</NavList> | ||
</> | ||
)} | ||
</nav> | ||
</div> | ||
<div className="bg-white pb-16"> | ||
<Switch> | ||
{pageRoutes} | ||
<Route> | ||
<Library.Page title=":( Sorry"> | ||
<h1 className="text-2xl">Page not found</h1> | ||
</Library.Page> | ||
</Route> | ||
</Switch> | ||
Comment on lines
+202
to
+209
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
</div> | ||
</div> | ||
<div className="bg-white pb-16">{content}</div> | ||
</div> | ||
</main> | ||
</main> | ||
</Router> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the page's sticky header. Just giving it an ID so I can query for it in the
PlaygroundApp
's navigation/scroll handlinguseEffect
hook.