From b2761b65b16a5e10bc5abe77fab8a6d7d20a4d62 Mon Sep 17 00:00:00 2001 From: Aaron Moat <2937187+AaronMoat@users.noreply.github.com> Date: Fri, 10 Jan 2025 09:51:05 +1100 Subject: [PATCH] Refactor Code components (#770) * Refactor Code components * Update breezy-kiwis-fold.md * Explicit label in example --------- Co-authored-by: Ryan Ling --- .changeset/breezy-kiwis-fold.md | 13 ++ .changeset/twelve-bags-grin.md | 5 + ..._Standalone_CodeContainer_Line_Numbers.png | 3 + ...andalone_CodeContainer_No_Line_Numbers.png | 3 + README.md | 25 +++- src/components/CodeBlock.stories.tsx | 21 --- src/components/CodeBlock.tsx | 138 +++++++----------- .../CodeBlock/GraphQLPlaygroundAction.tsx | 8 +- src/components/CodeBlock/LineNumbers.tsx | 28 ---- src/components/CodeBlock/Lines.tsx | 35 ----- ...{CodeBlock.css.ts => CodeContainer.css.ts} | 32 ++-- src/components/CodeContainer.stories.tsx | 27 ++++ src/components/CodeContainer.tsx | 78 ++++++++++ src/index.ts | 1 + styles/code.css.ts | 17 +-- 15 files changed, 224 insertions(+), 210 deletions(-) create mode 100644 .changeset/breezy-kiwis-fold.md create mode 100644 .changeset/twelve-bags-grin.md create mode 100644 .loki/reference/chrome_laptop_Standalone_CodeContainer_Line_Numbers.png create mode 100644 .loki/reference/chrome_laptop_Standalone_CodeContainer_No_Line_Numbers.png delete mode 100644 src/components/CodeBlock/LineNumbers.tsx delete mode 100644 src/components/CodeBlock/Lines.tsx rename src/components/{CodeBlock.css.ts => CodeContainer.css.ts} (61%) create mode 100644 src/components/CodeContainer.stories.tsx create mode 100644 src/components/CodeContainer.tsx diff --git a/.changeset/breezy-kiwis-fold.md b/.changeset/breezy-kiwis-fold.md new file mode 100644 index 00000000..5b798637 --- /dev/null +++ b/.changeset/breezy-kiwis-fold.md @@ -0,0 +1,13 @@ +--- +'scoobie': major +--- + +CodeBlock: change props + +- `label` and `language` are now required + +- `copy` is no longer accepted; the copy button is always shown + + To have just the code block without the top row with the copy button, use `CodeContainer` + +- `size` is no longer accepted; it is now hardcoded to `standard` diff --git a/.changeset/twelve-bags-grin.md b/.changeset/twelve-bags-grin.md new file mode 100644 index 00000000..ccf0613e --- /dev/null +++ b/.changeset/twelve-bags-grin.md @@ -0,0 +1,5 @@ +--- +'scoobie': minor +--- + +CodeContainer: add new component diff --git a/.loki/reference/chrome_laptop_Standalone_CodeContainer_Line_Numbers.png b/.loki/reference/chrome_laptop_Standalone_CodeContainer_Line_Numbers.png new file mode 100644 index 00000000..ef0dc807 --- /dev/null +++ b/.loki/reference/chrome_laptop_Standalone_CodeContainer_Line_Numbers.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:436e250e32ff0e325d7450c0124608baa617cad72466620dc35a5bcd43110dff +size 6949 diff --git a/.loki/reference/chrome_laptop_Standalone_CodeContainer_No_Line_Numbers.png b/.loki/reference/chrome_laptop_Standalone_CodeContainer_No_Line_Numbers.png new file mode 100644 index 00000000..aba5bcd7 --- /dev/null +++ b/.loki/reference/chrome_laptop_Standalone_CodeContainer_No_Line_Numbers.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fadb7ecce4a38c4292ad324ec4cadbcbeca51e2cda4f63e4496e0d780119aad +size 6128 diff --git a/README.md b/README.md index 0c9faa12..cc7a7c0a 100644 --- a/README.md +++ b/README.md @@ -78,16 +78,33 @@ export const MyFirstBlockquote = () => ( ### CodeBlock -Render lines of code with [Prism] syntax highlighting. - -[prism]: https://github.com/PrismJS/prism +Render a rich [`CodeContainer`](#codecontainer) with interactive copy & GraphQL playground link buttons. ```tsx import React from 'react'; import { CodeBlock } from 'scoobie'; export const MyFirstCodeBlock = () => ( - console.log('hello, world'); + + console.log('hello, world'); + +); +``` + +### CodeContainer + +Render code with [Prism] syntax highlighting, with optional `lineNumbers`. + +[prism]: https://github.com/PrismJS/prism + +```tsx +import React from 'react'; +import { CodeContainer } from 'scoobie'; + +export const MyFirstCodeContainer = () => ( + + console.log('hello, world'); + ); ``` diff --git a/src/components/CodeBlock.stories.tsx b/src/components/CodeBlock.stories.tsx index 131d6f6f..ae512c9f 100644 --- a/src/components/CodeBlock.stories.tsx +++ b/src/components/CodeBlock.stories.tsx @@ -11,15 +11,8 @@ export default { graphqlPlayground: 'https://manage.developer.seek.com/graphql-explorer', initialIndex: 0, language: 'graphql', - size: 'standard', trim: true, }, - argTypes: { - size: { - control: { type: 'radio' }, - options: ['standard', 'large'], - }, - }, } satisfies Meta; type Story = StoryObj; @@ -69,17 +62,3 @@ export const Multi: Story = { }, }, }; - -export const Minimal: Story = { - args: { - children: JSON.stringify( - { stuff: 'things', otherStuff: [{ id: 17 }] }, - null, - 2, - ), - label: '', - language: 'json', - copy: false, - lineNumbers: false, - }, -}; diff --git a/src/components/CodeBlock.tsx b/src/components/CodeBlock.tsx index 98f599d6..f2f8b6e9 100644 --- a/src/components/CodeBlock.tsx +++ b/src/components/CodeBlock.tsx @@ -1,35 +1,22 @@ import { Box, Stack, Text, TextLinkButton } from 'braid-design-system'; import { parse } from 'jsonc-parser'; -import { Highlight } from 'prism-react-renderer'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; -import { Prism, themes } from '../private/Prism'; import { ScrollableInline } from '../private/ScrollableInline'; -import { - DEFAULT_SIZE, - SIZE_TO_SMALLER, - SIZE_TO_TABLE_PADDING, - type Size, -} from '../private/size'; import { type CodeChildProps, normaliseChildren } from './CodeBlock/CodeChild'; import { GraphQLPlaygroundAction } from './CodeBlock/GraphQLPlaygroundAction'; -import { LineNumbers } from './CodeBlock/LineNumbers'; -import { Lines } from './CodeBlock/Lines'; +import { CodeContainer } from './CodeContainer'; import { CopyableText } from './CopyableText'; -import * as styles from './CodeBlock.css'; - interface Props { children: readonly CodeChildProps[] | string; graphqlPlayground?: string; initialIndex?: number; - label?: string; - language?: string; - size?: Size; + label: string; + language: string; trim?: boolean; lineNumbers?: boolean; - copy?: boolean; } export const CodeBlock = ({ @@ -38,10 +25,8 @@ export const CodeBlock = ({ initialIndex = 0, label: rawLabel, language: rawLanguage, - size = DEFAULT_SIZE, trim = true, lineNumbers = true, - copy = true, }: Props) => { const children = normaliseChildren( typeof rawChildren === 'string' @@ -56,9 +41,6 @@ export const CodeBlock = ({ trim, ); - const smallerSize = SIZE_TO_SMALLER[size]; - const tablePadding = SIZE_TO_TABLE_PADDING[size]; - const [index, setIndex] = useState({ dirty: false, value: initialIndex }); useEffect( @@ -84,87 +66,65 @@ export const CodeBlock = ({ const graphqlPlaygroundButton = children[0].language === 'graphql' && graphqlPlayground ? ( - + ) : undefined; - const topRow = - children.some(({ label }) => label) || copy || graphqlPlaygroundButton; - return ( - - {topRow ? ( - - - - {children.map(({ label }, labelIndex) => - label ? ( - + + + + {children.map(({ label }, labelIndex) => + label ? ( + + - - {children.length === 1 || index.value === labelIndex ? ( - label - ) : ( - - setIndex({ dirty: true, value: labelIndex }) - } - > - {label} - - )} - - - ) : null, - )} - - - - {copy ? ( - - {child.code} + {children.length === 1 || index.value === labelIndex ? ( + label + ) : ( + + setIndex({ dirty: true, value: labelIndex }) + } + > + {label} + + )} + - ) : null} - - {graphqlPlaygroundButton} - + ) : null, + )} - - ) : null} - - - - {({ getTokenProps, tokens }) => ( - - {lineNumbers ? ( - - ) : null} - + + + {child.code} - )} - - + + {graphqlPlaygroundButton} + + + + + ); }; diff --git a/src/components/CodeBlock/GraphQLPlaygroundAction.tsx b/src/components/CodeBlock/GraphQLPlaygroundAction.tsx index 37afccd8..e3c5cb8d 100644 --- a/src/components/CodeBlock/GraphQLPlaygroundAction.tsx +++ b/src/components/CodeBlock/GraphQLPlaygroundAction.tsx @@ -1,7 +1,6 @@ import { IconVideo, Text, TextLink } from 'braid-design-system'; -import React from 'react'; -import { SIZE_TO_SMALLER, type Size } from '../../private/size'; +import type { Size } from '../../private/size'; interface Props { query: string; @@ -14,17 +13,14 @@ export const GraphQLPlaygroundAction = ({ query, variables, graphqlPlayground, - size, }: Props) => { const playgroundUrl = new URL(graphqlPlayground); playgroundUrl.searchParams.set('query', query); playgroundUrl.searchParams.set('variables', variables ?? '{}'); const href = playgroundUrl.toString(); - const smallerSize = SIZE_TO_SMALLER[size]; - return ( - + } diff --git a/src/components/CodeBlock/LineNumbers.tsx b/src/components/CodeBlock/LineNumbers.tsx deleted file mode 100644 index e0322852..00000000 --- a/src/components/CodeBlock/LineNumbers.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Box, Stack } from 'braid-design-system'; -import React from 'react'; - -import type { Size } from '../../private/size'; - -import { code } from '../../../styles/code.css'; -import * as styles from '../CodeBlock.css'; - -interface Props { - count: number; - size: Size; -} - -export const LineNumbers = ({ count, size }: Props) => { - const numbers = [...new Array(count)].map((_, index) => index + 1); - - return ( - - - {numbers.map((number) => ( - - {number} - - ))} - - - ); -}; diff --git a/src/components/CodeBlock/Lines.tsx b/src/components/CodeBlock/Lines.tsx deleted file mode 100644 index 1b38d181..00000000 --- a/src/components/CodeBlock/Lines.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Box, Stack } from 'braid-design-system'; -import type { Highlight } from 'prism-react-renderer'; -import React from 'react'; - -import type { Size } from '../../private/size'; - -import { code } from '../../../styles/code.css'; - -type Highlight = Parameters[0]['children']>[0]; - -type Token = Parameters[0]['token']; - -interface Props { - getTokenProps: Highlight['getTokenProps']; - lines: Token[][]; - size: Size; -} - -export const Lines = ({ getTokenProps, lines, size }: Props) => ( - - - {lines.map((line, lineIndex) => ( - - - {line.map((token, tokenIndex) => { - const props = getTokenProps({ token }); - - return ; - })} - - - ))} - - -); diff --git a/src/components/CodeBlock.css.ts b/src/components/CodeContainer.css.ts similarity index 61% rename from src/components/CodeBlock.css.ts rename to src/components/CodeContainer.css.ts index bcc73ddd..12e7dd19 100644 --- a/src/components/CodeBlock.css.ts +++ b/src/components/CodeContainer.css.ts @@ -1,4 +1,4 @@ -import { composeStyles, style } from '@vanilla-extract/css'; +import { style } from '@vanilla-extract/css'; import { calc } from '@vanilla-extract/css-utils'; import { responsiveStyle, vars } from 'braid-design-system/css'; import { darken } from 'polished'; @@ -13,26 +13,24 @@ export const lineNumberContainer = style({ userSelect: 'none', }); -export const codeContainer = composeStyles( - style({ +export const codeContainer = style([ + { backgroundColor: codeBackgroundColor, borderColor: darken(0.05, codeBackgroundColor), borderStyle: 'solid', borderWidth: vars.borderWidth.standard, overflow: 'auto', - }), + }, - style( - responsiveStyle({ - mobile: { - // Roughly 15 lines of code at standard size. - maxHeight: calc.multiply(vars.grid, 90), - }, - tablet: { - // Roughly 30 lines of code at standard size. - maxHeight: calc.multiply(vars.grid, 172), - }, - }), - ), -); + responsiveStyle({ + mobile: { + // Roughly 15 lines of code at standard size. + maxHeight: calc.multiply(vars.grid, 90), + }, + tablet: { + // Roughly 30 lines of code at standard size. + maxHeight: calc.multiply(vars.grid, 172), + }, + }), +]); diff --git a/src/components/CodeContainer.stories.tsx b/src/components/CodeContainer.stories.tsx new file mode 100644 index 00000000..cc0f2dc8 --- /dev/null +++ b/src/components/CodeContainer.stories.tsx @@ -0,0 +1,27 @@ +import 'loki/configure-react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import { CodeContainer as Component } from './CodeContainer'; + +export default { + title: 'Standalone/CodeContainer', + component: Component, +} satisfies Meta; + +type Story = StoryObj; + +export const NoLineNumbers: Story = { + args: { + code: 'query {\n version\n}', + language: 'graphql', + }, +}; + +export const LineNumbers: Story = { + args: { + code: 'query {\n version\n}', + language: 'graphql', + lineNumbers: true, + }, +}; diff --git a/src/components/CodeContainer.tsx b/src/components/CodeContainer.tsx new file mode 100644 index 00000000..1715fae9 --- /dev/null +++ b/src/components/CodeContainer.tsx @@ -0,0 +1,78 @@ +import { Box, Stack } from 'braid-design-system'; +import { Highlight, type Token } from 'prism-react-renderer'; + +import { Prism, themes } from '../private/Prism'; + +import * as styles from './CodeContainer.css'; +import * as codeStyles from '../../styles/code.css'; + +export const CodeContainer = ({ + code, + language, + lineNumbers, +}: { + code: string; + language: string; + lineNumbers?: boolean; +}) => ( + + + {({ getTokenProps, tokens }) => ( + + {lineNumbers ? : null} + + + + )} + + +); + +const LineNumbers = ({ count }: { count: number }) => { + const numbers = [...new Array(count)].map((_, index) => index + 1); + + return ( + + + {numbers.map((number) => ( + + {number} + + ))} + + + ); +}; + +type HighlightProps = Parameters< + Parameters[0]['children'] +>[0]; + +const Lines = ({ + getTokenProps, + lines, +}: { + getTokenProps: HighlightProps['getTokenProps']; + lines: Token[][]; +}) => ( + + + {lines.map((line, lineIndex) => ( + + + {line.map((token, tokenIndex) => { + const props = getTokenProps({ token }); + + return ; + })} + + + ))} + + +); diff --git a/src/index.ts b/src/index.ts index 78517dc6..c59e3638 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export { Blockquote } from './components/Blockquote'; export { CodeBlock } from './components/CodeBlock'; +export { CodeContainer } from './components/CodeContainer'; export { CopyableText } from './components/CopyableText'; export { InlineCode } from './components/InlineCode'; export { InternalLink } from './components/InternalLink'; diff --git a/styles/code.css.ts b/styles/code.css.ts index 5a23c27b..a94fc99a 100644 --- a/styles/code.css.ts +++ b/styles/code.css.ts @@ -1,5 +1,5 @@ import { createStyleObject, getCapHeight } from '@capsizecss/core'; -import { styleVariants } from '@vanilla-extract/css'; +import { style } from '@vanilla-extract/css'; import { responsiveStyle } from 'braid-design-system/css'; import type { Size } from '../src/private/size'; @@ -65,14 +65,11 @@ const monospaceFontStylesForTarget = ( leading: tokens.typography.code[size][target].rows * tokens.grid, }); -export const code = styleVariants( - { standard: null, large: null }, - (_, size) => ({ - fontFamily: monospaceFontFamily, +export const code = style({ + fontFamily: monospaceFontFamily, - ...responsiveStyle({ - mobile: monospaceFontStylesForTarget(size, 'mobile'), - tablet: monospaceFontStylesForTarget(size, 'tablet'), - }), + ...responsiveStyle({ + mobile: monospaceFontStylesForTarget('standard', 'mobile'), + tablet: monospaceFontStylesForTarget('standard', 'tablet'), }), -); +});