diff --git a/.changeset/spotty-moons-battle.md b/.changeset/spotty-moons-battle.md new file mode 100644 index 0000000..3c21c65 --- /dev/null +++ b/.changeset/spotty-moons-battle.md @@ -0,0 +1,5 @@ +--- +"@openfun/cunningham-react": minor +--- + +add a popover component diff --git a/packages/react/src/components/Popover/index.scss b/packages/react/src/components/Popover/index.scss new file mode 100644 index 0000000..d6ccb21 --- /dev/null +++ b/packages/react/src/components/Popover/index.scss @@ -0,0 +1,11 @@ +.c__popover { + position: absolute; + display: flex; + width: fit-content; + z-index: 1; + + &:not(&--borderless) { + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + background-color: var(--c--components--forms-datepicker--menu-background-color); + } +} diff --git a/packages/react/src/components/Popover/index.stories.tsx b/packages/react/src/components/Popover/index.stories.tsx new file mode 100644 index 0000000..6998933 --- /dev/null +++ b/packages/react/src/components/Popover/index.stories.tsx @@ -0,0 +1,36 @@ +import React, { useRef, useState } from "react"; + +import { Meta } from "@storybook/react"; +import { Button } from ":/components/Button"; +import { Popover } from ":/components/Popover/index"; + +export default { + title: "Components/Popover", + component: Popover, +} as Meta; + +export const Default = () => { + const [isOpen, setIsOpen] = useState(false); + const parentRef = useRef(null); + + return ( +
+ + {isOpen && ( + setIsOpen(false)}> +
+ I am open +
+
+ )} +
+ ); +}; diff --git a/packages/react/src/components/Popover/index.tsx b/packages/react/src/components/Popover/index.tsx new file mode 100644 index 0000000..db42a4a --- /dev/null +++ b/packages/react/src/components/Popover/index.tsx @@ -0,0 +1,28 @@ +import React, { PropsWithChildren, RefObject } from "react"; +import classNames from "classnames"; +import { useHandleClickOutside } from ":/hooks/useHandleClickOutside"; + +type PopoverProps = PropsWithChildren & { + parentRef: RefObject; + onClickOutside: () => void; + borderless?: boolean; +}; + +export const Popover = ({ + parentRef, + children, + onClickOutside, + borderless = false, +}: PopoverProps) => { + useHandleClickOutside(parentRef, onClickOutside); + + return ( +
+ {children} +
+ ); +}; diff --git a/packages/react/src/hooks/useHandleClickOutside.ts b/packages/react/src/hooks/useHandleClickOutside.ts new file mode 100644 index 0000000..e095be8 --- /dev/null +++ b/packages/react/src/hooks/useHandleClickOutside.ts @@ -0,0 +1,18 @@ +import { RefObject, useEffect } from "react"; + +export const useHandleClickOutside = ( + ref: RefObject, + onClickOutside: any +) => { + useEffect(() => { + const outsideListenerEvent = (event: MouseEvent) => { + if (!ref.current || ref.current.contains(event.target as Node)) { + return; + } + onClickOutside(); + }; + document.addEventListener("click", outsideListenerEvent, true); + return () => + document.removeEventListener("click", outsideListenerEvent, true); + }, []); +}; diff --git a/packages/react/src/index.scss b/packages/react/src/index.scss index 7eb4842..128c26f 100644 --- a/packages/react/src/index.scss +++ b/packages/react/src/index.scss @@ -13,6 +13,7 @@ @import './components/Forms/Switch'; @import './components/Loader'; @import './components/Pagination'; +@import './components/Popover'; * { font-family: var(--c--theme--font--families--base);