src/badge/styled-components.ts (314 lines of code) (raw):
/*
Copyright (c) Uber Technologies, Inc.
This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
import { styled } from '../styles';
import type { Placement, Color, Shape, Role, Hierarchy } from './types';
import { COLOR, SHAPE, ROLE, PLACEMENT, HIERARCHY } from './constants';
// @ts-ignore
function getColorStyles({ $theme, $hierarchy, $color }): {
color: string;
backgroundColor: string;
} {
const COLOR_STYLES = {
[HIERARCHY.primary]: {
[COLOR.accent]: {
color: $theme.colors.contentOnColor,
backgroundColor: $theme.colors.backgroundAccent,
},
[COLOR.primary]: {
color: $theme.colors.contentInversePrimary,
backgroundColor: $theme.colors.backgroundInversePrimary,
},
[COLOR.positive]: {
color: $theme.colors.contentOnColor,
backgroundColor: $theme.colors.backgroundPositive,
},
[COLOR.negative]: {
color: $theme.colors.contentOnColor,
backgroundColor: $theme.colors.backgroundNegative,
},
[COLOR.warning]: {
color: $theme.colors.contentOnColorInverse,
backgroundColor: $theme.colors.backgroundWarning,
},
},
[HIERARCHY.secondary]: {
[COLOR.accent]: {
color: $theme.colors.contentAccent,
backgroundColor: $theme.colors.backgroundAccentLight,
},
[COLOR.primary]: {
color: $theme.colors.contentPrimary,
backgroundColor: $theme.colors.backgroundSecondary,
},
[COLOR.positive]: {
color: $theme.colors.contentPositive,
backgroundColor: $theme.colors.backgroundPositiveLight,
},
[COLOR.negative]: {
color: $theme.colors.contentNegative,
backgroundColor: $theme.colors.backgroundNegativeLight,
},
[COLOR.warning]: {
color: $theme.colors.contentWarning,
backgroundColor: $theme.colors.backgroundWarningLight,
},
},
};
// @ts-ignore
return COLOR_STYLES[$hierarchy][$color];
}
const DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT = {
top: '-10px',
right: '-10px',
};
const DEFAULT_HINT_DOT_PLACEMENT = {
top: '-4px',
right: '-4px',
};
const POSITION_STYLES = Object.freeze({
[ROLE.badge]: {
[PLACEMENT.topEdge]: {
top: '-8px',
left: '50%',
transform: 'translateX(-50%)',
},
[PLACEMENT.bottomEdge]: {
bottom: '-8px',
left: '50%',
transform: 'translateX(-50%)',
},
[PLACEMENT.topLeft]: {
top: '16px',
left: '16px',
},
[PLACEMENT.topRight]: {
top: '16px',
right: '16px',
},
[PLACEMENT.bottomRight]: {
bottom: '16px',
right: '16px',
},
[PLACEMENT.bottomLeft]: {
bottom: '16px',
left: '16px',
},
[PLACEMENT.topLeftEdge]: {
top: '-8px',
left: '16px',
},
[PLACEMENT.topRightEdge]: {
top: '-8px',
right: '16px',
},
[PLACEMENT.bottomRightEdge]: {
bottom: '-8px',
right: '16px',
},
[PLACEMENT.bottomLeftEdge]: {
bottom: '-8px',
left: '16px',
},
[PLACEMENT.leftTopEdge]: {
top: '16px',
left: '-8px',
},
[PLACEMENT.rightTopEdge]: {
top: '16px',
right: '-8px',
},
[PLACEMENT.rightBottomEdge]: {
bottom: '16px',
right: '-8px',
},
[PLACEMENT.leftBottomEdge]: {
bottom: '16px',
left: '-8px',
},
},
[ROLE.notificationCircle]: {
[PLACEMENT.topLeft]: {
top: '-10px',
left: '-10px',
},
[PLACEMENT.topRight]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT,
// NotificationCircle can only be placed topLeft or topRight, other values fall back to topRight
[PLACEMENT.topEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT,
[PLACEMENT.bottomEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT,
[PLACEMENT.bottomRight]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT,
[PLACEMENT.bottomLeft]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT,
[PLACEMENT.topLeftEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT,
[PLACEMENT.topRightEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT,
[PLACEMENT.bottomRightEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT,
[PLACEMENT.bottomLeftEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT,
[PLACEMENT.leftTopEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT,
[PLACEMENT.rightTopEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT,
[PLACEMENT.rightBottomEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT,
[PLACEMENT.leftBottomEdge]: DEFAULT_NOTIFICATION_CIRCLE_PLACEMENT,
},
[ROLE.hintDot]: {
[PLACEMENT.topLeft]: {
top: '-4px',
left: '-4px',
},
[PLACEMENT.topRight]: DEFAULT_HINT_DOT_PLACEMENT,
// HintDot can only be placed topLeft or topRight, other values fall back to topRight
[PLACEMENT.topEdge]: DEFAULT_HINT_DOT_PLACEMENT,
[PLACEMENT.bottomEdge]: DEFAULT_HINT_DOT_PLACEMENT,
[PLACEMENT.bottomRight]: DEFAULT_HINT_DOT_PLACEMENT,
[PLACEMENT.bottomLeft]: DEFAULT_HINT_DOT_PLACEMENT,
[PLACEMENT.topLeftEdge]: DEFAULT_HINT_DOT_PLACEMENT,
[PLACEMENT.topRightEdge]: DEFAULT_HINT_DOT_PLACEMENT,
[PLACEMENT.bottomRightEdge]: DEFAULT_HINT_DOT_PLACEMENT,
[PLACEMENT.bottomLeftEdge]: DEFAULT_HINT_DOT_PLACEMENT,
[PLACEMENT.leftTopEdge]: DEFAULT_HINT_DOT_PLACEMENT,
[PLACEMENT.rightTopEdge]: DEFAULT_HINT_DOT_PLACEMENT,
[PLACEMENT.rightBottomEdge]: DEFAULT_HINT_DOT_PLACEMENT,
[PLACEMENT.leftBottomEdge]: DEFAULT_HINT_DOT_PLACEMENT,
},
});
export const StyledRoot = styled<'div', {}>('div', () => {
return {
position: 'relative',
display: 'inline-block',
lineHeight: 'initial',
};
});
StyledRoot.displayName = 'StyledRoot';
const TOP_PLACEMENTS: Placement[] = [
PLACEMENT.topLeft,
PLACEMENT.topRight,
PLACEMENT.topLeftEdge,
PLACEMENT.topEdge,
PLACEMENT.topRightEdge,
PLACEMENT.leftTopEdge,
PLACEMENT.rightTopEdge,
];
const BOTTOM_PLACEMENTS: Placement[] = [
PLACEMENT.bottomLeft,
PLACEMENT.bottomRight,
PLACEMENT.bottomLeftEdge,
PLACEMENT.bottomEdge,
PLACEMENT.bottomRightEdge,
PLACEMENT.leftBottomEdge,
PLACEMENT.rightBottomEdge,
];
const LEFT_PLACEMENTS: Placement[] = [
PLACEMENT.topLeft,
PLACEMENT.topLeftEdge,
PLACEMENT.topEdge,
PLACEMENT.bottomLeft,
PLACEMENT.bottomLeftEdge,
PLACEMENT.bottomEdge,
PLACEMENT.leftTopEdge,
PLACEMENT.leftBottomEdge,
];
const RIGHT_PLACEMENTS: Placement[] = [
PLACEMENT.topRight,
PLACEMENT.topRightEdge,
PLACEMENT.bottomRight,
PLACEMENT.bottomRightEdge,
PLACEMENT.rightTopEdge,
PLACEMENT.rightBottomEdge,
];
export const StyledPositioner = styled<
'div',
{
$role: Role;
$placement: Placement;
$horizontalOffset?: string | null;
$verticalOffset?: string | null;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
>('div', ({ $theme, $role, $placement, $horizontalOffset, $verticalOffset }) => {
let positionStyle = POSITION_STYLES[$role][$placement];
if ($verticalOffset) {
if (TOP_PLACEMENTS.includes($placement)) {
positionStyle = { ...positionStyle, top: $verticalOffset };
}
if (BOTTOM_PLACEMENTS.includes($placement)) {
positionStyle = { ...positionStyle, bottom: $verticalOffset };
}
}
if ($horizontalOffset) {
if (LEFT_PLACEMENTS.includes($placement)) {
positionStyle = { ...positionStyle, left: $horizontalOffset };
}
if (RIGHT_PLACEMENTS.includes($placement)) {
positionStyle = { ...positionStyle, right: $horizontalOffset };
}
}
return {
...positionStyle,
position: 'absolute',
lineHeight: 'initial',
};
});
StyledPositioner.displayName = 'StyledPositioner';
export const StyledBadge = styled<
'div',
{
$shape?: Shape;
$color?: Color;
$hierarchy?: Hierarchy;
$hidden?: boolean;
}
>(
'div',
({
$theme,
$shape = SHAPE.rectangle,
$color = COLOR.accent,
$hierarchy = HIERARCHY.primary,
$hidden,
}) => {
return {
visibility: $hidden ? 'hidden' : 'inherit',
boxSizing: 'border-box',
height: $theme.sizing.scale700,
borderRadius:
$shape === SHAPE.rectangle ? $theme.borders.radius200 : $theme.borders.radius500,
paddingRight: $shape === SHAPE.rectangle ? $theme.sizing.scale100 : $theme.sizing.scale300,
paddingLeft: $shape === SHAPE.rectangle ? $theme.sizing.scale100 : $theme.sizing.scale300,
display: 'inline-flex',
alignItems: 'center',
...getColorStyles({ $theme, $hierarchy, $color }),
...($hierarchy === HIERARCHY.primary
? $theme.typography.LabelSmall
: $theme.typography.ParagraphSmall),
};
}
);
StyledBadge.displayName = 'StyledBadge';
export const StyledNotificationCircle = styled<
'div',
{
$color?: Color;
$hidden?: boolean;
}
>('div', ({ $theme, $color = COLOR.accent, $hidden }) => {
return {
visibility: $hidden ? 'hidden' : 'inherit',
height: $theme.sizing.scale700,
width: $theme.sizing.scale700,
borderRadius: '20px',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
...getColorStyles({ $theme, $hierarchy: HIERARCHY.primary, $color }),
...$theme.typography.LabelXSmall,
};
});
StyledNotificationCircle.displayName = 'StyledNotificationCircle';
export const StyledHintDot = styled<
'div',
{
$color: Color;
$hidden?: boolean;
}
>('div', ({ $theme, $color = COLOR.accent, $hidden }) => {
return {
visibility: $hidden ? 'hidden' : 'inherit',
// @ts-ignore
backgroundColor: $theme.colors[$color],
boxSizing: 'content-box',
height: '8px',
width: '8px',
borderRadius: '50%',
border: `4px solid ${$theme.colors.backgroundPrimary}`,
...getColorStyles({ $theme, $hierarchy: HIERARCHY.primary, $color }),
};
});
StyledHintDot.displayName = 'StyledHintDot';