Skip to content

Commit

Permalink
wip: popover
Browse files Browse the repository at this point in the history
  • Loading branch information
ElaBosak233 committed Nov 5, 2024
1 parent 2686a47 commit 6d047cc
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 152 deletions.
1 change: 1 addition & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { router } from "@/routers";

export default function App() {
const themeStore = useThemeStore();

useEffect(() => {
document.documentElement.setAttribute(
"data-theme",
Expand Down
33 changes: 0 additions & 33 deletions src/components/core/Dropdown/Dropdown.module.scss

This file was deleted.

52 changes: 0 additions & 52 deletions src/components/core/Dropdown/Dropdown.tsx

This file was deleted.

1 change: 0 additions & 1 deletion src/components/core/Dropdown/index.ts

This file was deleted.

31 changes: 31 additions & 0 deletions src/components/core/Popover/Popover.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.root {
position: relative;
width: fit-content;
}

.trigger {
display: inline-block;
}

.content {
position: absolute;
z-index: 1;

&.enter {
opacity: 0;
}

&.enter-active {
opacity: 1;
transition: all 200ms ease-in-out;
}

&.exit {
opacity: 1;
}

&.exit-active {
opacity: 0;
transition: all 200ms ease-in-out;
}
}
124 changes: 124 additions & 0 deletions src/components/core/Popover/Popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { cloneElement, useEffect, useMemo, useRef, useState } from "react";
import styles from "./Popover.module.scss";
import { CSSTransition } from "react-transition-group";

export interface PopoverProps {
/**
* The content of the popover.
*/
children: React.ReactElement;
/**
* The offset of the popover.
*/
offsetY?: number;
/**
* The offset of the popover.
*/
offsetX?: number;
/**
* The trigger of the popover.
*/
content: React.ReactElement;
/**
* Whether the popover is opened or not.
*/
opened: boolean;
/**
* The callback function when the popover is opened.
*/
onChange: (opened: boolean) => void;
}

export function Popover(props: PopoverProps) {
const {
children,
offsetY = 10,
offsetX = 0,
content,
opened,
onChange,
} = props;

const contentRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLElement>(null);

const [position, setPosition] = useState<"top" | "bottom">("bottom");

useEffect(() => {
if (triggerRef.current && opened) {
const triggerRect = triggerRef.current.getBoundingClientRect();
const viewportHeight = window.innerHeight;

const spaceBelow = viewportHeight - triggerRect.bottom - offsetY;
const spaceAbove = triggerRect.top - offsetY;

if (
spaceBelow < (contentRef?.current?.offsetHeight || 0) &&
spaceAbove > (contentRef?.current?.offsetHeight || 0)
) {
setPosition("top");
} else {
setPosition("bottom");
}
}
}, [opened, offsetY]);

const positionStyle = useMemo(() => {
switch (position) {
case "top":
return {
bottom: `calc(100% + ${offsetY}px)`,
right: `${offsetX}px`,
};
case "bottom":
default:
return {
top: `calc(100% + ${offsetY}px)`,
right: `${offsetX}px`,
};
}
}, [position, offsetY, offsetX]);

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
contentRef.current &&
!contentRef.current.contains(event.target as Node) &&
triggerRef &&
!triggerRef.current?.contains(event.target as Node)
) {
onChange(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);

return (
<div className={styles["root"]}>
{cloneElement(children, { ref: triggerRef })}
<CSSTransition
in={opened}
timeout={300}
unmountOnExit
nodeRef={contentRef}
classNames={{
enter: styles["enter"],
enterActive: styles["enter-active"],
exit: styles["exit"],
exitActive: styles["exit-active"],
}}
>
<div
className={styles["content"]}
style={positionStyle}
ref={contentRef}
>
{content}
</div>
</CSSTransition>
</div>
);
}
1 change: 1 addition & 0 deletions src/components/core/Popover/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Popover, type PopoverProps } from "./Popover";
2 changes: 1 addition & 1 deletion src/components/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ export * from "./Card";
export * from "./Checkbox";
export * from "./DatetimePicker";
export * from "./Dialog";
export * from "./Dropdown";
export * from "./Switch";
export * from "./TextInput";
export * from "./Toast";
export * from "./Tooltip";
export * from "./Popover";
// export * from "./Icon";
// export * from "./Input";
// export * from "./Link";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ $grid-color: var(--challenge-card-grid-color);
.root {
width: fit-content;
position: relative;
border-radius: 16px;
border-radius: 12px;

box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);

Expand Down Expand Up @@ -38,7 +38,7 @@ $grid-color: var(--challenge-card-grid-color);
height: 9rem;

background-color: $bg-color;
border-radius: 16px;
border-radius: 12px;
border: 2.75px solid $border-color;
padding: 10px;
transition: all 200ms ease-in-out;
Expand Down
54 changes: 28 additions & 26 deletions src/components/widgets/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import useThemeColor from "@/hooks/useThemeColor";
import { CSSProperties, useRef, useState } from "react";
import chroma from "chroma-js";
import { Link, useLocation } from "react-router-dom";
import { Avatar, Dropdown } from "@/components/core";
import { Avatar, Popover } from "@/components/core";

export function Navbar() {
const darkMode = useThemeStore.getState().darkMode;
Expand Down Expand Up @@ -102,36 +102,38 @@ export function Navbar() {
</>
)}
</button>
<div
className={styles["avatar"]}
onClick={() => {
setDropdownMenuOpen((r) => !r);
}}
ref={dropdownMenuButtonRef}
>
<Avatar
src={"https://e23.dev/Ella_Avatar.png"}
fallback={<>E</>}
color={"transparent"}
/>
</div>
<Dropdown
open={dropdownMenuOpen}
onClose={() => setDropdownMenuOpen(false)}
targetRef={dropdownMenuButtonRef}
<Popover
opened={dropdownMenuOpen}
onChange={setDropdownMenuOpen}
offsetY={20}
content={
<div
style={{
width: "10rem",
height: "100px",
borderRadius: "8px",
backgroundColor: "var(--bg-2-color)",
zIndex: 1000,
}}
>
1
</div>
}
>
<div
style={{
width: "10rem",
height: "100px",
borderRadius: "8px",
backgroundColor: "var(--bg-2-color)",
zIndex: 1000,
className={styles["avatar"]}
onClick={() => {
setDropdownMenuOpen((r) => !r);
}}
ref={dropdownMenuButtonRef}
>
1
<Avatar
src={"https://e23.dev/Ella_Avatar.png"}
fallback={<>E</>}
color={"transparent"}
/>
</div>
</Dropdown>
</Popover>
</div>
</div>
</div>
Expand Down
6 changes: 5 additions & 1 deletion src/layouts/Base/Base.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Toaster } from "@/components/widgets/Toaster";
import { Outlet } from "react-router-dom";
import globalRouter from "@/utils/globalRouter";
import { Outlet, useNavigate } from "react-router-dom";

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

return (
<>
<Outlet />
Expand Down
Loading

0 comments on commit 6d047cc

Please sign in to comment.