From fe8600b9926b5c11cf77ccaea333e1aef95d7a1d Mon Sep 17 00:00:00 2001 From: Summer Schrader Date: Thu, 15 Oct 2026 13:37:31 -0400 Subject: [PATCH] Add primaryTextColor, a separate color for brand-colored text Layout, BlankView, NewBadgeView, and NewPointsView take an optional primaryTextColor. It colors links and other text only; buttons, charts, and progress bars keep primaryColor. Each mode uses it only when that mode's value is set, otherwise text falls back to primaryColor, so views without it look the same as before. Co-Authored-By: Claude Opus 5.5 --- .../presentational/Layout/Layout.test.tsx | 41 +++++++++++++++++++ .../presentational/Layout/Layout.tsx | 16 ++++++-- src/components/view/BlankView/BlankView.tsx | 3 +- .../view/NewBadgeView/NewBadgeView.tsx | 3 +- .../view/NewPointsView/NewPointsView.tsx | 3 +- 5 files changed, 59 insertions(+), 7 deletions(-) diff --git a/__tests__/components/presentational/Layout/Layout.test.tsx b/__tests__/components/presentational/Layout/Layout.test.tsx index 06e1c5295..aa9a44fde 100644 --- a/__tests__/components/presentational/Layout/Layout.test.tsx +++ b/__tests__/components/presentational/Layout/Layout.test.tsx @@ -55,4 +55,45 @@ describe('Layout brand color tokens', () => { await renderLayout(); expect(globalCss()).not.toContain('--mdhui-color-primary-mark: #00B068'); }); + + it('uses primaryTextColor for text and keeps the brand on fills', async () => { + await renderLayout(); + expect(globalCss()).toContain('--mdhui-color-primary: #C2185B'); + expect(globalCss()).toContain('--mdhui-color-primary-text: #F48FB1'); + expect(globalCss()).toContain('--mdhui-color-primary-mark: #C2185B'); + }); + + it('uses primaryTextColor without a brand and leaves marks on the scheme default', async () => { + await renderLayout(); + expect(globalCss()).toContain('--mdhui-color-primary-text: #00693E'); + expect(globalCss()).not.toContain('--mdhui-color-primary: #00693E'); + expect(globalCss()).toContain('--mdhui-color-primary-mark: var(--mdh-blue-30)'); + }); + + it('falls back to the brand for text in dark when the text color has no dark value', async () => { + await renderLayout(); + expect(globalCss()).toContain('--mdhui-color-primary-text: #E91E63'); + expect(globalCss()).not.toContain('#C2185B'); + }); + + it('falls back to the brand for text in light when the text color has no light value', async () => { + await renderLayout(); + expect(globalCss()).toContain('--mdhui-color-primary-text: #E91E63'); + expect(globalCss()).not.toContain('#F48FB1'); + }); + + it('does not pin marks when text falls back to the brand', async () => { + await renderLayout(); + expect(globalCss()).toContain('--mdhui-color-primary-mark: var(--mdhui-color-primary-text)'); + expect(globalCss()).not.toContain('--mdhui-color-primary-mark: #'); + }); + + it('falls back to the brand for an invalid or null text color', async () => { + await renderLayout(); + expect(globalCss()).toContain('--mdhui-color-primary-text: #E91E63'); + cleanup(); + document.querySelectorAll('style').forEach(s => s.remove()); + await renderLayout(); + expect(globalCss()).toContain('--mdhui-color-primary-text: #E91E63'); + }); }); diff --git a/src/components/presentational/Layout/Layout.tsx b/src/components/presentational/Layout/Layout.tsx index 32266718b..a60304d49 100644 --- a/src/components/presentational/Layout/Layout.tsx +++ b/src/components/presentational/Layout/Layout.tsx @@ -2,13 +2,14 @@ import React, { createContext } from 'react'; import MyDataHelps, { StatusBarStyle } from '@careevolution/mydatahelps-js'; import { Global as EmotionGlobal, css } from '@emotion/react'; import "./Layout.css" -import { darkColorScheme, lightColorScheme, global, core } from '../../../helpers/globalCss'; +import { darkColorScheme, darkColorStyle, lightColorScheme, global, core } from '../../../helpers/globalCss'; import { ColorDefinition, resolveColor } from '../../../helpers/colors'; export interface LayoutProps { children?: React.ReactNode; bodyBackgroundColor?: ColorDefinition; primaryColor?: ColorDefinition; + primaryTextColor?: ColorDefinition; statusBarStyle?: StatusBarStyle; className?: string; noGlobalStyles?: boolean; @@ -62,10 +63,17 @@ export default function Layout(props: LayoutProps) { // scheme toggle would land the scheme tag after a separate brand tag and clobber it. const schemeStyles = context.colorScheme == "dark" ? darkColorScheme : lightColorScheme; const brand = props.primaryColor ? resolveColor(colorScheme, props.primaryColor) : undefined; - const brandStyles = brand ? css` + const textColor = props.primaryTextColor && typeof props.primaryTextColor === "object" + ? (colorScheme === "dark" ? props.primaryTextColor.darkMode : props.primaryTextColor.lightMode) + : props.primaryTextColor; + const brandText = resolveColor(colorScheme, textColor) ?? brand; + const schemeText = colorScheme === "dark" ? (darkColorStyle as Record)['--mdhui-color-primary-text'] : "var(--mdhui-color-primary)"; + const markColor = brand ?? schemeText; + const brandStyles = brand || brandText ? css` :root { - --mdhui-color-primary: ${brand}; - --mdhui-color-primary-text: ${brand}; + ${brand ? `--mdhui-color-primary: ${brand};` : ""} + ${brandText ? `--mdhui-color-primary-text: ${brandText};` : ""} + ${brandText !== brand ? `--mdhui-color-primary-mark: ${markColor};` : ""} }` : undefined; return ( diff --git a/src/components/view/BlankView/BlankView.tsx b/src/components/view/BlankView/BlankView.tsx index 41aaf321f..c67406961 100644 --- a/src/components/view/BlankView/BlankView.tsx +++ b/src/components/view/BlankView/BlankView.tsx @@ -10,6 +10,7 @@ export interface BlankViewProps { showBackButton?: boolean colorScheme?: "auto" | "light" | "dark" primaryColor?: ColorDefinition + primaryTextColor?: ColorDefinition bodyBackgroundColor?: ColorDefinition titleColor?: ColorDefinition; subtitleColor?: ColorDefinition; @@ -19,7 +20,7 @@ export interface BlankViewProps { export default function (props: BlankViewProps) { return ( - + {(props.showBackButton || props.showCloseButton) && + return
{badges == null && } {badges != null && <> diff --git a/src/components/view/NewPointsView/NewPointsView.tsx b/src/components/view/NewPointsView/NewPointsView.tsx index 01dc1ffe9..383ac58f2 100644 --- a/src/components/view/NewPointsView/NewPointsView.tsx +++ b/src/components/view/NewPointsView/NewPointsView.tsx @@ -20,6 +20,7 @@ export interface NewPointsViewProps { pointsToNextReward?: number; colorScheme?: 'auto' | 'light' | 'dark'; primaryColor?: ColorDefinition; + primaryTextColor?: ColorDefinition; doneButtonText?: string; } @@ -37,7 +38,7 @@ export default function NewPointsView(props: NewPointsViewProps) { }, totalBonusPoints); return ( - +