Skip to content

Commit 5ef2c97

Browse files
authored
fix: Use semantic table to show line numbers to assistive technology (#78)
* experiment: Use semantic table to show line numbers to assistive technology * Keep non-table format. * Fix test to deploy. * Try rendering line number without preceding text. * Final change with expected API. * Never mind, change API a little bit more. * Add labels for individual i18nStrings.
1 parent ab7efda commit 5ef2c97

File tree

6 files changed

+93
-8
lines changed

6 files changed

+93
-8
lines changed

pages/code-view/with-line-numbers.page.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,33 @@
33

44
import { Button, SpaceBetween } from "@cloudscape-design/components";
55

6-
import { CodeView } from "../../lib/components";
6+
import { CodeView, CodeViewProps } from "../../lib/components";
77
import { ScreenshotArea } from "../screenshot-area";
8+
9+
const i18nStrings: CodeViewProps.I18nStrings = {
10+
lineNumberLabel: `Line number`,
11+
codeLabel: `Code`,
12+
};
13+
814
export default function CodeViewPage() {
915
return (
1016
<ScreenshotArea>
1117
<h1>Code View</h1>
1218
<SpaceBetween direction="vertical" size="l">
13-
<CodeView lineNumbers={true} content={`# Hello World`} />
14-
<CodeView lineNumbers={true} content={`# Hello World\n\nThis is Cloudscape.`} />
19+
<CodeView lineNumbers={true} i18nStrings={i18nStrings} content={`# Hello World`} />
20+
<CodeView lineNumbers={true} i18nStrings={i18nStrings} content={`# Hello World\n\nThis is Cloudscape.`} />
1521
<CodeView
1622
lineNumbers={true}
23+
i18nStrings={i18nStrings}
1724
content={`# Hello World`}
1825
actions={<Button ariaLabel="Copy code" iconName="copy"></Button>}
1926
/>
2027
{/* Wrapping should not be affected by the parent's word-break property. */}
2128
<div style={{ wordBreak: "break-word" }}>
2229
<CodeView
2330
lineNumbers={true}
24-
content={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((i) => `This is line number #${i + 1}.`).join("\n")}
31+
i18nStrings={i18nStrings}
32+
content={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((i) => `This is line number #${i}.`).join("\n")}
2533
/>
2634
</div>
2735
</SpaceBetween>

src/__tests__/__snapshots__/documenter.test.ts.snap

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,31 @@ exports[`definition for 'code-view' matches the snapshot 1`] = `
4242
"optional": true,
4343
"type": "((code: string) => React.ReactNode)",
4444
},
45+
{
46+
"description": "An object containing all the necessary localized strings required by the component. The object should contain:
47+
48+
* \`lineNumberLabel\` - Label for the column that displays line numbers (when line numbers are visible)
49+
* \`codeLabel\` - Label for the column that displays the code content (when line numbers are visible)",
50+
"inlineType": {
51+
"name": "CodeViewProps.I18nStrings",
52+
"properties": [
53+
{
54+
"name": "codeLabel",
55+
"optional": true,
56+
"type": "string",
57+
},
58+
{
59+
"name": "lineNumberLabel",
60+
"optional": true,
61+
"type": "string",
62+
},
63+
],
64+
"type": "object",
65+
},
66+
"name": "i18nStrings",
67+
"optional": true,
68+
"type": "CodeViewProps.I18nStrings",
69+
},
4570
{
4671
"description": "Controls the display of line numbers.
4772

src/code-view/__tests__/code-view.test.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@ describe("CodeView", () => {
2626
expect(content.getElement()).toHaveTextContent("# Hello World This is a markdown example.");
2727
});
2828

29+
test("correctly renders table markup with line numbers", () => {
30+
render(
31+
<CodeView
32+
lineNumbers={true}
33+
i18nStrings={{ lineNumberLabel: "Line number", codeLabel: "Code" }}
34+
content={`# Hello World\n\nThis is a markdown example.`}
35+
/>,
36+
);
37+
const wrapper = createWrapper()!.findCodeView()!;
38+
const table = wrapper.find("table")!.getElement();
39+
expect(table).not.toHaveAttribute("role");
40+
const lineNumberColumn = wrapper.find("th:nth-child(1)")!.getElement();
41+
expect(lineNumberColumn).toHaveTextContent("Line number");
42+
const contentColumn = wrapper.find("th:nth-child(2)")!.getElement();
43+
expect(contentColumn).toHaveTextContent("Code");
44+
const lineNumberCell = wrapper.find("td:nth-child(1)")!.getElement();
45+
expect(lineNumberCell).not.toHaveAttribute("aria-hidden", "true");
46+
});
47+
2948
test("correctly renders copy button slot", () => {
3049
render(<CodeView content={"Hello World"} actions={<button>Copy</button>}></CodeView>);
3150
const wrapper = createWrapper()!.findCodeView();

src/code-view/interfaces.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,21 @@ export interface CodeViewProps {
3939

4040
/**
4141
* A function to perform custom syntax highlighting.
42-
*
4342
*/
4443
highlight?: (code: string) => React.ReactNode;
44+
45+
/**
46+
* An object containing all the necessary localized strings required by the component. The object should contain:
47+
*
48+
* * `lineNumberLabel` - Label for the column that displays line numbers (when line numbers are visible)
49+
* * `codeLabel` - Label for the column that displays the code content (when line numbers are visible)
50+
*/
51+
i18nStrings?: CodeViewProps.I18nStrings;
52+
}
53+
54+
export namespace CodeViewProps {
55+
export interface I18nStrings {
56+
lineNumberLabel?: string;
57+
codeLabel?: string;
58+
}
4559
}

src/code-view/internal.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export function InternalCodeView({
3939
highlight,
4040
ariaLabel,
4141
ariaLabelledby,
42+
i18nStrings,
4243
__internalRootRef = null,
4344
...props
4445
}: InternalCodeViewProps) {
@@ -47,6 +48,7 @@ export function InternalCodeView({
4748
const darkMode = useCurrentMode(containerRef) === "dark";
4849

4950
const regionProps = ariaLabel || ariaLabelledby ? { role: "region" } : {};
51+
const accessibleLineNumbers = lineNumbers && i18nStrings?.lineNumberLabel && i18nStrings?.codeLabel;
5052

5153
// Create tokenized React nodes of the content.
5254
const code = highlight ? highlight(content) : textHighlight(content);
@@ -66,7 +68,7 @@ export function InternalCodeView({
6668
>
6769
<div className={styles["scroll-container"]} ref={containerRef}>
6870
<table
69-
role="presentation"
71+
role={!accessibleLineNumbers ? "presentation" : undefined}
7072
className={clsx(
7173
styles["code-table"],
7274
actions && styles["code-table-with-actions"],
@@ -77,12 +79,23 @@ export function InternalCodeView({
7779
<col style={{ width: 1 } /* shrink to fit content */} />
7880
<col style={{ width: "auto" }} />
7981
</colgroup>
82+
{accessibleLineNumbers && (
83+
<thead className={styles["screenreader-only"]}>
84+
<tr>
85+
{lineNumbers && <th>{i18nStrings.lineNumberLabel}</th>}
86+
<th>{i18nStrings.codeLabel}</th>
87+
</tr>
88+
</thead>
89+
)}
8090
<tbody>
8191
{Children.map(codeElement.props.children, (child, index) => {
8292
return (
8393
<tr key={index}>
8494
{lineNumbers && (
85-
<td className={clsx(styles["line-number"], styles.unselectable)} aria-hidden={true}>
95+
<td
96+
className={clsx(styles["line-number"], styles.unselectable)}
97+
aria-hidden={!accessibleLineNumbers}
98+
>
8699
<Box variant="code" color="text-status-inactive" fontSize="body-m">
87100
{index + 1}
88101
</Box>

src/code-view/styles.scss

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ $color-background-code-view-dark: #282c34;
1414
border-start-end-radius: cs.$border-radius-tiles;
1515
border-end-start-radius: cs.$border-radius-tiles;
1616
border-end-end-radius: cs.$border-radius-tiles;
17-
17+
1818
background-color: $color-background-code-view-light;
1919
:global(.awsui-dark-mode) &,
2020
:global(.awsui-polaris-dark-mode) & {
@@ -43,6 +43,12 @@ $color-background-code-view-dark: #282c34;
4343
padding-inline-end: cs.$space-static-xxl;
4444
}
4545

46+
.screenreader-only {
47+
position: absolute;
48+
inset-block-start: -9999px;
49+
inset-inline-start: -9999px;
50+
}
51+
4652
.line-number {
4753
vertical-align: text-top;
4854
white-space: nowrap;

0 commit comments

Comments
 (0)