feat: Update
This commit is contained in:
@@ -6,10 +6,24 @@ class LanguageStore {
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const storedLanguage = window.localStorage.getItem("appLanguage");
|
||||
if (
|
||||
storedLanguage &&
|
||||
["ru", "en", "zh"].includes(storedLanguage.toLowerCase())
|
||||
) {
|
||||
this.language = storedLanguage.toLowerCase() as Language;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setLanguage = (language: Language) => {
|
||||
this.language = language;
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.localStorage.setItem("appLanguage", language);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
171
src/shared/ui/AnimatedCircleButton.tsx
Normal file
171
src/shared/ui/AnimatedCircleButton.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import { forwardRef } from "react";
|
||||
import { Button, ButtonProps, CircularProgress } from "@mui/material";
|
||||
import { alpha, keyframes, styled } from "@mui/material/styles";
|
||||
import type { Theme } from "@mui/material/styles";
|
||||
|
||||
type AnimatedCircleButtonProps = ButtonProps & {
|
||||
disableAnimation?: boolean;
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
type StyledButtonProps = AnimatedCircleButtonProps & { theme: Theme };
|
||||
|
||||
const loadingPulse = keyframes`
|
||||
0% {
|
||||
transform: translate(-50%, -50%) scale(0.6);
|
||||
opacity: 0.35;
|
||||
}
|
||||
50% {
|
||||
transform: translate(-50%, -50%) scale(1.45);
|
||||
opacity: 0.15;
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) scale(0.6);
|
||||
opacity: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button, {
|
||||
shouldForwardProp: (prop) =>
|
||||
prop !== "disableAnimation" && prop !== "loading",
|
||||
})<AnimatedCircleButtonProps>((props: StyledButtonProps) => {
|
||||
const {
|
||||
theme,
|
||||
disableAnimation = false,
|
||||
color,
|
||||
variant = "text",
|
||||
disabled = false,
|
||||
loading = false,
|
||||
} = props;
|
||||
|
||||
const shouldAnimate = !disableAnimation && (!disabled || loading);
|
||||
const pointerBlocked = loading;
|
||||
|
||||
const paletteMainMap: Record<string, string> = {
|
||||
primary: theme.palette.primary.main,
|
||||
secondary: theme.palette.secondary.main,
|
||||
error: theme.palette.error.main,
|
||||
warning: theme.palette.warning.main,
|
||||
info: theme.palette.info.main,
|
||||
success: theme.palette.success.main,
|
||||
inherit: theme.palette.primary.main,
|
||||
};
|
||||
|
||||
const paletteMain =
|
||||
(color && paletteMainMap[String(color)]) ?? theme.palette.primary.main;
|
||||
|
||||
const pulseColor =
|
||||
variant === "outlined" || variant === "text"
|
||||
? alpha(paletteMain, 0.18)
|
||||
: alpha(paletteMain, 0.3);
|
||||
|
||||
return {
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
borderRadius: 5,
|
||||
zIndex: 0,
|
||||
transition: "transform 0.2s ease, box-shadow 0.2s ease",
|
||||
pointerEvents: pointerBlocked ? "none" : undefined,
|
||||
"&::after": shouldAnimate
|
||||
? {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
width: "12px",
|
||||
height: "12px",
|
||||
backgroundColor: pulseColor,
|
||||
borderRadius: "50%",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
pointerEvents: "none",
|
||||
zIndex: 0,
|
||||
...(loading
|
||||
? {
|
||||
opacity: 0.35,
|
||||
transform: "translate(-50%, -50%) scale(0.6)",
|
||||
animation: `${loadingPulse} 1.2s ease-in-out infinite`,
|
||||
}
|
||||
: {
|
||||
opacity: 0,
|
||||
transform: "translate(-50%, -50%) scale(0)",
|
||||
transition: "transform 0.45s ease, opacity 0.45s ease",
|
||||
}),
|
||||
}
|
||||
: {},
|
||||
...(loading
|
||||
? {}
|
||||
: {
|
||||
"&:hover": {
|
||||
transform: "translateY(-1px)",
|
||||
boxShadow: theme.shadows[4],
|
||||
},
|
||||
"&:hover::after": shouldAnimate
|
||||
? {
|
||||
transform: "translate(-50%, -50%) scale(15)",
|
||||
opacity: 1,
|
||||
}
|
||||
: {},
|
||||
"&:active": {
|
||||
transform: "translateY(0)",
|
||||
boxShadow: theme.shadows[2],
|
||||
},
|
||||
"&:active::after": shouldAnimate
|
||||
? {
|
||||
transform: "translate(-50%, -50%) scale(18)",
|
||||
opacity: 0.4,
|
||||
}
|
||||
: {},
|
||||
}),
|
||||
"&.Mui-disabled": {
|
||||
boxShadow: "none",
|
||||
transform: "none",
|
||||
...(loading && shouldAnimate
|
||||
? {}
|
||||
: {
|
||||
"&::after": {
|
||||
opacity: 0,
|
||||
},
|
||||
}),
|
||||
},
|
||||
...(disabled && {
|
||||
boxShadow: "none",
|
||||
transform: "none",
|
||||
}),
|
||||
"& > *": {
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const AnimatedCircleButton = forwardRef<
|
||||
HTMLButtonElement,
|
||||
AnimatedCircleButtonProps
|
||||
>((props, ref) => {
|
||||
const {
|
||||
loading = false,
|
||||
disabled,
|
||||
children,
|
||||
startIcon,
|
||||
endIcon,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const effectiveStartIcon = loading ? (
|
||||
<CircularProgress size={16} color="inherit" />
|
||||
) : (
|
||||
startIcon
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
ref={ref}
|
||||
loading={loading}
|
||||
disabled={loading ? true : disabled}
|
||||
startIcon={effectiveStartIcon}
|
||||
endIcon={loading ? undefined : endIcon}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</StyledButton>
|
||||
);
|
||||
});
|
||||
@@ -2,3 +2,4 @@ export * from "./TabPanel";
|
||||
export * from "./BackButton";
|
||||
export * from "./Modal";
|
||||
export * from "./CoordinatesInput";
|
||||
export * from "./AnimatedCircleButton";
|
||||
|
||||
Reference in New Issue
Block a user