diff --git a/packages/css/src/footer/footer.scss b/packages/css/src/footer/footer.scss index c431c40c6f..5059ca70ca 100644 --- a/packages/css/src/footer/footer.scss +++ b/packages/css/src/footer/footer.scss @@ -7,9 +7,4 @@ .amsterdam-footer__top { background-color: var(--amsterdam-footer-top-background-color); - padding-block: 2.5rem; -} - -.amsterdam-footer__bottom { - padding-block: 0.5rem; } diff --git a/packages/css/src/grid/grid.scss b/packages/css/src/grid/grid.scss index 02e3b8981e..86713c9c96 100644 --- a/packages/css/src/grid/grid.scss +++ b/packages/css/src/grid/grid.scss @@ -6,10 +6,12 @@ @import "../../utils/breakpoint"; .amsterdam-grid { + --amsterdam-grid-gap: var(--amsterdam-grid-density-low-gap); + --amsterdam-grid-padding-inline: var(--amsterdam-grid-density-low-padding-inline); + display: grid; gap: var(--amsterdam-grid-gap); grid-template-columns: repeat(var(--amsterdam-grid-column-count), 1fr); - margin-inline: auto; padding-inline: var(--amsterdam-grid-padding-inline); @media screen and (min-width: $amsterdam-breakpoint-medium) { @@ -20,3 +22,44 @@ grid-template-columns: repeat(var(--amsterdam-grid-wide-column-count), 1fr); } } + +.amsterdam-grid--density-high { + --amsterdam-grid-gap: var(--amsterdam-grid-density-high-gap); + --amsterdam-grid-padding-inline: var(--amsterdam-grid-density-high-padding-inline); +} + +.amsterdam-grid--padding-bottom--small { + padding-block-end: calc(var(--amsterdam-grid-gap) / 2); +} + +.amsterdam-grid--padding-bottom--medium { + padding-block-end: var(--amsterdam-grid-gap); +} + +.amsterdam-grid--padding-bottom--large { + padding-block-end: calc(var(--amsterdam-grid-gap) * 2); +} + +.amsterdam-grid--padding-top--small { + padding-block-start: calc(var(--amsterdam-grid-gap) / 2); +} + +.amsterdam-grid--padding-top--medium { + padding-block-start: var(--amsterdam-grid-gap); +} + +.amsterdam-grid--padding-top--large { + padding-block-start: calc(var(--amsterdam-grid-gap) * 2); +} + +.amsterdam-grid--padding-vertical--small { + padding-block: calc(var(--amsterdam-grid-gap) / 2); +} + +.amsterdam-grid--padding-vertical--medium { + padding-block: var(--amsterdam-grid-gap); +} + +.amsterdam-grid--padding-vertical--large { + padding-block: calc(var(--amsterdam-grid-gap) * 2); +} diff --git a/packages/css/src/highlight/highlight.scss b/packages/css/src/highlight/highlight.scss index 6dccb66dbf..b033fb37ed 100644 --- a/packages/css/src/highlight/highlight.scss +++ b/packages/css/src/highlight/highlight.scss @@ -3,10 +3,6 @@ * Copyright (c) 2023 Gemeente Amsterdam */ -.amsterdam-highlight { - padding-block: var(--amsterdam-highlight-padding-block); -} - .amsterdam-highlight--blue { background-color: var(--amsterdam-highlight-blue-background-color); } diff --git a/packages/react/src/Grid/Grid.test.tsx b/packages/react/src/Grid/Grid.test.tsx index 5d62772910..a3d39fddfd 100644 --- a/packages/react/src/Grid/Grid.test.tsx +++ b/packages/react/src/Grid/Grid.test.tsx @@ -24,6 +24,48 @@ describe('Grid', () => { expect(component).toHaveClass('amsterdam-grid') }) + it('renders the high-density class name', () => { + const { container } = render() + const component = container.querySelector(':only-child') + expect(component).toHaveClass('amsterdam-grid--density-high') + }) + + it('renders a medium vertical spacing class name for a low-density grid', () => { + const { container } = render() + const component = container.querySelector(':only-child') + expect(component).toHaveClass('amsterdam-grid--padding-vertical--medium') + }) + + it('renders a medium vertical spacing class name for a high-density grid', () => { + const { container } = render() + const component = container.querySelector(':only-child') + expect(component).toHaveClass('amsterdam-grid--padding-vertical--medium') + }) + + it('renders a small top class name for a low-density grid', () => { + const { container } = render() + const component = container.querySelector(':only-child') + expect(component).toHaveClass('amsterdam-grid--padding-top--small') + }) + + it('renders a small top class name for a high-density grid', () => { + const { container } = render() + const component = container.querySelector(':only-child') + expect(component).toHaveClass('amsterdam-grid--padding-top--small') + }) + + it('renders a large bottom class name for a low-density grid', () => { + const { container } = render() + const component = container.querySelector(':only-child') + expect(component).toHaveClass('amsterdam-grid--padding-bottom--large') + }) + + it('renders a large bottom class name for a high-density grid', () => { + const { container } = render() + const component = container.querySelector(':only-child') + expect(component).toHaveClass('amsterdam-grid--padding-bottom--large') + }) + it('supports ForwardRef in React', () => { const ref = createRef() const { container } = render() diff --git a/packages/react/src/Grid/Grid.tsx b/packages/react/src/Grid/Grid.tsx index 32e96f317d..6799b253aa 100644 --- a/packages/react/src/Grid/Grid.tsx +++ b/packages/react/src/Grid/Grid.tsx @@ -22,17 +22,76 @@ export type GridColumnNumbers = { wide: GridColumnNumber } -interface GridComponent - extends ForwardRefExoticComponent> & RefAttributes> { +type GridDensity = 'low' | 'high' + +type GridPaddingSize = 'small' | 'medium' | 'large' + +type GridPaddingVerticalProp = { + paddingBottom?: never + paddingTop?: never + /** The amount of vertical whitespace above and below the grid. */ + paddingVertical?: GridPaddingSize +} + +type GridPaddingTopAndBottomProps = { + /** The amount of vertical whitespace below the grid. */ + paddingBottom?: GridPaddingSize + /** The amount of vertical whitespace above the grid. */ + paddingTop?: GridPaddingSize + paddingVertical?: never +} + +export type GridProps = { + /** + * The density of the grid: low (for websites) or high (for applications). + * Adjusts the pace with which columns get wider, and the start width as well. + * This is to be implemented more generally – it will be moved into a theme soon. + */ + density?: GridDensity +} & (GridPaddingVerticalProp | GridPaddingTopAndBottomProps) & + PropsWithChildren> + +const paddingClasses = ( + paddingBottom?: GridPaddingSize, + paddingTop?: GridPaddingSize, + paddingVertical?: GridPaddingSize, +): string[] => { + const classes = [] as string[] + + if (paddingVertical) { + return [`amsterdam-grid--padding-vertical--${paddingVertical}`] + } + + if (paddingBottom) { + classes.push(`amsterdam-grid--padding-bottom--${paddingBottom}`) + } + + if (paddingTop) { + classes.push(`amsterdam-grid--padding-top--${paddingTop}`) + } + + return classes +} + +interface GridComponent extends ForwardRefExoticComponent> { Cell: typeof GridCell } export const Grid = forwardRef( ( - { children, className, ...restProps }: PropsWithChildren>, + { children, className, density = 'low', paddingBottom, paddingTop, paddingVertical, ...restProps }: GridProps, ref: ForwardedRef, ) => ( -
+
{children}
), diff --git a/packages/react/src/Grid/GridCell.tsx b/packages/react/src/Grid/GridCell.tsx index 78ee2b57dd..3395b28e6f 100644 --- a/packages/react/src/Grid/GridCell.tsx +++ b/packages/react/src/Grid/GridCell.tsx @@ -23,7 +23,6 @@ type GridCellColumnProps = { start?: GridColumnNumber | GridColumnNumbers } -// The discriminated union and the `never` types prevent using `fullWidth` together with `span` or `start`. export type GridCellProps = (GridCellFullWidthProp | GridCellColumnProps) & PropsWithChildren> diff --git a/proprietary/tokens/src/components/amsterdam/grid.tokens.json b/proprietary/tokens/src/components/amsterdam/grid.tokens.json index 8e1bc9faea..9b45e920d1 100644 --- a/proprietary/tokens/src/components/amsterdam/grid.tokens.json +++ b/proprietary/tokens/src/components/amsterdam/grid.tokens.json @@ -4,13 +4,25 @@ "column-count": { "value": "4" }, - "gap": { - "value": "clamp(0.5rem, 0.375rem + 3.125vw, 3.5rem)", - "comment": "Grows from 8px at 320px wide to 56px at 1440px wide." + "density-low": { + "gap": { + "value": "clamp(1rem, 3.125vw + 0.375rem, 3.5rem)", + "comment": "Grows from 16px at 320px wide to 56px at 1600px wide." + }, + "padding-inline": { + "value": "clamp(1.5rem, 4.6875vw + 0.5625rem, 5.25rem)", + "comment": "Equals 1.5 times the gap." + } }, - "padding-inline": { - "value": "clamp(0.75rem, 0.5625rem + 4.875vw, 5.25rem)", - "comment": "Equals 1.5 times the gap." + "density-high": { + "gap": { + "value": "clamp(1rem, 1.5625vw - 0.0625rem, 2.5rem)", + "comment": "Grows from 16px at 1088px wide to 40px at 2624px wide." + }, + "padding-inline": { + "value": "clamp(1rem, 1.5625vw - 0.0625rem, 2.5rem)", + "comment": "Equals the gap." + } }, "medium": { "column-count": { diff --git a/proprietary/tokens/src/components/amsterdam/highlight.tokens.json b/proprietary/tokens/src/components/amsterdam/highlight.tokens.json index fa8430be77..90f3fd6b95 100644 --- a/proprietary/tokens/src/components/amsterdam/highlight.tokens.json +++ b/proprietary/tokens/src/components/amsterdam/highlight.tokens.json @@ -1,9 +1,6 @@ { "amsterdam": { "highlight": { - "padding-block": { - "value": "2rem" - }, "blue": { "background-color": { "value": "{amsterdam.color.primary-blue}" diff --git a/storybook/storybook-docs/src/grid.stories.mdx b/storybook/storybook-docs/src/grid.stories.mdx index 99a8317f9a..2e01b12d28 100644 --- a/storybook/storybook-docs/src/grid.stories.mdx +++ b/storybook/storybook-docs/src/grid.stories.mdx @@ -17,29 +17,55 @@ Het grid is volledig _responsive_: afhankelijk van de breedte van het venster ve In vensters tot 576 pixels breed levert het grid 4 kolommen. Tot een breedte van 1088 pixels zijn dat er 8. Op dat punt worden en blijven het er 12. +Alle kolommen zijn even breed. -Deze breakpoints zijn gebaseerd op stappen van 256 pixels, beginnend op een basis van 64 pixels. +De breakpoints zijn gebaseerd op stappen van 256 pixels, beginnend op een basis van 64 pixels. Zo is 576 = 64 + 2 × 256 en 1088 = 64 + 4 × 256. -### Veel witruimte +De maatvoering van de witruimtes en de typografie veranderen op deze breakpoints niet. +Wel wijzigt het aantal kolommen, die daardoor smaller of breder worden. +Ook kunnen elementen op dat moment van plaats veranderen op de pagina. + +### Twee thema’s voor witruimte + +#### Ruimtelijk + +Voor websites is het grid behoorlijk ruimtelijk. +Zo is de huisstijl ontworpen. De horizontale witruimte tussen kolommen is 16 pixels breed bij een vensterbreedte van 320 pixels. Voor elke 256 pixels extra breedte groeit de witruimte lineair met 8 pixels. Bij een vensterbreedte van 1600 pixels is het dus 56 pixels. -Dit past bij de ruimtelijke opzet van de huisstijl. +In vensters die breder zijn dan dat groeit de witruimte niet verder. -Op brede vensters worden de witruimtes bijna even breed als de kolommen zelf. +In brede vensters worden de witruimtes bijna even breed als de kolommen zelf. Dat lijkt misschien onnatuurlijk, maar is geen probleem – doorgaans worden elementen op 3 of 4 kolommen van het grid geplaatst en die krijgen dan ook de tussenliggende witruimtes mee. +#### Compact + +Voor applicaties is zo veel witruimte niet nodig, zelfs contraproductief. +Daarom is er een compact thema voor het grid. + +Hier begint de horizontale witruimte op 4 pixels bij een vensterbreedte van 320 pixels, +en groeit deze met 4 pixels per 256 pixels extra breedte. +Het maximum is hier 40 pixels en dat wordt pas bereikt bij een vensterbreedte van 2.624 pixels. + +Alle andere eigenschappen van de compacte variatie zijn gelijk aan de ruimtelijke. + ### Marges links en rechts -Het grid staat horizontaal gecentreerd op de pagina. +#### Voor websites + +Het grid centreert zichzelf horizontaal. Aan beide zijden reserveert het grid marges om afstand te houden tot de randen van het venster. Deze ruimte is 1½ keer zo breed als die tussen de kolommen. Sommige elementen mogen over deze marges gepositioneerd worden, zoals de Page Footer en een schermvullende afbeelding. Die zijn dan dus breder dan de rest van de content, maar worden wel beperkt door een maximale breedte. +Voor applicaties is de marge naast het grid gelijk aan de witruimtes erbinnen. +Hier worden geen elementen over de marges heen geplaatst. + ### Niet onbeperkt breed Een maximale breedte voor het grid zorgt ervoor dat de elementen van een pagina niet al te ver van elkaar verwijderd raken. @@ -48,16 +74,21 @@ Ook wordt de witruimte en typografie dan niet extreem groot. Voor websites is de maximale breedte van het grid 1432 pixels. In vensters van minimaal 1600 pixels breed heeft het grid deze breedte – de marges bedragen hier 84 pixels. -Voor applicaties kan de maximale breedte worden ingesteld op 1896 pixels. +De maximale breedte kan ook worden ingesteld op 1896 pixels. Inclusief de marges van 108 pixels komt de totale breedte hier op 2112 pixels. +Voor applicaties is er geen maximale breedte. + Er is geen minimale breedte. -Zelfs in vensters smaller dan 320 pixels, voor zover die in de praktijk voorkomen, blijft het grid zich schalen naar de beschikbare breedte. +Zelfs in vensters smaller dan 320 pixels, voor zover die in de praktijk voorkomen, +blijft het grid zich schalen naar de beschikbare breedte. Voor het gemak speelt de breedte van 320 wel een rol in de documentatie. ### De maatvoeringen per variant -Op het startpunt van de drie varianten van het grid hebben de kolommen, witruimtes en marges de volgende breedtes in pixels: +Op het startpunt van de drie varianten van het grid hebben de kolommen, witruimtes en marges de volgende breedtes in pixels. + +#### Ruimtelijk | Naam variant | Vanaf vensterbreedte | Aantal kolommen | Breedte kolom | Breedte witruimte | Breedte marge | Breedte grid | | :----------- | -------------------: | --------------: | ------------: | ----------------: | ------------: | -----------: | @@ -65,12 +96,38 @@ Op het startpunt van de drie varianten van het grid hebben de kolommen, witruimt | middelbreed | 576 | 8 | 42 | 24 | 36 | 504 | | breed | 1088 | 12 | 44 | 40 | 60 | 968 | -Voor de maximale breedtes van het grid: +#### Compact + +| Naam variant | Vanaf vensterbreedte | Aantal kolommen | Breedte kolom | Breedte witruimte | Breedte marge | Breedte grid | +| :----------- | -------------------: | --------------: | ------------: | ----------------: | ------------: | -----------: | +| smal | 320 | 4 | 56 | 16 | 24 | 272 | +| middelbreed | 576 | 8 | 42 | 24 | 36 | 504 | +| breed | 1088 | 12 | 44 | 40 | 60 | 968 | + +### Breedtes voor design + +In Figma werken we met drie referentie-breedtes om het ontwerp uit te werken voor de drie klassen van apparaten. +Op precies die breedtes is de maatvoering als volgt. + +#### Ruimtelijk + +| Klasse apparaat | Referentie-breedte | Aantal kolommen | Breedte kolom | Breedte witruimte | Breedte marge | Breedte grid | +| :-------------- | -----------------: | --------------: | ------------: | ----------------: | ------------: | -----------: | +| telefoon | 320 | 4 | 56 | 16 | 24 | 272 | +| tablet | 832 | 8 | 64 | 32 | 48 | 736 | +| desktop | 1600 | 12 | 68 | 56 | 84 | 1432 | + +#### Compact + +| Klasse apparaat | Referentie-breedte | Aantal kolommen | Breedte kolom | Breedte witruimte | Breedte marge | Breedte grid | +| :-------------- | -----------------: | --------------: | ------------: | ----------------: | ------------: | -----------: | +| telefoon | 320 | 4 | 60 | 16 | 24 | 288 | +| tablet | 832 | 8 | 86 | 16 | 16 | 800 | +| desktop | 1600 | 12 | 98 | 24 | 24 | 1440 | -| Maximale breedte voor | Bij vensterbreedte | Aantal kolommen | Breedte kolom | Breedte witruimte | Breedte marge | Breedte grid | -| :-------------------- | -----------------: | --------------: | ------------: | ----------------: | ------------: | -----------: | -| websites | 1600 | 12 | 68 | 56 | 84 | 1432 | -| applicaties | 2112 | 12 | 92 | 72 | 108 | 1896 | +Let op: vanaf een vensterbreedte van 1088 pixels staat er een navigatiebalk links van het grid. +Deze is 112 pixels breed. +Vandaar de afwijkende breedte van het grid. ### Vensters of schermen @@ -81,7 +138,8 @@ Het is goed hier bewust van te zijn. Op een beeldscherm is het venster van een browser niet per se gemaximaliseerd; dan is het dus smaller dan het scherm. En _responsive design_ gaat logischerwijs uit van de breedte van het venster van de browser. -Op telefoons en tablets komen beide wel vaak overeen, al is het op tablets wel degelijk mogelijk om twee (vensters van) apps naast elkaar op het scherm te zetten. +Op telefoons en tablets komen beide wel vaak overeen, +al is het op tablets wel degelijk mogelijk om twee (vensters van) apps naast elkaar op het scherm te zetten. ### Niet gebonden aan apparaten diff --git a/storybook/storybook-react/src/Grid/Grid.docs.mdx b/storybook/storybook-react/src/Grid/Grid.docs.mdx index 8a4791f670..72ce1a2e03 100644 --- a/storybook/storybook-react/src/Grid/Grid.docs.mdx +++ b/storybook/storybook-react/src/Grid/Grid.docs.mdx @@ -12,8 +12,33 @@ import README from "../../../../packages/css/src/grid/README.grid.md?raw"; De roze vlakken vertegenwoordigen de kolommen van het grid. Zoom in of verander de breedte van het venster om de breedtes en het aantal kolommen te zien veranderen. Let op: op brede schermen zijn de witruimtes tussen de kolommen soms breder dan de kolommen zelf. +Voor websites is het grid behoorlijk ruimtelijk. +Zo is de huisstijl ontworpen. + +### Compact + +Voor applicaties is zo veel witruimte niet nodig, zelfs contraproductief. +Daarom bestaat er een compacte variant van het grid. +Deze zet je aan via `density="high"`. + + + +### Verticale marge + +In tegenstelling tot de horizontale marges zijn de verticale instelbaar. +De opties `paddingVertical`, `paddingTop` en `paddingBottom` voegen witruimte boven en/of onder het grid toe. +Dit is handig in een vlak met een achtergrondkleur zoals [Footer](?path=/docs/react_containers-footer--docs) of +[Highlight](?path=/docs/react_containers-highlight--docs) of tussen twee opvolgende grids. + +Elk kan een waarde `medium`, `small` of `large` krijgen. +De witruimtes zijn even breed als de horizontale witruimte tussen de kolommen, +respectievelijk de helft en het dubbele daarvan. +Deze krimpen en groeien dus ook met de vensterbreedte. + + + ### Cellen Binnen het grid maak je cellen waarin andere componenten geplaatst kunnen worden. diff --git a/storybook/storybook-react/src/Grid/Grid.stories.tsx b/storybook/storybook-react/src/Grid/Grid.stories.tsx index e4a6ec174c..8f605e0e29 100644 --- a/storybook/storybook-react/src/Grid/Grid.stories.tsx +++ b/storybook/storybook-react/src/Grid/Grid.stories.tsx @@ -5,10 +5,16 @@ import { Grid, Image, Screen } from '@amsterdam/design-system-react' import { Meta, StoryObj } from '@storybook/react' +import { paddingArgType } from '../shared/argTypes' const meta = { title: 'Layout/Grid', component: Grid, + argTypes: { + paddingVertical: paddingArgType, + paddingTop: paddingArgType, + paddingBottom: paddingArgType, + }, } satisfies Meta export default meta @@ -33,6 +39,44 @@ export const Default: Story = { name: 'Basis', } +export const Compact: Story = { + ...StoryTemplate, + args: { + children: Array.from(Array(12).keys()).map((i) => ), + density: 'high', + }, + argTypes: { + density: { + control: { + type: 'radio', + }, + options: ['low', 'high'], + }, + }, + name: 'Compact', + parameters: { + outline: 'visible', + }, +} + +export const VerticalSpace: Story = { + ...StoryTemplate, + args: { + children: Array.from(Array(12).keys()).map((i) => ), + density: 'low', + paddingVertical: 'medium', + }, + argTypes: { + density: { + control: { + type: 'radio', + }, + options: ['low', 'high'], + }, + }, + name: 'Verticale witruimte', +} + export const Cells: Story = { ...StoryTemplate, args: { diff --git a/storybook/storybook-react/src/GridCell/GridCell.docs.mdx b/storybook/storybook-react/src/GridCell/GridCell.docs.mdx index a87e2d6792..6804ed62f6 100644 --- a/storybook/storybook-react/src/GridCell/GridCell.docs.mdx +++ b/storybook/storybook-react/src/GridCell/GridCell.docs.mdx @@ -50,4 +50,3 @@ Een voorbeeld met `start={2}`: ## Richtlijnen - Zorg ervoor dat het aantal kolommen dat je aan een cel toekent past bij het aantal kolommen van het grid voor de betreffende vensterbreedte. Datzelfde geldt voor het starten van een cel in een latere kolom. Als het totaal van beide waarden is te groot is voegt de browser kolommen toe aan het grid. Dit is niet toegestaan. -- Gebruik de `fullWidth` prop niet tegelijk met de `span` of `start` props. diff --git a/storybook/storybook-react/src/shared/argTypes.ts b/storybook/storybook-react/src/shared/argTypes.ts new file mode 100644 index 0000000000..c6d732d1dc --- /dev/null +++ b/storybook/storybook-react/src/shared/argTypes.ts @@ -0,0 +1,12 @@ +/** + * @license EUPL-1.2+ + * Copyright (c) 2023 Gemeente Amsterdam + */ + +export const paddingArgType = { + control: { + type: 'radio', + labels: { undefined: 'none', small: 'small', medium: 'medium', large: 'large' }, + }, + options: [undefined, 'small', 'medium', 'large'], +}