1// Copyright (C) 2024 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import m from 'mithril'; 16import {SplitPanelDrawerVisibility, SplitPanel} from './split_panel'; 17import {Gate} from '../base/mithril_utils'; 18import {Button} from './button'; 19 20export interface Tab { 21 // A unique key for this tab. 22 readonly key: string; 23 24 // The title of the tab to show on the tab strip. 25 readonly title: string; 26 27 // The content of this tab to show on the tab drawer. 28 readonly content: m.Children; 29 30 // Whether we have a close button or not on the tab handle. 31 readonly hasCloseButton?: boolean; 32 33 // Called when the tab is closed via its close button or via middle click on 34 // the tab handle. 35 onClose?(): void; 36} 37 38export interface TabbedSplitPanelAttrs { 39 // The list of tabs. 40 readonly tabs: ReadonlyArray<Tab>; 41 42 // The key of the currently showing tab. 43 readonly currentTabKey?: string; 44 45 // Content to put to the left of the tabs on the split handle. 46 readonly leftHandleContent?: m.Children; 47 48 // Whether the drawer is currently visible or not (when in controlled mode). 49 readonly visibility?: SplitPanelDrawerVisibility; 50 51 // Extra classes applied to the root element. 52 readonly className?: string; 53 54 // What height should the drawer be initially? 55 readonly startingHeight?: number; 56 57 // Called when the active tab is changed. 58 onTabChange?(key: string): void; 59 60 // Called when the drawer visibility is changed. 61 onVisibilityChange?(visibility: SplitPanelDrawerVisibility): void; 62} 63 64/** 65 * An extended SplitPanel with tabs which are displayed in a tab strip along the 66 * handle, and the active tab's content in shown in the drawer. 67 */ 68export class TabbedSplitPanel 69 implements m.ClassComponent<TabbedSplitPanelAttrs> 70{ 71 private currentTabKey?: string; 72 73 view({attrs, children}: m.CVnode<TabbedSplitPanelAttrs>) { 74 const { 75 currentTabKey = this.currentTabKey, 76 onTabChange, 77 leftHandleContent: leftContent, 78 startingHeight, 79 tabs, 80 visibility, 81 onVisibilityChange, 82 className, 83 } = attrs; 84 return m( 85 SplitPanel, 86 { 87 className, 88 drawerContent: tabs.map((tab) => 89 m(Gate, {open: tab.key === currentTabKey}, tab.content), 90 ), 91 startingHeight, 92 visibility, 93 onVisibilityChange, 94 handleContent: m( 95 '.pf-tab-handle', 96 leftContent, 97 m( 98 '.pf-tab-handle__tabs', 99 tabs.map((tab) => { 100 const {key, hasCloseButton = false} = tab; 101 return m( 102 '.pf-tab-handle__tab', 103 { 104 active: currentTabKey === key, 105 key, 106 // Click tab to switch to it 107 onclick: () => { 108 onTabChange?.(tab.key); 109 this.currentTabKey = tab.key; 110 }, 111 // Middle click to close 112 onauxclick: () => { 113 tab.onClose?.(); 114 }, 115 }, 116 m('span.pf-tab-handle__tab-title', tab.title), 117 hasCloseButton && 118 m(Button, { 119 onclick: (e: MouseEvent) => { 120 tab.onClose?.(); 121 e.stopPropagation(); 122 }, 123 compact: true, 124 icon: 'close', 125 }), 126 ); 127 }), 128 ), 129 ), 130 }, 131 children, 132 ); 133 } 134} 135