signal-desktop/ts/hooks/useTabs.tsx

99 lines
2.5 KiB
TypeScript
Raw Normal View History

2022-03-04 21:14:52 +00:00
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { KeyboardEvent } from 'react';
import React, { useState } from 'react';
import classNames from 'classnames';
import { assertDev } from '../util/assert';
2022-03-04 21:14:52 +00:00
import { getClassNamesFor } from '../util/getClassNamesFor';
type Tab = {
id: string;
label: string;
};
2022-10-11 17:59:02 +00:00
export type BaseTabsOptionsType = {
2022-03-04 21:14:52 +00:00
moduleClassName?: string;
tabs: Array<Tab>;
};
2022-10-11 17:59:02 +00:00
export type ControlledTabsOptionsType = BaseTabsOptionsType & {
selectedTab: string;
onTabChange: (selectedTab: string) => unknown;
};
export type UncontrolledTabsOptionsType = BaseTabsOptionsType & {
initialSelectedTab?: string;
onTabChange?: (selectedTab: string) => unknown;
};
export type TabsOptionsType =
| ControlledTabsOptionsType
| UncontrolledTabsOptionsType;
type TabsProps = {
2022-03-04 21:14:52 +00:00
selectedTab: string;
tabsHeaderElement: JSX.Element;
2022-10-11 17:59:02 +00:00
};
2022-03-04 21:14:52 +00:00
2022-10-11 17:59:02 +00:00
export function useTabs(options: TabsOptionsType): TabsProps {
assertDev(options.tabs.length, 'Tabs needs more than 1 tab present');
2022-03-04 21:14:52 +00:00
2022-10-11 17:59:02 +00:00
const getClassName = getClassNamesFor('Tabs', options.moduleClassName);
let selectedTab: string;
let onChange: (selectedTab: string) => void;
if ('selectedTab' in options) {
selectedTab = options.selectedTab;
onChange = options.onTabChange;
} else {
// useTabs should always be either controlled or uncontrolled.
// This is enforced by the type system.
// eslint-disable-next-line react-hooks/rules-of-hooks
const [tabState, setTabState] = useState<string>(
options.initialSelectedTab || options.tabs[0].id
);
selectedTab = tabState;
onChange = (newTab: string) => {
setTabState(newTab);
options.onTabChange?.(newTab);
};
}
2022-03-04 21:14:52 +00:00
const tabsHeaderElement = (
2023-05-04 19:34:52 +00:00
<div className={getClassName('')} data-supertab>
2022-10-11 17:59:02 +00:00
{options.tabs.map(({ id, label }) => (
2022-03-04 21:14:52 +00:00
<div
aria-selected={selectedTab === id}
2022-03-04 21:14:52 +00:00
className={classNames(
getClassName('__tab'),
selectedTab === id && getClassName('__tab--selected')
)}
key={id}
onClick={() => {
2022-10-11 17:59:02 +00:00
onChange(id);
2022-03-04 21:14:52 +00:00
}}
onKeyUp={(e: KeyboardEvent) => {
if (e.target === e.currentTarget && e.keyCode === 13) {
2022-10-11 17:59:02 +00:00
onChange(id);
2022-03-04 21:14:52 +00:00
e.preventDefault();
e.stopPropagation();
}
}}
role="tab"
tabIndex={0}
>
{label}
</div>
))}
</div>
);
return {
selectedTab,
tabsHeaderElement,
};
}