feat(theme): keep the by-role color-pairs @Preview alongside old-vs-new

Restore ThemeColorPairsPreview (every fg/bg pair listed by ColorScheme role
name, current theme, dark | light) next to the by-component old-vs-new table.
The role view answers "what is primaryContainer right now"; the comparison
answers "how did this surface change". Both read the live scheme.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVwY2Qzth2EREWg8qLhyzF
This commit is contained in:
Claude
2026-07-17 04:18:30 +00:00
parent d76c803e1d
commit 00ea0f0a01
@@ -294,3 +294,187 @@ fun ThemeStyleguideOldVsNewPreview() {
}
}
}
// ---------------------------------------------------------------------------
// Role-oriented view: every fg/bg pair listed by its ColorScheme role name (not
// by the component using it), rendered for the CURRENT theme in dark | light.
// Answers "what is primaryContainer right now"; the table above answers "how did
// this surface change". Both read the live scheme, so Theme.kt edits re-render.
// ---------------------------------------------------------------------------
private class RolePair(
val name: String,
val bg: (ColorScheme) -> Color,
val fg: (ColorScheme) -> Color,
val usage: String,
val highlight: Boolean = false,
)
private val ROLE_PAIRS =
listOf(
RolePair(
"filledAccent / onFilledAccent",
{ it.filledAccent },
{ it.onFilledAccent },
"Top-bar Save·Post·Send·Create button, standalone Save button, checked switch.",
highlight = true,
),
RolePair(
"primary / onPrimary",
{ it.primary },
{ it.onPrimary },
"Floating action buttons, generic filled buttons, sliders, progress.",
),
RolePair(
"primaryContainer / onPrimaryContainer",
{ it.primaryContainer },
{ it.onPrimaryContainer },
"Settings icon tiles, tonal cards, tonal chips.",
),
RolePair(
"secondaryContainer / onSecondaryContainer",
{ it.secondaryContainer },
{ it.onSecondaryContainer },
"Selected-reaction highlight, relay-group chips, chat header, reply toggle.",
),
RolePair(
"secondary / onSecondary",
{ it.secondary },
{ it.onSecondary },
"Teal accent (purple theme); tertiary mirrors it.",
),
RolePair(
"tertiaryContainer / onTertiaryContainer",
{ it.tertiaryContainer },
{ it.onTertiaryContainer },
"Tertiary tonal surfaces; derived from the accent.",
),
RolePair(
"error / onError",
{ it.error },
{ it.onError },
"Destructive dialogs and error states. Material baseline.",
),
RolePair(
"surface / onSurface",
{ it.surface },
{ it.onSurface },
"App background and primary body text.",
),
RolePair(
"surface / onSurfaceVariant",
{ it.surface },
{ it.onSurfaceVariant },
"Secondary text, timestamps, subtitles, icons.",
),
RolePair(
"surfaceContainer / onSurface",
{ it.surfaceContainer },
{ it.onSurface },
"Cards, bottom sheets, elevated rows.",
),
RolePair(
"surfaceVariant / onSurfaceVariant",
{ it.surfaceVariant },
{ it.onSurfaceVariant },
"Subtle filled areas — code blocks, inset panels.",
),
RolePair(
"surfaceContainerHighest / outline",
{ it.surfaceContainerHighest },
{ it.outline },
"Unchecked switch (off-track + thumb), inactive controls.",
),
)
private fun contrastLabel(ratio: Float): String =
when {
ratio >= 4.5f -> "AA"
ratio >= 3f -> "AA-lg"
else -> "low"
}
@Composable
private fun RolePairRow(pair: RolePair) {
val scheme = MaterialTheme.colorScheme
val bg = pair.bg(scheme)
val fg = pair.fg(scheme)
val ratio = contrastRatio(bg, fg)
Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp, vertical = 5.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier =
Modifier
.width(92.dp)
.height(58.dp)
.clip(RoundedCornerShape(10.dp))
.background(bg)
.border(
if (pair.highlight) 2.dp else 1.dp,
if (pair.highlight) scheme.onBackground else scheme.outlineVariant,
RoundedCornerShape(10.dp),
).padding(8.dp),
) {
Text("Aa", color = fg, fontSize = 19.sp, fontWeight = FontWeight.Bold)
Text(
"%.1f:1 %s".format(ratio, contrastLabel(ratio)),
color = fg,
fontSize = 8.5.sp,
fontFamily = FontFamily.Monospace,
modifier = Modifier.align(Alignment.BottomEnd),
)
}
Spacer(Modifier.width(10.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
pair.name,
color = scheme.onBackground,
fontSize = 12.5.sp,
fontWeight = if (pair.highlight) FontWeight.Bold else FontWeight.Medium,
)
Text(
"${bg.hex()} / ${fg.hex()}",
color = scheme.onSurfaceVariant,
fontSize = 10.5.sp,
fontFamily = FontFamily.Monospace,
)
Text(
pair.usage,
color = scheme.onSurfaceVariant,
fontSize = 10.sp,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
}
}
}
@Composable
private fun RolePairList() {
Column(
modifier = Modifier.padding(vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
"fg / bg pairs by role",
color = MaterialTheme.colorScheme.onBackground,
fontSize = 13.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
)
ROLE_PAIRS.forEach { RolePairRow(it) }
}
}
@Preview(name = "Theme color pairs by role · dark | light", widthDp = 820, showBackground = true)
@Composable
fun ThemeColorPairsPreview() {
ThemeComparisonRow {
RolePairList()
}
}