diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx
index 2526f6740f..a21fe2ce9d 100644
--- a/packages/app/src/App.tsx
+++ b/packages/app/src/App.tsx
@@ -60,18 +60,18 @@ import { HomepageCompositionRoot } from '@backstage/plugin-home';
import { LighthousePage } from '@backstage/plugin-lighthouse';
import { NewRelicPage } from '@backstage/plugin-newrelic';
import {
- ScaffolderFieldExtensions,
- ScaffolderPage,
NextScaffolderPage,
- scaffolderPlugin,
+ ScaffolderFieldExtensions,
ScaffolderLayouts,
+ ScaffolderPage,
+ scaffolderPlugin,
} from '@backstage/plugin-scaffolder';
import { SearchPage } from '@backstage/plugin-search';
import { TechRadarPage } from '@backstage/plugin-tech-radar';
import {
TechDocsIndexPage,
- TechDocsReaderPage,
techdocsPlugin,
+ TechDocsReaderPage,
} from '@backstage/plugin-techdocs';
import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
import {
@@ -80,8 +80,10 @@ import {
TextSize,
} from '@backstage/plugin-techdocs-module-addons-contrib';
import {
+ SettingsLayout,
+ UserSettingsFeatureFlags,
+ UserSettingsGeneral,
UserSettingsPage,
- UserSettingsTab,
} from '@backstage/plugin-user-settings';
import { AdvancedSettings } from './components/advancedSettings';
import AlarmIcon from '@material-ui/icons/Alarm';
@@ -267,9 +269,17 @@ const routes = (
element={}
/>
}>
-
-
-
+
+
+
+
+
+
+
+
+
+
+
} />
} />
diff --git a/plugins/user-settings/src/components/SettingsPage.test.tsx b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx
similarity index 89%
rename from plugins/user-settings/src/components/SettingsPage.test.tsx
rename to plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx
index 01810bcc8e..24810a149e 100644
--- a/plugins/user-settings/src/components/SettingsPage.test.tsx
+++ b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx
@@ -16,16 +16,15 @@
import React from 'react';
import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
-import { SettingsPage } from './SettingsPage';
-import { UserSettingsTab } from './UserSettingsTab';
+import { DefaultSettingsPage } from './DefaultSettingsPage';
+import { UserSettingsTab } from '../UserSettingsTab';
+import { useOutlet } from 'react-router';
jest.mock('react-router', () => ({
...jest.requireActual('react-router'),
useOutlet: jest.fn().mockReturnValue(undefined),
}));
-import { useOutlet } from 'react-router';
-
describe('', () => {
beforeEach(() => {
(useOutlet as jest.Mock).mockReset();
@@ -33,7 +32,7 @@ describe('', () => {
it('should render the settings page with 3 tabs', async () => {
const { container } = await renderWithEffects(
- wrapInTestApp(),
+ wrapInTestApp(),
);
const tabs = container.querySelectorAll('[class*=MuiTabs-root] button');
@@ -48,7 +47,7 @@ describe('', () => {
);
(useOutlet as jest.Mock).mockReturnValue(advancedTabRoute);
const { container } = await renderWithEffects(
- wrapInTestApp(),
+ wrapInTestApp(),
);
const tabs = container.querySelectorAll('[class*=MuiTabs-root] button');
diff --git a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx
new file mode 100644
index 0000000000..4b65a26e5e
--- /dev/null
+++ b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 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 React from 'react';
+import { UserSettingsAuthProviders } from '../AuthProviders';
+import { UserSettingsFeatureFlags } from '../FeatureFlags';
+import { UserSettingsGeneral } from '../General';
+import { SettingsLayout } from '../SettingsLayout';
+
+/**
+ * @public
+ */
+export const DefaultSettingsPage = (props: {
+ providerSettings?: JSX.Element;
+}) => {
+ const { providerSettings } = props;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/plugins/user-settings/src/components/DefaultSettingsPage/index.ts b/plugins/user-settings/src/components/DefaultSettingsPage/index.ts
new file mode 100644
index 0000000000..34dcb9340d
--- /dev/null
+++ b/plugins/user-settings/src/components/DefaultSettingsPage/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 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.
+ */
+
+export { DefaultSettingsPage } from './DefaultSettingsPage';
diff --git a/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx b/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx
new file mode 100644
index 0000000000..cba2b2bf1f
--- /dev/null
+++ b/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2022 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 React from 'react';
+import { TabProps } from '@material-ui/core';
+import {
+ Header,
+ Page,
+ RoutedTabs,
+ useSidebarPinState,
+} from '@backstage/core-components';
+import {
+ attachComponentData,
+ useElementFilter,
+} from '@backstage/core-plugin-api';
+
+/** @public */
+export type SubRoute = {
+ path: string;
+ title: string;
+ children: JSX.Element;
+ tabProps?: TabProps;
+};
+
+const dataKey = 'plugin.explore.settingsLayoutRoute';
+
+const Route: (props: SubRoute) => null = () => null;
+attachComponentData(Route, dataKey, true);
+
+// This causes all mount points that are discovered within this route to use the path of the route itself
+attachComponentData(Route, 'core.gatherMountPoints', true);
+
+/** @public */
+export type SettingsLayoutProps = {
+ title?: string;
+ subtitle?: string;
+ children?: React.ReactNode;
+};
+
+/**
+ * @public
+ */
+export const SettingsLayout = (props: SettingsLayoutProps) => {
+ const { title, children } = props;
+ const { isMobile } = useSidebarPinState();
+
+ const routes = useElementFilter(children, elements =>
+ elements
+ .selectByComponentData({
+ key: dataKey,
+ withStrictError:
+ 'Child of SettingsLayout must be an SettingsLayout.Route',
+ })
+ .getElements()
+ .map(child => child.props),
+ );
+
+ return (
+
+ {!isMobile && }
+
+
+ );
+};
+
+SettingsLayout.Route = Route;
diff --git a/plugins/user-settings/src/components/SettingsLayout/index.ts b/plugins/user-settings/src/components/SettingsLayout/index.ts
new file mode 100644
index 0000000000..36a5633c94
--- /dev/null
+++ b/plugins/user-settings/src/components/SettingsLayout/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2022 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.
+ */
+
+export type { SettingsLayoutProps, SubRoute } from './SettingsLayout';
+export { SettingsLayout } from './SettingsLayout';
diff --git a/plugins/user-settings/src/components/SettingsPage.tsx b/plugins/user-settings/src/components/SettingsPage.tsx
index 9803bf4761..3fa1057881 100644
--- a/plugins/user-settings/src/components/SettingsPage.tsx
+++ b/plugins/user-settings/src/components/SettingsPage.tsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Backstage Authors
+ * Copyright 2022 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.
@@ -13,60 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-import {
- Header,
- Page,
- TabbedLayout,
- useSidebarPinState,
-} from '@backstage/core-components';
-import React from 'react';
import { useOutlet } from 'react-router';
-import { useElementFilter } from '@backstage/core-plugin-api';
-import { UserSettingsAuthProviders } from './AuthProviders';
-import { UserSettingsFeatureFlags } from './FeatureFlags';
-import { UserSettingsGeneral } from './General';
-import { USER_SETTINGS_TAB_KEY, UserSettingsTabProps } from './UserSettingsTab';
+import React from 'react';
+import { DefaultSettingsPage } from './DefaultSettingsPage';
-/**
- * @public
- */
-export const SettingsPage = (props: { providerSettings?: JSX.Element }) => {
- const { providerSettings } = props;
- const { isMobile } = useSidebarPinState();
+export const SettingsPage = () => {
const outlet = useOutlet();
- const tabs = useElementFilter(outlet, elements =>
- elements
- .selectByComponentData({
- key: USER_SETTINGS_TAB_KEY,
- })
- .getElements(),
- );
-
- return (
-
- {!isMobile && }
-
-
-
-
-
-
-
-
-
-
-
- {tabs.map((child, i) => (
-
- {child}
-
- ))}
-
-
- );
+ return <>{outlet || }>;
};
diff --git a/plugins/user-settings/src/components/index.ts b/plugins/user-settings/src/components/index.ts
index 8dc76ee7f1..8eb43a7a10 100644
--- a/plugins/user-settings/src/components/index.ts
+++ b/plugins/user-settings/src/components/index.ts
@@ -21,3 +21,4 @@ export * from './General';
export * from './FeatureFlags';
export { useUserProfile } from './useUserProfileInfo';
export * from './UserSettingsTab';
+export * from './SettingsLayout';