grafana_bak/public/app/features/profile/UserProfileEditTabs.tsx
2025-04-01 10:38:02 +09:00

112 lines
3.3 KiB
TypeScript

import React, { type ComponentType, Fragment, type ReactElement, useCallback, useMemo } from 'react';
import { type ComponentTypeWithExtensionMeta, type UrlQueryValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Stack, Tab, TabContent, TabsBar } from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { t } from 'app/core/internationalization';
const TAB_QUERY_PARAM = 'tab';
const GENERAL_SETTINGS_TAB = 'general';
type Props = {
children?: React.ReactNode;
components: ComponentTypeWithExtensionMeta[];
};
export function UserProfileEditTabs(props: Props): ReactElement {
const { children, components } = props;
const tabsById = useTabInfoById(components, children);
const [activeTab, setActiveTab] = useActiveTab(tabsById);
const showTabs = components.length > 0;
if (showTabs === false) {
return <>{children}</>;
}
return (
<div data-testid={selectors.components.UserProfile.extensionPointTabs}>
<Stack direction="column" gap={2}>
<TabsBar>
{Object.values(tabsById).map(({ tabId, title }) => {
return (
<Tab
key={tabId}
label={title}
active={activeTab?.tabId === tabId}
onChangeTab={() => setActiveTab(tabId)}
data-testid={selectors.components.UserProfile.extensionPointTab(tabId)}
/>
);
})}
</TabsBar>
<TabContent>
{Boolean(activeTab) && (
<Fragment key={activeTab?.tabId}>
{activeTab?.components.map((Component, index) => <Component key={`${activeTab?.tabId}-${index}`} />)}
</Fragment>
)}
</TabContent>
</Stack>
</div>
);
}
type TabInfo = {
title: string;
tabId: string;
components: ComponentType[];
};
function useTabInfoById(components: Props['components'], general: React.ReactNode): Record<string, TabInfo> {
return useMemo(() => {
const tabs: Record<string, TabInfo> = {
[GENERAL_SETTINGS_TAB]: {
title: t('user-profile.tabs.general', 'General'),
tabId: GENERAL_SETTINGS_TAB,
components: [() => <>{general}</>],
},
};
return components.reduce((acc, component) => {
const { title } = component.meta;
const tabId = convertTitleToTabId(title);
if (!acc[tabId]) {
acc[tabId] = {
title,
tabId,
components: [],
};
}
acc[tabId].components.push(component);
return acc;
}, tabs);
}, [components, general]);
}
function useActiveTab(tabs: Record<string, TabInfo>): [TabInfo | undefined, (tabId: string) => void] {
const [queryParams, updateQueryParams] = useQueryParams();
const activeTabId = convertQueryParamToTabId(queryParams[TAB_QUERY_PARAM]);
const activeTab = tabs[activeTabId];
const setActiveTab = useCallback(
(tabId: string) => updateQueryParams({ [TAB_QUERY_PARAM]: tabId }),
[updateQueryParams]
);
return [activeTab, setActiveTab];
}
function convertQueryParamToTabId(queryParam: UrlQueryValue) {
if (typeof queryParam !== 'string') {
return GENERAL_SETTINGS_TAB;
}
return convertTitleToTabId(queryParam);
}
function convertTitleToTabId(title: string) {
return title.toLowerCase();
}