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