Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clean-lions-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Update CounterLabel to use CSS Modules behind feature flag
125 changes: 68 additions & 57 deletions e2e/components/CounterLabel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,83 @@ import {test, expect} from '@playwright/test'
import {visit} from '../test-helpers/storybook'
import {themes} from '../test-helpers/themes'

test.describe('CounterLabel', () => {
test.describe('Default', () => {
for (const theme of themes) {
test.describe(theme, () => {
test('default @vrt', async ({page}) => {
await visit(page, {
id: 'components-counterlabel--default',
globals: {
colorScheme: theme,
},
})
const stories = [
{
title: 'Default',
id: 'components-counterlabel--default',
},
{
title: 'Primary Theme',
id: 'components-counterlabel--primary-theme',
},
{
title: 'Secondary Theme',
id: 'components-counterlabel--secondary-theme',
},
] as const

// Default state
expect(await page.screenshot()).toMatchSnapshot(`CounterLabel.Default.${theme}.png`)
})
test.describe('CounterLabel', () => {
for (const story of stories) {
test.describe(story.title, () => {
for (const theme of themes) {
test.describe(theme, () => {
test('default @vrt', async ({page}) => {
await visit(page, {
id: story.id,
globals: {
colorScheme: theme,
featureFlags: {
primer_react_css_modules_team: true,
},
},
})

test('axe @aat', async ({page}) => {
await visit(page, {
id: 'components-counterlabel--default',
globals: {
colorScheme: theme,
},
// Default state
expect(await page.screenshot()).toMatchSnapshot(`CounterLabel.${story.title}.${theme}.png`)
})
await expect(page).toHaveNoViolations({
rules: {
'color-contrast': {
enabled: theme !== 'dark_dimmed',

test('default (styled-components) @vrt', async ({page}) => {
await visit(page, {
id: story.id,
globals: {
colorScheme: theme,
featureFlags: {
primer_react_css_modules_team: false,
},
},
},
})
})
})
}
})
})

test.describe('Primary Theme', () => {
for (const theme of themes) {
test.describe(theme, () => {
test('default @vrt', async ({page}) => {
await visit(page, {
id: 'components-counterlabel-features--primary-theme',
globals: {
colorScheme: theme,
},
// Default state
expect(await page.screenshot()).toMatchSnapshot(`CounterLabel.${story.title}.${theme}.png`)
})

// Default state
expect(await page.screenshot()).toMatchSnapshot(`CounterLabel.Primary Theme.${theme}.png`)
})

test('axe @aat', async ({page}) => {
await visit(page, {
id: 'components-counterlabel-features--primary-theme',
globals: {
colorScheme: theme,
},
test('axe @aat', async ({page}) => {
await visit(page, {
id: story.id,
globals: {
colorScheme: theme,
featureFlags: {
primer_react_css_modules_team: true,
},
},
})
await expect(page).toHaveNoViolations()
})
await expect(page).toHaveNoViolations({
rules: {
'color-contrast': {
enabled: theme !== 'dark_dimmed',

test('axe (styled-components) @aat', async ({page}) => {
await visit(page, {
id: story.id,
globals: {
colorScheme: theme,
featureFlags: {
primer_react_css_modules_team: false,
},
},
},
})
await expect(page).toHaveNoViolations()
})
})
})
}
})
}
})
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export default {
} as Meta<typeof CounterLabel>

export const PrimaryTheme: StoryFn<typeof CounterLabel> = () => <CounterLabel scheme="primary">12</CounterLabel>

export const SecondaryTheme: StoryFn<typeof CounterLabel> = () => <CounterLabel scheme="secondary">12</CounterLabel>
25 changes: 25 additions & 0 deletions packages/react/src/CounterLabel/CounterLabel.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.CounterLabel {
display: inline-block;
padding: var(--base-size-2) var(--base-size-4);
font-size: var(--text-body-size-small);
font-weight: var(--base-text-weight-semibold);
/* stylelint-disable-next-line primer/typography */
line-height: 1;
border: var(--borderWidth-thin) solid var(--counter-borderColor);
/* stylelint-disable-next-line primer/borders */
border-radius: 20px;

&[data-scheme='primary'] {
color: var(--fgColor-onEmphasis);
background-color: var(--bgColor-neutral-emphasis);
}

&[data-scheme='secondary'] {
color: var(--fgColor-default);
background-color: var(--bgColor-neutral-muted);
}

&:empty {
display: none;
}
}
16 changes: 15 additions & 1 deletion packages/react/src/CounterLabel/CounterLabel.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import React from 'react'
import type {StoryFn, Meta} from '@storybook/react'
import type {StoryFn, Meta, StoryObj} from '@storybook/react'
import CounterLabel from './CounterLabel'

export default {
title: 'Components/CounterLabel',
component: CounterLabel,
} as Meta<typeof CounterLabel>

export const Default: StoryFn<typeof CounterLabel> = () => <CounterLabel>12</CounterLabel>

export const Playground: StoryObj<typeof CounterLabel> = {
render: args => <CounterLabel {...args}>12</CounterLabel>,
args: {
scheme: 'primary',
},
argTypes: {
scheme: {
control: 'select',
options: ['primary', 'secondary'],
},
},
}
101 changes: 69 additions & 32 deletions packages/react/src/CounterLabel/CounterLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import {clsx} from 'clsx'
import type {HTMLAttributes} from 'react'
import React, {forwardRef} from 'react'
import Box from '../Box'
import type {BetterSystemStyleObject, SxProp} from '../sx'
import {merge} from '../sx'
import VisuallyHidden from '../_VisuallyHidden'
import styled from 'styled-components'
import {get} from '../constants'
import sx from '../sx'
import type {SxProp} from '../sx'
import {VisuallyHidden} from '../internal/components/VisuallyHidden'
import {defaultSxProp} from '../utils/defaultSxProp'
import {useFeatureFlag} from '../FeatureFlags'
import Box from '../Box'
import classes from './CounterLabel.module.css'

export type CounterLabelProps = React.PropsWithChildren<
HTMLAttributes<HTMLSpanElement> & {
Expand All @@ -14,42 +19,74 @@ export type CounterLabelProps = React.PropsWithChildren<
>

const CounterLabel = forwardRef<HTMLSpanElement, CounterLabelProps>(
({scheme = 'secondary', sx = defaultSxProp, children, className, ...props}, forwardedRef) => {
({scheme = 'secondary', sx = defaultSxProp, className, children, ...rest}, forwardedRef) => {
const enabled = useFeatureFlag('primer_react_css_modules_team')
const label = <VisuallyHidden>&nbsp;({children})</VisuallyHidden>
const counterProps = {
ref: forwardedRef,
['aria-hidden']: 'true' as const,
['data-scheme']: scheme,
...rest,
}

if (enabled) {
if (sx !== defaultSxProp) {
return (
<>
<Box as="span" {...counterProps} className={clsx(className, classes.CounterLabel)} sx={sx}>
{children}
</Box>
{label}
</>
)
}
return (
<>
<span {...counterProps} className={clsx(className, classes.CounterLabel)}>
{children}
</span>
{label}
</>
)
}

return (
<>
<Box
aria-hidden="true"
className={className}
sx={merge<BetterSystemStyleObject>(
{
display: 'inline-block',
padding: '2px 5px',
fontSize: 0,
fontWeight: 'bold',
lineHeight: 'condensedUltra',
borderRadius: '20px',
backgroundColor: scheme === 'primary' ? 'neutral.emphasis' : 'neutral.muted',
border:
'var(--borderWidth-thin,max(1px, 0.0625rem)) solid var(--counter-borderColor,var(--color-counter-border))',
color: scheme === 'primary' ? 'fg.onEmphasis' : 'fg.default',
'&:empty': {
display: 'none',
},
},
sx,
)}
{...props}
as="span"
ref={forwardedRef}
>
<StyledCounterLabel {...counterProps} className={className} sx={sx}>
{children}
</Box>
<VisuallyHidden>&nbsp;({children})</VisuallyHidden>
</StyledCounterLabel>
{label}
</>
)
},
)

const StyledCounterLabel = styled.span`
display: inline-block;
padding: 2px 5px;
font-size: 12px;
font-weight: bold;
line-height: 1;
border-radius: 20px;
border: var(--borderWidth-thin, max(1px, 0.0625rem)) solid var(--counter-borderColor, var(--color-counter-border));

&[data-scheme='primary'] {
background-color: ${get('colors.neutral.emphasis')};
color: ${get('colors.fg.onEmphasis')};
}

&[data-scheme='secondary'] {
background-color: ${get('colors.neutral.muted')};
color: ${get('colors.fg.default')};
}

&:empty {
display: none;
}

${sx}
`

CounterLabel.displayName = 'CounterLabel'

export default CounterLabel
Loading