diff --git a/.changeset/lucky-apricots-accept.md b/.changeset/lucky-apricots-accept.md new file mode 100644 index 00000000..8390e5e8 --- /dev/null +++ b/.changeset/lucky-apricots-accept.md @@ -0,0 +1,8 @@ +--- +'@nl-design-system-candidate/code-block-react': minor +--- + +Add `overflow` property to `CodeBlock`, with new options: + +- `overflow="nowrap"`: fit content, even for very long lines. +- `overflow="overflow"`: provide scrollbars to see very long lines. diff --git a/.changeset/strong-pants-train.md b/.changeset/strong-pants-train.md new file mode 100644 index 00000000..c4f139f5 --- /dev/null +++ b/.changeset/strong-pants-train.md @@ -0,0 +1,8 @@ +--- +'@nl-design-system-candidate/code-block-css': minor +--- + +Add new class names and mixins: + +- `nl-code-block--nowrap`: fit content, even for very long lines. +- `nl-code-block--overflow`: provide scrollbars to see very long lines. diff --git a/packages/components-css/code-block-css/src/_mixin.scss b/packages/components-css/code-block-css/src/_mixin.scss index 70288346..ce797c87 100644 --- a/packages/components-css/code-block-css/src/_mixin.scss +++ b/packages/components-css/code-block-css/src/_mixin.scss @@ -11,18 +11,26 @@ display: block; font-family: var(--nl-code-block-font-family, monospace), monospace; font-size: var(--nl-code-block-font-size); + hyphens: none; line-height: var(--nl-code-block-line-height); + padding-block: var(--nl-code-block-padding-block); + padding-inline: var(--nl-code-block-padding-inline); + white-space: pre-wrap; +} +@mixin nl-code-block--nowrap { + inline-size: fit-content; + white-space: pre; +} + +@mixin nl-code-block--overflow { /* stylelint-disable order/properties-alphabetical-order */ /* `overflow-inline` must come after `overflow-x` */ overflow-x: auto; overflow-inline: auto; - /* stylelint-enable order/properties-alphabetical-order */ - padding-block: var(--nl-code-block-padding-block); - padding-inline: var(--nl-code-block-padding-inline); white-space: pre; } diff --git a/packages/components-css/code-block-css/src/code-block.scss b/packages/components-css/code-block-css/src/code-block.scss index 1347e990..8b2e6231 100644 --- a/packages/components-css/code-block-css/src/code-block.scss +++ b/packages/components-css/code-block-css/src/code-block.scss @@ -4,6 +4,14 @@ @include mixin.nl-code-block; } +.nl-code-block--nowrap { + @include mixin.nl-code-block--nowrap; +} + +.nl-code-block--overflow { + @include mixin.nl-code-block--overflow; +} + .nl-code-block__code { @include mixin.nl-code-block__code; } diff --git a/packages/components-react/code-block-react/README.md b/packages/components-react/code-block-react/README.md index 9202ce86..02f2df11 100644 --- a/packages/components-react/code-block-react/README.md +++ b/packages/components-react/code-block-react/README.md @@ -6,9 +6,21 @@ Blok met 1 of meerdere regels computercode. ## Features -- Semantisch HTML: een `pre` en een `code` element, [zoals de HTML-specificatie voorschrijft bij het `pre` element](https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element). -- De tekstrichting van de code is links-naar-rechts ingesteld met het [HTML `dir` attribuut](https://html.spec.whatwg.org/multipage/dom.html#the-dir-attribute), ook in documenten waar de tekstrichting rechts-naar-links is. -- De code is gemarkeerd als niet-vertaalbaar met het [HTML `translate` attribuut](https://html.spec.whatwg.org/multipage/dom.html#the-translate-attribute) voor automatische vertaalsoftware. +- Semantisch HTML: een `pre` en een `code` element, + [zoals de HTML-specificatie voorschrijft bij het `pre` element](https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element). +- De tekstrichting van de code is links-naar-rechts ingesteld met het + [HTML `dir` attribuut](https://html.spec.whatwg.org/multipage/dom.html#the-dir-attribute), ook in documenten waar de + tekstrichting rechts-naar-links is. +- De code is gemarkeerd als niet-vertaalbaar met het + [HTML `translate` attribuut](https://html.spec.whatwg.org/multipage/dom.html#the-translate-attribute) voor + automatische vertaalsoftware. +- Met de `overflow` property kan het overflow gedrag worden bepaald: + - `overflow="wrap"`: de component wordt zonder `tabindex` attribuut gerenderd en tekst wrapt automatisch. Dit is de + default, ook als de prop niet expliciet gezet is. + - `overflow="nowrap"`: de component wordt zonder `tabindex` attribuut gerenderd, je moet dit zelf in combinatie met + scroll gedrag op een parent element doen. + - `overflow="overflow"`: de component wordt altijd mét een `tabindex="0"` attribuut gerenderd en heeft ingebouwd scroll + gedrag. - Het visueel ontwerp is door een monospace lettertype goed te onderscheiden van gewone tekst. - Het visueel ontwerp blijft onderscheidend wanneer: - wanneer de CSS niet geladen kan worden; diff --git a/packages/components-react/code-block-react/src/code-block.test.tsx b/packages/components-react/code-block-react/src/code-block.test.tsx index 5996be4b..deeb0d30 100644 --- a/packages/components-react/code-block-react/src/code-block.test.tsx +++ b/packages/components-react/code-block-react/src/code-block.test.tsx @@ -29,6 +29,45 @@ describe('Code Block', () => { expect(codeBlock).toHaveClass('nl-code-block', extraClassName); }); + it('renders an element with class names "nl-code-block" and "nl-code-block--overflow" when told to overflow', () => { + const { container } = render({testCode}); + const codeBlock = container.querySelector('pre:only-child'); + + expect(codeBlock).toHaveClass('nl-code-block', 'nl-code-block--overflow'); + }); + + it('renders an element with class names "nl-code-block" and "nl-code-block--nowrap" when told not to wrap', () => { + const { container } = render({testCode}); + const codeBlock = container.querySelector('pre:only-child'); + + expect(codeBlock).toHaveClass('nl-code-block', 'nl-code-block--nowrap'); + }); + + it('renders an element with `tabindex="0" when passed `overflow="overflow"`', () => { + const { container } = render({testCode}); + const codeBlock = container.querySelector('pre:only-child'); + + expect(codeBlock).toHaveAttribute('tabindex', '0'); + }); + + it('allows the tabindex to be overriden even when told to overflow', () => { + const { container } = render( + + {testCode} + , + ); + const codeBlock = container.querySelector('pre:only-child'); + + expect(codeBlock).not.toHaveAttribute('tabindex'); + }); + + it('renders an element with only class name "nl-code-block" when told to wrap and when no extra class name is passed', () => { + const { container } = render({testCode}); + const codeBlock = container.querySelector('pre:only-child'); + + expect(codeBlock).toHaveClass('nl-code-block', { exact: true }); + }); + it('renders an HTML pre element that contains an HTML code element', () => { const { container } = render({testCode}); const codeBlock = container.querySelector('pre:only-child'); diff --git a/packages/components-react/code-block-react/src/code-block.tsx b/packages/components-react/code-block-react/src/code-block.tsx index 4a990e2d..e5cd23a1 100644 --- a/packages/components-react/code-block-react/src/code-block.tsx +++ b/packages/components-react/code-block-react/src/code-block.tsx @@ -2,13 +2,30 @@ import type { HTMLAttributes } from 'react'; import { clsx } from 'clsx'; import { forwardRef } from 'react'; -export type CodeBlockProps = HTMLAttributes; +export interface CodeBlockProps extends HTMLAttributes { + overflow?: 'wrap' | 'nowrap' | 'overflow'; +} export const CodeBlock = forwardRef(function CodeBlock(props, forwardedRef) { - const { children, className, ...restProps } = props; + const { children, className, overflow = 'wrap', ...restProps } = props; + const tabIndex = overflow === 'overflow' ? 0 : undefined; return ( -
+    
       {children}
     
); diff --git a/packages/storybook-test/stories/code-block/code-block.stories.tsx b/packages/storybook-test/stories/code-block/code-block.stories.tsx index 089a2dc9..3b712aa7 100644 --- a/packages/storybook-test/stories/code-block/code-block.stories.tsx +++ b/packages/storybook-test/stories/code-block/code-block.stories.tsx @@ -66,10 +66,15 @@ import { const meta = { argTypes: { children: { control: 'text', table: { category: 'API' } }, + overflow: { + control: { labels: { undefined: '(undefined)' }, type: 'select' }, + options: [undefined, 'overflow', 'wrap', 'nowrap'], + table: { category: 'API' }, + type: 'string', + }, }, component: CodeBlock, decorators: [ExampleBodyTextDecorator], - parameters: { docs: { description: { @@ -86,7 +91,7 @@ const meta = { url: packageJSON.homepage, }, ], - testReport: { + testResult: { notApplicable: [ WCAG22_111_NON_TEXT_CONTENT, WCAG22_121_AUDIO_ONLY_AND_VIDEO_ONLY_PRERECORDED, @@ -138,6 +143,7 @@ const meta = { WCAG22_412_NAME_ROLE_VALUE, WCAG22_413_STATUS_MESSAGES, ], + notTested: [WCAG22_1410_REFLOW, WCAG22_1411_NON_TEXT_CONTRAST, WCAG22_312_LANGUAGE_OF_PARTS], }, tokens, }, @@ -153,12 +159,10 @@ export const Default: Story = { args: { children: `import { Code } from '@nl-design-system/code-react';`, }, - decorators: [ExampleBodyTextDecorator], globals: { dir: 'ltr', lang: 'nl', }, - parameters: { docs: { description: { @@ -168,21 +172,41 @@ Op een klein scherm of bij 400% zoom past niet de hele regel op het scherm, dan }, }, status: { type: [] }, - testReport: { + testResult: { date: '2024-12-17', - notTested: [ + fail: [WCAG22_144_RESIZE_TEXT], + pass: [ WCAG22_131_INFO_AND_RELATIONSHIPS, - WCAG22_1410_REFLOW, - WCAG22_1412_TEXT_SPACING, + WCAG22_141_USE_OF_COLOR, WCAG22_143_CONTRAST_MINIMUM, - WCAG22_144_RESIZE_TEXT, - WCAG22_312_LANGUAGE_OF_PARTS, + WCAG22_1412_TEXT_SPACING, ], - pass: [], }, }, }; +export const LineOverflow: Story = { + name: 'Code Block met 1 regel ECMAScript, met mogelijk een scrollbalk', + args: { + children: `import { Code } from '@nl-design-system/code-react';`, + overflow: 'overflow', + }, + globals: { + dir: 'ltr', + lang: 'nl', + }, + parameters: { + docs: { + description: { + story: `Een Code Block met een regel code van 53 tekens lang. + +Op een klein scherm of bij 400% zoom past niet de hele regel op het scherm, dan kan je in de richting van de tekst scrollen om het einde van de regel te zien.`, + }, + }, + status: { type: [] }, + }, +}; + export const KorteRegel: Story = { name: 'Code Block met 1 kort Unix shell commando', args: { @@ -200,17 +224,15 @@ export const KorteRegel: Story = { Op een klein scherm of bij 400% zoom past de tekst op 1 regel, dan scrollen is niet nodig om de hele tekst te zien.`, }, }, - testReport: { + testResult: { date: '2024-12-17', - notTested: [ + fail: [WCAG22_144_RESIZE_TEXT], + pass: [ WCAG22_131_INFO_AND_RELATIONSHIPS, - WCAG22_1410_REFLOW, - WCAG22_1412_TEXT_SPACING, + WCAG22_141_USE_OF_COLOR, WCAG22_143_CONTRAST_MINIMUM, - WCAG22_144_RESIZE_TEXT, - WCAG22_312_LANGUAGE_OF_PARTS, + WCAG22_1412_TEXT_SPACING, ], - pass: [], }, }, }; @@ -241,17 +263,66 @@ Code conventies gebruiken vaak een uiterste limiet van 120 tekens, maar soms zij De volledige inhoud van de Code Block moet leesbaar zijn, ook als de content heel lange regels bevat. De Code Block moet horizontaal kunnen scrollen.`, }, }, - testReport: { + testResult: { date: '2024-12-17', - notTested: [ + fail: [WCAG22_144_RESIZE_TEXT], + pass: [ WCAG22_131_INFO_AND_RELATIONSHIPS, - WCAG22_1410_REFLOW, - WCAG22_1412_TEXT_SPACING, + WCAG22_141_USE_OF_COLOR, WCAG22_143_CONTRAST_MINIMUM, - WCAG22_144_RESIZE_TEXT, - WCAG22_312_LANGUAGE_OF_PARTS, + WCAG22_1412_TEXT_SPACING, ], - pass: [], + }, + }, +}; + +export const LangeRegelLineOverflow: Story = { + name: 'Code Block met 1 extra lange regel, die kan scrollen', + args: { + ...LangeRegel.args, + overflow: 'overflow', + }, + globals: { + dir: 'ltr', + lang: 'nl', + }, + parameters: { + docs: { + description: { + story: `Een Code Block met een lange regel code van 155 tekens lang. + +Code conventies gebruiken vaak een uiterste limiet van 120 tekens, maar soms zijn er toch langere regels. + +De volledige inhoud van de Code Block moet leesbaar zijn, ook als de content heel lange regels bevat. De Code Block moet horizontaal kunnen scrollen.`, + }, + }, + }, +}; + +export const LangeRegelLineNowrap: Story = { + name: 'Code Block met 1 extra lange regel zonder line-wrap, die kan scrollen door een container element', + args: { + ...LangeRegel.args, + overflow: 'nowrap', + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + globals: { + dir: 'ltr', + lang: 'nl', + }, + parameters: { + docs: { + description: { + story: `Een Code Block met een lange regel code van 155 tekens lang. + +De volledige inhoud van de Code Block moet leesbaar zijn, ook als de content heel lange regels bevat. De Code Block moet horizontaal kunnen scrollen.`, + }, }, }, }; @@ -272,17 +343,15 @@ export const DefaultFont: Story = { story: 'De Code Block moet visueel onderscheidbaar zijn, ook als er geen font design tokens is ingesteld.', }, }, - testReport: { + testResult: { date: '2024-12-17', - notTested: [ + fail: [WCAG22_144_RESIZE_TEXT], + pass: [ WCAG22_131_INFO_AND_RELATIONSHIPS, - WCAG22_1410_REFLOW, - WCAG22_1412_TEXT_SPACING, + WCAG22_141_USE_OF_COLOR, WCAG22_143_CONTRAST_MINIMUM, - WCAG22_144_RESIZE_TEXT, - WCAG22_312_LANGUAGE_OF_PARTS, + WCAG22_1412_TEXT_SPACING, ], - pass: [], }, }, }; @@ -293,7 +362,7 @@ export const FallbackFont: Story = { children: `De Code Block moet visueel onderscheidbaar zijn.`, style: { '--nl-code-block-font-family': '""', - } as CSSProperties, + }, }, globals: { dir: 'ltr', @@ -306,17 +375,15 @@ export const FallbackFont: Story = { story: `De Code Block moet visueel onderscheidbaar zijn, ook wanneer de in de design token ingestelde font-family niet geladen kan worden.`, }, }, - testReport: { + testResult: { date: '2024-12-17', - notTested: [ + fail: [WCAG22_144_RESIZE_TEXT], + pass: [ WCAG22_131_INFO_AND_RELATIONSHIPS, - WCAG22_1410_REFLOW, - WCAG22_1412_TEXT_SPACING, + WCAG22_141_USE_OF_COLOR, WCAG22_143_CONTRAST_MINIMUM, - WCAG22_144_RESIZE_TEXT, - WCAG22_312_LANGUAGE_OF_PARTS, + WCAG22_1412_TEXT_SPACING, ], - pass: [], }, }, }; @@ -341,17 +408,15 @@ var antwoord = nummerA * nummerB;`, 'Een Code Block met een codevoorbeeld voor Nederlandstalige lezers, met namen van variabelen en commentaar in het Nederlands.', }, }, - testReport: { + testResult: { date: '2024-12-17', - notTested: [ + fail: [WCAG22_144_RESIZE_TEXT], + pass: [ WCAG22_131_INFO_AND_RELATIONSHIPS, - WCAG22_1410_REFLOW, - WCAG22_1412_TEXT_SPACING, + WCAG22_141_USE_OF_COLOR, WCAG22_143_CONTRAST_MINIMUM, - WCAG22_144_RESIZE_TEXT, - WCAG22_312_LANGUAGE_OF_PARTS, + WCAG22_1412_TEXT_SPACING, ], - pass: [], }, }, }; @@ -376,17 +441,15 @@ var answer = numberA * numberB;`, 'Een Code Block met een codevoorbeeld voor Engelstalig lezers, met namen van variabelen en commentaar in het Engels.', }, }, - testReport: { + testResult: { date: '2024-12-17', - notTested: [ + fail: [WCAG22_144_RESIZE_TEXT], + pass: [ WCAG22_131_INFO_AND_RELATIONSHIPS, - WCAG22_1410_REFLOW, - WCAG22_1412_TEXT_SPACING, + WCAG22_141_USE_OF_COLOR, WCAG22_143_CONTRAST_MINIMUM, - WCAG22_144_RESIZE_TEXT, - WCAG22_312_LANGUAGE_OF_PARTS, + WCAG22_1412_TEXT_SPACING, ], - pass: [], }, }, }; @@ -403,7 +466,7 @@ var answer = numberA * numberB;`, (Story) => ( <> في المثال التالي سوف نحسب 7 مرات 6 - {Story()} + ), ], @@ -419,17 +482,15 @@ var answer = numberA * numberB;`, 'Een Code Block in een codevoorbeeld voor lezers van Arabisch, met namen van variabelen en commentaar in het Engels. De hele pagina is rechts uitegelijnd, maar de tekst van het Code Block is links uitgelijnd.', }, }, - testReport: { + testResult: { date: '2024-12-17', - notTested: [ + fail: [WCAG22_144_RESIZE_TEXT], + pass: [ WCAG22_131_INFO_AND_RELATIONSHIPS, - WCAG22_1410_REFLOW, - WCAG22_1412_TEXT_SPACING, + WCAG22_141_USE_OF_COLOR, WCAG22_143_CONTRAST_MINIMUM, - WCAG22_144_RESIZE_TEXT, - WCAG22_312_LANGUAGE_OF_PARTS, + WCAG22_1412_TEXT_SPACING, ], - pass: [], }, }, };