import { Text, View, type TextStyle, type ViewStyle } from "react-native"; import { StyleSheet, withUnistyles } from "react-native-unistyles"; import { Server } from "lucide-react-native"; import { HOST_COLORS, type HostColor } from "@/hosts/appearance"; import { identityColor, identityTint } from "@/styles/identity-colors"; import type { Theme } from "@/styles/theme"; const ThemedServer = withUnistyles(Server); const MAX_NAMED_BADGE_WIDTH = 96; interface HostBadgeColorOverride { badge: ViewStyle | null; text: TextStyle | null; } function buildColorOverrides(): Record { const byColor = {} as Record; for (const color of HOST_COLORS) { byColor[color] = color === "none" ? { badge: null, text: null } : { badge: { backgroundColor: identityTint(color) }, text: { color: identityColor(color) }, }; } return byColor; } // The identity table is theme-independent, so its icon mapping is fixed for the life of the // module. It must not be rebuilt per render: a fresh `uniProps` identity makes withUnistyles // re-subscribe every pass. function buildIconMappings(): Record { color: string }> { const byColor = {} as Record { color: string }>; for (const color of HOST_COLORS) { byColor[color] = color === "none" ? (theme: Theme) => ({ color: theme.colors.foregroundMuted }) : () => ({ color: identityColor(color) }); } return byColor; } const COLOR_OVERRIDES = buildColorOverrides(); const ICON_MAPPINGS = buildIconMappings(); /** * Which host a workspace lives on, as a pill after the title. Only rendered in multi-host * setups — see selectHostBadges. It's a badge rather than a second line of text because the * row has to stay scannable at one line per workspace. * */ export function HostBadge({ serverId, label, color, showLabel, }: { serverId: string; label: string; color: HostColor; showLabel: boolean; }) { const override = COLOR_OVERRIDES[color]; return ( {showLabel ? ( {label} ) : null} ); } const hostBadgeStyles = StyleSheet.create((theme) => ({ // Sized off the title's 20pt line box so the pill never grows the row. A named badge must // leave room for the workspace title and trailing actions, even when a host has an arbitrary // custom label. The full host name remains in the row's accessible label and hover card. badge: { flexDirection: "row", alignItems: "center", gap: 3, height: 16, paddingLeft: 4, paddingRight: 6, borderRadius: theme.borderRadius.full, backgroundColor: theme.colors.surface2, maxWidth: MAX_NAMED_BADGE_WIDTH, minWidth: 0, flexShrink: 0, }, // Icon-only drops the label, so the asymmetric padding that made room for text leaves a // lopsided pill. Symmetric padding recenters the icon. badgeIconOnly: { paddingLeft: 4, paddingRight: 4, flexShrink: 0, }, text: { fontSize: theme.fontSize.xs, fontWeight: theme.fontWeight.normal, lineHeight: 14, color: theme.colors.foregroundMuted, flexShrink: 1, minWidth: 0, }, }));