xref: /aosp_15_r20/external/perfetto/ui/src/widgets/button.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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