feat(ui): animate navigation and operation icons
This commit is contained in:
@@ -18,56 +18,192 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.portfolioIcon {
|
||||
color: var(--mantine-color-blue-7);
|
||||
}
|
||||
|
||||
.cardsIcon {
|
||||
color: var(--mantine-color-grape-7);
|
||||
|
||||
& svg {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.rubles {
|
||||
pointer-events: none;
|
||||
color: var(--mantine-color-green-8);
|
||||
stroke: currentColor;
|
||||
|
||||
& path {
|
||||
opacity: 0;
|
||||
--flight-x: -7px;
|
||||
--flight-rotation: -20deg;
|
||||
--flight-delay: 0ms;
|
||||
}
|
||||
|
||||
& path:nth-child(2) {
|
||||
--flight-x: 0px;
|
||||
--flight-rotation: 8deg;
|
||||
--flight-delay: 120ms;
|
||||
}
|
||||
|
||||
& path:nth-child(3) {
|
||||
--flight-x: 7px;
|
||||
--flight-rotation: 24deg;
|
||||
--flight-delay: 240ms;
|
||||
}
|
||||
}
|
||||
|
||||
.stocksIcon {
|
||||
color: var(--mantine-color-green-8);
|
||||
}
|
||||
|
||||
.bondsIcon {
|
||||
color: var(--mantine-color-orange-8);
|
||||
}
|
||||
|
||||
.settingsIcon {
|
||||
color: var(--mantine-color-gray-7);
|
||||
}
|
||||
|
||||
.portfolioRuble {
|
||||
stroke: var(--mantine-color-green-8);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.stockArrow {
|
||||
transform-origin: 21px 5px;
|
||||
}
|
||||
|
||||
.topSlider,
|
||||
.bottomSlider {
|
||||
fill: var(--mantine-color-body);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.drawStroke {
|
||||
.navLink {
|
||||
transition: background-color 180ms ease-in-out;
|
||||
}
|
||||
|
||||
.drawStroke,
|
||||
.couponLines path {
|
||||
stroke-dasharray: 1;
|
||||
}
|
||||
|
||||
.topSlider {
|
||||
--slider-offset: -3px;
|
||||
--slider-offset: 3px;
|
||||
--slider-delay: 0ms;
|
||||
}
|
||||
|
||||
.bottomSlider {
|
||||
--slider-offset: 3px;
|
||||
--slider-offset: -3px;
|
||||
--slider-delay: 160ms;
|
||||
}
|
||||
|
||||
.navLink:focus-visible {
|
||||
.portfolioIcon {
|
||||
animation: portfolio-sway 650ms ease-in-out;
|
||||
.couponLines path:nth-child(2) {
|
||||
--line-delay: 140ms;
|
||||
}
|
||||
|
||||
.couponLines path:nth-child(3) {
|
||||
--line-delay: 280ms;
|
||||
}
|
||||
|
||||
svg[data-icon-animating] {
|
||||
.rubles path {
|
||||
animation: ruble-fly 800ms ease-out var(--flight-delay) both;
|
||||
}
|
||||
.portfolioHandle {
|
||||
animation: handle-lift 1200ms ease-in-out;
|
||||
}
|
||||
.portfolioLid {
|
||||
animation: lid-open 1200ms ease-in-out;
|
||||
}
|
||||
.portfolioRuble {
|
||||
animation: portfolio-ruble-reveal 1200ms ease-in-out;
|
||||
}
|
||||
.stockArrow {
|
||||
animation: arrow-pop 300ms ease-out 400ms both;
|
||||
}
|
||||
.couponLines path {
|
||||
animation: stroke-draw 300ms ease-out var(--line-delay, 0ms) both;
|
||||
}
|
||||
.drawStroke {
|
||||
animation: stroke-draw 650ms ease-in-out;
|
||||
animation: stroke-draw 450ms ease-out;
|
||||
}
|
||||
:is(.topSlider, .bottomSlider) {
|
||||
animation: slider-slide 650ms ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.navLink:hover {
|
||||
.portfolioIcon {
|
||||
animation: portfolio-sway 650ms ease-in-out;
|
||||
}
|
||||
.drawStroke {
|
||||
animation: stroke-draw 650ms ease-in-out;
|
||||
}
|
||||
:is(.topSlider, .bottomSlider) {
|
||||
animation: slider-slide 650ms ease-in-out;
|
||||
}
|
||||
animation: slider-slide 650ms ease-in-out var(--slider-delay) both;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes portfolio-sway {
|
||||
@keyframes ruble-fly {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate(0, 4px);
|
||||
}
|
||||
20%,
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translate(var(--flight-x), -10px) rotate(var(--flight-rotation));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes handle-lift {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0);
|
||||
transform: translateY(0);
|
||||
}
|
||||
30% {
|
||||
transform: translateY(-1px) rotate(-10deg);
|
||||
20% {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
65% {
|
||||
transform: rotate(7deg);
|
||||
40%,
|
||||
75% {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes lid-open {
|
||||
0%,
|
||||
15%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
40%,
|
||||
75% {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes portfolio-ruble-reveal {
|
||||
0%,
|
||||
25%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
40%,
|
||||
75% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes arrow-pop {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.7);
|
||||
}
|
||||
60% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+38
-13
@@ -2,6 +2,7 @@ import { AppShell as MantineAppShell, NavLink, Text } from "@mantine/core";
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Outlet, NavLink as RouterNavLink, useLocation } from "react-router";
|
||||
import { bindIconAnimation } from "../lib";
|
||||
import classes from "./AppShell.module.css";
|
||||
|
||||
type AppShellProps = {
|
||||
@@ -18,52 +19,75 @@ const AppShell: FC<AppShellProps> = (props) => {
|
||||
label: t("navigation.accounts"),
|
||||
iconClassName: classes.portfolioIcon,
|
||||
icon: (
|
||||
<path d="M8 6V4h8v2M3 10h18M9 10v3h6v-3M5 6h14a2 2 0 0 1 2 2v11H3V8a2 2 0 0 1 2-2Z" />
|
||||
<>
|
||||
<path d="M3 10v9h18v-9" />
|
||||
<path className={classes.portfolioHandle} d="M8 6V4h8v2" />
|
||||
<path
|
||||
className={classes.portfolioLid}
|
||||
d="M3 10V8a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2H3Zm6 0v3h6v-3"
|
||||
/>
|
||||
<path
|
||||
className={classes.portfolioRuble}
|
||||
d="M10 18v-7h3a2 2 0 0 1 0 4H9m0 1.5h4"
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
to: "/cards",
|
||||
label: t("navigation.cards"),
|
||||
icon: <path d="M3 5h18v14H3V5Zm0 4h18M6 15h4" />,
|
||||
iconClassName: classes.cardsIcon,
|
||||
icon: (
|
||||
<>
|
||||
<path d="M3 5h18v14H3V5Zm0 4h18M6 15h4" />
|
||||
<g className={classes.rubles}>
|
||||
<path d="M10 12V4h3a2 2 0 0 1 0 4H9m0 2h4" />
|
||||
<path d="M10 12V4h3a2 2 0 0 1 0 4H9m0 2h4" />
|
||||
<path d="M10 12V4h3a2 2 0 0 1 0 4H9m0 2h4" />
|
||||
</g>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
to: "/stocks",
|
||||
label: t("navigation.stocks"),
|
||||
iconClassName: classes.stocksIcon,
|
||||
icon: (
|
||||
<>
|
||||
<path d="M3 3v18h18" />
|
||||
<path
|
||||
className={classes.drawStroke}
|
||||
pathLength={1}
|
||||
d="M7 14l5-5 4 3 5-7M16 5h5v5"
|
||||
d="M7 14l5-5 4 3 5-7"
|
||||
/>
|
||||
<path className={classes.stockArrow} d="M16 5h5v5" />
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
to: "/bonds",
|
||||
label: t("navigation.bonds"),
|
||||
iconClassName: classes.bondsIcon,
|
||||
icon: (
|
||||
<>
|
||||
<path d="M6 3h12v18l-3-2-3 2-3-2-3 2V3Z" />
|
||||
<path
|
||||
className={classes.drawStroke}
|
||||
pathLength={1}
|
||||
d="M9 7h6M9 11h6M9 15h3"
|
||||
/>
|
||||
<g className={classes.couponLines}>
|
||||
<path pathLength={1} d="M9 7h6" />
|
||||
<path pathLength={1} d="M9 11h6" />
|
||||
<path pathLength={1} d="M9 15h3" />
|
||||
</g>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
to: "/settings",
|
||||
label: t("navigation.settings"),
|
||||
iconClassName: classes.settingsIcon,
|
||||
icon: (
|
||||
<>
|
||||
<path className={classes.topSlider} d="M4 7h9m4 0h3M13 4v6h4V4h-4Z" />
|
||||
<path
|
||||
className={classes.bottomSlider}
|
||||
d="M4 17h3m4 0h9M7 14v6h4v-6H7Z"
|
||||
/>
|
||||
<path d="M4 7h16M4 17h16" />
|
||||
<path className={classes.topSlider} d="M10 4v6h4V4h-4Z" />
|
||||
<path className={classes.bottomSlider} d="M10 14v6h4v-6h-4Z" />
|
||||
</>
|
||||
),
|
||||
},
|
||||
@@ -91,6 +115,7 @@ const AppShell: FC<AppShellProps> = (props) => {
|
||||
className={`${classes.navIcon} ${item.iconClassName ?? ""}`}
|
||||
>
|
||||
<svg
|
||||
ref={bindIconAnimation}
|
||||
width={20}
|
||||
height={20}
|
||||
viewBox="0 0 24 24"
|
||||
|
||||
Reference in New Issue
Block a user