+
+ Header
+
+ {content}
Footer
@@ -77,7 +92,7 @@ export const WithLongBody = meta.story({
),
});
-const ListRow = ({ children }: { children: React.ReactNode }) => {
+const ListRowComponent = ({ children }: { children: React.ReactNode }) => {
return (
{
paddingInline: 'var(--bui-space-3)',
borderRadius: 'var(--bui-radius-2)',
fontSize: 'var(--bui-font-size-3)',
- marginBottom: 'var(--bui-space-1)',
}}
>
{children}
@@ -97,29 +111,76 @@ const ListRow = ({ children }: { children: React.ReactNode }) => {
);
};
-export const WithListRow = meta.story({
+const listRowContent = (
+
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+
+);
+
+export const ListRow = meta.story({
+ render: () => (
+
+ {listRowContent}
+
+ ),
+});
+
+export const ListRowHeader = meta.story({
+ render: () => (
+
+
+ Header
+
+ {listRowContent}
+
+ ),
+});
+
+export const ListRowFooter = meta.story({
+ render: () => (
+
+ {listRowContent}
+
+ Footer
+
+
+ ),
+});
+
+export const ListRowHeaderFooter = meta.story({
render: () => (
Header
- Hello world
- Hello world
- Hello world
- Hello world
- Hello world
- Hello world
- Hello world
- Hello world
- Hello world
- Hello world
- Hello world
- Hello world
- Hello world
- Hello world
- Hello world
- Hello world
+
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+ Hello world
+
Footer
@@ -231,6 +292,91 @@ export const BgOnProviders = meta.story({
),
});
+export const Interactive = meta.story({
+ render: () => (
+ alert('Card pressed')}
+ label="View component details"
+ >
+
+ Interactive Card
+
+
+
+ Click anywhere on this card to trigger the press handler. The entire
+ card surface is interactive.
+
+
+
+
+ Click to interact
+
+
+
+ ),
+});
+
+export const InteractiveAsLink = meta.story({
+ render: () => (
+
+ Link Card
+
+
+ This card navigates to a URL when clicked. The entire card surface
+ acts as a link.
+
+
+ Opens backstage.io
+
+ ),
+});
+
+export const InteractiveWithNestedButtons = meta.story({
+ render: () => (
+ alert('Card pressed')}
+ label="View plugin details"
+ >
+
+ Card with Actions
+
+
+
+ Clicking the card background triggers the card press handler. The
+ buttons below remain independently interactive.
+
+
+
+
+
+
+
+
+
+ ),
+});
+
export const CustomCardWithBox = meta.story({
render: () => (
diff --git a/packages/ui/src/components/Card/Card.tsx b/packages/ui/src/components/Card/Card.tsx
index 5eb09f2edc..2527a3a46b 100644
--- a/packages/ui/src/components/Card/Card.tsx
+++ b/packages/ui/src/components/Card/Card.tsx
@@ -15,6 +15,7 @@
*/
import { forwardRef } from 'react';
+import { Button as RAButton, Link as RALink } from 'react-aria-components';
import { useDefinition } from '../../hooks/useDefinition';
import {
CardDefinition,
@@ -40,16 +41,36 @@ export const Card = forwardRef((props, ref) => {
CardDefinition,
props,
);
- const { classes, children } = ownProps;
+ const { classes, children, onPress, href, label, target, rel, download } =
+ ownProps;
+ const isInteractive = !!(onPress || href);
return (
+ {href && (
+
+ )}
+ {onPress && !href && (
+
+ )}
{children}
);
diff --git a/packages/ui/src/components/Card/definition.ts b/packages/ui/src/components/Card/definition.ts
index 6493d01839..3408828a4d 100644
--- a/packages/ui/src/components/Card/definition.ts
+++ b/packages/ui/src/components/Card/definition.ts
@@ -31,10 +31,17 @@ export const CardDefinition = defineComponent()({
styles,
classNames: {
root: 'bui-Card',
+ overlay: 'bui-CardOverlay',
},
propDefs: {
children: {},
className: {},
+ onPress: {},
+ href: {},
+ label: {},
+ target: {},
+ rel: {},
+ download: {},
},
});
diff --git a/packages/ui/src/components/Card/types.ts b/packages/ui/src/components/Card/types.ts
index e31b2cfda2..f1e91055e9 100644
--- a/packages/ui/src/components/Card/types.ts
+++ b/packages/ui/src/components/Card/types.ts
@@ -15,11 +15,46 @@
*/
import type { ReactNode } from 'react';
+import type { ButtonProps as RAButtonProps } from 'react-aria-components';
/** @public */
-export type CardOwnProps = {
- children?: ReactNode;
- className?: string;
+export type CardBaseProps = { children?: ReactNode; className?: string };
+
+/** @public */
+export type CardButtonVariant = {
+ /** Handler called when the card is pressed. Makes the card interactive as a button. */
+ onPress: NonNullable;
+ href?: never;
+ /** Accessible label announced by screen readers for the interactive card. */
+ label: string;
+ target?: never;
+ rel?: never;
+ download?: never;
+};
+
+/** @public */
+export type CardLinkVariant = {
+ /** URL to navigate to. Makes the card interactive as a link. */
+ href: string;
+ onPress?: never;
+ /** Accessible label announced by screen readers for the interactive card. */
+ label: string;
+ /** Specifies where to open the linked URL (e.g. `_blank` for a new tab). */
+ target?: string;
+ /** Relationship between the current document and the linked URL (e.g. `noopener`). */
+ rel?: string;
+ /** Prompts the user to save the linked URL. Pass `true` for default filename or a string for a custom filename. */
+ download?: boolean | string;
+};
+
+/** @public */
+export type CardStaticVariant = {
+ onPress?: never;
+ href?: never;
+ label?: never;
+ target?: never;
+ rel?: never;
+ download?: never;
};
/**
@@ -27,9 +62,26 @@ export type CardOwnProps = {
*
* @public
*/
-export interface CardProps
- extends CardOwnProps,
- React.HTMLAttributes {}
+export type CardProps = CardBaseProps &
+ Omit, 'onPress'> &
+ (CardButtonVariant | CardLinkVariant | CardStaticVariant);
+
+/**
+ * Flat own-props shape used by the component definition system.
+ * Derived from the Card variant types so it automatically stays in sync with CardProps.
+ * @public
+ */
+export type CardOwnProps = Pick<
+ CardBaseProps & (CardButtonVariant | CardLinkVariant | CardStaticVariant),
+ | 'children'
+ | 'className'
+ | 'onPress'
+ | 'href'
+ | 'label'
+ | 'target'
+ | 'rel'
+ | 'download'
+>;
/** @public */
export type CardHeaderOwnProps = {