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

🛠 Migrate to MyST as a React Component #176

Merged
merged 3 commits into from
Jul 14, 2023
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
10 changes: 10 additions & 0 deletions .changeset/brave-poems-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'myst-to-react': minor
'myst-demo': minor
'@myst-theme/providers': minor
'@myst-theme/diagrams': minor
'@myst-theme/jupyter': minor
'@myst-theme/site': minor
---

Update to myst renderer as a react component
42 changes: 40 additions & 2 deletions docs/stories/0-Introduction.stories.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Meta } from '@storybook/addon-docs';
import { Meta, Story, Canvas } from '@storybook/addon-docs';
import { FrontmatterBlock } from '@myst-theme/frontmatter';
import { Admonition } from 'myst-to-react';
import { ThemeProvider } from '@myst-theme/providers';
import { MyST, Admonition, DEFAULT_RENDERERS } from 'myst-to-react';

<Meta title="Components/Introduction" />

Expand Down Expand Up @@ -30,3 +31,40 @@ The library also has a number of UI-providers that help coordinate interactions
We can also use these components in this documentation, which is going to be great. 🚀

</Admonition>

## Using `MyST` Component

```tsx
import { ThemeProvider } from '@myst-theme/providers';
import { MyST, DEFAULT_RENDERERS } from 'myst-to-react';

function MyComponent({ node }) {
return (
<ThemeProvider renderers={DEFAULT_RENDERERS}>
<MyST ast={node.children} />
</ThemeProvider>
);
}
```

export const { text, abbreviation } = DEFAULT_RENDERERS;

export const Template = (args) => (
<ThemeProvider renderers={{ text, abbreviation }}>
<MyST ast={JSON.parse(args.ast)} />
</ThemeProvider>
);

This example creates an abbreviation with MyST, using a simple AST node.

<Canvas withToolbar>
<Story
inline
name="Simple MyST Demo from AST"
args={{
ast: `{ "type": "abbreviation", "title": "Markedly Structured Text", "children": [{ "type": "text", "value": "MyST" }] }`,
}}
>
{Template.bind({})}
</Story>
</Canvas>
4 changes: 2 additions & 2 deletions packages/diagrams/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ export function MermaidRenderer({ id, value }: { value: string; id: string }) {
);
}

export const MermaidNodeRenderer: NodeRenderer = (node) => {
return <MermaidRenderer key={node.key} id={node.key} value={node.value} />;
export const MermaidNodeRenderer: NodeRenderer = ({ node }) => {
return <MermaidRenderer id={node.key} value={node.value} />;
};
14 changes: 5 additions & 9 deletions packages/jupyter/src/embed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { JupyterIcon } from '@scienceicons/react/24/solid';
import { select } from 'unist-util-select';
import { useLinkProvider, useBaseurl, withBaseurl } from '@myst-theme/providers';
import { MyST } from 'myst-to-react';

function EmbedWithControls({
outputKey,
Expand Down Expand Up @@ -73,17 +74,12 @@ function EmbedWithControls({
);
}

export function Embed(node: GenericNode, children: React.ReactNode) {
export function Embed({ node }: { node: GenericNode }) {
const output = select('output', node) as GenericNode;
if (!output) return <>{children}</>;
if (!output) return <MyST ast={node.children} />;
return (
<EmbedWithControls
key={node.key}
outputKey={output.key}
title={node.source?.title}
url={node.source?.url}
>
{children}
<EmbedWithControls outputKey={output.key} title={node.source?.title} url={node.source?.url}>
<MyST ast={node.children} />
</EmbedWithControls>
);
}
4 changes: 1 addition & 3 deletions packages/jupyter/src/output.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,9 @@ function JupyterOutput({
);
}

export function Output(node: GenericNode) {
// Note, NodeRenderer's can't have hooks in it directly!
export function Output({ node }: { node: GenericNode }) {
return (
<JupyterOutput
key={node.key}
nodeKey={node.key}
nodeType={node.type}
identifier={node.identifier}
Expand Down
22 changes: 9 additions & 13 deletions packages/myst-demo/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { VFile } from 'vfile';
import type { LatexResult } from 'myst-to-tex'; // Only import the type!!
import type { VFileMessage } from 'vfile-message';
import yaml from 'js-yaml';
import type { References } from 'myst-common';
import type { GenericNode, References } from 'myst-common';
import { SourceFileKind } from 'myst-common';
import type { DocxResult } from 'myst-to-docx';
import { validatePageFrontmatter } from 'myst-frontmatter';
import type { PageFrontmatter } from 'myst-frontmatter';
import type { NodeRenderer } from '@myst-theme/providers';
import { useNodeRenderers, ReferencesProvider } from '@myst-theme/providers';
import { CopyIcon, CodeBlock, useParse } from 'myst-to-react';
import { ReferencesProvider } from '@myst-theme/providers';
import { CopyIcon, CodeBlock, MyST } from 'myst-to-react';
import React, { useEffect, useRef, useState } from 'react';
import classnames from 'classnames';
import ExclamationTriangleIcon from '@heroicons/react/24/outline/ExclamationTriangleIcon';
Expand Down Expand Up @@ -143,7 +143,7 @@ async function parse(
})
.stringify(mdast as any, jatsFile).result
: 'Problem loading myst-to-jats';
const content = useParse(mdast as any, options?.renderers);

return {
frontmatter,
yaml: mdastString,
Expand All @@ -153,7 +153,6 @@ async function parse(
texWarnings: texFile.messages,
jats: jats,
jatsWarnings: jatsFile.messages,
content,
warnings: file.messages,
};
}
Expand All @@ -175,7 +174,6 @@ export function MySTRenderer({
numbering?: any;
className?: string;
}) {
const renderers = useNodeRenderers();
const area = useRef<HTMLTextAreaElement | null>(null);
const [text, setText] = useState<string>(value.trim());
const [references, setReferences] = useState<References>({});
Expand All @@ -187,15 +185,14 @@ export function MySTRenderer({
const [jats, setJats] = useState<string>('Loading...');
const [jatsWarnings, setJatsWarnings] = useState<VFileMessage[]>([]);
const [warnings, setWarnings] = useState<VFileMessage[]>([]);
const [content, setContent] = useState<React.ReactNode>(<p>{value}</p>);
const [previewType, setPreviewType] = useState('DEMO');

useEffect(() => {
const ref = { current: true };
parse(
text,
{ numbering },
{ renderers, removeHeading: !!TitleBlock, jats: { fullArticle: !!TitleBlock } },
{ removeHeading: !!TitleBlock, jats: { fullArticle: !!TitleBlock } },
).then((result) => {
if (!ref.current) return;
setFrontmatter(result.frontmatter);
Expand All @@ -206,13 +203,12 @@ export function MySTRenderer({
setTexWarnings(result.texWarnings);
setJats(result.jats);
setJatsWarnings(result.jatsWarnings);
setContent(result.content);
setWarnings(result.warnings);
});
return () => {
ref.current = false;
};
}, [text, renderers]);
}, [text]);

useEffect(() => {
if (!area.current) return;
Expand Down Expand Up @@ -315,7 +311,7 @@ export function MySTRenderer({
<>
<ReferencesProvider references={references} frontmatter={frontmatter}>
{TitleBlock && <TitleBlock frontmatter={frontmatter}></TitleBlock>}
{content}
<MyST ast={references.article?.children as GenericNode[]} />
</ReferencesProvider>
</>
)}
Expand Down Expand Up @@ -359,6 +355,6 @@ export function MySTRenderer({
);
}

export const MystDemoRenderer: NodeRenderer = (node) => {
return <MySTRenderer key={node.key} value={node.value} numbering={node.numbering} />;
export const MystDemoRenderer: NodeRenderer = ({ node }) => {
return <MySTRenderer value={node.value} numbering={node.numbering} />;
};
16 changes: 10 additions & 6 deletions packages/myst-to-react/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ npm install myst-to-react

## Simple example

```typescript
import { useParse, DEFAULT_RENDERERS } from 'myst-to-react';

function MyComponent({ node, renderers = DEFAULT_RENDERERS }) {
const children = useParse(node, renderers);
return <div id={id}>{children}</div>;
```tsx
import { ThemeProvider } from '@myst-theme/providers';
import { MyST, DEFAULT_RENDERERS } from 'myst-to-react';

function MyComponent({ node }) {
return (
<ThemeProvider renderers={DEFAULT_RENDERERS}>
<MyST ast={node.children} />
</ThemeProvider>
);
}
```
28 changes: 28 additions & 0 deletions packages/myst-to-react/src/MyST.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useNodeRenderers } from '@myst-theme/providers';
import type { GenericNode } from 'myst-common';

function DefaultComponent({ node }: { node: GenericNode }) {
if (!node.children) return <span>{node.value}</span>;
return (
<div>
<MyST ast={node.children} />
</div>
);
}

export function MyST({ ast }: { ast?: GenericNode | GenericNode[] }) {
const renderers = useNodeRenderers();
if (!ast || ast.length === 0) return null;
if (!Array.isArray(ast)) {
const Component = renderers[ast.type] ?? DefaultComponent;
return <Component key={ast.key} node={ast} />;
}
return (
<>
{ast?.map((node) => {
const Component = renderers[node.type] ?? DefaultComponent;
return <Component key={node.key} node={node} />;
})}
</>
);
}
20 changes: 10 additions & 10 deletions packages/myst-to-react/src/admonitions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import XCircleIcon from '@heroicons/react/24/solid/XCircleIcon';
import BoltIcon from '@heroicons/react/24/solid/BoltIcon';
import ChevronRightIcon from '@heroicons/react/24/solid/ChevronRightIcon';
import classNames from 'classnames';
import { MyST } from './MyST';
import type { GenericNode } from 'myst-common';
// import { AdmonitionKind } from 'myst-common';

// TODO: get this from myst-spec?
Expand Down Expand Up @@ -105,8 +107,8 @@ function AdmonitionIcon({ kind, className }: { kind: AdmonitionKind; className?:
return <InformationCircleIcon className={cn} />;
}

export const AdmonitionTitle: NodeRenderer<AdmonitionTitleSpec> = (node, children) => {
return children;
export const AdmonitionTitle: NodeRenderer<AdmonitionTitleSpec> = ({ node }) => {
return <MyST ast={node.children} />;
};

const WrapperElement = ({
Expand Down Expand Up @@ -220,28 +222,26 @@ export function Admonition({
);
}

export const AdmonitionRenderer: NodeRenderer<AdmonitionSpec> = (node, children) => {
const [title, ...rest] = children as any[];
export const AdmonitionRenderer: NodeRenderer<AdmonitionSpec> = ({ node }) => {
const [title, ...rest] = node.children as GenericNode[];
const classes = getClasses(node.class);
const { kind, color } = getFirstKind({ kind: node.kind, classes });
const isDropdown = classes.includes('dropdown');
const isSimple = classes.includes('simple');
const hideIcon = (node as any).icon === false;
const hideIcon = node.icon === false;

const useTitle = node.children?.[0].type === 'admonitionTitle';
const useTitle = title?.type === 'admonitionTitle';

return (
<Admonition
key={node.key}
title={useTitle ? title : undefined}
title={useTitle ? <MyST ast={[title]} /> : undefined}
kind={kind}
color={color}
dropdown={isDropdown}
simple={isSimple}
hideIcon={hideIcon}
>
{!useTitle && title}
{rest}
{useTitle ? <MyST ast={rest} /> : <MyST ast={node.children} />}
</Admonition>
);
};
Expand Down
Loading