diff --git a/.changeset/happy-bags-invite.md b/.changeset/happy-bags-invite.md new file mode 100644 index 0000000000..829d7a0e49 --- /dev/null +++ b/.changeset/happy-bags-invite.md @@ -0,0 +1,9 @@ +--- +'@backstage/core-components': patch +'@backstage/app-defaults': patch +'@backstage/plugin-catalog-react': patch +'@backstage/core-app-api': patch +'@backstage/test-utils': patch +--- + +Allow custom star icons to be provided via the `star` and `unstarred` icon overrides. See how to override existing icons in the [Backstage documentation](https://backstage.io/docs/getting-started/app-custom-theme/#custom-icons). diff --git a/packages/app-defaults/src/defaults/icons.tsx b/packages/app-defaults/src/defaults/icons.tsx index 1ed5bf03c6..ebda36c131 100644 --- a/packages/app-defaults/src/defaults/icons.tsx +++ b/packages/app-defaults/src/defaults/icons.tsx @@ -36,6 +36,8 @@ import MuiPersonIcon from '@material-ui/icons/Person'; import MuiWarningIcon from '@material-ui/icons/Warning'; import MuiStorageIcon from '@material-ui/icons/Storage'; import MuiFeaturedPlayListIcon from '@material-ui/icons/FeaturedPlayList'; +import Star from '@material-ui/icons/Star'; +import StarBorder from '@material-ui/icons/StarBorder'; export const icons = { brokenImage: MuiBrokenImageIcon as IconComponent, @@ -62,4 +64,6 @@ export const icons = { 'kind:template': MuiFeaturedPlayListIcon as IconComponent, user: MuiPersonIcon as IconComponent, warning: MuiWarningIcon as IconComponent, + star: Star as IconComponent, + unstarred: StarBorder as IconComponent, }; diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 8b8cc991c8..3d2c44a148 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -196,6 +196,8 @@ export type AppIcons = { techdocs: IconComponent; user: IconComponent; warning: IconComponent; + star: IconComponent; + unstarred: IconComponent; }; // @public diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 9d2c77292c..513063e974 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -115,6 +115,8 @@ export type AppIcons = { techdocs: IconComponent; user: IconComponent; warning: IconComponent; + star: IconComponent; + unstarred: IconComponent; }; /** diff --git a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx index fe4770a08c..18846e0f21 100644 --- a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx +++ b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx @@ -76,7 +76,7 @@ describe('BackwardsCompatProvider', () => { expect(screen.getByTestId('ctx').textContent).toMatchInlineSnapshot(` "plugins: test, app components: NotFoundErrorPage, BootErrorPage, Progress, Router, ErrorBoundaryFallback - icons: brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, user, warning" + icons: brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, user, warning, star, unstarred" `); }); @@ -120,7 +120,7 @@ describe('ForwardsCompatProvider', () => { expect(screen.getByTestId('ctx').textContent).toMatchInlineSnapshot(` "components: progress=true, notFoundErrorPage=true, errorBoundaryFallback=true - icons: kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, user, warning" + icons: kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, user, warning, star, unstarred" `); }); diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 8062278c68..ebcc053648 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -1205,6 +1205,9 @@ export function SimpleStepperStep( // @public (undocumented) export type SimpleStepperStepClassKey = 'end'; +// @public (undocumented) +export function StarIcon(props: IconComponentProps): React_2.JSX.Element; + // Warning: (ae-missing-release-tag) "StatusAborted" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1474,6 +1477,9 @@ export function TrendLine( }, ): React_2.JSX.Element | null; +// @public (undocumented) +export function UnstarredIcon(props: IconComponentProps): React_2.JSX.Element; + // @public export function useContent(): { focusContent: () => void; diff --git a/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.test.tsx b/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.test.tsx index c5ca4c33f2..66b420b467 100644 --- a/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.test.tsx +++ b/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.test.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { render } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; import { FavoriteToggle } from './FavoriteToggle'; import React from 'react'; import userEvent from '@testing-library/user-event'; @@ -33,20 +33,22 @@ describe('', () => { }); it('renders with valid props', async () => { - const { getByRole } = render(); + const { getByRole } = await renderInTestApp(); expect(getByRole('button', { name: props.title })).toBeInTheDocument(); }); it('should return inverted value on toggle', async () => { - const { getByRole } = render(); + const { getByRole } = await renderInTestApp(); await userEvent.click(getByRole('button', { name: props.title })); expect(onToggle).toHaveBeenCalledWith(!props.isFavorite); }); it('should show accessible tooltip', async () => { - const { findByRole, getByRole } = render(); + const { findByRole, getByRole } = await renderInTestApp( + , + ); await userEvent.hover(getByRole('button', { name: props.title })); diff --git a/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.tsx b/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.tsx index 65124ab05c..8a517d27ea 100644 --- a/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.tsx +++ b/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.tsx @@ -16,9 +16,9 @@ import React, { ComponentProps } from 'react'; import IconButton from '@material-ui/core/IconButton'; import Tooltip from '@material-ui/core/Tooltip'; +import Typography from '@material-ui/core/Typography'; import { Theme, makeStyles } from '@material-ui/core/styles'; -import Star from '@material-ui/icons/Star'; -import StarBorder from '@material-ui/icons/StarBorder'; +import { StarIcon, UnstarredIcon } from '../../icons'; const useStyles = makeStyles( theme => ({ @@ -50,10 +50,13 @@ export function FavoriteToggleIcon(props: { isFavorite: boolean }) { const { isFavorite } = props; const classes = useStyles(); - return isFavorite ? ( - - ) : ( - + return ( + + {isFavorite ? : } + ); } diff --git a/packages/core-components/src/icons/icons.tsx b/packages/core-components/src/icons/icons.tsx index ca3b6fa76a..bd1bf9e86e 100644 --- a/packages/core-components/src/icons/icons.tsx +++ b/packages/core-components/src/icons/icons.tsx @@ -95,3 +95,11 @@ export function UserIcon(props: IconComponentProps) { export function WarningIcon(props: IconComponentProps) { return ; } +/** @public */ +export function StarIcon(props: IconComponentProps) { + return ; +} +/** @public */ +export function UnstarredIcon(props: IconComponentProps) { + return ; +} diff --git a/packages/test-utils/src/testUtils/appWrappers.tsx b/packages/test-utils/src/testUtils/appWrappers.tsx index 3a55389b74..6f9361f39b 100644 --- a/packages/test-utils/src/testUtils/appWrappers.tsx +++ b/packages/test-utils/src/testUtils/appWrappers.tsx @@ -63,6 +63,8 @@ const mockIcons = { help: MockIcon, user: MockIcon, warning: MockIcon, + star: MockIcon, + unstarred: MockIcon, }; const ErrorBoundaryFallback = ({ error }: { error: Error }) => { diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index c6b515aa67..f572cec814 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -28,7 +28,7 @@ import MenuItem from '@material-ui/core/MenuItem'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import SettingsIcon from '@material-ui/icons/Settings'; -import StarIcon from '@material-ui/icons/Star'; +import { StarIcon } from '@backstage/core-components'; import React, { Fragment, useEffect, useMemo, useState } from 'react'; import { EntityUserFilter } from '../../filters'; import { useEntityList } from '../../hooks';