Skip to content
This repository has been archived by the owner on Dec 6, 2022. It is now read-only.

Commit

Permalink
Merge pull request #726 from guardian/sa-responsive-column-width
Browse files Browse the repository at this point in the history
Support responsive column widths
  • Loading branch information
SiAdcock authored Feb 26, 2021
2 parents de59114 + b597775 commit e39792c
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 34 deletions.
11 changes: 9 additions & 2 deletions src/core/components/layout/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions src/core/components/layout/columns.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,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';
9 changes: 2 additions & 7 deletions src/core/components/layout/components/columns/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,12 @@ const Columns = ({
};

interface ColumnProps extends HTMLAttributes<HTMLDivElement>, 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 (
<div css={[column(width), cssOverrides]} {...props}>
{children}
Expand Down
107 changes: 82 additions & 25 deletions src/core/components/layout/components/columns/styles.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -74,27 +74,84 @@ export const collapseBelowWide = css`
}
`;

export const column = (width: 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);`
: ''}
`;
/*
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: ${-space[5]}px;
`;
}

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[]) => {
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};
`;
};
51 changes: 51 additions & 0 deletions src/core/components/layout/stories/columns/responsive.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => (
<Container>
<Columns>
<Column width={[1 / 2, 1 / 4]}>
<div css={contents}>50% at mobile, 25% above tablet</div>
</Column>
<Column width={[1 / 2, 3 / 4]}>
<div css={contents}>50% width at mobile, 75% above tablet</div>
</Column>
</Columns>
</Container>
);

responsive.story = {
name: 'responsive columns',
parameters: {
viewport: { defaultViewport: 'tablet' },
},
};

export const responsivelyHide = () => (
<Container>
<Columns>
<Column width={[0, 1 / 4]}>
<div css={contents}>
Not visible at mobile, 25% above tablet
</div>
</Column>
<Column width={[1, 3 / 4]}>
<div css={contents}>100% at mobile, 75% above tablet</div>
</Column>
</Columns>
</Container>
);

responsivelyHide.story = {
name: 'responsively hide columns',
parameters: {
viewport: { defaultViewport: 'tablet' },
},
};

0 comments on commit e39792c

Please sign in to comment.