/* Copyright 2024 New Vector Ltd. Copyright 2019, 2020 , 2024 The Matrix.org Foundation C.I.C. Copyright 2019 New Vector Ltd Copyright 2017 Travis Ralston SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ import * as React from "react"; import classNames from "classnames"; import { _t, TranslationKey } from "../../languageHandler"; import AutoHideScrollbar from "./AutoHideScrollbar"; import { PosthogScreenTracker, ScreenName } from "../../PosthogTrackers"; import { NonEmptyArray } from "../../@types/common"; import { RovingAccessibleButton, RovingTabIndexProvider } from "../../accessibility/RovingTabIndex"; import { useWindowWidth } from "../../hooks/useWindowWidth"; /** * Represents a tab for the TabbedView. */ export class Tab { /** * Creates a new tab. * @param {string} id The tab's ID. * @param {string} label The untranslated tab label. * @param {string|JSX.Element} icon An SVG element to use for the tab icon. Can also be a string for legacy icons, in which case it is the class for the tab icon. This should be a simple mask. * @param {React.ReactNode} body The JSX for the tab container. * @param {string} screenName The screen name to report to Posthog. */ public constructor( public readonly id: T, public readonly label: TranslationKey, public readonly icon: string | JSX.Element | null, public readonly body: React.ReactNode, public readonly screenName?: ScreenName, ) {} } export function useActiveTabWithDefault( tabs: NonEmptyArray>, defaultTabID: T, initialTabID?: T, ): [T, (tabId: T) => void] { const [activeTabId, setActiveTabId] = React.useState( initialTabID && tabs.some((t) => t.id === initialTabID) ? initialTabID : defaultTabID, ); return [activeTabId, setActiveTabId]; } export enum TabLocation { LEFT = "left", TOP = "top", } interface ITabPanelProps { tab: Tab; } function domIDForTabID(tabId: string): string { return `mx_tabpanel_${tabId}`; } function TabPanel({ tab }: ITabPanelProps): JSX.Element { return (
{tab.body}
); } interface ITabLabelProps { tab: Tab; isActive: boolean; showToolip: boolean; onClick: () => void; } function TabLabel({ tab, isActive, showToolip, onClick }: ITabLabelProps): JSX.Element { const classes = classNames("mx_TabbedView_tabLabel", { mx_TabbedView_tabLabel_active: isActive, }); let tabIcon: JSX.Element | undefined; if (tab.icon) { if (typeof tab.icon === "object") { tabIcon = tab.icon; } else if (typeof tab.icon === "string") { tabIcon = ; } } const id = domIDForTabID(tab.id); const label = _t(tab.label); return ( {tabIcon} {label} ); } interface IProps { // An array of objects representign tabs that the tabbed view will display. tabs: NonEmptyArray>; // The ID of the tab to show activeTabId: T; // The location of the tabs, dictating the layout of the TabbedView. tabLocation?: TabLocation; // A callback that is called when the active tab should change onChange: (tabId: T) => void; // The screen name to report to Posthog. screenName?: ScreenName; /** * If true, the layout of the tabbed view will be responsive to the viewport size (eg, just showing icons * instead of names of tabs). * Only applies if `tabLocation === TabLocation.LEFT`. * Default: false. */ responsive?: boolean; } /** * A tabbed view component. Given objects representing content with titles, displays * them in a tabbed view where the user can select which one of the items to view at once. */ export default function TabbedView(props: IProps): JSX.Element { const tabLocation = props.tabLocation ?? TabLocation.LEFT; const getTabById = (id: T): Tab | undefined => { return props.tabs.find((tab) => tab.id === id); }; const windowWidth = useWindowWidth(); const labels = props.tabs.map((tab) => ( props.onChange(tab.id)} // This should be the same as the the CSS breakpoint at which the tab labels are hidden showToolip={windowWidth < 1024 && tabLocation == TabLocation.LEFT} /> )); const tab = getTabById(props.activeTabId); const panel = tab ? : null; const tabbedViewClasses = classNames({ mx_TabbedView: true, mx_TabbedView_tabsOnLeft: tabLocation == TabLocation.LEFT, mx_TabbedView_tabsOnTop: tabLocation == TabLocation.TOP, mx_TabbedView_responsive: props.responsive, }); const screenName = tab?.screenName ?? props.screenName; return (
{screenName && } {({ onKeyDownHandler }) => (
    {labels}
)}
{panel}
); }