mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 07:09:07 +03:00
## What kind of change does this PR introduce?
Bug fix: custom icons now respect their source SVG’s stroke/fill and
stroke-width instead of always getting a global stroke.
## What is the current behavior?
Every custom icon gets `stroke="currentColor"` and `strokeWidth={2}`
from `createSupabaseIcon`, so fill-only logos get an extra stroke and
icons designed at `stroke-width="1"` (e.g. postgres, auth) render too
thick. Call sites and the design-system grid needed workarounds
(`strokeWidth={0}`, `FILL_ONLY_ICONS`, `STROKE_WIDTH_FROM_SOURCE`).
## What is the new behavior?
The icon build reads root SVG attributes (`fill`, `stroke`,
`stroke-width`, etc.) and passes them as per-icon defaults. Fill-only
icons (chatgpt, claude, axiom, last9) have `stroke="none"` in source and
render with no stroke; stroke icons keep their source stroke-width (e.g.
postgres at 1). No `strokeWidth={0}` or allowlists needed at call sites
or in the icon grid.
| Before | After |
| --- | --- |
| <img width="1826" height="552" alt="CleanShot 2026-03-05 at 10 53
31@2x-3D63CEF0-1132-44F5-A382-730346432F1E"
src="https://github.com/user-attachments/assets/9caf73fa-351b-4ea2-a420-b3339f934742"
/> | <img width="1814" height="552" alt="CleanShot 2026-03-05 at 10 53
40@2x-514FC8BD-D30F-4D23-B209-0ECD6D13A184"
src="https://github.com/user-attachments/assets/936ea5a5-dae0-413f-8545-b90a502cea69"
/> |
## Additional context
- Added `stroke="none"` to the four fill-only source SVGs;
sentry/grafana/otlp/datadog already had `stroke-width="0"`.
- `createSupabaseIcon` only applies `color`/`strokeWidth` when the
consumer passes them, so `svgDefaults` from the build are used
otherwise.
- Removed workarounds from `icons.tsx` and GuidesSidebar; updated
icons.mdx (defaults + fill-only guidance).
122 lines
3.1 KiB
TypeScript
122 lines
3.1 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
import { cn } from 'ui'
|
|
import { Datadog, Grafana, Sentry } from 'icons'
|
|
import { BracesIcon } from 'lucide-react'
|
|
|
|
export const AnimatedLogos = () => {
|
|
const [currIndex, setCurrIndex] = useState(0)
|
|
const timer = 2500
|
|
const iconSize = 36
|
|
|
|
const logos = [
|
|
{
|
|
id: 'datadog',
|
|
name: 'Datadog',
|
|
icon: <Datadog fill="currentColor" size={iconSize} />,
|
|
},
|
|
{
|
|
id: 'loki',
|
|
name: 'Loki',
|
|
icon: <Grafana fill="currentColor" size={iconSize} />,
|
|
},
|
|
{ id: 'https', name: 'HTTPS', icon: <BracesIcon size={iconSize} /> },
|
|
{
|
|
id: 'sentry',
|
|
name: 'Sentry',
|
|
icon: <Sentry fill="currentColor" size={iconSize} />,
|
|
},
|
|
]
|
|
|
|
useEffect(() => {
|
|
const interval = setInterval(() => {
|
|
setCurrIndex((prev) => (prev + 1) % logos.length)
|
|
}, timer)
|
|
return () => clearInterval(interval)
|
|
}, [logos.length])
|
|
|
|
const getPreviousIndex = () => (currIndex - 1 + logos.length) % logos.length
|
|
const getNextIndex = () => (currIndex + 1) % logos.length
|
|
|
|
const getPosition = (index: number) => {
|
|
if (index === currIndex) return 'center'
|
|
if (index === getPreviousIndex()) return 'left'
|
|
if (index === getNextIndex()) return 'right'
|
|
return 'hidden'
|
|
}
|
|
|
|
const logoVariants = {
|
|
hidden: {
|
|
x: 'calc(-50% + 120px)',
|
|
y: '-50%',
|
|
scale: 0.6,
|
|
opacity: 0,
|
|
filter: 'blur(1px)',
|
|
},
|
|
right: {
|
|
x: 'calc(-50% + 80px)',
|
|
y: '-50%',
|
|
scale: 0.8,
|
|
opacity: 0.5,
|
|
zIndex: 2,
|
|
filter: 'blur(1px)',
|
|
},
|
|
center: {
|
|
x: '-50%',
|
|
y: '-50%',
|
|
scale: 1,
|
|
opacity: 1,
|
|
zIndex: 3,
|
|
filter: 'blur(0px)',
|
|
},
|
|
left: {
|
|
x: 'calc(-50% - 80px)',
|
|
y: '-50%',
|
|
scale: 0.8,
|
|
opacity: 0.5,
|
|
zIndex: 2,
|
|
filter: 'blur(1px)',
|
|
},
|
|
exit: {
|
|
x: 'calc(-50% - 120px)',
|
|
y: '-50%',
|
|
scale: 0.6,
|
|
opacity: 0,
|
|
filter: 'blur(1px)',
|
|
},
|
|
}
|
|
|
|
const visibleIndices = [getPreviousIndex(), currIndex, getNextIndex()]
|
|
|
|
return (
|
|
<div className="relative w-48 h-32 mx-auto mb-8 overflow-hidden">
|
|
<AnimatePresence initial={false}>
|
|
{logos.map((logo, index) => {
|
|
if (!visibleIndices.includes(index)) return null
|
|
|
|
const position = getPosition(index)
|
|
const isCenter = index === currIndex
|
|
|
|
return (
|
|
<motion.div
|
|
key={logo.id}
|
|
className={cn(
|
|
'absolute top-1/2 left-1/2 flex items-center justify-center rounded-lg',
|
|
isCenter ? 'w-24 h-24' : 'w-20 h-20'
|
|
)}
|
|
variants={logoVariants}
|
|
initial="hidden"
|
|
animate={position}
|
|
exit="exit"
|
|
transition={{ duration: 0.5, ease: 'easeInOut' }}
|
|
>
|
|
<span>{logo.icon}</span>
|
|
</motion.div>
|
|
)
|
|
})}
|
|
</AnimatePresence>
|
|
<div className="absolute -inset-4 bg-gradient-to-r from-background-surface-75 via-transparent to-background-surface-75 z-40" />
|
|
</div>
|
|
)
|
|
}
|