import React, { JSX } from 'react' import type { TextStyle, ViewProps, ViewStyle } from 'react-native' import { Pressable, StyleSheet, View } from 'react-native' import Svg, { Defs, LinearGradient, Path, Stop } from 'react-native-svg' import { useFontScale, useTheme } from '../../hooks' import { platformFontWeightMedium } from '../../utils' import { tokens } from '../../vendor/tapestry/tokens' import { Icon, IconString } from './icon' import { Text } from './text' import { useStatusColorAppearanceMap, type StatusAppearanceUnion } from './utils/status_colors' // ================================= // ====== Constants ================ // ================================= type VariantUnion = 'default' | 'meta' | 'metaSubtle' type VariantStyles = Record< VariantUnion, { backgroundColor: string paddingHorizontal: number paddingVertical: number borderWidth: number borderRadius: number textColor: string gap: number metaLabelPaddingLeft: number fontWeight: TextStyle['fontWeight'] } > type ProductLogoName = 'Groups' | 'Services' | 'groups' | 'services' type ProductIconName = 'Chat' | 'chat' type ProductLogoComponent = ({ logoSize }: { logoSize: number }) => JSX.Element const PRODUCT_LOGO_COMPONENT_MAP: Record = { Groups: GroupsLogo, groups: GroupsLogo, services: ServicesIcon, Services: ServicesIcon, } // ================================= // ====== Component ================ // ================================= export interface BadgeProps { /** * Renders the main text. */ label: string /** * Renders secondary meta text to the right of the label. */ metaLabel?: string /** * Changes the status color for the badge and icon. */ appearance?: StatusAppearanceUnion /** * Changes form of the logo to support a meta label and badge */ variant?: VariantUnion /** * Adds a product logo to the left of the text. */ productLogoName?: ProductLogoName | ProductIconName /** * Shows an icon of the user choice to the left of the text. */ iconName?: IconString /** * Shows an icon of the user choice to the right of the text. */ iconNameRight?: IconString /** * Styles badge wrapper. */ style?: ViewStyle /** * Specifies the maximum size a font can reach when allowFontScaling is enabled. */ maxFontSizeMultiplier?: number /** * Callback function when the badge is pressed. */ onPress?: () => void /** * Specifies the number of lines for the meta label before truncating. */ numberOfLinesMetaLabel?: number } export function Badge({ appearance = 'neutral', metaLabel, style, iconName, iconNameRight, label, productLogoName, variant = 'default', maxFontSizeMultiplier, numberOfLinesMetaLabel = 1, onPress, }: BadgeProps) { const styles = useStyles({ appearance, maxFontSizeMultiplier, variant }) const { showBadgeProductLogo } = useTheme() const isMetaSubtle = variant === 'metaSubtle' const hasMetaLabel = Boolean(metaLabel) const showLogo = showBadgeProductLogo && productLogoName const isChatLogo = productLogoName?.toLowerCase() === 'chat' const ProductLogoSvg = showLogo && PRODUCT_LOGO_COMPONENT_MAP[productLogoName as ProductLogoName] const badgeLabel = isMetaSubtle && hasMetaLabel ? `${label}:` : label return ( {showLogo && ProductLogoSvg && } {showLogo && isChatLogo && } {iconName && } {badgeLabel} {iconNameRight && } {hasMetaLabel && ( {metaLabel} )} ) } const BadgeWrapper = ({ children, onPress, accessibilityRole, ...restProps }: ViewProps & Pick) => { if (onPress) { return ( {children} ) } return {children} } // ================================= // ====== Styles =================== // ================================= const useStyles = ({ appearance = 'neutral', maxFontSizeMultiplier, variant = 'default', }: Partial) => { const { colors } = useTheme() const fontScale = useFontScale({ maxFontSizeMultiplier }) const statusColorMap = useStatusColorAppearanceMap() const badgeGap = 4 * fontScale const badgePaddingHorizontal = 8 const badgePaddingVertical = 4 const badgeFontSize = 12 const variantStylesMap: VariantStyles = { default: { gap: badgeGap, backgroundColor: statusColorMap[appearance].background, metaLabelPaddingLeft: badgePaddingHorizontal, paddingHorizontal: badgePaddingHorizontal, paddingVertical: badgePaddingVertical, borderWidth: tokens.borderSizeDefault * fontScale, borderRadius: tokens.borderRadiusMd * fontScale, textColor: statusColorMap[appearance].text, fontWeight: 'normal', }, meta: { gap: badgeGap, backgroundColor: statusColorMap[appearance].background, metaLabelPaddingLeft: badgePaddingHorizontal, paddingHorizontal: badgePaddingHorizontal, paddingVertical: badgePaddingVertical, borderWidth: tokens.borderSizeDefault * fontScale, borderRadius: tokens.borderRadiusMd * fontScale, textColor: statusColorMap[appearance].text, fontWeight: platformFontWeightMedium, }, metaSubtle: { gap: badgeGap, backgroundColor: 'transparent', metaLabelPaddingLeft: badgeGap, paddingHorizontal: 0, paddingVertical: 0, borderWidth: 0, borderRadius: 0, textColor: colors.textColorDefaultSecondary, fontWeight: platformFontWeightMedium, }, } return StyleSheet.create({ badgeWrapper: { flexDirection: 'row', justifyContent: 'center', alignSelf: 'flex-start', borderRadius: variantStylesMap[variant].borderRadius, borderWidth: variantStylesMap[variant].borderWidth, borderColor: statusColorMap[appearance].background, }, badge: { flexDirection: 'row', alignItems: 'center', gap: variantStylesMap[variant].gap, paddingVertical: variantStylesMap[variant].paddingVertical, paddingHorizontal: variantStylesMap[variant].paddingHorizontal, backgroundColor: variantStylesMap[variant].backgroundColor, borderRadius: variantStylesMap[variant].borderRadius - 2, }, icon: { color: statusColorMap[appearance].icon, fontSize: badgeFontSize, }, iconRight: { color: statusColorMap[appearance].icon, fontSize: badgeFontSize - 2, }, logo: { fontSize: badgeFontSize * fontScale, }, label: { color: variantStylesMap[variant].textColor, fontWeight: variantStylesMap[variant].fontWeight, fontSize: badgeFontSize, flexShrink: 1, }, metaLabel: { paddingHorizontal: variantStylesMap[variant].paddingHorizontal, fontSize: badgeFontSize, alignSelf: 'center', flexShrink: 1, paddingLeft: variantStylesMap[variant].metaLabelPaddingLeft, }, }) } // ================================= // ====== Product Logos ============ // ================================= interface ProductLogoProps { logoSize: number } function GroupsLogo({ logoSize }: ProductLogoProps) { return ( ) } function ServicesIcon({ logoSize }: ProductLogoProps) { return ( ) }