src/tag/styled-components.ts (470 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 tint from 'polished/lib/color/tint.js'; import shade from 'polished/lib/color/shade.js'; import { styled, type Theme } from '../styles'; import { KIND, HIERARCHY, SIZE } from './constants'; import type { SharedPropsArg } from './types'; import * as DeprecatedStyles from './deprecated-styles'; export function customOnRamp(color: string, unit?: string) { // This is a temporary fix to prevent the tag from crashing when the color is not defined if (!color && !(unit === '0' || unit === '1000')) { return undefined; } switch (unit) { case '0': return 'white'; case '50': return tint(0.8, color); case '100': return tint(0.6, color); case '200': return tint(0.4, color); case '300': return tint(0.2, color); case '400': return color; case '500': return shade(0.2, color); case '600': return shade(0.4, color); case '700': return shade(0.6, color); case '800': return shade(0.8, color); case '1000': return 'black'; default: return color; } } const COLOR_STATE = { disabled: 'disabled', primary: 'primary', secondary: 'secondary', } as const; // Probably best to bake this into the theme once we hit our next major. // @ts-ignore const pick = (theme, light, dark) => (theme.name && theme.name.includes('dark') ? dark : light); const blueColorStates = { [COLOR_STATE.disabled]: (theme) => ({ color: theme.colors.tagBlueContentStateDisabled, backgroundColor: theme.colors.tagBlueBackgroundStateDisabled, borderColor: null, }), [COLOR_STATE.primary]: (theme) => ({ color: theme.colors.tagBlueContentPrimary, backgroundColor: theme.colors.tagBlueBackgroundPrimary, borderColor: theme.colors.tagBlueBorderPrimaryUnselected, }), [COLOR_STATE.secondary]: (theme) => ({ color: theme.colors.tagBlueContentSecondary, backgroundColor: theme.colors.tagBlueBackgroundSecondary, borderColor: theme.colors.tagBlueBorderSecondaryUnselected, }), }; const greenColorStates = { [COLOR_STATE.disabled]: (theme) => ({ color: theme.colors.tagGreenContentStateDisabled, backgroundColor: theme.colors.tagGreenBackgroundStateDisabled, borderColor: null, }), [COLOR_STATE.primary]: (theme) => ({ color: theme.colors.tagGreenContentPrimary, backgroundColor: theme.colors.tagGreenBackgroundPrimary, borderColor: theme.colors.tagGreenBorderPrimaryUnselected, }), [COLOR_STATE.secondary]: (theme) => ({ color: theme.colors.tagGreenContentSecondary, backgroundColor: theme.colors.tagGreenBackgroundSecondary, borderColor: theme.colors.tagGreenBorderSecondaryUnselected, }), }; const yellowColorStates = { [COLOR_STATE.disabled]: (theme) => ({ color: theme.colors.tagYellowContentStateDisabled, backgroundColor: theme.colors.tagYellowBackgroundStateDisabled, borderColor: null, }), [COLOR_STATE.primary]: (theme) => ({ color: theme.colors.tagYellowContentPrimary, backgroundColor: theme.colors.tagYellowBackgroundPrimary, borderColor: theme.colors.tagYellowBorderPrimaryUnselected, }), [COLOR_STATE.secondary]: (theme) => ({ color: theme.colors.tagYellowContentSecondary, backgroundColor: theme.colors.tagYellowBackgroundSecondary, borderColor: theme.colors.tagYellowBorderSecondaryUnselected, }), }; const redColorStates = { [COLOR_STATE.disabled]: (theme) => ({ color: theme.colors.tagRedContentStateDisabled, backgroundColor: theme.colors.tagRedBackgroundStateDisabled, borderColor: null, }), [COLOR_STATE.primary]: (theme) => ({ color: theme.colors.tagRedContentPrimary, backgroundColor: theme.colors.tagRedBackgroundPrimary, borderColor: theme.colors.tagRedBorderPrimaryUnselected, }), [COLOR_STATE.secondary]: (theme) => ({ color: theme.colors.tagRedContentSecondary, backgroundColor: theme.colors.tagRedBackgroundSecondary, borderColor: theme.colors.tagRedBorderSecondaryUnselected, }), }; const limeColorStates = { [COLOR_STATE.disabled]: (theme) => ({ color: theme.colors.tagLimeContentStateDisabled, backgroundColor: theme.colors.tagLimeBackgroundStateDisabled, borderColor: null, }), [COLOR_STATE.primary]: (theme) => ({ color: theme.colors.tagLimeContentPrimary, backgroundColor: theme.colors.tagLimeBackgroundPrimary, borderColor: theme.colors.tagLimeBorderPrimaryUnselected, }), [COLOR_STATE.secondary]: (theme) => ({ color: theme.colors.tagLimeContentSecondary, backgroundColor: theme.colors.tagLimeBackgroundSecondary, borderColor: theme.colors.tagLimeBorderSecondaryUnselected, }), }; const tealColorStates = { [COLOR_STATE.disabled]: (theme) => ({ color: theme.colors.tagTealContentStateDisabled, backgroundColor: theme.colors.tagTealBackgroundStateDisabled, borderColor: null, }), [COLOR_STATE.primary]: (theme) => ({ color: theme.colors.tagTealContentPrimary, backgroundColor: theme.colors.tagTealBackgroundPrimary, borderColor: theme.colors.tagTealBorderPrimaryUnselected, }), [COLOR_STATE.secondary]: (theme) => ({ color: theme.colors.tagTealContentSecondary, backgroundColor: theme.colors.tagTealBackgroundSecondary, borderColor: theme.colors.tagTealBorderSecondaryUnselected, }), }; const orangeColorStates = { [COLOR_STATE.disabled]: (theme) => ({ color: theme.colors.tagOrangeContentStateDisabled, backgroundColor: theme.colors.tagOrangeBackgroundStateDisabled, borderColor: null, }), [COLOR_STATE.primary]: (theme) => ({ color: theme.colors.tagOrangeContentPrimary, backgroundColor: theme.colors.tagOrangeBackgroundPrimary, borderColor: theme.colors.tagOrangeBorderPrimaryUnselected, }), [COLOR_STATE.secondary]: (theme) => ({ color: theme.colors.tagOrangeContentSecondary, backgroundColor: theme.colors.tagOrangeBackgroundSecondary, borderColor: theme.colors.tagOrangeBorderSecondaryUnselected, }), }; const purpleColorStates = { [COLOR_STATE.disabled]: (theme) => ({ color: theme.colors.tagPurpleContentStateDisabled, backgroundColor: theme.colors.tagPurpleBackgroundStateDisabled, borderColor: null, }), [COLOR_STATE.primary]: (theme) => ({ color: theme.colors.tagPurpleContentPrimary, backgroundColor: theme.colors.tagPurpleBackgroundPrimary, borderColor: theme.colors.tagPurpleBorderPrimaryUnselected, }), [COLOR_STATE.secondary]: (theme) => ({ color: theme.colors.tagPurpleContentSecondary, backgroundColor: theme.colors.tagPurpleBackgroundSecondary, borderColor: theme.colors.tagPurpleBorderSecondaryUnselected, }), }; const magentaColorStates = { [COLOR_STATE.disabled]: (theme) => ({ color: theme.colors.tagMagentaContentStateDisabled, backgroundColor: theme.colors.tagMagentaBackgroundStateDisabled, borderColor: null, }), [COLOR_STATE.primary]: (theme) => ({ color: theme.colors.tagMagentaContentPrimary, backgroundColor: theme.colors.tagMagentaBackgroundPrimary, borderColor: theme.colors.tagMagentaBorderPrimaryUnselected, }), [COLOR_STATE.secondary]: (theme) => ({ color: theme.colors.tagMagentaContentSecondary, backgroundColor: theme.colors.tagMagentaBackgroundSecondary, borderColor: theme.colors.tagMagentaBorderSecondaryUnselected, }), }; const grayColorStates = { [COLOR_STATE.disabled]: (theme) => ({ color: theme.colors.tagGrayContentStateDisabled, backgroundColor: theme.colors.tagGrayBackgroundStateDisabled, borderColor: null, }), [COLOR_STATE.primary]: (theme) => ({ color: theme.colors.tagGrayContentPrimary, backgroundColor: theme.colors.tagGrayBackgroundPrimary, borderColor: theme.colors.tagGrayBorderPrimaryUnselected, }), [COLOR_STATE.secondary]: (theme) => ({ color: theme.colors.tagGrayContentSecondary, backgroundColor: theme.colors.tagGrayBackgroundSecondary, borderColor: theme.colors.tagGrayBorderSecondaryUnselected, }), }; const customColorStates = { // @ts-ignore [COLOR_STATE.disabled]: (theme, color) => ({ color: customOnRamp(color, theme.colors.tagFontDisabledRampUnit), backgroundColor: null, borderColor: customOnRamp(color, theme.colors.tagSolidDisabledRampUnit), }), // @ts-ignore [COLOR_STATE.primary]: (theme, color) => ({ color: customOnRamp(color, theme.colors.tagSolidFontRampUnit), backgroundColor: customOnRamp(color, theme.colors.tagSolidRampUnit), borderColor: null, }), // @ts-ignore [COLOR_STATE.secondary]: (theme, color) => ({ color: customOnRamp(color, theme.colors.tagOutlinedFontRampUnit), backgroundColor: null, borderColor: customOnRamp(color, theme.colors.tagOutlinedRampUnit), }), }; const colorMap = { [KIND.neutral]: DeprecatedStyles.deprecatedNeutralColorStates, [KIND.primary]: DeprecatedStyles.deprecatedPrimaryColorStates, [KIND.accent]: DeprecatedStyles.deprecatedBlueColorStates, [KIND.positive]: DeprecatedStyles.deprecatedGreenColorStates, [KIND.warning]: DeprecatedStyles.deprecatedYellowColorStates, [KIND.negative]: DeprecatedStyles.deprecatedRedColorStates, [KIND.black]: DeprecatedStyles.deprecatedPrimaryColorStates, [KIND.blue]: blueColorStates, [KIND.green]: greenColorStates, [KIND.red]: redColorStates, [KIND.yellow]: yellowColorStates, [KIND.orange]: orangeColorStates, [KIND.purple]: purpleColorStates, [KIND.brown]: DeprecatedStyles.deprecatedBrownColorStates, [KIND.lime]: limeColorStates, [KIND.teal]: tealColorStates, [KIND.magenta]: magentaColorStates, [KIND.gray]: grayColorStates, [KIND.custom]: customColorStates, }; // @ts-ignore const getColorStateFromProps = (props) => { if (props.$disabled) return COLOR_STATE.disabled; if (props.$hierarchy === HIERARCHY.primary) return COLOR_STATE.primary; return COLOR_STATE.secondary; }; export const Action = styled<'span', SharedPropsArg>( 'span', ( props: SharedPropsArg & { $theme: Theme; } ) => { const { $theme, $disabled, $size = SIZE.small } = props; const bottomRadiusDir: string = $theme.direction === 'rtl' ? 'borderBottomLeftRadius' : 'borderBottomRightRadius'; const topRadiusDir: string = $theme.direction === 'rtl' ? 'borderTopLeftRadius' : 'borderTopRightRadius'; const marginDir: string = $theme.direction === 'rtl' ? 'marginRight' : 'marginLeft'; return { alignItems: 'center', [bottomRadiusDir]: $theme.borders.useRoundedCorners ? $theme.borders.radius400 : 0, [topRadiusDir]: $theme.borders.useRoundedCorners ? $theme.borders.radius400 : 0, cursor: $disabled ? 'not-allowed' : 'pointer', display: 'flex', [marginDir]: { [SIZE.xSmall]: $theme.sizing.scale100, [SIZE.small]: $theme.sizing.scale300, [SIZE.medium]: $theme.sizing.scale300, [SIZE.large]: $theme.sizing.scale500, }[$size], outline: 'none', transitionProperty: 'all', transitionDuration: 'background-color', transitionTimingFunction: $theme.animation.easeOutCurve, }; } ); Action.displayName = 'Action'; export const StartEnhancerContainer = styled<'div', SharedPropsArg>( 'div', ({ $theme, $size = SIZE.small, }: SharedPropsArg & { $theme: Theme; }) => { const paddingMagnitude = { [SIZE.xSmall]: $theme.sizing.scale100, [SIZE.small]: $theme.sizing.scale300, [SIZE.medium]: $theme.sizing.scale300, [SIZE.large]: $theme.sizing.scale500, }[$size]; const paddingDir: string = $theme.direction === 'rtl' ? 'paddingLeft' : 'paddingRight'; return { alignItems: 'center', display: 'flex', [paddingDir]: paddingMagnitude, }; } ); StartEnhancerContainer.displayName = 'StartEnhancerContainer'; export const Text = styled<'span', SharedPropsArg>( 'span', ( props: SharedPropsArg & { $theme: Theme; } ) => { const { $theme, $contentMaxWidth } = props; return { overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', maxWidth: $contentMaxWidth === null ? 'auto' : $contentMaxWidth || props.$theme.sizing.scale3200, order: $theme.direction === 'rtl' ? 1 : 0, }; } ); Text.displayName = 'Text'; export const Root = styled<'span', SharedPropsArg>( 'span', ( props: SharedPropsArg & { $theme: Theme; } ) => { const { $theme, $kind = KIND.primary, $clickable, $hierarchy, $disabled, $closeable, $isFocusVisible, $color, $size = SIZE.small, } = props; const borderRadius = $size === SIZE.small || $size === SIZE.xSmall ? $theme.borders.radius200 : $theme.borders.radius300; const paddingMagnitude = { [SIZE.xSmall]: $theme.sizing.scale100, [SIZE.small]: $theme.sizing.scale200, [SIZE.medium]: $theme.sizing.scale300, [SIZE.large]: $theme.sizing.scale400, }[$size]; const paddingLongitude = { [SIZE.xSmall]: $theme.sizing.scale0, [SIZE.small]: $theme.sizing.scale100, [SIZE.medium]: $theme.sizing.scale100, [SIZE.large]: $theme.sizing.scale300, }[$size]; const isActionable = !$disabled && ($clickable || $closeable); const borderWidthValue = { [SIZE.xSmall]: '1px', [SIZE.small]: '1px', [SIZE.medium]: '1px', [SIZE.large]: '2px', }[$size]; const { color, backgroundColor, borderColor } = colorMap[$kind][getColorStateFromProps(props)]( $theme, $color ); // previously, only secondary and disabled custom tags had borders // now, it extends to actionable tags as well const showBorder = ($kind === KIND.custom && ($hierarchy === HIERARCHY.secondary || $disabled)) || ($kind !== KIND.custom && isActionable && !!borderColor); const borderWidth = showBorder ? borderWidthValue : 0; return { ...{ [SIZE.xSmall]: $theme.typography.LabelXSmall, [SIZE.small]: $theme.typography.LabelSmall, [SIZE.medium]: $theme.typography.LabelMedium, [SIZE.large]: $theme.typography.LabelLarge, }[$size], alignItems: 'center', color, backgroundColor, borderLeftColor: borderColor, borderRightColor: borderColor, borderTopColor: borderColor, borderBottomColor: borderColor, borderLeftStyle: 'solid', borderRightStyle: 'solid', borderTopStyle: 'solid', borderBottomStyle: 'solid', borderLeftWidth: borderWidth, borderRightWidth: borderWidth, borderTopWidth: borderWidth, borderBottomWidth: borderWidth, borderTopLeftRadius: borderRadius, borderTopRightRadius: borderRadius, borderBottomRightRadius: borderRadius, borderBottomLeftRadius: borderRadius, boxSizing: 'border-box', cursor: $disabled ? 'not-allowed' : $clickable ? 'pointer' : 'default', display: 'inline-flex', height: { [SIZE.xSmall]: '20px', [SIZE.small]: '24px', [SIZE.medium]: '32px', [SIZE.large]: '40px', }[$size], justifyContent: 'space-between', marginTop: '5px', marginBottom: '5px', marginLeft: '5px', marginRight: '5px', paddingTop: paddingLongitude, paddingBottom: paddingLongitude, paddingLeft: paddingMagnitude, paddingRight: paddingMagnitude, outline: 'none', ...(isActionable ? { // adding overlay component to cover both border and content area for hovered and pressed state position: 'relative', '::before': { content: "''", position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, borderTopLeftRadius: 'inherit', borderTopRightRadius: 'inherit', borderBottomRightRadius: 'inherit', borderBottomLeftRadius: 'inherit', backgroundColor: 'transparent', pointerEvents: 'none', }, // end of overlay // Applies only on devices that support hovering, like desktop computers. '@media (hover: hover)': { ':hover::before': { backgroundColor: $theme.colors.hoverOverlayAlpha, // this box shadow is used to extend the overlay to the border boxShadow: `0 0 0 ${borderWidth} ${$theme.colors.hoverOverlayAlpha}`, }, }, ':active::before': { backgroundColor: $theme.colors.pressedOverlayAlpha, // this box shadow is used to extend the overlay to the border boxShadow: `0 0 0 ${borderWidth} ${$theme.colors.pressedOverlayAlpha}`, }, } : {}), ':focus': $disabled || (!$clickable && !$closeable) ? {} : { boxShadow: $isFocusVisible ? `0 0 0 3px ${ $kind === KIND.accent ? $theme.colors.backgroundInversePrimary : $theme.colors.backgroundAccent }` : 'none', }, }; } ); Root.displayName = 'Root';