Line data Source code
1 : import * as React from 'react';
2 : import * as styles from './button.module.css';
3 :
4 : import { Tooltip } from './tooltip.js';
5 : import { classNames } from '../../utils/classnames.js';
6 :
7 1 : const BUTTON_VARIANT_CLASSNAME = [
8 1 : styles.button_variant_default,
9 1 : styles.button_variant_primary,
10 1 : styles.button_variant_danger,
11 1 : styles.button_variant_invisible,
12 1 : styles.button_variant_invisible,
13 1 : ];
14 1 : const BUTTON_SIZE_CLASSNAME = [
15 1 : styles.button_size_tiny,
16 1 : styles.button_size_small,
17 1 : styles.button_size_medium,
18 1 : styles.button_size_large,
19 1 : ];
20 :
21 1 : export enum ButtonVariant {
22 1 : Default,
23 1 : Primary,
24 1 : Danger,
25 1 : Invisible,
26 1 : Outline,
27 1 : }
28 :
29 : export function mapButtonVariant(variant: ButtonVariant) {
30 : switch (variant) {
31 : case ButtonVariant.Default: return 'default';
32 : case ButtonVariant.Primary: return 'primary';
33 : case ButtonVariant.Danger: return 'danger';
34 : case ButtonVariant.Invisible: return 'invisible';
35 : case ButtonVariant.Outline: return 'outline';
36 : }
37 : }
38 :
39 1 : export enum ButtonSize {
40 1 : Tiny,
41 1 : Small,
42 1 : Medium,
43 1 : Large
44 1 : }
45 :
46 : export function mapButtonSize(size: ButtonSize) {
47 : switch (size) {
48 : case ButtonSize.Tiny: return 'tiny';
49 : case ButtonSize.Small: return 'small';
50 : case ButtonSize.Medium: return 'medium';
51 : case ButtonSize.Large: return 'large';
52 : }
53 : }
54 :
55 : interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
56 : className?: string;
57 : variant?: ButtonVariant;
58 : size?: ButtonSize;
59 : style?: React.CSSProperties;
60 : disabled?: boolean;
61 : inactive?: boolean;
62 : block?: boolean;
63 : leadingVisual?: React.ElementType | React.ReactElement;
64 : trailingVisual?: React.ElementType | React.ReactElement;
65 : trailingAction?: React.ReactElement<React.HTMLProps<HTMLButtonElement>>;
66 : children?: React.ReactNode;
67 : }
68 :
69 1 : export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props: ButtonProps, ref) => {
70 1 : const {
71 1 : block,
72 1 : children,
73 1 : className,
74 1 : inactive,
75 1 : leadingVisual,
76 1 : size: _size,
77 1 : style,
78 1 : trailingAction,
79 1 : trailingVisual,
80 1 : type,
81 1 : variant: _variant,
82 1 : ...rest
83 1 : } = props;
84 1 : const variantStyle = BUTTON_VARIANT_CLASSNAME[props.variant ?? ButtonVariant.Default];
85 1 : const sizeStyle = BUTTON_SIZE_CLASSNAME[props.size ?? ButtonSize.Medium];
86 1 : const leadingVisualElement = React.isValidElement(leadingVisual)
87 1 : ? leadingVisual
88 1 : : leadingVisual ? React.createElement(leadingVisual) : null;
89 1 : const trailingVisualElement = React.isValidElement(trailingVisual)
90 1 : ? trailingVisual
91 1 : : trailingVisual ? React.createElement(trailingVisual) : null;
92 1 : return (
93 1 : <button
94 1 : className={classNames(styles.button, variantStyle, sizeStyle, {
95 1 : [styles.inactive]: inactive,
96 1 : [styles.block]: block,
97 1 : [styles.disabled]: props.disabled,
98 1 : [styles.no_visuals]: !leadingVisual && !trailingVisual && !trailingAction ? true : undefined,
99 1 : }, className)}
100 1 : {...rest}
101 1 : disabled={props.disabled}
102 1 : ref={ref}
103 1 : style={style}
104 1 : type={type ?? 'button'}
105 : >
106 1 : <span className={styles.button_content}>
107 1 : {leadingVisualElement && (
108 1 : <span className={styles.leading_visual}>{leadingVisualElement}</span>
109 : )}
110 1 : {children && (
111 1 : <span className={styles.text}>{children}</span>
112 : )}
113 1 : {trailingVisualElement && (
114 1 : <span className={styles.trailing_visual}>{trailingVisualElement}</span>
115 : )}
116 1 : </span>
117 1 : {trailingAction && (
118 1 : <span className={styles.trailing_action}>
119 1 : {trailingAction}
120 1 : </span>
121 : )}
122 1 : </button>
123 : );
124 1 : });
125 :
126 : interface IconButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'aria-label'> {
127 : className?: string;
128 : variant?: ButtonVariant;
129 : size?: ButtonSize;
130 : disabled?: boolean;
131 : inactive?: boolean;
132 : children?: React.ReactNode;
133 : description?: string;
134 : 'aria-label': string;
135 : }
136 :
137 1 : export const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>((props: IconButtonProps, ref) => {
138 1 : const ariaLabel = props['aria-label'];
139 1 : const variantStyle = BUTTON_VARIANT_CLASSNAME[props.variant ?? ButtonVariant.Default];
140 1 : const sizeStyle = BUTTON_SIZE_CLASSNAME[props.size ?? ButtonSize.Medium];
141 1 : const {
142 1 : className,
143 1 : children,
144 1 : description,
145 1 : disabled,
146 1 : inactive,
147 1 : size: _size,
148 1 : variant: _variant,
149 1 : ['aria-label']: _ariaLabel,
150 1 : ...rest
151 1 : } = props;
152 1 : return (
153 1 : <Tooltip
154 1 : text={description ?? ariaLabel}
155 1 : type={description ? undefined : 'label'}
156 : >
157 1 : <button
158 1 : {...rest}
159 1 : aria-label={ariaLabel}
160 1 : className={classNames(styles.button, styles.icon_button, variantStyle, sizeStyle, {
161 1 : [styles.inactive]: inactive,
162 1 : [styles.disabled]: disabled,
163 1 : }, className)}
164 1 : disabled={disabled}
165 1 : ref={ref}
166 1 : type={props.type ?? 'button'}
167 : >
168 1 : {children}
169 1 : </button>
170 1 : </Tooltip>
171 : );
172 1 : });
|