+
+ 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,39 +292,6 @@ export const BgOnProviders = meta.story({
),
});
-export const InteractiveWithScrollableBody = meta.story({
- render: () => (
- alert('Card pressed')}
- label="View details"
- >
-
- Scrollable Interactive Card
-
-
-
- This is the first paragraph of a long body text that demonstrates how
- the Card component handles extensive content. The card should adjust
- accordingly to display all the text properly while maintaining its
- structure.
-
-
- Here's a second paragraph that adds more content to our card body.
- Having multiple paragraphs helps to visualize how spacing works within
- the card component.
-
-
- This third paragraph continues to add more text to ensure we have a
- proper demonstration of a card with significant content. This makes it
- easier to test scrolling behavior and overall layout when content
- exceeds the initial view.
-
-
-
- ),
-});
-
export const Interactive = meta.story({
render: () => (
-
- Link Card
-
+ Link Card
This card navigates to a URL when clicked. The entire card surface
acts as a link.
-
-
- Opens backstage.io
-
-
+ Opens backstage.io
),
});
diff --git a/packages/ui/src/components/Card/Card.tsx b/packages/ui/src/components/Card/Card.tsx
index 2a3e0f5124..b948e19df9 100644
--- a/packages/ui/src/components/Card/Card.tsx
+++ b/packages/ui/src/components/Card/Card.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { forwardRef, useCallback } from 'react';
+import { forwardRef } from 'react';
import { Button as RAButton, Link as RALink } from 'react-aria-components';
import { useDefinition } from '../../hooks/useDefinition';
import {
@@ -43,32 +43,10 @@ export const Card = forwardRef((props, ref) => {
props as CardOwnProps &
Omit, 'onPress'>,
);
- const { classes, children, onPress, href, label } = ownProps;
+ const { classes, children, onPress, href, label, target, rel, download } =
+ ownProps;
const isInteractive = !!(onPress || href);
- // CardBody sits above the overlay (z-index: 2) so that scroll events reach
- // its overflow container. As a result, text clicks inside CardBody bypass
- // the overlay and bubble up to here instead. We fire the card action only
- // when the click did not originate from a nested interactive element.
- const { onClick: userOnClick, ...restPropsWithoutClick } =
- restProps as React.HTMLAttributes;
-
- const handleContainerClick = useCallback(
- (e: React.MouseEvent) => {
- userOnClick?.(e);
- if (!isInteractive) return;
- const target = e.target as HTMLElement;
- if (
- target.closest?.(
- 'button, a[href], input, select, textarea, [role="button"], [role="link"]',
- )
- )
- return;
- onPress?.();
- },
- [userOnClick, isInteractive, onPress],
- );
-
return (
((props, ref) => {
className={classes.root}
data-interactive={isInteractive || undefined}
{...dataAttributes}
- {...restPropsWithoutClick}
- onClick={handleContainerClick}
+ {...restProps}
>
{href && (
e.stopPropagation()}
/>
)}
{onPress && !href && (
@@ -92,7 +71,6 @@ export const Card = forwardRef((props, ref) => {
className={classes.overlay}
onPress={onPress}
aria-label={label}
- onClick={e => e.stopPropagation()}
/>
)}
{children}
diff --git a/packages/ui/src/components/Card/definition.ts b/packages/ui/src/components/Card/definition.ts
index 814b42aa9d..3408828a4d 100644
--- a/packages/ui/src/components/Card/definition.ts
+++ b/packages/ui/src/components/Card/definition.ts
@@ -39,6 +39,9 @@ export const CardDefinition = defineComponent()({
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 a00e0b797f..f70f4b7c3b 100644
--- a/packages/ui/src/components/Card/types.ts
+++ b/packages/ui/src/components/Card/types.ts
@@ -26,6 +26,9 @@ export type CardOwnProps = {
onPress?: () => void;
href?: string;
label?: string;
+ target?: string;
+ rel?: string;
+ download?: boolean | string;
};
/** @public */
@@ -46,7 +49,9 @@ export type CardLinkVariant = {
href: string;
onPress?: never;
/** Accessible label announced by screen readers for the interactive card. */
- label?: string;
+ label: string;
+ /** Specifies where to open the linked URL (e.g. `_blank` for a new tab). */
+ target?: string;
};
/** @public */