diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index c820a6d4c1..57e197446c 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -118,6 +118,7 @@ export { CheckboxCard, type CheckboxCardProps } from "./control-card/checkboxCar export { RadioCard, type RadioCardProps } from "./control-card/radioCard"; export { SwitchCard, type SwitchCardProps } from "./control-card/switchCard"; export { Tab, type TabId, type TabProps } from "./tabs/tab"; +export { TabPanel, type TabPanelProps } from "./tabs/tabPanel"; // eslint-disable-next-line deprecation/deprecation export { Tabs, type TabsProps, TabsExpander, Expander } from "./tabs/tabs"; export { CompoundTag, type CompoundTagProps } from "./tag/compoundTag"; diff --git a/packages/core/src/components/tabs/tab.tsx b/packages/core/src/components/tabs/tab.tsx index af33d555c3..93241636dd 100644 --- a/packages/core/src/components/tabs/tab.tsx +++ b/packages/core/src/components/tabs/tab.tsx @@ -26,6 +26,17 @@ import type { TagProps } from "../tag/tag"; export type TabId = string | number; +export interface TabIdProps { + /** + * `id` prop of the tab title, and the `aria-labelledby` of the `TabPanel`. + */ + tabTitleId: string; + /** + * `id` prop of the `tabpanel`, and the `aria-controls` of the tab title. + */ + tabPanelId: string; +} + export interface TabProps extends Props, Omit { /** * Content of tab title, rendered in a list above the active panel. @@ -51,7 +62,7 @@ export interface TabProps extends Props, Omit React.JSX.Element); + panel?: React.JSX.Element | ((props: TabIdProps) => React.JSX.Element); /** * Space-delimited string of class names applied to tab panel container. diff --git a/packages/core/src/components/tabs/tabPanel.tsx b/packages/core/src/components/tabs/tabPanel.tsx new file mode 100644 index 0000000000..984f122b17 --- /dev/null +++ b/packages/core/src/components/tabs/tabPanel.tsx @@ -0,0 +1,63 @@ +/* + * Copyright 2024 Palantir Technologies, Inc. All rights reserved. + * + * 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 classNames from "classnames"; +import * as React from "react"; + +import { AbstractPureComponent, Classes, Utils } from "../../common"; + +import { type TabId, type TabProps } from "./tab"; +import type { TabsProps } from "./tabs"; +import { generateTabIds, type TabTitleProps } from "./tabTitle"; + +export interface TabPanelProps + extends Pick, + Pick, + Pick { + /** + * Used for setting visibility. This `TabPanel` will be visibile when `selectedTabId === id`, with proper accessibility attributes set. + */ + selectedTabId: TabId | undefined; +} + +/** + * Wraps the passed `panel`. + */ +export class TabPanel extends AbstractPureComponent { + public render() { + const { className, id, parentId, selectedTabId, panel, renderActiveTabPanelOnly } = this.props; + + const isSelected = id === selectedTabId; + + if (panel === undefined || (renderActiveTabPanelOnly && !isSelected)) { + return undefined; + } + + const { tabTitleId, tabPanelId } = generateTabIds(parentId, id); + + return ( +
+ {Utils.isFunction(panel) ? panel({ tabTitleId, tabPanelId }) : panel} +
+ ); + } +} diff --git a/packages/core/src/components/tabs/tabTitle.tsx b/packages/core/src/components/tabs/tabTitle.tsx index da56927ec8..5790d1bc94 100644 --- a/packages/core/src/components/tabs/tabTitle.tsx +++ b/packages/core/src/components/tabs/tabTitle.tsx @@ -22,7 +22,7 @@ import { DISPLAYNAME_PREFIX, removeNonHTMLProps } from "../../common/props"; import { Icon } from "../icon/icon"; import { Tag } from "../tag/tag"; -import type { TabId, TabProps } from "./tab"; +import type { TabId, TabIdProps, TabProps } from "./tab"; export interface TabTitleProps extends TabProps { /** Optional contents. */ @@ -55,18 +55,20 @@ export class TabTitle extends AbstractPureComponent { tagProps, ...htmlProps } = this.props; + const intent = selected ? Intent.PRIMARY : Intent.NONE; + const { tabPanelId, tabTitleId } = generateTabIds(parentId, id); return (