Skip to content

Commit

Permalink
refactor(suite): Refactor UI variant, sizes and alignments (trezor#11280
Browse files Browse the repository at this point in the history
)
  • Loading branch information
jvaclavik authored Feb 27, 2024
1 parent 9295896 commit a20b6c3
Show file tree
Hide file tree
Showing 26 changed files with 394 additions and 298 deletions.
170 changes: 80 additions & 90 deletions packages/components/src/components/Badge/Badge.tsx
Original file line number Diff line number Diff line change
@@ -1,85 +1,79 @@
import React from 'react';
import styled, { css, DefaultTheme } from 'styled-components';
import styled, { css, DefaultTheme, useTheme } from 'styled-components';
import { IconName } from '@suite-common/icons';
import { Icon } from '@suite-common/icons/src/webComponents';
import { borders, Color, spacings, spacingsPx, typography } from '@trezor/theme';
import { borders, Color, CSSColor, spacings, spacingsPx, typography } from '@trezor/theme';
import { focusStyleTransition, getFocusShadowStyle } from '../../utils/utils';
import type { UISize, UIVariant } from '../../config/types';

const getBackgroundColor = (variant: BadgeVariant | undefined, theme: DefaultTheme) => {
switch (variant) {
case 'green':
return theme.backgroundPrimarySubtleOnElevation0;
case 'red':
return theme.backgroundAlertRedSubtleOnElevation0;
case 'bold':
return theme.backgroundNeutralBold;
case 'neutral':
default:
return theme.backgroundNeutralSubtleOnElevation0;
}
type BadgeSize = Extract<UISize, 'tiny' | 'small' | 'medium'>;
type BadgeVariant = Extract<UIVariant, 'primary' | 'tertiary' | 'destructive'>;

export interface BadgeProps {
size?: BadgeSize;
variant?: BadgeVariant;
isDisabled?: boolean;
icon?: IconName;
hasAlert?: boolean;
className?: string;
children?: React.ReactNode;
}

type MapArgs = {
variant: BadgeVariant;
theme: DefaultTheme;
};

const getTextColor = (
variant: BadgeVariant | undefined,
isDisabled: boolean | undefined,
theme: DefaultTheme,
) => {
if (isDisabled) {
return theme.textDisabled;
}
type BadgeContainerProps = Required<Pick<BadgeProps, 'size' | 'variant' | 'hasAlert'>>;

switch (variant) {
case 'green':
return theme.textPrimaryDefault;
case 'red':
return theme.textAlertRed;
case 'bold':
return theme.textOnPrimary;
case 'neutral':
default:
return theme.textSubdued;
}
const mapVariantToBackgroundColor = ({ variant, theme }: MapArgs): CSSColor => {
const colorMap: Record<BadgeVariant, Color> = {
primary: 'backgroundPrimarySubtleOnElevation0',
tertiary: 'backgroundNeutralSubtleOnElevation0',
destructive: 'backgroundAlertRedSubtleOnElevation0',
};

return theme[colorMap[variant]];
};

const getIconColor = (variant: BadgeVariant, isDisabled: boolean | undefined): Color => {
if (isDisabled) {
return 'iconDisabled';
}
const mapVariantToTextColor = ({ variant, theme }: MapArgs): CSSColor => {
const colorMap: Record<BadgeVariant, Color> = {
primary: 'textPrimaryDefault',
tertiary: 'textSubdued',
destructive: 'textAlertRed',
};

switch (variant) {
case 'green':
return 'iconPrimaryDefault';
case 'red':
return 'iconAlertRed';
case 'bold':
return 'iconOnPrimary';
case 'neutral':
default:
return 'iconSubdued';
}
return theme[colorMap[variant]];
};

const getPadding = (size: BadgeSize) => {
switch (size) {
case 'tiny':
return `0 ${spacings.xs - spacings.xxxs}px`;
case 'small':
return `0 ${spacingsPx.xs}`;
default:
return `${spacingsPx.xxxs} ${spacingsPx.xs}`;
}
const mapVariantToIconColor = ({ variant, theme }: MapArgs): CSSColor => {
const colorMap: Record<BadgeVariant, Color> = {
primary: 'iconPrimaryDefault',
tertiary: 'iconSubdued',
destructive: 'iconAlertRed',
};

return theme[colorMap[variant]];
};

type BadgeContainerProps = Required<Pick<BadgeProps, 'size' | 'variant' | 'hasAlert'>>;
const mapVariantToPadding = ({ size }: { size: BadgeSize }): string => {
const colorMap: Record<BadgeSize, string> = {
tiny: `0 ${spacings.xs - spacings.xxxs}px`,
small: `0 ${spacingsPx.xs}`,
medium: `${spacingsPx.xxxs} ${spacingsPx.xs}`,
};

return colorMap[size];
};

const Container = styled.button<BadgeContainerProps>`
display: flex;
align-items: center;
gap: ${spacingsPx.xxs};
padding: ${({ size }) => getPadding(size)};
padding: ${mapVariantToPadding};
border-radius: ${borders.radii.full};
border: 1px solid transparent;
background: ${({ variant, theme }) => getBackgroundColor(variant, theme)};
background: ${mapVariantToBackgroundColor};
transition: ${focusStyleTransition};
:disabled {
Expand All @@ -99,43 +93,39 @@ const Container = styled.button<BadgeContainerProps>`
`;

const Content = styled.span<Required<Pick<BadgeProps, 'size' | 'variant' | 'isDisabled'>>>`
color: ${({ variant, isDisabled, theme }) => getTextColor(variant, isDisabled, theme)};
color: ${({ isDisabled, theme }) => (isDisabled ? theme.textDisabled : mapVariantToTextColor)};
${({ size }) => (size === 'medium' ? typography.hint : typography.label)};
`;

type BadgeSize = 'tiny' | 'small' | 'medium';
type BadgeVariant = 'neutral' | 'green' | 'red' | 'bold';

export interface BadgeProps {
size?: BadgeSize;
variant?: BadgeVariant;
isDisabled?: boolean;
icon?: IconName;
hasAlert?: boolean;
className?: string;
children?: React.ReactNode;
}

export const Badge = ({
size = 'medium',
variant = 'neutral',
variant = 'tertiary',
isDisabled,
icon,
hasAlert,
className,
children,
}: BadgeProps) => (
<Container
size={size}
variant={variant}
disabled={!!isDisabled}
hasAlert={!!hasAlert}
className={className}
>
{icon && <Icon name={icon} color={getIconColor(variant, isDisabled)} />}

<Content size={size} variant={variant} isDisabled={!!isDisabled}>
{children}
</Content>
</Container>
);
}: BadgeProps) => {
const theme = useTheme();

return (
<Container
size={size}
variant={variant}
disabled={!!isDisabled}
hasAlert={!!hasAlert}
className={className}
>
{icon && (
<Icon
name={icon}
color={isDisabled ? 'iconDisabled' : mapVariantToIconColor({ variant, theme })}
/>
)}

<Content size={size} variant={variant} isDisabled={!!isDisabled}>
{children}
</Content>
</Container>
);
};
24 changes: 12 additions & 12 deletions packages/components/src/components/Box/Box.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ const Wrapper = styled.div`
display: flex;
flex: 1;
flex-direction: column;
`;

const StyledRow = styled(BoxComponent)`
margin: 10px 0;
gap: 20px;
`;

export default {
Expand All @@ -25,18 +22,21 @@ export const Box: StoryObj = {
render: () => (
<>
<Wrapper>
<StyledRow>
<BoxComponent>
<Text>No state</Text>
</StyledRow>
<StyledRow>
</BoxComponent>
<BoxComponent variant="primary">
<Text>Success</Text>
</StyledRow>
<StyledRow state="error">
</BoxComponent>
<BoxComponent variant="destructive">
<Text>Error</Text>
</StyledRow>
<StyledRow state="warning">
</BoxComponent>
<BoxComponent variant="warning">
<Text>Warning</Text>
</StyledRow>
</BoxComponent>
<BoxComponent variant="info">
<Text>Info</Text>
</BoxComponent>
</Wrapper>
</>
),
Expand Down
87 changes: 48 additions & 39 deletions packages/components/src/components/Box/Box.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,61 @@
import { ReactNode, HTMLAttributes } from 'react';
import styled, { DefaultTheme, css } from 'styled-components';
import { borders, spacingsPx } from '@trezor/theme';

// TODO: since the fate of Box is unclear, decouple it from the input state
// later those "state" styles should be unified across components
export const getBoxStateBorderColor = (
state: BoxProps['state'] | undefined,
theme: DefaultTheme,
) => {
switch (state) {
case 'warning':
return theme.textAlertYellow;
case 'error':
return theme.borderAlertRed;
case 'success':
return theme.borderSecondary;
default:
return 'transparent';
}
import styled, { DefaultTheme } from 'styled-components';
import {
CSSColor,
Color,
Elevation,
borders,
mapElevationToBackground,
mapElevationToBorder,
spacingsPx,
} from '@trezor/theme';
import { useElevation } from '../ElevationContext/ElevationContext';
import { ElevationContext } from '../ElevationContext/ElevationContext';
import { UIVariant } from '../../config/types';

type BoxVariant = Extract<UIVariant, 'primary' | 'warning' | 'destructive' | 'info'>;

type MapArgs = {
variant: BoxVariant;
theme: DefaultTheme;
};

const mapVariantToBackgroundColor = ({ variant, theme }: MapArgs): CSSColor => {
const colorMap: Record<BoxVariant, Color> = {
primary: 'borderSecondary',
info: 'textAlertBlue',
warning: 'textAlertYellow',
destructive: 'borderAlertRed',
};

return theme[colorMap[variant]];
};

export interface BoxProps extends HTMLAttributes<HTMLDivElement> {
state?: 'warning' | 'error' | 'success';
variant?: BoxVariant;
children: ReactNode;
}

const Wrapper = styled.div<{ state: BoxProps['state'] }>`
const Wrapper = styled.div<{ variant?: BoxVariant; elevation: Elevation }>`
display: flex;
flex: 1;
border-radius: ${borders.radii.sm};
padding: ${spacingsPx.md};
border: solid 1px ${({ theme }) => theme.borderOnElevation0};
${({ state, theme }) =>
state &&
css`
border-left: 6px solid ${getBoxStateBorderColor(state, theme)};
`}
${({ state }) =>
!state &&
css`
padding-left: ${spacingsPx.lg};
`}
background: ${mapElevationToBackground};
border: solid 1px ${mapElevationToBorder};
${({ variant, theme }) =>
variant === undefined
? `padding-left: ${spacingsPx.lg};`
: `border-left: 6px solid ${mapVariantToBackgroundColor({ variant, theme })};`}
`;

const Box = ({ state, children, ...rest }: BoxProps) => (
<Wrapper state={state} {...rest}>
{children}
</Wrapper>
);
export const Box = ({ variant, children, ...rest }: BoxProps) => {
const { elevation } = useElevation();

export { Box };
return (
<Wrapper variant={variant} elevation={elevation} {...rest}>
<ElevationContext baseElevation={elevation}>{children}</ElevationContext>
</Wrapper>
);
};
Loading

0 comments on commit a20b6c3

Please sign in to comment.