first banner implementation with mui
This commit is contained in:
parent
6bc244cc1e
commit
1607c728bc
@ -128,6 +128,11 @@ export const decorators = [
|
||||
<Story />
|
||||
</div>
|
||||
}
|
||||
if (kind.startsWith('mui')) {
|
||||
return <div style={{ display: 'flex', flexWrap: 'wrap' }}>
|
||||
<Story />
|
||||
</div>
|
||||
}
|
||||
if (kind.startsWith('wallet')) {
|
||||
const path = /wallet(\/.*).*/.exec(kind)[1];
|
||||
return <div class="wallet-container">
|
||||
|
@ -33,10 +33,13 @@
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gnu-taler/pogen": "workspace:*",
|
||||
"@babel/core": "7.13.16",
|
||||
"@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",
|
||||
|
@ -61,9 +61,6 @@ export enum Pages {
|
||||
}
|
||||
|
||||
export function PopupNavBar({ path = "" }: { path?: string }): VNode {
|
||||
const innerUrl = chrome.runtime
|
||||
? new URL(chrome.runtime.getURL("/static/wallet.html#/settings")).href
|
||||
: "#";
|
||||
return (
|
||||
<NavigationHeader>
|
||||
<a href="/balance" class={path.startsWith("/balance") ? "active" : ""}>
|
||||
@ -73,7 +70,7 @@ export function PopupNavBar({ path = "" }: { path?: string }): VNode {
|
||||
<i18n.Translate>Backup</i18n.Translate>
|
||||
</a>
|
||||
<a />
|
||||
<a href={innerUrl} target="_blank" rel="noreferrer">
|
||||
<a href="/settings">
|
||||
<div class="settings-icon" title={i18n.str`Settings`} />
|
||||
</a>
|
||||
</NavigationHeader>
|
||||
|
41
packages/taler-wallet-webextension/src/components/Banner.tsx
Normal file
41
packages/taler-wallet-webextension/src/components/Banner.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { h, Fragment, VNode } from "preact";
|
||||
import { Divider } from "../mui/Divider";
|
||||
import { Button } from "./styled/index.js";
|
||||
import { Typography } from "../mui/Typography";
|
||||
import { Avatar } from "../mui/Avatar";
|
||||
import { Grid } from "../mui/Grid";
|
||||
import { Paper } from "../mui/Paper";
|
||||
|
||||
function SignalWifiOffIcon(): VNode {
|
||||
return <Fragment />;
|
||||
}
|
||||
|
||||
function Banner({}: {}) {
|
||||
return (
|
||||
<Fragment>
|
||||
<Paper elevation={0} /*className={classes.paper}*/>
|
||||
<Grid container wrap="nowrap" spacing={16} alignItems="center">
|
||||
<Grid item>
|
||||
<Avatar /*className={classes.avatar}*/>
|
||||
<SignalWifiOffIcon />
|
||||
</Avatar>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography>
|
||||
You have lost connection to the internet. This app is offline.
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container justify="flex-end" spacing={8}>
|
||||
<Grid item>
|
||||
<Button color="primary">Turn on wifi</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
<Divider />
|
||||
{/* <CssBaseline /> */}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default Banner;
|
5
packages/taler-wallet-webextension/src/mui/Avatar.tsx
Normal file
5
packages/taler-wallet-webextension/src/mui/Avatar.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { h, Fragment, VNode, ComponentChildren } from "preact";
|
||||
|
||||
export function Avatar({}: { children: ComponentChildren }): VNode {
|
||||
return <Fragment />;
|
||||
}
|
215
packages/taler-wallet-webextension/src/mui/Button.tsx
Normal file
215
packages/taler-wallet-webextension/src/mui/Button.tsx
Normal file
@ -0,0 +1,215 @@
|
||||
import { ComponentChildren, h, VNode } from "preact";
|
||||
import { css } from "@linaria/core";
|
||||
import { theme, ripple } from "./style";
|
||||
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;
|
||||
variant?: "contained" | "outlined" | "text";
|
||||
color?: "primary" | "secondary" | "success" | "error" | "info" | "warning";
|
||||
}
|
||||
|
||||
const baseStyle = css`
|
||||
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;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
vertical-align: middle;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
`;
|
||||
|
||||
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};
|
||||
}
|
||||
`;
|
||||
|
||||
const colorVariant = {
|
||||
outlined: css`
|
||||
color: var(--color-main);
|
||||
border: 1px solid var(--color-main-alpha-half);
|
||||
&: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-dark);
|
||||
}
|
||||
&: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);
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
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)};
|
||||
`,
|
||||
},
|
||||
};
|
||||
|
||||
export function Button({
|
||||
children,
|
||||
disabled,
|
||||
startIcon: sip,
|
||||
endIcon: eip,
|
||||
variant = "text",
|
||||
size = "medium",
|
||||
color = "primary",
|
||||
}: Props): VNode {
|
||||
const style = css`
|
||||
user-select: none;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
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;
|
||||
`,
|
||||
style,
|
||||
].join(" ")}
|
||||
>
|
||||
{sip}
|
||||
</span>
|
||||
);
|
||||
const endIcon = eip && (
|
||||
<span
|
||||
class={[
|
||||
css`
|
||||
margin-right: -4px;
|
||||
margin-left: 8px;
|
||||
`,
|
||||
style,
|
||||
].join(" ")}
|
||||
>
|
||||
{eip}
|
||||
</span>
|
||||
);
|
||||
return (
|
||||
<button
|
||||
disabled={disabled}
|
||||
class={[
|
||||
theme.typography.button,
|
||||
theme.shape.borderRadius,
|
||||
ripple,
|
||||
baseStyle,
|
||||
button,
|
||||
colorVariant[variant],
|
||||
sizeVariant[variant][size],
|
||||
].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-dark": theme.palette[color].dark,
|
||||
"--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,
|
||||
),
|
||||
}}
|
||||
>
|
||||
{startIcon}
|
||||
{children}
|
||||
{endIcon}
|
||||
</button>
|
||||
);
|
||||
}
|
5
packages/taler-wallet-webextension/src/mui/Divider.tsx
Normal file
5
packages/taler-wallet-webextension/src/mui/Divider.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { h, Fragment, VNode } from "preact";
|
||||
|
||||
export function Divider(): VNode {
|
||||
return <Fragment />;
|
||||
}
|
13
packages/taler-wallet-webextension/src/mui/Grid.tsx
Normal file
13
packages/taler-wallet-webextension/src/mui/Grid.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { h, Fragment, VNode, ComponentChildren } from "preact";
|
||||
|
||||
export function Grid({}: {
|
||||
container?: boolean;
|
||||
wrap?: string;
|
||||
item?: boolean;
|
||||
spacing?: number;
|
||||
alignItems?: string;
|
||||
justify?: string;
|
||||
children: ComponentChildren;
|
||||
}): VNode {
|
||||
return <Fragment />;
|
||||
}
|
149
packages/taler-wallet-webextension/src/mui/Paper.stories.tsx
Normal file
149
packages/taler-wallet-webextension/src/mui/Paper.stories.tsx
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
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 { Paper } from "./Paper";
|
||||
import { createExample } from "../test-utils";
|
||||
import { h } from "preact";
|
||||
|
||||
export default {
|
||||
title: "mui/paper",
|
||||
component: Paper,
|
||||
};
|
||||
|
||||
export const BasicExample = () => (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
wrap: "nowrap",
|
||||
backgroundColor: "lightgray",
|
||||
width: "100%",
|
||||
padding: 10,
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Paper elevation={0}>
|
||||
<div style={{ height: 128, width: 128 }} />
|
||||
</Paper>
|
||||
<Paper>
|
||||
<div style={{ height: 128, width: 128 }} />
|
||||
</Paper>
|
||||
<Paper elevation={3}>
|
||||
<div style={{ height: 128, width: 128 }} />
|
||||
</Paper>
|
||||
<Paper elevation={8}>
|
||||
<div style={{ height: 128, width: 128 }} />
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Outlined = () => (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
wrap: "nowrap",
|
||||
backgroundColor: "lightgray",
|
||||
width: "100%",
|
||||
padding: 10,
|
||||
justifyContent: "space-around",
|
||||
}}
|
||||
>
|
||||
<Paper variant="outlined">
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
height: 128,
|
||||
width: 128,
|
||||
lineHeight: "128px",
|
||||
}}
|
||||
>
|
||||
round
|
||||
</div>
|
||||
</Paper>
|
||||
<Paper variant="outlined" square>
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
height: 128,
|
||||
width: 128,
|
||||
lineHeight: "128px",
|
||||
}}
|
||||
>
|
||||
square
|
||||
</div>
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Elevation = () => (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
backgroundColor: "lightgray",
|
||||
width: "100%",
|
||||
padding: 50,
|
||||
justifyContent: "space-around",
|
||||
}}
|
||||
>
|
||||
{[0, 1, 2, 3, 4, 6, 8, 12, 16, 24].map((elevation) => (
|
||||
<div style={{ marginTop: 50 }} key={elevation}>
|
||||
<Paper elevation={elevation}>
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
height: 60,
|
||||
lineHeight: "60px",
|
||||
}}
|
||||
>{`elevation=${elevation}`}</div>
|
||||
</Paper>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const ElevationDark = () => (
|
||||
<div
|
||||
class="theme-dark"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
backgroundColor: "lightgray",
|
||||
width: "100%",
|
||||
padding: 50,
|
||||
justifyContent: "space-around",
|
||||
}}
|
||||
>
|
||||
to be implemented
|
||||
{/* {[0, 1, 2, 3, 4, 6, 8, 12, 16, 24].map((elevation) => (
|
||||
<div style={{ marginTop: 50 }} key={elevation}>
|
||||
<Paper elevation={elevation}>
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
height: 60,
|
||||
lineHeight: "60px",
|
||||
}}
|
||||
>{`elevation=${elevation}`}</div>
|
||||
</Paper>
|
||||
</div>
|
||||
))} */}
|
||||
</div>
|
||||
);
|
63
packages/taler-wallet-webextension/src/mui/Paper.tsx
Normal file
63
packages/taler-wallet-webextension/src/mui/Paper.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import { css } from "@linaria/core";
|
||||
import { h, Fragment, VNode, ComponentChildren } from "preact";
|
||||
import { alpha } from "./colors/manipulation";
|
||||
import { theme } from "./style";
|
||||
|
||||
const borderVariant = {
|
||||
outlined: css`
|
||||
border: 1px solid ${theme.palette.divider};
|
||||
`,
|
||||
elevation: css`
|
||||
box-shadow: var(--theme-shadow-elevation);
|
||||
`,
|
||||
};
|
||||
const baseStyle = css`
|
||||
background-color: ${theme.palette.background.paper};
|
||||
color: ${theme.palette.text.primary};
|
||||
|
||||
.theme-dark & {
|
||||
background-image: var(--gradient-white-elevation);
|
||||
}
|
||||
`;
|
||||
|
||||
export function Paper({
|
||||
elevation = 1,
|
||||
square,
|
||||
variant = "elevation",
|
||||
children,
|
||||
}: {
|
||||
elevation?: number;
|
||||
square?: boolean;
|
||||
variant?: "elevation" | "outlined";
|
||||
children?: ComponentChildren;
|
||||
}): VNode {
|
||||
return (
|
||||
<div
|
||||
class={[
|
||||
baseStyle,
|
||||
!square && theme.shape.borderRadius,
|
||||
borderVariant[variant],
|
||||
].join(" ")}
|
||||
style={{
|
||||
"--theme-shadow-elevation": theme.shadows[elevation],
|
||||
"--gradient-white-elevation": `linear-gradient(${alpha(
|
||||
"#fff",
|
||||
getOverlayAlpha(elevation),
|
||||
)}, ${alpha("#fff", getOverlayAlpha(elevation))})`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Inspired by https://github.com/material-components/material-components-ios/blob/bca36107405594d5b7b16265a5b0ed698f85a5ee/components/Elevation/src/UIColor%2BMaterialElevation.m#L61
|
||||
const getOverlayAlpha = (elevation: number): number => {
|
||||
let alphaValue;
|
||||
if (elevation < 1) {
|
||||
alphaValue = 5.11916 * elevation ** 2;
|
||||
} else {
|
||||
alphaValue = 4.5 * Math.log(elevation + 1) + 2;
|
||||
}
|
||||
return Number((alphaValue / 100).toFixed(2));
|
||||
};
|
@ -0,0 +1,9 @@
|
||||
import { h, Fragment, VNode, ComponentChildren } from "preact";
|
||||
|
||||
interface Props {
|
||||
children: ComponentChildren;
|
||||
}
|
||||
|
||||
export function Typography({ children }: Props): VNode {
|
||||
return <p>{children}</p>;
|
||||
}
|
348
packages/taler-wallet-webextension/src/mui/colors/constants.ts
Normal file
348
packages/taler-wallet-webextension/src/mui/colors/constants.ts
Normal file
@ -0,0 +1,348 @@
|
||||
export const amber = {
|
||||
50: '#fff8e1',
|
||||
100: '#ffecb3',
|
||||
200: '#ffe082',
|
||||
300: '#ffd54f',
|
||||
400: '#ffca28',
|
||||
500: '#ffc107',
|
||||
600: '#ffb300',
|
||||
700: '#ffa000',
|
||||
800: '#ff8f00',
|
||||
900: '#ff6f00',
|
||||
A100: '#ffe57f',
|
||||
A200: '#ffd740',
|
||||
A400: '#ffc400',
|
||||
A700: '#ffab00',
|
||||
};
|
||||
|
||||
|
||||
export const blueGrey = {
|
||||
50: '#eceff1',
|
||||
100: '#cfd8dc',
|
||||
200: '#b0bec5',
|
||||
300: '#90a4ae',
|
||||
400: '#78909c',
|
||||
500: '#607d8b',
|
||||
600: '#546e7a',
|
||||
700: '#455a64',
|
||||
800: '#37474f',
|
||||
900: '#263238',
|
||||
A100: '#cfd8dc',
|
||||
A200: '#b0bec5',
|
||||
A400: '#78909c',
|
||||
A700: '#455a64',
|
||||
};
|
||||
|
||||
|
||||
export const blue = {
|
||||
50: '#e3f2fd',
|
||||
100: '#bbdefb',
|
||||
200: '#90caf9',
|
||||
300: '#64b5f6',
|
||||
400: '#42a5f5',
|
||||
500: '#2196f3',
|
||||
600: '#1e88e5',
|
||||
700: '#1976d2',
|
||||
800: '#1565c0',
|
||||
900: '#0d47a1',
|
||||
A100: '#82b1ff',
|
||||
A200: '#448aff',
|
||||
A400: '#2979ff',
|
||||
A700: '#2962ff',
|
||||
};
|
||||
|
||||
|
||||
export const brown = {
|
||||
50: '#efebe9',
|
||||
100: '#d7ccc8',
|
||||
200: '#bcaaa4',
|
||||
300: '#a1887f',
|
||||
400: '#8d6e63',
|
||||
500: '#795548',
|
||||
600: '#6d4c41',
|
||||
700: '#5d4037',
|
||||
800: '#4e342e',
|
||||
900: '#3e2723',
|
||||
A100: '#d7ccc8',
|
||||
A200: '#bcaaa4',
|
||||
A400: '#8d6e63',
|
||||
A700: '#5d4037',
|
||||
};
|
||||
|
||||
|
||||
export const common = {
|
||||
black: '#000',
|
||||
white: '#fff',
|
||||
};
|
||||
|
||||
|
||||
export const cyan = {
|
||||
50: '#e0f7fa',
|
||||
100: '#b2ebf2',
|
||||
200: '#80deea',
|
||||
300: '#4dd0e1',
|
||||
400: '#26c6da',
|
||||
500: '#00bcd4',
|
||||
600: '#00acc1',
|
||||
700: '#0097a7',
|
||||
800: '#00838f',
|
||||
900: '#006064',
|
||||
A100: '#84ffff',
|
||||
A200: '#18ffff',
|
||||
A400: '#00e5ff',
|
||||
A700: '#00b8d4',
|
||||
};
|
||||
|
||||
|
||||
export const deepOrange = {
|
||||
50: '#fbe9e7',
|
||||
100: '#ffccbc',
|
||||
200: '#ffab91',
|
||||
300: '#ff8a65',
|
||||
400: '#ff7043',
|
||||
500: '#ff5722',
|
||||
600: '#f4511e',
|
||||
700: '#e64a19',
|
||||
800: '#d84315',
|
||||
900: '#bf360c',
|
||||
A100: '#ff9e80',
|
||||
A200: '#ff6e40',
|
||||
A400: '#ff3d00',
|
||||
A700: '#dd2c00',
|
||||
};
|
||||
|
||||
|
||||
export const deepPurple = {
|
||||
50: '#ede7f6',
|
||||
100: '#d1c4e9',
|
||||
200: '#b39ddb',
|
||||
300: '#9575cd',
|
||||
400: '#7e57c2',
|
||||
500: '#673ab7',
|
||||
600: '#5e35b1',
|
||||
700: '#512da8',
|
||||
800: '#4527a0',
|
||||
900: '#311b92',
|
||||
A100: '#b388ff',
|
||||
A200: '#7c4dff',
|
||||
A400: '#651fff',
|
||||
A700: '#6200ea',
|
||||
};
|
||||
|
||||
|
||||
export const green = {
|
||||
50: '#e8f5e9',
|
||||
100: '#c8e6c9',
|
||||
200: '#a5d6a7',
|
||||
300: '#81c784',
|
||||
400: '#66bb6a',
|
||||
500: '#4caf50',
|
||||
600: '#43a047',
|
||||
700: '#388e3c',
|
||||
800: '#2e7d32',
|
||||
900: '#1b5e20',
|
||||
A100: '#b9f6ca',
|
||||
A200: '#69f0ae',
|
||||
A400: '#00e676',
|
||||
A700: '#00c853',
|
||||
};
|
||||
|
||||
|
||||
export const grey = {
|
||||
50: '#fafafa',
|
||||
100: '#f5f5f5',
|
||||
200: '#eeeeee',
|
||||
300: '#e0e0e0',
|
||||
400: '#bdbdbd',
|
||||
500: '#9e9e9e',
|
||||
600: '#757575',
|
||||
700: '#616161',
|
||||
800: '#424242',
|
||||
900: '#212121',
|
||||
A100: '#f5f5f5',
|
||||
A200: '#eeeeee',
|
||||
A400: '#bdbdbd',
|
||||
A700: '#616161',
|
||||
};
|
||||
|
||||
|
||||
export const indigo = {
|
||||
50: '#e8eaf6',
|
||||
100: '#c5cae9',
|
||||
200: '#9fa8da',
|
||||
300: '#7986cb',
|
||||
400: '#5c6bc0',
|
||||
500: '#3f51b5',
|
||||
600: '#3949ab',
|
||||
700: '#303f9f',
|
||||
800: '#283593',
|
||||
900: '#1a237e',
|
||||
A100: '#8c9eff',
|
||||
A200: '#536dfe',
|
||||
A400: '#3d5afe',
|
||||
A700: '#304ffe',
|
||||
};
|
||||
|
||||
|
||||
export const lightBlue = {
|
||||
50: '#e1f5fe',
|
||||
100: '#b3e5fc',
|
||||
200: '#81d4fa',
|
||||
300: '#4fc3f7',
|
||||
400: '#29b6f6',
|
||||
500: '#03a9f4',
|
||||
600: '#039be5',
|
||||
700: '#0288d1',
|
||||
800: '#0277bd',
|
||||
900: '#01579b',
|
||||
A100: '#80d8ff',
|
||||
A200: '#40c4ff',
|
||||
A400: '#00b0ff',
|
||||
A700: '#0091ea',
|
||||
};
|
||||
|
||||
|
||||
export const lightGreen = {
|
||||
50: '#f1f8e9',
|
||||
100: '#dcedc8',
|
||||
200: '#c5e1a5',
|
||||
300: '#aed581',
|
||||
400: '#9ccc65',
|
||||
500: '#8bc34a',
|
||||
600: '#7cb342',
|
||||
700: '#689f38',
|
||||
800: '#558b2f',
|
||||
900: '#33691e',
|
||||
A100: '#ccff90',
|
||||
A200: '#b2ff59',
|
||||
A400: '#76ff03',
|
||||
A700: '#64dd17',
|
||||
};
|
||||
|
||||
|
||||
export const lime = {
|
||||
50: '#f9fbe7',
|
||||
100: '#f0f4c3',
|
||||
200: '#e6ee9c',
|
||||
300: '#dce775',
|
||||
400: '#d4e157',
|
||||
500: '#cddc39',
|
||||
600: '#c0ca33',
|
||||
700: '#afb42b',
|
||||
800: '#9e9d24',
|
||||
900: '#827717',
|
||||
A100: '#f4ff81',
|
||||
A200: '#eeff41',
|
||||
A400: '#c6ff00',
|
||||
A700: '#aeea00',
|
||||
};
|
||||
|
||||
|
||||
export const orange = {
|
||||
50: '#fff3e0',
|
||||
100: '#ffe0b2',
|
||||
200: '#ffcc80',
|
||||
300: '#ffb74d',
|
||||
400: '#ffa726',
|
||||
500: '#ff9800',
|
||||
600: '#fb8c00',
|
||||
700: '#f57c00',
|
||||
800: '#ef6c00',
|
||||
900: '#e65100',
|
||||
A100: '#ffd180',
|
||||
A200: '#ffab40',
|
||||
A400: '#ff9100',
|
||||
A700: '#ff6d00',
|
||||
};
|
||||
|
||||
|
||||
export const pink = {
|
||||
50: '#fce4ec',
|
||||
100: '#f8bbd0',
|
||||
200: '#f48fb1',
|
||||
300: '#f06292',
|
||||
400: '#ec407a',
|
||||
500: '#e91e63',
|
||||
600: '#d81b60',
|
||||
700: '#c2185b',
|
||||
800: '#ad1457',
|
||||
900: '#880e4f',
|
||||
A100: '#ff80ab',
|
||||
A200: '#ff4081',
|
||||
A400: '#f50057',
|
||||
A700: '#c51162',
|
||||
};
|
||||
|
||||
|
||||
export const purple = {
|
||||
50: '#f3e5f5',
|
||||
100: '#e1bee7',
|
||||
200: '#ce93d8',
|
||||
300: '#ba68c8',
|
||||
400: '#ab47bc',
|
||||
500: '#9c27b0',
|
||||
600: '#8e24aa',
|
||||
700: '#7b1fa2',
|
||||
800: '#6a1b9a',
|
||||
900: '#4a148c',
|
||||
A100: '#ea80fc',
|
||||
A200: '#e040fb',
|
||||
A400: '#d500f9',
|
||||
A700: '#aa00ff',
|
||||
};
|
||||
|
||||
|
||||
export const red = {
|
||||
50: '#ffebee',
|
||||
100: '#ffcdd2',
|
||||
200: '#ef9a9a',
|
||||
300: '#e57373',
|
||||
400: '#ef5350',
|
||||
500: '#f44336',
|
||||
600: '#e53935',
|
||||
700: '#d32f2f',
|
||||
800: '#c62828',
|
||||
900: '#b71c1c',
|
||||
A100: '#ff8a80',
|
||||
A200: '#ff5252',
|
||||
A400: '#ff1744',
|
||||
A700: '#d50000',
|
||||
};
|
||||
|
||||
|
||||
export const teal = {
|
||||
50: '#e0f2f1',
|
||||
100: '#b2dfdb',
|
||||
200: '#80cbc4',
|
||||
300: '#4db6ac',
|
||||
400: '#26a69a',
|
||||
500: '#009688',
|
||||
600: '#00897b',
|
||||
700: '#00796b',
|
||||
800: '#00695c',
|
||||
900: '#004d40',
|
||||
A100: '#a7ffeb',
|
||||
A200: '#64ffda',
|
||||
A400: '#1de9b6',
|
||||
A700: '#00bfa5',
|
||||
};
|
||||
|
||||
|
||||
export const yellow = {
|
||||
50: '#fffde7',
|
||||
100: '#fff9c4',
|
||||
200: '#fff59d',
|
||||
300: '#fff176',
|
||||
400: '#ffee58',
|
||||
500: '#ffeb3b',
|
||||
600: '#fdd835',
|
||||
700: '#fbc02d',
|
||||
800: '#f9a825',
|
||||
900: '#f57f17',
|
||||
A100: '#ffff8d',
|
||||
A200: '#ffff00',
|
||||
A400: '#ffea00',
|
||||
A700: '#ffd600',
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,305 @@
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
recomposeColor,
|
||||
hexToRgb,
|
||||
rgbToHex,
|
||||
hslToRgb,
|
||||
darken,
|
||||
decomposeColor,
|
||||
emphasize,
|
||||
alpha,
|
||||
getContrastRatio,
|
||||
getLuminance,
|
||||
lighten,
|
||||
} from './manipulation';
|
||||
|
||||
describe('utils/colorManipulator', () => {
|
||||
describe('recomposeColor', () => {
|
||||
it('converts a decomposed rgb color object to a string` ', () => {
|
||||
expect(
|
||||
recomposeColor({
|
||||
type: 'rgb',
|
||||
values: [255, 255, 255],
|
||||
}),
|
||||
).to.equal('rgb(255, 255, 255)');
|
||||
});
|
||||
|
||||
it('converts a decomposed rgba color object to a string` ', () => {
|
||||
expect(
|
||||
recomposeColor({
|
||||
type: 'rgba',
|
||||
values: [255, 255, 255, 0.5],
|
||||
}),
|
||||
).to.equal('rgba(255, 255, 255, 0.5)');
|
||||
});
|
||||
|
||||
it('converts a decomposed hsl color object to a string` ', () => {
|
||||
expect(
|
||||
recomposeColor({
|
||||
type: 'hsl',
|
||||
values: [100, 50, 25],
|
||||
}),
|
||||
).to.equal('hsl(100, 50%, 25%)');
|
||||
});
|
||||
|
||||
it('converts a decomposed hsla color object to a string` ', () => {
|
||||
expect(
|
||||
recomposeColor({
|
||||
type: 'hsla',
|
||||
values: [100, 50, 25, 0.5],
|
||||
}),
|
||||
).to.equal('hsla(100, 50%, 25%, 0.5)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hexToRgb', () => {
|
||||
it('converts a short hex color to an rgb color` ', () => {
|
||||
expect(hexToRgb('#9f3')).to.equal('rgb(153, 255, 51)');
|
||||
});
|
||||
|
||||
it('converts a long hex color to an rgb color` ', () => {
|
||||
expect(hexToRgb('#a94fd3')).to.equal('rgb(169, 79, 211)');
|
||||
});
|
||||
|
||||
it('converts a long alpha hex color to an argb color` ', () => {
|
||||
expect(hexToRgb('#111111f8')).to.equal('rgba(17, 17, 17, 0.973)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('rgbToHex', () => {
|
||||
it('converts an rgb color to a hex color` ', () => {
|
||||
expect(rgbToHex('rgb(169, 79, 211)')).to.equal('#a94fd3');
|
||||
});
|
||||
|
||||
it('converts an rgba color to a hex color` ', () => {
|
||||
expect(rgbToHex('rgba(169, 79, 211, 1)')).to.equal('#a94fd3ff');
|
||||
});
|
||||
|
||||
it('idempotent', () => {
|
||||
expect(rgbToHex('#A94FD3')).to.equal('#A94FD3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hslToRgb', () => {
|
||||
it('converts an hsl color to an rgb color` ', () => {
|
||||
expect(hslToRgb('hsl(281, 60%, 57%)')).to.equal('rgb(169, 80, 211)');
|
||||
});
|
||||
|
||||
it('converts an hsla color to an rgba color` ', () => {
|
||||
expect(hslToRgb('hsla(281, 60%, 57%, 0.5)')).to.equal('rgba(169, 80, 211, 0.5)');
|
||||
});
|
||||
|
||||
it('allow to convert values only', () => {
|
||||
expect(hslToRgb('hsl(281, 60%, 57%)')).to.equal('rgb(169, 80, 211)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('decomposeColor', () => {
|
||||
it('converts an rgb color string to an object with `type` and `value` keys', () => {
|
||||
const { type, values } = decomposeColor('rgb(255, 255, 255)');
|
||||
expect(type).to.equal('rgb');
|
||||
expect(values).to.deep.equal([255, 255, 255]);
|
||||
});
|
||||
|
||||
it('converts an rgba color string to an object with `type` and `value` keys', () => {
|
||||
const { type, values } = decomposeColor('rgba(255, 255, 255, 0.5)');
|
||||
expect(type).to.equal('rgba');
|
||||
expect(values).to.deep.equal([255, 255, 255, 0.5]);
|
||||
});
|
||||
|
||||
it('converts an hsl color string to an object with `type` and `value` keys', () => {
|
||||
const { type, values } = decomposeColor('hsl(100, 50%, 25%)');
|
||||
expect(type).to.equal('hsl');
|
||||
expect(values).to.deep.equal([100, 50, 25]);
|
||||
});
|
||||
|
||||
it('converts an hsla color string to an object with `type` and `value` keys', () => {
|
||||
const { type, values } = decomposeColor('hsla(100, 50%, 25%, 0.5)');
|
||||
expect(type).to.equal('hsla');
|
||||
expect(values).to.deep.equal([100, 50, 25, 0.5]);
|
||||
});
|
||||
|
||||
it('converts rgba hex', () => {
|
||||
const decomposed = decomposeColor('#111111f8');
|
||||
expect(decomposed).to.deep.equal({
|
||||
type: 'rgba',
|
||||
colorSpace: undefined,
|
||||
values: [17, 17, 17, 0.973],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getContrastRatio', () => {
|
||||
it('returns a ratio for black : white', () => {
|
||||
expect(getContrastRatio('#000', '#FFF')).to.equal(21);
|
||||
});
|
||||
|
||||
it('returns a ratio for black : black', () => {
|
||||
expect(getContrastRatio('#000', '#000')).to.equal(1);
|
||||
});
|
||||
|
||||
it('returns a ratio for white : white', () => {
|
||||
expect(getContrastRatio('#FFF', '#FFF')).to.equal(1);
|
||||
});
|
||||
|
||||
it('returns a ratio for dark-grey : light-grey', () => {
|
||||
expect(getContrastRatio('#707070', '#E5E5E5')).to.be.approximately(3.93, 0.01);
|
||||
});
|
||||
|
||||
it('returns a ratio for black : light-grey', () => {
|
||||
expect(getContrastRatio('#000', '#888')).to.be.approximately(5.92, 0.01);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLuminance', () => {
|
||||
|
||||
it('returns a valid luminance for rgb white ', () => {
|
||||
expect(getLuminance('rgba(255, 255, 255)')).to.equal(1);
|
||||
expect(getLuminance('rgb(255, 255, 255)')).to.equal(1);
|
||||
});
|
||||
|
||||
it('returns a valid luminance for rgb mid-grey', () => {
|
||||
expect(getLuminance('rgba(127, 127, 127)')).to.equal(0.212);
|
||||
expect(getLuminance('rgb(127, 127, 127)')).to.equal(0.212);
|
||||
});
|
||||
|
||||
it('returns a valid luminance for an rgb color', () => {
|
||||
expect(getLuminance('rgb(255, 127, 0)')).to.equal(0.364);
|
||||
});
|
||||
|
||||
it('returns a valid luminance from an hsl color', () => {
|
||||
expect(getLuminance('hsl(100, 100%, 50%)')).to.equal(0.735);
|
||||
});
|
||||
|
||||
it('returns an equal luminance for the same color in different formats', () => {
|
||||
const hsl = 'hsl(100, 100%, 50%)';
|
||||
const rgb = 'rgb(85, 255, 0)';
|
||||
expect(getLuminance(hsl)).to.equal(getLuminance(rgb));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('emphasize', () => {
|
||||
it('lightens a dark rgb color with the coefficient provided', () => {
|
||||
expect(emphasize('rgb(1, 2, 3)', 0.4)).to.equal(lighten('rgb(1, 2, 3)', 0.4));
|
||||
});
|
||||
|
||||
it('darkens a light rgb color with the coefficient provided', () => {
|
||||
expect(emphasize('rgb(250, 240, 230)', 0.3)).to.equal(darken('rgb(250, 240, 230)', 0.3));
|
||||
});
|
||||
|
||||
it('lightens a dark rgb color with the coefficient 0.15 by default', () => {
|
||||
expect(emphasize('rgb(1, 2, 3)')).to.equal(lighten('rgb(1, 2, 3)', 0.15));
|
||||
});
|
||||
|
||||
it('darkens a light rgb color with the coefficient 0.15 by default', () => {
|
||||
expect(emphasize('rgb(250, 240, 230)')).to.equal(darken('rgb(250, 240, 230)', 0.15));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('alpha', () => {
|
||||
it('converts an rgb color to an rgba color with the value provided', () => {
|
||||
expect(alpha('rgb(1, 2, 3)', 0.4)).to.equal('rgba(1, 2, 3, 0.4)');
|
||||
});
|
||||
|
||||
it('updates an rgba color with the alpha value provided', () => {
|
||||
expect(alpha('rgba(255, 0, 0, 0.2)', 0.5)).to.equal('rgba(255, 0, 0, 0.5)');
|
||||
});
|
||||
|
||||
it('converts an hsl color to an hsla color with the value provided', () => {
|
||||
expect(alpha('hsl(0, 100%, 50%)', 0.1)).to.equal('hsla(0, 100%, 50%, 0.1)');
|
||||
});
|
||||
|
||||
it('updates an hsla color with the alpha value provided', () => {
|
||||
expect(alpha('hsla(0, 100%, 50%, 0.2)', 0.5)).to.equal('hsla(0, 100%, 50%, 0.5)');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('darken', () => {
|
||||
it("doesn't modify rgb black", () => {
|
||||
expect(darken('rgb(0, 0, 0)', 0.1)).to.equal('rgb(0, 0, 0)');
|
||||
});
|
||||
|
||||
it('darkens rgb white to black when coefficient is 1', () => {
|
||||
expect(darken('rgb(255, 255, 255)', 1)).to.equal('rgb(0, 0, 0)');
|
||||
});
|
||||
|
||||
it('retains the alpha value in an rgba color', () => {
|
||||
expect(darken('rgba(0, 0, 0, 0.5)', 0.1)).to.equal('rgba(0, 0, 0, 0.5)');
|
||||
});
|
||||
|
||||
it('darkens rgb white by 10% when coefficient is 0.1', () => {
|
||||
expect(darken('rgb(255, 255, 255)', 0.1)).to.equal('rgb(229, 229, 229)');
|
||||
});
|
||||
|
||||
it('darkens rgb red by 50% when coefficient is 0.5', () => {
|
||||
expect(darken('rgb(255, 0, 0)', 0.5)).to.equal('rgb(127, 0, 0)');
|
||||
});
|
||||
|
||||
it('darkens rgb grey by 50% when coefficient is 0.5', () => {
|
||||
expect(darken('rgb(127, 127, 127)', 0.5)).to.equal('rgb(63, 63, 63)');
|
||||
});
|
||||
|
||||
it("doesn't modify rgb colors when coefficient is 0", () => {
|
||||
expect(darken('rgb(255, 255, 255)', 0)).to.equal('rgb(255, 255, 255)');
|
||||
});
|
||||
|
||||
it('darkens hsl red by 50% when coefficient is 0.5', () => {
|
||||
expect(darken('hsl(0, 100%, 50%)', 0.5)).to.equal('hsl(0, 100%, 25%)');
|
||||
});
|
||||
|
||||
it("doesn't modify hsl colors when coefficient is 0", () => {
|
||||
expect(darken('hsl(0, 100%, 50%)', 0)).to.equal('hsl(0, 100%, 50%)');
|
||||
});
|
||||
|
||||
it("doesn't modify hsl colors when l is 0%", () => {
|
||||
expect(darken('hsl(0, 50%, 0%)', 0.5)).to.equal('hsl(0, 50%, 0%)');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('lighten', () => {
|
||||
it("doesn't modify rgb white", () => {
|
||||
expect(lighten('rgb(255, 255, 255)', 0.1)).to.equal('rgb(255, 255, 255)');
|
||||
});
|
||||
|
||||
it('lightens rgb black to white when coefficient is 1', () => {
|
||||
expect(lighten('rgb(0, 0, 0)', 1)).to.equal('rgb(255, 255, 255)');
|
||||
});
|
||||
|
||||
it('retains the alpha value in an rgba color', () => {
|
||||
expect(lighten('rgba(255, 255, 255, 0.5)', 0.1)).to.equal('rgba(255, 255, 255, 0.5)');
|
||||
});
|
||||
|
||||
it('lightens rgb black by 10% when coefficient is 0.1', () => {
|
||||
expect(lighten('rgb(0, 0, 0)', 0.1)).to.equal('rgb(25, 25, 25)');
|
||||
});
|
||||
|
||||
it('lightens rgb red by 50% when coefficient is 0.5', () => {
|
||||
expect(lighten('rgb(255, 0, 0)', 0.5)).to.equal('rgb(255, 127, 127)');
|
||||
});
|
||||
|
||||
it('lightens rgb grey by 50% when coefficient is 0.5', () => {
|
||||
expect(lighten('rgb(127, 127, 127)', 0.5)).to.equal('rgb(191, 191, 191)');
|
||||
});
|
||||
|
||||
it("doesn't modify rgb colors when coefficient is 0", () => {
|
||||
expect(lighten('rgb(127, 127, 127)', 0)).to.equal('rgb(127, 127, 127)');
|
||||
});
|
||||
|
||||
it('lightens hsl red by 50% when coefficient is 0.5', () => {
|
||||
expect(lighten('hsl(0, 100%, 50%)', 0.5)).to.equal('hsl(0, 100%, 75%)');
|
||||
});
|
||||
|
||||
it("doesn't modify hsl colors when coefficient is 0", () => {
|
||||
expect(lighten('hsl(0, 100%, 50%)', 0)).to.equal('hsl(0, 100%, 50%)');
|
||||
});
|
||||
|
||||
it("doesn't modify hsl colors when `l` is 100%", () => {
|
||||
expect(lighten('hsl(0, 50%, 100%)', 0.5)).to.equal('hsl(0, 50%, 100%)');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -0,0 +1,273 @@
|
||||
|
||||
export type ColorFormat = ColorFormatWithAlpha | ColorFormatWithoutAlpha
|
||||
export type ColorFormatWithAlpha = 'rgb' | 'hsl';
|
||||
export type ColorFormatWithoutAlpha = 'rgba' | 'hsla';
|
||||
export type ColorObject = ColorObjectWithAlpha | ColorObjectWithoutAlpha
|
||||
export interface ColorObjectWithAlpha {
|
||||
type: ColorFormatWithAlpha;
|
||||
values: [number, number, number];
|
||||
colorSpace?: 'srgb' | 'display-p3' | 'a98-rgb' | 'prophoto-rgb' | 'rec-2020';
|
||||
}
|
||||
export interface ColorObjectWithoutAlpha {
|
||||
type: ColorFormatWithoutAlpha;
|
||||
values: [number, number, number, number];
|
||||
colorSpace?: 'srgb' | 'display-p3' | 'a98-rgb' | 'prophoto-rgb' | 'rec-2020';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a number whose value is limited to the given range.
|
||||
* @param {number} value The value to be clamped
|
||||
* @param {number} min The lower boundary of the output range
|
||||
* @param {number} max The upper boundary of the output range
|
||||
* @returns {number} A number in the range [min, max]
|
||||
*/
|
||||
function clamp(value: number, min: number = 0, max: number = 1): number {
|
||||
// if (process.env.NODE_ENV !== 'production') {
|
||||
// if (value < min || value > max) {
|
||||
// console.error(`MUI: The value provided ${value} is out of range [${min}, ${max}].`);
|
||||
// }
|
||||
// }
|
||||
|
||||
return Math.min(Math.max(min, value), max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color from CSS hex format to CSS rgb format.
|
||||
* @param {string} color - Hex color, i.e. #nnn or #nnnnnn
|
||||
* @returns {string} A CSS rgb color string
|
||||
*/
|
||||
export function hexToRgb(color: string): string {
|
||||
color = color.substr(1);
|
||||
|
||||
const re = new RegExp(`.{1,${color.length >= 6 ? 2 : 1}}`, 'g');
|
||||
let colors = color.match(re);
|
||||
|
||||
if (colors && colors[0].length === 1) {
|
||||
colors = colors.map((n) => n + n);
|
||||
}
|
||||
|
||||
return colors
|
||||
? `rgb${colors.length === 4 ? 'a' : ''}(${colors
|
||||
.map((n, index) => {
|
||||
return index < 3 ? parseInt(n, 16) : Math.round((parseInt(n, 16) / 255) * 1000) / 1000;
|
||||
})
|
||||
.join(', ')})`
|
||||
: '';
|
||||
}
|
||||
|
||||
function intToHex(int: number): string {
|
||||
const hex = int.toString(16);
|
||||
return hex.length === 1 ? `0${hex}` : hex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object with the type and values of a color.
|
||||
*
|
||||
* Note: Does not support rgb % values.
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @returns {object} - A MUI color object: {type: string, values: number[]}
|
||||
*/
|
||||
export function decomposeColor(color: string): ColorObject {
|
||||
const colorSpace = undefined;
|
||||
if (color.charAt(0) === '#') {
|
||||
return decomposeColor(hexToRgb(color));
|
||||
}
|
||||
|
||||
const marker = color.indexOf('(');
|
||||
const type = color.substring(0, marker);
|
||||
if (type != 'rgba' && type != 'hsla' && type != 'rgb' && type != 'hsl') {
|
||||
}
|
||||
|
||||
const values = color.substring(marker + 1, color.length - 1).split(',')
|
||||
if (type == 'rgb' || type == 'hsl') {
|
||||
return { type, colorSpace, values: [parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2])] }
|
||||
}
|
||||
if (type == 'rgba' || type == 'hsla') {
|
||||
return { type, colorSpace, values: [parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[3])] }
|
||||
}
|
||||
throw new Error(`Unsupported '${color}' color. The following formats are supported: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color object with type and values to a string.
|
||||
* @param {object} color - Decomposed color
|
||||
* @param {string} color.type - One of: 'rgb', 'rgba', 'hsl', 'hsla'
|
||||
* @param {array} color.values - [n,n,n] or [n,n,n,n]
|
||||
* @returns {string} A CSS color string
|
||||
*/
|
||||
export function recomposeColor(color: ColorObject): string {
|
||||
const { type, values: valuesNum } = color;
|
||||
|
||||
const valuesStr: string[] = [];
|
||||
if (type.indexOf('rgb') !== -1) {
|
||||
// Only convert the first 3 values to int (i.e. not alpha)
|
||||
valuesNum.map((n, i) => (i < 3 ? parseInt(String(n), 10) : n)).forEach((n, i) => valuesStr[i] = String(n));
|
||||
} else if (type.indexOf('hsl') !== -1) {
|
||||
valuesStr[0] = String(valuesNum[0])
|
||||
valuesStr[1] = `${valuesNum[1]}%`;
|
||||
valuesStr[2] = `${valuesNum[2]}%`;
|
||||
if (type === 'hsla') {
|
||||
valuesStr[3] = String(valuesNum[3])
|
||||
}
|
||||
}
|
||||
|
||||
return `${type}(${valuesStr.join(', ')})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color from CSS rgb format to CSS hex format.
|
||||
* @param {string} color - RGB color, i.e. rgb(n, n, n)
|
||||
* @returns {string} A CSS rgb color string, i.e. #nnnnnn
|
||||
*/
|
||||
export function rgbToHex(color: string): string {
|
||||
// Idempotent
|
||||
if (color.indexOf('#') === 0) {
|
||||
return color;
|
||||
}
|
||||
|
||||
const { values } = decomposeColor(color);
|
||||
return `#${values.map((n, i) => intToHex(i === 3 ? Math.round(255 * n) : n)).join('')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color from hsl format to rgb format.
|
||||
* @param {string} color - HSL color values
|
||||
* @returns {string} rgb color values
|
||||
*/
|
||||
export function hslToRgb(color: string): string {
|
||||
const colorObj = decomposeColor(color);
|
||||
const { values } = colorObj;
|
||||
const h = values[0];
|
||||
const s = values[1] / 100;
|
||||
const l = values[2] / 100;
|
||||
const a = s * Math.min(l, 1 - l);
|
||||
const f = (n: number, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
|
||||
if (colorObj.type === 'hsla') {
|
||||
return recomposeColor({
|
||||
type: 'rgba', values: [
|
||||
Math.round(f(0) * 255),
|
||||
Math.round(f(8) * 255),
|
||||
Math.round(f(4) * 255),
|
||||
colorObj.values[3]
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
return recomposeColor({
|
||||
type: 'rgb', values: [
|
||||
Math.round(f(0) * 255),
|
||||
Math.round(f(8) * 255),
|
||||
Math.round(f(4) * 255)]
|
||||
});
|
||||
}
|
||||
/**
|
||||
* The relative brightness of any point in a color space,
|
||||
* normalized to 0 for darkest black and 1 for lightest white.
|
||||
*
|
||||
* Formula: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color()
|
||||
* @returns {number} The relative brightness of the color in the range 0 - 1
|
||||
*/
|
||||
export function getLuminance(color: string): number {
|
||||
const colorObj = decomposeColor(color);
|
||||
|
||||
const rgb2 = colorObj.type === 'hsl' ? decomposeColor(hslToRgb(color)).values : colorObj.values;
|
||||
const rgb = rgb2.map((val) => {
|
||||
val /= 255; // normalized
|
||||
return val <= 0.03928 ? val / 12.92 : ((val + 0.055) / 1.055) ** 2.4;
|
||||
}) as typeof rgb2;
|
||||
|
||||
// Truncate at 3 digits
|
||||
return Number((0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]).toFixed(3));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the contrast ratio between two colors.
|
||||
*
|
||||
* Formula: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
|
||||
* @param {string} foreground - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @param {string} background - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
||||
* @returns {number} A contrast ratio value in the range 0 - 21.
|
||||
*/
|
||||
export function getContrastRatio(foreground: string, background: string): number {
|
||||
const lumA = getLuminance(foreground);
|
||||
const lumB = getLuminance(background);
|
||||
return (Math.max(lumA, lumB) + 0.05) / (Math.min(lumA, lumB) + 0.05);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the absolute transparency of a color.
|
||||
* Any existing alpha values are overwritten.
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color()
|
||||
* @param {number} value - value to set the alpha channel to in the range 0 - 1
|
||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||
*/
|
||||
export function alpha(color: string, value: number): string {
|
||||
const colorObj = decomposeColor(color);
|
||||
value = clamp(value);
|
||||
|
||||
if (colorObj.type === 'rgb' || colorObj.type === 'hsl') {
|
||||
colorObj.type += 'a';
|
||||
}
|
||||
colorObj.values[3] = value;
|
||||
|
||||
return recomposeColor(colorObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Darkens a color.
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color()
|
||||
* @param {number} coefficient - multiplier in the range 0 - 1
|
||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||
*/
|
||||
export function darken(color: string, coefficient: number): string {
|
||||
const colorObj = decomposeColor(color);
|
||||
coefficient = clamp(coefficient);
|
||||
|
||||
if (colorObj.type.indexOf('hsl') !== -1) {
|
||||
colorObj.values[2] *= 1 - coefficient;
|
||||
} else if (colorObj.type.indexOf('rgb') !== -1 || colorObj.type.indexOf('color') !== -1) {
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
colorObj.values[i] *= 1 - coefficient;
|
||||
}
|
||||
}
|
||||
return recomposeColor(colorObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightens a color.
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color()
|
||||
* @param {number} coefficient - multiplier in the range 0 - 1
|
||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||
*/
|
||||
export function lighten(color: string, coefficient: number): string {
|
||||
const colorObj = decomposeColor(color);
|
||||
coefficient = clamp(coefficient);
|
||||
|
||||
if (colorObj.type.indexOf('hsl') !== -1) {
|
||||
colorObj.values[2] += (100 - colorObj.values[2]) * coefficient;
|
||||
} else if (colorObj.type.indexOf('rgb') !== -1) {
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
colorObj.values[i] += (255 - colorObj.values[i]) * coefficient;
|
||||
}
|
||||
} else if (colorObj.type.indexOf('color') !== -1) {
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
colorObj.values[i] += (1 - colorObj.values[i]) * coefficient;
|
||||
}
|
||||
}
|
||||
|
||||
return recomposeColor(colorObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Darken or lighten a color, depending on its luminance.
|
||||
* Light colors are darkened, dark colors are lightened.
|
||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color()
|
||||
* @param {number} coefficient=0.15 - multiplier in the range 0 - 1
|
||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
||||
*/
|
||||
export function emphasize(color: string, coefficient: number = 0.15): string {
|
||||
return getLuminance(color) > 0.5 ? darken(color, coefficient) : lighten(color, coefficient);
|
||||
}
|
696
packages/taler-wallet-webextension/src/mui/style.tsx
Normal file
696
packages/taler-wallet-webextension/src/mui/style.tsx
Normal file
@ -0,0 +1,696 @@
|
||||
import { css } from "@linaria/core";
|
||||
import { darken, lighten } from "polished";
|
||||
import {
|
||||
common,
|
||||
purple,
|
||||
red,
|
||||
orange,
|
||||
blue,
|
||||
lightBlue,
|
||||
green,
|
||||
grey,
|
||||
} from "./colors/constants";
|
||||
import { getContrastRatio } from "./colors/manipulation";
|
||||
|
||||
export function round(value: number): number {
|
||||
return Math.round(value * 1e5) / 1e5;
|
||||
}
|
||||
const fontSize = 14;
|
||||
const htmlFontSize = 16;
|
||||
const coef = fontSize / 14;
|
||||
export function pxToRem(size: number): string {
|
||||
return `${(size / htmlFontSize) * coef}rem`;
|
||||
}
|
||||
|
||||
export const theme = createTheme();
|
||||
|
||||
export const ripple = css`
|
||||
background-position: center;
|
||||
|
||||
transition: background 0.5s;
|
||||
&:hover {
|
||||
background: #47a7f5 radial-gradient(circle, transparent 1%, #47a7f5 1%)
|
||||
center/15000%;
|
||||
}
|
||||
&:active {
|
||||
background-color: #6eb9f7;
|
||||
background-size: 100%;
|
||||
transition: background 0s;
|
||||
}
|
||||
`;
|
||||
|
||||
function createTheme() {
|
||||
const light = {
|
||||
// The colors used to style the text.
|
||||
text: {
|
||||
// The most important text.
|
||||
primary: "rgba(0, 0, 0, 0.87)",
|
||||
// Secondary text.
|
||||
secondary: "rgba(0, 0, 0, 0.6)",
|
||||
// Disabled text have even lower visual prominence.
|
||||
disabled: "rgba(0, 0, 0, 0.38)",
|
||||
},
|
||||
// The color used to divide different elements.
|
||||
divider: "rgba(0, 0, 0, 0.12)",
|
||||
// The background colors used to style the surfaces.
|
||||
// Consistency between these values is important.
|
||||
background: {
|
||||
paper: common.white,
|
||||
default: common.white,
|
||||
},
|
||||
// The colors used to style the action elements.
|
||||
action: {
|
||||
// The color of an active action like an icon button.
|
||||
active: "rgba(0, 0, 0, 0.54)",
|
||||
// The color of an hovered action.
|
||||
hover: "rgba(0, 0, 0, 0.04)",
|
||||
hoverOpacity: 0.04,
|
||||
// The color of a selected action.
|
||||
selected: "rgba(0, 0, 0, 0.08)",
|
||||
selectedOpacity: 0.08,
|
||||
// The color of a disabled action.
|
||||
disabled: "rgba(0, 0, 0, 0.26)",
|
||||
// The background color of a disabled action.
|
||||
disabledBackground: "rgba(0, 0, 0, 0.12)",
|
||||
disabledOpacity: 0.38,
|
||||
focus: "rgba(0, 0, 0, 0.12)",
|
||||
focusOpacity: 0.12,
|
||||
activatedOpacity: 0.12,
|
||||
},
|
||||
};
|
||||
|
||||
const dark = {
|
||||
text: {
|
||||
primary: common.white,
|
||||
secondary: "rgba(255, 255, 255, 0.7)",
|
||||
disabled: "rgba(255, 255, 255, 0.5)",
|
||||
icon: "rgba(255, 255, 255, 0.5)",
|
||||
},
|
||||
divider: "rgba(255, 255, 255, 0.12)",
|
||||
background: {
|
||||
paper: "#121212",
|
||||
default: "#121212",
|
||||
},
|
||||
action: {
|
||||
active: common.white,
|
||||
hover: "rgba(255, 255, 255, 0.08)",
|
||||
hoverOpacity: 0.08,
|
||||
selected: "rgba(255, 255, 255, 0.16)",
|
||||
selectedOpacity: 0.16,
|
||||
disabled: "rgba(255, 255, 255, 0.3)",
|
||||
disabledBackground: "rgba(255, 255, 255, 0.12)",
|
||||
disabledOpacity: 0.38,
|
||||
focus: "rgba(255, 255, 255, 0.12)",
|
||||
focusOpacity: 0.12,
|
||||
activatedOpacity: 0.24,
|
||||
},
|
||||
};
|
||||
|
||||
const defaultFontFamily = '"Roboto", "Helvetica", "Arial", sans-serif';
|
||||
|
||||
const shadowKeyUmbraOpacity = 0.2;
|
||||
const shadowKeyPenumbraOpacity = 0.14;
|
||||
const shadowAmbientShadowOpacity = 0.12;
|
||||
|
||||
const typography = createTypography({});
|
||||
const palette = createPalette({});
|
||||
const shadows = createAllShadows();
|
||||
const transitions = createTransitions({});
|
||||
const breakpoints = createBreakpoints({});
|
||||
const shape = {
|
||||
borderRadius: css`
|
||||
border-radius: 4px;
|
||||
`,
|
||||
};
|
||||
/////////////////////
|
||||
///////////////////// BREAKPOINTS
|
||||
/////////////////////
|
||||
function createBreakpoints(breakpoints: any) {
|
||||
const {
|
||||
// The breakpoint **start** at this value.
|
||||
// For instance with the first breakpoint xs: [xs, sm).
|
||||
values = {
|
||||
xs: 0,
|
||||
sm: 600,
|
||||
md: 900,
|
||||
lg: 1200,
|
||||
xl: 1536, // large screen
|
||||
},
|
||||
unit = "px",
|
||||
step = 5,
|
||||
// ...other
|
||||
} = breakpoints;
|
||||
|
||||
const keys = Object.keys(values);
|
||||
|
||||
function up(key: any) {
|
||||
const value = typeof values[key] === "number" ? values[key] : key;
|
||||
return `@media (min-width:${value}${unit})`;
|
||||
}
|
||||
|
||||
function down(key: any) {
|
||||
const value = typeof values[key] === "number" ? values[key] : key;
|
||||
return `@media (max-width:${value - step / 100}${unit})`;
|
||||
}
|
||||
|
||||
function between(start: any, end: any) {
|
||||
const endIndex = keys.indexOf(end);
|
||||
|
||||
return (
|
||||
`@media (min-width:${
|
||||
typeof values[start] === "number" ? values[start] : start
|
||||
}${unit}) and ` +
|
||||
`(max-width:${
|
||||
(endIndex !== -1 && typeof values[keys[endIndex]] === "number"
|
||||
? values[keys[endIndex]]
|
||||
: end) -
|
||||
step / 100
|
||||
}${unit})`
|
||||
);
|
||||
}
|
||||
|
||||
function only(key: any) {
|
||||
if (keys.indexOf(key) + 1 < keys.length) {
|
||||
return between(key, keys[keys.indexOf(key) + 1]);
|
||||
}
|
||||
|
||||
return up(key);
|
||||
}
|
||||
|
||||
function not(key: any) {
|
||||
// handle first and last key separately, for better readability
|
||||
const keyIndex = keys.indexOf(key);
|
||||
if (keyIndex === 0) {
|
||||
return up(keys[1]);
|
||||
}
|
||||
if (keyIndex === keys.length - 1) {
|
||||
return down(keys[keyIndex]);
|
||||
}
|
||||
|
||||
return between(key, keys[keys.indexOf(key) + 1]).replace(
|
||||
"@media",
|
||||
"@media not all and",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
keys,
|
||||
values,
|
||||
up,
|
||||
down,
|
||||
between,
|
||||
only,
|
||||
not,
|
||||
unit,
|
||||
// ...other,
|
||||
};
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
///////////////////// SHADOWS
|
||||
/////////////////////
|
||||
function createShadow(...px: number[]): string {
|
||||
return [
|
||||
`${px[0]}px ${px[1]}px ${px[2]}px ${px[3]}px rgba(0,0,0,${shadowKeyUmbraOpacity})`,
|
||||
`${px[4]}px ${px[5]}px ${px[6]}px ${px[7]}px rgba(0,0,0,${shadowKeyPenumbraOpacity})`,
|
||||
`${px[8]}px ${px[9]}px ${px[10]}px ${px[11]}px rgba(0,0,0,${shadowAmbientShadowOpacity})`,
|
||||
].join(",");
|
||||
}
|
||||
|
||||
function createAllShadows() {
|
||||
// Values from https://github.com/material-components/material-components-web/blob/be8747f94574669cb5e7add1a7c54fa41a89cec7/packages/mdc-elevation/_variables.scss
|
||||
return [
|
||||
"none",
|
||||
createShadow(0, 2, 1, -1, 0, 1, 1, 0, 0, 1, 3, 0),
|
||||
createShadow(0, 3, 1, -2, 0, 2, 2, 0, 0, 1, 5, 0),
|
||||
createShadow(0, 3, 3, -2, 0, 3, 4, 0, 0, 1, 8, 0),
|
||||
createShadow(0, 2, 4, -1, 0, 4, 5, 0, 0, 1, 10, 0),
|
||||
createShadow(0, 3, 5, -1, 0, 5, 8, 0, 0, 1, 14, 0),
|
||||
createShadow(0, 3, 5, -1, 0, 6, 10, 0, 0, 1, 18, 0),
|
||||
createShadow(0, 4, 5, -2, 0, 7, 10, 1, 0, 2, 16, 1),
|
||||
createShadow(0, 5, 5, -3, 0, 8, 10, 1, 0, 3, 14, 2),
|
||||
createShadow(0, 5, 6, -3, 0, 9, 12, 1, 0, 3, 16, 2),
|
||||
createShadow(0, 6, 6, -3, 0, 10, 14, 1, 0, 4, 18, 3),
|
||||
createShadow(0, 6, 7, -4, 0, 11, 15, 1, 0, 4, 20, 3),
|
||||
createShadow(0, 7, 8, -4, 0, 12, 17, 2, 0, 5, 22, 4),
|
||||
createShadow(0, 7, 8, -4, 0, 13, 19, 2, 0, 5, 24, 4),
|
||||
createShadow(0, 7, 9, -4, 0, 14, 21, 2, 0, 5, 26, 4),
|
||||
createShadow(0, 8, 9, -5, 0, 15, 22, 2, 0, 6, 28, 5),
|
||||
createShadow(0, 8, 10, -5, 0, 16, 24, 2, 0, 6, 30, 5),
|
||||
createShadow(0, 8, 11, -5, 0, 17, 26, 2, 0, 6, 32, 5),
|
||||
createShadow(0, 9, 11, -5, 0, 18, 28, 2, 0, 7, 34, 6),
|
||||
createShadow(0, 9, 12, -6, 0, 19, 29, 2, 0, 7, 36, 6),
|
||||
createShadow(0, 10, 13, -6, 0, 20, 31, 3, 0, 8, 38, 7),
|
||||
createShadow(0, 10, 13, -6, 0, 21, 33, 3, 0, 8, 40, 7),
|
||||
createShadow(0, 10, 14, -6, 0, 22, 35, 3, 0, 8, 42, 7),
|
||||
createShadow(0, 11, 14, -7, 0, 23, 36, 3, 0, 9, 44, 8),
|
||||
createShadow(0, 11, 15, -7, 0, 24, 38, 3, 0, 9, 46, 8),
|
||||
];
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
///////////////////// TYPOGRAPHY
|
||||
/////////////////////
|
||||
/**
|
||||
* @see @link{https://material.io/design/typography/the-type-system.html}
|
||||
* @see @link{https://material.io/design/typography/understanding-typography.html}
|
||||
*/
|
||||
function createTypography(typography: any) {
|
||||
// const {
|
||||
const fontFamily = defaultFontFamily,
|
||||
// The default font size of the Material Specification.
|
||||
fontSize = 14, // px
|
||||
fontWeightLight = 300,
|
||||
fontWeightRegular = 400,
|
||||
fontWeightMedium = 500,
|
||||
fontWeightBold = 700,
|
||||
// Tell MUI what's the font-size on the html element.
|
||||
// 16px is the default font-size used by browsers.
|
||||
htmlFontSize = 16;
|
||||
// Apply the CSS properties to all the variants.
|
||||
// allVariants,
|
||||
// pxToRem: pxToRem2,
|
||||
// ...other
|
||||
// } = typography;
|
||||
const variants = {
|
||||
// h1: buildVariant(fontWeightLight, 96, 1.167, -1.5),
|
||||
// h2: buildVariant(fontWeightLight, 60, 1.2, -0.5),
|
||||
// h3: buildVariant(fontWeightRegular, 48, 1.167, 0),
|
||||
// h4: buildVariant(fontWeightRegular, 34, 1.235, 0.25),
|
||||
// h5: buildVariant(fontWeightRegular, 24, 1.334, 0),
|
||||
// h6: buildVariant(fontWeightMedium, 20, 1.6, 0.15),
|
||||
// subtitle1: buildVariant(fontWeightRegular, 16, 1.75, 0.15),
|
||||
// subtitle2: buildVariant(fontWeightMedium, 14, 1.57, 0.1),
|
||||
// body1: buildVariant(fontWeightRegular, 16, 1.5, 0.15),
|
||||
// body2: buildVariant(fontWeightRegular, 14, 1.43, 0.15),
|
||||
button: css`
|
||||
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
|
||||
font-weight: ${fontWeightMedium};
|
||||
font-size: ${pxToRem(14)};
|
||||
line-height: 1.75;
|
||||
letter-spacing: ${round(0.4 / 14)}em;
|
||||
text-transform: uppercase;
|
||||
`,
|
||||
// button: buildVariant(fontWeightMedium, 14, 1.75, 0.4, caseAllCaps),
|
||||
// caption: buildVariant(fontWeightRegular, 12, 1.66, 0.4),
|
||||
// overline: buildVariant(fontWeightRegular, 12, 2.66, 1, caseAllCaps),
|
||||
};
|
||||
|
||||
return deepmerge(
|
||||
{
|
||||
htmlFontSize,
|
||||
pxToRem,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeightLight,
|
||||
fontWeightRegular,
|
||||
fontWeightMedium,
|
||||
fontWeightBold,
|
||||
...variants,
|
||||
},
|
||||
// other,
|
||||
{
|
||||
clone: false, // No need to clone deep
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
///////////////////// MIXINS
|
||||
/////////////////////
|
||||
function createMixins(breakpoints: any, spacing: any, mixins: any) {
|
||||
return {
|
||||
toolbar: {
|
||||
minHeight: 56,
|
||||
[`${breakpoints.up("xs")} and (orientation: landscape)`]: {
|
||||
minHeight: 48,
|
||||
},
|
||||
[breakpoints.up("sm")]: {
|
||||
minHeight: 64,
|
||||
},
|
||||
},
|
||||
...mixins,
|
||||
};
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
///////////////////// TRANSITION
|
||||
/////////////////////
|
||||
function formatMs(milliseconds: number) {
|
||||
return `${Math.round(milliseconds)}ms`;
|
||||
}
|
||||
|
||||
function getAutoHeightDuration(height: number) {
|
||||
if (!height) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const constant = height / 36;
|
||||
|
||||
// https://www.wolframalpha.com/input/?i=(4+%2B+15+*+(x+%2F+36+)+**+0.25+%2B+(x+%2F+36)+%2F+5)+*+10
|
||||
return Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10);
|
||||
}
|
||||
|
||||
function createTransitions(inputTransitions: any) {
|
||||
// Follow https://material.google.com/motion/duration-easing.html#duration-easing-natural-easing-curves
|
||||
// to learn the context in which each easing should be used.
|
||||
const easing = {
|
||||
// This is the most common easing curve.
|
||||
easeInOut: "cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
// Objects enter the screen at full velocity from off-screen and
|
||||
// slowly decelerate to a resting point.
|
||||
easeOut: "cubic-bezier(0.0, 0, 0.2, 1)",
|
||||
// Objects leave the screen at full velocity. They do not decelerate when off-screen.
|
||||
easeIn: "cubic-bezier(0.4, 0, 1, 1)",
|
||||
// The sharp curve is used by objects that may return to the screen at any time.
|
||||
sharp: "cubic-bezier(0.4, 0, 0.6, 1)",
|
||||
};
|
||||
|
||||
// Follow https://material.io/guidelines/motion/duration-easing.html#duration-easing-common-durations
|
||||
// to learn when use what timing
|
||||
const duration = {
|
||||
shortest: 150,
|
||||
shorter: 200,
|
||||
short: 250,
|
||||
// most basic recommended timing
|
||||
standard: 300,
|
||||
// this is to be used in complex animations
|
||||
complex: 375,
|
||||
// recommended when something is entering screen
|
||||
enteringScreen: 225,
|
||||
// recommended when something is leaving screen
|
||||
leavingScreen: 195,
|
||||
};
|
||||
|
||||
const mergedEasing = {
|
||||
...easing,
|
||||
...inputTransitions.easing,
|
||||
};
|
||||
|
||||
const mergedDuration = {
|
||||
...duration,
|
||||
...inputTransitions.duration,
|
||||
};
|
||||
|
||||
const create = (props = ["all"], options = {} as any) => {
|
||||
const {
|
||||
duration: durationOption = mergedDuration.standard,
|
||||
easing: easingOption = mergedEasing.easeInOut,
|
||||
delay = 0,
|
||||
// ...other
|
||||
} = options;
|
||||
|
||||
return (Array.isArray(props) ? props : [props])
|
||||
.map(
|
||||
(animatedProp) =>
|
||||
`${animatedProp} ${
|
||||
typeof durationOption === "string"
|
||||
? durationOption
|
||||
: formatMs(durationOption)
|
||||
} ${easingOption} ${
|
||||
typeof delay === "string" ? delay : formatMs(delay)
|
||||
}`,
|
||||
)
|
||||
.join(",");
|
||||
};
|
||||
|
||||
return {
|
||||
getAutoHeightDuration,
|
||||
create,
|
||||
...inputTransitions,
|
||||
easing: mergedEasing,
|
||||
duration: mergedDuration,
|
||||
};
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
///////////////////// PALETTE
|
||||
/////////////////////
|
||||
function createPalette(palette: any) {
|
||||
// const {
|
||||
const mode: "light" | "dark" = "light";
|
||||
const contrastThreshold = 3;
|
||||
const tonalOffset = 0.2;
|
||||
// ...other
|
||||
// } = palette;
|
||||
|
||||
const primary = palette.primary || getDefaultPrimary(mode);
|
||||
const secondary = palette.secondary || getDefaultSecondary(mode);
|
||||
const error = palette.error || getDefaultError(mode);
|
||||
const info = palette.info || getDefaultInfo(mode);
|
||||
const success = palette.success || getDefaultSuccess(mode);
|
||||
const warning = palette.warning || getDefaultWarning(mode);
|
||||
|
||||
// Use the same logic as
|
||||
// Bootstrap: https://github.com/twbs/bootstrap/blob/1d6e3710dd447de1a200f29e8fa521f8a0908f70/scss/_functions.scss#L59
|
||||
// and material-components-web https://github.com/material-components/material-components-web/blob/ac46b8863c4dab9fc22c4c662dc6bd1b65dd652f/packages/mdc-theme/_functions.scss#L54
|
||||
function getContrastText(background: string): string {
|
||||
const contrastText =
|
||||
getContrastRatio(background, dark.text.primary) >= contrastThreshold
|
||||
? dark.text.primary
|
||||
: light.text.primary;
|
||||
|
||||
return contrastText;
|
||||
}
|
||||
|
||||
const augmentColor = ({
|
||||
color,
|
||||
name,
|
||||
mainShade = 500,
|
||||
lightShade = 300,
|
||||
darkShade = 700,
|
||||
}: any) => {
|
||||
color = { ...color };
|
||||
if (!color.main && color[mainShade]) {
|
||||
color.main = color[mainShade];
|
||||
}
|
||||
|
||||
addLightOrDark(color, "light", lightShade, tonalOffset);
|
||||
addLightOrDark(color, "dark", darkShade, tonalOffset);
|
||||
if (!color.contrastText) {
|
||||
color.contrastText = getContrastText(color.main);
|
||||
}
|
||||
|
||||
return color;
|
||||
};
|
||||
|
||||
const modes = { dark, light };
|
||||
|
||||
// if (process.env.NODE_ENV !== "production") {
|
||||
// if (!modes[mode]) {
|
||||
// console.error(`MUI: The palette mode \`${mode}\` is not supported.`);
|
||||
// }
|
||||
// }
|
||||
const paletteOutput = deepmerge(
|
||||
{
|
||||
// A collection of common colors.
|
||||
common,
|
||||
// The palette mode, can be light or dark.
|
||||
mode,
|
||||
// The colors used to represent primary interface elements for a user.
|
||||
primary: augmentColor({ color: primary, name: "primary" }),
|
||||
// The colors used to represent secondary interface elements for a user.
|
||||
secondary: augmentColor({
|
||||
color: secondary,
|
||||
name: "secondary",
|
||||
mainShade: "A400",
|
||||
lightShade: "A200",
|
||||
darkShade: "A700",
|
||||
}),
|
||||
// The colors used to represent interface elements that the user should be made aware of.
|
||||
error: augmentColor({ color: error, name: "error" }),
|
||||
// The colors used to represent potentially dangerous actions or important messages.
|
||||
warning: augmentColor({ color: warning, name: "warning" }),
|
||||
// The colors used to present information to the user that is neutral and not necessarily important.
|
||||
info: augmentColor({ color: info, name: "info" }),
|
||||
// The colors used to indicate the successful completion of an action that user triggered.
|
||||
success: augmentColor({ color: success, name: "success" }),
|
||||
// The grey colors.
|
||||
grey,
|
||||
// Used by `getContrastText()` to maximize the contrast between
|
||||
// the background and the text.
|
||||
contrastThreshold,
|
||||
// Takes a background color and returns the text color that maximizes the contrast.
|
||||
getContrastText,
|
||||
// Generate a rich color object.
|
||||
augmentColor,
|
||||
// Used by the functions below to shift a color's luminance by approximately
|
||||
// two indexes within its tonal palette.
|
||||
// E.g., shift from Red 500 to Red 300 or Red 700.
|
||||
tonalOffset,
|
||||
// The light and dark mode object.
|
||||
...modes[mode],
|
||||
},
|
||||
// other:
|
||||
{},
|
||||
);
|
||||
|
||||
return paletteOutput;
|
||||
}
|
||||
|
||||
function addLightOrDark(
|
||||
intent: any,
|
||||
direction: any,
|
||||
shade: any,
|
||||
tonalOffset: any,
|
||||
): void {
|
||||
const tonalOffsetLight = tonalOffset.light || tonalOffset;
|
||||
const tonalOffsetDark = tonalOffset.dark || tonalOffset * 1.5;
|
||||
|
||||
if (!intent[direction]) {
|
||||
if (intent.hasOwnProperty(shade)) {
|
||||
intent[direction] = intent[shade];
|
||||
} else if (direction === "light") {
|
||||
intent.light = lighten(intent.main, tonalOffsetLight);
|
||||
} else if (direction === "dark") {
|
||||
intent.dark = darken(intent.main, tonalOffsetDark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultPrimary(mode = "light") {
|
||||
if (mode === "dark") {
|
||||
return {
|
||||
main: blue[200],
|
||||
light: blue[50],
|
||||
dark: blue[400],
|
||||
};
|
||||
}
|
||||
return {
|
||||
main: blue[700],
|
||||
light: blue[400],
|
||||
dark: blue[800],
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultSecondary(mode = "light") {
|
||||
if (mode === "dark") {
|
||||
return {
|
||||
main: purple[200],
|
||||
light: purple[50],
|
||||
dark: purple[400],
|
||||
};
|
||||
}
|
||||
return {
|
||||
main: purple[500],
|
||||
light: purple[300],
|
||||
dark: purple[700],
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultError(mode = "light") {
|
||||
if (mode === "dark") {
|
||||
return {
|
||||
main: red[500],
|
||||
light: red[300],
|
||||
dark: red[700],
|
||||
};
|
||||
}
|
||||
return {
|
||||
main: red[700],
|
||||
light: red[400],
|
||||
dark: red[800],
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultInfo(mode = "light") {
|
||||
if (mode === "dark") {
|
||||
return {
|
||||
main: lightBlue[400],
|
||||
light: lightBlue[300],
|
||||
dark: lightBlue[700],
|
||||
};
|
||||
}
|
||||
return {
|
||||
main: lightBlue[700],
|
||||
light: lightBlue[500],
|
||||
dark: lightBlue[900],
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultSuccess(mode = "light") {
|
||||
if (mode === "dark") {
|
||||
return {
|
||||
main: green[400],
|
||||
light: green[300],
|
||||
dark: green[700],
|
||||
};
|
||||
}
|
||||
return {
|
||||
main: green[800],
|
||||
light: green[500],
|
||||
dark: green[900],
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultWarning(mode = "light") {
|
||||
if (mode === "dark") {
|
||||
return {
|
||||
main: orange[400],
|
||||
light: orange[300],
|
||||
dark: orange[700],
|
||||
};
|
||||
}
|
||||
return {
|
||||
main: "#ed6c02",
|
||||
light: orange[500],
|
||||
dark: orange[900],
|
||||
};
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
///////////////////// DEEP MERGE
|
||||
/////////////////////
|
||||
function isPlainObject(item: unknown): item is Record<keyof any, unknown> {
|
||||
return (
|
||||
item !== null && typeof item === "object" && item.constructor === Object
|
||||
);
|
||||
}
|
||||
|
||||
interface DeepmergeOptions {
|
||||
clone?: boolean;
|
||||
}
|
||||
|
||||
function deepmerge<T>(
|
||||
target: T,
|
||||
source: unknown,
|
||||
options: DeepmergeOptions = { clone: true },
|
||||
): T {
|
||||
const output = options.clone ? { ...target } : target;
|
||||
|
||||
if (isPlainObject(target) && isPlainObject(source)) {
|
||||
Object.keys(source).forEach((key) => {
|
||||
// Avoid prototype pollution
|
||||
if (key === "__proto__") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isPlainObject(source[key]) &&
|
||||
key in target &&
|
||||
isPlainObject(target[key])
|
||||
) {
|
||||
// Since `output` is a clone of `target` and we have narrowed `target` in this block we can cast to the same type.
|
||||
(output as Record<keyof any, unknown>)[key] = deepmerge(
|
||||
target[key],
|
||||
source[key],
|
||||
options,
|
||||
);
|
||||
} else {
|
||||
(output as Record<keyof any, unknown>)[key] = source[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
return {
|
||||
typography,
|
||||
palette,
|
||||
shadows,
|
||||
shape,
|
||||
transitions,
|
||||
breakpoints,
|
||||
pxToRem,
|
||||
};
|
||||
}
|
@ -45,4 +45,5 @@ export const AllOff = createExample(TestedComponent, {
|
||||
retryInfo: undefined,
|
||||
},
|
||||
],
|
||||
coins: [],
|
||||
});
|
||||
|
@ -86,5 +86,3 @@ export const BitcoinTest = createExample(TestedComponent, {
|
||||
},
|
||||
exchangeBaseUrl: "https://exchange.demo.taler.net",
|
||||
});
|
||||
// tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx OK
|
||||
// tb10v8ahvcqqleage3q5rqn3agnr7pd25msd5wd4hcj
|
||||
|
@ -27,7 +27,6 @@ export function ReserveCreated({
|
||||
amount,
|
||||
}: Props): VNode {
|
||||
const paytoURI = parsePaytoUri(payto);
|
||||
// const url = new URL(paytoURI?.targetPath);
|
||||
if (!paytoURI) {
|
||||
return (
|
||||
<div>
|
||||
|
@ -61,7 +61,6 @@ export function View({
|
||||
<p>
|
||||
<i18n.Translate>Thank you for installing the wallet.</i18n.Translate>
|
||||
</p>
|
||||
<Diagnostics diagnostics={diagnostics} timedOut={timedOut} />
|
||||
<h2>
|
||||
<i18n.Translate>Permissions</i18n.Translate>
|
||||
</h2>
|
||||
|
@ -345,16 +345,19 @@ importers:
|
||||
'@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
|
||||
date-fns: ^2.28.0
|
||||
history: 4.10.1
|
||||
mocha: ^9.2.0
|
||||
nyc: ^15.1.0
|
||||
polished: ^4.1.4
|
||||
preact: ^10.6.5
|
||||
preact-cli: ^3.3.5
|
||||
preact-render-to-string: ^5.1.19
|
||||
@ -371,8 +374,11 @@ importers:
|
||||
dependencies:
|
||||
'@gnu-taler/taler-util': link:../taler-util
|
||||
'@gnu-taler/taler-wallet-core': link:../taler-wallet-core
|
||||
'@types/chai': 4.3.0
|
||||
chai: 4.3.6
|
||||
date-fns: 2.28.0
|
||||
history: 4.10.1
|
||||
polished: 4.1.4
|
||||
preact: 10.6.5
|
||||
preact-router: 3.2.1_preact@10.6.5
|
||||
qrcode-generator: 1.4.4
|
||||
@ -2992,7 +2998,6 @@ packages:
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
regenerator-runtime: 0.13.9
|
||||
dev: true
|
||||
|
||||
/@babel/template/7.14.5:
|
||||
resolution: {integrity: sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==}
|
||||
@ -6667,6 +6672,10 @@ packages:
|
||||
'@types/node': 17.0.17
|
||||
dev: true
|
||||
|
||||
/@types/chai/4.3.0:
|
||||
resolution: {integrity: sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==}
|
||||
dev: false
|
||||
|
||||
/@types/cheerio/0.22.30:
|
||||
resolution: {integrity: sha512-t7ZVArWZlq3dFa9Yt33qFBQIK4CQd1Q3UJp0V+UhP6vgLWLM6Qug7vZuRSGXg45zXeB1Fm5X2vmBkEX58LV2Tw==}
|
||||
dependencies:
|
||||
@ -7734,6 +7743,10 @@ packages:
|
||||
util: 0.10.3
|
||||
dev: true
|
||||
|
||||
/assertion-error/1.1.0:
|
||||
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
|
||||
dev: false
|
||||
|
||||
/assign-symbols/1.0.0:
|
||||
resolution: {integrity: sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -8747,6 +8760,19 @@ packages:
|
||||
resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==}
|
||||
dev: true
|
||||
|
||||
/chai/4.3.6:
|
||||
resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==}
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
assertion-error: 1.1.0
|
||||
check-error: 1.0.2
|
||||
deep-eql: 3.0.1
|
||||
get-func-name: 2.0.0
|
||||
loupe: 2.3.4
|
||||
pathval: 1.1.1
|
||||
type-detect: 4.0.8
|
||||
dev: false
|
||||
|
||||
/chalk/0.4.0:
|
||||
resolution: {integrity: sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=}
|
||||
engines: {node: '>=0.8.0'}
|
||||
@ -8812,6 +8838,10 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/check-error/1.0.2:
|
||||
resolution: {integrity: sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=}
|
||||
dev: false
|
||||
|
||||
/cheerio-select/1.5.0:
|
||||
resolution: {integrity: sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==}
|
||||
dependencies:
|
||||
@ -9933,6 +9963,13 @@ packages:
|
||||
mimic-response: 1.0.1
|
||||
dev: true
|
||||
|
||||
/deep-eql/3.0.1:
|
||||
resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==}
|
||||
engines: {node: '>=0.12'}
|
||||
dependencies:
|
||||
type-detect: 4.0.8
|
||||
dev: false
|
||||
|
||||
/deep-equal/1.1.1:
|
||||
resolution: {integrity: sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==}
|
||||
dependencies:
|
||||
@ -11843,6 +11880,10 @@ packages:
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
dev: true
|
||||
|
||||
/get-func-name/2.0.0:
|
||||
resolution: {integrity: sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=}
|
||||
dev: false
|
||||
|
||||
/get-intrinsic/1.1.1:
|
||||
resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==}
|
||||
dependencies:
|
||||
@ -13795,6 +13836,12 @@ packages:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
/loupe/2.3.4:
|
||||
resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==}
|
||||
dependencies:
|
||||
get-func-name: 2.0.0
|
||||
dev: false
|
||||
|
||||
/lower-case/1.1.4:
|
||||
resolution: {integrity: sha1-miyr0bno4K6ZOkv31YdcOcQujqw=}
|
||||
dev: true
|
||||
@ -15188,6 +15235,10 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/pathval/1.1.1:
|
||||
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
||||
dev: false
|
||||
|
||||
/pbkdf2/3.1.2:
|
||||
resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==}
|
||||
engines: {node: '>=0.12'}
|
||||
@ -15308,7 +15359,6 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.17.2
|
||||
dev: true
|
||||
|
||||
/portfinder/1.0.28:
|
||||
resolution: {integrity: sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==}
|
||||
@ -18840,7 +18890,6 @@ packages:
|
||||
/type-detect/4.0.8:
|
||||
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/type-fest/0.13.1:
|
||||
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
|
||||
|
Loading…
Reference in New Issue
Block a user