diff --git a/.changeset/witty-ants-cover.md b/.changeset/witty-ants-cover.md new file mode 100644 index 0000000000..7f499a88ae --- /dev/null +++ b/.changeset/witty-ants-cover.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home': patch +--- + +Allow specifying static widgets to custom home page diff --git a/packages/app/src/components/home/HomePage.tsx b/packages/app/src/components/home/HomePage.tsx index aa3739728e..a8ddc0e0e2 100644 --- a/packages/app/src/components/home/HomePage.tsx +++ b/packages/app/src/components/home/HomePage.tsx @@ -15,14 +15,14 @@ */ import { - HomePageRandomJoke, - WelcomeTitle, - HeaderWorldClock, ClockConfig, - HomePageStarredEntities, CustomHomepageGrid, - HomePageToolkit, + HeaderWorldClock, HomePageCompanyLogo, + HomePageRandomJoke, + HomePageStarredEntities, + HomePageToolkit, + WelcomeTitle, } from '@backstage/plugin-home'; import { Content, Header, Page } from '@backstage/core-components'; import { HomePageSearchBar } from '@backstage/plugin-search'; @@ -64,6 +64,9 @@ const defaultConfig = [ y: 0, width: 12, height: 1, + movable: false, + resizable: false, + deletable: false, }, { component: 'WelcomeTitle', @@ -77,7 +80,7 @@ const defaultConfig = [ x: 0, y: 2, width: 12, - height: 1, + height: 2, }, ]; diff --git a/plugins/home/README.md b/plugins/home/README.md index 57c2a4d243..e147cdfbdf 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -233,6 +233,9 @@ const defaultConfig = [ y: 0, width: 12, height: 1, + movable: true, + resizable: false, + deletable: false, }, ]; diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index 32c1c0eb9d..0499209013 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -147,6 +147,9 @@ export type LayoutConfiguration = { y: number; width: number; height: number; + movable?: boolean; + deletable?: boolean; + resizable?: boolean; }; // @public @deprecated (undocumented) diff --git a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx index f5ee8f3ddc..88c3273ff3 100644 --- a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx +++ b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { ReactNode, useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { Layout, Layouts, Responsive, WidthProvider } from 'react-grid-layout'; import { ElementCollection, @@ -40,6 +40,7 @@ import { WidgetSettingsOverlay } from './WidgetSettingsOverlay'; import { AddWidgetDialog } from './AddWidgetDialog'; import { CustomHomepageButtons } from './CustomHomepageButtons'; import { + CustomHomepageGridProps, CustomHomepageGridStateV1, CustomHomepageGridStateV1Schema, GridWidget, @@ -158,6 +159,9 @@ const convertConfigToDefaultWidgets = ( isResizable: false, }, settings: {}, + movable: conf.movable, + deletable: conf.deletable, + resizable: conf.resizable, }; }); return compact(ret); @@ -190,82 +194,6 @@ const availableWidgetsFilter = (elements: ElementCollection) => { }); }; -/** - * Breakpoint options for - * - * @public - */ -export type Breakpoint = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; - -/** - * Props customizing the component. - * - * @public - */ -export type CustomHomepageGridProps = { - /** - * Children contain all widgets user can configure on their own homepage. - */ - children?: ReactNode; - /** - * Default layout for the homepage before users have modified it. - */ - config?: LayoutConfiguration[]; - /** - * Height of grid row in pixels. - * @defaultValue 60 - */ - rowHeight?: number; - /** - * Screen width in pixels for different breakpoints. - * @defaultValue theme breakpoints - */ - breakpoints?: Record; - /** - * Number of grid columns for different breakpoints. - * @defaultValue \{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 \} - */ - cols?: Record; - /** - * Grid container padding (x, y) in pixels for all or specific breakpoints. - * @defaultValue [0, 0] - * @example [10, 10] - * @example \{ lg: [10, 10] \} - */ - containerPadding?: [number, number] | Record; - /** - * Grid container margin (x, y) in pixels for all or specific breakpoints. - * @defaultValue [0, 0] - * @example [10, 10] - * @example \{ lg: [10, 10] \} - */ - containerMargin?: [number, number] | Record; - /** - * Maximum number of rows user can have in the grid. - * @defaultValue unlimited - */ - maxRows?: number; - /** - * Custom style for grid. - */ - style?: React.CSSProperties; - /** - * Compaction type of widgets in the grid. This controls where widgets are moved in case - * they are overlapping in the grid. - */ - compactType?: 'vertical' | 'horizontal' | null; - /** - * Controls if widgets can overlap in the grid. If true, grid can be placed one over the other. - * @defaultValue false - */ - allowOverlap?: boolean; - /** - * Controls if widgets can collide with each other. If true, grid items won't change position when being dragged over. - * @defaultValue false - */ - preventCollision?: boolean; -}; - /** * A component that allows customizing components in home grid layout. * @@ -318,6 +246,9 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { isDraggable: editMode, }, settings: {}, + movable: widget.movable, + deletable: widget.deletable, + resizable: widget.resizable, }, ]); setAddWidgetDialogOpen(false); @@ -348,9 +279,11 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { setEditMode(mode); setWidgets( widgets.map(w => { + const resizable = w.resizable === false ? false : mode; + const movable = w.movable === false ? false : mode; return { ...w, - layout: { ...w.layout, isDraggable: mode, isResizable: mode }, + layout: { ...w.layout, isDraggable: movable, isResizable: resizable }, }; }), ); @@ -370,7 +303,20 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { }; const handleRestoreDefaultConfig = () => { - setWidgets(defaultLayout); + setWidgets( + defaultLayout.map(w => { + const resizable = w.resizable === false ? false : editMode; + const movable = w.movable === false ? false : editMode; + return { + ...w, + layout: { + ...w.layout, + isDraggable: movable, + isResizable: resizable, + }, + }; + }), + ); }; return ( @@ -404,7 +350,7 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { style={props.style} allowOverlap={props.allowOverlap} preventCollision={props.preventCollision} - draggableCancel=".overlayGridItem,.widgetSettingsDialog" + draggableCancel=".overlayGridItem,.widgetSettingsDialog,.disabled" containerPadding={props.containerPadding} margin={props.containerMargin} breakpoints={ @@ -435,7 +381,9 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { return (
@@ -447,6 +395,7 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { handleRemove={handleRemove} handleSettingsSave={handleSettingsSave} settings={w.settings} + deletable={w.deletable} /> )}
diff --git a/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx b/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx index 261dabb833..2c3a1d9ab9 100644 --- a/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx +++ b/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx @@ -58,10 +58,12 @@ interface WidgetSettingsOverlayProps { handleRemove: (id: string) => void; handleSettingsSave: (id: string, settings: Record) => void; settings?: Record; + deletable?: boolean; } export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => { - const { id, widget, settings, handleRemove, handleSettingsSave } = props; + const { id, widget, settings, handleRemove, handleSettingsSave, deletable } = + props; const [settingsDialogOpen, setSettingsDialogOpen] = React.useState(false); const styles = useStyles(); @@ -110,13 +112,15 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => { )} - - - handleRemove(id)}> - - - - + {deletable !== false && ( + + + handleRemove(id)}> + + + + + )} ); diff --git a/plugins/home/src/components/CustomHomepage/index.ts b/plugins/home/src/components/CustomHomepage/index.ts index eed597cca3..380fdb4b25 100644 --- a/plugins/home/src/components/CustomHomepage/index.ts +++ b/plugins/home/src/components/CustomHomepage/index.ts @@ -14,5 +14,8 @@ * limitations under the License. */ export { CustomHomepageGrid } from './CustomHomepageGrid'; -export type { CustomHomepageGridProps, Breakpoint } from './CustomHomepageGrid'; -export type { LayoutConfiguration } from './types'; +export type { + CustomHomepageGridProps, + Breakpoint, + LayoutConfiguration, +} from './types'; diff --git a/plugins/home/src/components/CustomHomepage/types.ts b/plugins/home/src/components/CustomHomepage/types.ts index 8e9c6f403e..79e94c5f03 100644 --- a/plugins/home/src/components/CustomHomepage/types.ts +++ b/plugins/home/src/components/CustomHomepage/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ReactElement } from 'react'; +import React, { ReactElement, ReactNode } from 'react'; import { Layout } from 'react-grid-layout'; import { z } from 'zod'; import { RJSFSchema, UiSchema } from '@rjsf/utils'; @@ -24,12 +24,91 @@ const RSJFTypeUiSchema: z.ZodType = z.any(); const ReactElementSchema: z.ZodType = z.any(); const LayoutSchema: z.ZodType = z.any(); +/** + * Breakpoint options for + * + * @public + */ +export type Breakpoint = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; + +/** + * Props customizing the component. + * + * @public + */ +export type CustomHomepageGridProps = { + /** + * Children contain all widgets user can configure on their own homepage. + */ + children?: ReactNode; + /** + * Default layout for the homepage before users have modified it. + */ + config?: LayoutConfiguration[]; + /** + * Height of grid row in pixels. + * @defaultValue 60 + */ + rowHeight?: number; + /** + * Screen width in pixels for different breakpoints. + * @defaultValue theme breakpoints + */ + breakpoints?: Record; + /** + * Number of grid columns for different breakpoints. + * @defaultValue \{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 \} + */ + cols?: Record; + /** + * Grid container padding (x, y) in pixels for all or specific breakpoints. + * @defaultValue [0, 0] + * @example [10, 10] + * @example \{ lg: [10, 10] \} + */ + containerPadding?: [number, number] | Record; + /** + * Grid container margin (x, y) in pixels for all or specific breakpoints. + * @defaultValue [0, 0] + * @example [10, 10] + * @example \{ lg: [10, 10] \} + */ + containerMargin?: [number, number] | Record; + /** + * Maximum number of rows user can have in the grid. + * @defaultValue unlimited + */ + maxRows?: number; + /** + * Custom style for grid. + */ + style?: React.CSSProperties; + /** + * Compaction type of widgets in the grid. This controls where widgets are moved in case + * they are overlapping in the grid. + */ + compactType?: 'vertical' | 'horizontal' | null; + /** + * Controls if widgets can overlap in the grid. If true, grid can be placed one over the other. + * @defaultValue false + */ + allowOverlap?: boolean; + /** + * Controls if widgets can collide with each other. If true, grid items won't change position when being dragged over. + * @defaultValue false + */ + preventCollision?: boolean; +}; + export const LayoutConfigurationSchema = z.object({ component: ReactElementSchema, x: z.number().nonnegative('x must be positive number'), y: z.number().nonnegative('y must be positive number'), width: z.number().positive('width must be positive number'), height: z.number().positive('height must be positive number'), + movable: z.boolean().optional(), + deletable: z.boolean().optional(), + resizable: z.boolean().optional(), }); /** @@ -43,6 +122,9 @@ export type LayoutConfiguration = { y: number; width: number; height: number; + movable?: boolean; + deletable?: boolean; + resizable?: boolean; }; export const WidgetSchema = z.object({ @@ -64,6 +146,9 @@ export const WidgetSchema = z.object({ .optional(), settingsSchema: RSJFTypeSchema.optional(), uiSchema: RSJFTypeUiSchema.optional(), + movable: z.boolean().optional(), + deletable: z.boolean().optional(), + resizable: z.boolean().optional(), }); export type Widget = z.infer; @@ -72,6 +157,9 @@ const GridWidgetSchema = z.object({ id: z.string(), layout: LayoutSchema, settings: z.record(z.string(), z.any()), + movable: z.boolean().optional(), + deletable: z.boolean().optional(), + resizable: z.boolean().optional(), }); export type GridWidget = z.infer;