diff --git a/packages/css/src/components/row/row.scss b/packages/css/src/components/row/row.scss index 05caa045f3..2d45fbc419 100644 --- a/packages/css/src/components/row/row.scss +++ b/packages/css/src/components/row/row.scss @@ -19,3 +19,39 @@ .ams-row--wrap { flex-wrap: wrap; } + +.ams-row--align-around { + justify-content: space-around; +} + +.ams-row--align-between { + justify-content: space-between; +} + +.ams-row--align-center { + justify-content: center; +} + +.ams-row--align-end { + justify-content: flex-end; +} + +.ams-row--align-evenly { + justify-content: space-evenly; +} + +.ams-row--align-vertical-baseline { + align-items: baseline; +} + +.ams-row--align-vertical-center { + align-items: center; +} + +.ams-row--align-vertical-end { + align-items: flex-end; +} + +.ams-row--align-vertical-start { + align-items: flex-start; +} diff --git a/packages/react/src/Row/Row.test.tsx b/packages/react/src/Row/Row.test.tsx index a0cd46f245..a13ac09f39 100644 --- a/packages/react/src/Row/Row.test.tsx +++ b/packages/react/src/Row/Row.test.tsx @@ -1,6 +1,7 @@ import { render, screen } from '@testing-library/react' import { createRef } from 'react' import { Row, rowGapSizes } from './Row' +import { crossAlignOptions, mainAlignOptions } from '../common/layout' import '@testing-library/jest-dom' describe('Row', () => { @@ -64,4 +65,26 @@ describe('Row', () => { expect(ref.current).toBe(component) }) + + describe('Alignment', () => { + mainAlignOptions.map((align) => + it(`sets the ‘${align}’ alignment`, () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass(`ams-row--align-${align}`) + }), + ) + + crossAlignOptions.map((align) => + it(`sets the ‘${align}’ vertical alignment`, () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass(`ams-row--align-vertical-${align}`) + }), + ) + }) }) diff --git a/packages/react/src/Row/Row.tsx b/packages/react/src/Row/Row.tsx index a8171430e3..649df27795 100644 --- a/packages/react/src/Row/Row.tsx +++ b/packages/react/src/Row/Row.tsx @@ -6,24 +6,43 @@ import clsx from 'clsx' import { forwardRef } from 'react' import type { HTMLAttributes, PropsWithChildren } from 'react' +import type { CrossAlign, MainAlign } from '../common/layout' export const rowGapSizes: Array = ['extra-small', 'small', 'medium', 'large', 'extra-large'] -type RowTag = 'article' | 'div' | 'section' type RowGap = (typeof rowGapSizes)[number] +type RowTag = 'article' | 'div' | 'section' export type RowProps = { + /** The horizontal alignment of the items in the row. */ + align?: MainAlign + /** The vertical alignment of the items in the row. */ + alignVertical?: CrossAlign /** The HTML element to use. */ as?: RowTag /** The amount of vertical space between items. */ gap?: RowGap /** Whether items of the row can wrap onto multiple lines. */ - wrap?: Boolean + wrap?: boolean } & PropsWithChildren> export const Row = forwardRef( - ({ as: Tag = 'div', children, className, gap = 'medium', wrap = false, ...restProps }: RowProps, ref: any) => ( - + ( + { align, alignVertical, as: Tag = 'div', children, className, gap = 'medium', wrap, ...restProps }: RowProps, + ref: any, + ) => ( + {children} ), diff --git a/packages/react/src/common/layout.ts b/packages/react/src/common/layout.ts new file mode 100644 index 0000000000..737a52a031 --- /dev/null +++ b/packages/react/src/common/layout.ts @@ -0,0 +1,5 @@ +export const crossAlignOptions: Array = ['baseline', 'center', 'end', 'start'] +export type CrossAlign = (typeof crossAlignOptions)[number] + +export const mainAlignOptions: Array = ['around', 'between', 'center', 'end', 'evenly'] +export type MainAlign = (typeof mainAlignOptions)[number] diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 1aef581892..e3337119a2 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -4,6 +4,7 @@ */ /* Append here */ +export * from './common/layout' export * from './FormErrorList' export * from './TableOfContents' export * from './ErrorMessage' diff --git a/storybook/src/components/Row/Row.docs.mdx b/storybook/src/components/Row/Row.docs.mdx index 65d713bad2..e8e5f8396c 100644 --- a/storybook/src/components/Row/Row.docs.mdx +++ b/storybook/src/components/Row/Row.docs.mdx @@ -24,6 +24,55 @@ Wrap a Row around a set of components that need the same amount of white space b +### Horizontal Alignment + +Items in the row can be aligned horizontally in several ways: + +- **Center**: center items within the row. +- **End**: align items to the end of the row. This is the right side in left-to-right languages. +- **Space Between**: distribute whitespace between items. +- **Space Around**: distribute whitespace around items. +- **Space Evenly**: distribute whitespace evenly around items. + +By default, items align to the **start** of the row – the left side in right-to-left languages. +This options has no class name or prop. + +We left out the word ‘space’ from the values for the alignment prop, e.g. `align="around"`. + +This example ensures that the spaces between and around all items are equally wide. + + + +#### End-align a Single Child + +To align a single component to the right (in left-to-right languages), wrap it in a `` and set the `align` prop to `end`. + + + +#### Align Opposing Texts + +This example shows a right-aligned link next to a heading. +Use `align="between"` to position them at opposing ends of the row. +Add `alignVertical="baseline"` to align them vertically to their common baseline. +Include `wrap` to allow the link to wrap to a new line if both components don’t fit on the same line. + + + +### Vertical Alignment + +Items in the row can be aligned vertically in several ways: + +- **Start**: align items to the top of the row. +- **Center**: align items to the middle of the row. +- **End**: align items to the bottom of the row. +- **Baseline**: align items to their common text baseline. + +By default, items **stretch** to the height of the row. This options has no class name or prop. + +This example centers three items vertically. + + + ### Wrapping Items that may not fit together on a single line should be allowed to wrap onto multiple lines. diff --git a/storybook/src/components/Row/Row.stories.tsx b/storybook/src/components/Row/Row.stories.tsx index 7144b87006..a529e8d7da 100644 --- a/storybook/src/components/Row/Row.stories.tsx +++ b/storybook/src/components/Row/Row.stories.tsx @@ -3,16 +3,32 @@ * Copyright Gemeente Amsterdam */ -import { Button, Paragraph, Row } from '@amsterdam/design-system-react/src' +import { + Avatar, + Button, + crossAlignOptions, + Heading, + Link, + mainAlignOptions, + Paragraph, + Row, +} from '@amsterdam/design-system-react/src' import { Meta, StoryObj } from '@storybook/react' const ThreeButtons = Array.from(Array(3).keys()).map((i) => ) - -const FourBoxes = Array.from(Array(4).keys()).map((i) => ( - - Wrapping row item {i + 1} - -)) +const ThreeParagraphs = [ + + One line + , + + Two +
+ lines +
, + + One line + , +] const meta = { title: 'Components/Layout/Row', @@ -21,6 +37,14 @@ const meta = { children: ThreeButtons, }, argTypes: { + align: { + control: 'radio', + options: mainAlignOptions, + }, + alignVertical: { + control: 'radio', + options: crossAlignOptions, + }, gap: { control: 'radio', options: ['extra-small', 'small', 'medium', 'large', 'extra-large'], @@ -34,9 +58,41 @@ type Story = StoryObj export const Default: Story = {} +export const HorizontalAlignment: Story = { + args: { + align: 'evenly', + children: ThreeParagraphs, + }, +} + +export const EndAlignASingleChild: Story = { + args: { + align: 'end', + children: , + }, +} + +export const AlignOpposingTexts: Story = { + args: { + align: 'between', + alignVertical: 'baseline', + children: [An example heading, An example link], + wrap: true, + }, +} + +export const VerticalAlignment: Story = { + args: { + alignVertical: 'center', + children: ThreeParagraphs, + }, +} + export const Wrapping: Story = { args: { - children: FourBoxes, + children: Array.from(Array(4).keys()).map((i) => ( + + )), wrap: true, }, }