Skip to content

Commit 05aee45

Browse files
authored
Make torchlight effect optional in Card (#980)
1 parent 0ab5275 commit 05aee45

File tree

13 files changed

+125
-40
lines changed

13 files changed

+125
-40
lines changed

.changeset/witty-zoos-behave.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@primer/react-brand': minor
3+
---
4+
5+
The torchlight visual effect on dark mode `Card` components is now optional. The new default effect is now similar to its `light` mode counterpart.
6+
7+
To re-enable the torchlight effect, use `variant="torchlight"`. Note this effect continues to only work in `dark` color modes.
8+
9+
```jsx
10+
<ThemeProvider colorMode="dark">
11+
<Card variant="torchlight" {...} />
12+
</ThemeProvider>
13+
```

apps/docs/content/components/Card/react.mdx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ a11yReviewed: true
99
import ComponentLayout from '../../../src/layouts/component-layout'
1010
export default ComponentLayout
1111

12-
import {LabelColors, CardIconColors} from '@primer/react-brand'
12+
import {LabelColors, CardIconColors, CardVariants} from '@primer/react-brand'
1313
import {IconColors} from '@primer/react-brand'
1414
import {PropTableValues} from '../../../src/components'
1515
import {CopilotIcon, RocketIcon, GitBranchIcon} from '@primer/octicons-react'
@@ -64,7 +64,7 @@ The call to action text defaults to `Learn more` and can be customized using the
6464

6565
A border can be provided using the `hasBorder` prop. This will render a border around the Card component giving further separation between the foreground and background. Especially when there is no shadow present on the background.
6666

67-
The `hasBorder` prop has no effect in when using the torchlight effect in dark mode. In this case, the border is always rendered.
67+
The `hasBorder` prop has no effect in when using the torchlight variant in dark mode. In this case, the border is always rendered.
6868

6969
```jsx live
7070
<Card href="https://github.com" hasBorder>
@@ -143,14 +143,17 @@ Use the `Image` component to add an image to the `Card`. The `Image` component i
143143

144144
### Torchlight effect
145145

146-
`Card` components in dark mode feature a unique "torchlight" effect.
146+
`Card` components in dark mode feature a unique "torchlight" effect in dark color modes.
147147

148-
The color of the lit area corrosponds to the nearest active, primary accent color. The color can modified by changing the value of the respective CSS variable.
148+
The color of the lit area corresponds to the nearest active, primary accent color. The color can modified by changing the value of the respective CSS variable.
149+
150+
Use `variant="torchlight"` to enable this effect in dark mode.
149151

150152
```jsx live
151153
<ThemeProvider colorMode="dark">
152154
<Box backgroundColor="default" padding="condensed">
153155
<Card
156+
variant="torchlight"
154157
href="https://github.com"
155158
fullWidth
156159
style={{
@@ -225,13 +228,14 @@ Use the `Stack` or `Grid` components to stack up to three cards horizontally in
225228

226229
### Card
227230

228-
| name | type | default | required | description |
229-
| ------------------ | --------- | ----------- | -------- | ---------------------------------------------------------------- |
230-
| `ctaText` | `string` | `Read more` | `false` | Label of the link at the bottom of the card |
231-
| `disableAnimation` | `boolean` | `false` | `false` | A flag to disable the default hover animation effect of the card |
232-
| `fullWidth` | `boolean` | `false` | `false` | A flag to optionally fill the width of the parent container |
233-
| `hasBorder` | `boolean` | `false` | `false` | A flag used to provide a border to the card |
234-
| `href` | `string` | | `true` | URL to the card content |
231+
| name | type | default | required | description |
232+
| ------------------ | ------------------------------------------------------- | ----------- | -------- | ---------------------------------------------------------------- |
233+
| `ctaText` | `string` | `Read more` | `false` | Label of the link at the bottom of the card |
234+
| `disableAnimation` | `boolean` | `false` | `false` | A flag to disable the default hover animation effect of the card |
235+
| `fullWidth` | `boolean` | `false` | `false` | A flag to optionally fill the width of the parent container |
236+
| `hasBorder` | `boolean` | `false` | `false` | A flag used to provide a border to the card |
237+
| `href` | `string` | | `true` | URL to the card content |
238+
| `variant` | <PropTableValues values={CardVariants} addLineBreaks /> | `'default'` | `false` | Specify alternative card appearance |
235239

236240
### Card.Image
237241

apps/docs/scripts/components-with-animation.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ export const supportedComponents = [
66
'ComparisonTable',
77
'FAQ',
88
'Heading',
9-
'Label',
109
'Image',
10+
'Label',
1111
'Pillar',
1212
'SectionIntro',
1313
'Stack',
14-
'Testimonial',
1514
'Statistic',
15+
'Testimonial',
1616
'Text',
1717
'Timeline',
1818
'Animate',

packages/react/src/Card/Card.features.stories.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ export const Stacked: StoryFn<typeof Card> = () => {
338338
)
339339
}
340340

341-
export const DarkColorModeEffect = () => {
341+
export const TorchlightVariant = () => {
342342
return (
343343
<>
344344
<Stack
@@ -348,12 +348,13 @@ export const DarkColorModeEffect = () => {
348348
}}
349349
gap={36}
350350
>
351-
<Card href="https://github.com">
351+
<Card variant="torchlight" href="https://github.com">
352352
<Card.Icon icon={ZapIcon} hasBackground />
353353
<Card.Heading>Collaboration is the key to DevOps success</Card.Heading>
354354
<Card.Description>Everything you need to know about getting started with GitHub Actions.</Card.Description>
355355
</Card>
356356
<Card
357+
variant="torchlight"
357358
href="https://github.com"
358359
style={{['--brand-color-accent-primary' as string]: 'var(--base-color-scale-indigo-2)'}}
359360
>
@@ -363,6 +364,7 @@ export const DarkColorModeEffect = () => {
363364
</Card>
364365

365366
<Card
367+
variant="torchlight"
366368
href="https://github.com"
367369
style={{['--brand-color-accent-primary' as string]: 'var(--base-color-scale-yellow-2)'}}
368370
>
@@ -379,6 +381,7 @@ export const DarkColorModeEffect = () => {
379381
gap={36}
380382
>
381383
<Card
384+
variant="torchlight"
382385
href="https://github.com"
383386
style={{['--brand-color-accent-primary' as string]: 'var(--base-color-scale-red-2)'}}
384387
>
@@ -388,6 +391,7 @@ export const DarkColorModeEffect = () => {
388391
</Card>
389392

390393
<Card
394+
variant="torchlight"
391395
href="https://github.com"
392396
style={{['--brand-color-accent-primary' as string]: 'var(--base-color-scale-orange-2)'}}
393397
>
@@ -396,6 +400,7 @@ export const DarkColorModeEffect = () => {
396400
<Card.Description>Everything you need to know about getting started with GitHub Actions.</Card.Description>
397401
</Card>
398402
<Card
403+
variant="torchlight"
399404
href="https://github.com"
400405
style={{['--brand-color-accent-primary' as string]: 'var(--base-color-scale-lime-2)'}}
401406
>
@@ -408,7 +413,7 @@ export const DarkColorModeEffect = () => {
408413
)
409414
}
410415

411-
DarkColorModeEffect.decorators = [
416+
TorchlightVariant.decorators = [
412417
Story => (
413418
<ThemeProvider colorMode="dark">
414419
<Box backgroundColor="default">

packages/react/src/Card/Card.module.css

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
padding: 0;
5555
}
5656

57-
.Card--colorMode-light {
57+
.Card--variant-default,
58+
.Card--variant-torchlight.Card--colorMode-light {
5859
background-color: var(--brand-Card-background-default);
5960
}
6061

@@ -121,7 +122,7 @@
121122
align-self: center;
122123
}
123124

124-
.Card:not(.Card--disableAnimation, .Card--variant-default.Card--colorMode-dark):hover {
125+
.Card:not(.Card--disableAnimation, .Card--variant-torchlight.Card--colorMode-dark):hover {
125126
transform: scale3d(1.025, 1.025, 1);
126127
}
127128

packages/react/src/Card/Card.module.css.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ declare const styles: {
1212
readonly "Card--skew": string;
1313
readonly "Card--variant-default": string;
1414
readonly "Card--variant-minimal": string;
15+
readonly "Card--variant-torchlight": string;
1516
readonly "Card__action": string;
1617
readonly "Card__description": string;
1718
readonly "Card__heading": string;

packages/react/src/Card/Card.test.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,29 @@ import '@testing-library/jest-dom'
44
import {Card} from './Card'
55
import {axe, toHaveNoViolations} from 'jest-axe'
66
import {GitMergeIcon} from '@primer/octicons-react'
7+
import {ThemeProvider} from '../ThemeProvider'
78

89
expect.extend(toHaveNoViolations)
910

1011
describe('Card', () => {
1112
afterEach(cleanup)
1213

14+
beforeAll(() => {
15+
Object.defineProperty(window, 'matchMedia', {
16+
writable: true,
17+
value: jest.fn().mockImplementation(query => ({
18+
matches: false,
19+
media: query,
20+
onchange: null,
21+
addListener: jest.fn(), // Deprecated
22+
removeListener: jest.fn(), // Deprecated
23+
dispatchEvent: jest.fn(),
24+
addEventListener: jest.fn(),
25+
removeEventListener: jest.fn(),
26+
})),
27+
})
28+
})
29+
1330
const mockHref = '#'
1431
const mockLabel = 'This is a mock label'
1532
const mockHeading = 'This is a mock heading'
@@ -31,6 +48,7 @@ describe('Card', () => {
3148
const mockTestId = 'test'
3249
const expectedClass = 'Card'
3350
const expectedCustomClass = 'custom-class'
51+
const defaultVariantClass = 'Card--variant-default'
3452
const expectedTag = 'div'
3553

3654
const {getByTestId} = render(
@@ -43,9 +61,48 @@ describe('Card', () => {
4361
const cardEl = getByTestId(mockTestId)
4462
expect(cardEl.tagName).toBe(expectedTag.toUpperCase())
4563
expect(cardEl.classList).toContain(expectedClass)
64+
expect(cardEl.classList).toContain(defaultVariantClass)
4665
expect(cardEl.classList).toContain(expectedCustomClass)
4766
})
4867

68+
it('can optionally render a minimal variant', () => {
69+
const mockTestId = 'test'
70+
71+
const {getByTestId} = render(
72+
<Card variant="minimal" href={mockHref} data-testid={mockTestId}>
73+
<Card.Heading>{mockHeading}</Card.Heading>
74+
</Card>,
75+
)
76+
const cardEl = getByTestId(mockTestId)
77+
expect(cardEl.classList).toContain('Card--variant-minimal')
78+
})
79+
80+
it('can optionally render a torchlight variant', () => {
81+
const mockTestId = 'test'
82+
83+
const {getByTestId} = render(
84+
<Card variant="torchlight" href={mockHref} data-testid={mockTestId}>
85+
<Card.Heading>{mockHeading}</Card.Heading>
86+
</Card>,
87+
)
88+
const cardEl = getByTestId(mockTestId)
89+
expect(cardEl.classList).toContain('Card--variant-torchlight')
90+
})
91+
92+
it('renders a default variant in dark mode and not torchlight', () => {
93+
const mockTestId = 'test'
94+
95+
const {getByTestId} = render(
96+
<ThemeProvider colorMode="dark">
97+
<Card href={mockHref} data-testid={mockTestId}>
98+
<Card.Heading>{mockHeading}</Card.Heading>
99+
</Card>
100+
</ThemeProvider>,
101+
)
102+
const cardEl = getByTestId(mockTestId)
103+
expect(cardEl.classList).not.toContain('Card--variant-torchlight')
104+
})
105+
49106
it('renders the correct default heading type', () => {
50107
const expectedHeadingTag = 'h3'
51108

packages/react/src/Card/Card.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,19 @@ import '@primer/brand-primitives/lib/design-tokens/css/tokens/functional/compone
2020
import styles from './Card.module.css'
2121
import stylesLink from '../Link/Link.module.css'
2222

23+
export const CardVariants = ['default', 'minimal', 'torchlight'] as const
24+
2325
export const CardIconColors = Colors
2426

2527
export const defaultCardIconColor = CardIconColors[0]
2628

29+
export type CardVariants = (typeof CardVariants)[number]
30+
2731
export type CardProps = {
2832
/**
2933
* Specify alternative card appearance
3034
*/
31-
variant?: 'default' | 'minimal'
35+
variant?: CardVariants
3236
/**
3337
* Valid children include Card.Image, Card.Heading, and Card.Description
3438
*/
@@ -108,7 +112,7 @@ const CardRoot = forwardRef<HTMLDivElement, CardProps>(
108112
child => React.isValidElement(child) && typeof child.type !== 'string' && child.type === CardIcon,
109113
)
110114

111-
const hasSkewEffect = colorMode === 'dark' && variant !== 'minimal'
115+
const hasSkewEffect = colorMode === 'dark' && variant === 'torchlight'
112116
const showBorder = hasSkewEffect || hasBorder
113117

114118
const WrapperComponent = hasSkewEffect ? CardSkewEffect : DefaultCardWrapperComponent

packages/react/src/Card/Card.visual.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ test.describe('Visual Comparison: Card', () => {
134134
expect(await page.screenshot({fullPage: true})).toMatchSnapshot()
135135
})
136136

137-
test('Card / Dark Color Mode Effect', async ({page}) => {
137+
test('Card / Torchlight Variant', async ({page}) => {
138138
await page.goto(
139-
'http://localhost:6006/iframe.html?args=&id=components-card-features--dark-color-mode-effect&viewMode=story',
139+
'http://localhost:6006/iframe.html?args=&id=components-card-features--torchlight-variant&viewMode=story',
140140
)
141141

142142
await page.waitForTimeout(500)
48.4 KB
Loading

0 commit comments

Comments
 (0)