From aa81906a1897f46a21e6726bcacd3227972827a5 Mon Sep 17 00:00:00 2001 From: Jess Telford Date: Fri, 15 Sep 2023 18:27:14 +1000 Subject: [PATCH] Add support for non-responsive values to `Grid`'s `gap`, `columns`, and `areas` props. --- .changeset/slimy-donuts-report.md | 5 + polaris-react/src/components/Grid/Grid.scss | 72 +++------ .../src/components/Grid/Grid.stories.tsx | 2 +- polaris-react/src/components/Grid/Grid.tsx | 53 +++---- .../components/Grid/components/Cell/Cell.scss | 46 +----- .../components/Grid/components/Cell/Cell.tsx | 21 +-- .../Grid/components/Cell/tests/Cell.test.tsx | 8 +- .../src/components/Grid/tests/Grid.test.tsx | 146 +++++++++++------- polaris-react/src/utilities/css.ts | 17 +- 9 files changed, 152 insertions(+), 218 deletions(-) create mode 100644 .changeset/slimy-donuts-report.md diff --git a/.changeset/slimy-donuts-report.md b/.changeset/slimy-donuts-report.md new file mode 100644 index 00000000000..3aa224c29a8 --- /dev/null +++ b/.changeset/slimy-donuts-report.md @@ -0,0 +1,5 @@ +--- +'@shopify/polaris': minor +--- + +Added support for non-responsive values to `Grid`'s `gap`, `columns`, and `areas` props. diff --git a/polaris-react/src/components/Grid/Grid.scss b/polaris-react/src/components/Grid/Grid.scss index 2a29a0f880a..e384d7345c2 100644 --- a/polaris-react/src/components/Grid/Grid.scss +++ b/polaris-react/src/components/Grid/Grid.scss @@ -1,60 +1,24 @@ @import '../../styles/common'; .Grid { - // Remap custom properties as mobile first fallbacks for grid-template-areas and grid-template-columns - // stylelint-disable -- Polaris component custom properties - --pc-grid-areas-xs: initial; - --pc-grid-areas-sm: var(--pc-grid-areas-xs); - --pc-grid-areas-md: var(--pc-grid-areas-sm); - --pc-grid-areas-lg: var(--pc-grid-areas-md); - --pc-grid-areas-xl: var(--pc-grid-areas-lg); - --pc-grid-columns-xs: 6; - --pc-grid-columns-sm: var(--pc-grid-columns-xs); - --pc-grid-columns-md: var(--pc-grid-columns-sm); - --pc-grid-columns-lg: 12; - --pc-grid-columns-xl: var(--pc-grid-columns-lg); - // stylelint-enable display: grid; - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - gap: var(--pc-grid-gap-xs, var(--p-space-400)); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-template-areas: var(--pc-grid-areas-xs); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-template-columns: repeat(var(--pc-grid-columns-xs), minmax(0, 1fr)); - - @media #{$p-breakpoints-sm-up} { - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - gap: var(--pc-grid-gap-sm, var(--p-space-400)); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-template-areas: var(--pc-grid-areas-sm); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-template-columns: repeat(var(--pc-grid-columns-sm), minmax(0, 1fr)); - } - @media #{$p-breakpoints-md-up} { - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - gap: var(--pc-grid-gap-md, var(--p-space-400)); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-template-areas: var(--pc-grid-areas-md); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-template-columns: repeat(var(--pc-grid-columns-md), minmax(0, 1fr)); - } - - @media #{$p-breakpoints-lg-up} { - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - gap: var(--pc-grid-gap-lg, var(--p-space-400)); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-template-areas: var(--pc-grid-areas-lg); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-template-columns: repeat(var(--pc-grid-columns-lg), minmax(0, 1fr)); - } - - @media #{$p-breakpoints-xl-up} { - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - gap: var(--pc-grid-gap-xl, var(--p-space-400)); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-template-areas: var(--pc-grid-areas-xl); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-template-columns: repeat(var(--pc-grid-columns-xl), minmax(0, 1fr)); - } + @include responsive-props( + 'grid', + 'gap', + 'gap', + $default: 'var(--p-space-400)' + ); + @include responsive-props('grid', 'areas', 'grid-template-areas'); + @include responsive-props( + 'grid', + 'columns', + '--pc-grid-template-columns', + $default: ('xs': 6, 'lg': 12) + ); + // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY + grid-template-columns: repeat( + var(--pc-grid-template-columns), + minmax(0, 1fr) + ); } diff --git a/polaris-react/src/components/Grid/Grid.stories.tsx b/polaris-react/src/components/Grid/Grid.stories.tsx index de636985cbf..b29295dc8cf 100644 --- a/polaris-react/src/components/Grid/Grid.stories.tsx +++ b/polaris-react/src/components/Grid/Grid.stories.tsx @@ -28,7 +28,7 @@ export function TwoColumn() { export function TwoThirdsAndOneThirdColumn() { return ( - +

View a summary of your online store’s sales.

diff --git a/polaris-react/src/components/Grid/Grid.tsx b/polaris-react/src/components/Grid/Grid.tsx index 7d7f5c7f492..c0cbe7c0e41 100644 --- a/polaris-react/src/components/Grid/Grid.tsx +++ b/polaris-react/src/components/Grid/Grid.tsx @@ -1,21 +1,17 @@ import React from 'react'; +import type {SpaceScale} from '@shopify/polaris-tokens'; + +import { + getResponsiveProps, + getResponsiveValue, + mapResponsivePropValues, +} from '../../utilities/css'; +import type {ResponsiveProp} from '../../utilities/css'; import {Cell} from './components'; import styles from './Grid.scss'; -type Breakpoints = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; - -type Areas = { - [Breakpoint in Breakpoints]?: string[]; -}; - -type Columns = { - [Breakpoint in Breakpoints]?: number; -}; - -type Gap = { - [Breakpoint in Breakpoints]?: string; -}; +type Area = string[]; export interface GridProps { /** @@ -24,32 +20,25 @@ export interface GridProps { * cells instead. See: * https://polaris.shopify.com/components/layout-and-structure */ - areas?: Areas; + areas?: ResponsiveProp; /* Number of columns */ - columns?: Columns; + columns?: ResponsiveProp; /* Grid gap */ - gap?: Gap; + gap?: ResponsiveProp; children?: React.ReactNode; } + export const Grid: React.FunctionComponent & { Cell: typeof Cell; } = function Grid({gap, areas, children, columns}: GridProps) { const style = { - '--pc-grid-gap-xs': gap?.xs, - '--pc-grid-gap-sm': gap?.sm, - '--pc-grid-gap-md': gap?.md, - '--pc-grid-gap-lg': gap?.lg, - '--pc-grid-gap-xl': gap?.xl, - '--pc-grid-columns-xs': columns?.xs, - '--pc-grid-columns-sm': columns?.sm, - '--pc-grid-columns-md': columns?.md, - '--pc-grid-columns-lg': columns?.lg, - '--pc-grid-columns-xl': columns?.xl, - '--pc-grid-areas-xs': formatAreas(areas?.xs), - '--pc-grid-areas-sm': formatAreas(areas?.sm), - '--pc-grid-areas-md': formatAreas(areas?.md), - '--pc-grid-areas-lg': formatAreas(areas?.lg), - '--pc-grid-areas-xl': formatAreas(areas?.xl), + ...getResponsiveProps('grid', 'gap', 'space', gap), + ...getResponsiveValue('grid', 'columns', columns), + ...getResponsiveValue( + 'grid', + 'areas', + mapResponsivePropValues(areas, formatAreas), + ), } as React.CSSProperties; return ( @@ -59,7 +48,7 @@ export const Grid: React.FunctionComponent & { ); }; -export function formatAreas(areas?: string[]) { +export function formatAreas(areas?: Area) { if (!areas) return; return `'${areas?.join(`' '`)}'`; } diff --git a/polaris-react/src/components/Grid/components/Cell/Cell.scss b/polaris-react/src/components/Grid/components/Cell/Cell.scss index a2746d9105f..ae8343c0b91 100644 --- a/polaris-react/src/components/Grid/components/Cell/Cell.scss +++ b/polaris-react/src/components/Grid/components/Cell/Cell.scss @@ -1,52 +1,10 @@ @import '../../../../styles/common'; .Cell { + @include responsive-props('grid-cell', 'row', 'grid-row'); + @include responsive-props('grid-cell', 'column', 'grid-column'); // Remap custom properties as mobile first fallbacks for grid-row and grid-column - // stylelint-disable -- Polaris component custom properties - --pc-row-xs: initial; - --pc-row-sm: var(--pc-row-xs); - --pc-row-md: var(--pc-row-sm); - --pc-row-lg: var(--pc-row-md); - --pc-row-xl: var(--pc-row-lg); - --pc-column-xs: initial; - --pc-column-sm: var(--pc-column-xs); - --pc-column-md: var(--pc-column-sm); - --pc-column-lg: var(--pc-column-md); - --pc-column-xl: var(--pc-column-lg); - // stylelint-enable min-width: 0; - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-row: var(--pc-row-xs); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-column: var(--pc-column-xs); - - @media #{$p-breakpoints-sm-up} { - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-row: var(--pc-row-sm); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-column: var(--pc-column-sm); - } - - @media #{$p-breakpoints-md-up} { - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-row: var(--pc-row-md); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-column: var(--pc-column-md); - } - - @media #{$p-breakpoints-lg-up} { - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-row: var(--pc-row-lg); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-column: var(--pc-column-lg); - } - - @media #{$p-breakpoints-xl-up} { - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-row: var(--pc-row-xl); - // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY - grid-column: var(--pc-column-xl); - } } @for $i from 1 through 6 { diff --git a/polaris-react/src/components/Grid/components/Cell/Cell.tsx b/polaris-react/src/components/Grid/components/Cell/Cell.tsx index 802c9c48b89..266d2a55e56 100644 --- a/polaris-react/src/components/Grid/components/Cell/Cell.tsx +++ b/polaris-react/src/components/Grid/components/Cell/Cell.tsx @@ -1,14 +1,11 @@ import React from 'react'; -import {classNames} from '../../../../utilities/css'; +import {classNames, getResponsiveValue} from '../../../../utilities/css'; +import type {ResponsiveProp} from '../../../../utilities/css'; import styles from './Cell.scss'; -type Breakpoints = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; - -type Cell = { - [Breakpoint in Breakpoints]?: string; -}; +type Cell = ResponsiveProp; interface Columns { /** Number of columns the section should span on extra small screens */ @@ -54,16 +51,8 @@ export function Cell({ const style = { gridArea, - '--pc-column-xs': column?.xs, - '--pc-column-sm': column?.sm, - '--pc-column-md': column?.md, - '--pc-column-lg': column?.lg, - '--pc-column-xl': column?.xl, - '--pc-row-xs': row?.xs, - '--pc-row-sm': row?.sm, - '--pc-row-md': row?.md, - '--pc-row-lg': row?.lg, - '--pc-row-xl': row?.xl, + ...getResponsiveValue('grid-cell', 'column', column), + ...getResponsiveValue('grid-cell', 'row', row), }; return ( diff --git a/polaris-react/src/components/Grid/components/Cell/tests/Cell.test.tsx b/polaris-react/src/components/Grid/components/Cell/tests/Cell.test.tsx index 462fd0c5c2c..60cf997be94 100644 --- a/polaris-react/src/components/Grid/components/Cell/tests/Cell.test.tsx +++ b/polaris-react/src/components/Grid/components/Cell/tests/Cell.test.tsx @@ -37,8 +37,8 @@ describe('', () => { expect(cell).toContainReactComponent('div', { style: { - '--pc-column-xs': '2 / span 1', - '--pc-column-lg': 'span 12', + '--pc-grid-cell-column-xs': '2 / span 1', + '--pc-grid-cell-column-lg': 'span 12', } as React.CSSProperties, }); }); @@ -50,8 +50,8 @@ describe('', () => { expect(cell).toContainReactComponent('div', { style: { - '--pc-row-xs': '2 / span 3', - '--pc-row-lg': '1 / span 2', + '--pc-grid-cell-row-xs': '2 / span 3', + '--pc-grid-cell-row-lg': '1 / span 2', } as React.CSSProperties, }); }); diff --git a/polaris-react/src/components/Grid/tests/Grid.test.tsx b/polaris-react/src/components/Grid/tests/Grid.test.tsx index 038c84f47a0..73d0e92cac2 100644 --- a/polaris-react/src/components/Grid/tests/Grid.test.tsx +++ b/polaris-react/src/components/Grid/tests/Grid.test.tsx @@ -21,67 +21,103 @@ describe('', () => { const lgAreas = ['lg1', 'lg2', 'lg3']; const xlAreas = ['xl1', 'xl2', 'xl3']; - it('applies grid-template-areas as custom properties', () => { - const grid = mountWithApp( - , - ); - - expect(grid).toContainReactComponent('div', { - style: { - '--pc-grid-areas-xs': formatAreas(xsAreas), - '--pc-grid-areas-sm': formatAreas(smAreas), - '--pc-grid-areas-md': formatAreas(mdAreas), - '--pc-grid-areas-lg': formatAreas(lgAreas), - '--pc-grid-areas-xl': formatAreas(xlAreas), - } as React.CSSProperties, + describe('applies grid-template-areas as custom properties', () => { + it('responsively', () => { + const grid = mountWithApp( + , + ); + + expect(grid).toContainReactComponent('div', { + style: { + '--pc-grid-areas-xs': formatAreas(xsAreas), + '--pc-grid-areas-sm': formatAreas(smAreas), + '--pc-grid-areas-md': formatAreas(mdAreas), + '--pc-grid-areas-lg': formatAreas(lgAreas), + '--pc-grid-areas-xl': formatAreas(xlAreas), + } as React.CSSProperties, + }); + }); + + it('non-responsively', () => { + const grid = mountWithApp(); + + expect(grid).toContainReactComponent('div', { + style: { + '--pc-grid-areas-xs': formatAreas(xsAreas), + } as React.CSSProperties, + }); }); }); - it('renders inline custom properties for custom columns', () => { - const grid = mountWithApp( - , - ); - - expect(grid).toContainReactComponent('div', { - style: { - '--pc-grid-columns-xs': 1, - '--pc-grid-columns-sm': 3, - '--pc-grid-columns-md': 7, - '--pc-grid-columns-lg': 12, - '--pc-grid-columns-xl': 12, - } as React.CSSProperties, + describe('renders inline custom properties for custom columns', () => { + it('responsively', () => { + const grid = mountWithApp( + , + ); + + expect(grid).toContainReactComponent('div', { + style: { + '--pc-grid-columns-xs': 1, + '--pc-grid-columns-sm': 3, + '--pc-grid-columns-md': 7, + '--pc-grid-columns-lg': 12, + '--pc-grid-columns-xl': 12, + } as React.CSSProperties, + }); + }); + + it('non-responsively', () => { + const grid = mountWithApp(); + + expect(grid).toContainReactComponent('div', { + style: { + '--pc-grid-columns-xs': 3, + } as React.CSSProperties, + }); }); }); - it('renders inline custom properties for custom gap', () => { - const grid = mountWithApp( - , - ); - - expect(grid).toContainReactComponent('div', { - style: { - '--pc-grid-gap-xs': 'var(--p-space-100)', - '--pc-grid-gap-sm': 'var(--p-space-100)', - '--pc-grid-gap-md': 'var(--p-space-200)', - '--pc-grid-gap-lg': 'var(--p-space-400)', - '--pc-grid-gap-xl': 'var(--p-space-400)', - } as React.CSSProperties, + describe('renders inline custom properties for custom gap', () => { + it('responsively', () => { + const grid = mountWithApp( + , + ); + + expect(grid).toContainReactComponent('div', { + style: { + '--pc-grid-gap-xs': 'var(--p-space-100)', + '--pc-grid-gap-sm': 'var(--p-space-100)', + '--pc-grid-gap-md': 'var(--p-space-200)', + '--pc-grid-gap-lg': 'var(--p-space-400)', + '--pc-grid-gap-xl': 'var(--p-space-400)', + } as React.CSSProperties, + }); + }); + + it('non-responsively', () => { + const grid = mountWithApp(); + + expect(grid).toContainReactComponent('div', { + style: { + '--pc-grid-gap-xs': 'var(--p-space-300)', + } as React.CSSProperties, + }); }); }); diff --git a/polaris-react/src/utilities/css.ts b/polaris-react/src/utilities/css.ts index 0a7380fa7bc..131a8731568 100644 --- a/polaris-react/src/utilities/css.ts +++ b/polaris-react/src/utilities/css.ts @@ -45,19 +45,12 @@ export function createPolarisCSSVar( tokenSubgroup: string, tokenValue: T, ): PolarisCSSVar { - // For backwards compatibility with `Grid` and `Grid.Cell`, accept already - // formed var()'s using either polaris or polaris component custom properties. + // `Grid`'s `gap` prop used to allow passing fully formed var() functions as + // the value. This is no longer supported in v12+. if (typeof tokenValue === 'string' && tokenValue.startsWith('var(')) { - if ( - !tokenValue.startsWith(`var(--p-${tokenSubgroup}-`) && - !tokenValue.startsWith(`var(--pc-${tokenSubgroup}-`) - ) { - throw new Error( - `"${tokenValue}" is not from the ${tokenSubgroup} token group.`, - ); - } - - return tokenValue as PolarisCSSVar; + throw new Error( + `"${tokenValue}" is not from the ${tokenSubgroup} token group.`, + ); } // NOTE: All our token values today are either strings or numbers, so