From 9bdf6ba48f66b4a94e77bbe814dde7f4fad997ff Mon Sep 17 00:00:00 2001 From: Mikkel Laursen Date: Mon, 18 Oct 2021 17:24:18 -0600 Subject: [PATCH] chore(docs): update code components to use css modules --- .../dev-utils/src/indexer/parseMarkdown.ts | 6 +- packages/documentation/.babelrc | 6 +- .../src/components/Blockquote.module.scss | 22 +++ .../src/components/Code/Code.module.scss | 23 +++ .../src/components/Code/Code.tsx | 49 +++++- .../src/components/Code/CodeBlock.module.scss | 112 +++++++++++++ .../src/components/Code/CodeBlock.tsx | 100 +++++++---- .../components/Code/LineNumbers.module.scss | 19 +++ .../src/components/Code/LineNumbers.tsx | 25 +-- packages/documentation/src/pages/app.scss | 158 ------------------ 10 files changed, 298 insertions(+), 222 deletions(-) create mode 100644 packages/documentation/src/components/Blockquote.module.scss create mode 100644 packages/documentation/src/components/Code/Code.module.scss create mode 100644 packages/documentation/src/components/Code/CodeBlock.module.scss create mode 100644 packages/documentation/src/components/Code/LineNumbers.module.scss diff --git a/packages/dev-utils/src/indexer/parseMarkdown.ts b/packages/dev-utils/src/indexer/parseMarkdown.ts index 2087361c1d..c32fdc1605 100644 --- a/packages/dev-utils/src/indexer/parseMarkdown.ts +++ b/packages/dev-utils/src/indexer/parseMarkdown.ts @@ -1,5 +1,5 @@ import { decode } from "he"; -import marked from "marked"; +import { parse, Renderer } from "marked"; import { getPackages } from "../utils"; import { TOCAnchor } from "./types"; @@ -16,7 +16,7 @@ const whitespace = "(?=\r?\n| |[^/])"; export function parseMarkdown(markdown: string): MarkdownResult { const joinedNames = getPackages().join("|"); const anchors: TOCAnchor[] = []; - const renderer = new marked.Renderer({ gfm: true, sanitize: false }); + const renderer = new Renderer({ gfm: true, sanitize: false }); renderer.heading = (text, _level, _raw, slugger) => { // if it is over 60 characters, it is probably not really a title const isNoMargin = text.includes(""); @@ -106,7 +106,7 @@ export function parseMarkdown(markdown: string): MarkdownResult { /#customizing-your-theme/g, "[customizing your theme](/guides/customizing-your-theme)" ); - marked.parse(updated, { renderer }); + parse(updated, { renderer }); return { anchors, diff --git a/packages/documentation/.babelrc b/packages/documentation/.babelrc index 94dff0d0ce..01dc7fd98d 100644 --- a/packages/documentation/.babelrc +++ b/packages/documentation/.babelrc @@ -7,16 +7,18 @@ { "languages": [ "javascript", - "typescript", "jsx", + "typescript", "tsx", "css", "scss", "css-extras", "markup", + "markdown", "bash", - "diff", + "shell", "git", + "diff", "json", "properties" ], diff --git a/packages/documentation/src/components/Blockquote.module.scss b/packages/documentation/src/components/Blockquote.module.scss new file mode 100644 index 0000000000..3462421907 --- /dev/null +++ b/packages/documentation/src/components/Blockquote.module.scss @@ -0,0 +1,22 @@ +@use '../everything' as *; + +.blockquote { + $border-style: 0.25em solid rmd-divider-theme-var(background-color); + + @include rmd-theme(color, text-secondary-on-background); + @include rmd-typography(subtitle-1); + @include rmd-utils-rtl-auto(border-left, $border-style); + + font-style: italic; + margin: 1em 0; + padding: 0.5em 1em; + position: relative; + + p:only-child { + margin: 0; + } + + p:last-child { + margin-bottom: 0; + } +} diff --git a/packages/documentation/src/components/Code/Code.module.scss b/packages/documentation/src/components/Code/Code.module.scss new file mode 100644 index 0000000000..a2a642dcdb --- /dev/null +++ b/packages/documentation/src/components/Code/Code.module.scss @@ -0,0 +1,23 @@ +@use '../../everything' as *; + +.code { + @include rmd-typography(body-1); + + font-family: 'Source Code Pro', Consolas, Monaco, monospace; +} + +.inline { + color: var(--code-color); + white-space: pre-wrap; +} + +.ticked { + &::before, + &::after { + content: '\`'; + } +} + +.oneline { + white-space: nowrap; +} diff --git a/packages/documentation/src/components/Code/Code.tsx b/packages/documentation/src/components/Code/Code.tsx index 3061f87722..5d0600b0d8 100644 --- a/packages/documentation/src/components/Code/Code.tsx +++ b/packages/documentation/src/components/Code/Code.tsx @@ -1,15 +1,48 @@ -import React, { forwardRef, HTMLAttributes, ReactNode } from "react"; +import React, { forwardRef, HTMLAttributes } from "react"; import cn from "classnames"; +import styles from "./Code.module.scss"; + export interface CodeProps extends HTMLAttributes { - className?: string; - children: ReactNode; + /** + * Boolean if the code is displayed inline with other text normally meaning it + * is not a child of the `CodeBlock` component. + * + * @defaultValue `true` when the `dangerouslySetInnerHTML` prop is not defined + */ inline?: boolean; + + /** + * Boolean if the code should no be able to line wrap. This should really only + * be enabled when the parent element also has `white-space: nowrap` and + * overflown text should be ellipsis-ed + * + * @defaultValue `false` + */ noWrap?: boolean; + + /** + * Boolean if the code should display backticks before and after the content + * to help show that this is a code block. + * + * @defaultValue `true` when the `dangerouslySetInnerHTML` prop is not define + */ + ticked?: boolean; } +/** + * This component is normally used to render inline code throughout the website. + */ export default forwardRef(function Code( - { inline = true, noWrap = false, className, children, ...props }, + { + children, + className, + dangerouslySetInnerHTML, + inline = !dangerouslySetInnerHTML, + ticked = inline, + noWrap = false, + ...props + }, ref ) { return ( @@ -17,13 +50,15 @@ export default forwardRef(function Code( {...props} ref={ref} className={cn( - "code", + styles.code, { - "code--inline": inline, - "code--no-wrap": inline && noWrap, + [styles.inline]: inline, + [styles.ticked]: ticked, + [styles.oneline]: inline && noWrap, }, className )} + dangerouslySetInnerHTML={dangerouslySetInnerHTML} > {children} diff --git a/packages/documentation/src/components/Code/CodeBlock.module.scss b/packages/documentation/src/components/Code/CodeBlock.module.scss new file mode 100644 index 0000000000..43c21a67c4 --- /dev/null +++ b/packages/documentation/src/components/Code/CodeBlock.module.scss @@ -0,0 +1,112 @@ +@use '../../everything' as *; + +.block { + @include rmd-typography(body-1); + + background-color: $solarized-base-03; + color: $solarized-base-1; + + // always enforce ltr for blocked code + direction: ltr; + font-family: 'Source Code Pro', Consolas, Monaco, monospace; + margin: 0.5em 0; + overflow: auto; + padding: 1em; + + &::selection, + *::selection { + background-color: $solarized-base-02; + } + + // to help with the collapse transition on expandable code, update it so + // that only the vertical overflow is hidden. + &.rmd-collapse--no-overflow { + overflow-y: hidden; + } + + // prismjs custom theme + // this is _basically_ my current vim theme, but not aas good since + // Prism doesn't have as many tokenizers + :global { + .token { + position: relative; + z-index: 1; + + &.comment, + &.doctype { + color: $solarized-base-01; + } + + &.keyword { + color: $solarized-green; + } + + &.punctuation { + color: $solarized-base-01; + } + + &.inserted, + &.string, + .language-tsx &.attr-value { + color: $solarized-cyan; + } + + &.tag, + &.selector, + &.class-name { + color: $solarized-blue; + } + + &.attr-name, + &.property, + &.builtin { + color: $solarized-yellow; + } + + &.script, + &.interpolation { + color: $solarized-base-1; + } + + &.deleted, + &.boolean, + &.number, + &.interpolation-punctuation { + color: $solarized-red; + } + + &.important, + &.bold { + font-weight: map-get($rmd-typography-font-weights, bold); + } + + &.italic { + font-style: italic; + } + + &.entity { + cursor: help; + } + } + } + + &:global(.language-scss) :global .token.function, + &:global(.language-scss) :global .token.variable { + color: $solarized-blue; + } + + &:global(.language-ts) :global .token + .class-name, + &:global(.language-tsx) :global .token + .class-name { + color: $solarized-base-1; + } + + &:global(.language-scss) :global .token.function, + &:global(.language-shell) :global .token.function { + color: $solarized-orange; + } +} + +.counted { + padding-left: 3em; + position: relative; +} diff --git a/packages/documentation/src/components/Code/CodeBlock.tsx b/packages/documentation/src/components/Code/CodeBlock.tsx index 0a76a5c585..8cb9a6d2f8 100644 --- a/packages/documentation/src/components/Code/CodeBlock.tsx +++ b/packages/documentation/src/components/Code/CodeBlock.tsx @@ -1,28 +1,68 @@ -/* eslint-disable react/no-danger */ -import React, { forwardRef, ReactNode, useMemo, HTMLAttributes } from "react"; +import React, { + DOMAttributes, + forwardRef, + HTMLAttributes, + ReactNode, +} from "react"; import cn from "classnames"; -import { bem } from "@react-md/utils"; import { highlightCode } from "components/Markdown/utils"; +import styles from "./CodeBlock.module.scss"; import Code from "./Code"; import LineNumbers from "./LineNumbers"; export interface CodeBlockProps extends HTMLAttributes { - className?: string; + /** + * An optional code language. This should be one of: + * - js + * - jsx + * - javascript + * - ts + * - tsx + * - typescript + * - css + * - scss + * - markup + * - markdown + * - bash + * - shell + * - git + * - diff + * - json + * - properties + * + * And whatever the default Prism languages are. Update the `.babelrc` to add + * additional languages for the `prismjs` plugin. + */ language?: string; - children: ReactNode; + + /** + * Boolean if the code should be highlighted using Prism + */ highlight?: boolean; + + /** + * Boolean if line numbers should be displayed with the code. + */ lineNumbers?: boolean; -} -const block = bem("code"); + /** + * This should normally be a string of code to display, but can also be any + * renderable thing. The {@link highlight} prop will only work for string + * `children`. + */ + children: ReactNode; +} +/** + * Renders a code block with highlighted code using PrismJS. + */ export default forwardRef(function CodeBlock( { className, language = "markdown", - children: propChildren, + children, highlight = true, lineNumbers = false, suppressHydrationWarning = false, @@ -30,43 +70,35 @@ export default forwardRef(function CodeBlock( }, ref ) { - const children = useMemo(() => { - if (!highlight || typeof propChildren !== "string") { - return ( - - {propChildren} - - ); - } + const code = typeof children === "string" ? children : ""; - return ( - - ); - }, [highlight, propChildren, language, suppressHydrationWarning]); + let dangerouslySetInnerHTML: DOMAttributes["dangerouslySetInnerHTML"]; + if (code && highlight) { + dangerouslySetInnerHTML = { + __html: highlightCode(code, language), + }; + } return (
-      {typeof propChildren === "string" && (
-        
-      )}
-      {children}
+      {lineNumbers && code && }
+      
+        {dangerouslySetInnerHTML ? undefined : children}
+      
     
); }); diff --git a/packages/documentation/src/components/Code/LineNumbers.module.scss b/packages/documentation/src/components/Code/LineNumbers.module.scss new file mode 100644 index 0000000000..6542b8aa7a --- /dev/null +++ b/packages/documentation/src/components/Code/LineNumbers.module.scss @@ -0,0 +1,19 @@ +@use '../../everything' as *; + +.container { + border-right: 1px solid $solarized-base-01; + counter-reset: line-numbers; + display: flex; + flex-direction: column; + left: 0; + padding-right: 0.25em; + pointer-events: none; + position: absolute; + text-align: right; + width: 2.5em; +} + +.counter::before { + content: counter(line-numbers); + counter-increment: line-numbers; +} diff --git a/packages/documentation/src/components/Code/LineNumbers.tsx b/packages/documentation/src/components/Code/LineNumbers.tsx index fc02d6878a..f1ae4ac996 100644 --- a/packages/documentation/src/components/Code/LineNumbers.tsx +++ b/packages/documentation/src/components/Code/LineNumbers.tsx @@ -1,30 +1,19 @@ -/* eslint-disable react/no-array-index-key */ import React, { ReactElement } from "react"; -import { bem } from "@react-md/utils"; + +import styles from "./LineNumbers.module.scss"; export interface LineNumbersProps { code: string; - enabled: boolean; } -const block = bem("code"); - -export default function LineNumbers({ - code, - enabled, -}: LineNumbersProps): ReactElement | null { - if (!enabled) { - return null; - } - - const lines = (code.match(/\r?\n/g) || []).length; +export default function LineNumbers({ code }: LineNumbersProps): ReactElement { + const lineCount = code.match(/\r?\n/g)?.length; + const lines = typeof lineCount === "number" ? lineCount + 1 : 1; return ( - + {Array.from({ length: lines }).map((_, i) => ( - - {i + 1} - + ))} ); diff --git a/packages/documentation/src/pages/app.scss b/packages/documentation/src/pages/app.scss index 0c2f9b075a..55fba4a383 100644 --- a/packages/documentation/src/pages/app.scss +++ b/packages/documentation/src/pages/app.scss @@ -26,27 +26,6 @@ width: 100%; } -.blockquote { - $border-style: 0.25em solid rmd-divider-theme-var(background-color); - - @include rmd-theme(color, text-secondary-on-background); - @include rmd-typography(subtitle-1); - @include rmd-utils-rtl-auto(border-left, $border-style); - - font-style: italic; - margin: 1em 0; - padding: 0.5em 1em; - position: relative; - - p:only-child { - margin: 0; - } - - p:last-child { - margin-bottom: 0; - } -} - .heading { @include rmd-utils-phone-media { // decrease font size and line-height so it can fit on mobile screens. this @@ -125,140 +104,3 @@ } } } - -.code { - @include rmd-typography(body-1); - - font-family: 'Source Code Pro', Consolas, Monaco, monospace; - - &--block { - background-color: $solarized-base-03; - color: $solarized-base-1; - - // always enforce ltr for blocked code - direction: ltr; - margin: 0.5em 0; - overflow: auto; - padding: 1em; - - &::selection, - *::selection { - background-color: $solarized-base-02; - } - - // to help with the collapse transition on expandable code, update it so - // that only the vertical overflow is hidden. - &.rmd-collapse--no-overflow { - overflow-y: hidden; - } - } - - &--inline { - &::before, - &::after { - content: '\`'; - } - - color: var(--code-color); - white-space: pre-wrap; - } - - &--no-wrap { - white-space: nowrap; - } - - &--counted { - padding-left: 3em; - position: relative; - } - - &__lines { - border-right: 1px solid $solarized-base-01; - display: flex; - flex-direction: column; - left: 0; - padding-right: 0.25em; - pointer-events: none; - position: absolute; - text-align: right; - width: 2.5em; - } -} - -// prismjs custom theme -// this is _basically_ my current vim theme, but not aas good since -// Prism doesn't have as many tokenizers -.token { - position: relative; - z-index: 1; - - &.comment, - &.doctype { - color: $solarized-base-01; - } - - &.keyword { - color: $solarized-green; - } - - &.punctuation { - color: $solarized-base-01; - } - - &.inserted, - &.string, - .language-tsx &.attr-value { - color: $solarized-cyan; - } - - &.tag, - &.selector, - &.class-name, - // fixes mixins/functions - .language-scss &.function, - // fixes variables - .language-scss &.variable { - color: $solarized-blue; - } - - &.attr-name, - &.property, - &.builtin { - color: $solarized-yellow; - } - - &.script, - &.interpolation, - // to fix interfaces - .language-ts &.keyword + .class-name, - .language-tsx &.keyword + .class-name { - color: $solarized-base-1; - } - - // fixes property colors - .language-scss &.keyword, - // for npm install - .language-shell &.function { - color: $solarized-orange; - } - - &.deleted, - &.boolean, - &.number, - &.interpolation-punctuation { - color: $solarized-red; - } - - &.important, - &.bold { - font-weight: map-get($rmd-typography-font-weights, bold); - } - - &.italic { - font-style: italic; - } - - &.entity { - cursor: help; - } -}