give replication destinations a brand mark

This commit is contained in:
Danny White committed 2026-09-14 09:19:38 +10:00
1 parent 26585dd4a4
commit 6ffa37190b
6 files changed
+74 -22

No files matched your search

@@ -0,0 +1,58 @@
import { cn, StatusIcon } from 'ui'
import { DestinationIcon } from './DestinationIcon'
import type { DestinationType } from './DestinationPanel/DestinationPanel.types'
import { BASE_PATH } from '@/lib/constants'
// 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<Record<DestinationType, string>> = {
BigQuery: `${BASE_PATH}/img/icons/bigquery-icon.svg`,
}
const SIZE_CLASS_NAME = {
small: { frame: 'h-8 w-8 rounded-md', mark: 'h-4 w-4', icon: 16 },
large: { frame: 'h-14 w-14 rounded-lg', mark: 'h-6 w-6', icon: 24 },
} as const
interface DestinationLogoProps {
type: DestinationType
size?: keyof typeof SIZE_CLASS_NAME
className?: string
/** Destructive badge on the bottom-right corner (e.g. table replication errors). */
hasErrors?: boolean
}
/**
* A destination's brand mark in a square app-icon frame, used wherever a destination is the
* subject: the list rows, the pipeline header, and the replication diagram.
*/
export const DestinationLogo = ({
type,
size = 'small',
className,
hasErrors = false,
}: DestinationLogoProps) => {
const sizing = SIZE_CLASS_NAME[size]
const brandMark = BRAND_MARK_BY_TYPE[type]
return (
<span className={cn('relative inline-flex shrink-0', className)}>
<span className={cn('flex items-center justify-center border bg-surface-100', sizing.frame)}>
{brandMark === undefined ? (
<DestinationIcon type={type} size={sizing.icon} className="text-foreground-light" />
) : (
<img src={brandMark} alt="" aria-hidden className={sizing.mark} />
)}
</span>
{hasErrors && (
<span
className="absolute -bottom-1 -right-1 rounded-sm bg-background outline outline-2 outline-background"
aria-hidden
>
<StatusIcon variant="destructive" />
</span>
)}
</span>
)
}
@@ -10,7 +10,7 @@ import {
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { DestinationIcon } from '../DestinationIcon'
import { DestinationLogo } from '../DestinationLogo'
import { useDestinationInformation } from '../useDestinationInformation'
import {
useIsETLBigQueryPrivateAlpha,
@@ -163,11 +163,7 @@ export const DestinationTypeSelection = () => {
<SelectTrigger className="h-auto py-2">
{selectedOption ? (
<div className="flex items-center gap-x-3 text-left">
<DestinationIcon
type={selectedOption.value}
size={20}
className="shrink-0 text-foreground-light"
/>
<DestinationLogo type={selectedOption.value} />
<span className="text-sm text-foreground">{selectedOption.label}</span>
</div>
) : (
@@ -182,11 +178,7 @@ export const DestinationTypeSelection = () => {
{group.options.map((option) => (
<SelectItem key={option.value} value={option.value} className="py-2">
<div className="flex items-center gap-x-3">
<DestinationIcon
type={option.value}
size={20}
className="shrink-0 text-foreground-light"
/>
<DestinationLogo type={option.value} />
<div className="flex flex-col gap-y-0.5">
<span className="text-foreground">{option.label}</span>
<span className="text-xs text-foreground-lighter">
@@ -15,7 +15,7 @@ import {
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import { DeleteDestination } from './DeleteDestination'
import { DestinationIcon } from './DestinationIcon'
import { DestinationLogo } from './DestinationLogo'
import { PipelineStatus } from './PipelineStatus'
import { PipelineStatusName, STATUS_REFRESH_FREQUENCY_MS } from './Replication.constants'
import { getFormattedLagValue } from './ReplicationPipelineStatus/ReplicationPipelineStatus.utils'
@@ -149,11 +149,7 @@ export const DestinationRow = ({ destinationId }: DestinationRowProps) => {
)}
{isPipelineSuccess && (
<TableRow>
<TableCell>
{type ? (
<DestinationIcon type={type} size={18} className="text-foreground-light" />
) : null}
</TableCell>
<TableCell>{type ? <DestinationLogo type={type} /> : null}</TableCell>
<TableCell className="max-w-[180px]">
{isPipelineLoading ? (
@@ -277,7 +277,7 @@ export const Destinations = () => {
<Table>
<TableHeader>
<TableRow>
<TableHead key="type" className="w-[20px]" />
<TableHead key="type" className="w-[40px]" />
<TableHead key="name" className="w-[250px]">
Name
</TableHead>
@@ -4,7 +4,7 @@ import { PropsWithChildren } from 'react'
import { AWS_REGIONS } from 'shared-data'
import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { DestinationIcon } from '../DestinationIcon'
import { DestinationLogo } from '../DestinationLogo'
import { getStatusName } from '../Pipeline.utils'
import { STATUS_REFRESH_FREQUENCY_MS } from '../Replication.constants'
import { getReplicationDestinationType } from './Nodes.utils'
@@ -75,8 +75,7 @@ export const ReplicationNode = ({ id }: { id: string }) => {
const type = getReplicationDestinationType(destination?.config)
return (
<NodeContainer className="justify-start gap-x-3">
{type ? <DestinationIcon type={type} size={20} className="text-foreground-light" /> : null}
<NodeContainer>
<div className="text-sm flex flex-col gap-y-0.5">
<div className="flex items-center">
<p>{type}</p>
@@ -101,6 +100,7 @@ export const ReplicationNode = ({ id }: { id: string }) => {
<p className="text-foreground-light">{destination?.name}</p>
<p className="text-foreground-light">ID: {destination?.id}</p>
</div>
{type ? <DestinationLogo type={type} /> : null}
<Handle type="target" position={Position.Left} className="opacity-25" />
</NodeContainer>
)
@@ -1 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path fill="#4386fa" d="M14.48 58.196.558 34.082a4.163 4.163 0 0 1 0-4.164L14.48 5.805a4.163 4.163 0 0 1 3.6-2.082h27.857c1.48.007 2.845.8 3.585 2.082l13.92 24.113a4.163 4.163 0 0 1 0 4.164L49.52 58.196a4.163 4.163 0 0 1-3.6 2.082H18.07a4.167 4.167 0 0 1-3.593-2.082z"/><path d="M40.697 24.235s3.87 9.283-1.406 14.545-14.883 1.894-14.883 1.894L43.95 60.27h1.984a4.163 4.163 0 0 0 3.6-2.082L58.75 42.23z" opacity=".1"/><path fill="#fff" d="M45.267 43.23 41 38.953a.67.67 0 0 0-.158-.12 11.63 11.63 0 1 0-2.032 2.037.67.67 0 0 0 .113.15l4.277 4.277a.67.67 0 0 0 .947 0l1.12-1.12a.67.67 0 0 0 0-.947zM31.64 40.464a8.75 8.75 0 1 1 8.749-8.749 8.75 8.75 0 0 1-8.749 8.749zm-5.593-9.216v3.616a6.426 6.426 0 0 0 2.338 2.375v-6.013zm4.375-2.998v9.772a6.45 6.45 0 0 0 2.338 0V28.25zm6.764 6.606v-2.142H34.85v4.5a6.43 6.43 0 0 0 2.338-2.368z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="#34a853" d="M311.9 418.9c-8.8 0-16-7.2-16-16V257.1c0-8.8 7.2-16 16-16s16 7.2 16 16v145.8c0 8.8-7.2 16-16 16ZM147.6 418.9c-8.8 0-16-7.2-16-16V202.4c0-8.8 7.2-16 16-16s16 7.2 16 16v200.5c0 8.8-7.2 16-16 16Z"/>
<path fill="#34a853" d="M229.8 437.4c-8.8 0-16-7.2-16-16V147.6c0-8.8 7.2-16 16-16s16 7.2 16 16v273.7c0 8.8-7.2 16-16 16Z"/>
<path fill="#fbbc04" d="M229.8 437.4c-114.5 0-207.6-93.1-207.6-207.6h32c0 96.8 78.8 175.6 175.6 175.6s175.6-78.8 175.6-175.6h32c0 114.5-93.1 207.6-207.6 207.6Z"/>
<path fill="#ea4335" d="M437.4 229.8h-32c0-96.8-78.8-175.6-175.6-175.6S54.1 132.9 54.1 229.8h-32c0-114.5 93.2-207.7 207.7-207.7s207.6 93.1 207.6 207.6Z"/>
<path fill="#4285f4" d="m487.4 464.8-100-100c32.3-37.6 49.9-85 49.9-135.1s-21.6-107.6-60.8-146.8l-22.6 22.6c33.2 33.2 51.4 77.3 51.4 124.2s-18.3 90.9-51.5 124.1c-5 5-7.5 7-11.8 10.8l122.8 122.8c3.1 3.1 7.2 4.7 11.3 4.7s8.2-1.6 11.3-4.7c6.2-6.2 6.2-16.4 0-22.6Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 903 B

After

Width:  |  Height:  |  Size: 1006 B