Skip to content

Commit

Permalink
Merge pull request #144 from hypothesis/simplified-library-layout
Browse files Browse the repository at this point in the history
Library layout components, simplified
  • Loading branch information
lyzadanger authored Jul 15, 2021
2 parents ca8b48f + f0ef4b4 commit cdb3a17
Show file tree
Hide file tree
Showing 3 changed files with 285 additions and 39 deletions.
162 changes: 162 additions & 0 deletions src/pattern-library/components/Library.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { toChildArray } from 'preact';
import { useState } from 'preact/hooks';

import { LabeledButton } from '../../components/buttons';

import { jsxToString } from '../util/jsx-to-string';

/**
* @typedef LibraryBaseProps
* @prop {import("preact").ComponentChildren} [children]
* @prop {string} [title]
*
*/

/**
* Components for rendering patterns, examples and demos in the pattern-library
* page. A pattern-library Page contains Patterns, which in turn contain
* Examples. An Example _may_ contain one or more Demos. Child content (markup)
* may also be rendered in these components, as desired.
*
* Example of structure:
*
* <Library.Page title="Elephants">
* <p>Any content you want on the page.</p>
* More content: it can be any valid `ComponentChildren`
*
* <Library.Pattern title="Elephant">
* <p>The `Elephant` component is used to render information about elephant
* personalities.</p>
* <Library.Example title="Colored elephants">
* <p>You can change the color of your elephant.</p>
* <Library.Demo withSource>
* <Elephant color="pink" />
* </Library.Demo>
* </Library.Example>
* // More Examples if desired
* </Library.Pattern>
*
* // more Patterns if desired...
* </Library.Page>
*/

/**
* Render a pattern-library page.
*
* @param {LibraryBaseProps} props
*/
function Page({ children, title }) {
return (
<section className="LibraryPage">
<h1 className="LibraryPage__heading">{title}</h1>
{children}
</section>
);
}

/**
* Render info about a single pattern (or component) on a pattern-library page.
*
* @param {LibraryBaseProps} props
*/
function Pattern({ children, title }) {
return (
<section className="LibraryPattern">
<h2 className="LibraryPattern__heading">{title}</h2>
{children}
</section>
);
}

/**
* Render example content and optional Demo(s) for a pattern.
*
* @param {LibraryBaseProps} props
*/
function Example({ children, title }) {
const kids = toChildArray(children);

// Extract Demo components out of any children
const demos = kids.filter(
kid => typeof kid === 'object' && kid?.type === Demo
);
// And everything else that is not a demo...
const notDemos = kids.filter(kid => !demos.includes(kid));

return (
<div className="LibraryExample">
<div className="LibraryExample__content">
{title && <h3 className="LibraryExample__heading">{title}</h3>}
{notDemos}
</div>
<div className="LibraryExample__content">{demos}</div>
</div>
);
}

/**
* Render a "Demo", with optional source. This will render the children as
* provided in a tabbed container. If `withSource` is `true`, the JSX source
* of the children will be provided in a separate "Source" tab from the
* rendered Demo content.
*
* @typedef DemoProps
* @prop {import("preact").ComponentChildren} [children]
* @prop {boolean} [withSource=false] - Should the demo also render the source?
* When true, a "Source" tab will be rendered, which will display the JSX
* source of the Demo's children
* @prop {object} [style] - Inline styles to apply to the demo container
*/
function Demo({ children, withSource = false, style = {} }) {
const [visibleTab, setVisibleTab] = useState('demo');
const source = toChildArray(children).map((child, idx) => {
return (
<li key={idx}>
<code>
<pre>{jsxToString(child)}</pre>
</code>
</li>
);
});
return (
<div className="LibraryDemo">
<div className="LibraryDemo__tabs">
<LabeledButton
onClick={() => setVisibleTab('demo')}
pressed={visibleTab === 'demo'}
variant="dark"
>
Demo
</LabeledButton>
{withSource && (
<LabeledButton
onClick={() => setVisibleTab('source')}
pressed={visibleTab === 'source'}
variant="dark"
>
Source
</LabeledButton>
)}
</div>
<div className="LibraryDemo__container">
{visibleTab === 'demo' && (
<div className="LibraryDemo__demo" style={style}>
<div className="LibraryDemo__demo-content">{children}</div>
</div>
)}
{visibleTab === 'source' && (
<div className="LibraryDemo__source">
<ul>{source}</ul>
</div>
)}
</div>
</div>
);
}

export default {
Page,
Pattern,
Example,
Demo,
};
80 changes: 42 additions & 38 deletions src/pattern-library/components/patterns/ContainerComponents.js
Original file line number Diff line number Diff line change
@@ -1,76 +1,80 @@
import { Frame, Card, Actions } from '../../../components/containers';
import { LabeledButton } from '../../../components/buttons';
import { Frame, Card, Actions } from '../../..';
import { LabeledButton } from '../../..';

import {
PatternPage,
Pattern,
PatternExamples,
PatternExample,
} from '../PatternPage';
import Library from '../Library';

export default function ContainerComponents() {
return (
<PatternPage title="Containers">
<Pattern title="Frame">
<p>
The <code>Frame</code> component pattern provides a framed layout with
padding and vertical spacing of children.
</p>
<PatternExamples>
<PatternExample details="Laying out content in a Frame">
<Library.Page title="Containers">
<Library.Pattern title="Frame">
<Library.Example title="Laying out content in a Frame">
<p>
The <code>Frame</code> component renders content inside of a{' '}
<code>frame</code> design pattern.
</p>
<Library.Demo withSource>
<Frame>
<div>This content is inside of a frame.</div>
<div>This content is inside of a frame.</div>
</Frame>
</PatternExample>
</PatternExamples>
</Pattern>
</Library.Demo>
</Library.Example>
</Library.Pattern>

<Pattern title="Card">
<Library.Pattern title="Card">
<p>
The <code>Card</code> component pattern provides a card-like layout
that builds on <code>Frame</code>.
using the <code>card</code> pattern.
</p>
<PatternExamples>
<PatternExample details="Laying out content in a Card">
<Library.Example title="Laying out content in a Card">
<Library.Demo withSource>
<Card>
<div>This content is inside of a card.</div>
<div>This content is inside of a card.</div>
</Card>
</PatternExample>

<PatternExample details="Example of overriding background color">
</Library.Demo>
</Library.Example>
<Library.Example title="Overriding styles">
<p>
This example shows overriding the background color of a{' '}
<code>Card</code> using a utility class.
</p>
<Library.Demo withSource>
<Card classes="hyp-u-bg-color--grey-3">
<div>This content is inside of a card.</div>
<div>This content is inside of a card.</div>
</Card>
</PatternExample>
</PatternExamples>
</Pattern>
</Library.Demo>
</Library.Example>
</Library.Pattern>

<Pattern title="Actions">
<Library.Pattern title="Actions">
<p>
The <code>Actions</code> component pattern lays out actions (buttons).
</p>
<PatternExamples>
<PatternExample details="Laying out buttons with Actions">

<Library.Example title="Laying out buttons with Actions">
<Library.Demo withSource>
<Actions>
<LabeledButton>Cancel</LabeledButton>
<LabeledButton>Maybe</LabeledButton>
<LabeledButton variant="primary">OK</LabeledButton>
</Actions>
</PatternExample>
<PatternExample details="Laying out buttons vertically with Actions">
</Library.Demo>
</Library.Example>

<Library.Example title="Laying out buttons vertically with Actions">
<Library.Demo withSource>
<Actions direction="column">
<LabeledButton>This is one option</LabeledButton>
<LabeledButton>This is another option</LabeledButton>
<LabeledButton variant="primary">
This is the best option
</LabeledButton>
</Actions>
</PatternExample>
</PatternExamples>
</Pattern>
</PatternPage>
</Library.Demo>
</Library.Example>
</Library.Pattern>
</Library.Page>
);
}
82 changes: 81 additions & 1 deletion styles/pattern-library.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
@use 'base';
@use 'variables' as var;
@use './mixins/layout';
@use './mixins/atoms';
@use './mixins/patterns/containers';

@use 'index'; // component styles
@use 'patterns';
Expand Down Expand Up @@ -29,7 +32,6 @@ h3 {
font-size: 1.125em;
font-weight: normal;
font-style: italic;
margin: 1em 0;
}

h4 {
Expand Down Expand Up @@ -217,3 +219,81 @@ body {
background-color: var.$color-grey-1;
}
}

$-library-vertical-spacing: 7;
.LibraryPage {
// Make sure the content width is not too terribly wide: it gets hard to read
max-width: 75rem;
@include layout.vertical-spacing($size: $-library-vertical-spacing);
// A little breathing room at the bottom of the page, as we don't have
// a footer.
// TODO: Consolidate padding after removal in future of `PlaygroundApp` styling
@include layout.padding($side: top, $size: 5);
@include layout.padding($side: bottom, $size: 7);

&__heading {
font-size: 1.75em;
font-weight: bold;
}
}

.LibraryPattern {
@include layout.vertical-spacing($size: $-library-vertical-spacing);
@include atoms.border(top);
// The following balances out spacing above and below border
@include layout.padding($size: $-library-vertical-spacing, $side: top);

&__heading {
font-size: 1.25rem;
width: 100%;
border-left: 6px solid var.$color-brand;
font-weight: bold;
padding-left: 0.5rem;
}
}

.LibraryExample {
// Narrower screen/default shows single column
@include layout.vertical-spacing($size: $-library-vertical-spacing);

// Wider screen shows description and demo side-by-side
@media screen and (min-width: 60em) {
@include layout.row;
// Turn off vertical-spacing, as content is side-by-side
@include layout.vertical-spacing($size: 0);
@include layout.horizontal-spacing($size: 6);

.LibraryExample__content {
width: 50%;
@include layout.vertical-spacing($size: $-library-vertical-spacing);
}
}
}

.LibraryDemo {
&__tabs {
@include layout.row;
@include layout.horizontal-spacing;
// Pull the buttons down into the top of the `container` element below,
// so that they look like tabs
margin-bottom: -1px;
}

&__container {
@include containers.frame;
@include layout.row($align: center, $justify: center);
// Make the demo take up at least a minimal amount of vertical space
min-height: 8rem;
background-color: var.$color-grey-1;
}

&__source,
&__demo {
@include containers.frame;
width: 100%;
}

&__demo-content {
@include layout.row($align: center, $justify: center);
}
}

0 comments on commit cdb3a17

Please sign in to comment.