Skip to content

Commit

Permalink
feat: introduce o3 grid CSS custom properties (#1764)
Browse files Browse the repository at this point in the history
* feat: introduce o3 grid CSS custom properties and `o3-grid` helper class

---------

Co-authored-by: Akaki Mikaia <[email protected]>
Co-authored-by: Lee Moody <[email protected]>
  • Loading branch information
3 people authored Aug 12, 2024
1 parent 62da89d commit 19759d8
Show file tree
Hide file tree
Showing 14 changed files with 312 additions and 19 deletions.
104 changes: 87 additions & 17 deletions components/o3-foundation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@

- [o3-foundation](#o3-foundation)
- [Usage](#usage)
- [Normalisation](#normalisation)
- [Normalisation](#normalisation)
- [Focus rings](#focus-rings)
- [Fonts](#fonts)
- [Icons](#icons)
- [Colours](#colours)
- [Spacing](#spacing)
- [Grid](#grid)
- [Helper classes](#helper-classes)
- [Migration](#migration)
- [Contact](#contact)
- [Licence](#licence)

## Usage

`o3-foundation` provides CSS Custom Properties for design tokens representing colours, typographic scale, spacing, and iconography.
`o3-foundation` provides CSS Custom Properties for design tokens representing colours, typographic scale, spacing, iconography and grid.

`o3-foundation` supports brands: `core`, `internal`, `professional`, `sustainable-views` and `whitelabel`.

Expand All @@ -23,9 +26,9 @@ Import your chosen brand to begin using tokens in your CSS/SCSS:
@import '@financial-times/o3-foundation/css/core.css';

body {
background-color: var(--o3-color-use-case-page-background);
font-size: var(--o3-font-size-1);
line-height: var(--o3-font-lineheight-1);
background-color: var(--o3-color-use-case-page-background);
font-size: var(--o3-font-size-1);
line-height: var(--o3-font-lineheight-1);
}
```

Expand All @@ -46,7 +49,7 @@ Then apply the brand data selector `data-o3-brand="[BRAND]"` on a container elem

```html
<body data-o3-brand="core">
<a href="#" class="example-custom-link">Example</a>
<a href="#" class="example-custom-link">Example</a>
</body>
```

Expand Down Expand Up @@ -85,17 +88,17 @@ For example, to create a `1rem` sized icon which inherits its colour from the cu

```css
.example-icon-use {
/* Create a square the size we want an icon */
display: inline-block;
width: 1rem;
height: 1rem;
/* Set the icon colour, In this case match the
/* Create a square the size we want an icon */
display: inline-block;
width: 1rem;
height: 1rem;
/* Set the icon colour, In this case match the
current foreground text colour. */
background-color: currentColor;
/* Mask the square with an Origami icon. */
mask-image: var(--o3-icons-ft-icon-plus);
mask-repeat: no-repeat;
mask-size: contain;
background-color: currentColor;
/* Mask the square with an Origami icon. */
mask-image: var(--o3-icons-ft-icon-plus);
mask-repeat: no-repeat;
mask-size: contain;
}
```

Expand All @@ -121,12 +124,79 @@ Example:

```css
.example-spacing {
margin: var(--o3-spacing-2xs);
margin: var(--o3-spacing-2xs);
}
```

See our documentation website for a [full list of spacing tokens](https://origami-for-everyone.ft.com/guides/spacing/).

### Grid

The `o3-grid` system, provided by `o3-foundation`, standardises usage of grid across ft. The `o3-grid` is responsive on different screen sizes and differs from [`o-grid`](https://o2.origami.ft.com/?path=/docs/o2-core_components-o-grid-readme--docs) component. For more detailed guidelines on grid system check our documentation for [`o3-grid`](https://origami-beta.ft.com/guides/grid/).

For a convenient and standardized grid layout, apply the `o3-grid` class to your element:

```html
<div class="o3-grid">
<!-- Grid Items -->
</div>
```

The `o3-grid` layout template follows a specific structure:

```txt
`bleed-start content-start [REST_OF_THE_COLUMNS] content-end bleed-end`,
```

The o3-grid system adapts to different screen sizes, the number of columns is variable according to viewport size, see design guidelines for [details](https://origami-beta.ft.com/guides/grid/).

- Content-area: The grid's main content area is defined by `content-start` and `content-end` boundaries.
- Bleed Columns for Extended Layouts: `bleed-start` & `bleed-end`: These special columns extend beyond the 12-column grid, reaching the edges of the viewport. This allows you to create "full-bleed" layouts where content extends off the screen. These columns are used as margin areas, providing visual breathing room around your central content.
- `[REST_OF_THE_COLUMNS]`: This represents any additional columns you might need in your grid.

#### Positioning grid items

You can precisely control the positioning of grid items using the `grid-column` style or css property:

```html
<div class="o3-grid">
<div style="grid-column: content-start / content-end;">
Spans the entire content area
</div>
<div style="grid-column: content-start / span 4;">
Starts at `content-start`, spans 4 columns
</div>
<div style="grid-column: content-start / span 2;">
Starts at `content-start`, spans 2 columns
</div>
<div style="grid-column: span 1 / content-end;">
Ends at `content-end`, spans 1 column back
</div>
<div style="grid-column: bleed-left / bleed-right;">
Spans the entire viewport (full bleed)
</div>
<div style="grid-column: bleed-left / content-end;">
Starts at the left edge, ends at `content-end`
</div>
</div>
```

#### Advanced usage of grid

For advanced usage `o3-foundation` provides CSS Custom Properties for grid that you can set on your class:

```css
.example-class {
display: grid;
/* Controls the column structure of your grid. */
grid-template-columns: var(--o3-grid-template);
/* Defines named areas for grid item placement. */
grid-template-areas: var(--o3-grid-area);
/* Manages the spacing between grid columns. */
column-gap: var(--o3-grid-gap);
}
```

### Helper classes

`o3-foundation` provides a set of helper classes to help with common tasks. The list of helper classes includes:
Expand Down
35 changes: 35 additions & 0 deletions components/o3-foundation/grid.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
:root {
--o3-grid-template: 0px
repeat(var(--o3-grid-columns), 1fr) 0px;
--o3-grid-area: 'bleed-left content-start . . content-end bleed-right';
--o3-grid-columns: 4;
--o3-grid-gap: 16px;

@media screen and (min-width: 740px) {
--o3-grid-template: 8px
repeat(var(--o3-grid-columns), 1fr) 8px;
--o3-grid-area: 'bleed-left content-start . . . . . . content-end bleed-right';
--o3-grid-columns: 8;
}

@media screen and (min-width: 980px) {
--o3-grid-template: 0px
repeat(var(--o3-grid-columns), 1fr) 0px;
--o3-grid-area: 'bleed-left content-start . . . . . . . . . . content-end bleed-right';
--o3-grid-columns: 12;
--o3-grid-gap: 24px;
}

@media screen and (min-width: 1268px) {
--column-width: calc((1220px - 11 * var(--o3-grid-gap)) / 12);
--o3-grid-template: 1fr
repeat(var(--o3-grid-columns), var(--column-width)) 1fr;
}
}

.o3-grid {
display: grid;
grid-template-columns: var(--o3-grid-template);
grid-template-areas: var(--o3-grid-area);
column-gap: var(--o3-grid-gap);
}
1 change: 1 addition & 0 deletions components/o3-foundation/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
@import './fonts.css';
@import './normalise.css';
@import './focus.css';
@import './grid.css';
7 changes: 6 additions & 1 deletion components/o3-foundation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
"description": "Origami foundations with design tokens",
"keywords": [
"foundation",
"design-tokens"
"design-tokens",
"colour",
"typography",
"spacing",
"icon",
"grid"
],
"homepage": "https://registry.origami.ft.com/components/o3-foundation",
"bugs": {
Expand Down
10 changes: 10 additions & 0 deletions components/o3-foundation/stories/core/grid.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {Meta, StoryObj} from '@storybook/react';
import {GridMetaGenerator} from '../storyTemplates'

export default {
title: 'Core/o3-grid',
tags: ['!autodocs'],
...GridMetaGenerator('core')
} as Meta;

export const Grid: StoryObj = {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {Meta, StoryObj} from '@storybook/react';
import {GridMetaGenerator} from '../../storyTemplates'

export default {
title: 'Core/Professional/o3-grid',
tags: ['!autodocs'],
...GridMetaGenerator('professional'),
} as Meta;

export const Grid: StoryObj = {};
9 changes: 9 additions & 0 deletions components/o3-foundation/stories/grid-sb-styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.o3-grid {
row-gap: 4px;
}

.o3-grid-item {
height: 100px;
background-color: tomato;
cursor: pointer;
}
79 changes: 79 additions & 0 deletions components/o3-foundation/stories/grid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {useEffect, useState} from 'react';
import "../grid.css";

const itemDetails = [
{
id: 1,
span: `content-start / content-end`,
text: 'Full content',
},
{
id: 2,
span: `content-start / span 6`,
text: 'Span 6',
},
{
id: 3,
span: `span 3`,
text: 'Span 3',
},
{
id: 4,
span: `span 1 / content-end`,
text: 'Span 1',
},
{
id: 5,
span: `bleed-left / bleed-right`,
text: 'Full bleed',
},
{
id: 6,
span: `bleed-left / content-end`,
text: 'Bleed only left',
},
];

export function O3Grid() {
// on window resize update the grid
const [gridItems, setGridItems] = useState(itemDetails);

useEffect(() => {
const handleResize = () => {
if (window.innerWidth < 740) {
const newGridItems = [...itemDetails];
itemDetails[1].span = `content-start / span 4`;
itemDetails[1].text = `Span 4`;
itemDetails[2].span = `content-start / span 2`;
itemDetails[2].text = `Span 2`;

setGridItems(newGridItems);
} else if (window.innerWidth < 980) {
const newGridItems = [...itemDetails];
itemDetails[2].span = `span 2`;
itemDetails[2].text = `Span 2`;
setGridItems(newGridItems);
}
};

handleResize();
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return (
<div className="o3-grid" style={{marginTop: '20px'}}>
{gridItems.map(item => (
<div
key={item.id}
className="o3-grid-item"
style={{
gridColumn: `${item.span}`,
}}>
{item.text}
</div>
))}
</div>
);
}
10 changes: 10 additions & 0 deletions components/o3-foundation/stories/internal/grid.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {Meta, StoryObj} from '@storybook/react';
import {GridMetaGenerator} from '../storyTemplates'

export default {
title: 'Internal/o3-grid',
tags: ['!autodocs'],
...GridMetaGenerator('internal'),
} as Meta;

export const Grid: StoryObj = {};
24 changes: 24 additions & 0 deletions components/o3-foundation/stories/storyTemplates.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type {Meta} from '@storybook/react';
import {transformCode} from './utils';
import {O3Grid} from './grid';
import './grid-sb-styles.css';

export function GridMetaGenerator(brand: string): Meta {
return {
component: O3Grid,
decorators: [
Story => (
<div data-o3-brand={brand}>
<Story />
</div>
),
],
parameters: {
layout: 'fullscreen',
backgrounds: {default: 'paper'},
html: {
transform: (code: string) => transformCode(code),
},
},
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {Meta, StoryObj} from '@storybook/react';
import {GridMetaGenerator} from '../storyTemplates'

export default {
title: 'Sustainable-views/o3-grid',
tags: ['!autodocs'],
...GridMetaGenerator('sustainable-views'),
} as Meta;

export const Grid: StoryObj = {};
20 changes: 20 additions & 0 deletions components/o3-foundation/stories/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function transformCode(code: string) {
let parser = new DOMParser();
let doc = parser.parseFromString(code, 'text/html');

// Remove the edit panel
let editPanel = doc.querySelector('.edit-panel');
if (editPanel) {
editPanel.remove();
}

// Remove the class 'o3-grid-item' from all items
let gridItems = doc.querySelectorAll('.o3-grid-item');
gridItems.forEach(item => {
item.removeAttribute('class');
});

// Serialize the modified DOM back to a string
let modifiedHtmlString = doc.body.innerHTML;
return modifiedHtmlString
}
Loading

0 comments on commit 19759d8

Please sign in to comment.