Resolve themed trust center logo in the graph

Add a TrustCenter.themedLogoUrl Relay live resolver that reads the
light and dark logos from the graph and subscribes to the system
prefers-color-scheme media query, returning the dark logo (falling
back to the light one) when the OS prefers dark.

The TopBar now selects this single field instead of pulling both logo
URLs and branching on useSystemTheme, keeping the theme-to-URL logic in
the graph where any component can reuse it.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-06-28 15:15:18 +02:00
parent 68907762c7
commit 2a69129a83
2 changed files with 67 additions and 6 deletions

View File

@@ -38,11 +38,9 @@ const topBarFragment = graphql`
...TopBarUserMenu_identity
}
currentTrustCenter @required(action: THROW) {
themedLogoUrl
organization {
name
logo {
downloadUrl
}
}
}
}
@@ -61,9 +59,9 @@ export function TopBar({ queryKey }: TopBarProps) {
const data = useFragment(topBarFragment, queryKey);
const { pathname } = useLocation();
const { organization } = data.currentTrustCenter;
const organizationName = organization.name;
const logoUrl = organization.logo?.downloadUrl ?? undefined;
const { currentTrustCenter } = data;
const organizationName = currentTrustCenter.organization.name;
const logoUrl = currentTrustCenter.themedLogoUrl ?? undefined;
const slots = topBar();

View File

@@ -0,0 +1,63 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import { graphql } from "react-relay";
import { type LiveState, readFragment } from "relay-runtime";
import type { TrustCenterLogoResolverFragment$key } from "./__generated__/TrustCenterLogoResolverFragment.graphql";
function prefersDark(): boolean {
return typeof window !== "undefined"
&& !!window.matchMedia
&& window.matchMedia("(prefers-color-scheme: dark)").matches;
}
/**
* @relayField TrustCenter.themedLogoUrl: String
* @rootFragment TrustCenterLogoResolverFragment
* @live
*
* Resolves the trust center logo download URL for the current system color
* scheme: the dark logo (falling back to the light one) when the OS prefers
* dark, otherwise the light logo. Lives in the graph so consumers select a
* single field instead of threading `useSystemTheme` through URL selection.
*/
export function themedLogoUrl(
key: TrustCenterLogoResolverFragment$key,
): LiveState<string | null> {
const data = readFragment(
graphql`
fragment TrustCenterLogoResolverFragment on TrustCenter {
logo {
downloadUrl
}
darkLogo {
downloadUrl
}
}
`,
key,
);
const lightUrl = data.logo?.downloadUrl ?? null;
const darkUrl = data.darkLogo?.downloadUrl ?? lightUrl;
return {
read: () => (prefersDark() ? darkUrl : lightUrl),
subscribe: (callback) => {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
mediaQuery.addEventListener("change", callback);
return () => mediaQuery.removeEventListener("change", callback);
},
};
}