From 29107ab7ba0628a4f71129d23e59c70b5e99ef0d Mon Sep 17 00:00:00 2001 From: Simon Adcock Date: Mon, 15 Feb 2021 12:02:44 +0000 Subject: [PATCH 1/3] support responsive column widths --- src/core/components/layout/README.md | 11 ++- .../components/layout/columns.stories.tsx | 1 + .../layout/components/columns/columns.tsx | 9 +- .../layout/components/columns/styles.ts | 87 ++++++++++++++----- .../layout/stories/columns/responsive.tsx | 51 +++++++++++ 5 files changed, 128 insertions(+), 31 deletions(-) create mode 100644 src/core/components/layout/stories/columns/responsive.tsx diff --git a/src/core/components/layout/README.md b/src/core/components/layout/README.md index 1ff0112db..702f02bfe 100644 --- a/src/core/components/layout/README.md +++ b/src/core/components/layout/README.md @@ -86,9 +86,16 @@ Columns will be stacked one on top of the other at viewport widths lower than th #### `width` -**`number`** +**`number | number[]`** -Fraction of the parent container's width that the column will occupy. If no value is provided, the column width will be fluid (i.e. take up remaining space, divided between all fluid columns) +Fraction of the parent container's width that the column will occupy. + +Pass 0 to hide the column completely. + +Pass an array of fractions to set the width that the column occupies at different breakpoints. The first value in the array will +reflect the width at mobile, the second value at tablet, then desktop, leftCol and wide. + +If no value is provided, the column width will be fluid (i.e. take up remaining space, divided between all fluid columns) ## Hide diff --git a/src/core/components/layout/columns.stories.tsx b/src/core/components/layout/columns.stories.tsx index 335f4aa94..ecb35d43a 100644 --- a/src/core/components/layout/columns.stories.tsx +++ b/src/core/components/layout/columns.stories.tsx @@ -14,5 +14,6 @@ export default { export * from './stories/columns/default'; export * from './stories/columns/collapse-below'; +export * from './stories/columns/responsive'; export * from './stories/columns/with-container'; export * from './stories/columns/with-width'; diff --git a/src/core/components/layout/components/columns/columns.tsx b/src/core/components/layout/components/columns/columns.tsx index de0e773ca..e181d8755 100644 --- a/src/core/components/layout/components/columns/columns.tsx +++ b/src/core/components/layout/components/columns/columns.tsx @@ -69,17 +69,12 @@ const Columns = ({ }; interface ColumnProps extends HTMLAttributes, Props { - width?: number; + width?: number | number[]; cssOverrides?: SerializedStyles | SerializedStyles[]; children: ReactNode; } -const Column = ({ - width = 0, - cssOverrides, - children, - ...props -}: ColumnProps) => { +const Column = ({ width, cssOverrides, children, ...props }: ColumnProps) => { return (
{children} diff --git a/src/core/components/layout/components/columns/styles.ts b/src/core/components/layout/components/columns/styles.ts index fe784af96..111f777c5 100644 --- a/src/core/components/layout/components/columns/styles.ts +++ b/src/core/components/layout/components/columns/styles.ts @@ -1,6 +1,6 @@ import { css } from '@emotion/react'; import { space } from '@guardian/src-foundations'; -import { until } from '@guardian/src-foundations/mq'; +import { Breakpoint, from, until } from '@guardian/src-foundations/mq'; export const columns = css` box-sizing: border-box; @@ -74,27 +74,70 @@ export const collapseBelowWide = css` } `; -export const column = (width: number) => css` +/* + A set of Columns has n columns and n-1 gutters: + | |g| |g| |g| | + This means if we take a simple fraction of the width of the set of Columns, + our Column will stop part-way through a gutter: + | |g| |g| |g| | + |====50%=====|====50%=====| + To calculate width of a Column correctly, we must add an imaginary extra gutter + and take a fraction of the whole: + | |g| |g| |g| |g| + |=====50%=====||====50%=====| + This will create a Column which is x columns and x gutters wide. We really want the + Column to be x columns and x-1 gutters, so we must subtract a gutter at the end: + | |g| |g| |g| |g| + |====50%====| |====50%====| +*/ +const calculateWidth = (width: number) => { + if (width === 0) { + return css` + width: 0; + + /* Hide the column from screen readers */ + visibility: hidden; + + /* offset the margin-left on the next sibling */ + margin-right: -20px; + `; + } + + return css` + width: calc((100% + ${space[5]}px) * ${width} - ${space[5]}px); + + /* Reset values that might have been set at a lower breakpoint */ + visibility: visible; + margin-right: 0; + `; +}; + +const generateWidthCSS = (width: number | number[]) => { + if (Array.isArray(width)) { + const breakpoints: Breakpoint[] = [ + 'mobile', + 'tablet', + 'desktop', + 'leftCol', + 'wide', + ]; + + return width.reduce((styles, w, i) => { + return css` + ${styles} + ${from[breakpoints[i]]} { + ${calculateWidth(w)}; + } + `; + }, css``); + } + + return calculateWidth(width); +}; + +export const column = (width?: number | number[]) => css` box-sizing: border-box; /* If a width is specified, don't allow column to grow. Use the width property */ - flex: ${width ? '0 0 auto' : 1}; - /* - A set of Columns has n columns and n-1 gutters: - | |g| |g| |g| | - This means if we take a simple fraction of the width of the set of Columns, - our Column will stop part-way through a gutter: - | |g| |g| |g| | - |====50%=====|====50%=====| - To calculate width of a Column correctly, we must add an imaginary extra gutter - and take a fraction of the whole: - | |g| |g| |g| |g| - |=====50%=====||====50%=====| - This will create a Column which is x columns and x gutters wide. We really want the - Column to be x columns and x-1 gutters, so we must subtract a gutter at the end: - | |g| |g| |g| |g| - |====50%====| |====50%====| - */ - ${width - ? `width: calc((100% + ${space[5]}px) * ${width} - ${space[5]}px);` - : ''} + flex: ${width !== undefined ? '0 0 auto' : 1}; + ${width !== undefined ? generateWidthCSS(width) : ''}; `; diff --git a/src/core/components/layout/stories/columns/responsive.tsx b/src/core/components/layout/stories/columns/responsive.tsx new file mode 100644 index 000000000..e7400f255 --- /dev/null +++ b/src/core/components/layout/stories/columns/responsive.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Columns, Column, Container } from '../../index'; +import { sport } from '@guardian/src-foundations/palette'; +import { css } from '@emotion/react'; + +const contents = css` + text-align: center; + background-color: ${sport[600]}; +`; + +export const responsive = () => ( + + + +
50% at mobile, 25% above tablet
+
+ +
50% width at mobile, 75% above tablet
+
+
+
+); + +responsive.story = { + name: 'responsive columns', + parameters: { + viewport: { defaultViewport: 'tablet' }, + }, +}; + +export const responsivelyHide = () => ( + + + +
+ Not visible at mobile, 25% above tablet +
+
+ +
100% at mobile, 75% above tablet
+
+
+
+); + +responsivelyHide.story = { + name: 'responsively hide columns', + parameters: { + viewport: { defaultViewport: 'tablet' }, + }, +}; From cfd3986ecfb04f0c641de618e34e95777a053fd2 Mon Sep 17 00:00:00 2001 From: Simon Adcock Date: Tue, 16 Feb 2021 08:57:21 +0000 Subject: [PATCH 2/3] supporting hiding above and below breakpoints --- .../components/layout/components/hide/hide.tsx | 1 + .../components/layout/stories/hide/default.tsx | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/core/components/layout/components/hide/hide.tsx b/src/core/components/layout/components/hide/hide.tsx index aa7a530c9..9069967d2 100644 --- a/src/core/components/layout/components/hide/hide.tsx +++ b/src/core/components/layout/components/hide/hide.tsx @@ -21,6 +21,7 @@ const Hide = ({ children, above, below }: HideProps) => { } if (above) { whenToHide = css` + ${whenToHide} ${from[above]} { display: none; } diff --git a/src/core/components/layout/stories/hide/default.tsx b/src/core/components/layout/stories/hide/default.tsx index 9c90fffb2..b4e129162 100644 --- a/src/core/components/layout/stories/hide/default.tsx +++ b/src/core/components/layout/stories/hide/default.tsx @@ -33,3 +33,17 @@ export const above = () => ( above.story = { name: 'above', }; + +export const between = () => ( + + +
+ Will only appear between desktop and leftCol +
+
+
+); + +between.story = { + name: 'between', +}; From 3ff8beb976d14058e8e2ffbb656d2b46b51655cd Mon Sep 17 00:00:00 2001 From: Simon Adcock Date: Thu, 18 Feb 2021 15:40:03 +0000 Subject: [PATCH 3/3] handle empty column width array --- .../layout/components/columns/styles.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/core/components/layout/components/columns/styles.ts b/src/core/components/layout/components/columns/styles.ts index 111f777c5..c52ac4e16 100644 --- a/src/core/components/layout/components/columns/styles.ts +++ b/src/core/components/layout/components/columns/styles.ts @@ -135,9 +135,23 @@ const generateWidthCSS = (width: number | number[]) => { return calculateWidth(width); }; -export const column = (width?: number | number[]) => css` - box-sizing: border-box; - /* If a width is specified, don't allow column to grow. Use the width property */ - flex: ${width !== undefined ? '0 0 auto' : 1}; - ${width !== undefined ? generateWidthCSS(width) : ''}; -`; +export const column = (width?: number | number[]) => { + let flex; + let widthCSS; + + if (width == null || (Array.isArray(width) && width.length === 0)) { + // If no width is specified, allow the column to grow + flex = 1; + widthCSS = css``; + } else { + // If a width is specified, don't allow column to grow. Use the width property + flex = '0 0 auto'; + widthCSS = generateWidthCSS(width); + } + + return css` + box-sizing: border-box; + flex: ${flex}; + ${widthCSS}; + `; +};