Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Library layout components, simplified #144

Merged
merged 2 commits into from
Jul 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼

@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);
}
}