Commit 599df082 authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Update playground app

Add some pattern structure to the playground app and a
placeholder "Menu" demo
parent d7a2a4e8
......@@ -4,7 +4,6 @@
<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>
......
import { toChildArray } from 'preact';
import { jsxToString } from '../util/jsx-to-string';
/**
* Components to render patterns and examples. Typical structure of a "Demo"
* page might be:
*
* <PatternPage title="Zoo Animals">
* <Pattern title="Elephant">
* <p>Everyone loves an elephant.</p>
*
* <PatternExamples title="Colored elephants">
* <PatternExample details="Pink elephant">
* <Elephant color="pink" />
* </PatternExample>
* <PatternExample details="Blue elephant">
* <Elephant color="blue" />
* </PatternExample>
* </PatternExamples>
*
* <PatternExamples title="Patterned elephants">
* <PatternExample details="Spotted elephant">
* <Elephant variant="spotted" />
* </PatternExample>
* </PatternExamples>
* </Pattern>
*
* <Pattern title="Monkeys">
* <PatternExamples>
* <PatternExample details="Your basic monkey">
* <Monkey />
* </PatternExample>
* </PatternExamples>
* </Pattern>
* </PatternPage>
*/
/**
* @typedef PatternPageProps
* @prop {import("preact").ComponentChildren} children
* @prop {string} title
*/
/**
* Render a page of patterns
* @param {PatternPageProps} props
*/
export function PatternPage({ children, title }) {
return (
<section className="PatternPage">
<h1>{title}</h1>
<div className="PatternPage__content">{children}</div>
</section>
);
}
/**
* @typedef PatternProps
* @prop {import("preact").ComponentChildren} children
* @prop {string} title
*/
/**
* A pattern and its examples
* @param {PatternProps} props
*/
export function Pattern({ children, title }) {
return (
<section className="Pattern">
<h2>{title}</h2>
{children}
</section>
);
}
/**
* @typedef PatternExamplesProps
* @prop {import("preact").ComponentChildren} children
* @prop {string} [title]
*/
/**
* Tabular layout of one or more `PatternExample` components, with optional
* title.
*
* @param {PatternExamplesProps} props
*/
export function PatternExamples({ children, title }) {
return (
<table className="PatternExamples">
{title && (
<tr>
<th colSpan="3">
<h3>{title}</h3>
</th>
</tr>
)}
<tr>
<th>Example</th>
<th>Details</th>
<th>Source</th>
</tr>
{children}
</table>
);
}
/**
* @typedef PatternExampleProps
* @prop {import("preact").ComponentChildren} children - Example source
* @prop {string} details - Details about the pattern example
*/
/**
* Tabular layout of a single example. Will render its `children` as the
* example, as well as the source JSX of its `children`
*
* @param {PatternExampleProps} props
*/
export function PatternExample({ children, details }) {
const source = toChildArray(children).map((child, idx) => {
return (
<li key={idx}>
<code>
<pre>{jsxToString(child)}</pre>
</code>
</li>
);
});
return (
<tr>
<td className="PatternExample__example">{children}</td>
<td>{details}</td>
<td>
<ul>{source}</ul>
</td>
</tr>
);
}
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 MenuPatterns from './patterns/MenuPatterns';
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>
);
}
import { useRoute } from '../router';
function HomeRoute() {
return (
......@@ -54,15 +19,10 @@ const routes = [
title: 'Home',
component: HomeRoute,
},
{
route: '/button',
title: 'Button',
component: ButtonDemo,
},
{
route: '/menu',
title: 'Menu',
component: MenuDemo,
component: MenuPatterns,
},
];
......@@ -83,23 +43,25 @@ export default function PlaygroundApp() {
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}
<div className="PlaygroundApp__sidebar-home">
<a href={baseUrl} onClick={e => navigate(e, '/')}>
<SvgIcon name="logo" />
</a>
))}
</div>
<ul>
{demoRoutes.map(c => (
<li key={c.route}>
<a
className="PlaygroundApp__nav-link"
key={c.route}
href={c.route}
onClick={e => navigate(e, c.route)}
>
{c.title}
</a>
</li>
))}
</ul>
</div>
<div className="PlaygroundApp__content">{content}</div>
</main>
......
import {
PatternPage,
Pattern,
PatternExamples,
PatternExample,
} from '../PatternPage';
import Menu from '../../../../src/sidebar/components/Menu';
import MenuItem from '../../../../src/sidebar/components/MenuItem';
export default function MenuPatterns() {
return (
<PatternPage title="Menu">
<Pattern title="Menu">
<p>A simple Menu usage example</p>
<PatternExamples>
<PatternExample details="Menu">
<Menu label="Edit">
<MenuItem label="Zoom in" />
<MenuItem label="Zoom out" />
<MenuItem label="Undo" />
<MenuItem label="Redo" />
</Menu>
</PatternExample>
</PatternExamples>
</Pattern>
</PatternPage>
);
}
import { registerIcons } from '@hypothesis/frontend-shared';
import { render } from 'preact';
import PlaygroundApp from './PlaygroundApp';
import PlaygroundApp from './components/PlaygroundApp';
import sidebarIcons from '../../src/sidebar/icons';
registerIcons(sidebarIcons);
......
function escapeQuotes(str) {
return str.replace(/"/g, '\\"');
}
function componentName(type) {
if (typeof type === 'string') {
return type;
} else {
return type.displayName || type.name;
}
}
export function jsxToString(vnode) {
if (typeof vnode === 'string' || typeof vnode === 'number') {
return vnode.toString();
} else if (vnode?.type) {
const name = componentName(vnode.type);
// TODO - Add in the `key` prop which is extracted off the props into `vnode.key`.
let propStr = Object.entries(vnode.props)
.map(([name, value]) => {
if (name === 'children') {
return '';
}
// nb. We assume that `value` is something that can easily be stringified
// using `String(value)`.
return `${name}="${escapeQuotes(String(value))}"`;
})
.join(' ')
.trim();
if (propStr.length > 0) {
propStr = ' ' + propStr;
}
const children = vnode.props.children;
if (children) {
// TODO - Be smart about splitting children over multiple lines
// and indentation
const childrenStr = Array.isArray(children)
? children.map(jsxToString).join('\n')
: jsxToString(children);
return `<${name}${propStr}>\n${childrenStr}\n</${name}>`;
} else {
// No children - use a self-closing tag.
return `<${name}${propStr}/>`;
}
} else {
return '';
}
}
@use '../reset';
@use '../sidebar/elements';
@use '../mixins/layout';
@use '../variables' as var;
@use '../sidebar/components/Menu';
@use '../sidebar/components/MenuItem';
$title-font: 20pt;
$background-color: #ededed;
$background-color: #9b9595;
body {
font-size: 100%;
}
h1 {
font-size: 1.75em;
font-weight: bold;
}
h2 {
font-size: 1.5em;
width: 100%;
border-left: 6px solid var.$color-brand;
font-weight: bold;
padding-left: 0.5em;
}
h3 {
width: 100%;
font-size: 1.125em;
font-weight: normal;
font-style: italic;
}
pre {
margin: 0;
}
// Utilities
.u-center {
......@@ -8,45 +44,114 @@ $background-color: #ededed;
// Component styles
.PlaygroundApp {
display: flex;
flex-direction: row;
display: grid;
grid-template-areas:
'navigation'
'content';
max-width: 600px;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
min-height: 90vh;
}
&__content {
padding: var.$layout-space;
}
&__sidebar {
grid-area: 'navigation';
max-height: 20em;
overflow: scroll;
background-color: var.$grey-2;
}
&__sidebar-home {
text-align: center;
padding: var.$layout-space;
}
.PlaygroundApp__sidebar {
display: flex;
flex-direction: column;
padding: 10px;
background-color: $background-color;
border-radius: 3px;
margin-right: 10px;
&__content {
grid-area: 'content';
}
}
.PlaygroundApp__nav-link {
padding: 5px;
font-size: 12pt;
width: 100%;
padding: var.$layout-space;
font-size: var.$font-size--subheading;
border-left: 5px solid transparent;
&:hover {
background-color: #ddd;
background-color: var.$grey-3;
border-left: 5px solid var.$color-brand;
}
}
.PlaygroundApp__content {
flex-grow: 1;
// Temporarily enforce the body font size of the client to demonstrate how
// components would scale in-situ
.PatternPage {
font-size: 13px;
&__content {
padding: 2em 0;
}
}
.heading {
font-size: $title-font;
border-bottom: 2px solid $background-color;
margin-bottom: 20px;
.Pattern {
margin-bottom: 4em;
p {
margin: 1em;
}
}
.PatternExamples {
border: 1px solid var.$grey-3;
border-collapse: collapse;
width: 100%;
margin-bottom: 2em;
th,
td {
padding: 0.5em;
}
th {
background-color: var.$grey-1;
border-bottom: 1px solid var.$grey-3;
text-align: left;
}
td {
padding-top: 10px;
padding-bottom: 10px;
}
tr:nth-child(even) td {
background: var.$grey-0;
}
code {
color: var.$grey-mid;
}
}
.PatternExample {
&__example {
display: flex;
align-items: center;
& > * + * {
margin-left: 1em;
}
}
}
// Element styles
body {
font-family: sans-serif;
}
@media screen and (min-width: 60em) {
.PlaygroundApp {
height: 100vh;
grid-template-columns: 20em auto;
column-gap: 1em;
grid-template-areas: 'navigation content';
&__sidebar {
max-height: none;
}
}
}
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