scoobie

15.3.0 • Public • Published

🧶 Scoobie

GitHub Release GitHub Validate npm package

Component library for SEEK documentation sites.

  • Author content in Markdown files
  • Diagram with mermaid code blocks
  • Render content with Braid styling
  • Integrate with sku

We use this to build developer.seek.com, among other things.

yarn add --exact scoobie

Table of contents

Setup

sku.config.js

Compile Scoobie and bundle your Markdown content with its Webpack plugin:

const { dangerouslySetWebpackConfig } = require('scoobie/webpack');

module.exports = {
  // ...

  compilePackages: ['scoobie'],
  dangerouslySetWebpackConfig,
};

For detailed usage, see the Webpack reference.

src/render.tsx

Fetch our favourite fonts from our Google overlords, Roboto and Roboto Mono:

import { robotoHtml, robotoMonoHtml } from 'scoobie/typography';

const skuRender: Render<RenderContext> = {
  renderDocument: ({ app, bodyTags, headTags }) => `
    <!DOCTYPE html>
    <html>
      <head>
        <!-- ... -->
        ${robotoHtml}
        ${robotoMonoHtml}
        <!-- ... -->
  `,
};

If you manually manage the Content Security Policy of your site, you can specify the following sources along with the script hashes from typography.ts:

Content-Security-Policy: font-src https://fonts.gstatic.com; script-src 'sha256-...' 'sha256-...'; style-src https://fonts.googleapis.com

src/scoobie.d.ts

Import TypeScript definitions for MDX, *.md and *.mdx:

import 'scoobie/types';

Markdown reference

Getting started

Scoobie’s Markdown support is powered by MDX and custom Remark plugins.

Create your content in .md or .mdx files:

# A normal Markdown heading

Some text...

import { Alert, Text } from 'braid-design-system';

<Alert tone="critical"><Text>And a React component!</Text></Alert>

Import your content into a typical .tsx file:

import React from 'react';

import Content from './Content.mdx';

export const ContentWithPointlessDiv = () => (
  <div>
    <Content />
  </div>
);

Nest your Markdown components within an MdxProvider:

import 'braid-design-system/reset';

import { BraidProvider } from 'braid-design-system';
import apacTheme from 'braid-design-system/themes/apac';
import React from 'react';
import { MdxProvider, ScoobieLink } from 'scoobie';

import { ContentWithPointlessDiv } from './SomeFile.tsx';

export const App = () => (
  <BraidProvider linkComponent={ScoobieLink} theme={apacTheme}>
    <MdxProvider>
      <ContentWithPointlessDiv />
    </MdxProvider>
  </BraidProvider>
);

(See React context to learn more about this pattern.)

Diagrams

Scoobie optionally supports simple, source-controlled diagrams via mermaid.

This requires the mermaid configuration option to be set on ScoobieWebpackPlugin, and @mermaid-js/mermaid-cli to be installed as a peer dependency. From there, the easiest way to get started is to check out the mermaid live editor.

You can use a named code block in Markdown files:

```mermaid Optional title
sequenceDiagram
  autonumber
  participant P as Partner
  participant S as SEEK

  P->>S: Make request
```

Or import an .mmd file like so:

![Diagram](./diagram.mmd 'Optional title')

If you use a separate diagram.mmd file, you can provide additional mermaid configuration with a diagram.mmd.json file in the same directory.

Headings

Anchor slugs are automatically generated for h1–h6:

# My Little Heading1

<!-- #my-little-heading1 -->

Images

Vanilla Markdown image syntax is supported:

![Boo raster](./image.png)

![Woo vector](./image.svg)

Define width and height px constraints by overloading the title:

![Alt text](./image.png '=100x20 Rest of title')

Links

Use a root-relative path to navigate to a route managed by your React app. This will be rendered as a React Router link and won't require a full page refresh.

Make sure to point to the route rather than the source file location:

[👍 like this](/root/relative)

[👎 not this](/src/root/Relative.mdx)

Use a full URL to denote an external link. This will open in a new tab.

[Schema reference](https://developer.seek.com/schema)

Tables

Standard Markdown table syntax is supported:

| Default | Left | Centre | Right |
| ------- | :--- | :----: | ----: |
| x       | x    |   x    |     x |

Use raw HTML to render multiple lines and lists in table cells.

Care must be taken with <p>s; they are mapped to Braid Texts, which have strict semantics. Paragraph tags must be placed around text content and cannot be nested within each other.

| Description             | Example                                                            |
| :---------------------- | :----------------------------------------------------------------- |
| Single-line             | Line                                                               |
| Multi-line              | <p>Line 1</p><p>Line 2</p>                                         |
| Bullets                 | <ul><li><p>Bullet 1</p></li></ul>                                  |
| Multi-line with bullets | <p>Line before</p><ul><li><p>Bullet</p></li></ul><p>Line after</p> |

React API reference

Blockquote

Renders rich quoted content.

import { List, Text } from 'braid-design-system';
import React from 'react';
import { Blockquote } from 'scoobie';

export const MyFirstBlockquote = () => (
  <Blockquote>
    <Text>This is a paragraph.</Text>

    <List>
      <Text>This is a bullet point.</Text>
    </List>
  </Blockquote>
);

CodeBlock

Render lines of code with Prism syntax highlighting.

import React from 'react';
import { CodeBlock } from 'scoobie';

export const MyFirstCodeBlock = () => (
  <CodeBlock language="javascript">console.log('hello, world');</CodeBlock>
);

CopyableText

Render a Text component that copies the children string to clipboard on click.

import React from 'react';
import { CodeBlock } from 'scoobie';

export const MyFirstCopyableText = () => (
  <CopyableText>This gets copied to clipboard.</CopyableText>
);

InlineCode

Render code inline with text.

import { Text } from 'braid-design-system';
import React from 'react';
import { InlineCode } from 'scoobie';

export const MyFirstInlineCode = () => (
  <Text>
    Some text with <InlineCode>InlineCode</InlineCode>!
  </Text>
);

InternalLink

Render an internal link with the same opinions as our MdxProvider:

  • Internal links pass through the v or ScoobieLinkProvider URL parameters for UI version switching

Unlike SmartTextLink, this is not bound to a parent Text as it has no underlying TextLink. It can be used to make complex components navigable rather than just sections of text.

import { Stack, Text } from 'braid-design-system';
import React from 'react';
import { InternalLink } from 'scoobie';

export const SomeComplexLinkElement = () => (
  <InternalLink href="/page#id" reset>
    <Stack space="medium">
      <Text>InternalLink supports complex children.</Text>

      <Text size="small">It is not bound to a parent Text component.</Text>
    </Stack>
  </InternalLink>
);

MdxProvider

Provide a base collection of Braid-styled renderers for child MDX documents.

This should be paired with ScoobieLink for proper internal link rendering.

import { BraidProvider, Card } from 'braid-design-system';
import apacTheme from 'braid-design-system/themes/apac';
import React from 'react';
import { MdxProvider, ScoobieLink } from 'scoobie';

import Content from './Content.mdx';

export const Component = () => (
  <BraidProvider linkComponent={ScoobieLink} theme={apacTheme}>
    <MdxProvider>
      <Card>
        <Content />
      </Card>
    </MdxProvider>
  </BraidProvider>
);

ScoobieLink

Render all underlying links as follows:

  • Internal links pass through the v or ScoobieLinkProvider URL parameters for UI version switching
  • External links open in a new tab
  • Links with a download attribute prompt the user with a file download

This should be supplied to BraidProvider as the custom linkComponent:

import { BraidProvider, TextLink } from 'braid-design-system';
import apacTheme from 'braid-design-system/themes/apac';
import React from 'react';
import { ScoobieLink } from 'scoobie';

export const Component = () => (
  <BraidProvider linkComponent={ScoobieLink} theme={apacTheme}>
    <TextLink href="/root-relative">Internal link</TextLink>
  </BraidProvider>
);

ScoobieLinkProvider

Propagate a custom set of URL parameters on internal links.

import { BraidProvider, TextLink } from 'braid-design-system';
import apacTheme from 'braid-design-system/themes/apac';
import React from 'react';
import { ScoobieLink } from 'scoobie';

export const Component = () => (
  <ScoobieLinkProvider propagateSearchParams={['debug', 'v']}>
    <BraidProvider linkComponent={ScoobieLink} theme={apacTheme}>
      <TextLink href="/root-relative">Internal link</TextLink>
    </BraidProvider>
  </ScoobieLinkProvider>
);

SmartTextLink

Render a text link with the same opinions as our MdxProvider:

  • External links open in a new tab and have an IconNewWindow suffix
import { Stack, Text } from 'braid-design-system';
import React from 'react';
import { SmartTextLink } from 'scoobie';

export const SomeLinks = () => (
  <Text>
    <Stack space="medium">
      <SmartTextLink href="/page#id">Scrolls smoothly</SmartTextLink>

      <SmartTextLink href="https://developer.seek.com/schema">
        Opens in new tab
      </SmartTextLink>
    </Stack>
  </Text>
);

Table

Render an HTML table with the same styling as our MdxProvider:

import React, { Fragment } from 'react';
import { Table, TableRow } from 'scoobie';

export const MyFirstTable = () => (
  <Table header={['Column A', 'Column B']}>
    <TableRow>
      <Fragment>This is body cell A1.</Fragment>
      <Fragment>B1</Fragment>
    </TableRow>

    <TableRow>
      <Fragment>A2</Fragment>
      <Fragment>This is body cell B2.</Fragment>
    </TableRow>
  </Table>
);

TableRow

<tr> component for use with Table.

Row children are flattened then wrapped with <td>s.

TocRenderer

Render headings from an MDX document with a custom function.

import { Stack, TextLink } from 'braid-design-system';
import React from 'react';
import { TocRenderer } from 'scoobie';

import Content from './Content.mdx';

export const PageWithToc = () => (
  <Stack space="medium">
    <TocRenderer document={Content}>
      {(toc) => (
        <Text>
          <Stack space="small">
            {toc.map((item) => (
              <TextLink href={`#${item.id}`} key={item.id}>
                {'|'.repeat(item.level)} {item.children}
              </TextLink>
            ))}
          </Stack>
        </Text>
      )}
    </TocRenderer>

    <Content />
  </Stack>
);

A heading must start at the beginning of its line to be parsed:

# Good

- ## Bad

  - ### Super bad

## Good again

(This can be enforced with markdownlint’s MD023 rule.)

WrapperRenderer

Render an MDX document with a customised wrapper.

This allows you to derive arbitrary components from select parts of the document.

import { Text } from 'braid-design-system';
import React, { Children } from 'react';
import { WrapperRenderer } from 'scoobie';

export const NodeCount = (Document: MDX.Document) => (
  <WrapperRenderer document={Document}>
    {({ children }) => (
      <Text>{Children.toArray(children).length} top-level node(s)</Text>
    )}
  </WrapperRenderer>
);

Styling reference

Scoobie distributes some vanilla-extract styles via scoobie/styles submodules.

code

Render text with the same monospace styling as our CodeBlock:

import { Box } from 'braid-design-system';
import React from 'react';
import { code } from 'scoobie/styles/code.css';

export const MyBox = () => (
  <Box className={code.standard}>
    <Box component="pre">Hello</Box>
  </Box>
);

img

Render an image with the same styling as our MdxProvider:

import React from 'react';
import { img } from 'scoobie/styles/img.css';

export const MySvg = () => (
  <svg className={img}>
    <path />
  </svg>
);

Webpack reference

Scoobie distributes its Webpack config via a scoobie/webpack submodule:

const { ScoobieWebpackPlugin } = require('scoobie/webpack');

Compatibility notes:

  • SVGs cannot be directly imported into JSX as components.

    Consider inlining the SVGs in your JSX instead.

ScoobieWebpackPlugin

A bundle of MDX and image loaders that complement sku's Webpack config.

This needs to be ordered to run after SkuWebpackPlugin:

const { ScoobieWebpackPlugin, merge } = require('scoobie/webpack');

module.exports = {
  // ...

  compilePackages: ['scoobie'],
  dangerouslySetWebpackConfig: (config) =>
    merge(config, {
      plugins: [
        new ScoobieWebpackPlugin({
          // Optional configuration option to enable mermaid support.
          // `@mermaid-js/mermaid-cli` must be installed as a peer dependency.
          // Temporary files are written to `${rootDir}/mermaid`.
          mermaid: {
            rootDir: __dirname,
          },
        }),
      ],
    }),
};

dangerouslySetWebpackConfig

Zero-config option referenced in sku.config.js above.

This slots in on top of sku without much fuss. If you're wrangling other Webpack config and need something more composable, see ScoobieWebpackPlugin.

merge

Re-export of webpack-merge for convenience.

Readme

Keywords

none

Package Sidebar

Install

npm i scoobie

Weekly Downloads

210

Version

15.3.0

License

MIT

Unpacked Size

107 kB

Total Files

77

Last publish

Collaborators

  • seek-oss-ci