Files
supabase/apps/studio/components/ui/ReactFlow/EdgeVisual.tsx
T
Joshen Lim 09a1f431bc Mass update for all lucide icon imports to use the component with Icon suffix (#51659)
## Context

This is largely just a mechanical change here:
Opting to use lucide icons with the `Icon` suffix across all icon
imports within `studio`
- so that it’s clearer which components are icons + reduces the
possibility of name collision with our own components
- e.g <User /> vs <UserIcon />

Also added a eslint rule for this:
<img width="1069" height="167" alt="image"
src="https://github.com/user-attachments/assets/7f36f443-5ca2-4bf6-8d22-86e774513bfc"
/>

And updated `AGENTS.md` as such:
```
Icons come from `lucide-react`, always imported with the `Icon` suffix (`import { CheckIcon, Trash2Icon } from 'lucide-react'`) — never the bare names (`Check`) or `Check as CheckIcon` aliases (lint-enforced).
```
2026-10-14 12:56:55 +02:00

89 lines
2.2 KiB
TypeScript

import { ArrowRightIcon, Loader2Icon, SquareIcon, XIcon, type LucideIcon } from 'lucide-react'
import { cn } from 'ui'
export interface ReplicationState {
isComingUp: boolean
isReplicating: boolean
isFailed: boolean
}
export interface EdgeVisual {
Icon: LucideIcon
// CSS color shared by the icon and the connecting line so they always match.
color: string
opacity: number
dashArray: string
strokeWidth: number
shouldAnimate: boolean
shouldSpin?: boolean
isFilled?: boolean
}
// Picks the icon + line appearance for a replication state. Both the icon and the line are derived
// here from the same state so they always stay in sync. We deliberately don't surface lag: the line
// just communicates whether data is moving, stopped, starting, or broken.
export const getEdgeVisual = ({
isComingUp,
isReplicating,
isFailed,
}: ReplicationState): EdgeVisual => {
if (isFailed) {
return {
Icon: XIcon,
color: 'hsl(var(--destructive-default))',
opacity: 1,
dashArray: '5 5',
shouldAnimate: false,
strokeWidth: 4,
}
}
if (isComingUp) {
return {
Icon: Loader2Icon,
color: 'var(--foreground-light)',
opacity: 1,
dashArray: '5',
strokeWidth: 2,
shouldAnimate: true,
shouldSpin: true,
}
}
if (isReplicating) {
return {
Icon: ArrowRightIcon,
color: 'var(--primary-bright)',
opacity: 1,
dashArray: '5',
strokeWidth: 2,
shouldAnimate: true,
}
}
return {
Icon: SquareIcon,
color: 'var(--foreground-lighter)',
opacity: 0.5,
dashArray: '5 5',
strokeWidth: 2,
shouldAnimate: false,
isFilled: true,
}
}
/** The round icon chip rendered mid-edge, bordered in the edge's color. */
export const EdgeVisualChip = ({ visual }: { visual: EdgeVisual }) => {
const { Icon, color, shouldSpin, isFilled } = visual
return (
<div
className="w-6 h-6 rounded-full flex items-center justify-center border bg-surface-100"
style={{ borderColor: color }}
>
<Icon
size={14}
className={cn(shouldSpin && 'motion-safe:animate-spin')}
style={{ color, fill: isFilled ? color : undefined }}
/>
</div>
)
}