refactor(app): unify status colours on one token family
Three systems were reporting the same states: a full and a muted status tier in the theme, plus raw palette steps in StatusBadge and getStatusDotColor. A PR icon was muted in the sidebar and full-strength in the PR panel, and the Resolved badge used a theme-independent green so it painted a dark block in light mode. The muted tier is deleted and every status surface now reads the one token per signal. Dots keep a band of their own. A dot is a few points of solid colour with no shape to read and no label attached, and the running one pulses, so at the status band's lightness it sat below the check icons and host badge on its own row — backwards, since the dot is the row's state. Chroma caps at 90% of gamut rather than 100% because at the edge green reaches C=0.215 while blue manages 0.116 and the set stops reading as one family; dark lightness caps at 0.72 because red desaturates to pink above it. Host badges needed a foreground variant of the identity table. That table is tuned for a white letter on the colour as a fill; used as text it cannot clear 4.5:1 on both themes at once, since light needs luminance <= 0.183 and dark needs >= 0.212. The new set is generated on the status family's exact lightness and chroma fraction, so a host name and a CI check in the same meta row carry equal weight instead of the brighter one winning the row. Also: PR avatars had their own eight-hex palette and hash duplicating identity-colors; workspace rows under a project group now indent to the same rail as status-grouped rows; group headers are muted; and the pulsing dot is faster and fades further.
This commit is contained in:
@@ -2,7 +2,7 @@ import { Text, View } from "react-native";
|
||||
import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
||||
import { Server } from "lucide-react-native";
|
||||
import { HOST_COLORS, type HostBadgeModel, type HostColor } from "@/hosts/appearance";
|
||||
import { identityColor } from "@/styles/identity-colors";
|
||||
import { identityForeground } from "@/styles/identity-colors";
|
||||
import type { Theme } from "@/styles/theme";
|
||||
|
||||
/**
|
||||
@@ -15,19 +15,26 @@ const ThemedServer = withUnistyles(Server);
|
||||
|
||||
const mutedMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
|
||||
|
||||
// Theme-independent, so the mapping per host color is fixed for the life of the module. A
|
||||
// fresh `uniProps` identity on every render makes withUnistyles re-subscribe each pass.
|
||||
// One stable mapping per host color for the life of the module — a fresh `uniProps` identity
|
||||
// on every render makes withUnistyles re-subscribe each pass. The scheme is read inside the
|
||||
// mapping rather than baked in, so a theme change repaints the glyph without a React render.
|
||||
//
|
||||
// Glyph and label share one color, so a badge reads as one object rather than a colored icon
|
||||
// with an unrelated grey word beside it.
|
||||
const HOST_ICON_MAPPINGS: Record<HostColor, (theme: Theme) => { color: string }> = (() => {
|
||||
const byColor = {} as Record<HostColor, (theme: Theme) => { color: string }>;
|
||||
for (const color of HOST_COLORS) {
|
||||
byColor[color] = color === "none" ? mutedMapping : () => ({ color: identityColor(color) });
|
||||
byColor[color] =
|
||||
color === "none"
|
||||
? mutedMapping
|
||||
: (theme: Theme) => ({ color: identityForeground(color, theme.colorScheme) });
|
||||
}
|
||||
return byColor;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Which machine something lives on, drawn the same way everywhere it appears: a server glyph
|
||||
* in the host's identity color, and the host's name when the host is configured to show one.
|
||||
* and, when the host is configured to show one, its name — both in the host's identity color.
|
||||
*
|
||||
* A hostname is the least interesting thing on any line that carries it and the only one whose
|
||||
* length nobody chose, so the badge yields space before its neighbours rather than alongside
|
||||
@@ -42,7 +49,7 @@ export function HostBadge({ badge }: { badge: HostBadgeModel }) {
|
||||
>
|
||||
<ThemedServer size={HOST_BADGE_ICON_SIZE} uniProps={HOST_ICON_MAPPINGS[badge.color]} />
|
||||
{badge.showLabel ? (
|
||||
<Text style={styles.label} numberOfLines={1}>
|
||||
<Text style={[styles.label, labelColorStyle(badge.color)]} numberOfLines={1}>
|
||||
{badge.label}
|
||||
</Text>
|
||||
) : null}
|
||||
@@ -50,6 +57,36 @@ export function HostBadge({ badge }: { badge: HostBadgeModel }) {
|
||||
);
|
||||
}
|
||||
|
||||
// Text has no `color` prop, so the label cannot ride the icon's uniProps mapping — its color
|
||||
// has to come from a registered style. One entry per host color, picked at render time; a
|
||||
// module-level lookup table would read the style proxies before the persisted theme lands.
|
||||
function labelColorStyle(color: HostColor) {
|
||||
switch (color) {
|
||||
case "none":
|
||||
return null;
|
||||
case "violet":
|
||||
return styles.labelViolet;
|
||||
case "sky":
|
||||
return styles.labelSky;
|
||||
case "emerald":
|
||||
return styles.labelEmerald;
|
||||
case "orange":
|
||||
return styles.labelOrange;
|
||||
case "pink":
|
||||
return styles.labelPink;
|
||||
case "indigo":
|
||||
return styles.labelIndigo;
|
||||
case "teal":
|
||||
return styles.labelTeal;
|
||||
case "red":
|
||||
return styles.labelRed;
|
||||
case "amber":
|
||||
return styles.labelAmber;
|
||||
case "blue":
|
||||
return styles.labelBlue;
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
badge: {
|
||||
flexDirection: "row",
|
||||
@@ -69,4 +106,14 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flexShrink: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
labelViolet: { color: identityForeground("violet", theme.colorScheme) },
|
||||
labelSky: { color: identityForeground("sky", theme.colorScheme) },
|
||||
labelEmerald: { color: identityForeground("emerald", theme.colorScheme) },
|
||||
labelOrange: { color: identityForeground("orange", theme.colorScheme) },
|
||||
labelPink: { color: identityForeground("pink", theme.colorScheme) },
|
||||
labelIndigo: { color: identityForeground("indigo", theme.colorScheme) },
|
||||
labelTeal: { color: identityForeground("teal", theme.colorScheme) },
|
||||
labelRed: { color: identityForeground("red", theme.colorScheme) },
|
||||
labelAmber: { color: identityForeground("amber", theme.colorScheme) },
|
||||
labelBlue: { color: identityForeground("blue", theme.colorScheme) },
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user