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
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.
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
Import TypeScript definitions for MDX
, *.md
and *.mdx
:
import 'scoobie/types';
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.)
Scoobie optionally supports simple, source-controlled diagrams via mermaid.
This requires the mermaid
configuration option to be set on ScoobieWebpackPlugin, and the mermaid-isomorphic
peer dependency installed.
A playwright installattion of chromium is required, which can be done by installing playwright
and executing playwright install chromium
.
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
```
Some Mermaid diagrams allow for YAML frontmatter. Scoobie has extended this by allowing a deep merge of the Mermaid configuration
with a special overrides
key in the frontmatter. This could be useful for diagram-specific configuration that is not otherwise easy to set.
```mermaid
---
overrides:
gantt:
useWidth: 500
---
gantt
title A Gantt Diagram
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
```
Anchor slugs are automatically generated for h1–h6:
# My Little Heading1
<!-- #my-little-heading1 -->
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')
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)
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 Text
s, 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> |
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>
);
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>
);
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>
);
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>
);
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>
);
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>
);
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>
);
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>
);
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>
);
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>
);
<tr>
component for use with Table.
Row children are flattened then wrapped with <td>
s.
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.)
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>
);
Scoobie distributes some vanilla-extract styles via scoobie/styles
submodules.
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>
);
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>
);
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.
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,
},
}),
],
}),
};
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.
Re-export of webpack-merge for convenience.