Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fab): update fab content #258

Merged
merged 1 commit into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@red-hat-developer-hub/backstage-plugin-global-floating-action-button': patch
---

update fab content
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 The Backstage Authors
* Copyright Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
GitHubIcon,
Link,
Expand Down Expand Up @@ -96,28 +97,43 @@ export const Root = ({ children }: PropsWithChildren<{}>) => {
to: '/create',
},
{
slot: Slot.BOTTOM_CENTER,
slot: Slot.BOTTOM_LEFT,
icon: <LibraryBooks />,
label: 'Api Docs',
toolTip: 'Api Docs',
to: '/api-docs',
},
{
slot: Slot.BOTTOM_LEFT,
icon: <ExtensionIcon />,
showLabel: true,
label: 'Docs',
toolTip: 'Docs',
to: '/docs',
},
{
color: 'success',
icon: <SearchIcon />,
label: 'Search',
toolTip: 'Search',
onClick: toggleModal,
},
{
color: 'success',
showLabel: true,
icon: <GitHubIcon />,
label: 'RHDH plugins',
label: 'RHDH pluginsssssssssssssss',
showLabel: true,
toolTip: 'RHDH plugins',
to: 'https://github.com/redhat-developer/rhdh-plugins',
visibleOnPaths: ['/catalog'],
},
{
color: 'success',
icon: <GitHubIcon />,
label: 'RHDH pluginsssssssssssssss',
toolTip: 'External link',
to: 'https://github.com/redhat-developer/rhdh-plugins',
visibleOnPaths: ['/catalog'],
},
{
color: 'success',
icon: <UserSettingsSignInAvatar />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ This plugin has been added to the example app in this workspace, meaning it can
to: '/create',
},
{
slot: Slot.BOTTOM_CENTER,
slot: Slot.BOTTOM_LEFT,
icon: <LibraryBooks />,
label: 'Docs',
toolTip: 'Docs',
Expand All @@ -57,9 +57,9 @@ This plugin has been added to the example app in this workspace, meaning it can

| Name | Type | Description | Notes |
| ------------------ | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| **slot** | `enum` | The position where the fab will be placed. Valid values: `PAGE_END`, `BOTTOM_CENTER`. | [optional] default to `PAGE_END`. |
| **slot** | `enum` | The position where the fab will be placed. Valid values: `PAGE_END`, `BOTTOM_LEFT`. | [optional] default to `PAGE_END`. |
| **label** | `String` | A name for your action button. | required |
| **icon** | `String`<br>`React.ReactElement` | An icon for your floating button. | optional |
| **icon** | `String`<br>`React.ReactElement` | An icon for your floating button. Recommended to use **filled** icons from the [Material Design library](https://fonts.google.com/icons) | required |
| **showLabel** | `Boolean` | To display the label next to your icon. | optional |
| **size** | `'small'`<br>`'medium'`<br>`'large'` | A name for your action button. | [optional] default to `'medium'` |
| **color** | `'default'`<br>`'error'`<br>`'info'`<br>`'inherit'`<br>`'primary'`<br>`'secondary'`<br>`'success'`<br>`'warning'` | The color of the component. It supports both default and custom theme colors, which can be added as shown in the [palette customization guide](https://mui.com/material-ui/customization/palette/#custom-colors). | [optional] default to `'default'`. |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 The Backstage Authors
* Copyright Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -29,6 +29,7 @@ import {
ContentHeader,
HeaderLabel,
SupportButton,
GitHubIcon,
} from '@backstage/core-components';
import { ExampleFetchComponent } from './ExampleFetchComponent';
import { GlobalFloatingActionButton, Slot } from '../../src';
Expand Down Expand Up @@ -77,7 +78,7 @@ export const ExampleComponent = () => (
icon: '<svg viewBox="0 0 250 300" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M200.134 0l55.555 117.514-55.555 117.518h-47.295l55.555-117.518L152.84 0h47.295zM110.08 99.836l20.056-38.092-2.29-8.868L102.847 0H55.552l48.647 102.898 5.881-3.062zm17.766 74.433l-17.333-39.034-6.314-3.101-48.647 102.898h47.295l25-52.88v-7.883z" fill="#40B4E5"/><path d="M152.842 235.032L97.287 117.514 152.842 0h47.295l-55.555 117.514 55.555 117.518h-47.295zm-97.287 0L0 117.514 55.555 0h47.296L47.295 117.514l55.556 117.518H55.555z" fill="#003764"/></svg>',
},
{
slot: Slot.BOTTOM_CENTER,
slot: Slot.BOTTOM_LEFT,
color: 'success',
icon: <AddIcon />,
label: 'Add',
Expand All @@ -86,10 +87,11 @@ export const ExampleComponent = () => (
priority: 100,
},
{
slot: Slot.BOTTOM_CENTER,
slot: Slot.BOTTOM_LEFT,
color: 'success',
label: 'Menu',
toolTip: 'Menu',
label: 'Github',
icon: <GitHubIcon />,
toolTip: 'Github',
to: 'https://github.com/xyz',
priority: 200,
visibleOnPaths: ['/test-global-floating-action'],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 The Backstage Authors
* Copyright Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 The Backstage Authors
* Copyright Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type FloatingActionButton = {
slot?: Slot;
label: string;
showLabel?: boolean;
icon?: string | React.ReactElement;
icon: string | React.ReactElement;
size?: 'small' | 'medium' | 'large';
color?:
| 'default'
Expand Down Expand Up @@ -50,7 +50,7 @@ export const globalFloatingActionButtonPlugin: BackstagePlugin<{}, {}, {}>;

// @public
export enum Slot {
BOTTOM_CENTER = 'bottom-center',
BOTTOM_LEFT = 'bottom-left',
PAGE_END = 'page-end',
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { render, screen } from '@testing-library/react';
import GitIcon from '@mui/icons-material/GitHub';
import * as React from 'react';
import { FAB } from './FAB';

jest.mock('react-router-dom', () => ({
useNavigate: jest.fn(),
useLocation: jest.fn(() => ({
pathname: '/test-path',
})),
}));

jest.mock('@backstage/core-plugin-api', () => ({
useApp: jest.fn(() => ({
getSystemIcon: jest.fn(),
})),
usetheme: jest.fn(() => ({
theme: {
transitions: {
easing: {
easeOut: 'eo',
sharp: 's',
},
},
},
})),
}));

describe('Floating Action Button', () => {
it('should render the floating action button with icon and label', () => {
render(
<FAB
actionButton={{
color: 'success',
icon: <GitIcon />,
label: 'Git repo',
showLabel: true,
to: 'https://github.com/xyz',
toolTip: 'Git',
}}
/>,
);
expect(screen.getByTestId('git-repo')).toBeInTheDocument();
expect(screen.getByTestId('GitHubIcon')).toBeInTheDocument();
expect(screen.getByText('Git repo')).toBeInTheDocument();
expect(screen.getByTestId('OpenInNewIcon')).toBeInTheDocument();
});

it('should render the floating action button with icon', () => {
render(
<FAB
actionButton={{
color: 'success',
icon: <GitIcon />,
label: 'Git repo',
to: 'https://github.com/xyz',
toolTip: 'Git',
}}
/>,
);
expect(screen.getByTestId('git-repo')).toBeInTheDocument();
expect(screen.getByTestId('GitHubIcon')).toBeInTheDocument();
expect(screen.queryByText('Git repo')).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 The Backstage Authors
* Copyright Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,50 +16,126 @@

import * as React from 'react';
import { useNavigate } from 'react-router-dom';
import { makeStyles } from '@mui/styles';
import Fab from '@mui/material/Fab';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import { FabIcon } from './FabIcon';
import { FloatingActionButton, Slot } from '../types';
import { slotOptions } from '../utils';

const useStyles = makeStyles(() => ({
openInNew: { paddingBottom: '5px', paddingTop: '3px' },
}));

const FABLabel = ({
label,
slot,
showExternalIcon,
icon,
order,
}: {
label: string;
slot: Slot;
showExternalIcon: boolean;
icon: string | React.ReactElement;
order: { externalIcon?: number; icon?: number };
}) => {
const styles = useStyles();
const marginStyle = slotOptions[slot].margin;
return (
<Typography sx={{ display: 'flex' }}>
{showExternalIcon && (
<OpenInNewIcon
className={styles.openInNew}
sx={{ ...marginStyle, order: order.externalIcon }}
/>
)}
{label && (
<Typography
sx={{
...marginStyle,
color: '#151515',
order: 2,
}}
>
{label}
</Typography>
)}
<Typography sx={{ mb: -1, order: order.icon }}>
<FabIcon icon={icon} />
</Typography>
</Typography>
);
};

export const FAB = ({
actionButton,
size,
className,
}: {
actionButton: FloatingActionButton;
size?: 'small' | 'medium' | 'large';
className?: string;
}) => {
const navigate = useNavigate();
const isExternalUri = (uri: string) => /^([a-z+.-]+):/.test(uri);
const external = isExternalUri(actionButton.to!);
const newWindow = external && !!/^https?:/.exec(actionButton.to!);
const isExternal = isExternalUri(actionButton.to!);
const newWindow = isExternal && !!/^https?:/.exec(actionButton.to!);
const navigateTo = () =>
actionButton.to && !external ? navigate(actionButton.to) : '';
actionButton.to && !isExternal ? navigate(actionButton.to) : '';
const labelText =
actionButton.label.length > 20
? `${actionButton.label.slice(0, actionButton.label.length)}...`
: actionButton.label;
const getColor = () => {
if (actionButton.color) {
return actionButton.color;
}
if (!className) {
return 'info';
}
return undefined;
};
Comment on lines +92 to +100
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just asking myself: Why are you mixing className and color here?

Couldn't we use info always as fallback?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@christoph-jerolimov As per the UX the sub-menu actions should have the secondary color (ref: --pf-t--global--background--color--secondary--default , https://www.patternfly.org/design-foundations/colors/#background-colors) by default if the color is not specified. And for the main FAB button the default color should be blue or 'info'

cc @ShiranHi


const displayOnRight =
actionButton.slot === Slot.PAGE_END || !actionButton.slot;

return (
<Tooltip
title={actionButton.toolTip}
placement={Slot.PAGE_END ? 'left' : 'right'}
placement={
slotOptions[actionButton.slot || Slot.PAGE_END].tooltipDirection
}
>
<div>
<div className={className}>
<Fab
{...(newWindow ? { target: '_blank', rel: 'noopener' } : {})}
style={{ color: '#1f1f1f' }}
variant={
actionButton.showLabel || !actionButton.icon
? 'extended'
: 'circular'
actionButton.showLabel || isExternal ? 'extended' : 'circular'
}
size={size || actionButton.size || 'medium'}
color={actionButton.color || 'default'}
color={getColor()}
aria-label={actionButton.label}
data-testid={actionButton.label
.replace(' ', '-')
.toLocaleLowerCase('en-US')}
onClick={actionButton.onClick || navigateTo}
{...(external ? { href: actionButton.to } : {})}
{...(isExternal ? { href: actionButton.to } : {})}
>
{actionButton.icon && <FabIcon icon={actionButton.icon} />}
{(actionButton.showLabel || !actionButton.icon) && (
<Typography sx={actionButton.icon ? { ml: 1 } : {}}>
{actionButton.label}
</Typography>
)}
<FABLabel
showExternalIcon={isExternal}
icon={actionButton.icon}
label={actionButton.showLabel ? labelText : ''}
order={
displayOnRight
? { externalIcon: isExternal ? 1 : -1, icon: 3 }
: { externalIcon: isExternal ? 3 : -1, icon: 1 }
}
slot={actionButton.slot || Slot.PAGE_END}
/>
</Fab>
</div>
</Tooltip>
Expand Down
Loading
Loading