Skip to content

Commit

Permalink
wip: update
Browse files Browse the repository at this point in the history
  • Loading branch information
ElaBosak233 committed Dec 14, 2024
1 parent dd1cc63 commit fff92bb
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 71 deletions.
79 changes: 62 additions & 17 deletions src/components/core/Tooltip/Tooltip.module.scss
Original file line number Diff line number Diff line change
@@ -1,36 +1,81 @@
.root {
position: fixed;
position: absolute;
background: #333;
color: #fff;
padding: 8px 12px;
border-radius: 12px;
font-size: 14px;
white-space: nowrap;
z-index: 9999;
z-index: 1;
transition:
opacity 0.2s ease,
visibility 0.2s ease;

inset: 0px auto auto 0px;
margin: 0;

user-select: none;

&[data-position="top"] {
transform: translate(-50%, -100%);
margin-bottom: var(--tooltip-offset, 8px);
}
&[data-arrow="true"] {
&[data-position="top"] {
&::after {
content: "";
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%, 100%);
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #333;
}
}

&[data-position="bottom"] {
transform: translate(-50%, 0%);
margin-top: var(--tooltip-offset, 8px);
}
&[data-position="bottom"] {
&::before {
content: "";
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -100%);
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 5px solid #333;
}
}

&[data-position="left"] {
transform: translate(-100%, -50%);
margin-right: var(--tooltip-offset, 8px);
}
&[data-position="left"] {
&::before {
content: "";
position: absolute;
top: 50%;
right: 0;
transform: translate(100%, -50%);
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 5px solid #333;
}
}

&[data-position="right"] {
transform: translate(0%, -50%);
margin-left: var(--tooltip-offset, 8px);
&[data-position="right"] {
&::after {
content: "";
position: absolute;
top: 50%;
left: 0;
transform: translate(-100%, -50%);
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-right: 5px solid #333;
}
}
}

&.enter {
Expand Down
130 changes: 85 additions & 45 deletions src/components/core/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CSSTransition } from "react-transition-group";
import { Box, BoxProps } from "../Box";
import clsx from "clsx";
import { createPortal } from "react-dom";
import { useTooltipStore } from "@/stores/tooltip";

export interface TooltipProps extends Omit<BoxProps, "content"> {
content?: React.ReactNode;
Expand All @@ -25,91 +26,130 @@ export function Tooltip(props: TooltipProps) {
content,
position = "top",
offset = 8,
hasArrow = false,
children,
className,
style,
...rest
} = props;

const tooltipStore = useTooltipStore();
const triggerRef = useRef<HTMLElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const isHovered = useHover(triggerRef);

const [tooltipStyle, setTooltipStyle] = useState<CSSProperties>({});

useEffect(() => {
if (!isHovered || !triggerRef.current || !contentRef.current) return;
if (
!isHovered ||
!triggerRef.current ||
!contentRef.current ||
!tooltipStore?.portal
)
return;

const newStyle: CSSProperties = {};
const triggerRect = triggerRef.current.getBoundingClientRect();
const contentRect = contentRef.current.getBoundingClientRect();
const portalRect = tooltipStore.portal.getBoundingClientRect();

const triggerRect = triggerRef?.current?.getBoundingClientRect();
const contentRect = contentRef?.current?.getBoundingClientRect();
const newStyle: CSSProperties = {};

switch (position) {
case "top":
newStyle.top =
triggerRect?.top! - contentRect?.height! - offset;
triggerRect.top -
portalRect.top -
contentRect.height -
offset;
newStyle.left =
triggerRect?.left! +
(triggerRect?.width! - contentRect?.width!) / 2;
triggerRect.left -
portalRect.left +
(triggerRect.width - contentRect.width) / 2;
break;
case "right":
newStyle.top =
triggerRect.top -
portalRect.top +
(triggerRect.height - contentRect.height) / 2;
newStyle.left = triggerRect.right - portalRect.left + offset;
break;
case "bottom":
newStyle.top = triggerRect?.bottom! + offset;
newStyle.top = triggerRect.bottom - portalRect.top + offset;
newStyle.left =
triggerRect?.left! +
(triggerRect?.width! - contentRect?.width!) / 2;
triggerRect.left -
portalRect.left +
(triggerRect.width - contentRect.width) / 2;
break;
case "left":
newStyle.top =
triggerRect?.top! +
(triggerRect?.height! - contentRect?.height!) / 2;
triggerRect.top -
portalRect.top +
(triggerRect.height - contentRect.height) / 2;
newStyle.left =
triggerRect?.left! - contentRect?.width! - offset;
triggerRect.left -
portalRect.left -
contentRect.width -
offset;
break;
case "right":
newStyle.top =
triggerRect?.top! +
(triggerRect?.height! - contentRect?.height!) / 2;
newStyle.left = triggerRect?.right! + offset;
default:
break;
}

newStyle.position = "fixed";
newStyle.top = Math.max(0, Number(newStyle.top) || 0);
newStyle.left = Math.max(0, Number(newStyle.left) || 0);

setTooltipStyle(newStyle);
}, [isHovered, position, offset]);
}, [isHovered, position, offset, tooltipStore?.portal]);

return (
<>
{cloneElement<any>(children, {
ref: triggerRef,
})}
{createPortal(
<CSSTransition
in={isHovered}
unmountOnExit
timeout={300}
nodeRef={contentRef}
classNames={{
enter: styles["enter"],
enterActive: styles["enter-active"],
exit: styles["exit"],
exitActive: styles["exit-active"],
}}
>
<Box
className={clsx(styles["root"], className)}
style={{
...style,
...tooltipStyle,
{tooltipStore?.portal &&
createPortal(
<CSSTransition
in={isHovered}
unmountOnExit
timeout={300}
nodeRef={contentRef}
classNames={{
enter: styles["enter"],
enterActive: styles["enter-active"],
exit: styles["exit"],
exitActive: styles["exit-active"],
}}
ref={contentRef}
{...rest}
>
{content}
</Box>
</CSSTransition>,
document.body
)}
<Box
className={clsx(styles["root"], className, {
[styles["has-arrow"]]: hasArrow,
})}
style={{
...style,
...tooltipStyle,
}}
data-position={position}
data-arrow={hasArrow}
ref={contentRef}
{...rest}
>
{content}
{hasArrow && (
<div
className={clsx(
styles["arrow"],
styles[`arrow-${position}`]
)}
style={{
position: "absolute",
}}
/>
)}
</Box>
</CSSTransition>,
tooltipStore?.portal
)}
</>
);
}
1 change: 1 addition & 0 deletions src/components/widgets/ChallengeCard/ChallengeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export function ChallengeCard(props: ChallengeCard) {
</Box>
<Box className={styles["status"]}>
<Tooltip
hasArrow
content={
<>
{Number(status?.solved_times) > 0 && (
Expand Down
14 changes: 14 additions & 0 deletions src/layouts/Base/Base.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.wrapper {
overflow: auto;
height: 100vh;
width: 100%;
z-index: 0;
transition: all 0.2s ease-in-out;
display: flex;
flex-direction: column;

main {
position: relative;
flex: 1;
}
}
19 changes: 18 additions & 1 deletion src/layouts/Base/Base.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
import { ErrorFallback } from "@/components/utils/ErrorFallback/ErrorFallback";
import { Toaster } from "@/components/widgets/Toaster";
import { useTooltipStore } from "@/stores/tooltip";
import globalRouter from "@/utils/globalRouter";
import { useEffect, useRef } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { Outlet, useNavigate } from "react-router";
import styles from "./Base.module.scss";
import { Box } from "@/components/core";

export function Base() {
const navigate = useNavigate();
globalRouter.navigate = navigate;

const tooltipStore = useTooltipStore();
const ref = useRef<HTMLDivElement>(null);

useEffect(() => {
if (ref.current) {
tooltipStore.setPortal(ref.current);
}
}, [ref.current]);

return (
<>
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Outlet />
<Box className={styles["wrapper"]}>
<main ref={ref}>
<Outlet />
</main>
</Box>
<Toaster />
</ErrorBoundary>
</>
Expand Down
10 changes: 2 additions & 8 deletions src/layouts/Default/Default.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import { Navbar } from "@/components/widgets/Navbar";
import styles from "./Default.module.scss";
import { Outlet } from "react-router";
import { Box } from "@/components/core";

export function Default() {
return (
<>
<main className={styles["main"]}>
<Navbar />
<Box className={styles["outlet"]}>
<Outlet />
</Box>
</main>
<Navbar />
<Outlet />
</>
);
}
11 changes: 11 additions & 0 deletions src/stores/tooltip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { create } from "zustand";

interface TooltipState {
portal?: HTMLDivElement;
setPortal: (el: HTMLDivElement) => void;
}

export const useTooltipStore = create<TooltipState>()((set) => ({
porta: undefined,
setPortal: (el) => set({ portal: el }),
}));

0 comments on commit fff92bb

Please sign in to comment.