aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/mui
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2022-03-18 17:52:46 -0300
committerSebastian <sebasjm@gmail.com>2022-03-18 17:52:46 -0300
commit65eb64cd07dcaf1b57405189fcd054684d3f5e2f (patch)
tree4d10faf8f975bbccceb2286ce2eb00a5000bbbbc /packages/taler-wallet-webextension/src/mui
parent98761a2b8d50b1547ed1230f7c462ed205656c77 (diff)
downloadwallet-core-65eb64cd07dcaf1b57405189fcd054684d3f5e2f.tar.xz
mui text field, standard variation
Diffstat (limited to 'packages/taler-wallet-webextension/src/mui')
-rw-r--r--packages/taler-wallet-webextension/src/mui/Button.stories.tsx133
-rw-r--r--packages/taler-wallet-webextension/src/mui/Button.tsx87
-rw-r--r--packages/taler-wallet-webextension/src/mui/TextField.stories.tsx108
-rw-r--r--packages/taler-wallet-webextension/src/mui/TextField.tsx69
-rw-r--r--packages/taler-wallet-webextension/src/mui/input/FormControl.tsx156
-rw-r--r--packages/taler-wallet-webextension/src/mui/input/FormHelperText.tsx54
-rw-r--r--packages/taler-wallet-webextension/src/mui/input/FormLabel.tsx67
-rw-r--r--packages/taler-wallet-webextension/src/mui/input/InputBase.tsx258
-rw-r--r--packages/taler-wallet-webextension/src/mui/input/InputFilled.tsx5
-rw-r--r--packages/taler-wallet-webextension/src/mui/input/InputLabel.tsx98
-rw-r--r--packages/taler-wallet-webextension/src/mui/input/InputOutlined.tsx5
-rw-r--r--packages/taler-wallet-webextension/src/mui/input/InputStandard.tsx124
-rw-r--r--packages/taler-wallet-webextension/src/mui/input/SelectFilled.tsx5
-rw-r--r--packages/taler-wallet-webextension/src/mui/input/SelectOutlined.tsx5
-rw-r--r--packages/taler-wallet-webextension/src/mui/input/SelectStandard.tsx5
-rw-r--r--packages/taler-wallet-webextension/src/mui/style.tsx16
16 files changed, 1184 insertions, 11 deletions
diff --git a/packages/taler-wallet-webextension/src/mui/Button.stories.tsx b/packages/taler-wallet-webextension/src/mui/Button.stories.tsx
new file mode 100644
index 000000000..a6863add3
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/Button.stories.tsx
@@ -0,0 +1,133 @@
+/*
+ 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 { Button } from "./Button";
+import { Fragment, h } from "preact";
+import DeleteIcon from "../../static/img/delete_24px.svg";
+import SendIcon from "../../static/img/send_24px.svg";
+import { styled } from "@linaria/react";
+
+export default {
+ title: "mui/button",
+ component: Button,
+};
+
+const Stack = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+export const BasicExample = () => (
+ <Fragment>
+ <Stack>
+ <Button size="small" variant="text">
+ Text
+ </Button>
+ <Button size="small" variant="contained">
+ Contained
+ </Button>
+ <Button size="small" variant="outlined">
+ Outlined
+ </Button>
+ </Stack>
+ <Stack>
+ <Button variant="text">Text</Button>
+ <Button variant="contained">Contained</Button>
+ <Button variant="outlined">Outlined</Button>
+ </Stack>
+ <Stack>
+ <Button size="large" variant="text">
+ Text
+ </Button>
+ <Button size="large" variant="contained">
+ Contained
+ </Button>
+ <Button size="large" variant="outlined">
+ Outlined
+ </Button>
+ </Stack>
+ </Fragment>
+);
+
+export const Others = () => (
+ <Fragment>
+ <p>colors</p>
+ <Stack>
+ <Button color="secondary">Secondary</Button>
+ <Button variant="contained" color="success">
+ Success
+ </Button>
+ <Button variant="outlined" color="error">
+ Error
+ </Button>
+ </Stack>
+ <p>disabled</p>
+ <Stack>
+ <Button disabled variant="text">
+ Text
+ </Button>
+ <Button disabled variant="contained">
+ Contained
+ </Button>
+ <Button disabled variant="outlined">
+ Outlined
+ </Button>
+ </Stack>
+ </Fragment>
+);
+
+export const WithIcons = () => (
+ <Fragment>
+ <Stack>
+ <Button variant="outlined" size="small" startIcon={DeleteIcon}>
+ Delete
+ </Button>
+ <Button variant="contained" size="small" endIcon={SendIcon}>
+ Send
+ </Button>
+ <Button variant="text" size="small" endIcon={SendIcon}>
+ Send
+ </Button>
+ </Stack>
+ <Stack>
+ <Button variant="outlined" startIcon={DeleteIcon}>
+ Delete
+ </Button>
+ <Button variant="contained" endIcon={SendIcon}>
+ Send
+ </Button>
+ <Button variant="text" endIcon={SendIcon}>
+ Send
+ </Button>
+ </Stack>
+ <Stack>
+ <Button variant="outlined" size="large" startIcon={DeleteIcon}>
+ Delete
+ </Button>
+ <Button variant="contained" size="large" endIcon={SendIcon}>
+ Send
+ </Button>
+ <Button variant="text" size="large" endIcon={SendIcon}>
+ Send
+ </Button>
+ </Stack>
+ </Fragment>
+);
diff --git a/packages/taler-wallet-webextension/src/mui/Button.tsx b/packages/taler-wallet-webextension/src/mui/Button.tsx
index ccca360fa..8da5b86be 100644
--- a/packages/taler-wallet-webextension/src/mui/Button.tsx
+++ b/packages/taler-wallet-webextension/src/mui/Button.tsx
@@ -1,6 +1,6 @@
import { ComponentChildren, h, VNode } from "preact";
import { css } from "@linaria/core";
-import { theme, ripple } from "./style";
+import { theme, ripple, Colors } from "./style";
import { alpha } from "./colors/manipulation";
interface Props {
@@ -12,9 +12,9 @@ interface Props {
fullWidth?: boolean;
href?: string;
size?: "small" | "medium" | "large";
- startIcon?: VNode;
+ startIcon?: VNode | string;
variant?: "contained" | "outlined" | "text";
- color?: "primary" | "secondary" | "success" | "error" | "info" | "warning";
+ color?: Colors;
onClick?: () => void;
}
@@ -28,7 +28,7 @@ const baseStyle = css`
outline: 0;
border: 0;
margin: 0;
- border-radius: 0;
+ /* border-radius: 0; */
padding: 0;
cursor: pointer;
user-select: none;
@@ -50,6 +50,17 @@ const button = css`
color: ${theme.palette.action.disabled};
}
`;
+const colorIconVariant = {
+ outlined: css`
+ background-color: var(--color-main);
+ `,
+ contained: css`
+ background-color: var(--color-contrastText);
+ `,
+ text: css`
+ background-color: var(--color-main);
+ `,
+};
const colorVariant = {
outlined: css`
@@ -90,6 +101,47 @@ const colorVariant = {
`,
};
+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)};
+ `,
+ },
+};
const sizeVariant = {
outlined: {
small: css`
@@ -162,12 +214,18 @@ export function Button({
css`
margin-right: 8px;
margin-left: -4px;
+ mask: var(--image) no-repeat center;
`,
+ colorIconVariant[variant],
+ sizeIconVariant[variant][size],
style,
].join(" ")}
- >
- {sip}
- </span>
+ style={{
+ "--image": `url("${sip}")`,
+ "--color-main": theme.palette[color].main,
+ "--color-contrastText": theme.palette[color].contrastText,
+ }}
+ />
);
const endIcon = eip && (
<span
@@ -175,12 +233,19 @@ export function Button({
css`
margin-right: -4px;
margin-left: 8px;
+ mask: var(--image) no-repeat center;
`,
+ colorIconVariant[variant],
+ sizeIconVariant[variant][size],
style,
].join(" ")}
- >
- {eip}
- </span>
+ style={{
+ "--image": `url("${eip}")`,
+ "--color-main": theme.palette[color].main,
+ "--color-contrastText": theme.palette[color].contrastText,
+ "--color-dark": theme.palette[color].dark,
+ }}
+ />
);
return (
<button
@@ -196,8 +261,8 @@ export function Button({
].join(" ")}
style={{
"--color-main": theme.palette[color].main,
- "--color-main-alpha-half": alpha(theme.palette[color].main, 0.5),
"--color-contrastText": theme.palette[color].contrastText,
+ "--color-main-alpha-half": alpha(theme.palette[color].main, 0.5),
"--color-dark": theme.palette[color].dark,
"--color-main-alpha-opacity": alpha(
theme.palette[color].main,
diff --git a/packages/taler-wallet-webextension/src/mui/TextField.stories.tsx b/packages/taler-wallet-webextension/src/mui/TextField.stories.tsx
new file mode 100644
index 000000000..a2f7e1e66
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/TextField.stories.tsx
@@ -0,0 +1,108 @@
+/*
+ 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 { styled } from "@linaria/react";
+import { Fragment, h } from "preact";
+import { useState } from "preact/hooks";
+import { TextField, Props } from "./TextField";
+
+export default {
+ title: "mui/TextField",
+ component: TextField,
+};
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ & > * {
+ margin: 20px;
+ }
+`;
+
+const BasicExample = (variant: Props["variant"]) => {
+ const [value, onChange] = useState("");
+ return (
+ <Container>
+ <TextField variant={variant} label="Name" {...{ value, onChange }} />
+ <TextField
+ variant={variant}
+ type="password"
+ label="Password"
+ {...{ value, onChange }}
+ />
+ <TextField
+ disabled
+ variant={variant}
+ label="Country"
+ helperText="this is disabled"
+ value="disabled"
+ />
+ <TextField
+ error
+ variant={variant}
+ label="Something"
+ {...{ value, onChange }}
+ />
+ <TextField
+ error
+ disabled
+ variant={variant}
+ label="Disabled and Error"
+ value="disabled with error"
+ helperText="this field has an error"
+ />
+ <TextField
+ variant={variant}
+ required
+ label="Name"
+ {...{ value, onChange }}
+ helperText="this field is required"
+ />
+ </Container>
+ );
+};
+
+export const Standard = () => BasicExample("standard");
+export const Filled = () => BasicExample("filled");
+export const Outlined = () => BasicExample("outlined");
+
+export const Color = () => (
+ <Container>
+ <TextField
+ variant="standard"
+ label="Outlined secondary"
+ color="secondary"
+ focused
+ />
+ <TextField
+ label="Filled success"
+ variant="standard"
+ color="success"
+ focused
+ />
+ <TextField
+ label="Standard warning"
+ variant="standard"
+ color="warning"
+ focused
+ />
+ </Container>
+);
diff --git a/packages/taler-wallet-webextension/src/mui/TextField.tsx b/packages/taler-wallet-webextension/src/mui/TextField.tsx
new file mode 100644
index 000000000..ada8d5d85
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/TextField.tsx
@@ -0,0 +1,69 @@
+import { ComponentChildren, h, VNode } from "preact";
+import { FormControl } from "./input/FormControl";
+import { FormHelperText } from "./input/FormHelperText";
+import { InputFilled } from "./input/InputFilled";
+import { InputLabel } from "./input/InputLabel";
+import { InputOutlined } from "./input/InputOutlined";
+import { InputStandard } from "./input/InputStandard";
+import { SelectFilled } from "./input/SelectFilled";
+import { SelectOutlined } from "./input/SelectOutlined";
+import { SelectStandard } from "./input/SelectStandard";
+import { Colors } from "./style";
+
+export interface Props {
+ autoComplete?: string;
+ autoFocus?: boolean;
+ color?: Colors;
+ disabled?: boolean;
+ error?: boolean;
+ fullWidth?: boolean;
+ helperText?: VNode | string;
+ id?: string;
+ label?: VNode | string;
+ margin?: "dense" | "normal" | "none";
+ maxRows?: number;
+ minRows?: number;
+ multiline?: boolean;
+ onChange?: (s: string) => void;
+ placeholder?: string;
+ required?: boolean;
+ focused?: boolean;
+ rows?: number;
+ select?: boolean;
+ type?: string;
+ value?: string;
+ variant?: "filled" | "outlined" | "standard";
+ children?: ComponentChildren;
+}
+
+export function TextField({
+ label,
+ select,
+ helperText,
+ children,
+ variant = "standard",
+ ...props
+}: Props): VNode {
+ // htmlFor={id} id={inputLabelId}
+ const Input = select ? selectVariant[variant] : inputVariant[variant];
+ // console.log("variant", Input);
+ return (
+ <FormControl {...props}>
+ {label && <InputLabel>{label}</InputLabel>}
+ <Input {...props}>{children}</Input>
+ {helperText && <FormHelperText>{helperText}</FormHelperText>}
+ </FormControl>
+ );
+}
+
+const inputVariant = {
+ standard: InputStandard,
+ filled: InputFilled,
+ outlined: InputOutlined,
+};
+
+const selectVariant = {
+ standard: SelectStandard,
+ filled: SelectFilled,
+ outlined: SelectOutlined,
+};
diff --git a/packages/taler-wallet-webextension/src/mui/input/FormControl.tsx b/packages/taler-wallet-webextension/src/mui/input/FormControl.tsx
new file mode 100644
index 000000000..7a8395705
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/input/FormControl.tsx
@@ -0,0 +1,156 @@
+import { css } from "@linaria/core";
+import { ComponentChildren, createContext, h } from "preact";
+import { useContext, useState } from "preact/hooks";
+import { Colors } from "../style";
+
+export interface Props {
+ color: Colors;
+ disabled: boolean;
+ error: boolean;
+ focused: boolean;
+ fullWidth: boolean;
+ hiddenLabel: boolean;
+ required: boolean;
+ variant: "filled" | "outlined" | "standard";
+ margin: "none" | "normal" | "dense";
+ size: "medium" | "small";
+ children: ComponentChildren;
+}
+
+export const root = css`
+ display: inline-flex;
+ flex-direction: column;
+ position: relative;
+ min-width: 0px;
+ padding: 0px;
+ margin: 0px;
+ border: 0px;
+ vertical-align: top;
+`;
+
+const marginVariant = {
+ none: "",
+ normal: css`
+ margin-top: 16px;
+ margin-bottom: 8px;
+ `,
+ dense: css`
+ margin-top: 8px;
+ margin-bottom: 4px;
+ `,
+};
+const fullWidthStyle = css`
+ width: 100%;
+`;
+
+export function FormControl({
+ color = "primary",
+ disabled = false,
+ error = false,
+ focused: visuallyFocused,
+ fullWidth = false,
+ hiddenLabel = false,
+ margin = "none",
+ required = false,
+ size = "medium",
+ variant = "standard",
+ children,
+}: Partial<Props>) {
+ const [filled, setFilled] = useState(false);
+ const [focusedState, setFocused] = useState(false);
+ const focused =
+ visuallyFocused !== undefined && !disabled ? visuallyFocused : focusedState;
+
+ const value: FCCProps = {
+ color,
+ disabled,
+ error,
+ filled,
+ focused,
+ fullWidth,
+ hiddenLabel,
+ size,
+ onBlur: () => {
+ setFocused(false);
+ },
+ onEmpty: () => {
+ setFilled(false);
+ },
+ onFilled: () => {
+ setFilled(true);
+ },
+ onFocus: () => {
+ setFocused(true);
+ },
+ required,
+ variant,
+ };
+
+ return (
+ <div
+ class={[
+ root,
+ marginVariant[margin],
+ fullWidth ? fullWidthStyle : "",
+ ].join(" ")}
+ >
+ <FormControlContext.Provider value={value}>
+ {children}
+ </FormControlContext.Provider>
+ </div>
+ );
+}
+
+export interface FCCProps {
+ // adornedStart,
+ // setAdornedStart,
+ color: Colors;
+ disabled: boolean;
+ error: boolean;
+ filled: boolean;
+ focused: boolean;
+ fullWidth: boolean;
+ hiddenLabel: boolean;
+ size: "medium" | "small";
+ onBlur: () => void;
+ onEmpty: () => void;
+ onFilled: () => void;
+ onFocus: () => void;
+ // registerEffect,
+ required: boolean;
+ variant: "filled" | "outlined" | "standard";
+}
+
+export const FormControlContext = createContext<FCCProps | null>(null);
+
+const defaultContextValue: FCCProps = {
+ color: "primary",
+ disabled: false,
+ error: false,
+ filled: false,
+ focused: false,
+ fullWidth: false,
+ hiddenLabel: false,
+ size: "medium",
+ onBlur: () => {},
+ onEmpty: () => {},
+ onFilled: () => {},
+ onFocus: () => {},
+ required: false,
+ variant: "outlined",
+};
+
+function withoutUndefinedProperties(obj: any) {
+ return Object.keys(obj).reduce((acc, key) => {
+ const _acc: any = acc;
+ if (obj[key] !== undefined) _acc[key] = obj[key];
+ return _acc;
+ }, {});
+}
+
+export function useFormControl(props: Partial<FCCProps> = {}): FCCProps {
+ const ctx = useContext(FormControlContext);
+ const cleanedProps = withoutUndefinedProperties(props);
+ if (!ctx) return { ...defaultContextValue, ...cleanedProps };
+ return { ...ctx, ...cleanedProps };
+}
diff --git a/packages/taler-wallet-webextension/src/mui/input/FormHelperText.tsx b/packages/taler-wallet-webextension/src/mui/input/FormHelperText.tsx
new file mode 100644
index 000000000..4854a6384
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/input/FormHelperText.tsx
@@ -0,0 +1,54 @@
+import { css } from "@linaria/core";
+import { ComponentChildren, h } from "preact";
+import { theme } from "../style";
+import { useFormControl } from "./FormControl";
+
+const root = css`
+ color: ${theme.palette.text.secondary};
+ text-align: left;
+ margin-top: 3px;
+ margin-bottom: 0px;
+ margin-right: 0px;
+ margin-left: 0px;
+`;
+const disabledStyle = css`
+ color: ${theme.palette.text.disabled};
+`;
+const errorStyle = css`
+ color: ${theme.palette.error.main};
+`;
+const sizeSmallStyle = css`
+ margin-top: 4px;
+`;
+const containedStyle = css`
+ margin-right: 14px;
+ margin-left: 14px;
+`;
+
+interface Props {
+ disabled?: boolean;
+ error?: boolean;
+ filled?: boolean;
+ focused?: boolean;
+ margin?: "dense";
+ required?: boolean;
+ children: ComponentChildren;
+}
+export function FormHelperText({ children, ...props }: Props) {
+ const fcs = useFormControl(props);
+ const contained = fcs.variant === "filled" || fcs.variant === "outlined";
+ return (
+ <p
+ class={[
+ root,
+ theme.typography.caption,
+ fcs.disabled && disabledStyle,
+ fcs.error && errorStyle,
+ fcs.size === "small" && sizeSmallStyle,
+ contained && containedStyle,
+ ].join(" ")}
+ >
+ {children}
+ </p>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/mui/input/FormLabel.tsx b/packages/taler-wallet-webextension/src/mui/input/FormLabel.tsx
new file mode 100644
index 000000000..e5ca53263
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/input/FormLabel.tsx
@@ -0,0 +1,67 @@
+import { css } from "@linaria/core";
+import { ComponentChildren, h } from "preact";
+import { Colors, theme } from "../style";
+import { useFormControl } from "./FormControl";
+
+export interface Props {
+ class?: string;
+ disabled?: boolean;
+ error?: boolean;
+ filled?: boolean;
+ focused?: boolean;
+ required?: boolean;
+ color?: Colors;
+ children?: ComponentChildren;
+}
+
+const root = css`
+ color: ${theme.palette.text.secondary};
+ line-height: 1.4375em;
+ padding: 0px;
+ position: relative;
+ &[data-focused] {
+ color: var(--color-main);
+ }
+ &[data-disabled] {
+ color: ${theme.palette.text.disabled};
+ }
+ &[data-error] {
+ color: ${theme.palette.error.main};
+ }
+`;
+
+export function FormLabel({
+ disabled,
+ error,
+ filled,
+ focused,
+ required,
+ color,
+ class: _class,
+ children,
+ ...rest
+}: Props) {
+ const fcs = useFormControl({
+ disabled,
+ error,
+ filled,
+ focused,
+ required,
+ color,
+ });
+ return (
+ <label
+ data-focused={fcs.focused}
+ data-error={fcs.error}
+ data-disabled={fcs.disabled}
+ class={[_class, root, theme.typography.body1].join(" ")}
+ {...rest}
+ style={{
+ "--color-main": theme.palette[fcs.color].main,
+ }}
+ >
+ {children}
+ {fcs.required && <span data-error={fcs.error}>&thinsp;{"*"}</span>}
+ </label>
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/mui/input/InputBase.tsx b/packages/taler-wallet-webextension/src/mui/input/InputBase.tsx
new file mode 100644
index 000000000..5714eb1ba
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/input/InputBase.tsx
@@ -0,0 +1,258 @@
+import { css } from "@linaria/core";
+import { h, JSX } from "preact";
+import { useEffect, useLayoutEffect, useState } from "preact/hooks";
+import { theme } from "../style";
+import { FormControlContext, useFormControl } from "./FormControl";
+
+const rootStyle = css`
+ color: ${theme.palette.text.primary};
+ line-height: 1.4375em;
+ box-sizing: border-box;
+ position: relative;
+ cursor: text;
+ display: inline-flex;
+ align-items: center;
+`;
+const rootDisabledStyle = css`
+ color: ${theme.palette.text.disabled};
+ cursor: default;
+`;
+const rootMultilineStyle = css`
+ padding: 4px 0 5px;
+`;
+const fullWidthStyle = css`
+ width: "100%";
+`;
+
+export function InputBaseRoot({
+ class: _class,
+ disabled,
+ error,
+ multiline,
+ focused,
+ fullWidth,
+ children,
+}: any) {
+ const fcs = useFormControl({});
+ return (
+ <div
+ data-disabled={disabled}
+ data-focused={focused}
+ data-error={error}
+ class={[
+ _class,
+ rootStyle,
+ theme.typography.body1,
+ disabled && rootDisabledStyle,
+ multiline && rootMultilineStyle,
+ fullWidth && fullWidthStyle,
+ ].join(" ")}
+ style={{
+ "--color-main": theme.palette[fcs.color].main,
+ }}
+ >
+ {children}
+ </div>
+ );
+}
+
+const componentStyle = css`
+ font: inherit;
+ letter-spacing: inherit;
+ color: currentColor;
+ padding: 4px 0 5px;
+ border: 0px;
+ box-sizing: content-box;
+ background: none;
+ height: 1.4375em;
+ margin: 0px;
+ -webkit-tap-highlight-color: transparent;
+ display: block;
+ min-width: 0px;
+ width: 100%;
+ animation-name: "auto-fill-cancel";
+ animation-duration: 10ms;
+
+ @keyframes auto-fill {
+ from {
+ display: block;
+ }
+ }
+ @keyframes auto-fill-cancel {
+ from {
+ display: block;
+ }
+ }
+ &::placeholder {
+ color: "currentColor";
+ opacity: ${theme.palette.mode === "light" ? 0.42 : 0.5};
+ transition: ${theme.transitions.create("opacity", {
+ duration: theme.transitions.duration.shorter,
+ })};
+ }
+ &:focus {
+ outline: 0;
+ }
+ &:invalid {
+ box-shadow: none;
+ }
+ &::-webkit-search-decoration {
+ -webkit-appearance: none;
+ }
+ &:-webkit-autofill {
+ animation-duration: 5000s;
+ animation-name: auto-fill;
+ }
+`;
+const componentDisabledStyle = css`
+ opacity: 1;
+ --webkit-text-fill-color: ${theme.palette.text.disabled};
+`;
+const componentSmallStyle = css`
+ padding-top: 1px;
+`;
+const componentMultilineStyle = css`
+ height: auto;
+ resize: none;
+ padding: 0px;
+ padding-top: 0px;
+`;
+const searchStyle = css`
+ -moz-appearance: textfield;
+ -webkit-appearance: textfield;
+`;
+
+export function InputBaseComponent({
+ disabled,
+ size,
+ multiline,
+ type,
+ ...props
+}: any) {
+ return (
+ <input
+ disabled={disabled}
+ type={type}
+ class={[
+ componentStyle,
+ disabled && componentDisabledStyle,
+ size === "small" && componentSmallStyle,
+ multiline && componentMultilineStyle,
+ type === "search" && searchStyle,
+ ].join(" ")}
+ {...props}
+ />
+ );
+}
+
+export function InputBase({
+ Root = InputBaseRoot,
+ Input,
+ onChange,
+ name,
+ placeholder,
+ readOnly,
+ onKeyUp,
+ onKeyDown,
+ rows,
+ type = "text",
+ value,
+ onClick,
+ ...props
+}: any) {
+ const fcs = useFormControl(props);
+ // const [focused, setFocused] = useState(false);
+ useLayoutEffect(() => {
+ if (value && value !== "") {
+ fcs.onFilled();
+ } else {
+ fcs.onEmpty();
+ }
+ }, [value]);
+
+ const handleFocus = (event: JSX.TargetedFocusEvent<EventTarget>) => {
+ // Fix a bug with IE11 where the focus/blur events are triggered
+ // while the component is disabled.
+ if (fcs.disabled) {
+ event.stopPropagation();
+ return;
+ }
+
+ // if (onFocus) {
+ // onFocus(event);
+ // }
+ // if (inputPropsProp.onFocus) {
+ // inputPropsProp.onFocus(event);
+ // }
+
+ fcs.onFocus();
+ };
+
+ const handleBlur = () => {
+ // if (onBlur) {
+ // onBlur(event);
+ // }
+ // if (inputPropsProp.onBlur) {
+ // inputPropsProp.onBlur(event);
+ // }
+
+ fcs.onBlur();
+ };
+
+ const handleChange = (
+ event: JSX.TargetedEvent<HTMLElement & { value?: string }>,
+ ) => {
+ // if (inputPropsProp.onChange) {
+ // inputPropsProp.onChange(event, ...args);
+ // }
+
+ // Perform in the willUpdate
+ if (onChange) {
+ onChange(event.currentTarget.value);
+ }
+ };
+
+ const handleClick = (
+ event: JSX.TargetedMouseEvent<HTMLElement & { value?: string }>,
+ ) => {
+ // if (inputRef.current && event.currentTarget === event.target) {
+ // inputRef.current.focus();
+ // }
+
+ if (onClick) {
+ onClick(event.currentTarget.value);
+ }
+ };
+
+ if (!Input) {
+ Input = props.multiline ? TextareaAutoSize : InputBaseComponent;
+ }
+
+ return (
+ <Root {...fcs} onClick={handleClick}>
+ <FormControlContext.Provider value={null}>
+ <Input
+ aria-invalid={fcs.error}
+ // aria-describedby={}
+ disabled={fcs.disabled}
+ name={name}
+ placeholder={placeholder}
+ readOnly={readOnly}
+ required={fcs.required}
+ rows={rows}
+ value={value}
+ onKeyDown={onKeyDown}
+ onKeyUp={onKeyUp}
+ type={type}
+ onChange={handleChange}
+ onBlur={handleBlur}
+ onFocus={handleFocus}
+ />
+ </FormControlContext.Provider>
+ </Root>
+ );
+}
+
+export function TextareaAutoSize() {
+ return <input onClick={(e) => null} />;
+}
diff --git a/packages/taler-wallet-webextension/src/mui/input/InputFilled.tsx b/packages/taler-wallet-webextension/src/mui/input/InputFilled.tsx
new file mode 100644
index 000000000..5c50a8b72
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/input/InputFilled.tsx
@@ -0,0 +1,5 @@
+import { h, VNode } from "preact";
+
+export function InputFilled(): VNode {
+ return <div />;
+}
diff --git a/packages/taler-wallet-webextension/src/mui/input/InputLabel.tsx b/packages/taler-wallet-webextension/src/mui/input/InputLabel.tsx
new file mode 100644
index 000000000..c70c5bfc0
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/input/InputLabel.tsx
@@ -0,0 +1,98 @@
+import { css } from "@linaria/core";
+import { ComponentChildren, h } from "preact";
+import { Colors, theme } from "../style";
+import { useFormControl } from "./FormControl";
+import { FormLabel } from "./FormLabel";
+
+const root = css`
+ display: block;
+ transform-origin: top left;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 100%;
+
+ &[data-form-control] {
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ transform: translate(0, 20px) scale(1);
+ }
+ &[data-size="small"] {
+ transform: translate(0, 17px) scale(1);
+ }
+ &[data-shrink] {
+ transform: translate(0, -1.5px) scale(0.75);
+ transform-origin: top left;
+ max-width: 133%;
+ }
+ &:not([data-disable-animation]) {
+ transition: ${theme.transitions.create(
+ ["color", "transform", "max-width"],
+ {
+ duration: theme.transitions.duration.shorter,
+ easing: theme.transitions.easing.easeOut,
+ },
+ )};
+ }
+ &[data-variant="filled"] {
+ z-index: 1;
+ pointer-events: none;
+ transform: translate(12px, 16px) scale(1);
+ max-width: calc(100% - 24px);
+ &[data-size="small"] {
+ transform: translate(12px, 13px) scale(1);
+ }
+ &[data-shrink] {
+ user-select: none;
+ pointer-events: auto;
+ transform: translate(12px, 7px) scale(0.75);
+ max-width: calc(133% - 24px);
+ &[data-size="small"] {
+ transform: translate(12px, 4px) scale(0.75);
+ }
+ }
+ }
+ &[data-variant="outlined"] {
+ z-index: 1;
+ pointer-events: none;
+ transform: translate(14px, 16px) scale(1);
+ max-width: calc(100% - 24px);
+ &[data-size="small"] {
+ transform: translate(14px, 9px) scale(1);
+ }
+ &[data-shrink] {
+ user-select: none;
+ pointer-events: auto;
+ transform: translate(14px, -9px) scale(0.75);
+ max-width: calc(133% - 24px);
+ }
+ }
+`;
+
+interface InputLabelProps {
+ color: Colors;
+ disableAnimation: boolean;
+ disabled: boolean;
+ error: boolean;
+ focused: boolean;
+ margin: boolean;
+ required: boolean;
+ shrink: boolean;
+ variant: "filled" | "outlined" | "standard";
+ children: ComponentChildren;
+}
+export function InputLabel(props: Partial<InputLabelProps>) {
+ const fcs = useFormControl(props);
+ return (
+ <FormLabel
+ data-form-control={!!fcs}
+ data-size={fcs.size}
+ data-shrink={props.shrink || fcs.filled || fcs.focused}
+ data-disable-animation={props.disableAnimation}
+ data-variant={fcs.variant}
+ class={root}
+ {...props}
+ />
+ );
+}
diff --git a/packages/taler-wallet-webextension/src/mui/input/InputOutlined.tsx b/packages/taler-wallet-webextension/src/mui/input/InputOutlined.tsx
new file mode 100644
index 000000000..3b40ffc70
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/input/InputOutlined.tsx
@@ -0,0 +1,5 @@
+import { h, VNode } from "preact";
+
+export function InputOutlined(): VNode {
+ return <div />;
+}
diff --git a/packages/taler-wallet-webextension/src/mui/input/InputStandard.tsx b/packages/taler-wallet-webextension/src/mui/input/InputStandard.tsx
new file mode 100644
index 000000000..ba5145719
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/input/InputStandard.tsx
@@ -0,0 +1,124 @@
+import { css } from "@linaria/core";
+import { h, VNode } from "preact";
+import { Colors, theme } from "../style";
+import { useFormControl } from "./FormControl";
+import { InputBase, InputBaseComponent, InputBaseRoot } from "./InputBase";
+
+export interface Props {
+ autoComplete?: string;
+ autoFocus?: boolean;
+ color?: Colors;
+ defaultValue?: string;
+ disabled?: boolean;
+ disableUnderline?: boolean;
+ endAdornment?: VNode;
+ error?: boolean;
+ fullWidth?: boolean;
+ id?: string;
+ margin?: "dense" | "normal" | "none";
+ maxRows?: number;
+ minRows?: number;
+ multiline?: boolean;
+ name?: string;
+ onChange?: (s: string) => void;
+ placeholder?: string;
+ readOnly?: boolean;
+ required?: boolean;
+ rows?: number;
+ startAdornment?: VNode;
+ type?: string;
+ value?: string;
+}
+export function InputStandard({
+ type = "text",
+ multiline,
+ ...props
+}: Props): VNode {
+ const fcs = useFormControl(props);
+ return (
+ <InputBase
+ Root={Root}
+ Input={Input}
+ fullWidth={fcs.fullWidth}
+ multiline={multiline}
+ type={type}
+ {...props}
+ />
+ );
+}
+
+const rootStyle = css`
+ position: relative;
+`;
+const formControlStyle = css`
+ label + & {
+ margin-top: 16px;
+ }
+`;
+const underlineStyle = css`
+ &:after {
+ border-bottom: 2px solid var(--color-main);
+ left: 0px;
+ bottom: 0px;
+ content: "";
+ position: absolute;
+ right: 0px;
+ transform: scaleX(0);
+ transition: ${theme.transitions.create("transform", {
+ duration: theme.transitions.duration.shorter,
+ easing: theme.transitions.easing.easeOut,
+ })};
+ pointer-events: none;
+ }
+ &[data-focused]:after {
+ transform: scaleX(1);
+ }
+ &[data-error]:after {
+ border-bottom-color: ${theme.palette.error.main};
+ transform: scaleY(1);
+ }
+ &:before {
+ border-bottom: 1px solid
+ ${theme.palette.mode === "light"
+ ? "rgba(0, 0, 0, 0.42)"
+ : "rgba(255, 255, 255, 0.7)"};
+ left: 0px;
+ bottom: 0px;
+ right: 0px;
+ content: "\\00a0";
+ position: absolute;
+ transition: ${theme.transitions.create("border-bottom-color", {
+ duration: theme.transitions.duration.shorter,
+ })};
+ pointer-events: none;
+ }
+ &:hover:not([data-disabled]:before) {
+ border-bottom: 2px solid var(--color-main);
+ @media (hover: none) {
+ border-bottom: 1px solid
+ ${theme.palette.mode === "light"
+ ? "rgba(0, 0, 0, 0.42)"
+ : "rgba(255, 255, 255, 0.7)"};
+ }
+ }
+ &[data-disabled]:before {
+ border-bottom-style: solid;
+ }
+`;
+
+function Root({ disabled, focused, error, children }: any) {
+ return (
+ <InputBaseRoot
+ disabled={disabled}
+ focused={focused}
+ error={error}
+ class={[rootStyle, formControlStyle, underlineStyle].join(" ")}
+ >
+ {children}
+ </InputBaseRoot>
+ );
+}
+
+function Input(props: any) {
+ return <InputBaseComponent {...props} />;
+}
diff --git a/packages/taler-wallet-webextension/src/mui/input/SelectFilled.tsx b/packages/taler-wallet-webextension/src/mui/input/SelectFilled.tsx
new file mode 100644
index 000000000..28b1859f8
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/input/SelectFilled.tsx
@@ -0,0 +1,5 @@
+import { h, VNode } from "preact";
+
+export function SelectFilled(): VNode {
+ return <div />;
+}
diff --git a/packages/taler-wallet-webextension/src/mui/input/SelectOutlined.tsx b/packages/taler-wallet-webextension/src/mui/input/SelectOutlined.tsx
new file mode 100644
index 000000000..10ee4015c
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/input/SelectOutlined.tsx
@@ -0,0 +1,5 @@
+import { h, VNode } from "preact";
+
+export function SelectOutlined(): VNode {
+ return <div />;
+}
diff --git a/packages/taler-wallet-webextension/src/mui/input/SelectStandard.tsx b/packages/taler-wallet-webextension/src/mui/input/SelectStandard.tsx
new file mode 100644
index 000000000..72cb635df
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/mui/input/SelectStandard.tsx
@@ -0,0 +1,5 @@
+import { h, VNode } from "preact";
+
+export function SelectStandard(): VNode {
+ return <div />;
+}
diff --git a/packages/taler-wallet-webextension/src/mui/style.tsx b/packages/taler-wallet-webextension/src/mui/style.tsx
index 5f9cd2244..3fa3b7e33 100644
--- a/packages/taler-wallet-webextension/src/mui/style.tsx
+++ b/packages/taler-wallet-webextension/src/mui/style.tsx
@@ -12,6 +12,14 @@ import {
} from "./colors/constants";
import { getContrastRatio } from "./colors/manipulation";
+export type Colors =
+ | "primary"
+ | "secondary"
+ | "success"
+ | "error"
+ | "info"
+ | "warning";
+
export function round(value: number): number {
return Math.round(value * 1e5) / 1e5;
}
@@ -386,6 +394,14 @@ function createTheme() {
`,
/* just of caseAllCaps */
// button: buildVariant(fontWeightMedium, 14, 1.75, 0.4, caseAllCaps),
+
+ caption: css`
+ font-family: "Roboto", "Helvetica", "Arial", sans-serif;
+ font-weight: ${fontWeightMedium};
+ font-size: ${pxToRem(12)};
+ line-height: 1.66;
+ letter-spacing: ${round(0.4 / 12)}em;
+ `,
// caption: buildVariant(fontWeightRegular, 12, 1.66, 0.4),
// overline: buildVariant(fontWeightRegular, 12, 2.66, 1, caseAllCaps),
};