diff --git a/.changeset/three-rivers-remain.md b/.changeset/three-rivers-remain.md index 8fa3893e28..d6bfeba093 100644 --- a/.changeset/three-rivers-remain.md +++ b/.changeset/three-rivers-remain.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-user-settings': minor +'@backstage/plugin-user-settings': patch --- Added the ability to render extra setting tabs diff --git a/plugins/user-settings/api-report.md b/plugins/user-settings/api-report.md index e95d0d9fe3..b2b57507aa 100644 --- a/plugins/user-settings/api-report.md +++ b/plugins/user-settings/api-report.md @@ -9,7 +9,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { ProfileInfo } from '@backstage/core-plugin-api'; -import { default as React_2 } from 'react'; +import { PropsWithChildren } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { SessionApi } from '@backstage/core-plugin-api'; @@ -44,6 +44,11 @@ export const Router: ({ providerSettings }: Props) => JSX.Element; // @public (undocumented) export const Settings: (props: SettingsProps) => JSX.Element; +// Warning: (ae-missing-release-tag) "USER_SETTINGS_TAB_KEY" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const USER_SETTINGS_TAB_KEY = 'user-settings.tab'; + // Warning: (ae-missing-release-tag) "UserSettingsAppearanceCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -109,11 +114,16 @@ export const UserSettingsProfileCard: () => JSX.Element; // @public (undocumented) export const UserSettingsSignInAvatar: ({ size }: Props_5) => JSX.Element; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "UserSettingsTab" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// @public +export const UserSettingsTab: (props: UserSettingsTabProps) => JSX.Element; + +// Warning: (ae-missing-release-tag) "UserSettingsTabProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const UserSettingsTab: React_2.FC; +export type UserSettingsTabProps = PropsWithChildren<{ + path: string; + title: string; +}>; // Warning: (ae-missing-release-tag) "UserSettingsThemeToggle" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -128,4 +138,8 @@ export const useUserProfile: () => { displayName: string; loading: boolean; }; + +// Warnings were encountered during analysis: +// +// src/components/UserSettingsTab/UserSettingsTab.d.ts:6:17 - (tsdoc-code-span-missing-delimiter) The code span is missing its closing backtick ``` diff --git a/plugins/user-settings/src/components/SettingsPage.tsx b/plugins/user-settings/src/components/SettingsPage.tsx index 5be5851c16..8942235fda 100644 --- a/plugins/user-settings/src/components/SettingsPage.tsx +++ b/plugins/user-settings/src/components/SettingsPage.tsx @@ -20,11 +20,13 @@ import { SidebarPinStateContext, TabbedLayout, } from '@backstage/core-components'; -import React, { useContext, ReactElement } from 'react'; +import React, { useContext } 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'; type Props = { providerSettings?: JSX.Element; @@ -34,16 +36,13 @@ export const SettingsPage = ({ providerSettings }: Props) => { const { isMobile } = useContext(SidebarPinStateContext); const outlet = useOutlet(); - const extraTabs = (React.Children.toArray(outlet?.props?.children) || - []) as Array; - const notCompliantTab = Array.from(extraTabs).some( - child => (child.type as any)?.displayName !== 'UserSettingsTab', + const tabs = useElementFilter(outlet, elements => + elements + .selectByComponentData({ + key: USER_SETTINGS_TAB_KEY, + }) + .getElements(), ); - if (notCompliantTab) { - throw new Error( - 'Invalid element passed to SettingsPage Outlet. You may only pass children of type UserSettingsTab.', - ); - } return ( @@ -62,16 +61,11 @@ export const SettingsPage = ({ providerSettings }: Props) => { - {extraTabs.map((child, i) => { - const path: string = child.props.path; - const title: string = child.props.title; - - return ( - - {child} - - ); - })} + {tabs.map((child, i) => ( + + {child} + + ))} ); diff --git a/plugins/user-settings/src/components/UserSettingsTab/UserSettingsTab.tsx b/plugins/user-settings/src/components/UserSettingsTab/UserSettingsTab.tsx index a0716ed88d..0da61feae6 100644 --- a/plugins/user-settings/src/components/UserSettingsTab/UserSettingsTab.tsx +++ b/plugins/user-settings/src/components/UserSettingsTab/UserSettingsTab.tsx @@ -13,15 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { PropsWithChildren } from 'react'; +import { attachComponentData } from '@backstage/core-plugin-api'; -interface Props { +export const USER_SETTINGS_TAB_KEY = 'user-settings.tab'; + +export type UserSettingsTabProps = PropsWithChildren<{ + /** + * The path to the tab in the settings route + * @example `/settings/advanced + */ path: string; - title: React.ReactNode; -} + /** The title of the tab. It will also reflect in the document title when the tab is active */ + title: string; +}>; -export const UserSettingsTab: React.FC = ({ children }) => { - return <>{children}; +/** + * Renders a tab inside the settings page + * @param props - Component props + * @public + */ +export const UserSettingsTab = (props: UserSettingsTabProps) => { + return <>{props.children}; }; -UserSettingsTab.displayName = 'UserSettingsTab'; +attachComponentData(UserSettingsTab, USER_SETTINGS_TAB_KEY, 'UserSettingsTab');