diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationLogo.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationLogo.tsx index 54cf3019961..77b9fcca777 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationLogo.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationLogo.tsx @@ -1,15 +1,17 @@ +import { useTheme } from 'next-themes' import { cn, StatusIcon } from 'ui' import { DestinationIcon } from './DestinationIcon' import type { DestinationType } from './DestinationPanel/DestinationPanel.types' -import { BASE_PATH } from '@/lib/constants' +import { BRAND_ICONS, resolveThemedIconSrc, type ThemedIconSrc } from '@/lib/brand-icons' +import { resolveThemeOverrideMode } from '@/lib/theme-overrides' -type DestinationBrand = { type: 'mark'; src: string } | { type: 'monogram'; label: string } +type DestinationBrand = { type: 'mark'; src: ThemedIconSrc } | { type: 'monogram'; label: string } const BRAND_BY_TYPE: Partial> = { - BigQuery: { type: 'mark', src: `${BASE_PATH}/img/icons/bigquery-icon.svg` }, - ClickHouse: { type: 'monogram', label: 'CH' }, - DuckLake: { type: 'mark', src: `${BASE_PATH}/img/icons/ducklake-icon.svg` }, + BigQuery: { type: 'mark', src: BRAND_ICONS.bigquery }, + ClickHouse: { type: 'mark', src: BRAND_ICONS.clickhouse }, + DuckLake: { type: 'mark', src: BRAND_ICONS.ducklake }, Snowflake: { type: 'monogram', label: 'SF' }, } @@ -36,8 +38,11 @@ export const DestinationLogo = ({ className, hasErrors = false, }: DestinationLogoProps) => { + const { resolvedTheme } = useTheme() + const isDark = resolveThemeOverrideMode(resolvedTheme) === 'dark' const sizing = SIZE_CLASS_NAME[size] const brand = BRAND_BY_TYPE[type] + const brandMarkSrc = brand?.type === 'mark' ? resolveThemedIconSrc(brand.src, isDark) : undefined return ( @@ -46,7 +51,7 @@ export const DestinationLogo = ({ )} {brand?.type === 'mark' && ( - + )} {brand?.type === 'monogram' && ( { + it('returns a single-asset path for either theme', () => { + expect(resolveThemedIconSrc('/img/icons/bigquery-icon.svg', false)).toBe( + '/img/icons/bigquery-icon.svg' + ) + expect(resolveThemedIconSrc('/img/icons/bigquery-icon.svg', true)).toBe( + '/img/icons/bigquery-icon.svg' + ) + }) + + it('picks light or dark when both are provided', () => { + const src = { light: '/light.svg', dark: '/dark.svg' } + expect(resolveThemedIconSrc(src, false)).toBe('/light.svg') + expect(resolveThemedIconSrc(src, true)).toBe('/dark.svg') + }) +}) + +describe('resolveLightIconSrc', () => { + it('returns the only asset or the light variant', () => { + expect(resolveLightIconSrc('/img/icons/bigquery-icon.svg')).toBe('/img/icons/bigquery-icon.svg') + expect(resolveLightIconSrc({ light: '/light.svg', dark: '/dark.svg' })).toBe('/light.svg') + }) +}) diff --git a/apps/studio/lib/brand-icons.ts b/apps/studio/lib/brand-icons.ts new file mode 100644 index 00000000000..e383491365a --- /dev/null +++ b/apps/studio/lib/brand-icons.ts @@ -0,0 +1,35 @@ +import { BASE_PATH } from '@/lib/constants' + +/** + * Brand mark paths shared by Pipelines (`DestinationLogo`) and Integrations + * Wrappers. A single string is theme-agnostic; `{ light, dark }` opts into + * per-theme assets. + * + * Wrappers / `IntegrationLogo` force a white tile, so they should always use + * {@link resolveLightIconSrc}. DestinationLogo sits on `bg-surface-100` and + * should resolve against the active theme. + */ +export type ThemedIconSrc = string | { light: string; dark: string } + +export const BRAND_ICONS = { + bigquery: `${BASE_PATH}/img/icons/bigquery-icon.svg`, + clickhouse: { + light: `${BASE_PATH}/img/icons/clickhouse-icon-on-light.svg`, + dark: `${BASE_PATH}/img/icons/clickhouse-icon-on-dark.svg`, + }, + ducklake: { + light: `${BASE_PATH}/img/icons/ducklake-icon-on-light.svg`, + dark: `${BASE_PATH}/img/icons/ducklake-icon-on-dark.svg`, + }, + snowflake: `${BASE_PATH}/img/icons/snowflake-icon.svg`, +} as const satisfies Record + +export function resolveThemedIconSrc(src: ThemedIconSrc, isDark: boolean): string { + if (typeof src === 'string') return src + return isDark ? src.dark : src.light +} + +/** Light (or only) variant — for surfaces that force a white logo tile. */ +export function resolveLightIconSrc(src: ThemedIconSrc): string { + return typeof src === 'string' ? src : src.light +} diff --git a/apps/studio/public/img/icons/clickhouse-icon.svg b/apps/studio/public/img/icons/clickhouse-icon-on-dark.svg similarity index 100% rename from apps/studio/public/img/icons/clickhouse-icon.svg rename to apps/studio/public/img/icons/clickhouse-icon-on-dark.svg diff --git a/apps/studio/public/img/icons/clickhouse-icon-on-light.svg b/apps/studio/public/img/icons/clickhouse-icon-on-light.svg new file mode 100644 index 00000000000..651c515694a --- /dev/null +++ b/apps/studio/public/img/icons/clickhouse-icon-on-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/studio/public/img/icons/ducklake-icon.svg b/apps/studio/public/img/icons/ducklake-icon-on-dark.svg similarity index 100% rename from apps/studio/public/img/icons/ducklake-icon.svg rename to apps/studio/public/img/icons/ducklake-icon-on-dark.svg diff --git a/apps/studio/public/img/icons/ducklake-icon-on-light.svg b/apps/studio/public/img/icons/ducklake-icon-on-light.svg new file mode 100644 index 00000000000..61f2846cbd2 --- /dev/null +++ b/apps/studio/public/img/icons/ducklake-icon-on-light.svg @@ -0,0 +1,11 @@ + + + + + + + + + + +