Commit bf3cc024 authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Extend `PlaygroundApp` to take `extraRoutes`

Devise a way to "extend" a common base playground app with local-app
routes and patterns
parent a4831b57
import {
PatternPage,
Pattern,
PatternExamples,
PatternExample,
} from '../shared/components/PatternPage';
import { IconButton } from '../../../src/shared/components/buttons';
export default function ButtonPatterns() {
return (
<PatternPage title="Buttons">
<Pattern title="Non-Responsive IconButton">
<p>
An icon-only button overriding responsive affordances to fit in
specific or tight spaces.
</p>
<PatternExamples>
<PatternExample details="Sizes: medium is default">
<IconButton icon="edit" title="Edit" size="small" />
<IconButton icon="edit" title="Edit" size="medium" />
<IconButton icon="edit" title="Edit" size="large" />
</PatternExample>
</PatternExamples>
</Pattern>
</PatternPage>
);
}
import { registerIcons } from '@hypothesis/frontend-shared'; import { registerIcons } from '@hypothesis/frontend-shared';
import { render } from 'preact'; import { render } from 'preact';
import ButtonPatterns from './components/ButtonPatterns';
import PlaygroundApp from './shared/components/PlaygroundApp'; import PlaygroundApp from './shared/components/PlaygroundApp';
import sidebarIcons from '../../src/sidebar/icons'; import sidebarIcons from '../../src/sidebar/icons';
registerIcons(sidebarIcons); registerIcons(sidebarIcons);
const container = document.querySelector('#app'); const container = document.querySelector('#app');
render(<PlaygroundApp />, /** @type Element */ (container));
const extraRoutes = [
{
route: '/buttons',
title: 'Buttons',
component: ButtonPatterns,
},
];
render(
<PlaygroundApp extraRoutes={extraRoutes} />,
/** @type Element */ (container)
);
...@@ -4,6 +4,15 @@ import SharedButtonPatterns from './patterns/SharedButtonPatterns'; ...@@ -4,6 +4,15 @@ import SharedButtonPatterns from './patterns/SharedButtonPatterns';
import { useRoute } from '../router'; import { useRoute } from '../router';
/**
* @typedef PlaygroundRoute - Route "handler" that provides a component (function)
* that should be rendered for the indicated route
* @prop {RegExp|string} route - Pattern or string path relative to
* `baseURL`, e.g. '/my-patterns'
* @prop {string} title
* @prop {import("preact").FunctionComponent<{}>} component
*/
function HomeRoute() { function HomeRoute() {
return ( return (
<> <>
...@@ -13,6 +22,7 @@ function HomeRoute() { ...@@ -13,6 +22,7 @@ function HomeRoute() {
); );
} }
/** @type {PlaygroundRoute[]} */
const routes = [ const routes = [
{ {
route: /^\/?$/, route: /^\/?$/,
...@@ -21,16 +31,33 @@ const routes = [ ...@@ -21,16 +31,33 @@ const routes = [
}, },
{ {
route: '/shared-buttons', route: '/shared-buttons',
title: 'Buttons (Shared)', title: 'Buttons',
component: SharedButtonPatterns, component: SharedButtonPatterns,
}, },
]; ];
const demoRoutes = routes.filter(r => r.component !== HomeRoute); const demoRoutes = routes.filter(r => r.component !== HomeRoute);
export default function PlaygroundApp() { /**
const baseUrl = '/ui-playground'; * @typedef PlaygroundAppProps
const [route, navigate] = useRoute(baseUrl, routes); * @prop {string} [baseURL]
* @prop {PlaygroundRoute[]} [extraRoutes] - Local-/application-specific routes
* to add to this pattern library in addition to the shared/common routes
*/
/**
* Render web content for the playground application. This includes the "frame"
* around the page and a navigation channel, as well as the content rendered
* by the component handling the current route.
*
* @param {PlaygroundAppProps} props
*/
export default function PlaygroundApp({
baseURL = '/ui-playground',
extraRoutes = [],
}) {
const allRoutes = routes.concat(extraRoutes);
const [route, navigate] = useRoute(baseURL, allRoutes);
const content = route ? ( const content = route ? (
<route.component /> <route.component />
) : ( ) : (
...@@ -44,10 +71,11 @@ export default function PlaygroundApp() { ...@@ -44,10 +71,11 @@ export default function PlaygroundApp() {
<main className="PlaygroundApp"> <main className="PlaygroundApp">
<div className="PlaygroundApp__sidebar"> <div className="PlaygroundApp__sidebar">
<div className="PlaygroundApp__sidebar-home"> <div className="PlaygroundApp__sidebar-home">
<a href={baseUrl} onClick={e => navigate(e, '/')}> <a href={baseURL} onClick={e => navigate(e, '/')}>
<SvgIcon name="logo" /> <SvgIcon name="logo" />
</a> </a>
</div> </div>
<h2>Common Patterns</h2>
<ul> <ul>
{demoRoutes.map(c => ( {demoRoutes.map(c => (
<li key={c.route}> <li key={c.route}>
...@@ -62,6 +90,25 @@ export default function PlaygroundApp() { ...@@ -62,6 +90,25 @@ export default function PlaygroundApp() {
</li> </li>
))} ))}
</ul> </ul>
{extraRoutes.length && (
<>
<h2>Application Patterns</h2>
<ul>
{extraRoutes.map(c => (
<li key={c.route}>
<a
className="PlaygroundApp__nav-link"
key={c.route}
href={/** @type string */ (c.route)}
onClick={e => navigate(e, c.route)}
>
{c.title}
</a>
</li>
))}
</ul>
</>
)}
</div> </div>
<div className="PlaygroundApp__content">{content}</div> <div className="PlaygroundApp__content">{content}</div>
</main> </main>
......
...@@ -13,7 +13,7 @@ import { ...@@ -13,7 +13,7 @@ import {
export default function SharedButtonPatterns() { export default function SharedButtonPatterns() {
return ( return (
<PatternPage title="Buttons (Shared)"> <PatternPage title="Buttons">
<Pattern title="IconButton"> <Pattern title="IconButton">
<p>A button containing an icon and no other content.</p> <p>A button containing an icon and no other content.</p>
......
import { useEffect, useMemo, useState } from 'preact/hooks'; import { useEffect, useMemo, useState } from 'preact/hooks';
function routeFromCurrentUrl(baseUrl) { function routeFromCurrentURL(baseURL) {
return location.pathname.slice(baseUrl.length); return location.pathname.slice(baseURL.length);
} }
export function useRoute(baseUrl, routes) { export function useRoute(baseURL, routes) {
const [route, setRoute] = useState(() => routeFromCurrentUrl(baseUrl)); const [route, setRoute] = useState(() => routeFromCurrentURL(baseURL));
const routeData = useMemo(() => routes.find(r => route.match(r.route)), [ const routeData = useMemo(() => routes.find(r => route.match(r.route)), [
route, route,
routes, routes,
...@@ -18,17 +18,17 @@ export function useRoute(baseUrl, routes) { ...@@ -18,17 +18,17 @@ export function useRoute(baseUrl, routes) {
useEffect(() => { useEffect(() => {
const popstateListener = () => { const popstateListener = () => {
setRoute(routeFromCurrentUrl(baseUrl)); setRoute(routeFromCurrentURL(baseURL));
}; };
window.addEventListener('popstate', popstateListener); window.addEventListener('popstate', popstateListener);
return () => { return () => {
window.removeEventListener('popstate', popstateListener); window.removeEventListener('popstate', popstateListener);
}; };
}, [baseUrl]); }, [baseURL]);
const navigate = (event, route) => { const navigate = (event, route) => {
event.preventDefault(); event.preventDefault();
history.pushState({}, title, baseUrl + route); history.pushState({}, title, baseURL + route);
setRoute(route); setRoute(route);
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment