diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/mui')
7 files changed, 376 insertions, 39 deletions
diff --git a/packages/taler-wallet-webextension/src/mui/Alert.stories.tsx b/packages/taler-wallet-webextension/src/mui/Alert.stories.tsx new file mode 100644 index 000000000..12b2a8625 --- /dev/null +++ b/packages/taler-wallet-webextension/src/mui/Alert.stories.tsx @@ -0,0 +1,86 @@ +/* + This file is part of GNU Taler + (C) 2021 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/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { css } from "@linaria/core"; +import { ComponentChildren, Fragment, h, VNode } from "preact"; +import { Alert } from "./Alert.jsx"; + +export default { + title: "mui/alert", + component: Alert, +}; + +function Wrapper({ children }: { children: ComponentChildren }): VNode { + return ( + <div + class={css` + & > * { + margin: 2em; + } + `} + > + {children} + </div> + ); +} + +export const BasicExample = (): VNode => ( + <Wrapper> + <Alert severity="warning">this is an warning</Alert> + <Alert severity="error">this is an error</Alert> + <Alert severity="success">this is an success</Alert> + <Alert severity="info">this is an info</Alert> + </Wrapper> +); + +export const WithTitle = (): VNode => ( + <Wrapper> + <Alert title="Warning" severity="warning"> + this is an warning + </Alert> + <Alert title="Error" severity="error"> + this is an error + </Alert> + <Alert title="Success" severity="success"> + this is an success + </Alert> + <Alert title="Info" severity="info"> + this is an info + </Alert> + </Wrapper> +); + +export const WithAction = (): VNode => ( + <Wrapper> + <Alert title="Warning" severity="warning" onClose={() => alert("closed")}> + this is an warning + </Alert> + <Alert title="Error" severity="error" onClose={() => alert("closed")}> + this is an error + </Alert> + <Alert title="Success" severity="success" onClose={() => alert("closed")}> + this is an success + </Alert> + <Alert title="Info" severity="info" onClose={() => alert("closed")}> + this is an info + </Alert> + </Wrapper> +); diff --git a/packages/taler-wallet-webextension/src/mui/Alert.tsx b/packages/taler-wallet-webextension/src/mui/Alert.tsx new file mode 100644 index 000000000..7d0ce55d0 --- /dev/null +++ b/packages/taler-wallet-webextension/src/mui/Alert.tsx @@ -0,0 +1,160 @@ +import { css } from "@linaria/core"; +import { ComponentChildren, h, VNode } from "preact"; +// eslint-disable-next-line import/extensions +import CloseIcon from "../svg/close_24px.svg"; +import ErrorOutlineIcon from "../svg/error_outline_outlined_24px.svg"; +import InfoOutlinedIcon from "../svg/info_outlined_24px.svg"; +import ReportProblemOutlinedIcon from "../svg/report_problem_outlined_24px.svg"; +import SuccessOutlinedIcon from "../svg/success_outlined_24px.svg"; +import { IconButton } from "./Button.js"; +// eslint-disable-next-line import/extensions +import { darken, lighten } from "./colors/manipulation"; +import { Paper } from "./Paper.js"; +// eslint-disable-next-line import/extensions +import { theme } from "./style"; +import { Typography } from "./Typography.js"; + +const defaultIconMapping = { + success: SuccessOutlinedIcon, + warning: ReportProblemOutlinedIcon, + error: ErrorOutlineIcon, + info: InfoOutlinedIcon, +}; + +const baseStyle = css` + background-color: transparent; + display: flex; + padding: 6px 16px; +`; + +const colorVariant = { + standard: css` + color: var(--color-light-06); + background-color: var(--color-background-light-09); + `, + outlined: css` + color: var(--color-light-06); + border-width: 1px; + border-style: solid; + border-color: var(--color-light); + `, + filled: css` + color: "#fff"; + font-weight: ${theme.typography.fontWeightMedium}; + background-color: var(--color-main); + `, +}; + +interface Props { + title?: string; + variant?: "filled" | "outlined" | "standard"; + role?: string; + onClose?: () => void; + // icon: VNode; + severity?: "info" | "warning" | "success" | "error"; + children: ComponentChildren; + icon?: boolean; +} + +const getColor = theme.palette.mode === "light" ? darken : lighten; +const getBackgroundColor = theme.palette.mode === "light" ? lighten : darken; + +function Icon({ svg }: { svg: VNode }): VNode { + return ( + <div + class={css` + margin-right: 12px; + padding: 7px 0px; + display: flex; + font-size: 22px; + opacity: 0.9; + fill: currentColor; + `} + dangerouslySetInnerHTML={{ __html: svg as any }} + ></div> + ); +} + +function Action({ children }: { children: ComponentChildren }): VNode { + return ( + <div + class={css` + display: flex; + align-items: flex-start; + padding: 4px 0px 0px 16px; + margin-left: auto; + margin-right: -8px; + `} + > + {children} + </div> + ); +} + +function Message({ + title, + children, +}: { + title?: string; + children: ComponentChildren; +}): VNode { + return ( + <div + class={css` + padding: 8px 0px; + width: 100%; + `} + > + {title && ( + <Typography + class={css` + font-weight: ${theme.typography.fontWeightMedium}; + `} + gutterBottom + > + {title} + </Typography> + )} + {children} + </div> + ); +} + +export function Alert({ + variant = "standard", + severity = "success", + title, + children, + icon, + onClose, + ...rest +}: Props): VNode { + return ( + <Paper + class={[ + theme.typography.body2, + baseStyle, + severity && colorVariant[variant], + ].join(" ")} + style={{ + "--color-light-06": getColor(theme.palette[severity].light, 0.6), + "--color-background-light-09": getBackgroundColor( + theme.palette[severity].light, + 0.9, + ), + "--color-main": theme.palette[severity].main, + "--color-light": theme.palette[severity].light, + // ...(style as any), + }} + elevation={1} + > + {icon != false ? <Icon svg={defaultIconMapping[severity]} /> : null} + <Message title={title}>{children}</Message> + {onClose && ( + <Action> + <IconButton svg={CloseIcon} onClick={onClose} /> + </Action> + )} + </Paper> + ); +} diff --git a/packages/taler-wallet-webextension/src/mui/Button.tsx b/packages/taler-wallet-webextension/src/mui/Button.tsx index 083bbea0d..451b1d48d 100644 --- a/packages/taler-wallet-webextension/src/mui/Button.tsx +++ b/packages/taler-wallet-webextension/src/mui/Button.tsx @@ -1,26 +1,11 @@ -import { ComponentChildren, h, VNode } from "preact"; +import { ComponentChildren, h, VNode, JSX } from "preact"; import { css } from "@linaria/core"; // eslint-disable-next-line import/extensions import { theme, ripple, Colors } from "./style"; // eslint-disable-next-line import/extensions import { alpha } from "./colors/manipulation"; -interface Props { - children?: ComponentChildren; - disabled?: boolean; - disableElevation?: boolean; - disableFocusRipple?: boolean; - endIcon?: VNode; - fullWidth?: boolean; - href?: string; - size?: "small" | "medium" | "large"; - startIcon?: VNode | string; - variant?: "contained" | "outlined" | "text"; - color?: Colors; - onClick?: () => void; -} - -const baseStyle = css` +const buttonBaseStyle = css` display: inline-flex; align-items: center; justify-content: center; @@ -30,7 +15,7 @@ const baseStyle = css` outline: 0; border: 0; margin: 0; - /* border-radius: 0; */ + border-radius: 0; padding: 0; cursor: pointer; user-select: none; @@ -39,6 +24,21 @@ const baseStyle = css` color: inherit; `; +interface Props { + children?: ComponentChildren; + disabled?: boolean; + disableElevation?: boolean; + disableFocusRipple?: boolean; + endIcon?: string | VNode; + fullWidth?: boolean; + href?: string; + size?: "small" | "medium" | "large"; + startIcon?: VNode | string; + variant?: "contained" | "outlined" | "text"; + color?: Colors; + onClick?: () => void; +} + const button = css` min-width: 64px; &:hover { @@ -54,13 +54,13 @@ const button = css` `; const colorIconVariant = { outlined: css` - background-color: var(--color-main); + fill: var(--color-main); `, contained: css` - background-color: var(--color-contrastText); + fill: var(--color-contrastText); `, text: css` - background-color: var(--color-main); + fill: var(--color-main); `, }; @@ -68,6 +68,7 @@ const colorVariant = { outlined: css` color: var(--color-main); border: 1px solid var(--color-main-alpha-half); + background-color: var(--color-contrastText); &:hover { border: 1px solid var(--color-main); background-color: var(--color-main-alpha-opacity); @@ -81,7 +82,7 @@ const colorVariant = { background-color: var(--color-main); box-shadow: ${theme.shadows[2]}; &:hover { - background-color: var(--color-dark); + background-color: var(--color-grey-or-dark); } &:active { box-shadow: ${theme.shadows[8]}; @@ -186,11 +187,16 @@ const sizeVariant = { }, }; +const fullWidthStyle = css` + width: 100%; +`; + export function Button({ children, disabled, startIcon: sip, endIcon: eip, + fullWidth, variant = "text", size = "medium", color = "primary", @@ -198,8 +204,8 @@ export function Button({ }: Props): VNode { const style = css` user-select: none; - width: 1em; - height: 1em; + width: 24px; + height: 24px; display: inline-block; fill: currentColor; flex-shrink: 0; @@ -222,8 +228,9 @@ export function Button({ sizeIconVariant[variant][size], style, ].join(" ")} + //FIXME: check when sip can be a vnode + dangerouslySetInnerHTML={{ __html: sip as string }} style={{ - "--image": `url("${sip}")`, "--color-main": theme.palette[color].main, "--color-contrastText": theme.palette[color].contrastText, }} @@ -241,8 +248,8 @@ export function Button({ sizeIconVariant[variant][size], style, ].join(" ")} + dangerouslySetInnerHTML={{ __html: eip as string }} style={{ - "--image": `url("${eip}")`, "--color-main": theme.palette[color].main, "--color-contrastText": theme.palette[color].contrastText, "--color-dark": theme.palette[color].dark, @@ -250,17 +257,17 @@ export function Button({ /> ); return ( - <button + <ButtonBase disabled={disabled} class={[ theme.typography.button, theme.shape.roundBorder, - ripple, - baseStyle, button, + fullWidth && fullWidthStyle, colorVariant[variant], sizeVariant[variant][size], ].join(" ")} + onClick={onClick} style={{ "--color-main": theme.palette[color].main, "--color-contrastText": theme.palette[color].contrastText, @@ -274,11 +281,75 @@ export function Button({ theme.palette.text.primary, theme.palette.action.hoverOpacity, ), + "--color-grey-or-dark": !color + ? theme.palette.grey.A100 + : theme.palette[color].dark, }} > {startIcon} {children} {endIcon} + </ButtonBase> + ); +} + +interface BaseProps extends JSX.HTMLAttributes<HTMLButtonElement> { + class: string; + onClick?: () => void; + children?: ComponentChildren; +} + +function ButtonBase({ + class: _class, + children, + onClick, + dangerouslySetInnerHTML, + ...rest +}: BaseProps): VNode { + function doClick(): void { + if (onClick) onClick(); + } + const classNames = [buttonBaseStyle, _class, ripple].join(" "); + if (dangerouslySetInnerHTML) { + return ( + <button + onClick={doClick} + class={classNames} + dangerouslySetInnerHTML={dangerouslySetInnerHTML} + {...rest} + /> + ); + } + return ( + <button onClick={doClick} class={classNames} {...rest}> + {children} </button> ); } + +export function IconButton({ + svg, + onClick, +}: { + svg: any; + onClick?: () => 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(" ")} + dangerouslySetInnerHTML={{ __html: svg }} + /> + ); +} diff --git a/packages/taler-wallet-webextension/src/mui/Paper.tsx b/packages/taler-wallet-webextension/src/mui/Paper.tsx index eeb4083d4..7d36c32d1 100644 --- a/packages/taler-wallet-webextension/src/mui/Paper.tsx +++ b/packages/taler-wallet-webextension/src/mui/Paper.tsx @@ -34,11 +34,14 @@ export function Paper({ square, variant = "elevation", children, + class: _class, + style, ...rest }: Props): VNode { return ( <div class={[ + _class, baseStyle, !square && theme.shape.roundBorder, borderVariant[variant], @@ -49,6 +52,7 @@ export function Paper({ "#fff", getOverlayAlpha(elevation), )}, ${alpha("#fff", getOverlayAlpha(elevation))})`, + ...(style as any), }} {...rest} > diff --git a/packages/taler-wallet-webextension/src/mui/Typography.tsx b/packages/taler-wallet-webextension/src/mui/Typography.tsx index ecd2b8ec3..1610a1c66 100644 --- a/packages/taler-wallet-webextension/src/mui/Typography.tsx +++ b/packages/taler-wallet-webextension/src/mui/Typography.tsx @@ -1,5 +1,6 @@ import { css } from "@linaria/core"; import { ComponentChildren, h, VNode } from "preact"; +import { useTranslationContext } from "../context/translation.js"; // eslint-disable-next-line import/extensions import { theme } from "./style"; @@ -22,10 +23,13 @@ type VariantEnum = interface Props { align?: "center" | "inherit" | "justify" | "left" | "right"; gutterBottom?: boolean; + bold?: boolean; + inline?: boolean; noWrap?: boolean; paragraph?: boolean; variant?: VariantEnum; - children?: ComponentChildren; + children: string[] | string; + class?: string; } const defaultVariantMapping = { @@ -57,6 +61,9 @@ const gutterBottomStyle = css` const paragraphStyle = css` margin-bottom: 16px; `; +const boldStyle = css` + font-weight: bold; +`; export function Typography({ align, @@ -64,9 +71,16 @@ export function Typography({ noWrap = false, paragraph = false, variant = "body1", + bold, + inline, children, + class: _class, }: Props): VNode { - const cmp = paragraph + const { i18n } = useTranslationContext(); + + const Component = inline + ? "span" + : paragraph === true ? "p" : defaultVariantMapping[variant as "h1"] || "span"; @@ -76,20 +90,21 @@ export function Typography({ : { textAlign: align, }; + return h( - cmp, + Component, { class: [ + _class, root, noWrap && noWrapStyle, gutterBottom && gutterBottomStyle, paragraph && paragraphStyle, + bold && boldStyle, theme.typography[variant as "button"], //FIXME: implement the rest of the typography and remove the casting ].join(" "), - style: { - ...alignStyle, - }, + style: alignStyle, }, - children, + <i18n.Translate>{children}</i18n.Translate>, ); } diff --git a/packages/taler-wallet-webextension/src/mui/index.stories.tsx b/packages/taler-wallet-webextension/src/mui/index.stories.tsx index 302114ed5..41b3a7bd9 100644 --- a/packages/taler-wallet-webextension/src/mui/index.stories.tsx +++ b/packages/taler-wallet-webextension/src/mui/index.stories.tsx @@ -23,5 +23,6 @@ import * as a1 from "./Button.stories.js"; import * as a3 from "./Grid.stories.js"; import * as a4 from "./Paper.stories.js"; import * as a5 from "./TextField.stories.js"; +import * as a6 from "./Alert.stories.js"; -export default [a1, a3, a4, a5]; +export default [a1, a3, a4, a5, a6]; diff --git a/packages/taler-wallet-webextension/src/mui/style.tsx b/packages/taler-wallet-webextension/src/mui/style.tsx index 5be978794..652a71d46 100644 --- a/packages/taler-wallet-webextension/src/mui/style.tsx +++ b/packages/taler-wallet-webextension/src/mui/style.tsx @@ -48,11 +48,11 @@ export const ripple = css` transition: background 0.5s; &:hover { - background: #47a7f5 radial-gradient(circle, transparent 1%, #47a7f5 1%) + background: #eeeeee radial-gradient(circle, transparent 1%, #eeeeee 1%) center/15000%; } &:active { - background-color: #6eb9f7; + background-color: currentColor; background-size: 100%; transition: background 0s; } |