diff --git a/.changeset/lucky-terms-sing.md b/.changeset/lucky-terms-sing.md new file mode 100644 index 00000000000..c9fc974e679 --- /dev/null +++ b/.changeset/lucky-terms-sing.md @@ -0,0 +1,5 @@ +--- +'@primer/react': minor +--- + +Card: Add `data-component` attributes to `Card` and its subcomponents (`Icon`, `Image`, `Heading`, `Description`, `Metadata`, `Menu`). Add an `as` prop (`'div' | 'section'`) so standalone Cards can render as a labelled region landmark; `as="section"` requires `aria-label` or `aria-labelledby`. `Card` now requires `children`. Also improves docs and stories. diff --git a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-colorblind-linux.png b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-colorblind-linux.png index 9d513c74652..91f0540bf99 100644 Binary files a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-colorblind-linux.png and b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-dimmed-linux.png b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-dimmed-linux.png index 692774fea4f..d527154a961 100644 Binary files a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-dimmed-linux.png and b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-dimmed-linux.png differ diff --git a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-high-contrast-linux.png b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-high-contrast-linux.png index ac8caa19164..97b47c0bc02 100644 Binary files a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-high-contrast-linux.png and b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-linux.png b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-linux.png index 9d513c74652..91f0540bf99 100644 Binary files a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-linux.png and b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-linux.png differ diff --git a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-tritanopia-linux.png b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-tritanopia-linux.png index 9d513c74652..91f0540bf99 100644 Binary files a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-tritanopia-linux.png and b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-dark-tritanopia-linux.png differ diff --git a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-colorblind-linux.png b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-colorblind-linux.png index 171b4a01008..5be730c7196 100644 Binary files a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-colorblind-linux.png and b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-colorblind-linux.png differ diff --git a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-high-contrast-linux.png b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-high-contrast-linux.png index 501b37ef7e1..65f7e72ad59 100644 Binary files a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-high-contrast-linux.png and b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-high-contrast-linux.png differ diff --git a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-linux.png b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-linux.png index 171b4a01008..5be730c7196 100644 Binary files a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-linux.png and b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-linux.png differ diff --git a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-tritanopia-linux.png b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-tritanopia-linux.png index 171b4a01008..5be730c7196 100644 Binary files a/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-tritanopia-linux.png and b/.playwright/snapshots/components/Card.test.ts-snapshots/Card-Default-light-tritanopia-linux.png differ diff --git a/packages/react/src/Card/Card.docs.json b/packages/react/src/Card/Card.docs.json index 60805a2a823..12e1a5d1ae9 100644 --- a/packages/react/src/Card/Card.docs.json +++ b/packages/react/src/Card/Card.docs.json @@ -13,9 +13,27 @@ }, { "id": "experimental-components-card-features--with-metadata" + }, + { + "id": "experimental-components-card-features--with-menu" + }, + { + "id": "experimental-components-card-features--standalone-section" + }, + { + "id": "experimental-components-card-features--in-list" + }, + { + "id": "experimental-components-card-features--interactive-content" } ], "props": [ + { + "name": "children", + "type": "React.ReactNode", + "required": true, + "description": "The contents of the card. Provide either `Card.*` subcomponents (for example `Card.Heading`, `Card.Description`, `Card.Metadata`) or any custom content. A card with no children will not render." + }, { "name": "className", "type": "string", @@ -32,6 +50,12 @@ "type": "'medium' | 'large'", "defaultValue": "'large'", "description": "Controls the border radius of the Card." + }, + { + "name": "as", + "type": "'div' | 'section'", + "defaultValue": "'div'", + "description": "The HTML element to render. Use `'section'` for **standalone** Cards (not inside a list of cards) so screen readers announce the Card as a labelled region. When `Card.Heading` is present, `aria-labelledby` is automatically wired. Use the default `'div'` when the Card is inside an `
` element so should be flowing text content." + } + ] }, { "name": "Card.Menu", - "props": [] + "props": [ + { + "name": "children", + "type": "React.ReactNode", + "required": true, + "description": "Interactive control for the top-right corner of the card." + } + ] }, { "name": "Card.Metadata", - "props": [] + "props": [ + { + "name": "children", + "type": "React.ReactNode", + "required": true, + "description": "Metadata content for the bottom of the card. Accepts any content: plain text, icons, or other Primer components." + } + ] } ] } diff --git a/packages/react/src/Card/Card.features.stories.tsx b/packages/react/src/Card/Card.features.stories.tsx index 67e664489c8..6e6f93d38be 100644 --- a/packages/react/src/Card/Card.features.stories.tsx +++ b/packages/react/src/Card/Card.features.stories.tsx @@ -1,56 +1,164 @@ import type {Meta} from '@storybook/react-vite' -import {RepoIcon, StarIcon} from '@primer/octicons-react' +import {KebabHorizontalIcon, RepoIcon, RepoForkedIcon, StarIcon} from '@primer/octicons-react' +import {ActionList, ActionMenu, Button, IconButton, VisuallyHidden} from '..' import {Card} from './index' +import classes from './Card.stories.module.css' const meta = { title: 'Experimental/Components/Card/Features', component: Card, + decorators: [ + Story => ( +
This card uses arbitrary custom content instead of the built-in subcomponents.
+This card uses arbitrary custom content instead of the built-in subcomponents.
-Custom
+`, so keep it to flowing text.
+ */
children: React.ReactNode
}
@@ -60,17 +72,40 @@ type ImageProps = React.ComponentPropsWithoutRef<'img'> & {
}
type MenuProps = {
+ /** Interactive control for the top-right corner of the card. */
children: React.ReactNode
}
type MetadataProps = React.ComponentPropsWithoutRef<'div'> & {
+ /**
+ * Metadata row at the bottom of the card. Any content works: text, icons,
+ * a `Label`, an `Octicon`.
+ */
children: React.ReactNode
}
-const CardImpl = forwardRef
+
{children}
+function CardImage({src, alt = '', className, ...rest}: ImageProps) {
+ return (
+
+ )
}
CardImage.displayName = 'Card.Image'
+/**
+ * Heading shown at the top of a Card.
+ *
+ * When the parent Card uses `as="section"`, the heading's `id` is
+ * automatically wired to the section's `aria-labelledby`.
+ */
const CardHeading = forwardRef