-
Notifications
You must be signed in to change notification settings - Fork 658
Add Card component #7723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
liuliu-dev
wants to merge
12
commits into
main
Choose a base branch
from
liuliu/add-card-component
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add Card component #7723
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
7a94475
card from pacer
liuliu-dev 6b253f0
Merge branch 'main' into liuliu/add-card-component
liuliu-dev c5f4685
test(vrt): update snapshots
liuliu-dev 4fe8773
revert snapshot changes
liuliu-dev 093961a
image src
liuliu-dev 027ed75
test snapshot
liuliu-dev 648be81
padding token
liuliu-dev 9991be0
add heading levels
liuliu-dev 1d09b8c
lint
liuliu-dev 13f4c99
margin, icon image order, vrt
liuliu-dev bce86e8
test(vrt): update snapshots
liuliu-dev 289544f
restore actionlist snapshots
liuliu-dev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@primer/react': minor | ||
| --- | ||
|
|
||
| Add Card component with subcomponents: Card.Icon, Card.Image, Card.Heading, Card.Description, Card.Menu, and Card.Metadata |
Binary file added
BIN
+15 KB
...pshots/components/Card.test.ts-snapshots/Card-Default-dark-colorblind-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+14.9 KB
.../snapshots/components/Card.test.ts-snapshots/Card-Default-dark-dimmed-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+14.9 KB
...ots/components/Card.test.ts-snapshots/Card-Default-dark-high-contrast-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+15 KB
...ywright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+15 KB
...pshots/components/Card.test.ts-snapshots/Card-Default-dark-tritanopia-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+14.6 KB
...shots/components/Card.test.ts-snapshots/Card-Default-light-colorblind-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+14.7 KB
...ts/components/Card.test.ts-snapshots/Card-Default-light-high-contrast-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+14.6 KB
...wright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+14.6 KB
...shots/components/Card.test.ts-snapshots/Card-Default-light-tritanopia-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+25.1 KB
...ots/components/Card.test.ts-snapshots/Card-With-Image-dark-colorblind-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+25.1 KB
...apshots/components/Card.test.ts-snapshots/Card-With-Image-dark-dimmed-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+24.8 KB
.../components/Card.test.ts-snapshots/Card-With-Image-dark-high-contrast-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+25.1 KB
...ight/snapshots/components/Card.test.ts-snapshots/Card-With-Image-dark-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+25.1 KB
...ots/components/Card.test.ts-snapshots/Card-With-Image-dark-tritanopia-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+24.5 KB
...ts/components/Card.test.ts-snapshots/Card-With-Image-light-colorblind-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+24.7 KB
...components/Card.test.ts-snapshots/Card-With-Image-light-high-contrast-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+24.5 KB
...ght/snapshots/components/Card.test.ts-snapshots/Card-With-Image-light-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+24.5 KB
...ts/components/Card.test.ts-snapshots/Card-With-Image-light-tritanopia-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16.9 KB
.../components/Card.test.ts-snapshots/Card-With-Metadata-dark-colorblind-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16.8 KB
...hots/components/Card.test.ts-snapshots/Card-With-Metadata-dark-dimmed-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16.8 KB
...mponents/Card.test.ts-snapshots/Card-With-Metadata-dark-high-contrast-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16.9 KB
...t/snapshots/components/Card.test.ts-snapshots/Card-With-Metadata-dark-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16.9 KB
.../components/Card.test.ts-snapshots/Card-With-Metadata-dark-tritanopia-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16.4 KB
...components/Card.test.ts-snapshots/Card-With-Metadata-light-colorblind-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16.5 KB
...ponents/Card.test.ts-snapshots/Card-With-Metadata-light-high-contrast-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16.4 KB
.../snapshots/components/Card.test.ts-snapshots/Card-With-Metadata-light-linux.png
Oops, something went wrong.
Binary file added
BIN
+16.4 KB
...components/Card.test.ts-snapshots/Card-With-Metadata-light-tritanopia-linux.png
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import {test, expect} from '@playwright/test' | ||
| import {visit} from '../test-helpers/storybook' | ||
| import {themes} from '../test-helpers/themes' | ||
|
|
||
| test.describe('Card', () => { | ||
| test.describe('Default', () => { | ||
| for (const theme of themes) { | ||
| test.describe(theme, () => { | ||
| test('default @vrt', async ({page}) => { | ||
| await visit(page, { | ||
| id: 'components-card--default', | ||
| globals: { | ||
| colorScheme: theme, | ||
| }, | ||
| }) | ||
|
|
||
| // Default state | ||
| expect(await page.screenshot()).toMatchSnapshot(`Card.Default.${theme}.png`) | ||
| }) | ||
| }) | ||
| } | ||
| }) | ||
|
|
||
| test.describe('With Image', () => { | ||
| for (const theme of themes) { | ||
| test.describe(theme, () => { | ||
| test('default @vrt', async ({page}) => { | ||
| await visit(page, { | ||
| id: 'components-card--with-image', | ||
| globals: { | ||
| colorScheme: theme, | ||
| }, | ||
| }) | ||
|
|
||
| // Default state | ||
| expect(await page.screenshot()).toMatchSnapshot(`Card.With Image.${theme}.png`) | ||
| }) | ||
| }) | ||
| } | ||
| }) | ||
|
|
||
| test.describe('With Metadata', () => { | ||
| for (const theme of themes) { | ||
| test.describe(theme, () => { | ||
| test('default @vrt', async ({page}) => { | ||
| await visit(page, { | ||
| id: 'components-card--with-metadata', | ||
| globals: { | ||
| colorScheme: theme, | ||
| }, | ||
| }) | ||
|
|
||
| // Default state | ||
| expect(await page.screenshot()).toMatchSnapshot(`Card.With Metadata.${theme}.png`) | ||
| }) | ||
| }) | ||
| } | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| .Card { | ||
| display: grid; | ||
| position: relative; | ||
| border-radius: var(--borderRadius-large); | ||
| overflow: hidden; | ||
| grid-auto-rows: max-content auto; | ||
| border: var(--borderWidth-thin) solid var(--borderColor-default); | ||
| box-shadow: var(--shadow-resting-small); | ||
| background-color: var(--bgColor-default); | ||
| } | ||
|
|
||
| .CardHeader { | ||
| display: block; | ||
| width: 100%; | ||
| height: auto; | ||
| /* stylelint-disable primer/spacing */ | ||
| padding: var(--stack-padding-spacious) var(--stack-padding-spacious) var(--stack-padding-normal) | ||
| var(--stack-padding-spacious); | ||
| /* stylelint-enable primer/spacing */ | ||
| } | ||
|
|
||
| .CardHeaderEdgeToEdge { | ||
| padding: 0; | ||
| margin-bottom: var(--base-size-16); | ||
| } | ||
|
|
||
| .CardImage { | ||
| display: block; | ||
| width: 100%; | ||
| height: auto; | ||
| } | ||
|
|
||
| .CardIcon { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| width: var(--base-size-32); | ||
| height: var(--base-size-32); | ||
| border-radius: var(--borderRadius-medium); | ||
| background-color: var(--bgColor-muted); | ||
| color: var(--fgColor-muted); | ||
| } | ||
|
|
||
| .CardBody { | ||
| display: grid; | ||
| gap: var(--base-size-16); | ||
| /* stylelint-disable-next-line primer/spacing */ | ||
| padding: 0 var(--stack-padding-spacious) var(--stack-padding-spacious) var(--stack-padding-spacious); | ||
| } | ||
|
|
||
| .CardContent { | ||
| display: grid; | ||
| gap: var(--base-size-8); | ||
| } | ||
|
|
||
| .CardHeading { | ||
| font-size: var(--text-body-size-large); | ||
| font-weight: var(--base-text-weight-semibold); | ||
| color: var(--fgColor-default); | ||
| white-space: nowrap; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| margin: 0; | ||
| } | ||
|
|
||
| .CardDescription { | ||
| font-size: var(--text-body-size-medium); | ||
| color: var(--fgColor-muted); | ||
| display: -webkit-box; | ||
| overflow: hidden; | ||
| -webkit-box-orient: vertical; | ||
| -webkit-line-clamp: 2; | ||
| line-clamp: 2; | ||
| margin: 0; | ||
| } | ||
|
|
||
| .CardMetadataContainer { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: var(--base-size-16); | ||
| font-size: var(--text-body-size-medium); | ||
| color: var(--fgColor-muted); | ||
| } | ||
|
|
||
| .CardMetadataItem { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: var(--base-size-8); | ||
| font: var(--text-body-shorthand-small); | ||
| } | ||
|
|
||
| .CardMenu { | ||
| position: absolute; | ||
| top: var(--base-size-16); | ||
| right: var(--base-size-16); | ||
| z-index: 1; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import type {Meta} from '@storybook/react-vite' | ||
| import {RocketIcon, RepoIcon, StarIcon} from '@primer/octicons-react' | ||
| import {Card} from './index' | ||
|
|
||
| const meta = { | ||
| title: 'Components/Card', | ||
| component: Card, | ||
| } satisfies Meta<typeof Card> | ||
|
|
||
| export default meta | ||
|
|
||
| export const Default = () => { | ||
| return ( | ||
| <div style={{maxWidth: '400px'}}> | ||
| <Card> | ||
| <Card.Icon icon={RocketIcon} /> | ||
| <Card.Heading>Card Heading</Card.Heading> | ||
| <Card.Description>This is a description of the card providing supplemental information.</Card.Description> | ||
| <Card.Metadata>Updated 2 hours ago</Card.Metadata> | ||
| </Card> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| export const WithImage = () => { | ||
| return ( | ||
| <div style={{maxWidth: '400px'}}> | ||
| <Card> | ||
| <Card.Image src="https://github.com/octocat.png" alt="Octocat" /> | ||
| <Card.Heading>Card with Image</Card.Heading> | ||
| <Card.Description>This card uses an edge-to-edge image instead of an icon.</Card.Description> | ||
| </Card> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| export const WithMetadata = () => { | ||
| return ( | ||
| <div style={{maxWidth: '400px'}}> | ||
| <Card> | ||
| <Card.Icon icon={RepoIcon} /> | ||
| <Card.Heading>primer/react</Card.Heading> | ||
| <Card.Description> | ||
| {"GitHub's design system implemented as React components for building consistent user interfaces."} | ||
| </Card.Description> | ||
| <Card.Metadata> | ||
| <StarIcon size={16} /> | ||
| 1.2k stars | ||
| </Card.Metadata> | ||
| </Card> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| export const Playground = { | ||
| render: () => ( | ||
| <div style={{maxWidth: '400px'}}> | ||
| <Card> | ||
| <Card.Icon icon={RocketIcon} /> | ||
| <Card.Heading>Playground Card</Card.Heading> | ||
| <Card.Description>Experiment with the Card component and its subcomponents.</Card.Description> | ||
| <Card.Metadata>Just now</Card.Metadata> | ||
| </Card> | ||
| </div> | ||
| ), | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| import {describe, expect, it} from 'vitest' | ||
| import {render, screen} from '@testing-library/react' | ||
| import {Card} from '../Card' | ||
| import {implementsClassName} from '../utils/testing' | ||
| import classes from './Card.module.css' | ||
|
|
||
| const TestIcon = () => <svg data-testid="test-icon" aria-hidden="true" /> | ||
|
|
||
| describe('Card', () => { | ||
| implementsClassName(props => <Card {...props} />, classes.Card) | ||
|
|
||
| it('should render a Card with heading and description', () => { | ||
| render( | ||
| <Card> | ||
| <Card.Heading>Test Heading</Card.Heading> | ||
| <Card.Description>Test Description</Card.Description> | ||
| </Card>, | ||
| ) | ||
| expect(screen.getByText('Test Heading')).toBeInTheDocument() | ||
| expect(screen.getByText('Test Description')).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it('should render a heading as an h3 element', () => { | ||
| render( | ||
| <Card> | ||
| <Card.Heading>Heading</Card.Heading> | ||
| </Card>, | ||
| ) | ||
| expect(screen.getByRole('heading', {level: 3, name: 'Heading'})).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it('should render an icon', () => { | ||
| render( | ||
| <Card> | ||
| <Card.Icon icon={TestIcon} /> | ||
| <Card.Heading>With Icon</Card.Heading> | ||
| </Card>, | ||
| ) | ||
| expect(screen.getByTestId('test-icon')).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it('should render an image', () => { | ||
| render( | ||
| <Card> | ||
| <Card.Image src="https://example.com/image.png" alt="Example" /> | ||
| <Card.Heading>With Image</Card.Heading> | ||
| </Card>, | ||
| ) | ||
| const img = screen.getByRole('img', {name: 'Example'}) | ||
| expect(img).toBeInTheDocument() | ||
| expect(img).toHaveAttribute('src', 'https://example.com/image.png') | ||
| }) | ||
|
|
||
| it('should render metadata', () => { | ||
| render( | ||
| <Card> | ||
| <Card.Heading>Metadata Card</Card.Heading> | ||
| <Card.Metadata>Updated 2 hours ago</Card.Metadata> | ||
| </Card>, | ||
| ) | ||
| expect(screen.getByText('Updated 2 hours ago')).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it('should render a menu', () => { | ||
| render( | ||
| <Card> | ||
| <Card.Heading>Menu Card</Card.Heading> | ||
| <Card.Menu> | ||
| <button type="button">Options</button> | ||
| </Card.Menu> | ||
| </Card>, | ||
| ) | ||
| expect(screen.getByRole('button', {name: 'Options'})).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it('should apply edge-to-edge styling when image is provided', () => { | ||
| const {container} = render( | ||
| <Card> | ||
| <Card.Image src="https://example.com/image.png" alt="" /> | ||
| <Card.Heading>Edge to Edge</Card.Heading> | ||
| </Card>, | ||
| ) | ||
| const header = container.querySelector(`.${classes.CardHeader}`) | ||
| expect(header).toHaveClass(classes.CardHeaderEdgeToEdge) | ||
| }) | ||
|
|
||
| it('should not apply edge-to-edge styling when only icon is provided', () => { | ||
| const {container} = render( | ||
| <Card> | ||
| <Card.Icon icon={TestIcon} /> | ||
| <Card.Heading>With Icon</Card.Heading> | ||
| </Card>, | ||
| ) | ||
| const header = container.querySelector(`.${classes.CardHeader}`) | ||
| expect(header).not.toHaveClass(classes.CardHeaderEdgeToEdge) | ||
| }) | ||
|
|
||
| it('should support a custom className on the root element', () => { | ||
| const {container} = render( | ||
| <Card className="custom-class"> | ||
| <Card.Heading>Custom</Card.Heading> | ||
| </Card>, | ||
| ) | ||
| expect(container.firstChild).toHaveClass('custom-class') | ||
| expect(container.firstChild).toHaveClass(classes.Card) | ||
| }) | ||
|
|
||
| it('should forward a ref to the root element', () => { | ||
| const ref = {current: null as HTMLDivElement | null} | ||
| render( | ||
| <Card ref={ref}> | ||
| <Card.Heading>Ref Card</Card.Heading> | ||
| </Card>, | ||
| ) | ||
| expect(ref.current).toBeInstanceOf(HTMLDivElement) | ||
| }) | ||
| }) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.CardDescriptiononly resetsmargin-bottom, leaving the default<p>top margin in place, which can create unintended extra spacing (especially since.CardContentalready usesgap). Consider resetting the full margin (e.g.margin: 0) to keep spacing controlled by layout gaps.