mui text field, standard variation
This commit is contained in:
parent
98761a2b8d
commit
65eb64cd07
@ -37,9 +37,6 @@
|
||||
"@babel/plugin-transform-react-jsx-source": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@gnu-taler/pogen": "workspace:*",
|
||||
"@types/chai": "^4.3.0",
|
||||
"chai": "^4.3.6",
|
||||
"polished": "^4.1.4",
|
||||
"@linaria/babel-preset": "3.0.0-beta.4",
|
||||
"@linaria/core": "3.0.0-beta.4",
|
||||
"@linaria/react": "3.0.0-beta.4",
|
||||
@ -57,14 +54,17 @@
|
||||
"@storybook/preact": "6.4.18",
|
||||
"@testing-library/preact": "^2.0.1",
|
||||
"@testing-library/preact-hooks": "^1.1.0",
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/chrome": "0.0.176",
|
||||
"@types/history": "^4.7.8",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^17.0.8",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-transform-react-jsx": "^6.24.1",
|
||||
"chai": "^4.3.6",
|
||||
"mocha": "^9.2.0",
|
||||
"nyc": "^15.1.0",
|
||||
"polished": "^4.1.4",
|
||||
"preact-cli": "^3.3.5",
|
||||
"preact-render-to-string": "^5.1.19",
|
||||
"rimraf": "^3.0.2",
|
||||
|
133
packages/taler-wallet-webextension/src/mui/Button.stories.tsx
Normal file
133
packages/taler-wallet-webextension/src/mui/Button.stories.tsx
Normal file
@ -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>
|
||||
);
|
@ -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,
|
||||
|
108
packages/taler-wallet-webextension/src/mui/TextField.stories.tsx
Normal file
108
packages/taler-wallet-webextension/src/mui/TextField.stories.tsx
Normal file
@ -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>
|
||||
);
|
69
packages/taler-wallet-webextension/src/mui/TextField.tsx
Normal file
69
packages/taler-wallet-webextension/src/mui/TextField.tsx
Normal file
@ -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,
|
||||
};
|
156
packages/taler-wallet-webextension/src/mui/input/FormControl.tsx
Normal file
156
packages/taler-wallet-webextension/src/mui/input/FormControl.tsx
Normal file
@ -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 };
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -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}> {"*"}</span>}
|
||||
</label>
|
||||
);
|
||||
}
|
258
packages/taler-wallet-webextension/src/mui/input/InputBase.tsx
Normal file
258
packages/taler-wallet-webextension/src/mui/input/InputBase.tsx
Normal file
@ -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} />;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { h, VNode } from "preact";
|
||||
|
||||
export function InputFilled(): VNode {
|
||||
return <div />;
|
||||
}
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { h, VNode } from "preact";
|
||||
|
||||
export function InputOutlined(): VNode {
|
||||
return <div />;
|
||||
}
|
@ -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} />;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { h, VNode } from "preact";
|
||||
|
||||
export function SelectFilled(): VNode {
|
||||
return <div />;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { h, VNode } from "preact";
|
||||
|
||||
export function SelectOutlined(): VNode {
|
||||
return <div />;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { h, VNode } from "preact";
|
||||
|
||||
export function SelectStandard(): VNode {
|
||||
return <div />;
|
||||
}
|
@ -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),
|
||||
};
|
||||
|
@ -57,7 +57,7 @@ function main(): void {
|
||||
}
|
||||
}
|
||||
|
||||
setupI18n("en-US", strings);
|
||||
setupI18n("en", strings);
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", main);
|
||||
|
@ -73,7 +73,7 @@ function main(): void {
|
||||
}
|
||||
}
|
||||
|
||||
setupI18n("en-US", strings);
|
||||
setupI18n("en", strings);
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", main);
|
||||
@ -102,7 +102,6 @@ function Application(): VNode {
|
||||
return (
|
||||
<TranslationProvider>
|
||||
<DevContextProvider>
|
||||
{({ devMode }: { devMode: boolean }) => (
|
||||
<IoCProviderForRuntime>
|
||||
{/* <Match/> won't work in the first render if <Router /> is not called first */}
|
||||
{/* https://github.com/preactjs/preact-router/issues/415 */}
|
||||
@ -167,10 +166,7 @@ function Application(): VNode {
|
||||
component={TransactionPage}
|
||||
goToWalletHistory={(currency?: string) => {
|
||||
route(
|
||||
Pages.balance_history.replace(
|
||||
":currency",
|
||||
currency || "",
|
||||
),
|
||||
Pages.balance_history.replace(":currency", currency || ""),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
@ -283,7 +279,6 @@ function Application(): VNode {
|
||||
</Router>
|
||||
</WalletBox>
|
||||
</IoCProviderForRuntime>
|
||||
)}
|
||||
</DevContextProvider>
|
||||
</TranslationProvider>
|
||||
);
|
||||
|
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
|
After Width: | Height: | Size: 192 B |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
|
After Width: | Height: | Size: 152 B |
Loading…
Reference in New Issue
Block a user