From 59ecbce6bb34e58a99abcddf5fc6797c4f44ea5e Mon Sep 17 00:00:00 2001 From: Alice Koreman Date: Sun, 25 Feb 2024 00:28:28 +0100 Subject: [PATCH 01/29] feat: add option for line-wrapping to prevent horizontal scrolling --- package.json | 1 + pages/code-view/with-line-wrapping.page.tsx | 35 +++++++++ .../__snapshots__/documenter.test.ts.snap | 10 ++- src/code-view/__tests__/code-view.test.tsx | 31 +++++++- src/code-view/highlight/index.tsx | 4 +- src/code-view/interfaces.ts | 9 ++- src/code-view/internal.tsx | 75 +++++++++++-------- src/code-view/styles.scss | 64 +++++++--------- src/test-utils/dom/code-view/index.ts | 4 +- 9 files changed, 158 insertions(+), 75 deletions(-) create mode 100644 pages/code-view/with-line-wrapping.page.tsx diff --git a/package.json b/package.json index 1ccf2ce..77eda3f 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "prebuild": "rm -rf lib dist .cache", "build": "npm-run-all build:pkg --parallel build:src:* --parallel build:pages:* build:themeable", "lint": "eslint --ignore-path .gitignore --ext ts,tsx,js . && stylelint --ignore-path .gitignore '{src,pages}/**/*.{css,scss}'", + "fix": "eslint --fix --ignore-path .gitignore --ext ts,tsx,js . && stylelint --ignore-path .gitignore '{src,pages}/**/*.{css,scss}'", "prepare": "husky install", "test:unit": "vitest run --config vite.unit.config.mjs", "test:visual": "run-p -r preview test:visual:vitest", diff --git a/pages/code-view/with-line-wrapping.page.tsx b/pages/code-view/with-line-wrapping.page.tsx new file mode 100644 index 0000000..169e226 --- /dev/null +++ b/pages/code-view/with-line-wrapping.page.tsx @@ -0,0 +1,35 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { SpaceBetween } from "@cloudscape-design/components"; +import { CodeView } from "../../lib/components"; +import { ScreenshotArea } from "../screenshot-area"; +export default function CodeViewPage() { + return ( + +

Code View

+ + No wrapping, no line numbers + + No wrapping, line numbers + + Wrapping, no line numbers + + Wrapping, line numbers + + +
+ ); +} diff --git a/src/__tests__/__snapshots__/documenter.test.ts.snap b/src/__tests__/__snapshots__/documenter.test.ts.snap index 2a435b8..341fdbb 100644 --- a/src/__tests__/__snapshots__/documenter.test.ts.snap +++ b/src/__tests__/__snapshots__/documenter.test.ts.snap @@ -28,7 +28,7 @@ exports[`definition for code-view matches the snapshot > code-view 1`] = ` "description": "A function to perform custom syntax highlighting.", "name": "highlight", "optional": true, - "type": "(code: string) => React.ReactNode", + "type": "(code: string) => ReactElement", }, { "description": "Controls the display of line numbers. @@ -38,6 +38,14 @@ Defaults to \`false\`. "optional": true, "type": "boolean", }, + { + "description": "Controls lines wrap when overflowing on the right side. +Defaults to \`false\`. +", + "name": "lineWrapping", + "optional": true, + "type": "boolean", + }, ], "regions": [ { diff --git a/src/code-view/__tests__/code-view.test.tsx b/src/code-view/__tests__/code-view.test.tsx index 3fc3d77..02cb68d 100644 --- a/src/code-view/__tests__/code-view.test.tsx +++ b/src/code-view/__tests__/code-view.test.tsx @@ -14,7 +14,7 @@ describe("CodeView", () => { test("correctly renders component content", () => { render(); const wrapper = createWrapper()!.findCodeView(); - expect(wrapper!.findContent().getElement().textContent).toBe("Hello World"); + expect(wrapper!.findContent()[0].getElement()).toHaveTextContent("Hello World"); }); test("correctly renders copy button slot", () => { @@ -26,7 +26,9 @@ describe("CodeView", () => { test("correctly renders line numbers", () => { render(); const wrapper = createWrapper()!.findCodeView(); - expect(wrapper!.findByClassName(styles["line-numbers"])!.getElement()).toHaveTextContent("123"); + expect(wrapper!.findAllByClassName(styles["line-number"])[0]!.getElement()).toHaveTextContent("1"); + expect(wrapper!.findAllByClassName(styles["line-number"])[1]!.getElement()).toHaveTextContent("2"); + expect(wrapper!.findAllByClassName(styles["line-number"])[2]!.getElement()).toHaveTextContent("3"); }); test("correctly renders aria-label", () => { @@ -57,13 +59,13 @@ describe("CodeView", () => { > ); const wrapper = createWrapper().findCodeView()!; - expect(wrapper!.findContent().getElement().innerHTML).toContain('class="tokenized"'); + expect(wrapper!.findContent()[0].getElement().innerHTML).toContain("tokenized"); }); test("correctly tokenizes content if highlight is set to language rules", () => { render(); const wrapper = createWrapper().findCodeView()!; - const element = wrapper!.findContent().getElement(); + const element = wrapper!.findContent()[0].getElement(); // Check that the content is tokenized following typescript rules. expect(getByText(element, "const")).toHaveClass("ace_type"); @@ -71,4 +73,25 @@ describe("CodeView", () => { expect(getByText(element, "string")).toHaveClass("ace_type"); expect(getByText(element, '"world"')).toHaveClass("ace_string"); }); + + test("sets nowrap class to line if linesWrapping undefined", () => { + render(); + const wrapper = createWrapper().findCodeView()!; + const element = wrapper!.findContent()[0].getElement(); + expect(element.outerHTML).toContain("code-line-nowrap"); + }); + + test("sets nowrap class to line if linesWrapping false", () => { + render(); + const wrapper = createWrapper().findCodeView()!; + const element = wrapper!.findContent()[0].getElement(); + expect(element.outerHTML).toContain("code-line-nowrap"); + }); + + test("sets wrap class to line if linesWrapping true", () => { + render(); + const wrapper = createWrapper().findCodeView()!; + const element = wrapper!.findContent()[0].getElement(); + expect(element.outerHTML).toContain("code-line-wrap"); + }); }); diff --git a/src/code-view/highlight/index.tsx b/src/code-view/highlight/index.tsx index aa4c066..ca5194a 100644 --- a/src/code-view/highlight/index.tsx +++ b/src/code-view/highlight/index.tsx @@ -6,7 +6,9 @@ import { Fragment } from "react"; import "ace-code/styles/theme/cloud_editor.css"; import "ace-code/styles/theme/cloud_editor_dark.css"; -export function createHighlight(rules: Ace.HighlightRules) { +type CreateHighlightType = (code: string) => React.ReactElement; + +export function createHighlight(rules: Ace.HighlightRules): CreateHighlightType { return (code: string) => { const tokens = tokenize(code, rules); return ( diff --git a/src/code-view/interfaces.ts b/src/code-view/interfaces.ts index e6131de..d01a7cf 100644 --- a/src/code-view/interfaces.ts +++ b/src/code-view/interfaces.ts @@ -24,6 +24,13 @@ export interface CodeViewProps { */ lineNumbers?: boolean; + /** + * Controls lines wrap when overflowing on the right side. + * + * Defaults to `false`. + */ + lineWrapping?: boolean; + /** * An optional slot to display a button to enable users to perform actions, such as copy or download the code snippet. * @@ -34,5 +41,5 @@ export interface CodeViewProps { * A function to perform custom syntax highlighting. * */ - highlight?: (code: string) => React.ReactNode; + highlight?: (code: string) => React.ReactElement; } diff --git a/src/code-view/internal.tsx b/src/code-view/internal.tsx index e605bc6..c1ce37f 100644 --- a/src/code-view/internal.tsx +++ b/src/code-view/internal.tsx @@ -2,71 +2,84 @@ // SPDX-License-Identifier: Apache-2.0 import { useCurrentMode } from "@cloudscape-design/component-toolkit/internal"; import Box from "@cloudscape-design/components/box"; +import { TextHighlightRules } from "ace-code/src/mode/text_highlight_rules"; import clsx from "clsx"; import { useRef } from "react"; +import { Children } from "react"; import { InternalBaseComponentProps, getBaseProps } from "../internal/base-component/use-base-component"; +import { createHighlight } from "./highlight"; import { CodeViewProps } from "./interfaces"; import styles from "./styles.css.js"; const ACE_CLASSES = { light: "ace-cloud_editor", dark: "ace-cloud_editor_dark" }; -function getLineNumbers(content: string) { - return content.split("\n").map((_, n) => n + 1); -} - type InternalCodeViewProps = CodeViewProps & InternalBaseComponentProps; +const textHighlight = createHighlight(new TextHighlightRules()); + export function InternalCodeView({ content, actions, lineNumbers, + lineWrapping, highlight, ariaLabel, ariaLabelledby, __internalRootRef = null, ...props }: InternalCodeViewProps) { - const code = highlight ? highlight(content) : {content}; const baseProps = getBaseProps(props); const preRef = useRef(null); const darkMode = useCurrentMode(preRef) === "dark"; const regionProps = ariaLabel || ariaLabelledby ? { role: "region" } : {}; + // Create tokenized elements of the content. + const code = highlight ? highlight(content) : textHighlight(content); + return (
-
- - {lineNumbers && ( -
- {getLineNumbers(content).map((number) => ( - {number} - ))} -
- )} -
-
-          
-            {code}
-          
-        
- {actions &&
{actions}
} -
+ + + + + + + {Children.map(code.props.children, (child, index) => { + return ( + + {lineNumbers && ( + + )} + + + ); + })} + +
+ + {index + 1} + + + + + {child} + + +
+ {actions &&
{actions}
}
); } diff --git a/src/code-view/styles.scss b/src/code-view/styles.scss index 373ee08..831f8d2 100644 --- a/src/code-view/styles.scss +++ b/src/code-view/styles.scss @@ -5,57 +5,51 @@ $color-background-code-view-dark: #282c34; .root { position: relative; - &-with-numbers { - display: flex; - align-items: stretch; - } -} - -.code { :global(.awsui-dark-mode) &, :global(.awsui-polaris-dark-mode) & { background-color: $color-background-code-view-dark; } - &-with-line-numbers { - border-start-start-radius: 0; - border-end-start-radius: 0; - flex: 1; - } - &-with-actions { - min-block-size: cs.$space-scaled-xxl; - padding-inline-end: calc(2 * cs.$space-static-xxxl); - align-items: center; - } background-color: $color-background-code-view-light; - display: flex; + overflow-x: auto; +} + +.code-table { border-start-start-radius: cs.$border-radius-tiles; border-start-end-radius: cs.$border-radius-tiles; border-end-start-radius: cs.$border-radius-tiles; border-end-end-radius: cs.$border-radius-tiles; - padding-block: cs.$space-static-xs; - padding-inline: cs.$space-static-xs; - margin-block: 0; - margin-inline: 0; - overflow: auto; + padding-top: cs.$space-static-xs; + padding-bottom: cs.$space-static-xs; + table-layout: auto; + width: 100%; + border-spacing: 0; } -.line-numbers { +.line-number { + border-right-color: cs.$color-border-divider-default; :global(.awsui-dark-mode) &, :global(.awsui-polaris-dark-mode) & { background-color: $color-background-code-view-dark; } - border-start-start-radius: cs.$border-radius-tiles; - border-end-start-radius: cs.$border-radius-tiles; background-color: $color-background-code-view-light; - padding-block: cs.$space-static-xs; - padding-inline: cs.$space-static-xs; - display: flex; - flex-direction: column; - align-items: flex-end; - border-inline-end-width: 1px; - border-inline-end-style: solid; - border-inline-end-color: cs.$color-border-divider-default; - justify-content: center; + vertical-align: text-top; + position: sticky; + left: 0; + border-right-width: 1px; + border-right-style: solid; + padding-left: cs.$space-static-xs; + padding-right: cs.$space-static-xs; +} + +.code-line { + padding-left: cs.$space-static-xs; + padding-right: cs.$space-static-xs; + &-wrap { + white-space: pre-wrap; + } + &-nowrap { + white-space: nowrap; + } } .actions { diff --git a/src/test-utils/dom/code-view/index.ts b/src/test-utils/dom/code-view/index.ts index a228873..55e8f70 100644 --- a/src/test-utils/dom/code-view/index.ts +++ b/src/test-utils/dom/code-view/index.ts @@ -6,8 +6,8 @@ import styles from "../../../code-view/styles.selectors.js"; export default class CodeViewWrapper extends ComponentWrapper { static rootSelector: string = styles.root; - findContent(): ElementWrapper { - return this.findByClassName(styles.code)!; + findContent() { + return this.findAllByClassName(styles["code-line"])!; } findActions(): ElementWrapper | null { From 6cd99c46e1d8cd67856cba0b1df2fa8414e8d3ab Mon Sep 17 00:00:00 2001 From: Alice Koreman Date: Sun, 25 Feb 2024 01:08:27 +0100 Subject: [PATCH 02/29] chore: remove accidentally commited npm script --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 8ce0bcc..e8c83d9 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ "prebuild": "rm -rf lib dist .cache", "build": "npm-run-all build:pkg --parallel build:src:* --parallel build:pages:* build:themeable", "lint": "eslint --ignore-path .gitignore --ext ts,tsx,js . && stylelint --ignore-path .gitignore '{src,pages}/**/*.{css,scss}'", - "fix": "eslint --fix --ignore-path .gitignore --ext ts,tsx,js . && stylelint --ignore-path .gitignore '{src,pages}/**/*.{css,scss}'", "prepare": "husky install", "test:unit": "vitest run --config vite.unit.config.mjs", "test:visual": "run-p -r preview test:visual:vitest", From b40b2b0e016c438dae21343066c73a1df0be0df4 Mon Sep 17 00:00:00 2001 From: Alice Koreman Date: Sun, 25 Feb 2024 17:00:31 +0100 Subject: [PATCH 03/29] fix: make line numbers unselectable --- src/code-view/internal.tsx | 2 +- src/code-view/styles.scss | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/code-view/internal.tsx b/src/code-view/internal.tsx index c1ce37f..fd46c32 100644 --- a/src/code-view/internal.tsx +++ b/src/code-view/internal.tsx @@ -56,7 +56,7 @@ export function InternalCodeView({ return ( {lineNumbers && ( - + {index + 1} diff --git a/src/code-view/styles.scss b/src/code-view/styles.scss index 831f8d2..8e61510 100644 --- a/src/code-view/styles.scss +++ b/src/code-view/styles.scss @@ -41,6 +41,11 @@ $color-background-code-view-dark: #282c34; padding-right: cs.$space-static-xs; } +.unselectable { + -webkit-user-select: none; + user-select: none; +} + .code-line { padding-left: cs.$space-static-xs; padding-right: cs.$space-static-xs; From 6b9b6e727ddfa0071401ceeec1098599e83b9e88 Mon Sep 17 00:00:00 2001 From: Alice Koreman Date: Sun, 25 Feb 2024 17:42:02 +0100 Subject: [PATCH 04/29] chore: replace Ace text highlight rules with in-repo function --- src/code-view/internal.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/code-view/internal.tsx b/src/code-view/internal.tsx index fd46c32..e56e90b 100644 --- a/src/code-view/internal.tsx +++ b/src/code-view/internal.tsx @@ -2,12 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 import { useCurrentMode } from "@cloudscape-design/component-toolkit/internal"; import Box from "@cloudscape-design/components/box"; -import { TextHighlightRules } from "ace-code/src/mode/text_highlight_rules"; import clsx from "clsx"; import { useRef } from "react"; import { Children } from "react"; import { InternalBaseComponentProps, getBaseProps } from "../internal/base-component/use-base-component"; -import { createHighlight } from "./highlight"; import { CodeViewProps } from "./interfaces"; import styles from "./styles.css.js"; @@ -15,7 +13,21 @@ const ACE_CLASSES = { light: "ace-cloud_editor", dark: "ace-cloud_editor_dark" } type InternalCodeViewProps = CodeViewProps & InternalBaseComponentProps; -const textHighlight = createHighlight(new TextHighlightRules()); +// Breaks down the input code for non-highlighted code-view into React +// Elements similar to how a highlight function would do. +const textHighlight = (code: string) => { + const lines = code.split("\n"); + return ( + + {lines.map((line, lineIndex) => ( + + {line} + {"\n"} + + ))} + + ); +}; export function InternalCodeView({ content, From 81bc51e23a8514560e51944a0fde6ec831f08225 Mon Sep 17 00:00:00 2001 From: Ruben Carvalho Date: Tue, 12 Mar 2024 11:26:14 +0100 Subject: [PATCH 05/29] chore: Fix minimum-height and actions absolute positioning --- pages/code-view/with-actions-button.page.tsx | 6 ++ src/code-view/internal.tsx | 70 +++++++++++--------- src/code-view/styles.scss | 15 ++++- 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/pages/code-view/with-actions-button.page.tsx b/pages/code-view/with-actions-button.page.tsx index 3834fac..727f0d6 100644 --- a/pages/code-view/with-actions-button.page.tsx +++ b/pages/code-view/with-actions-button.page.tsx @@ -14,6 +14,12 @@ export default function CodeViewPage() { actions={} content={`Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.`} /> + } + content={`Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.`} + /> ); diff --git a/src/code-view/internal.tsx b/src/code-view/internal.tsx index e56e90b..b326e45 100644 --- a/src/code-view/internal.tsx +++ b/src/code-view/internal.tsx @@ -58,39 +58,47 @@ export function InternalCodeView({ aria-labelledby={ariaLabelledby} ref={__internalRootRef} > - - - - - - - {Children.map(code.props.children, (child, index) => { - return ( - - {lineNumbers && ( - + ); + })} + +
- - {index + 1} +
+ + + + + + + {Children.map(code.props.children, (child, index) => { + return ( + + {lineNumbers && ( + + )} + - )} - - - ); - })} - -
+ + {index + 1} + + + + + {child} + - - - {child} - - -
+
+ {actions &&
{actions}
} ); diff --git a/src/code-view/styles.scss b/src/code-view/styles.scss index 8e61510..83461f0 100644 --- a/src/code-view/styles.scss +++ b/src/code-view/styles.scss @@ -10,6 +10,9 @@ $color-background-code-view-dark: #282c34; background-color: $color-background-code-view-dark; } background-color: $color-background-code-view-light; +} + +.scroll-container { overflow-x: auto; } @@ -23,6 +26,14 @@ $color-background-code-view-dark: #282c34; table-layout: auto; width: 100%; border-spacing: 0; + + &-with-actions { + min-height: calc(2 * cs.$space-scaled-xs + cs.$space-scaled-xxl); + } +} + +.code-table-with-actions.code-table-with-line-wrapping { + padding-inline-end: cs.$space-static-xxl; } .line-number { @@ -59,7 +70,7 @@ $color-background-code-view-dark: #282c34; .actions { position: absolute; - inset-block-start: cs.$space-static-xs; - inset-inline-end: cs.$space-static-xs; + inset-block-start: cs.$space-scaled-xs; + inset-inline-end: cs.$space-scaled-xs; padding-inline-start: cs.$space-container-horizontal; } From 4cd5711d9852368d630a38fbf6df8ca622bee493 Mon Sep 17 00:00:00 2001 From: Ruben Carvalho Date: Tue, 12 Mar 2024 12:48:24 +0100 Subject: [PATCH 06/29] fix: Fix code indentation by setting white-space to pre --- pages/code-view/simple.page.tsx | 3 +++ pages/code-view/with-line-wrapping.page.tsx | 5 +++++ src/code-view/styles.scss | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pages/code-view/simple.page.tsx b/pages/code-view/simple.page.tsx index 4f2a634..0d3d00e 100644 --- a/pages/code-view/simple.page.tsx +++ b/pages/code-view/simple.page.tsx @@ -8,6 +8,9 @@ export default function CodeViewPage() {

Code View

+
); } diff --git a/pages/code-view/with-line-wrapping.page.tsx b/pages/code-view/with-line-wrapping.page.tsx index 169e226..c8c9d31 100644 --- a/pages/code-view/with-line-wrapping.page.tsx +++ b/pages/code-view/with-line-wrapping.page.tsx @@ -29,6 +29,11 @@ export default function CodeViewPage() { lineNumbers={true} content={`Hello this is a short line.\nHello this is a long line. Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\nHello this is another short line.`} /> + ); diff --git a/src/code-view/styles.scss b/src/code-view/styles.scss index 83461f0..fa0516b 100644 --- a/src/code-view/styles.scss +++ b/src/code-view/styles.scss @@ -64,7 +64,7 @@ $color-background-code-view-dark: #282c34; white-space: pre-wrap; } &-nowrap { - white-space: nowrap; + white-space: pre; } } From 1cdc45ff09d0e00fe1ea6bfd78c8126cf1ad044e Mon Sep 17 00:00:00 2001 From: Ruben Carvalho Date: Tue, 12 Mar 2024 12:54:10 +0100 Subject: [PATCH 07/29] chore: Fix code color and lines Box variant --- src/code-view/internal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/code-view/internal.tsx b/src/code-view/internal.tsx index b326e45..6baab1a 100644 --- a/src/code-view/internal.tsx +++ b/src/code-view/internal.tsx @@ -76,13 +76,13 @@ export function InternalCodeView({ {lineNumbers && ( - + {index + 1} )} - + Date: Tue, 12 Mar 2024 13:24:53 +0100 Subject: [PATCH 08/29] chore: Add multi-line test --- src/code-view/__tests__/code-view.test.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/code-view/__tests__/code-view.test.tsx b/src/code-view/__tests__/code-view.test.tsx index 02cb68d..1e79f44 100644 --- a/src/code-view/__tests__/code-view.test.tsx +++ b/src/code-view/__tests__/code-view.test.tsx @@ -11,12 +11,26 @@ describe("CodeView", () => { afterEach(() => { cleanup(); }); - test("correctly renders component content", () => { + test("correctly renders simple content", () => { render(); const wrapper = createWrapper()!.findCodeView(); expect(wrapper!.findContent()[0].getElement()).toHaveTextContent("Hello World"); }); + test("correctly renders multi line content", () => { + const content = `# Hello World\n\nThis is a markdown example.`; + + render(); + const wrapper = createWrapper()!.findCodeView(); + const contentElements = wrapper!.findContent(); + expect(contentElements.length).toEqual(3); + const renderedContent = contentElements + .map((element) => element.getElement().textContent) + .join("") + .trim(); + expect(renderedContent).toBe(content); + }); + test("correctly renders copy button slot", () => { render(Copy}>); const wrapper = createWrapper()!.findCodeView(); From fb13d495d8e4bd502909672377153171a5111374 Mon Sep 17 00:00:00 2001 From: Ruben Carvalho Date: Tue, 12 Mar 2024 13:43:12 +0100 Subject: [PATCH 09/29] chore: Increase size-limit by 8 bytes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b408146..341bc08 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "size-limit": [ { "path": "lib/components/index.js", - "limit": "0.5kb" + "limit": "508b" }, { "path": "lib/components/code-view/highlight/javascript.js", From 6da84dcb6fc8060a0d4536dc615989fc690baf90 Mon Sep 17 00:00:00 2001 From: Ruben Carvalho Date: Tue, 12 Mar 2024 16:03:18 +0100 Subject: [PATCH 10/29] chore: Use logical properties --- src/code-view/styles.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/code-view/styles.scss b/src/code-view/styles.scss index fa0516b..b7f8aaa 100644 --- a/src/code-view/styles.scss +++ b/src/code-view/styles.scss @@ -21,8 +21,8 @@ $color-background-code-view-dark: #282c34; border-start-end-radius: cs.$border-radius-tiles; border-end-start-radius: cs.$border-radius-tiles; border-end-end-radius: cs.$border-radius-tiles; - padding-top: cs.$space-static-xs; - padding-bottom: cs.$space-static-xs; + padding-block-start: cs.$space-static-xs; + padding-block-end: cs.$space-static-xs; table-layout: auto; width: 100%; border-spacing: 0; From 06587166269205e37713c1ebd679872bb58933ab Mon Sep 17 00:00:00 2001 From: Ruben Carvalho Date: Tue, 12 Mar 2024 16:18:30 +0100 Subject: [PATCH 11/29] chore: Update findContent() test to be backward compatible --- src/code-view/__tests__/code-view.test.tsx | 27 ++++++++-------------- src/test-utils/dom/code-view/index.ts | 4 ++-- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/code-view/__tests__/code-view.test.tsx b/src/code-view/__tests__/code-view.test.tsx index 1e79f44..dca16a3 100644 --- a/src/code-view/__tests__/code-view.test.tsx +++ b/src/code-view/__tests__/code-view.test.tsx @@ -14,21 +14,14 @@ describe("CodeView", () => { test("correctly renders simple content", () => { render(); const wrapper = createWrapper()!.findCodeView(); - expect(wrapper!.findContent()[0].getElement()).toHaveTextContent("Hello World"); + expect(wrapper!.findContent().getElement()).toHaveTextContent("Hello World"); }); test("correctly renders multi line content", () => { - const content = `# Hello World\n\nThis is a markdown example.`; - - render(); - const wrapper = createWrapper()!.findCodeView(); - const contentElements = wrapper!.findContent(); - expect(contentElements.length).toEqual(3); - const renderedContent = contentElements - .map((element) => element.getElement().textContent) - .join("") - .trim(); - expect(renderedContent).toBe(content); + render(); + const wrapper = createWrapper()!.findCodeView()!; + const content = wrapper.findContent(); + expect(content.getElement()).toHaveTextContent("# Hello World This is a markdown example."); }); test("correctly renders copy button slot", () => { @@ -73,13 +66,13 @@ describe("CodeView", () => { > ); const wrapper = createWrapper().findCodeView()!; - expect(wrapper!.findContent()[0].getElement().innerHTML).toContain("tokenized"); + expect(wrapper!.findContent().getElement().innerHTML).toContain("tokenized"); }); test("correctly tokenizes content if highlight is set to language rules", () => { render(); const wrapper = createWrapper().findCodeView()!; - const element = wrapper!.findContent()[0].getElement(); + const element = wrapper!.findContent().getElement(); // Check that the content is tokenized following typescript rules. expect(getByText(element, "const")).toHaveClass("ace_type"); @@ -91,21 +84,21 @@ describe("CodeView", () => { test("sets nowrap class to line if linesWrapping undefined", () => { render(); const wrapper = createWrapper().findCodeView()!; - const element = wrapper!.findContent()[0].getElement(); + const element = wrapper!.findContent().getElement(); expect(element.outerHTML).toContain("code-line-nowrap"); }); test("sets nowrap class to line if linesWrapping false", () => { render(); const wrapper = createWrapper().findCodeView()!; - const element = wrapper!.findContent()[0].getElement(); + const element = wrapper!.findContent().getElement(); expect(element.outerHTML).toContain("code-line-nowrap"); }); test("sets wrap class to line if linesWrapping true", () => { render(); const wrapper = createWrapper().findCodeView()!; - const element = wrapper!.findContent()[0].getElement(); + const element = wrapper!.findContent().getElement(); expect(element.outerHTML).toContain("code-line-wrap"); }); }); diff --git a/src/test-utils/dom/code-view/index.ts b/src/test-utils/dom/code-view/index.ts index 55e8f70..434dfc9 100644 --- a/src/test-utils/dom/code-view/index.ts +++ b/src/test-utils/dom/code-view/index.ts @@ -6,8 +6,8 @@ import styles from "../../../code-view/styles.selectors.js"; export default class CodeViewWrapper extends ComponentWrapper { static rootSelector: string = styles.root; - findContent() { - return this.findAllByClassName(styles["code-line"])!; + findContent(): ElementWrapper { + return this.find("tbody")!; } findActions(): ElementWrapper | null { From 73a2b338d12bcdd3264258a91e9de383beee70bc Mon Sep 17 00:00:00 2001 From: Gethin Webster Date: Wed, 20 Mar 2024 13:11:30 +0100 Subject: [PATCH 12/29] Give role="presentation" --- src/code-view/internal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/code-view/internal.tsx b/src/code-view/internal.tsx index 6baab1a..b5810c9 100644 --- a/src/code-view/internal.tsx +++ b/src/code-view/internal.tsx @@ -60,6 +60,7 @@ export function InternalCodeView({ >
Date: Sun, 25 Feb 2024 00:28:28 +0100 Subject: [PATCH 13/29] feat: add option for line-wrapping to prevent horizontal scrolling --- package.json | 1 + pages/code-view/with-line-wrapping.page.tsx | 35 +++++++++ .../__snapshots__/documenter.test.ts.snap | 10 ++- src/code-view/__tests__/code-view.test.tsx | 31 +++++++- src/code-view/highlight/index.tsx | 4 +- src/code-view/interfaces.ts | 9 ++- src/code-view/internal.tsx | 75 +++++++++++-------- src/code-view/styles.scss | 64 +++++++--------- src/test-utils/dom/code-view/index.ts | 4 +- 9 files changed, 158 insertions(+), 75 deletions(-) create mode 100644 pages/code-view/with-line-wrapping.page.tsx diff --git a/package.json b/package.json index b408146..0174e77 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "build": "npm-run-all build:pkg --parallel build:src:* --parallel build:pages:* build:themeable", "postbuild": "size-limit", "lint": "eslint --ignore-path .gitignore --ext ts,tsx,js . && stylelint --ignore-path .gitignore '{src,pages}/**/*.{css,scss}'", + "fix": "eslint --fix --ignore-path .gitignore --ext ts,tsx,js . && stylelint --ignore-path .gitignore '{src,pages}/**/*.{css,scss}'", "prepare": "husky install", "test:unit": "vitest run --config vite.unit.config.mjs", "test:visual": "run-p -r preview test:visual:vitest", diff --git a/pages/code-view/with-line-wrapping.page.tsx b/pages/code-view/with-line-wrapping.page.tsx new file mode 100644 index 0000000..169e226 --- /dev/null +++ b/pages/code-view/with-line-wrapping.page.tsx @@ -0,0 +1,35 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { SpaceBetween } from "@cloudscape-design/components"; +import { CodeView } from "../../lib/components"; +import { ScreenshotArea } from "../screenshot-area"; +export default function CodeViewPage() { + return ( + +

Code View

+ + No wrapping, no line numbers + + No wrapping, line numbers + + Wrapping, no line numbers + + Wrapping, line numbers + + +
+ ); +} diff --git a/src/__tests__/__snapshots__/documenter.test.ts.snap b/src/__tests__/__snapshots__/documenter.test.ts.snap index 2a435b8..341fdbb 100644 --- a/src/__tests__/__snapshots__/documenter.test.ts.snap +++ b/src/__tests__/__snapshots__/documenter.test.ts.snap @@ -28,7 +28,7 @@ exports[`definition for code-view matches the snapshot > code-view 1`] = ` "description": "A function to perform custom syntax highlighting.", "name": "highlight", "optional": true, - "type": "(code: string) => React.ReactNode", + "type": "(code: string) => ReactElement", }, { "description": "Controls the display of line numbers. @@ -38,6 +38,14 @@ Defaults to \`false\`. "optional": true, "type": "boolean", }, + { + "description": "Controls lines wrap when overflowing on the right side. +Defaults to \`false\`. +", + "name": "lineWrapping", + "optional": true, + "type": "boolean", + }, ], "regions": [ { diff --git a/src/code-view/__tests__/code-view.test.tsx b/src/code-view/__tests__/code-view.test.tsx index 3fc3d77..02cb68d 100644 --- a/src/code-view/__tests__/code-view.test.tsx +++ b/src/code-view/__tests__/code-view.test.tsx @@ -14,7 +14,7 @@ describe("CodeView", () => { test("correctly renders component content", () => { render(); const wrapper = createWrapper()!.findCodeView(); - expect(wrapper!.findContent().getElement().textContent).toBe("Hello World"); + expect(wrapper!.findContent()[0].getElement()).toHaveTextContent("Hello World"); }); test("correctly renders copy button slot", () => { @@ -26,7 +26,9 @@ describe("CodeView", () => { test("correctly renders line numbers", () => { render(); const wrapper = createWrapper()!.findCodeView(); - expect(wrapper!.findByClassName(styles["line-numbers"])!.getElement()).toHaveTextContent("123"); + expect(wrapper!.findAllByClassName(styles["line-number"])[0]!.getElement()).toHaveTextContent("1"); + expect(wrapper!.findAllByClassName(styles["line-number"])[1]!.getElement()).toHaveTextContent("2"); + expect(wrapper!.findAllByClassName(styles["line-number"])[2]!.getElement()).toHaveTextContent("3"); }); test("correctly renders aria-label", () => { @@ -57,13 +59,13 @@ describe("CodeView", () => { > ); const wrapper = createWrapper().findCodeView()!; - expect(wrapper!.findContent().getElement().innerHTML).toContain('class="tokenized"'); + expect(wrapper!.findContent()[0].getElement().innerHTML).toContain("tokenized"); }); test("correctly tokenizes content if highlight is set to language rules", () => { render(); const wrapper = createWrapper().findCodeView()!; - const element = wrapper!.findContent().getElement(); + const element = wrapper!.findContent()[0].getElement(); // Check that the content is tokenized following typescript rules. expect(getByText(element, "const")).toHaveClass("ace_type"); @@ -71,4 +73,25 @@ describe("CodeView", () => { expect(getByText(element, "string")).toHaveClass("ace_type"); expect(getByText(element, '"world"')).toHaveClass("ace_string"); }); + + test("sets nowrap class to line if linesWrapping undefined", () => { + render(); + const wrapper = createWrapper().findCodeView()!; + const element = wrapper!.findContent()[0].getElement(); + expect(element.outerHTML).toContain("code-line-nowrap"); + }); + + test("sets nowrap class to line if linesWrapping false", () => { + render(); + const wrapper = createWrapper().findCodeView()!; + const element = wrapper!.findContent()[0].getElement(); + expect(element.outerHTML).toContain("code-line-nowrap"); + }); + + test("sets wrap class to line if linesWrapping true", () => { + render(); + const wrapper = createWrapper().findCodeView()!; + const element = wrapper!.findContent()[0].getElement(); + expect(element.outerHTML).toContain("code-line-wrap"); + }); }); diff --git a/src/code-view/highlight/index.tsx b/src/code-view/highlight/index.tsx index aa4c066..ca5194a 100644 --- a/src/code-view/highlight/index.tsx +++ b/src/code-view/highlight/index.tsx @@ -6,7 +6,9 @@ import { Fragment } from "react"; import "ace-code/styles/theme/cloud_editor.css"; import "ace-code/styles/theme/cloud_editor_dark.css"; -export function createHighlight(rules: Ace.HighlightRules) { +type CreateHighlightType = (code: string) => React.ReactElement; + +export function createHighlight(rules: Ace.HighlightRules): CreateHighlightType { return (code: string) => { const tokens = tokenize(code, rules); return ( diff --git a/src/code-view/interfaces.ts b/src/code-view/interfaces.ts index e6131de..d01a7cf 100644 --- a/src/code-view/interfaces.ts +++ b/src/code-view/interfaces.ts @@ -24,6 +24,13 @@ export interface CodeViewProps { */ lineNumbers?: boolean; + /** + * Controls lines wrap when overflowing on the right side. + * + * Defaults to `false`. + */ + lineWrapping?: boolean; + /** * An optional slot to display a button to enable users to perform actions, such as copy or download the code snippet. * @@ -34,5 +41,5 @@ export interface CodeViewProps { * A function to perform custom syntax highlighting. * */ - highlight?: (code: string) => React.ReactNode; + highlight?: (code: string) => React.ReactElement; } diff --git a/src/code-view/internal.tsx b/src/code-view/internal.tsx index e605bc6..c1ce37f 100644 --- a/src/code-view/internal.tsx +++ b/src/code-view/internal.tsx @@ -2,71 +2,84 @@ // SPDX-License-Identifier: Apache-2.0 import { useCurrentMode } from "@cloudscape-design/component-toolkit/internal"; import Box from "@cloudscape-design/components/box"; +import { TextHighlightRules } from "ace-code/src/mode/text_highlight_rules"; import clsx from "clsx"; import { useRef } from "react"; +import { Children } from "react"; import { InternalBaseComponentProps, getBaseProps } from "../internal/base-component/use-base-component"; +import { createHighlight } from "./highlight"; import { CodeViewProps } from "./interfaces"; import styles from "./styles.css.js"; const ACE_CLASSES = { light: "ace-cloud_editor", dark: "ace-cloud_editor_dark" }; -function getLineNumbers(content: string) { - return content.split("\n").map((_, n) => n + 1); -} - type InternalCodeViewProps = CodeViewProps & InternalBaseComponentProps; +const textHighlight = createHighlight(new TextHighlightRules()); + export function InternalCodeView({ content, actions, lineNumbers, + lineWrapping, highlight, ariaLabel, ariaLabelledby, __internalRootRef = null, ...props }: InternalCodeViewProps) { - const code = highlight ? highlight(content) : {content}; const baseProps = getBaseProps(props); const preRef = useRef(null); const darkMode = useCurrentMode(preRef) === "dark"; const regionProps = ariaLabel || ariaLabelledby ? { role: "region" } : {}; + // Create tokenized elements of the content. + const code = highlight ? highlight(content) : textHighlight(content); + return (
-
- - {lineNumbers && ( -
- {getLineNumbers(content).map((number) => ( - {number} - ))} -
- )} -
-
-          
-            {code}
-          
-        
- {actions &&
{actions}
} -
+
+ + + + + + {Children.map(code.props.children, (child, index) => { + return ( + + {lineNumbers && ( + + )} + + + ); + })} + +
+ + {index + 1} + + + + + {child} + + +
+ {actions &&
{actions}
} ); } diff --git a/src/code-view/styles.scss b/src/code-view/styles.scss index 373ee08..831f8d2 100644 --- a/src/code-view/styles.scss +++ b/src/code-view/styles.scss @@ -5,57 +5,51 @@ $color-background-code-view-dark: #282c34; .root { position: relative; - &-with-numbers { - display: flex; - align-items: stretch; - } -} - -.code { :global(.awsui-dark-mode) &, :global(.awsui-polaris-dark-mode) & { background-color: $color-background-code-view-dark; } - &-with-line-numbers { - border-start-start-radius: 0; - border-end-start-radius: 0; - flex: 1; - } - &-with-actions { - min-block-size: cs.$space-scaled-xxl; - padding-inline-end: calc(2 * cs.$space-static-xxxl); - align-items: center; - } background-color: $color-background-code-view-light; - display: flex; + overflow-x: auto; +} + +.code-table { border-start-start-radius: cs.$border-radius-tiles; border-start-end-radius: cs.$border-radius-tiles; border-end-start-radius: cs.$border-radius-tiles; border-end-end-radius: cs.$border-radius-tiles; - padding-block: cs.$space-static-xs; - padding-inline: cs.$space-static-xs; - margin-block: 0; - margin-inline: 0; - overflow: auto; + padding-top: cs.$space-static-xs; + padding-bottom: cs.$space-static-xs; + table-layout: auto; + width: 100%; + border-spacing: 0; } -.line-numbers { +.line-number { + border-right-color: cs.$color-border-divider-default; :global(.awsui-dark-mode) &, :global(.awsui-polaris-dark-mode) & { background-color: $color-background-code-view-dark; } - border-start-start-radius: cs.$border-radius-tiles; - border-end-start-radius: cs.$border-radius-tiles; background-color: $color-background-code-view-light; - padding-block: cs.$space-static-xs; - padding-inline: cs.$space-static-xs; - display: flex; - flex-direction: column; - align-items: flex-end; - border-inline-end-width: 1px; - border-inline-end-style: solid; - border-inline-end-color: cs.$color-border-divider-default; - justify-content: center; + vertical-align: text-top; + position: sticky; + left: 0; + border-right-width: 1px; + border-right-style: solid; + padding-left: cs.$space-static-xs; + padding-right: cs.$space-static-xs; +} + +.code-line { + padding-left: cs.$space-static-xs; + padding-right: cs.$space-static-xs; + &-wrap { + white-space: pre-wrap; + } + &-nowrap { + white-space: nowrap; + } } .actions { diff --git a/src/test-utils/dom/code-view/index.ts b/src/test-utils/dom/code-view/index.ts index a228873..55e8f70 100644 --- a/src/test-utils/dom/code-view/index.ts +++ b/src/test-utils/dom/code-view/index.ts @@ -6,8 +6,8 @@ import styles from "../../../code-view/styles.selectors.js"; export default class CodeViewWrapper extends ComponentWrapper { static rootSelector: string = styles.root; - findContent(): ElementWrapper { - return this.findByClassName(styles.code)!; + findContent() { + return this.findAllByClassName(styles["code-line"])!; } findActions(): ElementWrapper | null { From a6277a053308ae0c2bbb024964a501bc88209199 Mon Sep 17 00:00:00 2001 From: Alice Koreman Date: Sun, 25 Feb 2024 01:08:27 +0100 Subject: [PATCH 14/29] chore: remove accidentally commited npm script --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 0174e77..b408146 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "build": "npm-run-all build:pkg --parallel build:src:* --parallel build:pages:* build:themeable", "postbuild": "size-limit", "lint": "eslint --ignore-path .gitignore --ext ts,tsx,js . && stylelint --ignore-path .gitignore '{src,pages}/**/*.{css,scss}'", - "fix": "eslint --fix --ignore-path .gitignore --ext ts,tsx,js . && stylelint --ignore-path .gitignore '{src,pages}/**/*.{css,scss}'", "prepare": "husky install", "test:unit": "vitest run --config vite.unit.config.mjs", "test:visual": "run-p -r preview test:visual:vitest", From d0550ce4f6a38db745e14f11e11a1620300c7f48 Mon Sep 17 00:00:00 2001 From: Alice Koreman Date: Sun, 25 Feb 2024 17:00:31 +0100 Subject: [PATCH 15/29] fix: make line numbers unselectable --- src/code-view/internal.tsx | 2 +- src/code-view/styles.scss | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/code-view/internal.tsx b/src/code-view/internal.tsx index c1ce37f..fd46c32 100644 --- a/src/code-view/internal.tsx +++ b/src/code-view/internal.tsx @@ -56,7 +56,7 @@ export function InternalCodeView({ return ( {lineNumbers && ( - + {index + 1} diff --git a/src/code-view/styles.scss b/src/code-view/styles.scss index 831f8d2..8e61510 100644 --- a/src/code-view/styles.scss +++ b/src/code-view/styles.scss @@ -41,6 +41,11 @@ $color-background-code-view-dark: #282c34; padding-right: cs.$space-static-xs; } +.unselectable { + -webkit-user-select: none; + user-select: none; +} + .code-line { padding-left: cs.$space-static-xs; padding-right: cs.$space-static-xs; From 529aeb40f58308b0d0c130e92796af5e36f86d93 Mon Sep 17 00:00:00 2001 From: Alice Koreman Date: Sun, 25 Feb 2024 17:42:02 +0100 Subject: [PATCH 16/29] chore: replace Ace text highlight rules with in-repo function --- src/code-view/internal.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/code-view/internal.tsx b/src/code-view/internal.tsx index fd46c32..e56e90b 100644 --- a/src/code-view/internal.tsx +++ b/src/code-view/internal.tsx @@ -2,12 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 import { useCurrentMode } from "@cloudscape-design/component-toolkit/internal"; import Box from "@cloudscape-design/components/box"; -import { TextHighlightRules } from "ace-code/src/mode/text_highlight_rules"; import clsx from "clsx"; import { useRef } from "react"; import { Children } from "react"; import { InternalBaseComponentProps, getBaseProps } from "../internal/base-component/use-base-component"; -import { createHighlight } from "./highlight"; import { CodeViewProps } from "./interfaces"; import styles from "./styles.css.js"; @@ -15,7 +13,21 @@ const ACE_CLASSES = { light: "ace-cloud_editor", dark: "ace-cloud_editor_dark" } type InternalCodeViewProps = CodeViewProps & InternalBaseComponentProps; -const textHighlight = createHighlight(new TextHighlightRules()); +// Breaks down the input code for non-highlighted code-view into React +// Elements similar to how a highlight function would do. +const textHighlight = (code: string) => { + const lines = code.split("\n"); + return ( + + {lines.map((line, lineIndex) => ( + + {line} + {"\n"} + + ))} + + ); +}; export function InternalCodeView({ content, From 67411645d3a10dd30212a8daf265a3146ec14b01 Mon Sep 17 00:00:00 2001 From: Ruben Carvalho Date: Tue, 12 Mar 2024 11:26:14 +0100 Subject: [PATCH 17/29] chore: Fix minimum-height and actions absolute positioning --- pages/code-view/with-actions-button.page.tsx | 6 ++ src/code-view/internal.tsx | 70 +++++++++++--------- src/code-view/styles.scss | 15 ++++- 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/pages/code-view/with-actions-button.page.tsx b/pages/code-view/with-actions-button.page.tsx index 3834fac..727f0d6 100644 --- a/pages/code-view/with-actions-button.page.tsx +++ b/pages/code-view/with-actions-button.page.tsx @@ -14,6 +14,12 @@ export default function CodeViewPage() { actions={} content={`Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.`} /> + } + content={`Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.`} + /> ); diff --git a/src/code-view/internal.tsx b/src/code-view/internal.tsx index e56e90b..b326e45 100644 --- a/src/code-view/internal.tsx +++ b/src/code-view/internal.tsx @@ -58,39 +58,47 @@ export function InternalCodeView({ aria-labelledby={ariaLabelledby} ref={__internalRootRef} > - - - - - - - {Children.map(code.props.children, (child, index) => { - return ( - - {lineNumbers && ( - + ); + })} + +
- - {index + 1} +
+ + + + + + + {Children.map(code.props.children, (child, index) => { + return ( + + {lineNumbers && ( + + )} + - )} - - - ); - })} - -
+ + {index + 1} + + + + + {child} + - - - {child} - - -
+
+ {actions &&
{actions}
} ); diff --git a/src/code-view/styles.scss b/src/code-view/styles.scss index 8e61510..83461f0 100644 --- a/src/code-view/styles.scss +++ b/src/code-view/styles.scss @@ -10,6 +10,9 @@ $color-background-code-view-dark: #282c34; background-color: $color-background-code-view-dark; } background-color: $color-background-code-view-light; +} + +.scroll-container { overflow-x: auto; } @@ -23,6 +26,14 @@ $color-background-code-view-dark: #282c34; table-layout: auto; width: 100%; border-spacing: 0; + + &-with-actions { + min-height: calc(2 * cs.$space-scaled-xs + cs.$space-scaled-xxl); + } +} + +.code-table-with-actions.code-table-with-line-wrapping { + padding-inline-end: cs.$space-static-xxl; } .line-number { @@ -59,7 +70,7 @@ $color-background-code-view-dark: #282c34; .actions { position: absolute; - inset-block-start: cs.$space-static-xs; - inset-inline-end: cs.$space-static-xs; + inset-block-start: cs.$space-scaled-xs; + inset-inline-end: cs.$space-scaled-xs; padding-inline-start: cs.$space-container-horizontal; } From 4dc0603ed1bf0acc897fe84a693ab9f5f999013e Mon Sep 17 00:00:00 2001 From: Ruben Carvalho Date: Tue, 12 Mar 2024 12:48:24 +0100 Subject: [PATCH 18/29] fix: Fix code indentation by setting white-space to pre --- pages/code-view/simple.page.tsx | 3 +++ pages/code-view/with-line-wrapping.page.tsx | 5 +++++ src/code-view/styles.scss | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pages/code-view/simple.page.tsx b/pages/code-view/simple.page.tsx index 4f2a634..0d3d00e 100644 --- a/pages/code-view/simple.page.tsx +++ b/pages/code-view/simple.page.tsx @@ -8,6 +8,9 @@ export default function CodeViewPage() {

Code View

+
); } diff --git a/pages/code-view/with-line-wrapping.page.tsx b/pages/code-view/with-line-wrapping.page.tsx index 169e226..c8c9d31 100644 --- a/pages/code-view/with-line-wrapping.page.tsx +++ b/pages/code-view/with-line-wrapping.page.tsx @@ -29,6 +29,11 @@ export default function CodeViewPage() { lineNumbers={true} content={`Hello this is a short line.\nHello this is a long line. Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\nHello this is another short line.`} /> + ); diff --git a/src/code-view/styles.scss b/src/code-view/styles.scss index 83461f0..fa0516b 100644 --- a/src/code-view/styles.scss +++ b/src/code-view/styles.scss @@ -64,7 +64,7 @@ $color-background-code-view-dark: #282c34; white-space: pre-wrap; } &-nowrap { - white-space: nowrap; + white-space: pre; } } From d746e4269f9d93189b06c0675a9262c6fc2fb7fa Mon Sep 17 00:00:00 2001 From: Ruben Carvalho Date: Tue, 12 Mar 2024 12:54:10 +0100 Subject: [PATCH 19/29] chore: Fix code color and lines Box variant --- src/code-view/internal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/code-view/internal.tsx b/src/code-view/internal.tsx index b326e45..6baab1a 100644 --- a/src/code-view/internal.tsx +++ b/src/code-view/internal.tsx @@ -76,13 +76,13 @@ export function InternalCodeView({ {lineNumbers && ( - + {index + 1} )} - + Date: Tue, 12 Mar 2024 13:24:53 +0100 Subject: [PATCH 20/29] chore: Add multi-line test --- src/code-view/__tests__/code-view.test.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/code-view/__tests__/code-view.test.tsx b/src/code-view/__tests__/code-view.test.tsx index 02cb68d..1e79f44 100644 --- a/src/code-view/__tests__/code-view.test.tsx +++ b/src/code-view/__tests__/code-view.test.tsx @@ -11,12 +11,26 @@ describe("CodeView", () => { afterEach(() => { cleanup(); }); - test("correctly renders component content", () => { + test("correctly renders simple content", () => { render(); const wrapper = createWrapper()!.findCodeView(); expect(wrapper!.findContent()[0].getElement()).toHaveTextContent("Hello World"); }); + test("correctly renders multi line content", () => { + const content = `# Hello World\n\nThis is a markdown example.`; + + render(); + const wrapper = createWrapper()!.findCodeView(); + const contentElements = wrapper!.findContent(); + expect(contentElements.length).toEqual(3); + const renderedContent = contentElements + .map((element) => element.getElement().textContent) + .join("") + .trim(); + expect(renderedContent).toBe(content); + }); + test("correctly renders copy button slot", () => { render(Copy}>); const wrapper = createWrapper()!.findCodeView(); From b015fba21994827342cc032e44e468cc684d0422 Mon Sep 17 00:00:00 2001 From: Ruben Carvalho Date: Tue, 12 Mar 2024 13:43:12 +0100 Subject: [PATCH 21/29] chore: Increase size-limit by 8 bytes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b408146..341bc08 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "size-limit": [ { "path": "lib/components/index.js", - "limit": "0.5kb" + "limit": "508b" }, { "path": "lib/components/code-view/highlight/javascript.js", From b10273d9f5716ea4f075c09ed57ec84a93a1593d Mon Sep 17 00:00:00 2001 From: Gethin Webster Date: Wed, 20 Mar 2024 13:08:48 +0100 Subject: [PATCH 22/29] Give role="presentation" --- src/code-view/internal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/code-view/internal.tsx b/src/code-view/internal.tsx index 6baab1a..b5810c9 100644 --- a/src/code-view/internal.tsx +++ b/src/code-view/internal.tsx @@ -60,6 +60,7 @@ export function InternalCodeView({ >
Date: Wed, 20 Mar 2024 14:11:42 +0100 Subject: [PATCH 23/29] Increase size limit --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 341bc08..b7c77b3 100644 --- a/package.json +++ b/package.json @@ -143,11 +143,11 @@ "size-limit": [ { "path": "lib/components/index.js", - "limit": "508b" + "limit": "600b" }, { "path": "lib/components/code-view/highlight/javascript.js", "limit": "1kb" } ] -} +} \ No newline at end of file From 323c76fbf01a884e79807ef9bb68ea7938a5d36f Mon Sep 17 00:00:00 2001 From: Alice Koreman Date: Tue, 2 Apr 2024 22:34:37 +0200 Subject: [PATCH 24/29] chore: increase size-limit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 392f1ab..236fe4f 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,7 @@ "size-limit": [ { "path": "lib/components/index.js", - "limit": "6.2kb" + "limit": "6.25kb" }, { "path": "lib/components/code-view/highlight/javascript.js", From 18ce8bbba9ac9b6024f66a54f4494d4ed50a636a Mon Sep 17 00:00:00 2001 From: Alice Koreman Date: Thu, 14 Nov 2024 20:13:51 +0100 Subject: [PATCH 25/29] small fixes after merging up to main --- package.json | 2 +- src/code-view/__tests__/code-view.test.tsx | 7 ++++--- src/code-view/styles.scss | 20 +++++--------------- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 95ebb42..97890a7 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,7 @@ "size-limit": [ { "path": "lib/components/index.js", - "limit": "6.25kb" + "limit": "6.52kb" }, { "path": "lib/components/code-view/highlight/javascript.js", diff --git a/src/code-view/__tests__/code-view.test.tsx b/src/code-view/__tests__/code-view.test.tsx index a31c74f..a2f2fe7 100644 --- a/src/code-view/__tests__/code-view.test.tsx +++ b/src/code-view/__tests__/code-view.test.tsx @@ -16,15 +16,16 @@ describe("CodeView", () => { test("correctly renders simple content", () => { render(); const wrapper = createWrapper()!.findCodeView(); - expect(wrapper!.findContent().getElement()).toHaveTextContent("Hello World"); + expect(wrapper!.findContent()[0].getElement()).toHaveTextContent("Hello World"); }); test("correctly renders multi line content", () => { render(); const wrapper = createWrapper()!.findCodeView()!; const content = wrapper.findContent(); - expect(content.getElement()).toHaveTextContent("# Hello World This is a markdown example."); - expect(wrapper!.findContent()[0].getElement()).toHaveTextContent("Hello World"); + expect(content[0].getElement()).toHaveTextContent("# Hello World"); + expect(content[1].getElement()).toHaveTextContent(""); + expect(content[2].getElement()).toHaveTextContent("This is a markdown example."); }); test("correctly renders multi line content", () => { diff --git a/src/code-view/styles.scss b/src/code-view/styles.scss index 9e0d5ff..e819028 100644 --- a/src/code-view/styles.scss +++ b/src/code-view/styles.scss @@ -3,16 +3,6 @@ SPDX-License-Identifier: Apache-2.0 */ -/* - Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - SPDX-License-Identifier: Apache-2.0 -*/ - -/* - Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - SPDX-License-Identifier: Apache-2.0 -*/ - @use "../../node_modules/@cloudscape-design/design-tokens/index.scss" as cs; $color-background-code-view-light: #f8f8f8; @@ -20,11 +10,11 @@ $color-background-code-view-dark: #282c34; .root { position: relative; + background-color: $color-background-code-view-light; :global(.awsui-dark-mode) &, :global(.awsui-polaris-dark-mode) & { background-color: $color-background-code-view-dark; } - background-color: $color-background-code-view-light; } .scroll-container { @@ -53,10 +43,6 @@ $color-background-code-view-dark: #282c34; .line-number { border-right-color: cs.$color-border-divider-default; - :global(.awsui-dark-mode) &, - :global(.awsui-polaris-dark-mode) & { - background-color: $color-background-code-view-dark; - } background-color: $color-background-code-view-light; vertical-align: text-top; position: sticky; @@ -65,6 +51,10 @@ $color-background-code-view-dark: #282c34; border-right-style: solid; padding-left: cs.$space-static-xs; padding-right: cs.$space-static-xs; + :global(.awsui-dark-mode) &, + :global(.awsui-polaris-dark-mode) & { + background-color: $color-background-code-view-dark; + } } .unselectable { From 6e86b8dcf277749437602f36e8be32bacb93d806 Mon Sep 17 00:00:00 2001 From: Alice Koreman Date: Thu, 14 Nov 2024 20:15:06 +0100 Subject: [PATCH 26/29] small fix after merging up to main --- pages/app/page-layout.module.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pages/app/page-layout.module.css b/pages/app/page-layout.module.css index 988945c..3e3c215 100644 --- a/pages/app/page-layout.module.css +++ b/pages/app/page-layout.module.css @@ -2,10 +2,6 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -/* - Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - SPDX-License-Identifier: Apache-2.0 -*/ .content { padding-block: 20px; padding-inline: 20px; From e4c46c729a22265ae50aa2d03186c4b68f499053 Mon Sep 17 00:00:00 2001 From: Alice Koreman Date: Thu, 14 Nov 2024 20:27:10 +0100 Subject: [PATCH 27/29] bump size limit --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 97890a7..a5c505d 100644 --- a/package.json +++ b/package.json @@ -146,11 +146,11 @@ "size-limit": [ { "path": "lib/components/index.js", - "limit": "6.52kb" + "limit": "6.53kb" }, { "path": "lib/components/code-view/highlight/javascript.js", "limit": "10kb" } ] -} \ No newline at end of file +} From f300bd13481db74766122e93e12900fc4ba1533e Mon Sep 17 00:00:00 2001 From: Alice Koreman Date: Thu, 14 Nov 2024 20:40:22 +0100 Subject: [PATCH 28/29] keep test util backwards compatible --- src/code-view/__tests__/code-view.test.tsx | 31 ++++++++-------------- src/test-utils/dom/code-view/index.ts | 4 +-- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/code-view/__tests__/code-view.test.tsx b/src/code-view/__tests__/code-view.test.tsx index a2f2fe7..57b46ca 100644 --- a/src/code-view/__tests__/code-view.test.tsx +++ b/src/code-view/__tests__/code-view.test.tsx @@ -16,30 +16,21 @@ describe("CodeView", () => { test("correctly renders simple content", () => { render(); const wrapper = createWrapper()!.findCodeView(); - expect(wrapper!.findContent()[0].getElement()).toHaveTextContent("Hello World"); + expect(wrapper!.findContent().getElement()).toHaveTextContent("Hello World"); }); test("correctly renders multi line content", () => { render(); const wrapper = createWrapper()!.findCodeView()!; const content = wrapper.findContent(); - expect(content[0].getElement()).toHaveTextContent("# Hello World"); - expect(content[1].getElement()).toHaveTextContent(""); - expect(content[2].getElement()).toHaveTextContent("This is a markdown example."); + expect(content.getElement()).toHaveTextContent("# Hello World This is a markdown example."); }); test("correctly renders multi line content", () => { - const content = `# Hello World\n\nThis is a markdown example.`; - - render(); - const wrapper = createWrapper()!.findCodeView(); - const contentElements = wrapper!.findContent(); - expect(contentElements.length).toEqual(3); - const renderedContent = contentElements - .map((element) => element.getElement().textContent) - .join("") - .trim(); - expect(renderedContent).toBe(content); + render(); + const wrapper = createWrapper()!.findCodeView()!; + const content = wrapper.findContent(); + expect(content.getElement()).toHaveTextContent("# Hello World This is a markdown example."); }); test("correctly renders copy button slot", () => { @@ -84,13 +75,13 @@ describe("CodeView", () => { >, ); const wrapper = createWrapper().findCodeView()!; - expect(wrapper!.findContent()[0].getElement().innerHTML).toContain("tokenized"); + expect(wrapper!.findContent().getElement().innerHTML).toContain("tokenized"); }); test("correctly tokenizes content if highlight is set to language rules", () => { render(); const wrapper = createWrapper().findCodeView()!; - const element = wrapper!.findContent()[0].getElement(); + const element = wrapper!.findContent().getElement(); // Check that the content is tokenized following typescript rules. expect(getByText(element, "const")).toHaveClass("ace_type"); @@ -102,21 +93,21 @@ describe("CodeView", () => { test("sets nowrap class to line if linesWrapping undefined", () => { render(); const wrapper = createWrapper().findCodeView()!; - const element = wrapper!.findContent()[0].getElement(); + const element = wrapper!.findContent().getElement(); expect(element.outerHTML).toContain("code-line-nowrap"); }); test("sets nowrap class to line if linesWrapping false", () => { render(); const wrapper = createWrapper().findCodeView()!; - const element = wrapper!.findContent()[0].getElement(); + const element = wrapper!.findContent().getElement(); expect(element.outerHTML).toContain("code-line-nowrap"); }); test("sets wrap class to line if linesWrapping true", () => { render(); const wrapper = createWrapper().findCodeView()!; - const element = wrapper!.findContent()[0].getElement(); + const element = wrapper!.findContent().getElement(); expect(element.outerHTML).toContain("code-line-wrap"); }); }); diff --git a/src/test-utils/dom/code-view/index.ts b/src/test-utils/dom/code-view/index.ts index 2d04d96..92df5c2 100644 --- a/src/test-utils/dom/code-view/index.ts +++ b/src/test-utils/dom/code-view/index.ts @@ -7,8 +7,8 @@ import styles from "../../../code-view/styles.selectors.js"; export default class CodeViewWrapper extends ComponentWrapper { static rootSelector: string = styles.root; - findContent() { - return this.findAllByClassName(styles["code-line"])!; + findContent(): ElementWrapper { + return this.find("tbody")!; } findActions(): ElementWrapper | null { From 5b7e6e62893d13c597386c78309bd5218a1dac55 Mon Sep 17 00:00:00 2001 From: Alice Koreman Date: Thu, 14 Nov 2024 20:45:43 +0100 Subject: [PATCH 29/29] small fixes from mistakes made while merging --- src/code-view/__tests__/code-view.test.tsx | 7 ------- src/code-view/styles.scss | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/code-view/__tests__/code-view.test.tsx b/src/code-view/__tests__/code-view.test.tsx index 57b46ca..5cc85f5 100644 --- a/src/code-view/__tests__/code-view.test.tsx +++ b/src/code-view/__tests__/code-view.test.tsx @@ -26,13 +26,6 @@ describe("CodeView", () => { expect(content.getElement()).toHaveTextContent("# Hello World This is a markdown example."); }); - test("correctly renders multi line content", () => { - render(); - const wrapper = createWrapper()!.findCodeView()!; - const content = wrapper.findContent(); - expect(content.getElement()).toHaveTextContent("# Hello World This is a markdown example."); - }); - test("correctly renders copy button slot", () => { render(Copy}>); const wrapper = createWrapper()!.findCodeView(); diff --git a/src/code-view/styles.scss b/src/code-view/styles.scss index e819028..4004f5d 100644 --- a/src/code-view/styles.scss +++ b/src/code-view/styles.scss @@ -26,8 +26,8 @@ $color-background-code-view-dark: #282c34; border-start-end-radius: cs.$border-radius-tiles; border-end-start-radius: cs.$border-radius-tiles; border-end-end-radius: cs.$border-radius-tiles; - padding-top: cs.$space-static-xs; - padding-bottom: cs.$space-static-xs; + padding-block-start: cs.$space-static-xs; + padding-block-end: cs.$space-static-xs; table-layout: auto; width: 100%; border-spacing: 0;