Commit ada3e957 authored by Robert Knight's avatar Robert Knight

Add a UI component demo app

Add a new `/ui-playground` route to the client's dev server which hosts
a UI component playground/demo app, plus a link to this route on the dev
server homepage. The demo app provides a place to see and experiment with
UI components outside the context of the sidebar/notebook apps.

 - Add new `/ui-playground` route to dev server which serves a small
   Preact SPA

 - Add Preact SPA in `dev-server/ui-playground/` with a couple of
   placeholder "demos" for the `Button` and `Menu` components

 - Add configuration to gulpfile.js to build ui-playground JS and CSS
   bundles for Preact SPA
parent 180a5de4
......@@ -119,6 +119,14 @@ function serveDev(port, config) {
}
});
// Serve UI component playground
app.get('/ui-playground/:path?', (req, res) => {
res.render('ui-playground', {
resourceRoot:
'http://localhost:3001/hypothesis/1.0.0-dummy-version/build',
});
});
// Nothing else matches: this is a 404
app.use((req, res) => {
res.render('404', templateContext(config));
......
......@@ -42,6 +42,12 @@
Open sidebar
</button>
<button class="js-toggle-client">Load client</button>
<h2>Development tools</h2>
<ul>
<li><a href="/ui-playground">UI component playground</a></li>
</ul>
<h2>Useful links</h2>
<ul>
<li><a href="https://github.com/hypothesis/client">GitHub project</a></li>
......
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>UI component playground</title>
<link rel="stylesheet" href="{{ resourceRoot }}/styles/sidebar.css">
<link rel="stylesheet" href="{{ resourceRoot }}/styles/ui-playground.css">
</head>
<body>
<div id="app"></div>
<script src="{{ resourceRoot }}/scripts/ui-playground.bundle.js"></script>
</body>
</html>
{
"parserOptions": {
"sourceType": "module"
},
"rules": {
"react/prop-types": "off",
}
}
import { SvgIcon } from '@hypothesis/frontend-shared';
import Button from '../../src/sidebar/components/Button';
import Menu from '../../src/sidebar/components/Menu';
import MenuItem from '../../src/sidebar/components/MenuItem';
import { useRoute } from './router';
function ComponentDemo({ title, children }) {
return (
<section>
<h1 className="heading">{title}</h1>
{children}
</section>
);
}
function ButtonDemo() {
return (
<ComponentDemo title="Button">
<Button buttonText="Text button" />
<Button icon="edit" title="Icon button" />
<Button icon="trash" buttonText="Icon + text button" />
<Button disabled={true} buttonText="Disabled button" />
</ComponentDemo>
);
}
function MenuDemo() {
return (
<ComponentDemo title="Menu">
<Menu label="Edit">
<MenuItem label="Zoom in" />
<MenuItem label="Zoom out" />
<MenuItem label="Undo" />
<MenuItem label="Redo" />
</Menu>
</ComponentDemo>
);
}
function HomeRoute() {
return (
<>
<h1 className="heading">UI component playground</h1>
<p>Select a component to view examples.</p>
</>
);
}
const routes = [
{
route: /^\/?$/,
title: 'Home',
component: HomeRoute,
},
{
route: '/button',
title: 'Button',
component: ButtonDemo,
},
{
route: '/menu',
title: 'Menu',
component: MenuDemo,
},
];
const demoRoutes = routes.filter(r => r.component !== HomeRoute);
export default function PlaygroundApp() {
const baseUrl = '/ui-playground';
const [route, navigate] = useRoute(baseUrl, routes);
const content = route ? (
<route.component />
) : (
<>
<h1 className="heading">:(</h1>
<p>Page not found.</p>
</>
);
return (
<main className="PlaygroundApp">
<div className="PlaygroundApp__sidebar">
<a
className="PlaygroundApp__nav-link u-center"
href={baseUrl}
onClick={e => navigate(e, '/')}
>
<SvgIcon name="logo" />
</a>
{demoRoutes.map(c => (
<a
className="PlaygroundApp__nav-link"
key={c.route}
href={c.route}
onClick={e => navigate(e, c.route)}
>
{c.title}
</a>
))}
</div>
<div className="PlaygroundApp__content">{content}</div>
</main>
);
}
import { registerIcons } from '@hypothesis/frontend-shared';
import { render } from 'preact';
import PlaygroundApp from './PlaygroundApp';
import sidebarIcons from '../../src/sidebar/icons';
registerIcons(sidebarIcons);
const container = document.querySelector('#app');
render(<PlaygroundApp />, container);
import { useEffect, useMemo, useState } from 'preact/hooks';
function routeFromCurrentUrl(baseUrl) {
return location.pathname.slice(baseUrl.length);
}
export function useRoute(baseUrl, routes) {
const [route, setRoute] = useState(() => routeFromCurrentUrl(baseUrl));
const routeData = useMemo(() => routes.find(r => route.match(r.route)), [
route,
routes,
]);
const title = `${routeData.title}: Hypothesis UI playground`;
useEffect(() => {
document.title = title;
}, [title]);
useEffect(() => {
const popstateListener = () => {
setRoute(routeFromCurrentUrl(baseUrl));
};
window.addEventListener('popstate', popstateListener);
return () => {
window.removeEventListener('popstate', popstateListener);
};
}, [baseUrl]);
const navigate = (event, route) => {
event.preventDefault();
history.pushState({}, title, baseUrl + route);
setRoute(route);
};
return [routeData, navigate];
}
......@@ -101,6 +101,12 @@ const appBundles = [
entry: './src/annotator/index',
transforms: ['babel'],
},
{
// A web app to assist with testing UI components.
name: 'ui-playground',
entry: './dev-server/ui-playground/index',
transforms: ['babel'],
},
];
// Polyfill bundles. Polyfills are grouped into "sets" (one bundle per set)
......@@ -170,13 +176,16 @@ gulp.task(
);
const cssBundles = [
// H
// Hypothesis client
'./src/styles/annotator/annotator.scss',
'./src/styles/annotator/pdfjs-overrides.scss',
'./src/styles/sidebar/sidebar.scss',
// Vendor
'./node_modules/katex/dist/katex.min.css',
// Development tools
'./src/styles/ui-playground/ui-playground.scss',
];
gulp.task('build-css', function () {
......
$title-font: 20pt;
$background-color: #ededed;
// Utilities
.u-center {
align-self: center;
}
// Component styles
.PlaygroundApp {
display: flex;
flex-direction: row;
max-width: 600px;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
min-height: 90vh;
}
.PlaygroundApp__sidebar {
display: flex;
flex-direction: column;
padding: 10px;
background-color: $background-color;
border-radius: 3px;
margin-right: 10px;
}
.PlaygroundApp__nav-link {
padding: 5px;
font-size: 12pt;
&:hover {
background-color: #ddd;
}
}
.PlaygroundApp__content {
flex-grow: 1;
}
.heading {
font-size: $title-font;
border-bottom: 2px solid $background-color;
margin-bottom: 20px;
}
// Element styles
body {
font-family: sans-serif;
}
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