• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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