import { Platform, PlatformColor } from 'react-native' import type { ColorSchemeName, ColorValue, OpaqueColorValue } from 'react-native' import { aliasTokensColorMap } from '../vendor/tapestry/alias_tokens_color_map' import { tokens } from '../vendor/tapestry/tokens' export interface ChatTheme extends DefaultTheme { colors: DefaultTheme['colors'] & (typeof aliasTokensColorMap.light | typeof aliasTokensColorMap.dark) } /** ============================================= NOTE: The specific values for `colors` and the `productBadge` are temporary examples that can be replaced once we start building out UI. This line's comment can be removed at that time too. The default theme is intended to support two types of customizations: 1. Specific color properties for a chat component (eg. `primaryButtonBackgroundColor`) 2. Any styles for a specific component. (Use only one level of nesting.) ``` primaryButton: { borderRadius: number container: ViewStyle text: TextStyle } ``` ============================================= */ export interface DefaultTheme { colors: ChatColors button: { borderRadius: number } chip: { hslLightnessChipSelected: number } showBadgeProductLogo: boolean } export const defaultTheme = (colorScheme: ColorSchemeName | null): ChatTheme => { const scheme: 'light' | 'dark' = colorScheme === 'dark' ? 'dark' : 'light' const defaultColors = { ...chatThemeColorMap[scheme], ...aliasTokensColorMap[scheme], } return { colors: defaultColors, button: { borderRadius: 40, }, chip: { hslLightnessChipSelected: scheme === 'dark' ? 12 : 92, }, showBadgeProductLogo: true, // Overrides visibility of the product logo in `src/display/badge.tsx` } } export type TemporaryDefaultColorsType = Partial interface ChatColors { name: string buttonStart: string | undefined buttonEnd: string | undefined interaction: string androidModalHeaderButtonTextColor: ColorValue | string | undefined iOSModalHeaderButtonTextColor: OpaqueColorValue | string | undefined androidSwitchThumbColor: string | undefined conversationActionsBackground: string testColor: string statusSuccessIcon: string statusSuccessText: string statusSuccessBorder: string statusSuccessFill: string statusInfoIcon: string statusInfoText: string statusInfoBorder: string statusInfoFill: string statusWarningIcon: string statusWarningText: string statusWarningBorder: string statusWarningFill: string statusErrorIcon: string statusErrorText: string statusErrorBorder: string statusErrorFill: string statusSuccessComposedIcon: string statusSuccessComposedText: string statusSuccessComposedBackground: string statusInfoComposedIcon: string statusInfoComposedText: string statusInfoComposedBackground: string statusWarningComposedIcon: string statusWarningComposedText: string statusWarningComposedBackground: string statusErrorComposedIcon: string statusErrorComposedText: string statusErrorComposedBackground: string statusNeutralComposedIcon: string statusNeutralComposedText: string statusNeutralComposedBackground: string borderColorChip: string fillColorChip: string textColorChip: string /** * Selected-chip colors (border/fill/text). Never assign a static default * directly — these stay optional so they always fall back to * `colors.interaction` via `??` at the read site. Every current consumer * overrides `interaction` at runtime, and a seeded default here could win * over that override depending on merge order. */ borderColorChipSelected?: string fillColorChipSelected?: string textColorChipSelected?: string } // 'link' is an iOS-only semantic color name — constructing it on Android is // undefined behavior, so only build it when the code is actually running on iOS const iOSLinkColor = Platform.OS === 'ios' ? PlatformColor('link') : undefined const colorsChatLight: ChatColors = { name: 'light', androidModalHeaderButtonTextColor: tokens.fillColorInteractionDefault, iOSModalHeaderButtonTextColor: iOSLinkColor, buttonStart: undefined, buttonEnd: undefined, interaction: tokens.fillColorInteractionDefault, androidSwitchThumbColor: undefined, conversationActionsBackground: tokens.colorNeutral100White, testColor: 'hotpink', statusInfoIcon: tokens.iconColorStatusInfoPrimary, statusInfoText: tokens.textColorStatusInfo, statusInfoBorder: tokens.borderColorStatusInfo, statusInfoFill: tokens.fillColorStatusInfoSolid, statusSuccessIcon: tokens.iconColorStatusSuccessPrimary, statusSuccessText: tokens.textColorStatusSuccess, statusSuccessBorder: tokens.borderColorStatusSuccess, statusSuccessFill: tokens.fillColorStatusSuccessSolid, statusWarningIcon: tokens.iconColorStatusWarningPrimary, statusWarningText: tokens.textColorStatusWarning, statusWarningBorder: tokens.borderColorStatusWarning, statusWarningFill: tokens.fillColorStatusWarningSolid, statusErrorIcon: tokens.iconColorStatusErrorPrimary, statusErrorText: tokens.textColorStatusError, statusErrorBorder: tokens.borderColorStatusError, statusErrorFill: tokens.fillColorStatusErrorSolid, statusInfoComposedIcon: tokens.iconColorStatusInfoPrimary, statusInfoComposedText: tokens.textColorStatusInfo, statusInfoComposedBackground: tokens.fillColorStatusInfoGhost, statusSuccessComposedIcon: tokens.iconColorStatusSuccessPrimary, statusSuccessComposedText: tokens.textColorStatusSuccess, statusSuccessComposedBackground: tokens.fillColorStatusSuccessGhost, statusWarningComposedIcon: tokens.iconColorStatusWarningPrimary, statusWarningComposedText: tokens.textColorStatusWarning, statusWarningComposedBackground: tokens.fillColorStatusWarningGhost, statusErrorComposedIcon: tokens.iconColorStatusErrorPrimary, statusErrorComposedText: tokens.textColorStatusError, statusErrorComposedBackground: tokens.fillColorStatusErrorGhost, statusNeutralComposedIcon: tokens.iconColorStatusNeutralPrimary, statusNeutralComposedText: tokens.textColorStatusNeutral, statusNeutralComposedBackground: tokens.fillColorStatusNeutralGhost, borderColorChip: tokens.colorNeutral88, fillColorChip: 'transparent', textColorChip: tokens.colorNeutral45, } const colorsChatDark: ChatColors = { name: 'dark', androidModalHeaderButtonTextColor: tokens.fillColorInteractionDefaultDark, iOSModalHeaderButtonTextColor: iOSLinkColor, buttonStart: undefined, buttonEnd: undefined, interaction: tokens.fillColorInteractionDefaultDark, androidSwitchThumbColor: undefined, conversationActionsBackground: tokens.colorNeutral7, testColor: 'pink', statusInfoIcon: tokens.iconColorStatusInfoPrimaryDark, statusInfoText: tokens.textColorStatusInfoDark, statusInfoBorder: tokens.borderColorStatusInfoDark, statusInfoFill: tokens.fillColorStatusInfoSolidDark, statusSuccessIcon: tokens.iconColorStatusSuccessPrimaryDark, statusSuccessText: tokens.textColorStatusSuccessDark, statusSuccessBorder: tokens.borderColorStatusSuccessDark, statusSuccessFill: tokens.fillColorStatusSuccessSolidDark, statusWarningIcon: tokens.iconColorStatusWarningPrimaryDark, statusWarningText: tokens.textColorStatusWarningDark, statusWarningBorder: tokens.borderColorStatusWarningDark, statusWarningFill: tokens.fillColorStatusWarningSolidDark, statusErrorIcon: tokens.iconColorStatusErrorPrimaryDark, statusErrorText: tokens.textColorStatusErrorDark, statusErrorBorder: tokens.borderColorStatusErrorDark, statusErrorFill: tokens.fillColorStatusErrorSolidDark, // Note: CCA inverts composed status colors in dark mode statusInfoComposedIcon: tokens.iconColorStatusInfoPrimaryDark, statusInfoComposedText: tokens.textColorStatusInfoDark, statusInfoComposedBackground: tokens.fillColorStatusInfoGhostDark, statusSuccessComposedIcon: tokens.iconColorStatusSuccessPrimaryDark, statusSuccessComposedText: tokens.textColorStatusSuccessDark, statusSuccessComposedBackground: tokens.fillColorStatusSuccessGhostDark, statusWarningComposedIcon: tokens.iconColorStatusWarningPrimaryDark, statusWarningComposedText: tokens.textColorStatusWarningDark, statusWarningComposedBackground: tokens.fillColorStatusWarningGhostDark, statusErrorComposedIcon: tokens.iconColorStatusErrorPrimaryDark, statusErrorComposedText: tokens.textColorStatusErrorDark, statusErrorComposedBackground: tokens.fillColorStatusErrorGhostDark, statusNeutralComposedIcon: tokens.iconColorStatusNeutralPrimaryDark, statusNeutralComposedText: tokens.textColorStatusNeutralDark, statusNeutralComposedBackground: tokens.fillColorStatusNeutralGhostDark, borderColorChip: tokens.colorNeutral24, fillColorChip: 'transparent', textColorChip: tokens.colorNeutral68, } const chatThemeColorMap = { light: colorsChatLight, dark: colorsChatDark, }