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';