Refine device traffic chart collapse animation
Build and Deploy Gateway / build-and-push (push) Successful in 19s
Build and Deploy Gateway / deploy (push) Successful in 6s

This commit is contained in:
2026-08-10 00:17:13 +03:00
parent 3a4173db43
commit ec68ba6a7b
7 changed files with 89 additions and 49 deletions
+4 -3
View File
@@ -367,7 +367,8 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
<div className="client-devices-list" aria-live="polite" aria-busy={status === 'loading' || status === 'refreshing'}>
{devices.map((device) => {
const collapsing = Boolean(pinCollapses[device.id]);
const displayPinned = device.pinned || collapsing;
const expanded = device.pinned && !collapsing;
const showDetails = device.pinned || collapsing;
const hasName = Boolean(device.alias || device.hostname);
const title = device.alias || device.hostname || device.ip || 'Неизвестное устройство';
const editing = editingId === device.id;
@@ -409,7 +410,7 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
if (node) deviceNodes.current.set(device.id, node);
else deviceNodes.current.delete(device.id);
}}
className={`client-device is-${device.status}${displayPinned ? ' is-pinned' : ''}${collapsing ? ' is-collapsing' : ''}`}
className={`client-device is-${device.status}${expanded ? ' is-pinned' : ''}${collapsing ? ' is-collapsing' : ''}`}
key={device.id}
>
<span className="client-device-pin-wrap client-tooltip-anchor">
@@ -528,7 +529,7 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
scale={trafficScale}
capacity={snapshot?.trafficHistoryCapacity || device.trafficHistory?.length || 1}
routeLabel={device.appliedPolicy === 'direct' ? 'Напрямую' : 'Gateway'}
pinned={displayPinned}
pinned={showDetails}
collapsing={collapsing}
onCollapseEnd={() => finishPinCollapse(device.id)}
/>
+1 -1
View File
@@ -200,7 +200,7 @@ export function TrafficChart({
'--traffic-chart-mid': `${(100 + TRAFFIC_CHART_HEADROOM) / 2}%`,
} as CSSProperties}
onAnimationEnd={(event: AnimationEvent<HTMLSpanElement>) => {
if (collapsing && event.animationName === 'client-device-chart-collapse') onCollapseEnd?.();
if (collapsing && event.animationName === 'client-device-traffic-plot-collapse') onCollapseEnd?.();
}}
>
{pinned && max > 0n && <span className="client-device-traffic-axis" aria-hidden="true">
+58 -27
View File
@@ -670,13 +670,40 @@
overflow: visible;
}
.client-device.is-pinned .client-device-traffic-chart {
transform-origin: bottom center;
animation: client-device-chart-expand 520ms cubic-bezier(0.16, 1, 0.3, 1) both;
.client-device.is-pinned .client-device-traffic-plot {
transform-origin: top center;
animation: client-device-traffic-plot-expand 520ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.client-device.is-pinned .client-device-traffic-chart.is-collapsing {
animation-name: client-device-chart-collapse;
.client-device.is-collapsing .client-device-traffic-plot {
transform-origin: top center;
animation: client-device-traffic-plot-collapse 520ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.client-device.is-pinned .client-device-traffic-axis,
.client-device.is-pinned .client-device-traffic-grid {
animation: client-device-traffic-detail-in 360ms 80ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.client-device.is-collapsing .client-device-traffic-axis {
position: absolute;
top: 0;
left: 0;
width: 34px;
height: 58px;
}
.client-device.is-collapsing .client-device-traffic-axis,
.client-device.is-collapsing .client-device-traffic-grid {
animation: client-device-traffic-detail-out 260ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.client-device.is-pinned .client-device-traffic-time {
animation: client-device-traffic-time-expand 520ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.client-device.is-collapsing .client-device-traffic-time {
animation: client-device-traffic-time-collapse 520ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.client-device-traffic-axis {
@@ -861,30 +888,34 @@
from { opacity: 0; filter: blur(3px); }
}
@keyframes client-device-chart-expand {
from {
opacity: 0.72;
clip-path: inset(calc(100% - 34px) 0 0);
transform: scaleY(0.48);
}
to {
opacity: 1;
clip-path: inset(0);
transform: scaleY(1);
}
@keyframes client-device-traffic-plot-expand {
from { transform: scaleY(0.3448275862); }
to { transform: scaleY(1); }
}
@keyframes client-device-chart-collapse {
from {
opacity: 1;
clip-path: inset(0);
transform: scaleY(1);
}
to {
opacity: 0.72;
clip-path: inset(calc(100% - 34px) 0 0);
transform: scaleY(0.48);
}
@keyframes client-device-traffic-plot-collapse {
from { transform: scaleY(2.9); }
to { transform: scaleY(1); }
}
@keyframes client-device-traffic-time-expand {
from { transform: translateY(-38px); }
to { transform: translateY(0); }
}
@keyframes client-device-traffic-time-collapse {
from { transform: translateY(38px); }
to { transform: translateY(0); }
}
@keyframes client-device-traffic-detail-in {
from { opacity: 0; transform: translateY(5px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes client-device-traffic-detail-out {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(5px); }
}
.client-device-policy-wrap {
+4
View File
@@ -75,6 +75,10 @@
.client-device-traffic-value > span,
.client-device-traffic-breakdown,
.client-device-traffic-breakdown > span,
.client-device-traffic-plot,
.client-device-traffic-axis,
.client-device-traffic-grid,
.client-device-traffic-time,
.client-device-traffic-lines,
.client-device-traffic-point-tooltip,
.client-device-traffic-chart,