diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationLogo.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationLogo.tsx index c595dd9fcb9..a36cf0fc4d4 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationLogo.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationLogo.tsx @@ -1,16 +1,18 @@ +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' // Destinations with a brand mark. Anything absent falls back to the line icon in the same frame, // so a new destination type never renders an empty square. -const BRAND_MARK_BY_TYPE: Partial> = { - BigQuery: `${BASE_PATH}/img/icons/bigquery-icon.svg`, - ClickHouse: `${BASE_PATH}/img/icons/clickhouse-icon.svg`, - DuckLake: `${BASE_PATH}/img/icons/ducklake-icon.svg`, - Snowflake: `${BASE_PATH}/img/icons/snowflake-icon.svg`, +const BRAND_MARK_BY_TYPE: Partial> = { + BigQuery: BRAND_ICONS.bigquery, + ClickHouse: BRAND_ICONS.clickhouse, + DuckLake: BRAND_ICONS.ducklake, + Snowflake: BRAND_ICONS.snowflake, } const SIZE_CLASS_NAME = { @@ -36,16 +38,19 @@ export const DestinationLogo = ({ className, hasErrors = false, }: DestinationLogoProps) => { + const { resolvedTheme } = useTheme() + const isDark = resolveThemeOverrideMode(resolvedTheme) === 'dark' const sizing = SIZE_CLASS_NAME[size] const brandMark = BRAND_MARK_BY_TYPE[type] + const brandMarkSrc = brandMark === undefined ? undefined : resolveThemedIconSrc(brandMark, isDark) return ( - {brandMark === undefined ? ( + {brandMarkSrc === undefined ? ( ) : ( - + )} {hasErrors && ( diff --git a/apps/studio/lib/brand-icons.test.ts b/apps/studio/lib/brand-icons.test.ts new file mode 100644 index 00000000000..152e855c5b5 --- /dev/null +++ b/apps/studio/lib/brand-icons.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest' + +import { resolveThemedIconSrc } from './brand-icons' + +describe('resolveThemedIconSrc', () => { + it('returns a single asset for either theme', () => { + expect(resolveThemedIconSrc('/icon.svg', false)).toBe('/icon.svg') + expect(resolveThemedIconSrc('/icon.svg', true)).toBe('/icon.svg') + }) + + it('selects the matching themed asset', () => { + const src = { light: '/light.svg', dark: '/dark.svg' } + expect(resolveThemedIconSrc(src, false)).toBe('/light.svg') + expect(resolveThemedIconSrc(src, true)).toBe('/dark.svg') + }) +}) diff --git a/apps/studio/lib/brand-icons.ts b/apps/studio/lib/brand-icons.ts new file mode 100644 index 00000000000..7a669a62302 --- /dev/null +++ b/apps/studio/lib/brand-icons.ts @@ -0,0 +1,18 @@ +import { BASE_PATH } from '@/lib/constants' + +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-light.svg`, + dark: `${BASE_PATH}/img/icons/clickhouse-icon.svg`, + }, + ducklake: `${BASE_PATH}/img/icons/ducklake-icon.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 +} diff --git a/apps/studio/public/img/icons/clickhouse-icon-light.svg b/apps/studio/public/img/icons/clickhouse-icon-light.svg new file mode 100644 index 00000000000..651c515694a --- /dev/null +++ b/apps/studio/public/img/icons/clickhouse-icon-light.svg @@ -0,0 +1,4 @@ + + + +