diff --git a/src/pattern-library/components/Library.js b/src/pattern-library/components/Library.js
new file mode 100644
index 000000000..ec76e59e9
--- /dev/null
+++ b/src/pattern-library/components/Library.js
@@ -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:
+ *
+ *
+ * Any content you want on the page.
+ * More content: it can be any valid `ComponentChildren`
+ *
+ *
+ * The `Elephant` component is used to render information about elephant
+ * personalities.
+ *
+ * You can change the color of your elephant.
+ *
+ *
+ *
+ *
+ * // More Examples if desired
+ *
+ *
+ * // more Patterns if desired...
+ *
+ */
+
+/**
+ * Render a pattern-library page.
+ *
+ * @param {LibraryBaseProps} props
+ */
+function Page({ children, title }) {
+ return (
+
+ );
+}
+
+/**
+ * Render info about a single pattern (or component) on a pattern-library page.
+ *
+ * @param {LibraryBaseProps} props
+ */
+function Pattern({ children, title }) {
+ return (
+
+ );
+}
+
+/**
+ * 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 (
+
+
+ {title &&
{title}
}
+ {notDemos}
+
+
{demos}
+
+ );
+}
+
+/**
+ * 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 (
+
+
+ {jsxToString(child)}
+
+
+ );
+ });
+ return (
+
+
+ setVisibleTab('demo')}
+ pressed={visibleTab === 'demo'}
+ variant="dark"
+ >
+ Demo
+
+ {withSource && (
+ setVisibleTab('source')}
+ pressed={visibleTab === 'source'}
+ variant="dark"
+ >
+ Source
+
+ )}
+
+
+ {visibleTab === 'demo' && (
+
+ )}
+ {visibleTab === 'source' && (
+
+ )}
+
+
+ );
+}
+
+export default {
+ Page,
+ Pattern,
+ Example,
+ Demo,
+};
diff --git a/src/pattern-library/components/patterns/ContainerComponents.js b/src/pattern-library/components/patterns/ContainerComponents.js
index b4a22332e..bc7a1948b 100644
--- a/src/pattern-library/components/patterns/ContainerComponents.js
+++ b/src/pattern-library/components/patterns/ContainerComponents.js
@@ -1,66 +1,70 @@
-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 (
-
-
-
- The Frame
component pattern provides a framed layout with
- padding and vertical spacing of children.
-
-
-
+
+
+
+
+ The Frame
component renders content inside of a{' '}
+ frame
design pattern.
+
+
This content is inside of a frame.
This content is inside of a frame.
-
-
-
+
+
+
-
+
The Card
component pattern provides a card-like layout
- that builds on Frame
.
+ using the card
pattern.
-
-
+
+
This content is inside of a card.
This content is inside of a card.
-
-
-
+
+
+
+
+ This example shows overriding the background color of a{' '}
+ Card
using a utility class.
+
+
This content is inside of a card.
This content is inside of a card.
-
-
-
+
+
+
-
+
The Actions
component pattern lays out actions (buttons).
-
-
+
+
+
Cancel
Maybe
OK
-
-
+
+
+
+
+
This is one option
This is another option
@@ -68,9 +72,9 @@ export default function ContainerComponents() {
This is the best option
-
-
-
-
+
+
+
+
);
}
diff --git a/styles/pattern-library.scss b/styles/pattern-library.scss
index 91ef24182..d44438035 100644
--- a/styles/pattern-library.scss
+++ b/styles/pattern-library.scss
@@ -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';
@@ -29,7 +32,6 @@ h3 {
font-size: 1.125em;
font-weight: normal;
font-style: italic;
- margin: 1em 0;
}
h4 {
@@ -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);
+ }
+}