1// Copyright (C) 2023 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 {classNames} from '../base/classnames'; 17import {HTMLAttrs, HTMLButtonAttrs, Intent, classForIntent} from './common'; 18import {Icon} from './icon'; 19import {Popup} from './popup'; 20import {Spinner} from './spinner'; 21 22interface CommonAttrs extends HTMLButtonAttrs { 23 // Always show the button as if the "active" pseudo class were applied, which 24 // makes the button look permanently pressed. 25 // Useful for when the button represents some toggleable state, such as 26 // showing/hiding a popup menu. 27 // Defaults to false. 28 active?: boolean; 29 // Use minimal padding, reducing the overall size of the button by a few px. 30 // Defaults to false. 31 compact?: boolean; 32 // Optional right icon. 33 rightIcon?: string; 34 // List of space separated class names forwarded to the icon. 35 className?: string; 36 // Allow clicking this button to close parent popups. 37 // Defaults to false. 38 dismissPopup?: boolean; 39 // Show loading spinner instead of icon. 40 // Defaults to false. 41 loading?: boolean; 42 // Whether to use a filled icon 43 // Defaults to false; 44 iconFilled?: boolean; 45 // Indicate button colouring by intent. 46 // Defaults to undefined aka "None" 47 intent?: Intent; 48} 49 50interface IconButtonAttrs extends CommonAttrs { 51 // Icon buttons require an icon. 52 icon: string; 53} 54 55interface LabelButtonAttrs extends CommonAttrs { 56 // Label buttons require a label. 57 label: string; 58 // Label buttons can have an optional icon. 59 icon?: string; 60} 61 62export type ButtonAttrs = LabelButtonAttrs | IconButtonAttrs; 63 64export class Button implements m.ClassComponent<ButtonAttrs> { 65 view({attrs}: m.CVnode<ButtonAttrs>) { 66 const { 67 icon, 68 active, 69 compact, 70 rightIcon, 71 className, 72 dismissPopup, 73 iconFilled, 74 intent = Intent.None, 75 ...htmlAttrs 76 } = attrs; 77 78 const label = 'label' in attrs ? attrs.label : undefined; 79 80 const classes = classNames( 81 active && 'pf-active', 82 compact && 'pf-compact', 83 classForIntent(intent), 84 icon && !label && 'pf-icon-only', 85 dismissPopup && Popup.DISMISS_POPUP_GROUP_CLASS, 86 className, 87 ); 88 89 return m( 90 'button.pf-button', 91 { 92 ...htmlAttrs, 93 className: classes, 94 }, 95 this.renderIcon(attrs), 96 rightIcon && 97 m(Icon, { 98 className: 'pf-right-icon', 99 icon: rightIcon, 100 filled: iconFilled, 101 }), 102 label || '\u200B', // Zero width space keeps button in-flow 103 ); 104 } 105 106 private renderIcon(attrs: ButtonAttrs): m.Children { 107 const {icon, iconFilled} = attrs; 108 const className = 'pf-left-icon'; 109 if (attrs.loading) { 110 return m(Spinner, {className}); 111 } else if (icon) { 112 return m(Icon, {className, icon, filled: iconFilled}); 113 } else { 114 return undefined; 115 } 116 } 117} 118 119/** 120 * Space buttons out with a little gap between each one. 121 */ 122export class ButtonBar implements m.ClassComponent<HTMLAttrs> { 123 view({attrs, children}: m.CVnode<HTMLAttrs>): m.Children { 124 return m('.pf-button-bar', attrs, children); 125 } 126} 127