Skip to content

Commit f4f76c9

Browse files
Plugin card UX improvements (#1388)
1 parent d87ba9e commit f4f76c9

7 files changed

Lines changed: 645 additions & 57 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-marketplace': patch
3+
---
4+
5+
Plugin card UX improvements:
6+
7+
- Added visual install status indicators (Installed, Disabled) on plugin cards
8+
- Enhanced hover interaction on category tags with visual feedback
9+
- Implemented long tag name truncation with ellipsis and full-name tooltips for better layout
10+
- Fixed alignment for plugin cards without tags to maintain consistent layout
11+
- Ensured plugin description text always starts at the same horizontal point for better readability
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Copyright The Backstage Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import type { MouseEvent } from 'react';
18+
import { render, screen, fireEvent } from '@testing-library/react';
19+
import { BrowserRouter } from 'react-router-dom';
20+
21+
import { CategoryLinkButton } from './CategoryLinkButton';
22+
23+
const renderCategoryLinkButton = (props: {
24+
categoryName: string;
25+
to: string;
26+
onClick?: (event: MouseEvent) => void;
27+
maxLength?: number;
28+
}) => {
29+
return render(
30+
<BrowserRouter>
31+
<CategoryLinkButton {...props} />
32+
</BrowserRouter>,
33+
);
34+
};
35+
36+
describe('CategoryLinkButton', () => {
37+
describe('Rendering', () => {
38+
it('should render category name when within default max length', () => {
39+
renderCategoryLinkButton({
40+
categoryName: 'Kubernetes',
41+
to: '/catalog?filter=spec.categories=Kubernetes',
42+
});
43+
44+
expect(screen.getByRole('button')).toHaveTextContent('Kubernetes');
45+
});
46+
47+
it('should truncate long category names', () => {
48+
renderCategoryLinkButton({
49+
categoryName: 'this-is-a-very-long-category-name-that-exceeds-limit',
50+
to: '/catalog?filter=spec.categories=this-is-a-very-long-category-name-that-exceeds-limit',
51+
});
52+
53+
expect(screen.getByRole('button')).toHaveTextContent(
54+
'this-is-a-very-long-categ...',
55+
);
56+
});
57+
58+
it('should respect custom max length', () => {
59+
renderCategoryLinkButton({
60+
categoryName: 'medium-length-category',
61+
to: '/catalog?filter=spec.categories=medium-length-category',
62+
maxLength: 10,
63+
});
64+
65+
expect(screen.getByRole('button')).toHaveTextContent('medium-len...');
66+
});
67+
68+
it('should show tooltip for truncated names', () => {
69+
renderCategoryLinkButton({
70+
categoryName: 'this-is-a-very-long-category-name-that-exceeds-limit',
71+
to: '/catalog?filter=spec.categories=this-is-a-very-long-category-name-that-exceeds-limit',
72+
});
73+
74+
// The tooltip should contain the full category name as aria-label
75+
expect(
76+
screen.getByLabelText(
77+
'this-is-a-very-long-category-name-that-exceeds-limit',
78+
),
79+
).toBeInTheDocument();
80+
});
81+
82+
it('should not show tooltip for non-truncated names', () => {
83+
renderCategoryLinkButton({
84+
categoryName: 'Kubernetes',
85+
to: '/catalog?filter=spec.categories=Kubernetes',
86+
});
87+
88+
// The tooltip should be empty for non-truncated names (empty aria-label)
89+
expect(screen.getByLabelText('')).toBeInTheDocument();
90+
});
91+
});
92+
93+
describe('Navigation', () => {
94+
it('should have correct link target', () => {
95+
renderCategoryLinkButton({
96+
categoryName: 'Kubernetes',
97+
to: '/catalog?filter=spec.categories=Kubernetes',
98+
});
99+
100+
const linkButton = screen.getByRole('button').closest('a');
101+
expect(linkButton).toHaveAttribute(
102+
'href',
103+
'/catalog?filter=spec.categories=Kubernetes',
104+
);
105+
});
106+
});
107+
108+
describe('Click handling', () => {
109+
it('should call onClick handler when provided', () => {
110+
const handleClick = jest.fn();
111+
renderCategoryLinkButton({
112+
categoryName: 'Kubernetes',
113+
to: '/catalog?filter=spec.categories=Kubernetes',
114+
onClick: handleClick,
115+
});
116+
117+
fireEvent.click(screen.getByRole('button'));
118+
expect(handleClick).toHaveBeenCalledTimes(1);
119+
});
120+
121+
it('should work without onClick handler', () => {
122+
renderCategoryLinkButton({
123+
categoryName: 'Kubernetes',
124+
to: '/catalog?filter=spec.categories=Kubernetes',
125+
});
126+
127+
// Should not throw error when clicking without onClick handler
128+
expect(() => {
129+
fireEvent.click(screen.getByRole('button'));
130+
}).not.toThrow();
131+
});
132+
});
133+
134+
describe('Styling and accessibility', () => {
135+
it('should have proper button variant', () => {
136+
renderCategoryLinkButton({
137+
categoryName: 'Kubernetes',
138+
to: '/catalog?filter=spec.categories=Kubernetes',
139+
});
140+
141+
const button = screen.getByRole('button');
142+
expect(button).toHaveClass('MuiButton-outlined');
143+
});
144+
145+
it('should have minimum height container', () => {
146+
renderCategoryLinkButton({
147+
categoryName: 'Kubernetes',
148+
to: '/catalog?filter=spec.categories=Kubernetes',
149+
});
150+
151+
const container = screen.getByRole('button').parentElement;
152+
expect(container).toHaveStyle('min-height: 28px');
153+
});
154+
});
155+
});
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright The Backstage Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import type { MouseEvent } from 'react';
18+
19+
import { LinkButton } from '@backstage/core-components';
20+
21+
import Tooltip from '@mui/material/Tooltip';
22+
import { makeStyles } from '@material-ui/core';
23+
24+
import { getCategoryTagDisplayInfo } from '../utils';
25+
26+
const useStyles = makeStyles(() => ({
27+
pluginCategoryLinkButton: {
28+
fontWeight: 'normal',
29+
padding: '2px 6px',
30+
transition: 'border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out',
31+
'&:hover': {
32+
borderColor: '#a3a3a3',
33+
boxShadow: 'inset 0 0 0 1px #a3a3a3',
34+
},
35+
'&:focus-visible': {
36+
borderColor: '#a3a3a3',
37+
boxShadow: 'inset 0 0 0 1px #a3a3a3',
38+
outline: '2px solid #06c',
39+
outlineOffset: '2px',
40+
},
41+
},
42+
}));
43+
44+
export interface CategoryLinkButtonProps {
45+
categoryName: string;
46+
to: string;
47+
onClick?: (event: MouseEvent) => void;
48+
maxLength?: number;
49+
}
50+
51+
export const CategoryLinkButton = ({
52+
categoryName,
53+
to,
54+
onClick,
55+
maxLength = 25,
56+
}: CategoryLinkButtonProps) => {
57+
const classes = useStyles();
58+
const { displayName, tooltipTitle } = getCategoryTagDisplayInfo(
59+
categoryName,
60+
{
61+
maxLength,
62+
},
63+
);
64+
65+
return (
66+
<Tooltip title={tooltipTitle} arrow>
67+
<div style={{ display: 'inline-block', minHeight: '28px' }}>
68+
<LinkButton
69+
to={to}
70+
variant="outlined"
71+
className={classes.pluginCategoryLinkButton}
72+
onClick={onClick}
73+
>
74+
{displayName}
75+
</LinkButton>
76+
</div>
77+
</Tooltip>
78+
);
79+
};

0 commit comments

Comments
 (0)