Skip to content

Commit

Permalink
feat(components-react): create Heading component (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
MMeijerink authored Oct 16, 2024
1 parent 9926fb8 commit edab49a
Show file tree
Hide file tree
Showing 9 changed files with 369 additions and 9 deletions.
8 changes: 8 additions & 0 deletions .changeset/fair-tigers-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@lux-design-system/components-react": minor
---

In deze commit:

- Nieuw component: LuxHeading
- Nieuwe componenten: LuxHeading1, LuxHeading2, LuxHeading3, LuxHeading4, LuxHeading5, LuxHeading6
4 changes: 2 additions & 2 deletions packages/components-react/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "1.0.0-alpha.0",
"version": "1.0.0-alpha.1",
"author": "Community for NL Design System",
"description": "React component library for LUX, the Design System for Logius, based on the NL Design System architecture",
"license": "EUPL-1.2",
Expand Down Expand Up @@ -59,7 +59,7 @@
"@rollup/plugin-commonjs": "26.0.1",
"@rollup/plugin-node-resolve": "15.2.3",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.4.8",
"@testing-library/jest-dom": "6.5.0",
"@testing-library/react": "16.0.0",
"@testing-library/user-event": "14.5.2",
"@types/jest": "29.5.12",
Expand Down
53 changes: 53 additions & 0 deletions packages/components-react/src/heading/Heading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Heading as UtrechtHeading } from '@utrecht/component-library-react/dist/css-module';
import { HTMLAttributes, PropsWithChildren, ReactNode } from 'react';

type HeadingLevels = 1 | 2 | 3 | 4 | 5 | 6;

export interface LuxHeadingProps extends HTMLAttributes<HTMLHeadingElement> {
appearance?: HeadingLevels;
level: HeadingLevels;
}

const APPEARANCES: { [key: number]: string } = {
1: 'utrecht-heading-1',
2: 'utrecht-heading-2',
3: 'utrecht-heading-3',
4: 'utrecht-heading-4',
5: 'utrecht-heading-5',
6: 'utrecht-heading-6',
};

export const LuxHeading = ({
children,
level,
appearance = level,
...restProps
}: PropsWithChildren<LuxHeadingProps>): ReactNode => {
const _level = Math.max(Math.min(level, 6), 1);
const _appearance = Math.max(Math.min(appearance, 6), 1);

return (
<UtrechtHeading {...restProps} level={_level} {...(_appearance ? { appearance: APPEARANCES[_appearance] } : {})}>
{children}
</UtrechtHeading>
);
};

export const LuxHeading1 = ({ ...props }: PropsWithChildren<HTMLAttributes<HTMLHeadingElement>>): ReactNode => (
<LuxHeading {...props} level={1} />
);
export const LuxHeading2 = ({ ...props }: PropsWithChildren<HTMLAttributes<HTMLHeadingElement>>): ReactNode => (
<LuxHeading {...props} level={2} />
);
export const LuxHeading3 = ({ ...props }: PropsWithChildren<HTMLAttributes<HTMLHeadingElement>>): ReactNode => (
<LuxHeading {...props} level={3} />
);
export const LuxHeading4 = ({ ...props }: PropsWithChildren<HTMLAttributes<HTMLHeadingElement>>): ReactNode => (
<LuxHeading {...props} level={4} />
);
export const LuxHeading5 = ({ ...props }: PropsWithChildren<HTMLAttributes<HTMLHeadingElement>>): ReactNode => (
<LuxHeading {...props} level={5} />
);
export const LuxHeading6 = ({ ...props }: PropsWithChildren<HTMLAttributes<HTMLHeadingElement>>): ReactNode => (
<LuxHeading {...props} level={6} />
);
148 changes: 148 additions & 0 deletions packages/components-react/src/heading/test/Heading.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { describe, expect, it } from '@jest/globals';
import { render, screen } from '@testing-library/react';
import { LuxHeading, LuxHeading1, LuxHeading2, LuxHeading3, LuxHeading4, LuxHeading5, LuxHeading6 } from '../Heading';

describe('Heading', () => {
it('renders a heading at heading level 1', () => {
render(<LuxHeading level={1}>Lux Heading</LuxHeading>);

const heading = screen.getByRole('heading', {
name: 'Lux Heading',
level: 1,
});
expect(heading).toBeInTheDocument();
});
it('can have a different appearance level', () => {
render(
<LuxHeading level={1} appearance={6}>
Lux Heading
</LuxHeading>,
);

const heading = screen.getByRole('heading', {
name: 'Lux Heading',
level: 1,
});

expect(heading).toHaveClass('utrecht-heading-6');
});

it('renders rich text content', () => {
render(
<LuxHeading level={1}>
<strong>Lux</strong> Heading
</LuxHeading>,
);

const heading = screen.getByRole('heading', {
name: 'Lux Heading',
});

const richText = heading.querySelector('strong');

expect(richText).toBeInTheDocument();
});

it('can be hidden', () => {
const { container } = render(<LuxHeading level={1} hidden />);

const heading = container.querySelector(':only-child');

expect(heading).not.toBeVisible();
});

it('can have an additional class name', () => {
render(
<LuxHeading level={1} className="large">
Lux Heading
</LuxHeading>,
);

const heading = screen.getByRole('heading', {
name: 'Lux Heading',
level: 1,
});

expect(heading).toHaveClass('large');

expect(heading).toHaveClass('utrecht-heading-1');
});

it('can should fall back to level 6 when the level is outside of the heading range', () => {
/* @ts-ignore */
render(<LuxHeading level={8}>Lux Heading</LuxHeading>);
const heading = screen.getByRole('heading', {
name: 'Lux Heading',
level: 6,
});

expect(heading).toBeInTheDocument();
});
});
describe('Heading 1', () => {
it('renders a heading1 component', () => {
render(<LuxHeading1>Lux Heading</LuxHeading1>);

const heading = screen.getByRole('heading', {
name: 'Lux Heading',
level: 1,
});
expect(heading).toBeInTheDocument();
});
});

describe('Heading 2', () => {
it('renders a heading2 component', () => {
render(<LuxHeading2>Lux Heading</LuxHeading2>);

const heading = screen.getByRole('heading', {
name: 'Lux Heading',
level: 2,
});
expect(heading).toBeInTheDocument();
});
});
describe('Heading 3', () => {
it('renders a heading3 component', () => {
render(<LuxHeading3>Lux Heading</LuxHeading3>);

const heading = screen.getByRole('heading', {
name: 'Lux Heading',
level: 3,
});
expect(heading).toBeInTheDocument();
});
});
describe('Heading 4', () => {
it('renders a heading4 component', () => {
render(<LuxHeading4>Lux Heading</LuxHeading4>);

const heading = screen.getByRole('heading', {
name: 'Lux Heading',
level: 4,
});
expect(heading).toBeInTheDocument();
});
});
describe('Heading 5', () => {
it('renders a heading5 component', () => {
render(<LuxHeading5>Lux Heading</LuxHeading5>);

const heading = screen.getByRole('heading', {
name: 'Lux Heading',
level: 5,
});
expect(heading).toBeInTheDocument();
});
});
describe('Heading 6', () => {
it('renders a heading6 component', () => {
render(<LuxHeading6>Lux Heading</LuxHeading6>);

const heading = screen.getByRole('heading', {
name: 'Lux Heading',
level: 6,
});
expect(heading).toBeInTheDocument();
});
});
11 changes: 10 additions & 1 deletion packages/components-react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import '@utrecht/component-library-css/dist/index.css';

export { LuxDocument, type LuxDocumentProps } from './document/Document';
export {
LuxHeading,
LuxHeading1,
LuxHeading2,
LuxHeading3,
LuxHeading4,
LuxHeading5,
LuxHeading6,
type LuxHeadingProps,
} from './heading/Heading';
export { LuxParagraph, type LuxParagraphProps } from './paragraph/Paragraph';
1 change: 1 addition & 0 deletions packages/storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@types/react-dom": "18.3.0",
"@utrecht/alert-css": "1.1.0",
"@utrecht/button-css": "1.2.0",
"@utrecht/heading-css": "1.2.0",
"@utrecht/link-css": "1.1.0",
"@utrecht/paragraph-css": "1.1.0",
"@vitejs/plugin-react": "4.3.1",
Expand Down
35 changes: 35 additions & 0 deletions packages/storybook/src/react-components/heading/heading.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Canvas, Controls, Description, Markdown, Meta } from "@storybook/blocks";
import markdown from "@utrecht/heading-css/README.md?raw";
import * as HeadingStories from "./heading.stories";
import { CitationDocumentation } from "../../utils/CitationDocumentation";

<Meta of={HeadingStories} />

# Lux Heading

<CitationDocumentation
component="Utrecht Heading"
url="https://nl-design-system.github.io/utrecht/storybook-css/index.html?path=/docs/css-heading--docs"
/>

<Markdown>{markdown}</Markdown>

## Opmerkingen

- Als de waarde voor de property `appearance` niet gedefinieerd is krijgt het de waarde van de property `level`
- De componenten "Heading1" t/m "Heading6" zijn gebouwd met het dynamische "Heading" component.

## Playground

<Canvas of={HeadingStories.Playground} />
<Controls of={HeadingStories.Playground} />

## Heading 1-6

<Description of={HeadingStories.Headings} />
<Canvas of={HeadingStories.Headings} />

## Heading With Different Appearance

<Description of={HeadingStories.HeadingWithDifferentAppearance} />
<Canvas of={HeadingStories.HeadingWithDifferentAppearance} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {
LuxHeading as Heading,
LuxHeading1,
LuxHeading2,
LuxHeading3,
LuxHeading4,
LuxHeading5,
LuxHeading6,
type LuxHeadingProps,
} from '@lux-design-system/components-react';
import tokens from '@lux-design-system/design-tokens/dist/index.json';
import type { Meta, StoryObj } from '@storybook/react';
import { type HTMLAttributes, type PropsWithChildren } from 'react';

const LuxHeading = (props: PropsWithChildren<LuxHeadingProps> & HTMLAttributes<HTMLHeadingElement>) => (
<Heading {...props} />
);

type Story = StoryObj<typeof meta>;

const meta = {
title: 'React Components/Heading',
id: 'react-components-heading',
component: LuxHeading,
subcomponents: {},
parameters: {
tokens,
tokensPrefix: 'react-heading',
},
argTypes: {
children: {
name: 'content',
description: 'React text',
control: 'text',
table: {
type: {
summary: 'HTML Content',
},
},
},
level: {
type: 'number',
control: 'number',
},
appearance: {
type: 'number',
control: 'number',
},
},
} satisfies Meta<typeof LuxHeading>;

export default meta;

const headingText = "Pa's wijze lynx bezag vroom het fikse aquaduct!";

const HeadingTemplate: Story = {
render: ({ children, ...args }) => <LuxHeading {...args}>{children}</LuxHeading>,
args: {
children: headingText,
level: 6,
},
};

export const Playground: Story = {
...HeadingTemplate,
name: 'Playground',
parameters: {
docs: {
sourceState: 'shown',
},
},
tags: ['!autodocs'],
};

export const Headings: Story = {
render: ({ children }) => (
<div>
<LuxHeading1>{children}</LuxHeading1>
<LuxHeading2>{children}</LuxHeading2>
<LuxHeading3>{children}</LuxHeading3>
<LuxHeading4>{children}</LuxHeading4>
<LuxHeading5>{children}</LuxHeading5>
<LuxHeading6>{children}</LuxHeading6>
</div>
),
args: {
children: headingText,
level: 6,
},
};

export const HeadingWithDifferentAppearance: Story = {
...HeadingTemplate,
args: {
children: headingText,
level: 3,
appearance: 1,
},
};
Loading

0 comments on commit edab49a

Please sign in to comment.