Merge branch 'main' of http://192.168.1.163:3000/inmake/stream.graff.tech-new
This commit is contained in:
@@ -23,3 +23,5 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.env
|
||||
|
||||
@@ -30,7 +30,7 @@ function ParticipantItem({ id }: { id: string }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between w-full h-[2.5vw]">
|
||||
<div className="flex items-center gap-[0.833vw]">
|
||||
<Avatar size="medium" />
|
||||
<Avatar size="medium" status="caution" />
|
||||
<div className="flex flex-col gap-[0.278vw]">
|
||||
<span className="button-m">Иван Иванович {id}</span>
|
||||
<span className="caption-s text-[#CCCCCC]">Роль</span>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import clsx from "clsx";
|
||||
import Warning from "../indicators/Warning";
|
||||
import Admin from "../indicators/Admin";
|
||||
import Tooltip from "./Tooltip";
|
||||
|
||||
interface AvatarProps {
|
||||
size: "small" | "medium" | "large";
|
||||
@@ -28,8 +29,16 @@ export default function Avatar({ size, status, src, name }: AvatarProps) {
|
||||
size === "large" && "bottom-[2.5vw] left-[2.5vw]"
|
||||
)}
|
||||
>
|
||||
{status === "caution" && <Warning type="caution" />}
|
||||
{status === "admin" && <Admin />}
|
||||
{status === "caution" && (
|
||||
<Tooltip label="Проблема с соединением" position="top">
|
||||
<Warning type="caution" />
|
||||
</Tooltip>
|
||||
)}
|
||||
{status === "admin" && (
|
||||
<Tooltip label="Администратор" position="top">
|
||||
<Admin />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
import { motion } from "motion/react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
interface TooltipProps {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
position?: "top" | "bottom" | "left" | "right" | "cursor";
|
||||
showDelay?: number;
|
||||
}
|
||||
|
||||
interface TooltipPosition {
|
||||
top?: number | string;
|
||||
left?: number | string;
|
||||
bottom?: number | string;
|
||||
right?: number | string;
|
||||
transform?: string;
|
||||
}
|
||||
|
||||
export default function Tooltip({
|
||||
label,
|
||||
children,
|
||||
position = "top",
|
||||
showDelay = 500,
|
||||
}: TooltipProps) {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [tooltipPosition, setTooltipPosition] = useState<TooltipPosition>({
|
||||
top: 0,
|
||||
left: 0,
|
||||
transform: "none",
|
||||
});
|
||||
const tooltipWrapperRef = useRef<HTMLDivElement>(null);
|
||||
const tooltipRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!tooltipWrapperRef.current) return;
|
||||
const current = tooltipWrapperRef.current;
|
||||
|
||||
current.addEventListener("mouseenter", () => setIsVisible(true));
|
||||
current.addEventListener("mouseleave", () => setIsVisible(false));
|
||||
return () => {
|
||||
current?.removeEventListener("mouseenter", () => setIsVisible(true));
|
||||
current?.removeEventListener("mouseleave", () => setIsVisible(false));
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isVisible || !tooltipRef.current || !tooltipWrapperRef.current) return;
|
||||
const pos = calculatePosition(position, tooltipRef, tooltipWrapperRef);
|
||||
setTooltipPosition(pos);
|
||||
}, [isVisible, position]);
|
||||
|
||||
return (
|
||||
<div ref={tooltipWrapperRef} className="relative hover:cursor-pointer">
|
||||
{children}
|
||||
{isVisible && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: showDelay / 1000, duration: 0 }}
|
||||
>
|
||||
<TooltipContent
|
||||
label={label}
|
||||
position={tooltipPosition || {}}
|
||||
ref={tooltipRef as React.RefObject<HTMLDivElement>}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TooltipContent({
|
||||
label,
|
||||
position,
|
||||
ref,
|
||||
}: {
|
||||
label: string;
|
||||
position: TooltipPosition;
|
||||
ref: React.RefObject<HTMLDivElement>;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={{ ...position, transform: position.transform || "none" }}
|
||||
className="z-[9999] absolute px-[0.694vw] py-[0.417vw] bg-[#00000073] caption-s text-white rounded-[0.556vw] whitespace-nowrap backdrop-blur-[4px] shadow-[0_2px_4px_0_#00000059] cursor-default"
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function calculatePosition(
|
||||
position: TooltipProps["position"],
|
||||
tooltipRef: React.RefObject<HTMLDivElement | null>,
|
||||
tooltipWrapperRef: React.RefObject<HTMLDivElement | null>
|
||||
) {
|
||||
if (!tooltipWrapperRef.current || !tooltipRef.current) return {};
|
||||
|
||||
const wrapperRect = tooltipWrapperRef.current.getBoundingClientRect();
|
||||
const tooltipRect = tooltipRef.current.getBoundingClientRect();
|
||||
const GAP = 8;
|
||||
|
||||
function adjustHorizontal() {
|
||||
let left = wrapperRect.width / 2;
|
||||
let transform = "translateX(-50%)";
|
||||
|
||||
const tooltipLeft = wrapperRect.left + left - tooltipRect.width / 2;
|
||||
const tooltipRight = tooltipLeft + tooltipRect.width;
|
||||
|
||||
// Если не влезает горизонтально, то сдвигаем
|
||||
if (tooltipLeft < GAP) {
|
||||
left = GAP - wrapperRect.left;
|
||||
transform = "none";
|
||||
} else if (tooltipRight > window.innerWidth - GAP) {
|
||||
left = window.innerWidth - GAP - wrapperRect.left - tooltipRect.width;
|
||||
transform = "none";
|
||||
}
|
||||
|
||||
return { left, transform };
|
||||
}
|
||||
|
||||
function adjustVertical() {
|
||||
return { top: wrapperRect.height / 2, transform: "translateY(-50%)" };
|
||||
}
|
||||
|
||||
switch (position) {
|
||||
case "top": {
|
||||
return wrapperRect.bottom + tooltipRect.height + GAP < 0
|
||||
? { bottom: -tooltipRect.height - GAP, ...adjustHorizontal() }
|
||||
: { top: -tooltipRect.height - GAP, ...adjustHorizontal() };
|
||||
}
|
||||
case "bottom": {
|
||||
return wrapperRect.bottom + tooltipRect.height + GAP > window.innerHeight
|
||||
? { top: -tooltipRect.height - GAP, ...adjustHorizontal() }
|
||||
: { bottom: -tooltipRect.height - GAP, ...adjustHorizontal() };
|
||||
}
|
||||
case "left": {
|
||||
return wrapperRect.left - tooltipRect.width - GAP < 0
|
||||
? { right: -tooltipRect.width - GAP, ...adjustVertical() }
|
||||
: { left: -tooltipRect.width - GAP, ...adjustVertical() };
|
||||
}
|
||||
case "right": {
|
||||
return wrapperRect.right + tooltipRect.width + GAP > window.innerWidth
|
||||
? { left: -tooltipRect.width - GAP, ...adjustVertical() }
|
||||
: { right: -tooltipRect.width - GAP, ...adjustVertical() };
|
||||
}
|
||||
case "cursor": {
|
||||
return { transform: "none" };
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
Reference in New Issue
Block a user