BlueprintCard: Make hand cursor appear on hover

This commit fixes an issue where if both isClickable and isSelectable
are passed to a card in Patternfly 6, the hand cursor no longer appears
on hover and it is not obvious the card is clickable.

Co-Author: Gianluca Zuccarelli <gzuccare@redhat.com>
This commit is contained in:
Lucas Garfield 2025-06-19 10:44:21 -05:00 committed by Klara Simickova
parent bf952c5c7a
commit 5d6c6dc58b
5 changed files with 32 additions and 32 deletions

View file

@ -16,7 +16,9 @@ export const createBlueprint = async (
await page.getByRole('button', { name: 'Close' }).first().click();
await page.getByRole('button', { name: 'Create blueprint' }).click();
await page.getByRole('textbox', { name: 'Search input' }).fill(blueprintName);
await page.getByTestId('blueprint-card').getByText(blueprintName).click();
// the clickable blueprint cards are a bit awkward, so use the
// button's id instead
await page.locator(`button[id="${blueprintName}"]`).click();
};
/**
@ -91,10 +93,10 @@ export const deleteBlueprint = async (page: Page, blueprintName: string) => {
} catch (error) {
// If the No BP heading was not found, it means the blueprint (possibly) was created -> continue with deletion
}
await frame
.getByTestId('blueprint-card')
.getByText(blueprintName)
.click();
// the clickable blueprint cards are a bit awkward, so use the
// button's id instead
await frame.locator(`button[id="${blueprintName}"]`).click();
await frame.getByRole('button', { name: 'Menu toggle' }).click();
await frame.getByRole('menuitem', { name: 'Delete blueprint' }).click();
await frame.getByRole('button', { name: 'Delete' }).click();

View file

@ -100,7 +100,9 @@ test.describe.serial('test', () => {
await frame
.getByRole('textbox', { name: 'Search input' })
.fill(blueprintName);
await frame.getByText(blueprintName, { exact: true }).first().click();
// the clickable blueprint cards are a bit awkward, so use the
// button's id instead
await frame.locator(`button[id="${blueprintName}"]`).click();
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
await frame.getByRole('button', { name: 'Additional packages' }).click();
@ -134,7 +136,9 @@ test.describe.serial('test', () => {
await frame
.getByRole('textbox', { name: 'Search input' })
.fill(blueprintName);
await frame.getByText(blueprintName, { exact: true }).first().click();
// the clickable blueprint cards are a bit awkward, so use the
// button's id instead
await frame.locator(`button[id="${blueprintName}"]`).click();
await frame.getByTestId('blueprint-build-image-menu-option').click();
// make sure the image is present
@ -154,7 +158,9 @@ test.describe.serial('test', () => {
await frame
.getByRole('textbox', { name: 'Search input' })
.fill(blueprintName);
await frame.getByText(blueprintName, { exact: true }).first().click();
// the clickable blueprint cards are a bit awkward, so use the
// button's id instead
await frame.locator(`button[id="${blueprintName}"]`).click();
await frame.getByTestId('blueprint-action-menu-toggle').click();
await frame.getByRole('menuitem', { name: 'Delete blueprint' }).click();
await frame.getByRole('button', { name: 'Delete' }).click();

View file

@ -33,8 +33,7 @@ const BlueprintCard = ({ blueprint }: blueprintProps) => {
return (
<>
<Card
isSelected={blueprint.id === selectedBlueprintId}
isSelectable
isClicked={blueprint.id === selectedBlueprintId}
data-testid={`blueprint-card`}
isCompact
isClickable
@ -43,11 +42,12 @@ const BlueprintCard = ({ blueprint }: blueprintProps) => {
<CardHeader
data-testid={blueprint.id}
selectableActions={{
name: blueprint.id,
selectableActionId: blueprint.id,
selectableActionAriaLabel: blueprint.id,
name: blueprint.name,
// use the name rather than the id. This helps us
// chose the correct item in the playwright tests
selectableActionId: blueprint.name,
selectableActionAriaLabel: blueprint.name,
onChange: () => dispatch(setBlueprintId(blueprint.id)),
isHidden: true, // hide the card's checkbox
}}
>
<CardTitle>

View file

@ -36,7 +36,7 @@ type TargetEnvironmentCardProps = {
title: string;
imageSrc: string;
imageAlt: string;
isSelected: boolean;
isClicked: boolean;
isDisabled?: boolean;
testId: string;
handleOnClick: () => void;
@ -49,7 +49,7 @@ const TargetEnvironmentCard = ({
imageAlt,
handleOnClick,
onMouseEnter,
isSelected,
isClicked,
isDisabled = false,
testId,
}: TargetEnvironmentCardProps) => {
@ -58,26 +58,18 @@ const TargetEnvironmentCard = ({
data-testid={testId}
style={{ textAlign: 'center' } as React.CSSProperties}
onMouseUp={onMouseEnter}
onClick={handleOnClick}
isSelected={isSelected}
isClicked={isClicked}
isDisabled={isDisabled}
isSelectable
isClickable
isLarge
onClick={handleOnClick}
>
<CardHeader
selectableActions={{
name: title,
selectableActionId: title.toLowerCase(),
selectableActionAriaLabel: title.toLowerCase(),
// we need to give the `selectableActions` an
// onChange handler since the card actions use
// checkboxes to handle selectable cards.
// This workaround reduces noise in the test
// output
onChange: () => {},
isChecked: isSelected,
isHidden: true, // hide the card's checkbox
onClickAction: handleOnClick,
}}
>
<Flex direction={{ default: 'column' }}>
@ -165,7 +157,7 @@ const TargetEnvironment = () => {
imageAlt="Amazon Web Services logo"
handleOnClick={() => handleToggleEnvironment('aws')}
onMouseEnter={() => prefetchSources({ provider: 'aws' })}
isSelected={environments.includes('aws')}
isClicked={environments.includes('aws')}
/>
)}
{supportedEnvironments?.includes('gcp') && (
@ -178,7 +170,7 @@ const TargetEnvironment = () => {
imageAlt="Google Cloud Platform logo"
handleOnClick={() => handleToggleEnvironment('gcp')}
onMouseEnter={() => prefetchSources({ provider: 'gcp' })}
isSelected={environments.includes('gcp')}
isClicked={environments.includes('gcp')}
/>
)}
{supportedEnvironments?.includes('azure') && (
@ -191,7 +183,7 @@ const TargetEnvironment = () => {
imageAlt="Microsoft Azure logo"
handleOnClick={() => handleToggleEnvironment('azure')}
onMouseEnter={() => prefetchSources({ provider: 'azure' })}
isSelected={environments.includes('azure')}
isClicked={environments.includes('azure')}
/>
)}
{supportedEnvironments?.includes('oci') && (
@ -203,7 +195,7 @@ const TargetEnvironment = () => {
}
imageAlt="Oracle Cloud Infrastructure logo"
handleOnClick={() => handleToggleEnvironment('oci')}
isSelected={environments.includes('oci')}
isClicked={environments.includes('oci')}
/>
)}
</Gallery>

View file

@ -24,7 +24,7 @@ const testTile = async (tile: HTMLElement) => {
tile.focus();
await waitFor(() => user.keyboard(' '));
expect(tile).toHaveClass('pf-m-selectable');
expect(tile).toHaveClass('pf-m-clickable');
};
describe('Create Image Wizard', () => {