diff --git a/.changeset/dull-bottles-teach.md b/.changeset/dull-bottles-teach.md
new file mode 100644
index 0000000000..74203eb758
--- /dev/null
+++ b/.changeset/dull-bottles-teach.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-home': patch
+---
+
+Allow more customization for the CustomHomepageGrid
diff --git a/packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx b/packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx
new file mode 100644
index 0000000000..e587e52da4
--- /dev/null
+++ b/packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * 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 {
+ HomePageStarredEntities,
+ HomePageRandomJoke,
+ CustomHomepageGrid,
+} from '@backstage/plugin-home';
+import {
+ starredEntitiesApiRef,
+ MockStarredEntitiesApi,
+ entityRouteRef,
+ catalogApiRef,
+} from '@backstage/plugin-catalog-react';
+import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils';
+import { configApiRef } from '@backstage/core-plugin-api';
+import { ConfigReader } from '@backstage/config';
+import { searchApiRef } from '@backstage/plugin-search-react';
+import { HomePageSearchBar, searchPlugin } from '@backstage/plugin-search';
+import { HomePageCalendar } from '@backstage/plugin-gcalendar';
+import { MicrosoftCalendarCard } from '@backstage/plugin-microsoft-calendar';
+import React, { ComponentType } from 'react';
+
+const entities = [
+ {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: {
+ name: 'mock-starred-entity',
+ title: 'Mock Starred Entity!',
+ },
+ },
+ {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: {
+ name: 'mock-starred-entity-2',
+ title: 'Mock Starred Entity 2!',
+ },
+ },
+ {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: {
+ name: 'mock-starred-entity-3',
+ title: 'Mock Starred Entity 3!',
+ },
+ },
+ {
+ apiVersion: '1',
+ kind: 'Component',
+ metadata: {
+ name: 'mock-starred-entity-4',
+ title: 'Mock Starred Entity 4!',
+ },
+ },
+];
+
+const mockCatalogApi = {
+ getEntities: async () => ({ items: entities }),
+};
+
+const starredEntitiesApi = new MockStarredEntitiesApi();
+starredEntitiesApi.toggleStarred('component:default/example-starred-entity');
+starredEntitiesApi.toggleStarred('component:default/example-starred-entity-2');
+starredEntitiesApi.toggleStarred('component:default/example-starred-entity-3');
+starredEntitiesApi.toggleStarred('component:default/example-starred-entity-4');
+
+export default {
+ title: 'Plugins/Home/Templates',
+ decorators: [
+ (Story: ComponentType<{}>) =>
+ wrapInTestApp(
+ <>
+ Promise.resolve({ results: [] }) }],
+ [
+ configApiRef,
+ new ConfigReader({
+ backend: {
+ baseUrl: 'https://localhost:7007',
+ },
+ }),
+ ],
+ ]}
+ >
+
+
+ >,
+ {
+ mountedRoutes: {
+ '/hello-company': searchPlugin.routes.root,
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ },
+ },
+ ),
+ ],
+};
+export const CustomizableTemplate = () => {
+ // This is the default configuration that is shown to the user
+ // when first arriving to the homepage.
+ const defaultConfig = [
+ {
+ component: 'HomePageSearchBar',
+ x: 0,
+ y: 0,
+ width: 12,
+ height: 5,
+ },
+ {
+ component: 'HomePageRandomJoke',
+ x: 0,
+ y: 2,
+ width: 6,
+ height: 16,
+ },
+ {
+ component: 'HomePageStarredEntities',
+ x: 6,
+ y: 2,
+ width: 6,
+ height: 12,
+ },
+ ];
+
+ return (
+
+ // Insert the allowed widgets inside the grid. User can add, organize and
+ // remove the widgets as they want.
+
+
+
+
+
+
+ );
+};
diff --git a/plugins/home/README.md b/plugins/home/README.md
index 20b91289b1..42fbf73d18 100644
--- a/plugins/home/README.md
+++ b/plugins/home/README.md
@@ -247,4 +247,5 @@ Additionally, the API is at a very early state, so contributing with additional
### Homepage Templates
-We are hoping that we together can build up a collection of Homepage templates. We therefore put together a place where we can collect all the templates for the Home Plugin in the [storybook](https://backstage.io/storybook/?path=/story/plugins-home-templates). If you would like to contribute with a template, start by taking a look at the [DefaultTemplate storybook example to create your own](/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx), and then open a PR with your suggestion.
+We are hoping that we together can build up a collection of Homepage templates. We therefore put together a place where we can collect all the templates for the Home Plugin in the [storybook](https://backstage.io/storybook/?path=/story/plugins-home-templates).
+If you would like to contribute with a template, start by taking a look at the [DefaultTemplate storybook example](/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx) or [CustomizableTemplate storybook example](/packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx) to create your own, and then open a PR with your suggestion.
diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md
index 56b1f54331..e4ff522062 100644
--- a/plugins/home/api-report.md
+++ b/plugins/home/api-report.md
@@ -19,6 +19,9 @@ import { ReactNode } from 'react';
import { RendererProps as RendererProps_2 } from '@backstage/plugin-home-react';
import { RouteRef } from '@backstage/core-plugin-api';
+// @public
+export type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+
// @public @deprecated (undocumented)
export type CardConfig = CardConfig_2;
@@ -77,11 +80,20 @@ export const CustomHomepageGrid: (
props: CustomHomepageGridProps,
) => JSX.Element;
-// @public (undocumented)
+// @public
export type CustomHomepageGridProps = {
children?: ReactNode;
config?: LayoutConfiguration[];
rowHeight?: number;
+ breakpoints?: Record;
+ cols?: Record;
+ containerPadding?: [number, number] | Record;
+ containerMargin?: [number, number] | Record;
+ maxRows?: number;
+ style?: React_2.CSSProperties;
+ compactType?: 'vertical' | 'horizontal' | null;
+ allowOverlap?: boolean;
+ preventCollision?: boolean;
};
// @public
diff --git a/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx b/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx
index c2fdf5b979..22c88a30c7 100644
--- a/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx
+++ b/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx
@@ -20,6 +20,7 @@ import SaveIcon from '@material-ui/icons/Save';
import DeleteIcon from '@material-ui/icons/Delete';
import AddIcon from '@material-ui/icons/Add';
import EditIcon from '@material-ui/icons/Edit';
+import CancelIcon from '@material-ui/icons/Cancel';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -41,6 +42,8 @@ interface CustomHomepageButtonsProps {
clearLayout: () => void;
setAddWidgetDialogOpen: (open: boolean) => void;
changeEditMode: (mode: boolean) => void;
+ defaultConfigAvailable: boolean;
+ restoreDefault: () => void;
}
export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => {
const {
@@ -49,6 +52,8 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => {
clearLayout,
setAddWidgetDialogOpen,
changeEditMode,
+ defaultConfigAvailable,
+ restoreDefault,
} = props;
const styles = useStyles();
@@ -59,27 +64,41 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => {
variant="contained"
color="primary"
onClick={() => changeEditMode(true)}
+ size="small"
startIcon={}
>
Edit
) : (
<>
+ {defaultConfigAvailable && (
+ }
+ >
+ Restore defaults
+
+ )}
{numWidgets > 0 && (
}
>
- Clear
+ Clear all
)}