fix(ui): improve types, button styles, add color-modes

This commit is contained in:
Thomas Osmonson
2020-06-23 17:18:11 -05:00
committed by Thomas Osmonson
parent 17fb88cc76
commit 67c7f31357
26 changed files with 378 additions and 71 deletions

View File

@@ -16,11 +16,13 @@
"@reach/rect": "^0.10.2",
"@styled-system/css": "5.1.5",
"@styled-system/should-forward-prop": "^5.1.5",
"@styled-system/theme-get": "^5.1.2",
"@types/react-transition-group": "^4.2.4",
"@types/styled-components": "^5.1.0",
"@types/styled-system": "^5.1.6",
"@types/styled-system__css": "^5.0.8",
"@types/styled-system__should-forward-prop": "^5.1.1",
"@types/styled-system__theme-get": "^5.0.0",
"color": "3.1.2",
"flushable": "^1.0.0",
"prettier": "^2.0.5",
@@ -30,6 +32,7 @@
"react-spring": "8.0.27",
"react-transition-group": "^4.4.1",
"styled-system": "5.1.5",
"type-fest": "^0.15.1",
"use-events": "^1.4.1",
"use-onclickoutside": "^0.3.1"
},

View File

@@ -33,7 +33,7 @@ export const Button = forwardRef<Ref<HTMLDivElement>, ButtonProps>(
mode = 'primary',
variant = 'solid',
type,
size = 'lg',
size = 'md',
isLoading,
loadingText,
customStyles,
@@ -78,7 +78,7 @@ export const Button = forwardRef<Ref<HTMLDivElement>, ButtonProps>(
position={loadingText ? 'relative' : 'absolute'}
mx={!loadingText ? 'auto' : 'unset'}
color="currentColor"
size="sm"
size={size === 'sm' ? 'xs' : 'sm'}
/>
)}
{isLoading

View File

@@ -30,6 +30,12 @@ const sizes = {
fontSize: '14px !important',
px: 4,
},
sm: {
minHeight: 6,
minWidth: 10,
fontSize: '11px !important',
px: 3,
},
};
const unstyledStyle = {

View File

@@ -1,11 +1,10 @@
import * as React from 'react';
import * as StyledSystem from 'styled-system';
import { PseudoBoxProps } from '../pseudo-box';
import { BoxProps } from '../box';
/**
* The size of the button
*/
export type ButtonSizes = 'md' | 'lg';
export type ButtonSizes = 'sm' | 'md' | 'lg';
/**
* The color scheme of the button variant. Use the color keys passed in `theme.colors`.
*/
@@ -20,7 +19,7 @@ export type ButtonVariants = 'outline' | 'unstyled' | 'link' | 'solid';
export type ButtonModes = 'primary' | 'secondary' | 'tertiary';
export type CustomStyles = {
[key in ButtonModes]: PseudoBoxProps;
[key in ButtonModes]: BoxProps;
};
interface ButtonPropsBase {
@@ -55,21 +54,6 @@ interface ButtonPropsBase {
* The content of the button.
*/
children: React.ReactNode;
/**
* If added, the button will show an icon before the button's label.
* Use the icon key in `theme.iconPath`
*/
leftIcon?: string;
/**
* If added, the button will show an icon after the button's label.
* Use the icon key in `theme.iconPath`
*/
rightIcon?: string;
/**
* The space between the button icon and label.
* Use the styled-system tokens or add custom values as a string
*/
iconSpacing?: StyledSystem.MarginProps['margin'];
}
export interface ButtonStyles {
@@ -79,4 +63,4 @@ export interface ButtonStyles {
customStyles?: CustomStyles;
}
export type ButtonProps = ButtonPropsBase & PseudoBoxProps & React.RefAttributes<HTMLButtonElement>;
export type ButtonProps = ButtonPropsBase & BoxProps & React.RefAttributes<HTMLButtonElement>;

View File

@@ -0,0 +1,64 @@
import React, { useEffect, useCallback } from 'react';
import { useMediaQuery } from '../hooks/use-media-query';
import { ColorModes } from './styles';
export * from './utils';
export * from './types';
export * from './styles';
export const ColorModeContext = React.createContext<{ colorMode?: string; toggleColorMode?: any }>({
colorMode: undefined,
});
export const ColorModeProvider = ({
colorMode,
children,
onChange,
}: {
colorMode?: string;
children: any;
onChange?: (mode: string) => void;
}) => {
const [mode, setMode] = React.useState(colorMode);
const [darkmode] = useMediaQuery('(prefers-color-scheme: dark)');
const [lightmode] = useMediaQuery('(prefers-color-scheme: light)');
useEffect(() => {
if (!mode) {
setMode(darkmode ? 'dark' : 'light');
}
}, [mode, darkmode, lightmode]);
const setColorMode = useCallback(
(mode: 'light' | 'dark') => {
setMode(mode);
onChange && onChange(mode);
},
[mode]
);
const toggleColorMode = useCallback(() => {
if (mode === 'light') {
setColorMode('dark');
return;
}
if (mode === 'dark') {
setColorMode('light');
return;
}
if (!colorMode && darkmode) {
setColorMode('light');
return;
}
if (!mode && lightmode) {
setColorMode('dark');
return;
}
}, [mode, lightmode, darkmode]);
return (
<ColorModeContext.Provider value={{ colorMode: mode, toggleColorMode }}>
<ColorModes colorMode={mode} />
{children}
</ColorModeContext.Provider>
);
};

View File

@@ -0,0 +1,54 @@
import { createGlobalStyle } from 'styled-components';
import { generateCssVariables } from './utils';
export const ColorModes = createGlobalStyle`
:root{
${generateCssVariables('light')};
}
@media (prefers-color-scheme: dark) {
:root {
${generateCssVariables('dark')};
}
}
@media (prefers-color-scheme: light) {
:root {
${generateCssVariables('light')};
}
}
html, body, #__next {
background: var(--colors-bg);
border-color: var(--colors-border);
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:focus,
select:-webkit-autofill,
select:-webkit-autofill:hover,
select:-webkit-autofill:focus {
-webkit-text-fill-color: var(--colors-text-body);
font-size: 16px !important;
transition: background-color 5000s ease-in-out 0s;
}
input:-ms-input-placeholder,
textarea:-ms-input-placeholder {
color: var(--colors-input-placeholder) !important;
}
input::-ms-input-placeholder,
textarea::-ms-input-placeholder {
color: var(--colors-input-placeholder) !important;
}
input::placeholder,
textarea::placeholder {
color: var(--colors-input-placeholder) !important;
}
`;

View File

@@ -0,0 +1,74 @@
export enum Color {
Accent = 'accent',
Bg = 'bg',
BgAlt = 'bg-alt',
BgLight = 'bg-light',
Invert = 'invert',
TextHover = 'text-hover',
TextTitle = 'text-title',
TextCaption = 'text-caption',
TextBody = 'text-body',
InputPlaceholder = 'input-placeholder',
Border = 'border',
FeedbackAlert = 'feedback-alert',
FeedbackError = 'feedback-error',
FeedbackSuccess = 'feedback-success',
}
export type ColorsStringLiteral =
| 'accent'
| 'bg'
| 'bg-alt'
| 'bg-light'
| 'invert'
| 'text-hover'
| 'text-title'
| 'text-caption'
| 'text-body'
| 'input-placeholder'
| 'border'
| 'feedback-alert'
| 'feedback-error'
| 'feedback-success';
export type ColorModeTypes = {
[key in ColorsStringLiteral]: string;
};
export interface ColorModesInterface {
light: ColorModeTypes;
dark: ColorModeTypes;
}
export type ThemeColorsStringLiteral =
| 'transparent'
| 'current'
| 'black'
| 'white'
| 'blue'
| 'blue.100'
| 'blue.200'
| 'blue.300'
| 'blue.400'
| 'blue.900'
| 'blue.hover'
| 'ink'
| 'ink.50'
| 'ink.100'
| 'ink.150'
| 'ink.200'
| 'ink.250'
| 'ink.300'
| 'ink.400'
| 'ink.600'
| 'darken.50'
| 'darken.100'
| 'darken.150'
| 'red'
| 'green'
| 'orange'
| 'cyan'
| 'feedback.error'
| 'feedback.success'
| 'feedback.warning'
| 'feedback.info';

View File

@@ -0,0 +1,69 @@
import { Theme } from '@blockstack/ui';
import {
ColorModesInterface,
ColorsStringLiteral,
ColorModeTypes,
Color,
ThemeColorsStringLiteral,
} from './types';
import { themeGet } from '@styled-system/theme-get';
export const colorGet = (path: string, fallback?: string): ((props: any) => any) =>
themeGet('colors.' + path, fallback);
const colors = (props: { theme: Theme }): ColorModesInterface => ({
light: {
[Color.Accent]: colorGet('blue')(props),
[Color.Bg]: 'white',
[Color.BgAlt]: colorGet('ink.50')(props),
[Color.BgLight]: 'white',
[Color.Invert]: colorGet('ink')(props),
[Color.TextHover]: colorGet('blue')(props),
[Color.TextTitle]: colorGet('ink')(props),
[Color.TextCaption]: colorGet('ink.600')(props),
[Color.TextBody]: colorGet('ink.900')(props),
[Color.InputPlaceholder]: colorGet('ink.400')(props),
[Color.Border]: 'rgb(229, 229, 236)',
[Color.FeedbackAlert]: colorGet('orange')(props),
[Color.FeedbackError]: colorGet('red')(props),
[Color.FeedbackSuccess]: colorGet('green')(props),
},
dark: {
[Color.Accent]: colorGet('blue.400')(props),
[Color.Bg]: colorGet('ink')(props),
[Color.BgAlt]: 'rgba(255,255,255,0.05)',
[Color.BgLight]: 'rgba(255,255,255,0.08)',
[Color.Invert]: 'white',
[Color.TextHover]: colorGet('blue.300')(props),
[Color.TextTitle]: 'white',
[Color.TextCaption]: '#a7a7ad',
[Color.TextBody]: colorGet('ink.300')(props),
[Color.InputPlaceholder]: 'rgba(255,255,255,0.3)',
[Color.Border]: 'rgb(39, 41, 46)',
[Color.FeedbackAlert]: colorGet('orange')(props),
[Color.FeedbackError]: colorGet('red')(props),
[Color.FeedbackSuccess]: colorGet('green')(props),
},
});
const colorModeStyles = (props: { theme: Theme; colorMode: 'light' | 'dark' }): ColorModeTypes =>
colors(props)[props.colorMode];
const colorMap = (props: { theme: Theme; colorMode: 'light' | 'dark' }): ColorsStringLiteral[] =>
Object.keys(colors(props)[props.colorMode]) as ColorsStringLiteral[];
export const color = (name: ColorsStringLiteral): string => {
return `var(--colors-${name})`;
};
export const themeColor = (name: ThemeColorsStringLiteral): string => {
return name;
};
export const generateCssVariables = (mode: 'light' | 'dark') => ({
colorMode = mode,
...rest
}: any) =>
colorMap({ colorMode, ...rest }).map((key: ColorsStringLiteral) => {
return `--colors-${key}: ${colorModeStyles({ colorMode, ...rest })[key]};`;
});

View File

@@ -9,3 +9,4 @@ export * from './use-previous';
export * from './use-safe-layout-effect';
export * from './use-timeout';
export * from './use-theme';
export * from './use-color-mode';

View File

@@ -0,0 +1,7 @@
import { useContext } from 'react';
import { ColorModeContext } from '../color-modes';
export const useColorMode = () => {
const { colorMode, toggleColorMode } = useContext(ColorModeContext);
return { colorMode, toggleColorMode };
};

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useSafeLayoutEffect } from '@blockstack/ui';
import { useSafeLayoutEffect } from '../hooks';
const isBrowser = typeof window !== 'undefined';

View File

@@ -30,3 +30,4 @@ export * from './transition';
export * from './use-clipboard';
export * from './utils/safe-await';
export * from './visually-hidden';
export * from './color-modes';

View File

@@ -17,12 +17,21 @@ const StyledBox = styled(Box)`
animation: ${spin} ${(props: SpinnerProps) => props.speed} linear infinite;
`;
const sizes = {
xs: '0.75rem',
sm: '1rem',
md: '1.5rem',
lg: '2rem',
xl: '3rem',
const getSize = (size: SpinnerSize): string => {
switch (size) {
case 'xs':
return '0.75rem';
case 'sm':
return '1rem';
case 'md':
return '1.5rem';
case 'lg':
return '2rem';
case 'xl':
return '3rem';
default:
return size;
}
};
/**
@@ -43,7 +52,7 @@ const Spinner = forwardRef<any, SpinnerProps>(
},
ref
) => {
const _size = (sizes[size] || size) as SpinnerSize;
const _size = getSize(size);
return (
<StyledBox

View File

@@ -1,6 +1,17 @@
import { BoxProps } from '../box';
import { LiteralUnion } from 'type-fest';
export type SpinnerSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
export enum SpinnerSizes {
xs = 'xs',
sm = 'sm',
md = 'md',
lg = 'lg',
xl = 'xl',
}
export type NamedSizeLiterals = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
export type SpinnerSize = LiteralUnion<NamedSizeLiterals, string>;
export interface SpinnerPropsBase {
/**

View File

@@ -1,10 +1,10 @@
import { ThemeProvider as StyledComponentsThemeProvider } from 'styled-components';
import React from 'react';
import { ThemeProvider as StyledComponentsThemeProvider } from 'styled-components';
import { theme as defaultTheme, Theme } from '../theme';
export const ThemeContext = React.createContext(defaultTheme);
const ThemeProvider: React.FC<{ theme: Theme; children: any }> = ({
const ThemeProvider: React.FC<{ theme?: Theme; children: any }> = ({
theme = defaultTheme,
children,
}) => <StyledComponentsThemeProvider theme={theme}>{children}</StyledComponentsThemeProvider>;

View File

@@ -1,2 +1,6 @@
export { theme } from './theme';
export * from './colors';
export * from './colors-utils';
export * from './sizes';
export * from './theme';
export * from './types';
export * from './typography';

View File

@@ -1,3 +1,5 @@
import { SpacingTypes } from './types';
const containers = {
'screen-sm': '640px',
'screen-md': '768px',
@@ -38,6 +40,8 @@ export const namedSpacingUnits = {
'extra-loose': baseSizes[8], //32px
};
export const space = (spacing: SpacingTypes): SpacingTypes => spacing;
export const sizes = {
...baseSizes,
containers,

View File

@@ -1,4 +1,5 @@
import { Theme as StyledSystemTheme } from 'styled-system';
import { LiteralUnion } from 'type-fest';
interface CustomTheme {
opacity?: {
@@ -18,3 +19,17 @@ export type Responsive<T, ThemeType extends Theme = RequiredTheme> =
| T
| (T | null)[]
| { [key in keyof ThemeType['breakpoints']]?: T };
export type NamedSpacingLiteral =
| 'none'
| 'extra-tight'
| 'tight'
| 'base-tight'
| 'base'
| 'base-loose'
| 'loose'
| 'extra-loose';
export type Spacing = LiteralUnion<NamedSpacingLiteral, string | number>;
export type SpacingTypes = Spacing | Spacing[];

View File

@@ -80,10 +80,11 @@ export function Tooltip(props: TooltipProps) {
borderRadius="6px"
textStyle="caption.medium"
maxWidth="320px"
{...tooltipProps}
style={{
...tooltipProps.style,
useSelect: 'none',
}}
{...tooltipProps}
>
{label}
{hasAriaLabel && <VisuallyHidden {...hiddenProps}>{ariaLabel}</VisuallyHidden>}

View File

@@ -19,13 +19,13 @@ export type TransitionStatus =
export interface TransitionProps {
in?: boolean;
addEndListener?: EndHandler;
onEnter?: EnterHandler;
onEntering?: EnterHandler;
onEntered?: EnterHandler;
onExit?: ExitHandler;
onExiting?: ExitHandler;
onExited?: ExitHandler;
addEndListener?: EndHandler<any>;
onEnter?: EnterHandler<any>;
onEntering?: EnterHandler<any>;
onEntered?: EnterHandler<any>;
onExit?: ExitHandler<any>;
onExiting?: ExitHandler<any>;
onExited?: ExitHandler<any>;
unmountOnExit?: boolean;
timeout?: TProps['timeout'];
transition?: string;

View File

@@ -1443,34 +1443,6 @@
resolved "https://registry.yarnpkg.com/@blockstack/stats/-/stats-0.7.0.tgz#be39d3e76c2a16c1cd1283aa702d1e20b5e04645"
integrity sha512-AS/UdJH/cJ7K8la3/TbQziEMlRrNI/4VEHSfYUsEzU7Nx892G6qE4uu/XvW9ycS0Jg2/TW2bjTnRijsyecaEJA==
"@blockstack/ui@2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@blockstack/ui/-/ui-2.7.0.tgz#e9d41534885e66783d836d8615695df473079dfa"
integrity sha512-zuex1Oksfrqtc9JWXnHCQQH38H5kXUoWwxuEcKjepnhV2sLHCwU1Nx7p9DM+/R8eBvZXtUVQ1U10I7hbLE7RQg==
dependencies:
"@popperjs/core" "^2.4.0"
"@reach/alert" "^0.10.2"
"@reach/auto-id" "^0.10.0"
"@reach/rect" "^0.10.2"
"@styled-system/css" "5.1.5"
"@styled-system/should-forward-prop" "^5.1.5"
"@types/react-transition-group" "^4.2.4"
"@types/styled-components" "^5.1.0"
"@types/styled-system" "^5.1.6"
"@types/styled-system__css" "^5.0.8"
"@types/styled-system__should-forward-prop" "^5.1.1"
color "3.1.2"
flushable "^1.0.0"
prettier "^2.0.5"
prism-react-renderer "^1.0.2"
prismjs "^1.20.0"
prop-types "^15.7.2"
react-spring "8.0.27"
react-transition-group "^4.4.1"
styled-system "5.1.5"
use-events "^1.4.1"
use-onclickoutside "^0.3.1"
"@blockstack/ui@^1.6.2":
version "1.6.3"
resolved "https://registry.yarnpkg.com/@blockstack/ui/-/ui-1.6.3.tgz#3c56f577d88289f4321d63915f65a1861777c00d"
@@ -6666,6 +6638,11 @@ cookie@0.4.0:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
cookie@^0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
copy-concurrently@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
@@ -13327,6 +13304,14 @@ node-sass-tilde-importer@^1.0.2:
dependencies:
find-parent-dir "^0.3.0"
nookies@^2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/nookies/-/nookies-2.3.2.tgz#5a6111dc8f7247d3b532f35a073fe4e408c8f027"
integrity sha512-iV0aRpJhpmf3Ri7q0tulK5epR8sYvYuifphVwSMlm/SIt0gVII+R6lSDYNgLJLFMCK+9gvJC6H87vSCA4CtKcA==
dependencies:
cookie "^0.4.0"
set-cookie-parser "^2.4.3"
noop-logger@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2"
@@ -14010,7 +13995,7 @@ parse-path@^4.0.0:
is-ssh "^1.3.0"
protocols "^1.4.0"
"parse-srcset@github:ikatyang/parse-srcset#54eb9c1cb21db5c62b4d0e275d7249516df6f0ee":
parse-srcset@ikatyang/parse-srcset#54eb9c1cb21db5c62b4d0e275d7249516df6f0ee:
version "1.0.2"
resolved "https://codeload.github.com/ikatyang/parse-srcset/tar.gz/54eb9c1cb21db5c62b4d0e275d7249516df6f0ee"
@@ -16534,6 +16519,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
set-cookie-parser@^2.4.3:
version "2.4.6"
resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.4.6.tgz#43bdea028b9e6f176474ee5298e758b4a44799c3"
integrity sha512-mNCnTUF0OYPwYzSHbdRdCfNNHqrne+HS5tS5xNb6yJbdP9wInV0q5xPLE0EyfV/Q3tImo3y/OXpD8Jn0Jtnjrg==
set-value@^2.0.0, set-value@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
@@ -17045,6 +17035,11 @@ stealthy-require@^1.1.1:
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
store@^2.0.12:
version "2.0.12"
resolved "https://registry.yarnpkg.com/store/-/store-2.0.12.tgz#8c534e2a0b831f72b75fc5f1119857c44ef5d593"
integrity sha1-jFNOKguDH3K3X8XxEZhXxE711ZM=
stream-browserify@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"
@@ -18352,6 +18347,11 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typeface-inter@^3.12.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/typeface-inter/-/typeface-inter-3.12.0.tgz#3cd7cdae730daa73cc01e88fdbe27601dc7c4fd7"
integrity sha512-3vKBTKCl6S0OJhMKwmyAOodsfTD3Gxsrj5ReavpQmdZ385ecUr3swfNxiBZ4u594mNfojlqL/zUebavV3A1yCA==
typeforce@^1.11.3, typeforce@^1.11.5:
version "1.18.0"
resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc"