wallet-core/packages/taler-wallet-webextension/src/mui/Button.tsx

386 lines
8.7 KiB
TypeScript
Raw Normal View History

2022-06-06 17:06:25 +02:00
/*
This file is part of GNU Taler
(C) 2022 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { ComponentChildren, h, VNode, JSX } from "preact";
2022-03-09 18:00:02 +01:00
import { css } from "@linaria/core";
2022-03-29 04:41:07 +02:00
// eslint-disable-next-line import/extensions
2022-06-24 16:42:21 +02:00
import { theme, Colors, rippleEnabled, rippleEnabledOutlined } from "./style";
2022-03-29 04:41:07 +02:00
// eslint-disable-next-line import/extensions
2022-03-09 18:00:02 +01:00
import { alpha } from "./colors/manipulation";
2022-06-24 16:42:21 +02:00
export const buttonBaseStyle = css`
2022-03-09 18:00:02 +01:00
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
box-sizing: border-box;
background-color: transparent;
outline: 0;
border: 0;
margin: 0;
border-radius: 0;
2022-03-09 18:00:02 +01:00
padding: 0;
cursor: pointer;
user-select: none;
vertical-align: middle;
text-decoration: none;
color: inherit;
`;
interface Props {
children?: ComponentChildren;
disabled?: boolean;
disableElevation?: boolean;
disableFocusRipple?: boolean;
endIcon?: string | VNode;
fullWidth?: boolean;
2022-06-01 20:47:47 +02:00
style?: h.JSX.CSSProperties;
href?: string;
size?: "small" | "medium" | "large";
startIcon?: VNode | string;
variant?: "contained" | "outlined" | "text";
2022-08-17 21:12:21 +02:00
tooltip?: string;
color?: Colors;
2022-06-01 20:47:47 +02:00
onClick?: () => Promise<void>;
}
2022-03-09 18:00:02 +01:00
const button = css`
min-width: 64px;
&:hover {
text-decoration: none;
background-color: var(--text-primary-alpha-opacity);
@media (hover: none) {
background-color: transparent;
}
}
&:disabled {
color: ${theme.palette.action.disabled};
}
`;
2022-03-18 21:52:46 +01:00
const colorIconVariant = {
outlined: css`
fill: var(--color-main);
2022-03-18 21:52:46 +01:00
`,
contained: css`
fill: var(--color-contrastText);
2022-03-18 21:52:46 +01:00
`,
text: css`
fill: var(--color-main);
2022-03-18 21:52:46 +01:00
`,
};
2022-03-09 18:00:02 +01:00
const colorVariant = {
outlined: css`
color: var(--color-main);
border: 1px solid var(--color-main-alpha-half);
background-color: var(--color-contrastText);
2022-03-09 18:00:02 +01:00
&:hover {
border: 1px solid var(--color-main);
background-color: var(--color-main-alpha-opacity);
}
&:disabled {
border: 1px solid ${theme.palette.action.disabledBackground};
}
`,
contained: css`
color: var(--color-contrastText);
background-color: var(--color-main);
box-shadow: ${theme.shadows[2]};
&:hover {
background-color: var(--color-grey-or-dark);
2022-03-09 18:00:02 +01:00
}
&:active {
box-shadow: ${theme.shadows[8]};
}
&:focus-visible {
box-shadow: ${theme.shadows[6]};
}
&:disabled {
color: ${theme.palette.action.disabled};
box-shadow: ${theme.shadows[0]};
background-color: ${theme.palette.action.disabledBackground};
}
`,
text: css`
color: var(--color-main);
&:hover {
background-color: var(--color-main-alpha-opacity);
}
`,
};
2022-03-18 21:52:46 +01:00
const sizeIconVariant = {
outlined: {
small: css`
padding: 3px;
font-size: ${theme.pxToRem(7)};
`,
medium: css`
padding: 5px;
`,
large: css`
padding: 7px;
font-size: ${theme.pxToRem(10)};
`,
},
contained: {
small: css`
padding: 4px;
font-size: ${theme.pxToRem(13)};
`,
medium: css`
padding: 6px;
`,
large: css`
padding: 8px;
font-size: ${theme.pxToRem(10)};
`,
},
text: {
small: css`
padding: 4px;
font-size: ${theme.pxToRem(13)};
`,
medium: css`
padding: 6px;
`,
large: css`
padding: 8px;
font-size: ${theme.pxToRem(15)};
`,
},
};
2022-03-09 18:00:02 +01:00
const sizeVariant = {
outlined: {
small: css`
padding: 3px 9px;
font-size: ${theme.pxToRem(13)};
`,
medium: css`
padding: 5px 15px;
`,
large: css`
padding: 7px 21px;
font-size: ${theme.pxToRem(15)};
`,
},
contained: {
small: css`
padding: 4px 10px;
font-size: ${theme.pxToRem(13)};
`,
medium: css`
padding: 6px 16px;
`,
large: css`
padding: 8px 22px;
font-size: ${theme.pxToRem(15)};
`,
},
text: {
small: css`
padding: 4px 5px;
font-size: ${theme.pxToRem(13)};
`,
medium: css`
padding: 6px 8px;
`,
large: css`
padding: 8px 11px;
font-size: ${theme.pxToRem(15)};
`,
},
};
const fullWidthStyle = css`
width: 100%;
`;
2022-03-09 18:00:02 +01:00
export function Button({
children,
disabled,
startIcon: sip,
endIcon: eip,
fullWidth,
2022-08-17 21:12:21 +02:00
tooltip,
2022-03-09 18:00:02 +01:00
variant = "text",
size = "medium",
2022-06-01 20:47:47 +02:00
style: parentStyle,
2022-03-09 18:00:02 +01:00
color = "primary",
2022-03-11 06:17:40 +01:00
onClick,
2022-03-09 18:00:02 +01:00
}: Props): VNode {
const style = css`
user-select: none;
width: 24px;
height: 24px;
2022-03-09 18:00:02 +01:00
display: inline-block;
fill: currentColor;
flex-shrink: 0;
transition: fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
& > svg {
font-size: 20;
}
`;
const startIcon = sip && (
<span
class={[
css`
margin-right: 8px;
margin-left: -4px;
2022-03-18 21:52:46 +01:00
mask: var(--image) no-repeat center;
2022-03-09 18:00:02 +01:00
`,
2022-03-18 21:52:46 +01:00
colorIconVariant[variant],
sizeIconVariant[variant][size],
2022-03-09 18:00:02 +01:00
style,
].join(" ")}
//FIXME: check when sip can be a vnode
dangerouslySetInnerHTML={{ __html: sip as string }}
2022-03-18 21:52:46 +01:00
style={{
"--color-main": theme.palette[color].main,
"--color-contrastText": theme.palette[color].contrastText,
}}
/>
2022-03-09 18:00:02 +01:00
);
const endIcon = eip && (
<span
class={[
css`
margin-right: -4px;
margin-left: 8px;
2022-03-18 21:52:46 +01:00
mask: var(--image) no-repeat center;
2022-03-09 18:00:02 +01:00
`,
2022-03-18 21:52:46 +01:00
colorIconVariant[variant],
sizeIconVariant[variant][size],
2022-03-09 18:00:02 +01:00
style,
].join(" ")}
dangerouslySetInnerHTML={{ __html: eip as string }}
2022-03-18 21:52:46 +01:00
style={{
"--color-main": theme.palette[color].main,
"--color-contrastText": theme.palette[color].contrastText,
"--color-dark": theme.palette[color].dark,
}}
/>
2022-03-09 18:00:02 +01:00
);
return (
<ButtonBase
2022-03-09 18:00:02 +01:00
disabled={disabled}
class={[
theme.typography.button,
2022-03-11 03:13:10 +01:00
theme.shape.roundBorder,
2022-03-09 18:00:02 +01:00
button,
fullWidth && fullWidthStyle,
2022-03-09 18:00:02 +01:00
colorVariant[variant],
sizeVariant[variant][size],
].join(" ")}
2022-06-01 20:47:47 +02:00
containedRipple={variant === "contained"}
onClick={onClick}
2022-03-09 18:00:02 +01:00
style={{
2022-06-01 20:47:47 +02:00
...parentStyle,
2022-03-09 18:00:02 +01:00
"--color-main": theme.palette[color].main,
"--color-contrastText": theme.palette[color].contrastText,
2022-03-18 21:52:46 +01:00
"--color-main-alpha-half": alpha(theme.palette[color].main, 0.5),
2022-03-09 18:00:02 +01:00
"--color-dark": theme.palette[color].dark,
2022-06-01 20:47:47 +02:00
"--color-light": theme.palette[color].light,
2022-03-09 18:00:02 +01:00
"--color-main-alpha-opacity": alpha(
theme.palette[color].main,
theme.palette.action.hoverOpacity,
),
"--text-primary-alpha-opacity": alpha(
theme.palette.text.primary,
theme.palette.action.hoverOpacity,
),
"--color-grey-or-dark": !color
? theme.palette.grey.A100
: theme.palette[color].dark,
2022-03-09 18:00:02 +01:00
}}
2022-08-17 21:12:21 +02:00
title={tooltip}
2022-03-09 18:00:02 +01:00
>
{startIcon}
{children}
{endIcon}
</ButtonBase>
);
}
interface BaseProps extends JSX.HTMLAttributes<HTMLButtonElement> {
class: string;
2022-06-01 20:47:47 +02:00
onClick?: () => Promise<void>;
containedRipple?: boolean;
children?: ComponentChildren;
2022-06-24 16:42:21 +02:00
svg?: any;
}
function ButtonBase({
class: _class,
children,
2022-06-01 20:47:47 +02:00
containedRipple,
onClick,
2022-06-24 16:42:21 +02:00
svg,
...rest
}: BaseProps): VNode {
function doClick(): void {
if (onClick) onClick();
}
2022-06-01 20:47:47 +02:00
const classNames = [
buttonBaseStyle,
_class,
2022-06-24 16:42:21 +02:00
containedRipple ? rippleEnabled : rippleEnabledOutlined,
2022-06-01 20:47:47 +02:00
].join(" ");
2022-06-24 16:42:21 +02:00
if (svg) {
return (
<button
onClick={doClick}
class={classNames}
2022-06-24 16:42:21 +02:00
dangerouslySetInnerHTML={{ __html: svg }}
{...rest}
/>
);
}
return (
<button onClick={doClick} class={classNames} {...rest}>
{children}
2022-03-09 18:00:02 +01:00
</button>
);
}
export function IconButton({
svg,
onClick,
}: {
svg: any;
2022-06-01 20:47:47 +02:00
onClick?: () => Promise<void>;
}): VNode {
return (
<ButtonBase
onClick={onClick}
class={[
css`
text-align: center;
flex: 0 0 auto;
font-size: ${theme.typography.pxToRem(24)};
padding: 8px;
border-radius: 50%;
overflow: visible;
color: "inherit";
fill: currentColor;
`,
].join(" ")}
2022-06-24 16:42:21 +02:00
svg={svg}
/>
);
}