blob: f32a15011f771dda27633f4de31c86d1defe8aa1 [file] [log] [blame] [edit]
// Copyright (C) 2026 The Android Open Source Project
//
// 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 m from 'mithril';
import {classNames} from '../base/classnames';
import {Gate} from '../base/mithril_utils';
import {Button} from './button';
import {Icon} from './icon';
import {Icons} from '../base/semantic_icons';
export interface TabsTab {
// Unique identifier for the tab.
readonly key: string;
// Content to display in the tab handle.
readonly title: m.Children;
// Content to display when this tab is active.
readonly content: m.Children;
// Whether to show a close button on the tab.
readonly closeButton?: boolean;
// Icon to display on the left side of the tab title.
readonly leftIcon?: string | m.Children;
}
export interface TabsAttrs {
// The tabs to display.
readonly tabs: TabsTab[];
// The currently active tab key (controlled mode).
// If not provided, the component manages its own state (uncontrolled mode).
readonly activeTabKey?: string;
// Called when a tab is clicked.
onTabChange?(key: string): void;
// Called when a tab's close button is clicked.
onTabClose?(key: string): void;
// Additional class name for the container.
readonly className?: string;
}
interface TabHandleAttrs {
readonly active?: boolean;
readonly hasCloseButton?: boolean;
readonly onClose?: () => void;
readonly onclick?: () => void;
readonly leftIcon?: string | m.Children;
}
class TabHandle implements m.ClassComponent<TabHandleAttrs> {
view({attrs, children}: m.CVnode<TabHandleAttrs>): m.Children {
const {active, hasCloseButton, onClose, onclick, leftIcon} = attrs;
const renderLeftIcon = () => {
if (leftIcon === undefined) {
return undefined;
}
const style = {alignSelf: 'center'};
if (typeof leftIcon === 'string') {
return m(Icon, {icon: leftIcon, className: 'pf-tabs__tab-icon', style});
}
return m('.pf-tabs__tab-icon', {style}, leftIcon);
};
return m(
'.pf-tabs__tab',
{
className: classNames(active && 'pf-tabs__tab--active'),
onclick,
onauxclick: () => onClose?.(),
},
renderLeftIcon(),
m('.pf-tabs__tab-title', children),
hasCloseButton &&
m(Button, {
compact: true,
icon: Icons.Close,
onclick: (e: Event) => {
e.stopPropagation();
onClose?.();
},
}),
);
}
}
export class Tabs implements m.ClassComponent<TabsAttrs> {
// Current active tab key (for uncontrolled mode).
private internalActiveTab?: string;
view({attrs}: m.CVnode<TabsAttrs>): m.Children {
const {tabs, activeTabKey, onTabChange, onTabClose, className} = attrs;
// Get active tab key (controlled or uncontrolled)
const activeKey = activeTabKey ?? this.internalActiveTab ?? tabs[0]?.key;
return m(
'.pf-tabs',
{className},
m(
'.pf-tabs__tabs',
tabs.map((tab) =>
m(
TabHandle,
{
active: tab.key === activeKey,
hasCloseButton: tab.closeButton,
leftIcon: tab.leftIcon,
onclick: () => {
this.internalActiveTab = tab.key;
onTabChange?.(tab.key);
},
onClose: () => onTabClose?.(tab.key),
},
tab.title,
),
),
),
m(
'.pf-tabs__content',
tabs.map((tab) =>
m(Gate, {key: tab.key, open: tab.key === activeKey}, tab.content),
),
),
);
}
}