diff --git a/packages/docusaurus-playground/src/theme/Admonition/index.js b/packages/docusaurus-playground/src/theme/Admonition/index.js new file mode 100644 index 0000000..cc4218e --- /dev/null +++ b/packages/docusaurus-playground/src/theme/Admonition/index.js @@ -0,0 +1,188 @@ +import React from 'react' +import clsx from 'clsx' +import { ThemeClassNames } from '@docusaurus/theme-common' +import Translate from '@docusaurus/Translate' +import styles from './styles.module.css' +function NoteIcon() { + return ( + + + + ) +} +function TipIcon() { + return ( + + + + ) +} +function DangerIcon() { + return ( + + + + ) +} +function InfoIcon() { + return ( + + + + ) +} +function CautionIcon() { + return ( + + + + ) +} +// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style +const AdmonitionConfigs = { + note: { + infimaClassName: 'secondary', + iconComponent: NoteIcon, + label: ( + + note + + ), + }, + tip: { + infimaClassName: 'success', + iconComponent: TipIcon, + label: ( + + tip + + ), + }, + danger: { + infimaClassName: 'danger', + iconComponent: DangerIcon, + label: ( + + danger + + ), + }, + info: { + infimaClassName: 'info', + iconComponent: InfoIcon, + label: ( + + info + + ), + }, + caution: { + infimaClassName: 'warning', + iconComponent: CautionIcon, + label: ( + + caution + + ), + }, +} +// Legacy aliases, undocumented but kept for retro-compatibility +const aliases = { + secondary: 'note', + important: 'info', + success: 'tip', + warning: 'danger', +} +function getAdmonitionConfig(unsafeType) { + const type = aliases[unsafeType] ?? unsafeType + const config = AdmonitionConfigs[type] + if (config) { + return config + } + console.warn( + `No admonition config found for admonition type "${type}". Using Info as fallback.`, + ) + return AdmonitionConfigs.info +} +// Workaround because it's difficult in MDX v1 to provide a MDX title as props +// See https://github.com/facebook/docusaurus/pull/7152#issuecomment-1145779682 +function extractMDXAdmonitionTitle(children) { + const items = React.Children.toArray(children) + const mdxAdmonitionTitle = items.find( + (item) => + React.isValidElement(item) && + item.props?.mdxType === 'mdxAdmonitionTitle', + ) + const rest = <>{items.filter((item) => item !== mdxAdmonitionTitle)} + return { + mdxAdmonitionTitle, + rest, + } +} +function processAdmonitionProps(props) { + const { mdxAdmonitionTitle, rest } = extractMDXAdmonitionTitle(props.children) + return { + ...props, + title: props.title ?? mdxAdmonitionTitle, + children: rest, + } +} +export default function Admonition(props) { + const { + children, + type, + title, + icon: iconProp, + } = processAdmonitionProps(props) + const typeConfig = getAdmonitionConfig(type) + const titleLabel = title ?? typeConfig.label + const { iconComponent: IconComponent } = typeConfig + const icon = iconProp ?? + return ( +
+
+ {icon} + {titleLabel} +
+
{children}
+
+ ) +} diff --git a/packages/docusaurus-playground/src/theme/Admonition/styles.module.css b/packages/docusaurus-playground/src/theme/Admonition/styles.module.css new file mode 100644 index 0000000..634c9dc --- /dev/null +++ b/packages/docusaurus-playground/src/theme/Admonition/styles.module.css @@ -0,0 +1,31 @@ +.admonition { + margin-bottom: 1em; +} + +.admonitionHeading { + font: var(--ifm-heading-font-weight) var(--ifm-h5-font-size) / + var(--ifm-heading-line-height) var(--ifm-heading-font-family); + text-transform: uppercase; + margin-bottom: 0.3rem; +} + +.admonitionHeading code { + text-transform: none; +} + +.admonitionIcon { + display: inline-block; + vertical-align: middle; + margin-right: 0.4em; +} + +.admonitionIcon svg { + display: inline-block; + height: 1.6em; + width: 1.6em; + fill: var(--ifm-alert-foreground-color); +} + +.admonitionContent > :last-child { + margin-bottom: 0; +} diff --git a/packages/docusaurus-playground/src/theme/CodeBlock/Container/index.js b/packages/docusaurus-playground/src/theme/CodeBlock/Container/index.js new file mode 100644 index 0000000..bb90ffe --- /dev/null +++ b/packages/docusaurus-playground/src/theme/CodeBlock/Container/index.js @@ -0,0 +1,21 @@ +import React from 'react' +import clsx from 'clsx' +import { ThemeClassNames, usePrismTheme } from '@docusaurus/theme-common' +import { getPrismCssVariables } from '@docusaurus/theme-common/internal' +import styles from './styles.module.css' +export default function CodeBlockContainer({ as: As, ...props }) { + const prismTheme = usePrismTheme() + const prismCssVariables = getPrismCssVariables(prismTheme) + return ( + + ) +} diff --git a/packages/docusaurus-playground/src/theme/CodeBlock/Container/styles.module.css b/packages/docusaurus-playground/src/theme/CodeBlock/Container/styles.module.css new file mode 100644 index 0000000..3c79968 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/CodeBlock/Container/styles.module.css @@ -0,0 +1,7 @@ +.codeBlockContainer { + background: var(--prism-background-color); + color: var(--prism-color); + margin-bottom: var(--ifm-leading); + box-shadow: var(--ifm-global-shadow-lw); + border-radius: var(--ifm-code-border-radius); +} diff --git a/packages/docusaurus-playground/src/theme/CodeBlock/Content/Element.js b/packages/docusaurus-playground/src/theme/CodeBlock/Content/Element.js new file mode 100644 index 0000000..a094c77 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/CodeBlock/Content/Element.js @@ -0,0 +1,18 @@ +import React from 'react' +import clsx from 'clsx' +import Container from '@theme/CodeBlock/Container' +import styles from './styles.module.css' +//
 tags in markdown map to CodeBlocks. They may contain JSX children. When
+// the children is not a simple string, we just return a styled block without
+// actually highlighting.
+export default function CodeBlockJSX({ children, className }) {
+  return (
+    
+      {children}
+    
+  )
+}
diff --git a/packages/docusaurus-playground/src/theme/CodeBlock/Content/String.js b/packages/docusaurus-playground/src/theme/CodeBlock/Content/String.js
new file mode 100644
index 0000000..420a492
--- /dev/null
+++ b/packages/docusaurus-playground/src/theme/CodeBlock/Content/String.js
@@ -0,0 +1,100 @@
+import React from 'react'
+import clsx from 'clsx'
+import { useThemeConfig, usePrismTheme } from '@docusaurus/theme-common'
+import {
+  parseCodeBlockTitle,
+  parseLanguage,
+  parseLines,
+  containsLineNumbers,
+  useCodeWordWrap,
+} from '@docusaurus/theme-common/internal'
+import Highlight, { defaultProps } from 'prism-react-renderer'
+import Line from '@theme/CodeBlock/Line'
+import CopyButton from '@theme/CodeBlock/CopyButton'
+import WordWrapButton from '@theme/CodeBlock/WordWrapButton'
+import Container from '@theme/CodeBlock/Container'
+import styles from './styles.module.css'
+export default function CodeBlockString({
+  children,
+  className: blockClassName = '',
+  metastring,
+  title: titleProp,
+  showLineNumbers: showLineNumbersProp,
+  language: languageProp,
+}) {
+  const {
+    prism: { defaultLanguage, magicComments },
+  } = useThemeConfig()
+  const language =
+    languageProp ?? parseLanguage(blockClassName) ?? defaultLanguage
+  const prismTheme = usePrismTheme()
+  const wordWrap = useCodeWordWrap()
+  // We still parse the metastring in case we want to support more syntax in the
+  // future. Note that MDX doesn't strip quotes when parsing metastring:
+  // "title=\"xyz\"" => title: "\"xyz\""
+  const title = parseCodeBlockTitle(metastring) || titleProp
+  const { lineClassNames, code } = parseLines(children, {
+    metastring,
+    language,
+    magicComments,
+  })
+  const showLineNumbers = showLineNumbersProp ?? containsLineNumbers(metastring)
+  return (
+    
+      {title && 
{title}
} +
+ + {({ className, tokens, getLineProps, getTokenProps }) => ( +
+              
+                {tokens.map((line, i) => (
+                  
+                ))}
+              
+            
+ )} +
+
+ {(wordWrap.isEnabled || wordWrap.isCodeScrollable) && ( + wordWrap.toggle()} + isEnabled={wordWrap.isEnabled} + /> + )} + +
+
+
+ ) +} diff --git a/packages/docusaurus-playground/src/theme/CodeBlock/Content/styles.module.css b/packages/docusaurus-playground/src/theme/CodeBlock/Content/styles.module.css new file mode 100644 index 0000000..c2103cd --- /dev/null +++ b/packages/docusaurus-playground/src/theme/CodeBlock/Content/styles.module.css @@ -0,0 +1,80 @@ +.codeBlockContent { + position: relative; + /* rtl:ignore */ + direction: ltr; + border-radius: inherit; +} + +.codeBlockTitle { + border-bottom: 1px solid var(--ifm-color-emphasis-300); + font-size: var(--ifm-code-font-size); + font-weight: 500; + padding: 0.75rem var(--ifm-pre-padding); + border-top-left-radius: inherit; + border-top-right-radius: inherit; +} + +.codeBlock { + --ifm-pre-background: var(--prism-background-color); + margin: 0; + padding: 0; +} + +.codeBlockTitle + .codeBlockContent .codeBlock { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.codeBlockStandalone { + padding: 0; +} + +.codeBlockLines { + font: inherit; + /* rtl:ignore */ + float: left; + min-width: 100%; + padding: var(--ifm-pre-padding); +} + +.codeBlockLinesWithNumbering { + display: table; + padding: var(--ifm-pre-padding) 0; +} + +@media print { + .codeBlockLines { + white-space: pre-wrap; + } +} + +.buttonGroup { + display: flex; + column-gap: 0.2rem; + position: absolute; + /* rtl:ignore */ + right: calc(var(--ifm-pre-padding) / 2); + top: calc(var(--ifm-pre-padding) / 2); +} + +.buttonGroup button { + display: flex; + align-items: center; + background: var(--prism-background-color); + color: var(--prism-color); + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: var(--ifm-global-radius); + padding: 0.4rem; + line-height: 0; + transition: opacity var(--ifm-transition-fast) ease-in-out; + opacity: 0; +} + +.buttonGroup button:focus-visible, +.buttonGroup button:hover { + opacity: 1 !important; +} + +:global(.theme-code-block:hover) .buttonGroup button { + opacity: 0.4; +} diff --git a/packages/docusaurus-playground/src/theme/CodeBlock/CopyButton/index.js b/packages/docusaurus-playground/src/theme/CodeBlock/CopyButton/index.js new file mode 100644 index 0000000..f21e974 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/CodeBlock/CopyButton/index.js @@ -0,0 +1,55 @@ +import React, { useCallback, useState, useRef, useEffect } from 'react' +import clsx from 'clsx' +// @ts-expect-error: TODO, we need to make theme-classic have type: module +import copy from 'copy-text-to-clipboard' +import { translate } from '@docusaurus/Translate' +import IconCopy from '@theme/Icon/Copy' +import IconSuccess from '@theme/Icon/Success' +import styles from './styles.module.css' +export default function CopyButton({ code, className }) { + const [isCopied, setIsCopied] = useState(false) + const copyTimeout = useRef(undefined) + const handleCopyCode = useCallback(() => { + copy(code) + setIsCopied(true) + copyTimeout.current = window.setTimeout(() => { + setIsCopied(false) + }, 1000) + }, [code]) + useEffect(() => () => window.clearTimeout(copyTimeout.current), []) + return ( + + ) +} diff --git a/packages/docusaurus-playground/src/theme/CodeBlock/CopyButton/styles.module.css b/packages/docusaurus-playground/src/theme/CodeBlock/CopyButton/styles.module.css new file mode 100644 index 0000000..d5268e9 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/CodeBlock/CopyButton/styles.module.css @@ -0,0 +1,40 @@ +:global(.theme-code-block:hover) .copyButtonCopied { + opacity: 1 !important; +} + +.copyButtonIcons { + position: relative; + width: 1.125rem; + height: 1.125rem; +} + +.copyButtonIcon, +.copyButtonSuccessIcon { + position: absolute; + top: 0; + left: 0; + fill: currentColor; + opacity: inherit; + width: inherit; + height: inherit; + transition: all var(--ifm-transition-fast) ease; +} + +.copyButtonSuccessIcon { + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0.33); + opacity: 0; + color: #00d600; +} + +.copyButtonCopied .copyButtonIcon { + transform: scale(0.33); + opacity: 0; +} + +.copyButtonCopied .copyButtonSuccessIcon { + transform: translate(-50%, -50%) scale(1); + opacity: 1; + transition-delay: 0.075s; +} diff --git a/packages/docusaurus-playground/src/theme/CodeBlock/Line/index.js b/packages/docusaurus-playground/src/theme/CodeBlock/Line/index.js new file mode 100644 index 0000000..bdfd277 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/CodeBlock/Line/index.js @@ -0,0 +1,34 @@ +import React from 'react' +import clsx from 'clsx' +import styles from './styles.module.css' +export default function CodeBlockLine({ + line, + classNames, + showLineNumbers, + getLineProps, + getTokenProps, +}) { + if (line.length === 1 && line[0].content === '\n') { + line[0].content = '' + } + const lineProps = getLineProps({ + line, + className: clsx(classNames, showLineNumbers && styles.codeLine), + }) + const lineTokens = line.map((token, key) => ( + + )) + return ( + + {showLineNumbers ? ( + <> + + {lineTokens} + + ) : ( + lineTokens + )} +
+
+ ) +} diff --git a/packages/docusaurus-playground/src/theme/CodeBlock/Line/styles.module.css b/packages/docusaurus-playground/src/theme/CodeBlock/Line/styles.module.css new file mode 100644 index 0000000..7c28ed9 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/CodeBlock/Line/styles.module.css @@ -0,0 +1,45 @@ +/* Intentionally has zero specificity, so that to be able to override +the background in custom CSS file due bug https://github.com/facebook/docusaurus/issues/3678 */ +:where(:root) { + --docusaurus-highlighted-code-line-bg: rgb(72 77 91); +} + +:where([data-theme='dark']) { + --docusaurus-highlighted-code-line-bg: rgb(100 100 100); +} + +:global(.theme-code-block-highlighted-line) { + background-color: var(--docusaurus-highlighted-code-line-bg); + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); +} + +.codeLine { + display: table-row; + counter-increment: line-count; +} + +.codeLineNumber { + display: table-cell; + text-align: right; + width: 1%; + position: sticky; + left: 0; + padding: 0 var(--ifm-pre-padding); + background: var(--ifm-pre-background); + overflow-wrap: normal; +} + +.codeLineNumber::before { + content: counter(line-count); + opacity: 0.4; +} + +:global(.theme-code-block-highlighted-line) .codeLineNumber::before { + opacity: 0.8; +} + +.codeLineContent { + padding-right: var(--ifm-pre-padding); +} diff --git a/packages/docusaurus-playground/src/theme/CodeBlock/WordWrapButton/index.js b/packages/docusaurus-playground/src/theme/CodeBlock/WordWrapButton/index.js new file mode 100644 index 0000000..bdd76ad --- /dev/null +++ b/packages/docusaurus-playground/src/theme/CodeBlock/WordWrapButton/index.js @@ -0,0 +1,28 @@ +import React from 'react' +import clsx from 'clsx' +import { translate } from '@docusaurus/Translate' +import IconWordWrap from '@theme/Icon/WordWrap' +import styles from './styles.module.css' +export default function WordWrapButton({ className, onClick, isEnabled }) { + const title = translate({ + id: 'theme.CodeBlock.wordWrapToggle', + message: 'Toggle word wrap', + description: + 'The title attribute for toggle word wrapping button of code block lines', + }) + return ( + + ) +} diff --git a/packages/docusaurus-playground/src/theme/CodeBlock/WordWrapButton/styles.module.css b/packages/docusaurus-playground/src/theme/CodeBlock/WordWrapButton/styles.module.css new file mode 100644 index 0000000..fdbc894 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/CodeBlock/WordWrapButton/styles.module.css @@ -0,0 +1,8 @@ +.wordWrapButtonIcon { + width: 1.2rem; + height: 1.2rem; +} + +.wordWrapButtonEnabled .wordWrapButtonIcon { + color: var(--ifm-color-primary); +} diff --git a/packages/docusaurus-playground/src/theme/CodeBlock/index.js b/packages/docusaurus-playground/src/theme/CodeBlock/index.js new file mode 100644 index 0000000..e31fcbb --- /dev/null +++ b/packages/docusaurus-playground/src/theme/CodeBlock/index.js @@ -0,0 +1,32 @@ +import React, { isValidElement } from 'react' +import useIsBrowser from '@docusaurus/useIsBrowser' +import ElementContent from '@theme/CodeBlock/Content/Element' +import StringContent from '@theme/CodeBlock/Content/String' +/** + * Best attempt to make the children a plain string so it is copyable. If there + * are react elements, we will not be able to copy the content, and it will + * return `children` as-is; otherwise, it concatenates the string children + * together. + */ +function maybeStringifyChildren(children) { + if (React.Children.toArray(children).some((el) => isValidElement(el))) { + return children + } + // The children is now guaranteed to be one/more plain strings + return Array.isArray(children) ? children.join('') : children +} +export default function CodeBlock({ children: rawChildren, ...props }) { + // The Prism theme on SSR is always the default theme but the site theme can + // be in a different mode. React hydration doesn't update DOM styles that come + // from SSR. Hence force a re-render after mounting to apply the current + // relevant styles. + const isBrowser = useIsBrowser() + const children = maybeStringifyChildren(rawChildren) + const CodeBlockComp = + typeof children === 'string' ? StringContent : ElementContent + return ( + + {children} + + ) +} diff --git a/packages/docusaurus-playground/src/theme/Footer/Layout/index.js b/packages/docusaurus-playground/src/theme/Footer/Layout/index.js new file mode 100644 index 0000000..797b405 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/Footer/Layout/index.js @@ -0,0 +1,21 @@ +import React from 'react' +import clsx from 'clsx' +export default function FooterLayout({ style, links, logo, copyright }) { + return ( +
+
+ {links} + {(logo || copyright) && ( +
+ {logo &&
{logo}
} + {copyright} +
+ )} +
+
+ ) +} diff --git a/packages/docusaurus-playground/src/theme/Footer/LinkItem/index.js b/packages/docusaurus-playground/src/theme/Footer/LinkItem/index.js new file mode 100644 index 0000000..89e1001 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/Footer/LinkItem/index.js @@ -0,0 +1,26 @@ +import React from 'react' +import Link from '@docusaurus/Link' +import useBaseUrl from '@docusaurus/useBaseUrl' +import isInternalUrl from '@docusaurus/isInternalUrl' +import IconExternalLink from '@theme/Icon/ExternalLink' +export default function FooterLinkItem({ item }) { + const { to, href, label, prependBaseUrlToHref, ...props } = item + const toUrl = useBaseUrl(to) + const normalizedHref = useBaseUrl(href, { forcePrependBaseUrl: true }) + return ( + + {label} + {href && !isInternalUrl(href) && } + + ) +} diff --git a/packages/docusaurus-playground/src/theme/Footer/Links/MultiColumn/index.js b/packages/docusaurus-playground/src/theme/Footer/Links/MultiColumn/index.js new file mode 100644 index 0000000..bf8b308 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/Footer/Links/MultiColumn/index.js @@ -0,0 +1,37 @@ +import React from 'react' +import LinkItem from '@theme/Footer/LinkItem' +function ColumnLinkItem({ item }) { + return item.html ? ( +
  • + ) : ( +
  • + +
  • + ) +} +function Column({ column }) { + return ( +
    +
    {column.title}
    +
      + {column.items.map((item, i) => ( + + ))} +
    +
    + ) +} +export default function FooterLinksMultiColumn({ columns }) { + return ( +
    + {columns.map((column, i) => ( + + ))} +
    + ) +} diff --git a/packages/docusaurus-playground/src/theme/Footer/Links/index.js b/packages/docusaurus-playground/src/theme/Footer/Links/index.js new file mode 100644 index 0000000..3680596 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/Footer/Links/index.js @@ -0,0 +1,11 @@ +import React from 'react' +import { isMultiColumnFooterLinks } from '@docusaurus/theme-common' +import FooterLinksMultiColumn from '@theme/Footer/Links/MultiColumn' +import FooterLinksSimple from '@theme/Footer/Links/Simple' +export default function FooterLinks({ links }) { + return isMultiColumnFooterLinks(links) ? ( + + ) : ( + + ) +} diff --git a/packages/docusaurus-playground/src/theme/Footer/Logo/index.js b/packages/docusaurus-playground/src/theme/Footer/Logo/index.js new file mode 100644 index 0000000..9228cb0 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/Footer/Logo/index.js @@ -0,0 +1,36 @@ +import React from 'react' +import clsx from 'clsx' +import Link from '@docusaurus/Link' +import { useBaseUrlUtils } from '@docusaurus/useBaseUrl' +import ThemedImage from '@theme/ThemedImage' +import styles from './styles.module.css' +function LogoImage({ logo }) { + const { withBaseUrl } = useBaseUrlUtils() + const sources = { + light: withBaseUrl(logo.src), + dark: withBaseUrl(logo.srcDark ?? logo.src), + } + return ( + + ) +} +export default function FooterLogo({ logo }) { + return logo.href ? ( + + + + ) : ( + + ) +} diff --git a/packages/docusaurus-playground/src/theme/Footer/Logo/styles.module.css b/packages/docusaurus-playground/src/theme/Footer/Logo/styles.module.css new file mode 100644 index 0000000..faf0e60 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/Footer/Logo/styles.module.css @@ -0,0 +1,9 @@ +.footerLogoLink { + opacity: 0.5; + transition: opacity var(--ifm-transition-fast) + var(--ifm-transition-timing-default); +} + +.footerLogoLink:hover { + opacity: 1; +} diff --git a/packages/docusaurus-playground/src/theme/Footer/index.js b/packages/docusaurus-playground/src/theme/Footer/index.js new file mode 100644 index 0000000..4586803 --- /dev/null +++ b/packages/docusaurus-playground/src/theme/Footer/index.js @@ -0,0 +1,22 @@ +import React from 'react' +import { useThemeConfig } from '@docusaurus/theme-common' +import FooterLinks from '@theme/Footer/Links' +import FooterLogo from '@theme/Footer/Logo' +import FooterCopyright from '@theme/Footer/Copyright' +import FooterLayout from '@theme/Footer/Layout' +function Footer() { + const { footer } = useThemeConfig() + if (!footer) { + return null + } + const { copyright, links, logo, style } = footer + return ( + 0 && } + logo={logo && } + copyright={copyright && } + /> + ) +} +export default React.memo(Footer)