Merge branch 'master' into range-slider-component

This commit is contained in:
root
2026-03-05 14:09:58 +05:30
1360 changed files with 28424 additions and 12143 deletions
@@ -7,6 +7,8 @@ import {
CardFooter,
} from '../../../../../packages/ui/src/components/Card/Card';
import { Text } from '../../../../../packages/ui/src/components/Text/Text';
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
export const Default = () => {
return (
@@ -32,6 +34,70 @@ export const HeaderAndBody = () => {
);
};
export const InteractiveButton = () => {
return (
<Card
style={{ width: '300px' }}
onPress={() => {}}
label="View component details"
>
<CardHeader>Interactive Card</CardHeader>
<CardBody>
Click anywhere on this card to trigger the press handler.
</CardBody>
<CardFooter>
<Text variant="body-small" color="secondary">
Click to interact
</Text>
</CardFooter>
</Card>
);
};
export const InteractiveLink = () => {
return (
<Card
style={{ width: '300px' }}
href="https://backstage.io"
label="Open Backstage documentation"
>
<CardHeader>Link Card</CardHeader>
<CardBody>This card navigates to a URL when clicked.</CardBody>
<CardFooter>
<Text variant="body-small" color="secondary">
Opens backstage.io
</Text>
</CardFooter>
</Card>
);
};
export const InteractiveWithNestedButtons = () => {
return (
<Card
style={{ width: '300px' }}
onPress={() => {}}
label="View plugin details"
>
<CardHeader>Card with Actions</CardHeader>
<CardBody>
Clicking the card background triggers the card press handler. The
buttons below remain independently interactive.
</CardBody>
<CardFooter>
<Flex gap="2">
<Button size="small" variant="secondary" onPress={() => {}}>
Primary
</Button>
<Button size="small" variant="tertiary" onPress={() => {}}>
Secondary
</Button>
</Flex>
</CardFooter>
</Card>
);
};
export const WithLongBody = () => {
return (
<Card style={{ width: '300px', height: '200px' }}>
+54 -1
View File
@@ -12,8 +12,18 @@ import {
defaultSnippet,
headerAndBodySnippet,
withLongBodySnippet,
interactiveButtonSnippet,
interactiveLinkSnippet,
interactiveWithNestedButtonsSnippet,
} from './snippets';
import { Default, HeaderAndBody, WithLongBody } from './components';
import {
Default,
HeaderAndBody,
WithLongBody,
InteractiveButton,
InteractiveLink,
InteractiveWithNestedButtons,
} from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { CardDefinition } from '../../../utils/definitions';
@@ -81,6 +91,49 @@ When body content exceeds the available height, CardBody scrolls while header an
code={withLongBodySnippet}
/>
## Interactive cards
Cards can be made interactive without wrapping the entire card in a button or link — which would conflict with any interactive elements inside. Instead, a transparent overlay covers the card surface, and nested buttons and links remain independently clickable above it.
### Button
Pass `onPress` and a `label` (used as the accessible name for screen readers) to make the whole card surface pressable.
<Snippet
align="center"
py={4}
open
layout="side-by-side"
preview={<InteractiveButton />}
code={interactiveButtonSnippet}
/>
### Link
Pass `href` to make the card surface navigate to a URL. `label` is required — it provides the accessible name for the invisible overlay link read by screen readers.
<Snippet
align="center"
py={4}
open
layout="side-by-side"
preview={<InteractiveLink />}
code={interactiveLinkSnippet}
/>
### With nested buttons
Buttons and links inside the card remain independently interactive. Clicking them does not trigger the card's `onPress` handler.
<Snippet
align="center"
py={4}
open
layout="side-by-side"
preview={<InteractiveWithNestedButtons />}
code={interactiveWithNestedButtonsSnippet}
/>
<Theming definition={CardDefinition} />
<ChangelogComponent component="card" />
@@ -15,6 +15,25 @@ const optionalChildrenPropDef: Record<string, PropDef> = {
export const cardPropDefs: Record<string, PropDef> = {
...optionalChildrenPropDef,
onPress: {
type: 'enum',
values: ['() => void'],
responsive: false,
description:
'Handler called when the card is pressed. Makes the card interactive as a button. Requires label.',
},
href: {
type: 'string',
responsive: false,
description:
'URL to navigate to. Makes the card interactive as a link. Mutually exclusive with onPress.',
},
label: {
type: 'string',
responsive: false,
description:
'Accessible label announced by screen readers for the interactive overlay. Required when onPress or href is provided.',
},
...classNamePropDefs,
...stylePropDefs,
};
@@ -17,6 +17,50 @@ export const headerAndBodySnippet = `<Card style={{ width: '300px', height: '200
<CardBody>Body content without a footer</CardBody>
</Card>`;
export const interactiveButtonSnippet = `<Card
style={{ width: '300px' }}
onPress={() => console.log('Card pressed')}
label="View component details"
>
<CardHeader>Interactive Card</CardHeader>
<CardBody>Click anywhere on this card to trigger the press handler.</CardBody>
<CardFooter>Click to interact</CardFooter>
</Card>`;
export const interactiveLinkSnippet = `<Card
style={{ width: '300px' }}
href="https://backstage.io"
label="Open Backstage documentation"
>
<CardHeader>Link Card</CardHeader>
<CardBody>This card navigates to a URL when clicked.</CardBody>
<CardFooter>Opens backstage.io</CardFooter>
</Card>`;
export const interactiveWithNestedButtonsSnippet = `import { Button, Flex } from '@backstage/ui';
<Card
style={{ width: '300px' }}
onPress={() => console.log('Card pressed')}
label="View plugin details"
>
<CardHeader>Card with Actions</CardHeader>
<CardBody>
Clicking the card background triggers the card press handler.
The buttons below remain independently interactive.
</CardBody>
<CardFooter>
<Flex gap="2">
<Button size="small" variant="secondary" onPress={() => console.log('Primary')}>
Primary
</Button>
<Button size="small" variant="tertiary" onPress={() => console.log('Secondary')}>
Secondary
</Button>
</Flex>
</CardFooter>
</Card>`;
export const withLongBodySnippet = `import { Text } from '@backstage/ui';
<Card style={{ width: '300px', height: '200px' }}>
@@ -38,7 +38,7 @@ export const linkPropDefs: Record<string, PropDef> = {
'body-small',
'body-x-small',
],
default: 'body',
default: 'body-medium',
responsive: true,
description:
'Typography style. Title variants for headings, body for paragraph text.',
@@ -56,7 +56,7 @@ export const linkPropDefs: Record<string, PropDef> = {
},
color: {
type: 'enum',
values: ['primary', 'secondary', 'danger', 'warning', 'success'],
values: ['primary', 'secondary', 'danger', 'warning', 'success', 'info'],
default: 'primary',
responsive: true,
description:
+4 -4
View File
@@ -1,19 +1,19 @@
export const surfacesSnippet = `<Flex direction="column" gap="4">
<Box bg="neutral-1">
<Box bg="neutral">
<Button variant="secondary">Hello World</Button>
</Box>
<Box bg="neutral-1">
<Box bg="neutral">
<Button variant="secondary">Hello World</Button>
</Box>
</Flex>`;
export const adaptiveSnippet = `<Box bg="neutral-1">
export const adaptiveSnippet = `<Box bg="neutral">
<Card> {/* automatically set background to neutral-2 */}
<Button variant="secondary">Button with background set to neutral-3</Button>
</Card>
</Box>`;
export const customCardSnippet = `<Box bg="autoIncrement">
export const customCardSnippet = `<Box bg="neutral">
<Text>Hello World</Text>
</Box>`;