diff --git a/documentation/storybook.md b/documentation/storybook.md index b7c0374ecf..107101c91b 100644 --- a/documentation/storybook.md +++ b/documentation/storybook.md @@ -19,7 +19,8 @@ We write our documentation in Dutch. ## Best practices for controls -1. For props offering five options or less, use radio buttons rather than a dropdown. This makes it easier to compare the options. It saves the user a click to select each option and clearly shows all of them up front. +1. For props offering five options or less, use radio buttons rather than a select. This makes it easier to compare the options. It saves the user a click to select each option and clearly shows all of them up front. +2. Don’t use inline radios. Their options appear rather small, making them difficult to target with a pointing device. More to follow. diff --git a/packages/css/src/components/badge/README.md b/packages/css/src/components/badge/README.md new file mode 100644 index 0000000000..740d4831b0 --- /dev/null +++ b/packages/css/src/components/badge/README.md @@ -0,0 +1,10 @@ +# Badge + +A prominently coloured box containing 1 or 2 words. +Guides the user in taking a specific action or describes its surrounding content. + +## Design + +The badge can contain a short text or a number. +The default background colour is dark green. +Suggestions on when to use the other colours will follow soon. diff --git a/packages/css/src/components/badge/badge.scss b/packages/css/src/components/badge/badge.scss new file mode 100644 index 0000000000..1c20a45492 --- /dev/null +++ b/packages/css/src/components/badge/badge.scss @@ -0,0 +1,58 @@ +/** + * @license EUPL-1.2+ + * Copyright (c) 2024 Gemeente Amsterdam + */ + +.amsterdam-badge { + display: inline-block; + font-family: var(--amsterdam-badge-font-family); + font-size: var(--amsterdam-badge-spacious-font-size); + font-weight: var(--amsterdam-badge-font-weight); + line-height: var(--amsterdam-badge-spacious-line-height); + padding-inline: var(--amsterdam-badge-padding-inline); + + .amsterdam-theme--compact & { + font-size: var(--amsterdam-badge-compact-font-size); + line-height: var(--amsterdam-badge-compact-line-height); + } +} + +.amsterdam-badge--blue { + background-color: var(--amsterdam-badge-blue-background-color); + color: var(--amsterdam-badge-blue-color); +} + +.amsterdam-badge--dark-blue { + background-color: var(--amsterdam-badge-dark-blue-background-color); + color: var(--amsterdam-badge-dark-blue-color); +} + +.amsterdam-badge--dark-green { + background-color: var(--amsterdam-badge-dark-green-background-color); + color: var(--amsterdam-badge-dark-green-color); +} + +.amsterdam-badge--green { + background-color: var(--amsterdam-badge-green-background-color); + color: var(--amsterdam-badge-green-color); +} + +.amsterdam-badge--magenta { + background-color: var(--amsterdam-badge-magenta-background-color); + color: var(--amsterdam-badge-magenta-color); +} + +.amsterdam-badge--orange { + background-color: var(--amsterdam-badge-orange-background-color); + color: var(--amsterdam-badge-orange-color); +} + +.amsterdam-badge--purple { + background-color: var(--amsterdam-badge-purple-background-color); + color: var(--amsterdam-badge-purple-color); +} + +.amsterdam-badge--yellow { + background-color: var(--amsterdam-badge-yellow-background-color); + color: var(--amsterdam-badge-yellow-color); +} diff --git a/packages/css/src/components/index.scss b/packages/css/src/components/index.scss index f4374cd68d..247c622e19 100644 --- a/packages/css/src/components/index.scss +++ b/packages/css/src/components/index.scss @@ -4,6 +4,7 @@ */ /* Append here */ +@import "./badge/badge"; @import "./table/table"; @import "./mega-menu/mega-menu"; @import "./icon-button/icon-button"; diff --git a/packages/react/src/Badge/Badge.test.tsx b/packages/react/src/Badge/Badge.test.tsx new file mode 100644 index 0000000000..5d029e2d57 --- /dev/null +++ b/packages/react/src/Badge/Badge.test.tsx @@ -0,0 +1,67 @@ +import { render } from '@testing-library/react' +import { createRef } from 'react' +import { Badge, badgeColors } from './Badge' +import '@testing-library/jest-dom' + +describe('Badge', () => { + it('renders', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toBeInTheDocument() + expect(component).toBeVisible() + }) + + it('renders a design system BEM class name', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass('amsterdam-badge') + }) + + it('renders an additional class name', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass('amsterdam-badge extra') + }) + + it('supports ForwardRef in React', () => { + const ref = createRef() + + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(ref.current).toBe(component) + }) + + it('renders with a number label', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveTextContent('1') + }) + + it('renders with default color', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass('amsterdam-badge--dark-green') + }) + + badgeColors.map((color) => + it(`renders with ${color} color`, () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass(`amsterdam-badge--${color}`) + }), + ) +}) diff --git a/packages/react/src/Badge/Badge.tsx b/packages/react/src/Badge/Badge.tsx new file mode 100644 index 0000000000..56a1571aaa --- /dev/null +++ b/packages/react/src/Badge/Badge.tsx @@ -0,0 +1,36 @@ +/** + * @license EUPL-1.2+ + * Copyright (c) 2024 Gemeente Amsterdam + */ + +import clsx from 'clsx' +import { forwardRef } from 'react' +import type { ForwardedRef, HTMLAttributes } from 'react' + +export const badgeColors = [ + 'blue', + 'dark-blue', + 'dark-green', + 'green', + 'magenta', + 'orange', + 'purple', + 'yellow', +] as const + +type BadgeColor = (typeof badgeColors)[number] + +export type BadgeProps = { + color?: BadgeColor + label: string | number +} & HTMLAttributes + +export const Badge = forwardRef( + ({ label, className, color = 'dark-green', ...restProps }: BadgeProps, ref: ForwardedRef) => ( + + {label} + + ), +) + +Badge.displayName = 'Badge' diff --git a/packages/react/src/Badge/README.md b/packages/react/src/Badge/README.md new file mode 100644 index 0000000000..b34e77c7c1 --- /dev/null +++ b/packages/react/src/Badge/README.md @@ -0,0 +1,3 @@ +# React Badge component + +[Badge documentation](../../../css/src/badge/README.md) diff --git a/packages/react/src/Badge/index.ts b/packages/react/src/Badge/index.ts new file mode 100644 index 0000000000..292a59dbf2 --- /dev/null +++ b/packages/react/src/Badge/index.ts @@ -0,0 +1,2 @@ +export { Badge } from './Badge' +export type { BadgeProps } from './Badge' diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 992c789f5c..9df069b981 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -4,6 +4,7 @@ */ /* Append here */ +export * from './Badge' export * from './Table' export * from './MegaMenu' export * from './IconButton' diff --git a/proprietary/tokens/src/components/amsterdam/badge.tokens.json b/proprietary/tokens/src/components/amsterdam/badge.tokens.json new file mode 100644 index 0000000000..0a722c1226 --- /dev/null +++ b/proprietary/tokens/src/components/amsterdam/badge.tokens.json @@ -0,0 +1,49 @@ +{ + "amsterdam": { + "badge": { + "font-family": { "value": "{amsterdam.typography.font-family}" }, + "font-weight": { "value": "{amsterdam.typography.font-weight.bold}" }, + "padding-inline": { "value": "0.5rem" }, + "spacious": { + "font-size": { "value": "{amsterdam.typography.spacious.text-level.5.font-size}" }, + "line-height": { "value": "{amsterdam.typography.spacious.text-level.5.line-height}" } + }, + "compact": { + "font-size": { "value": "{amsterdam.typography.compact.text-level.5.font-size}" }, + "line-height": { "value": "{amsterdam.typography.compact.text-level.5.line-height}" } + }, + "blue": { + "background-color": { "value": "{amsterdam.color.blue}" }, + "color": { "value": "{amsterdam.color.primary-black}" } + }, + "dark-blue": { + "background-color": { "value": "{amsterdam.color.primary-blue}" }, + "color": { "value": "{amsterdam.color.primary-white}" } + }, + "dark-green": { + "background-color": { "value": "{amsterdam.color.dark-green}" }, + "color": { "value": "{amsterdam.color.primary-white}" } + }, + "green": { + "background-color": { "value": "{amsterdam.color.green}" }, + "color": { "value": "{amsterdam.color.primary-black}" } + }, + "magenta": { + "background-color": { "value": "{amsterdam.color.magenta}" }, + "color": { "value": "{amsterdam.color.primary-white}" } + }, + "orange": { + "background-color": { "value": "{amsterdam.color.orange}" }, + "color": { "value": "{amsterdam.color.primary-black}" } + }, + "purple": { + "background-color": { "value": "{amsterdam.color.purple}" }, + "color": { "value": "{amsterdam.color.primary-white}" } + }, + "yellow": { + "background-color": { "value": "{amsterdam.color.yellow}" }, + "color": { "value": "{amsterdam.color.primary-black}" } + } + } + } +} diff --git a/storybook/storybook-react/src/Accordion/Accordion.stories.tsx b/storybook/storybook-react/src/Accordion/Accordion.stories.tsx index 3108a2106e..6b2cc639ee 100644 --- a/storybook/storybook-react/src/Accordion/Accordion.stories.tsx +++ b/storybook/storybook-react/src/Accordion/Accordion.stories.tsx @@ -24,11 +24,15 @@ const meta = { }, }, headingLevel: { - control: { type: 'select' }, + control: { + type: 'radio', + }, options: [1, 2, 3, 4], }, section: { - control: { type: 'boolean' }, + control: { + type: 'boolean', + }, }, }, } satisfies Meta diff --git a/storybook/storybook-react/src/Badge/Badge.docs.mdx b/storybook/storybook-react/src/Badge/Badge.docs.mdx new file mode 100644 index 0000000000..2a21267d46 --- /dev/null +++ b/storybook/storybook-react/src/Badge/Badge.docs.mdx @@ -0,0 +1,15 @@ +import { Controls, Markdown, Meta, Primary } from "@storybook/blocks"; +import * as BadgeStories from "./Badge.stories.tsx"; +import README from "../../../../packages/css/src/components/badge/README.md?raw"; + + + +{README} + +## Stories + +### Default + + + + diff --git a/storybook/storybook-react/src/Badge/Badge.stories.tsx b/storybook/storybook-react/src/Badge/Badge.stories.tsx new file mode 100644 index 0000000000..130951da4d --- /dev/null +++ b/storybook/storybook-react/src/Badge/Badge.stories.tsx @@ -0,0 +1,30 @@ +/** + * @license EUPL-1.2+ + * Copyright (c) 2024 Gemeente Amsterdam + */ + +import { Badge } from '@amsterdam/design-system-react' +import { Meta, StoryObj } from '@storybook/react' + +const meta = { + title: 'Feedback/Badge', + component: Badge, + args: { + label: 'Tip', + }, + argTypes: { + color: { + control: { + type: 'select', + }, + options: ['blue', 'dark-blue', 'dark-green', 'green', 'magenta', 'orange', 'purple', 'yellow'], + selected: 'dark-green', + }, + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Default: Story = {} diff --git a/storybook/storybook-react/src/Grid/Grid.stories.tsx b/storybook/storybook-react/src/Grid/Grid.stories.tsx index f6af49f610..12af326926 100644 --- a/storybook/storybook-react/src/Grid/Grid.stories.tsx +++ b/storybook/storybook-react/src/Grid/Grid.stories.tsx @@ -18,7 +18,9 @@ const argTypes = { const gridArgTypes = { compact: { - control: { type: 'boolean' }, + control: { + type: 'boolean', + }, }, gapVertical: { control: { @@ -34,14 +36,24 @@ const gridArgTypes = { const gridCellArgTypes = { as: { - control: { type: 'inline-radio' }, + control: { + type: 'radio', + }, options: ['article', 'div', 'section'], }, span: { - control: { type: 'number', min: 1, max: 12 }, + control: { + type: 'number', + min: 1, + max: 12, + }, }, start: { - control: { type: 'number', min: 1, max: 12 }, + control: { + type: 'number', + min: 1, + max: 12, + }, }, } diff --git a/storybook/storybook-react/src/Header/Header.stories.tsx b/storybook/storybook-react/src/Header/Header.stories.tsx index f269d04b37..15f1bc5297 100644 --- a/storybook/storybook-react/src/Header/Header.stories.tsx +++ b/storybook/storybook-react/src/Header/Header.stories.tsx @@ -13,7 +13,7 @@ const meta = { argTypes: { logoBrand: { control: { - type: 'select', + type: 'radio', }, options: ['amsterdam', 'ggd-amsterdam', 'stadsarchief', 'stadsbank-van-lening', 'vga-verzekeringen'], }, diff --git a/storybook/storybook-react/src/Icon/Icon.stories.tsx b/storybook/storybook-react/src/Icon/Icon.stories.tsx index d7a2b0cc4c..802db0ccf7 100644 --- a/storybook/storybook-react/src/Icon/Icon.stories.tsx +++ b/storybook/storybook-react/src/Icon/Icon.stories.tsx @@ -12,14 +12,20 @@ const meta = { component: Icon, argTypes: { size: { - control: { type: 'select' }, + control: { + type: 'radio', + }, options: ['level-3', 'level-4', 'level-5', 'level-6'], }, square: { - control: { type: 'boolean' }, + control: { + type: 'boolean', + }, }, svg: { - control: { type: 'select' }, + control: { + type: 'select', + }, options: Object.keys(Icons), mapping: Icons, }, diff --git a/storybook/storybook-react/src/IconButton/IconButton.stories.tsx b/storybook/storybook-react/src/IconButton/IconButton.stories.tsx index 19e887e650..abb9b0ba19 100644 --- a/storybook/storybook-react/src/IconButton/IconButton.stories.tsx +++ b/storybook/storybook-react/src/IconButton/IconButton.stories.tsx @@ -31,7 +31,9 @@ const meta = { options: ['level-3', 'level-4', 'level-5', 'level-6'], }, svg: { - control: { type: 'select' }, + control: { + type: 'select', + }, options: Object.keys(Icons), mapping: Icons, }, diff --git a/storybook/storybook-react/src/Link/Link.stories.tsx b/storybook/storybook-react/src/Link/Link.stories.tsx index c058718a02..44d37aaff2 100644 --- a/storybook/storybook-react/src/Link/Link.stories.tsx +++ b/storybook/storybook-react/src/Link/Link.stories.tsx @@ -14,7 +14,9 @@ const meta = { component: Link, argTypes: { icon: { - control: { type: 'select' }, + control: { + type: 'select', + }, options: Object.keys(Icons), mapping: Icons, table: { @@ -22,7 +24,10 @@ const meta = { }, }, onBackground: { - control: { type: 'select', labels: { undefined: 'default', light: 'light', dark: 'dark' } }, + control: { + type: 'radio', + labels: { undefined: 'default', light: 'light', dark: 'dark' }, + }, options: [undefined, 'light', 'dark'], table: { category: 'API', @@ -30,7 +35,7 @@ const meta = { }, variant: { control: { - type: 'select', + type: 'radio', labels: { standalone: 'standalone', inline: 'inline', inList: 'inList' }, }, options: ['standalone', 'inline', 'inList'], diff --git a/storybook/storybook-react/src/Logo/Logo.stories.tsx b/storybook/storybook-react/src/Logo/Logo.stories.tsx index de319d64ca..634e024fbb 100644 --- a/storybook/storybook-react/src/Logo/Logo.stories.tsx +++ b/storybook/storybook-react/src/Logo/Logo.stories.tsx @@ -12,7 +12,7 @@ const meta = { argTypes: { brand: { control: { - type: 'select', + type: 'radio', }, options: ['amsterdam', 'ggd-amsterdam', 'stadsarchief', 'stadsbank-van-lening', 'vga-verzekeringen'], }, diff --git a/storybook/storybook-react/src/Paragraph/Paragraph.stories.tsx b/storybook/storybook-react/src/Paragraph/Paragraph.stories.tsx index d52efb3473..c73887c059 100644 --- a/storybook/storybook-react/src/Paragraph/Paragraph.stories.tsx +++ b/storybook/storybook-react/src/Paragraph/Paragraph.stories.tsx @@ -19,7 +19,7 @@ const meta = { argTypes: { size: { control: { - type: 'select', + type: 'radio', labels: { undefined: 'default', large: 'large', small: 'small' }, }, options: [undefined, 'large', 'small'], diff --git a/storybook/storybook-react/src/Spotlight/Spotlight.stories.tsx b/storybook/storybook-react/src/Spotlight/Spotlight.stories.tsx index b106b0af84..fc6427a8c4 100644 --- a/storybook/storybook-react/src/Spotlight/Spotlight.stories.tsx +++ b/storybook/storybook-react/src/Spotlight/Spotlight.stories.tsx @@ -14,13 +14,14 @@ const meta = { component: Spotlight, argTypes: { as: { - control: { type: 'inline-radio' }, + control: { + type: 'radio', + }, options: ['article', 'aside', 'div', 'footer', 'section'], }, color: { - options: ['blue', 'dark-green', 'green', 'light-blue', 'magenta', 'orange', 'purple', 'yellow'], control: { - type: 'radio', + type: 'select', labels: { blue: 'Blauw', 'dark-green': 'Donkergroen', @@ -32,6 +33,7 @@ const meta = { yellow: 'Geel', }, }, + options: ['blue', 'dark-green', 'green', 'light-blue', 'magenta', 'orange', 'purple', 'yellow'], }, children: { control: {