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 { render } from 'preact';
import ButtonPatterns from './components/ButtonPatterns';
import PlaygroundApp from './shared/components/PlaygroundApp';
import sidebarIcons from '../../src/sidebar/icons';
registerIcons(sidebarIcons);
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';
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() {
return (
<>
......@@ -13,6 +22,7 @@ function HomeRoute() {
);
}
/** @type {PlaygroundRoute[]} */
const routes = [
{
route: /^\/?$/,
......@@ -21,16 +31,33 @@ const routes = [
},
{
route: '/shared-buttons',
title: 'Buttons (Shared)',
title: 'Buttons',
component: SharedButtonPatterns,
},
];
const demoRoutes = routes.filter(r => r.component !== HomeRoute);
export default function PlaygroundApp() {
const baseUrl = '/ui-playground';
const [route, navigate] = useRoute(baseUrl, routes);
/**
* @typedef PlaygroundAppProps
* @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 ? (
<route.component />
) : (
......@@ -44,10 +71,11 @@ export default function PlaygroundApp() {
<main className="PlaygroundApp">
<div className="PlaygroundApp__sidebar">
<div className="PlaygroundApp__sidebar-home">
<a href={baseUrl} onClick={e => navigate(e, '/')}>
<a href={baseURL} onClick={e => navigate(e, '/')}>
<SvgIcon name="logo" />
</a>
</div>
<h2>Common Patterns</h2>
<ul>
{demoRoutes.map(c => (
<li key={c.route}>
......@@ -62,6 +90,25 @@ export default function PlaygroundApp() {
</li>
))}
</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 className="PlaygroundApp__content">{content}</div>
</main>
......
......@@ -13,7 +13,7 @@ import {
export default function SharedButtonPatterns() {
return (
<PatternPage title="Buttons (Shared)">
<PatternPage title="Buttons">
<Pattern title="IconButton">
<p>A button containing an icon and no other content.</p>
......
import { useEffect, useMemo, useState } from 'preact/hooks';
function routeFromCurrentUrl(baseUrl) {
return location.pathname.slice(baseUrl.length);
function routeFromCurrentURL(baseURL) {
return location.pathname.slice(baseURL.length);
}
export function useRoute(baseUrl, routes) {
const [route, setRoute] = useState(() => routeFromCurrentUrl(baseUrl));
export function useRoute(baseURL, routes) {
const [route, setRoute] = useState(() => routeFromCurrentURL(baseURL));
const routeData = useMemo(() => routes.find(r => route.match(r.route)), [
route,
routes,
......@@ -18,17 +18,17 @@ export function useRoute(baseUrl, routes) {
useEffect(() => {
const popstateListener = () => {
setRoute(routeFromCurrentUrl(baseUrl));
setRoute(routeFromCurrentURL(baseURL));
};
window.addEventListener('popstate', popstateListener);
return () => {
window.removeEventListener('popstate', popstateListener);
};
}, [baseUrl]);
}, [baseURL]);
const navigate = (event, route) => {
event.preventDefault();
history.pushState({}, title, baseUrl + route);
history.pushState({}, title, baseURL + 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