From fc834b6285b7af1c136e448d9b77da5860451cc9 Mon Sep 17 00:00:00 2001 From: Jon Rohan Date: Fri, 1 Nov 2024 20:07:29 +0000 Subject: [PATCH 1/3] Convert Stack component to CSS modules --- packages/react/src/Stack/Stack.module.css | 323 +++++++++++++++ packages/react/src/Stack/Stack.tsx | 478 +++++++++++----------- 2 files changed, 572 insertions(+), 229 deletions(-) create mode 100644 packages/react/src/Stack/Stack.module.css diff --git a/packages/react/src/Stack/Stack.module.css b/packages/react/src/Stack/Stack.module.css new file mode 100644 index 00000000000..f326ef06e9e --- /dev/null +++ b/packages/react/src/Stack/Stack.module.css @@ -0,0 +1,323 @@ +.Stack { + display: flex; + flex-flow: column; + align-items: stretch; + align-content: flex-start; + gap: var(--stack-gap, var(--stack-gap-normal)); + + &[data-padding='none'], + &[data-padding-narrow='none'] { + padding: 0; + } + + &[data-padding='condensed'], + &[data-padding-narrow='condensed'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-condensed); + } + + &[data-padding='normal'], + &[data-padding-narrow='normal'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-normal); + } + + &[data-padding='spacious'], + &[data-padding-narrow='spacious'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-spacious); + } + + &[data-direction='horizontal'], + &[data-direction-narrow='horizontal'] { + flex-flow: row; + } + + &[data-direction='vertical'], + &[data-direction-narrow='vertical'] { + flex-flow: column; + } + + &[data-gap='none'], + &[data-gap-narrow='none'] { + --stack-gap: 0; + } + + &[data-gap='condensed'], + &[data-gap-narrow='condensed'] { + --stack-gap: var(--stack-gap-condensed); + } + + &[data-gap='normal'], + &[data-gap-narrow='normal'] { + --stack-gap: var(--stack-gap-normal); + } + + &[data-gap='spacious'], + &[data-gap-narrow='spacious'] { + --stack-gap: var(--stack-gap-spacious); + } + + &[data-align='start'], + &[data-align-narrow='start'] { + align-items: flex-start; + } + + &[data-align='center'], + &[data-align-narrow='center'] { + align-items: center; + } + + &[data-align='end'], + &[data-align-narrow='end'] { + align-items: flex-end; + } + + &[data-align='baseline'], + &[data-align-narrow='baseline'] { + align-items: baseline; + } + + &[data-justify='start'], + &[data-justify-narrow='start'] { + justify-content: flex-start; + } + + &[data-justify='center'], + &[data-justify-narrow='center'] { + justify-content: center; + } + + &[data-justify='end'], + &[data-justify-narrow='end'] { + justify-content: flex-end; + } + + &[data-justify='space-between'], + &[data-justify-narrow='space-between'] { + justify-content: space-between; + } + + &[data-justify='space-evenly'], + &[data-justify-narrow='space-evenly'] { + justify-content: space-evenly; + } + + &[data-wrap='wrap'], + &[data-wrap-narrow='wrap'] { + flex-wrap: wrap; + } + + &[data-wrap='nowrap'], + &[data-wrap-narrow='nowrap'] { + flex-wrap: nowrap; + } + + @media (--veiwportRange-regular) { + &[data-padding-regular='none'] { + padding: 0; + } + + &[data-padding-regular='condensed'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-condensed); + } + + &[data-padding-regular='normal'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-normal); + } + + &[data-padding-regular='spacious'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-spacious); + } + + &[data-direction-regular='horizontal'] { + flex-flow: row; + } + + &[data-direction-regular='vertical'] { + flex-flow: column; + } + + &[data-gap-regular='none'] { + --stack-gap: 0; + } + + &[data-gap-regular='condensed'] { + --stack-gap: var(--stack-gap-condensed); + } + + &[data-gap-regular='normal'] { + --stack-gap: var(--stack-gap-normal); + } + + &[data-gap-regular='spacious'] { + --stack-gap: var(--stack-gap-spacious); + } + + &[data-align-regular='start'] { + align-items: flex-start; + } + + &[data-align-regular='center'] { + align-items: center; + } + + &[data-align-regular='end'] { + align-items: flex-end; + } + + &[data-align-regular='baseline'] { + align-items: baseline; + } + + &[data-justify-regular='start'] { + justify-content: flex-start; + } + + &[data-justify-regular='center'] { + justify-content: center; + } + + &[data-justify-regular='end'] { + justify-content: flex-end; + } + + &[data-justify-regular='space-between'] { + justify-content: space-between; + } + + &[data-justify-regular='space-evenly'] { + justify-content: space-evenly; + } + + &[data-wrap-regular='wrap'] { + flex-wrap: wrap; + } + + &[data-wrap-regular='nowrap'] { + flex-wrap: nowrap; + } + } + + @media (--viewportRange-wide) { + &[data-padding-wide='none'] { + padding: 0; + } + + &[data-padding-wide='condensed'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-condensed); + } + + &[data-padding-wide='normal'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-normal); + } + + &[data-padding-wide='spacious'] { + /* stylelint-disable-next-line primer/spacing */ + padding: var(--stack-padding-spacious); + } + + &[data-direction-wide='horizontal'] { + flex-flow: row; + } + + &[data-direction-wide='vertical'] { + flex-flow: column; + } + + &[data-gap-wide='none'] { + --stack-gap: 0; + } + + &[data-gap-wide='condensed'] { + --stack-gap: var(--stack-gap-condensed); + } + + &[data-gap-wide='normal'] { + --stack-gap: var(--stack-gap-normal); + } + + &[data-gap-wide='spacious'] { + --stack-gap: var(--stack-gap-spacious); + } + + &[data-align-wide='start'] { + align-items: flex-start; + } + + &[data-align-wide='center'] { + align-items: center; + } + + &[data-align-wide='end'] { + align-items: flex-end; + } + + &[data-align-wide='baseline'] { + align-items: baseline; + } + + &[data-justify-wide='start'] { + justify-content: flex-start; + } + + &[data-justify-wide='center'] { + justify-content: center; + } + + &[data-justify-wide='end'] { + justify-content: flex-end; + } + + &[data-justify-wide='space-between'] { + justify-content: space-between; + } + + &[data-justify-wide='space-evenly'] { + justify-content: space-evenly; + } + + &[data-wrap-wide='wrap'] { + flex-wrap: wrap; + } + + &[data-wrap-wide='nowrap'] { + flex-wrap: nowrap; + } + } +} + +.StackItem { + flex: 0 1 auto; + min-inline-size: 0; + + &[data-grow='true'], + &[data-grow-narrow='true'] { + flex-grow: 1; + } + + @media (--veiwportRange-regular) { + &[data-grow-regular='true'] { + flex-grow: 1; + } + + &[data-grow-regular='false'] { + flex-grow: 0; + } + } + + @media (--viewportRange-wide) { + &[data-grow-wide='true'] { + flex-grow: 1; + } + + &[data-grow-wide='false'] { + flex-grow: 0; + } + } +} diff --git a/packages/react/src/Stack/Stack.tsx b/packages/react/src/Stack/Stack.tsx index 6b14b52a3e8..865936e9da9 100644 --- a/packages/react/src/Stack/Stack.tsx +++ b/packages/react/src/Stack/Stack.tsx @@ -2,295 +2,305 @@ import React, {type ElementType} from 'react' import styled from 'styled-components' import type {ResponsiveValue} from '../hooks/useResponsiveValue' import {getResponsiveAttributes} from '../internal/utils/getResponsiveAttributes' - -const StyledStack = styled.div` - display: flex; - flex-flow: column; - align-items: stretch; - align-content: flex-start; - gap: var(--stack-gap, var(--stack-gap-normal, 1rem)); - - // non-responsive values - - &[data-padding='none'], - &[data-padding-narrow='none'] { - padding: 0; - } - - &[data-padding='condensed'], - &[data-padding-narrow='condensed'] { - padding: var(--stack-padding-condensed, 8px); - } - - &[data-padding='normal'], - &[data-padding-narrow='normal'] { - padding: var(--stack-padding-normal, 16px); - } - - &[data-padding='spacious'], - &[data-padding-narrow='spacious'] { - padding: var(--stack-padding-spacious, 24px); - } - - &[data-direction='horizontal'], - &[data-direction-narrow='horizontal'] { - flex-flow: row; - } - - &[data-direction='vertical'], - &[data-direction-narrow='vertical'] { +import classes from './Stack.module.css' +import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent' +import {useFeatureFlag} from '../FeatureFlags' +import {clsx} from 'clsx' + +const CSS_MODULE_FEATURE_FLAG = 'primer_react_css_modules_team' + +const StyledStack = toggleStyledComponent( + CSS_MODULE_FEATURE_FLAG, + 'div', + styled.div` + display: flex; flex-flow: column; - } - - &[data-gap='none'], - &[data-gap-narrow='none'] { - --stack-gap: var(--stack-gap-none, 0); - } - - &[data-gap='condensed'], - &[data-gap-narrow='condensed'] { - --stack-gap: var(--stack-gap-condensed, 0.5rem); - } - - &[data-gap='normal'], - &[data-gap-narrow='normal'] { - --stack-gap: var(--stack-gap-normal, 1rem); - } - - &[data-gap='spacious'], - &[data-gap-narrow='spacious'] { - --stack-gap: var(--stack-gap-spacious, 1.5rem); - } - - &[data-align='start'], - &[data-align-narrow='start'] { - align-items: flex-start; - } - - &[data-align='center'], - &[data-align-narrow='center'] { - align-items: center; - } - - &[data-align='end'], - &[data-align-narrow='end'] { - align-items: flex-end; - } - - &[data-align='baseline'], - &[data-align-narrow='baseline'] { - align-items: baseline; - } - - &[data-justify='start'], - &[data-justify-narrow='start'] { - justify-content: flex-start; - } - - &[data-justify='center'], - &[data-justify-narrow='center'] { - justify-content: center; - } - - &[data-justify='end'], - &[data-justify-narrow='end'] { - justify-content: flex-end; - } - - &[data-justify='space-between'], - &[data-justify-narrow='space-between'] { - justify-content: space-between; - } - - &[data-justify='space-evenly'], - &[data-justify-narrow='space-evenly'] { - justify-content: space-evenly; - } - - &[data-wrap='wrap'], - &[data-wrap-narrow='wrap'] { - flex-wrap: wrap; - } - - &[data-wrap='nowrap'], - &[data-wrap-narrow='nowrap'] { - flex-wrap: nowrap; - } - - // @custom-media --veiwportRange-regular - @media (min-width: 48rem) { - &[data-padding-regular='none'] { + align-items: stretch; + align-content: flex-start; + gap: var(--stack-gap, var(--stack-gap-normal, 1rem)); + + // non-responsive values + + &[data-padding='none'], + &[data-padding-narrow='none'] { padding: 0; } - &[data-padding-regular='condensed'] { + &[data-padding='condensed'], + &[data-padding-narrow='condensed'] { padding: var(--stack-padding-condensed, 8px); } - &[data-padding-regular='normal'] { + &[data-padding='normal'], + &[data-padding-narrow='normal'] { padding: var(--stack-padding-normal, 16px); } - &[data-padding-regular='spacious'] { + &[data-padding='spacious'], + &[data-padding-narrow='spacious'] { padding: var(--stack-padding-spacious, 24px); } - &[data-direction-regular='horizontal'] { + &[data-direction='horizontal'], + &[data-direction-narrow='horizontal'] { flex-flow: row; } - &[data-direction-regular='vertical'] { + &[data-direction='vertical'], + &[data-direction-narrow='vertical'] { flex-flow: column; } - &[data-gap-regular='none'] { + &[data-gap='none'], + &[data-gap-narrow='none'] { --stack-gap: var(--stack-gap-none, 0); } - &[data-gap-regular='condensed'] { + &[data-gap='condensed'], + &[data-gap-narrow='condensed'] { --stack-gap: var(--stack-gap-condensed, 0.5rem); } - &[data-gap-regular='normal'] { + &[data-gap='normal'], + &[data-gap-narrow='normal'] { --stack-gap: var(--stack-gap-normal, 1rem); } - &[data-gap-regular='spacious'] { + &[data-gap='spacious'], + &[data-gap-narrow='spacious'] { --stack-gap: var(--stack-gap-spacious, 1.5rem); } - &[data-align-regular='start'] { + &[data-align='start'], + &[data-align-narrow='start'] { align-items: flex-start; } - &[data-align-regular='center'] { + &[data-align='center'], + &[data-align-narrow='center'] { align-items: center; } - &[data-align-regular='end'] { + &[data-align='end'], + &[data-align-narrow='end'] { align-items: flex-end; } - &[data-align-regular='baseline'] { + &[data-align='baseline'], + &[data-align-narrow='baseline'] { align-items: baseline; } - &[data-justify-regular='start'] { + &[data-justify='start'], + &[data-justify-narrow='start'] { justify-content: flex-start; } - &[data-justify-regular='center'] { + &[data-justify='center'], + &[data-justify-narrow='center'] { justify-content: center; } - &[data-justify-regular='end'] { + &[data-justify='end'], + &[data-justify-narrow='end'] { justify-content: flex-end; } - &[data-justify-regular='space-between'] { + &[data-justify='space-between'], + &[data-justify-narrow='space-between'] { justify-content: space-between; } - &[data-justify-regular='space-evenly'] { + &[data-justify='space-evenly'], + &[data-justify-narrow='space-evenly'] { justify-content: space-evenly; } - &[data-wrap-regular='wrap'] { + &[data-wrap='wrap'], + &[data-wrap-narrow='wrap'] { flex-wrap: wrap; } - &[data-wrap-regular='nowrap'] { + &[data-wrap='nowrap'], + &[data-wrap-narrow='nowrap'] { flex-wrap: nowrap; } - } - // @custom-media --viewportRange-wide - @media (min-width: 87.5rem) { - &[data-padding-wide='none'] { - padding: 0; - } + // @custom-media --veiwportRange-regular + @media (min-width: 48rem) { + &[data-padding-regular='none'] { + padding: 0; + } - &[data-padding-wide='condensed'] { - padding: var(--stack-padding-condensed, 8px); - } + &[data-padding-regular='condensed'] { + padding: var(--stack-padding-condensed, 8px); + } - &[data-padding-wide='normal'] { - padding: var(--stack-padding-normal, 16px); - } + &[data-padding-regular='normal'] { + padding: var(--stack-padding-normal, 16px); + } - &[data-padding-wide='spacious'] { - padding: var(--stack-padding-spacious, 24px); - } + &[data-padding-regular='spacious'] { + padding: var(--stack-padding-spacious, 24px); + } - &[data-direction-wide='horizontal'] { - flex-flow: row; - } + &[data-direction-regular='horizontal'] { + flex-flow: row; + } - &[data-direction-wide='vertical'] { - flex-flow: column; - } + &[data-direction-regular='vertical'] { + flex-flow: column; + } - &[data-gap-wide='none'] { - --stack-gap: var(--stack-gap-none, 0); - } + &[data-gap-regular='none'] { + --stack-gap: var(--stack-gap-none, 0); + } - &[data-gap-wide='condensed'] { - --stack-gap: var(--stack-gap-condensed, 0.5rem); - } + &[data-gap-regular='condensed'] { + --stack-gap: var(--stack-gap-condensed, 0.5rem); + } - &[data-gap-wide='normal'] { - --stack-gap: var(--stack-gap-normal, 1rem); - } + &[data-gap-regular='normal'] { + --stack-gap: var(--stack-gap-normal, 1rem); + } - &[data-gap-wide='spacious'] { - --stack-gap: var(--stack-gap-spacious, 1.5rem); - } + &[data-gap-regular='spacious'] { + --stack-gap: var(--stack-gap-spacious, 1.5rem); + } - &[data-align-wide='start'] { - align-items: flex-start; - } + &[data-align-regular='start'] { + align-items: flex-start; + } - &[data-align-wide='center'] { - align-items: center; - } + &[data-align-regular='center'] { + align-items: center; + } - &[data-align-wide='end'] { - align-items: flex-end; - } + &[data-align-regular='end'] { + align-items: flex-end; + } - &[data-align-wide='baseline'] { - align-items: baseline; - } + &[data-align-regular='baseline'] { + align-items: baseline; + } - &[data-justify-wide='start'] { - justify-content: flex-start; - } + &[data-justify-regular='start'] { + justify-content: flex-start; + } - &[data-justify-wide='center'] { - justify-content: center; - } + &[data-justify-regular='center'] { + justify-content: center; + } - &[data-justify-wide='end'] { - justify-content: flex-end; - } + &[data-justify-regular='end'] { + justify-content: flex-end; + } - &[data-justify-wide='space-between'] { - justify-content: space-between; - } + &[data-justify-regular='space-between'] { + justify-content: space-between; + } - &[data-justify-wide='space-evenly'] { - justify-content: space-evenly; - } + &[data-justify-regular='space-evenly'] { + justify-content: space-evenly; + } - &[data-wrap-wide='wrap'] { - flex-wrap: wrap; + &[data-wrap-regular='wrap'] { + flex-wrap: wrap; + } + + &[data-wrap-regular='nowrap'] { + flex-wrap: nowrap; + } } - &[data-wrap-wide='nowrap'] { - flex-wrap: nowrap; + // @custom-media --viewportRange-wide + @media (min-width: 87.5rem) { + &[data-padding-wide='none'] { + padding: 0; + } + + &[data-padding-wide='condensed'] { + padding: var(--stack-padding-condensed, 8px); + } + + &[data-padding-wide='normal'] { + padding: var(--stack-padding-normal, 16px); + } + + &[data-padding-wide='spacious'] { + padding: var(--stack-padding-spacious, 24px); + } + + &[data-direction-wide='horizontal'] { + flex-flow: row; + } + + &[data-direction-wide='vertical'] { + flex-flow: column; + } + + &[data-gap-wide='none'] { + --stack-gap: var(--stack-gap-none, 0); + } + + &[data-gap-wide='condensed'] { + --stack-gap: var(--stack-gap-condensed, 0.5rem); + } + + &[data-gap-wide='normal'] { + --stack-gap: var(--stack-gap-normal, 1rem); + } + + &[data-gap-wide='spacious'] { + --stack-gap: var(--stack-gap-spacious, 1.5rem); + } + + &[data-align-wide='start'] { + align-items: flex-start; + } + + &[data-align-wide='center'] { + align-items: center; + } + + &[data-align-wide='end'] { + align-items: flex-end; + } + + &[data-align-wide='baseline'] { + align-items: baseline; + } + + &[data-justify-wide='start'] { + justify-content: flex-start; + } + + &[data-justify-wide='center'] { + justify-content: center; + } + + &[data-justify-wide='end'] { + justify-content: flex-end; + } + + &[data-justify-wide='space-between'] { + justify-content: space-between; + } + + &[data-justify-wide='space-evenly'] { + justify-content: space-evenly; + } + + &[data-wrap-wide='wrap'] { + flex-wrap: wrap; + } + + &[data-wrap-wide='nowrap'] { + flex-wrap: nowrap; + } } - } -` + `, +) type GapScale = 'none' | 'condensed' | 'normal' | 'spacious' type Gap = GapScale | ResponsiveValue @@ -366,12 +376,12 @@ function Stack({ ...rest }: StackProps & React.ComponentPropsWithoutRef) { const BaseComponent = as ?? 'div' - + const enabled = useFeatureFlag(CSS_MODULE_FEATURE_FLAG) return ( ({ ) } -const StyledStackItem = styled.div` - flex: 0 1 auto; - min-inline-size: 0; - - &[data-grow='true'], - &[data-grow-narrow='true'] { - flex-grow: 1; - } +const StyledStackItem = toggleStyledComponent( + CSS_MODULE_FEATURE_FLAG, + 'div', + styled.div` + flex: 0 1 auto; + min-inline-size: 0; - // @custom-media --veiwportRange-regular - @media (min-width: 48rem) { - &[data-grow-regular='true'] { + &[data-grow='true'], + &[data-grow-narrow='true'] { flex-grow: 1; } - &[data-grow-regular='false'] { - flex-grow: 0; - } - } + // @custom-media --veiwportRange-regular + @media (min-width: 48rem) { + &[data-grow-regular='true'] { + flex-grow: 1; + } - // @custom-media --viewportRange-wide - @media (min-width: 87.5rem) { - &[data-grow-wide='true'] { - flex-grow: 1; + &[data-grow-regular='false'] { + flex-grow: 0; + } } - &[data-grow-wide='false'] { - flex-grow: 0; + // @custom-media --viewportRange-wide + @media (min-width: 87.5rem) { + &[data-grow-wide='true'] { + flex-grow: 1; + } + + &[data-grow-wide='false'] { + flex-grow: 0; + } } - } -` + `, +) type StackItemProps = React.PropsWithChildren<{ /** @@ -438,9 +452,15 @@ function StackItem({ ...rest }: StackItemProps & React.ComponentPropsWithoutRef) { const BaseComponent = as ?? 'div' + const enabled = useFeatureFlag(CSS_MODULE_FEATURE_FLAG) return ( - + {children} ) From d68a084ebde141a6e84bf01f0e569aaeeb3bb97e Mon Sep 17 00:00:00 2001 From: Jon Rohan Date: Fri, 1 Nov 2024 20:13:45 +0000 Subject: [PATCH 2/3] Adding className tests --- .../react/src/Stack/__tests__/Stack.test.tsx | 20 ++++++++++++++ .../src/Stack/__tests__/StackItem.test.tsx | 26 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/packages/react/src/Stack/__tests__/Stack.test.tsx b/packages/react/src/Stack/__tests__/Stack.test.tsx index e4cca6c27d0..e98402bc024 100644 --- a/packages/react/src/Stack/__tests__/Stack.test.tsx +++ b/packages/react/src/Stack/__tests__/Stack.test.tsx @@ -1,8 +1,28 @@ import {render, screen} from '@testing-library/react' import React from 'react' import {Stack} from '../Stack' +import {FeatureFlags} from '../../FeatureFlags' describe('Stack', () => { + it('should support `className` on the outermost element', () => { + const Element = () => + const FeatureFlagElement = () => { + return ( + + + + ) + } + expect(render().container.firstChild).toHaveClass('test-class-name') + expect(render().container.firstChild).toHaveClass('test-class-name') + }) + it('should support rendering content through `children`', () => { render( diff --git a/packages/react/src/Stack/__tests__/StackItem.test.tsx b/packages/react/src/Stack/__tests__/StackItem.test.tsx index 4daa252d46b..56eae72c474 100644 --- a/packages/react/src/Stack/__tests__/StackItem.test.tsx +++ b/packages/react/src/Stack/__tests__/StackItem.test.tsx @@ -1,8 +1,34 @@ import {render, screen} from '@testing-library/react' import React from 'react' import {Stack, StackItem} from '../Stack' +import {FeatureFlags} from '../../FeatureFlags' describe('StackItem', () => { + it('should support `className` on the outermost element', () => { + const Element = () => ( + + + Content + + + ) + const FeatureFlagElement = () => { + return ( + + + + ) + } + expect(render().getAllByTestId('stack-item')[0]).toHaveClass('test-class-name') + expect(render().getAllByTestId('stack-item')[1]).toHaveClass('test-class-name') + }) + it('should render its children', () => { render( From ff73594fc5ec2d3a87ea5c3544527f813ecee038 Mon Sep 17 00:00:00 2001 From: Jon Rohan Date: Fri, 1 Nov 2024 13:15:51 -0700 Subject: [PATCH 3/3] Create gentle-doors-sell.md --- .changeset/gentle-doors-sell.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/gentle-doors-sell.md diff --git a/.changeset/gentle-doors-sell.md b/.changeset/gentle-doors-sell.md new file mode 100644 index 00000000000..795c99d1078 --- /dev/null +++ b/.changeset/gentle-doors-sell.md @@ -0,0 +1,5 @@ +--- +"@primer/react": patch +--- + +Convert Stack to CSS modules behind feature flag