1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2023 The Android Open Source Project 2*6dbdd20aSAndroid Build Coastguard Worker// 3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*6dbdd20aSAndroid Build Coastguard Worker// 7*6dbdd20aSAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*6dbdd20aSAndroid Build Coastguard Worker// 9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License. 14*6dbdd20aSAndroid Build Coastguard Worker 15*6dbdd20aSAndroid Build Coastguard Workerimport m from 'mithril'; 16*6dbdd20aSAndroid Build Coastguard Workerimport {classNames} from '../base/classnames'; 17*6dbdd20aSAndroid Build Coastguard Workerimport {hasChildren} from '../base/mithril_utils'; 18*6dbdd20aSAndroid Build Coastguard Workerimport {HTMLAttrs} from './common'; 19*6dbdd20aSAndroid Build Coastguard Workerimport {Icon} from './icon'; 20*6dbdd20aSAndroid Build Coastguard Workerimport {Popup, PopupAttrs, PopupPosition} from './popup'; 21*6dbdd20aSAndroid Build Coastguard Worker 22*6dbdd20aSAndroid Build Coastguard Workerexport interface MenuItemAttrs extends HTMLAttrs { 23*6dbdd20aSAndroid Build Coastguard Worker // Text to display on the menu button. 24*6dbdd20aSAndroid Build Coastguard Worker label: string; 25*6dbdd20aSAndroid Build Coastguard Worker // Optional left icon. 26*6dbdd20aSAndroid Build Coastguard Worker icon?: string; 27*6dbdd20aSAndroid Build Coastguard Worker // Optional right icon. 28*6dbdd20aSAndroid Build Coastguard Worker rightIcon?: string; 29*6dbdd20aSAndroid Build Coastguard Worker // Make the item appear greyed out block any interaction with it. No events 30*6dbdd20aSAndroid Build Coastguard Worker // will be fired. 31*6dbdd20aSAndroid Build Coastguard Worker // Defaults to false. 32*6dbdd20aSAndroid Build Coastguard Worker disabled?: boolean; 33*6dbdd20aSAndroid Build Coastguard Worker // Always show the button as if the "active" pseudo class were applied, which 34*6dbdd20aSAndroid Build Coastguard Worker // makes the button look permanently pressed. 35*6dbdd20aSAndroid Build Coastguard Worker // Useful for when the button represents some toggleable state, such as 36*6dbdd20aSAndroid Build Coastguard Worker // showing/hiding a popup menu. 37*6dbdd20aSAndroid Build Coastguard Worker // Defaults to false. 38*6dbdd20aSAndroid Build Coastguard Worker active?: boolean; 39*6dbdd20aSAndroid Build Coastguard Worker // If this menu item is a descendant of a popup, this setting means that 40*6dbdd20aSAndroid Build Coastguard Worker // clicking it will result in the popup being dismissed. 41*6dbdd20aSAndroid Build Coastguard Worker // Defaults to false when menuitem has children, true otherwise. 42*6dbdd20aSAndroid Build Coastguard Worker closePopupOnClick?: boolean; 43*6dbdd20aSAndroid Build Coastguard Worker} 44*6dbdd20aSAndroid Build Coastguard Worker 45*6dbdd20aSAndroid Build Coastguard Worker// An interactive menu element with an icon. 46*6dbdd20aSAndroid Build Coastguard Worker// If this node has children, a nested popup menu will be rendered. 47*6dbdd20aSAndroid Build Coastguard Workerexport class MenuItem implements m.ClassComponent<MenuItemAttrs> { 48*6dbdd20aSAndroid Build Coastguard Worker view(vnode: m.CVnode<MenuItemAttrs>): m.Children { 49*6dbdd20aSAndroid Build Coastguard Worker if (hasChildren(vnode)) { 50*6dbdd20aSAndroid Build Coastguard Worker return this.renderNested(vnode); 51*6dbdd20aSAndroid Build Coastguard Worker } else { 52*6dbdd20aSAndroid Build Coastguard Worker return this.renderSingle(vnode); 53*6dbdd20aSAndroid Build Coastguard Worker } 54*6dbdd20aSAndroid Build Coastguard Worker } 55*6dbdd20aSAndroid Build Coastguard Worker 56*6dbdd20aSAndroid Build Coastguard Worker private renderNested({attrs, children}: m.CVnode<MenuItemAttrs>) { 57*6dbdd20aSAndroid Build Coastguard Worker const { 58*6dbdd20aSAndroid Build Coastguard Worker rightIcon = 'chevron_right', 59*6dbdd20aSAndroid Build Coastguard Worker closePopupOnClick = false, 60*6dbdd20aSAndroid Build Coastguard Worker ...rest 61*6dbdd20aSAndroid Build Coastguard Worker } = attrs; 62*6dbdd20aSAndroid Build Coastguard Worker 63*6dbdd20aSAndroid Build Coastguard Worker return m( 64*6dbdd20aSAndroid Build Coastguard Worker PopupMenu2, 65*6dbdd20aSAndroid Build Coastguard Worker { 66*6dbdd20aSAndroid Build Coastguard Worker popupPosition: PopupPosition.RightStart, 67*6dbdd20aSAndroid Build Coastguard Worker trigger: m(MenuItem, { 68*6dbdd20aSAndroid Build Coastguard Worker rightIcon: rightIcon, 69*6dbdd20aSAndroid Build Coastguard Worker closePopupOnClick, 70*6dbdd20aSAndroid Build Coastguard Worker ...rest, 71*6dbdd20aSAndroid Build Coastguard Worker }), 72*6dbdd20aSAndroid Build Coastguard Worker showArrow: false, 73*6dbdd20aSAndroid Build Coastguard Worker createNewGroup: false, 74*6dbdd20aSAndroid Build Coastguard Worker edgeOffset: 5, // Adjust for popup padding & border. 75*6dbdd20aSAndroid Build Coastguard Worker }, 76*6dbdd20aSAndroid Build Coastguard Worker children, 77*6dbdd20aSAndroid Build Coastguard Worker ); 78*6dbdd20aSAndroid Build Coastguard Worker } 79*6dbdd20aSAndroid Build Coastguard Worker 80*6dbdd20aSAndroid Build Coastguard Worker private renderSingle({attrs}: m.CVnode<MenuItemAttrs>) { 81*6dbdd20aSAndroid Build Coastguard Worker const { 82*6dbdd20aSAndroid Build Coastguard Worker label, 83*6dbdd20aSAndroid Build Coastguard Worker icon, 84*6dbdd20aSAndroid Build Coastguard Worker rightIcon, 85*6dbdd20aSAndroid Build Coastguard Worker disabled, 86*6dbdd20aSAndroid Build Coastguard Worker active, 87*6dbdd20aSAndroid Build Coastguard Worker closePopupOnClick = true, 88*6dbdd20aSAndroid Build Coastguard Worker className, 89*6dbdd20aSAndroid Build Coastguard Worker ...htmlAttrs 90*6dbdd20aSAndroid Build Coastguard Worker } = attrs; 91*6dbdd20aSAndroid Build Coastguard Worker 92*6dbdd20aSAndroid Build Coastguard Worker const classes = classNames( 93*6dbdd20aSAndroid Build Coastguard Worker active && 'pf-active', 94*6dbdd20aSAndroid Build Coastguard Worker !disabled && closePopupOnClick && Popup.DISMISS_POPUP_GROUP_CLASS, 95*6dbdd20aSAndroid Build Coastguard Worker className, 96*6dbdd20aSAndroid Build Coastguard Worker ); 97*6dbdd20aSAndroid Build Coastguard Worker 98*6dbdd20aSAndroid Build Coastguard Worker return m( 99*6dbdd20aSAndroid Build Coastguard Worker 'button.pf-menu-item' + (disabled ? '[disabled]' : ''), 100*6dbdd20aSAndroid Build Coastguard Worker { 101*6dbdd20aSAndroid Build Coastguard Worker ...htmlAttrs, 102*6dbdd20aSAndroid Build Coastguard Worker className: classes, 103*6dbdd20aSAndroid Build Coastguard Worker }, 104*6dbdd20aSAndroid Build Coastguard Worker icon && m(Icon, {className: 'pf-left-icon', icon}), 105*6dbdd20aSAndroid Build Coastguard Worker rightIcon && m(Icon, {className: 'pf-right-icon', icon: rightIcon}), 106*6dbdd20aSAndroid Build Coastguard Worker label, 107*6dbdd20aSAndroid Build Coastguard Worker ); 108*6dbdd20aSAndroid Build Coastguard Worker } 109*6dbdd20aSAndroid Build Coastguard Worker} 110*6dbdd20aSAndroid Build Coastguard Worker 111*6dbdd20aSAndroid Build Coastguard Worker// An element which shows a dividing line between menu items. 112*6dbdd20aSAndroid Build Coastguard Workerexport class MenuDivider implements m.ClassComponent { 113*6dbdd20aSAndroid Build Coastguard Worker view() { 114*6dbdd20aSAndroid Build Coastguard Worker return m('.pf-menu-divider'); 115*6dbdd20aSAndroid Build Coastguard Worker } 116*6dbdd20aSAndroid Build Coastguard Worker} 117*6dbdd20aSAndroid Build Coastguard Worker 118*6dbdd20aSAndroid Build Coastguard Worker// A siple container for a menu. 119*6dbdd20aSAndroid Build Coastguard Worker// The menu contents are passed in as children, and are typically MenuItems or 120*6dbdd20aSAndroid Build Coastguard Worker// MenuDividers, but really they can be any Mithril component. 121*6dbdd20aSAndroid Build Coastguard Workerexport class Menu implements m.ClassComponent<HTMLAttrs> { 122*6dbdd20aSAndroid Build Coastguard Worker view({attrs, children}: m.CVnode<HTMLAttrs>) { 123*6dbdd20aSAndroid Build Coastguard Worker return m('.pf-menu', attrs, children); 124*6dbdd20aSAndroid Build Coastguard Worker } 125*6dbdd20aSAndroid Build Coastguard Worker} 126*6dbdd20aSAndroid Build Coastguard Worker 127*6dbdd20aSAndroid Build Coastguard Workerinterface PopupMenu2Attrs extends PopupAttrs { 128*6dbdd20aSAndroid Build Coastguard Worker // The trigger is mithril component which is used to toggle the popup when 129*6dbdd20aSAndroid Build Coastguard Worker // clicked, and provides the anchor on the page which the popup shall hover 130*6dbdd20aSAndroid Build Coastguard Worker // next to, and to which the popup's arrow shall point. The popup shall move 131*6dbdd20aSAndroid Build Coastguard Worker // around the page with this component, as if attached to it. 132*6dbdd20aSAndroid Build Coastguard Worker // This trigger can be any mithril component, but it is typically a Button, 133*6dbdd20aSAndroid Build Coastguard Worker // an Icon, or some other interactive component. 134*6dbdd20aSAndroid Build Coastguard Worker // Beware this element will have its `onclick`, `ref`, and `active` attributes 135*6dbdd20aSAndroid Build Coastguard Worker // overwritten. 136*6dbdd20aSAndroid Build Coastguard Worker // eslint-disable-next-line @typescript-eslint/no-explicit-any 137*6dbdd20aSAndroid Build Coastguard Worker trigger: m.Vnode<any, any>; 138*6dbdd20aSAndroid Build Coastguard Worker // Which side of the trigger to place to popup. 139*6dbdd20aSAndroid Build Coastguard Worker // Defaults to "bottom". 140*6dbdd20aSAndroid Build Coastguard Worker popupPosition?: PopupPosition; 141*6dbdd20aSAndroid Build Coastguard Worker // Whether we should show the little arrow pointing to the trigger. 142*6dbdd20aSAndroid Build Coastguard Worker // Defaults to true. 143*6dbdd20aSAndroid Build Coastguard Worker showArrow?: boolean; 144*6dbdd20aSAndroid Build Coastguard Worker // Whether this popup should form a new popup group. 145*6dbdd20aSAndroid Build Coastguard Worker // When nesting popups, grouping controls how popups are closed. 146*6dbdd20aSAndroid Build Coastguard Worker // When closing popups via the Escape key, each group is closed one by one, 147*6dbdd20aSAndroid Build Coastguard Worker // starting at the topmost group in the stack. 148*6dbdd20aSAndroid Build Coastguard Worker // When using a magic button to close groups (see DISMISS_POPUP_GROUP_CLASS), 149*6dbdd20aSAndroid Build Coastguard Worker // only the group in which the button lives and it's children will be closed. 150*6dbdd20aSAndroid Build Coastguard Worker // Defaults to true. 151*6dbdd20aSAndroid Build Coastguard Worker createNewGroup?: boolean; 152*6dbdd20aSAndroid Build Coastguard Worker} 153*6dbdd20aSAndroid Build Coastguard Worker 154*6dbdd20aSAndroid Build Coastguard Worker// A combination of a Popup and a Menu component. 155*6dbdd20aSAndroid Build Coastguard Worker// The menu contents are passed in as children, and are typically MenuItems or 156*6dbdd20aSAndroid Build Coastguard Worker// MenuDividers, but really they can be any Mithril component. 157*6dbdd20aSAndroid Build Coastguard Workerexport class PopupMenu2 implements m.ClassComponent<PopupMenu2Attrs> { 158*6dbdd20aSAndroid Build Coastguard Worker view({attrs, children}: m.CVnode<PopupMenu2Attrs>) { 159*6dbdd20aSAndroid Build Coastguard Worker const { 160*6dbdd20aSAndroid Build Coastguard Worker trigger, 161*6dbdd20aSAndroid Build Coastguard Worker popupPosition = PopupPosition.Bottom, 162*6dbdd20aSAndroid Build Coastguard Worker ...popupAttrs 163*6dbdd20aSAndroid Build Coastguard Worker } = attrs; 164*6dbdd20aSAndroid Build Coastguard Worker 165*6dbdd20aSAndroid Build Coastguard Worker return m( 166*6dbdd20aSAndroid Build Coastguard Worker Popup, 167*6dbdd20aSAndroid Build Coastguard Worker { 168*6dbdd20aSAndroid Build Coastguard Worker trigger, 169*6dbdd20aSAndroid Build Coastguard Worker position: popupPosition, 170*6dbdd20aSAndroid Build Coastguard Worker className: 'pf-popup-menu', 171*6dbdd20aSAndroid Build Coastguard Worker ...popupAttrs, 172*6dbdd20aSAndroid Build Coastguard Worker }, 173*6dbdd20aSAndroid Build Coastguard Worker m(Menu, children), 174*6dbdd20aSAndroid Build Coastguard Worker ); 175*6dbdd20aSAndroid Build Coastguard Worker } 176*6dbdd20aSAndroid Build Coastguard Worker} 177