This commit is contained in:
Eduard Gert
2026-05-04 10:14:41 +02:00
parent 670b0f66ac
commit d07f25fc49
24 changed files with 161 additions and 1591 deletions

View File

@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { motion } from "framer-motion";
import { cn } from "@/lib/cn";
import netbirdLogo from "@/assets/netbird.svg";
import netbirdLogo from "@/assets/logos/netbird.svg";
export enum ConnectionState {
Disconnected = "disconnected",
@@ -16,13 +16,13 @@ type StateProps = {
type NetBirdConnectToggleProps = {
state: ConnectionState;
size?: number;
onClick?: () => void;
};
export const NetBirdConnectToggle = ({ state, onClick }: NetBirdConnectToggleProps) => {
export const NetBirdConnectToggle = ({ state, size = 140, onClick }: NetBirdConnectToggleProps) => {
const [visualState, setVisualState] = useState(state);
// Sync with external state when it reaches a settled value
useEffect(() => {
setVisualState(state);
}, [state]);
@@ -36,20 +36,30 @@ export const NetBirdConnectToggle = ({ state, onClick }: NetBirdConnectTogglePro
onClick?.();
};
const padding = size * 0.075;
const borderGap = 2;
const borderInset = padding - borderGap;
const innerSize = size * 0.7;
const logoSize = size * 0.26;
const pingInset = size * 0.075;
return (
<motion.button
className="p-3 rounded-full relative overflow-visible cursor-pointer outline-none border-none bg-transparent"
onClick={handleClick}
whileTap={{ scale: 0.98 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
>
<OuterRing state={visualState} />
<BorderInnerRing state={visualState} />
<InnerRing>
<NetBirdLogo state={visualState} />
<PingRing state={visualState} />
</InnerRing>
</motion.button>
<div>
<motion.button
className="rounded-full relative overflow-visible cursor-pointer outline-none border-none bg-transparent"
style={{ padding }}
onClick={handleClick}
whileTap={{ scale: 0.98 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
>
<OuterRing state={visualState} />
<BorderInnerRing state={visualState} inset={borderInset} />
<InnerRing size={innerSize}>
<NetBirdLogo state={visualState} logoSize={logoSize} />
<PingRing state={visualState} inset={pingInset} />
</InnerRing>
</motion.button>
</div>
);
};
@@ -67,7 +77,7 @@ const OuterRing = ({ state }: StateProps) => {
);
};
const BorderInnerRing = ({ state }: StateProps) => (
const BorderInnerRing = ({ state, inset }: StateProps & { inset: number }) => (
<div
className={cn(
"absolute rounded-full transition-all duration-1000",
@@ -75,19 +85,20 @@ const BorderInnerRing = ({ state }: StateProps) => (
state === ConnectionState.Disconnecting && "bg-conic-netbird animate-spin-slow",
state !== ConnectionState.Connected && state !== ConnectionState.Disconnecting && "bg-neutral-500",
)}
style={{ inset: "12px" }}
style={{ inset }}
/>
);
const InnerRing = ({ children }: { children: React.ReactNode }) => (
const InnerRing = ({ children, size }: { children: React.ReactNode; size: number }) => (
<div
className="h-28 w-28 rounded-full bg-nb-gray flex items-center justify-center relative z-10 m-1"
className="rounded-full bg-nb-gray flex items-center justify-center relative z-10 mx-auto"
style={{ width: size, height: size }}
>
{children}
</div>
);
const NetBirdLogo = ({ state }: StateProps) => {
const NetBirdLogo = ({ state, logoSize }: StateProps & { logoSize: number }) => {
const isConnecting = state === ConnectionState.Connecting;
return (
@@ -98,7 +109,7 @@ const NetBirdLogo = ({ state }: StateProps) => {
<img
src={netbirdLogo}
alt="NetBird"
width={42}
width={logoSize}
className={cn(
"filter transition-all duration-1000",
state === ConnectionState.Disconnected ? "grayscale" : "grayscale-0",
@@ -108,11 +119,12 @@ const NetBirdLogo = ({ state }: StateProps) => {
);
};
const PingRing = ({ state }: StateProps) => (
const PingRing = ({ state, inset }: StateProps & { inset: number }) => (
<span
className={cn(
"block absolute inset-3 border-2 border-netbird rounded-full",
"block absolute border-2 border-netbird rounded-full",
state === ConnectionState.Connecting ? "animate-ping-slow" : "hidden",
)}
style={{ inset }}
/>
);