diff --git a/index.html b/index.html index 80aec14..07a9876 100644 --- a/index.html +++ b/index.html @@ -16,6 +16,7 @@
+
diff --git a/src/app.jsx b/src/app.jsx index d3303ef..0052846 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -1,5 +1,6 @@ import { BrowserRouter, Route, Routes } from "react-router"; import ModalProvider from "./components/modal/modal-provider"; +import PopoverProvider from "./components/popover/popover-provider"; import DropdownProvider from "./components/text-field/dropdown-input/dropdown-provider"; import ContentLayout from "./layouts/content-layout"; import OnboardingLayout from "./layouts/onboarding-layout"; @@ -13,7 +14,9 @@ import TestPage from "./pages/test-page"; function Provider({ children }) { return ( - {children} + + {children} + ); } diff --git a/src/components/popover/popover-alignment.js b/src/components/popover/popover-alignment.js new file mode 100644 index 0000000..a08fdf5 --- /dev/null +++ b/src/components/popover/popover-alignment.js @@ -0,0 +1,6 @@ +const POPOVER_ALIGNMENT = Object.freeze({ + left: "left", + right: "right", +}); + +export default POPOVER_ALIGNMENT; diff --git a/src/components/popover/popover-context.js b/src/components/popover/popover-context.js new file mode 100644 index 0000000..06b8815 --- /dev/null +++ b/src/components/popover/popover-context.js @@ -0,0 +1,5 @@ +import { createContext } from "react"; + +const PopoverContext = createContext(); + +export default PopoverContext; diff --git a/src/components/popover/popover-provider.jsx b/src/components/popover/popover-provider.jsx new file mode 100644 index 0000000..4529615 --- /dev/null +++ b/src/components/popover/popover-provider.jsx @@ -0,0 +1,10 @@ +import { useState } from "react"; +import PopoverContext from "./popover-context"; + +function PopoverProvider({ children }) { + const [showsPopover, setShowsPopover] = useState(false); + const value = { showsPopover, setShowsPopover }; + return {children}; +} + +export default PopoverProvider; diff --git a/src/components/popover/popover.jsx b/src/components/popover/popover.jsx new file mode 100644 index 0000000..2258612 --- /dev/null +++ b/src/components/popover/popover.jsx @@ -0,0 +1,39 @@ +import { createPortal } from "react-dom"; +import styled from "styled-components"; + +const Container = styled.div` + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; +`; + +function PopoverPortal({ children }) { + return createPortal(children, document.getElementById("popover")); +} + +const StyledPopover = styled.div` + position: absolute; + top: ${({ $position }) => $position.top}px; + ${({ $position }) => ($position.left ? `left: ${$position.left}px` : "")}; + ${({ $position }) => ($position.right ? `right: ${$position.right}px` : "")}; + border-radius: 8px; + border: 1px solid #b6b6b6; + background-color: white; + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); +`; + +function Popover({ isOpen, onClose, position, children }) { + return ( + isOpen && ( + + + {children} + + + ) + ); +} + +export default Popover; diff --git a/src/hooks/use-popover.jsx b/src/hooks/use-popover.jsx new file mode 100644 index 0000000..70cea0d --- /dev/null +++ b/src/hooks/use-popover.jsx @@ -0,0 +1,68 @@ +import { useContext, useState, useEffect } from "react"; +import POPOVER_ALIGNMENT from "../components/popover/popover-alignment"; +import PopoverContext from "../components/popover/popover-context"; + +function calculatePopoverPosition(target, alignment) { + if (!target) { + return { top: 0, left: 0, right: 0 }; + } + + const targetRect = target.getBoundingClientRect(); + const position = { + top: targetRect.bottom + 8, + }; + + switch (alignment) { + case POPOVER_ALIGNMENT.right: + position.right = window.innerWidth - targetRect.right; + break; + default: + position.left = targetRect.left; + break; + } + + return position; +} + +function usePopover() { + const { showsPopover, setShowsPopover } = useContext(PopoverContext); + const [popoverPosition, setPopoverPosition] = useState(); + const [target, setTarget] = useState(); + const [alignment, setAlignment] = useState(POPOVER_ALIGNMENT.left); + + const openPopopver = ({ target, alignment }) => { + updatePopoverPosition(target, alignment); + setTarget(target); + setAlignment(alignment); + setShowsPopover(true); + }; + + const closePopover = () => { + setShowsPopover(false); + }; + + const updatePopoverPosition = (target, alignment) => { + const position = calculatePopoverPosition(target, alignment); + setPopoverPosition(position); + }; + + useEffect(() => { + if (!showsPopover) return; + + function handleWindowResize() { + updatePopoverPosition(target, alignment); + } + + window.addEventListener("resize", handleWindowResize); + return () => window.removeEventListener("resize", handleWindowResize); + }, [showsPopover, target, alignment]); + + return { + popoverPosition, + showsPopover, + openPopopver, + closePopover, + }; +} + +export { usePopover }; diff --git a/src/pages/test-page.jsx b/src/pages/test-page.jsx index eae2ac7..fcaad0f 100644 --- a/src/pages/test-page.jsx +++ b/src/pages/test-page.jsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useRef, useState } from "react"; import styled from "styled-components"; import smileAddImage from "../assets/ic-face-smile-add.svg"; import Avatar from "../components/avatar/avatar"; @@ -17,10 +17,13 @@ import BUTTON_SIZE from "../components/button/button-size"; import ToggleButton from "../components/button/toggle-button"; import Header from "../components/header/header"; import Modal from "../components/modal/modal"; +import Popover from "../components/popover/popover"; +import POPOVER_ALIGNMENT from "../components/popover/popover-alignment"; import TextField from "../components/text-field/text-field"; import TEXT_FIELD_TYPE from "../components/text-field/text-field-type"; import Toast from "../components/toast/toast"; import { useModal } from "../hooks/use-modal"; +import { usePopover } from "../hooks/use-popover"; import { useToast } from "../hooks/use-toast"; const OutlinedHeader = styled(Header)` @@ -51,6 +54,28 @@ function TestPage() { const { showsModal, setShowsModal } = useModal(); const handleModalClick = () => setShowsModal(true); +<<<<<<< HEAD + /* Popover */ + const { popoverPosition, showsPopover, openPopopver, closePopover } = + usePopover(); + const popoverLeftRef = useRef(); + const popoverRightRef = useRef(); + + const handlePopoverLeftClick = () => { + openPopopver({ + target: popoverLeftRef.current, + alignment: POPOVER_ALIGNMENT.left, + }); + }; + const handlePopoverRightClick = () => { + openPopopver({ + target: popoverRightRef.current, + alignment: POPOVER_ALIGNMENT.right, + }); + }; + +======= +>>>>>>> upstream/develop return (
)}
+
+ + + +

This is Popover.

+
+
@@ -224,9 +270,6 @@ function TestPage() { size={AVATAR_SIZE.extraSmall} />
-
);