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 ( +
+

{title}

+ {children} +
+ ); +} + +/** + * Render info about a single pattern (or component) on a pattern-library page. + * + * @param {LibraryBaseProps} props + */ +function Pattern({ children, title }) { + return ( +
+

{title}

+ {children} +
+ ); +} + +/** + * 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' && ( +
    +
    {children}
    +
    + )} + {visibleTab === 'source' && ( +
    +
      {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); + } +}