Refine Harbor brand mode hover animation
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 14s
Build and Deploy Gateway / deploy (push) Successful in 1s

This commit is contained in:
2026-07-11 16:02:49 +03:00
parent bfc85c3056
commit 9efd446d4e
2 changed files with 32 additions and 13 deletions

View File

@@ -74,12 +74,27 @@ function DurationPart({ name, children }) {
}
function HarborBrand({ isGateway, gatewayAvailable, gatewayDirect, busy, onSetGatewayAuto }) {
const [modeAnimating, setModeAnimating] = useState(false);
const stopModeAnimationRef = useRef(false);
const product = isGateway ? 'Gateway' : 'Connect';
const switchable = !isGateway && gatewayAvailable;
const label = gatewayDirect
? 'Игнорировать Harbor Gateway и использовать локальный VPN'
: 'Использовать обнаруженный Harbor Gateway';
function startModeAnimation() {
stopModeAnimationRef.current = false;
setModeAnimating(true);
}
function finishModeAnimation() {
if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
setModeAnimating(false);
return;
}
stopModeAnimationRef.current = true;
}
const content = <div className="harbor-brand-content">
<svg viewBox="0 0 32 32" aria-hidden="true">
<circle cx="16" cy="6" r="3" />
@@ -111,12 +126,22 @@ function HarborBrand({ isGateway, gatewayAvailable, gatewayDirect, busy, onSetGa
return (
<div className={`harbor-brand is-${product.toLowerCase()}${switchable ? ` is-switchable${gatewayDirect ? ' is-gateway-active' : ''}` : ''}`}>
{switchable ? <button
className="harbor-brand-control"
className={`harbor-brand-control${modeAnimating ? ' is-mode-animating' : ''}`}
type="button"
aria-label={label}
aria-describedby="harbor-mode-tooltip"
aria-pressed={gatewayDirect}
disabled={busy}
onPointerEnter={startModeAnimation}
onPointerLeave={finishModeAnimation}
onFocus={startModeAnimation}
onBlur={finishModeAnimation}
onAnimationIteration={(event) => {
if (event.animationName === 'harbor-mode-float-front' && stopModeAnimationRef.current) {
stopModeAnimationRef.current = false;
setModeAnimating(false);
}
}}
onClick={() => onSetGatewayAuto(!gatewayDirect)}
>
{content}

View File

@@ -246,23 +246,19 @@ p {
transform: none;
}
.harbor-brand-control:hover .harbor-mode-connect,
.harbor-brand-control:focus-visible .harbor-mode-connect {
.harbor-brand-control.is-mode-animating .harbor-mode-connect {
animation: harbor-mode-float-front 1500ms ease-in-out infinite;
}
.harbor-brand-control:hover .harbor-mode-gateway,
.harbor-brand-control:focus-visible .harbor-mode-gateway {
.harbor-brand-control.is-mode-animating .harbor-mode-gateway {
animation: harbor-mode-float-back 1500ms ease-in-out infinite;
}
.harbor-brand.is-gateway-active .harbor-brand-control:hover .harbor-mode-connect,
.harbor-brand.is-gateway-active .harbor-brand-control:focus-visible .harbor-mode-connect {
.harbor-brand.is-gateway-active .harbor-brand-control.is-mode-animating .harbor-mode-connect {
animation-name: harbor-mode-float-back;
}
.harbor-brand.is-gateway-active .harbor-brand-control:hover .harbor-mode-gateway,
.harbor-brand.is-gateway-active .harbor-brand-control:focus-visible .harbor-mode-gateway {
.harbor-brand.is-gateway-active .harbor-brand-control.is-mode-animating .harbor-mode-gateway {
animation-name: harbor-mode-float-front;
}
@@ -357,13 +353,11 @@ p {
opacity: 1;
}
.harbor-brand-control:hover .harbor-mode-swap .is-connect path,
.harbor-brand-control:focus-visible .harbor-mode-swap .is-connect path {
.harbor-brand-control.is-mode-animating .harbor-mode-swap .is-connect path {
animation: harbor-arrow-float-right 1500ms ease-in-out infinite;
}
.harbor-brand-control:hover .harbor-mode-swap .is-gateway path,
.harbor-brand-control:focus-visible .harbor-mode-swap .is-gateway path {
.harbor-brand-control.is-mode-animating .harbor-mode-swap .is-gateway path {
animation: harbor-arrow-float-left 1500ms ease-in-out infinite;
}