From c637e2d63109e12886b6f688c643146707967c7a Mon Sep 17 00:00:00 2001 From: MAZE Date: Sun, 29 Oct 2023 18:12:06 +0330 Subject: [PATCH] feat: create reusable tooltip --- src/components/buttons/unselect/unselect.tsx | 89 +++++---------- src/components/tooltip/index.ts | 1 + src/components/tooltip/tooltip.module.css | 8 ++ src/components/tooltip/tooltip.tsx | 111 +++++++++++++++++++ src/lib/motion.ts | 4 +- 5 files changed, 149 insertions(+), 64 deletions(-) create mode 100644 src/components/tooltip/index.ts create mode 100644 src/components/tooltip/tooltip.module.css create mode 100644 src/components/tooltip/tooltip.tsx diff --git a/src/components/buttons/unselect/unselect.tsx b/src/components/buttons/unselect/unselect.tsx index 0a2a37e..2671585 100644 --- a/src/components/buttons/unselect/unselect.tsx +++ b/src/components/buttons/unselect/unselect.tsx @@ -1,19 +1,8 @@ -import { useState } from 'react'; import { BiUndo, BiTrash } from 'react-icons/bi/index'; -import { - useFloating, - autoUpdate, - offset, - flip, - shift, - useHover, - useFocus, - useDismiss, - useRole, - useInteractions, -} from '@floating-ui/react'; import { AnimatePresence, motion } from 'framer-motion'; +import { Tooltip } from '@/components/tooltip'; + import { useSoundStore } from '@/store'; import { cn } from '@/helpers/styles'; import { fade, mix, slideX } from '@/lib/motion'; @@ -21,28 +10,6 @@ import { fade, mix, slideX } from '@/lib/motion'; import styles from './unselect.module.css'; export function UnselectButton() { - const [isTooltipOpen, setIsTooltipOpen] = useState(false); - - const { context, floatingStyles, refs } = useFloating({ - middleware: [offset(15), flip(), shift()], - onOpenChange: setIsTooltipOpen, - open: isTooltipOpen, - placement: 'top', - whileElementsMounted: autoUpdate, - }); - - const hover = useHover(context, { move: false }); - const focus = useFocus(context); - const dismiss = useDismiss(context); - const role = useRole(context, { role: 'tooltip' }); - - const { getFloatingProps, getReferenceProps } = useInteractions([ - hover, - focus, - dismiss, - role, - ]); - const noSelected = useSoundStore(state => state.noSelected()); const restoreHistory = useSoundStore(state => state.restoreHistory); const hasHistory = useSoundStore(state => !!state.history); @@ -60,38 +27,36 @@ export function UnselectButton() { initial="hidden" variants={variants} > - + + )} - - {isTooltipOpen && ( -
- {hasHistory ? 'Restore unselected sounds.' : 'Unselect all sounds.'} -
- )} ); } diff --git a/src/components/tooltip/index.ts b/src/components/tooltip/index.ts new file mode 100644 index 0000000..c20c22c --- /dev/null +++ b/src/components/tooltip/index.ts @@ -0,0 +1 @@ +export { Tooltip } from './tooltip'; diff --git a/src/components/tooltip/tooltip.module.css b/src/components/tooltip/tooltip.module.css new file mode 100644 index 0000000..e319e38 --- /dev/null +++ b/src/components/tooltip/tooltip.module.css @@ -0,0 +1,8 @@ +.tooltip { + width: max-content; + padding: 6px 12px; + border: 1px solid var(--color-neutral-200); + border-radius: 100px; + background-color: var(--color-neutral-100); + font-size: var(--font-xsm); +} diff --git a/src/components/tooltip/tooltip.tsx b/src/components/tooltip/tooltip.tsx new file mode 100644 index 0000000..87c4324 --- /dev/null +++ b/src/components/tooltip/tooltip.tsx @@ -0,0 +1,111 @@ +import { useState, cloneElement } from 'react'; +import { + useFloating, + autoUpdate, + offset, + flip, + shift, + useHover, + useFocus, + useDismiss, + useRole, + useInteractions, + type Placement, +} from '@floating-ui/react'; +import { motion, AnimatePresence } from 'framer-motion'; + +import styles from './tooltip.module.css'; + +interface TooltipProps { + children: JSX.Element; + content: React.ReactNode; + hideDelay?: number; + placement?: Placement; + showDelay?: number; +} + +export function Tooltip({ + children, + content, + hideDelay = 100, + placement = 'top', + showDelay = 500, +}: TooltipProps) { + const [isTooltipOpen, setIsTooltipOpen] = useState(false); + + const { + context, + floatingStyles, + placement: computedPlacement, + refs, + strategy, + x, + y, + } = useFloating({ + middleware: [offset(12), flip(), shift()], + onOpenChange: setIsTooltipOpen, + open: isTooltipOpen, + placement: placement, + whileElementsMounted: autoUpdate, + }); + + const hover = useHover(context, { + delay: showDelay, + move: false, + restMs: hideDelay, + }); + const focus = useFocus(context); + const dismiss = useDismiss(context); + const role = useRole(context, { role: 'tooltip' }); + + const { getFloatingProps, getReferenceProps } = useInteractions([ + hover, + focus, + dismiss, + role, + ]); + + const translate = { + bottom: { translateY: -5 }, + left: { translateX: 5 }, + right: { translateX: -5 }, + top: { translateY: 5 }, + }[ + computedPlacement.includes('-') + ? computedPlacement.split('-')[0] + : computedPlacement + ]; + + const variants = { + hidden: { opacity: 0, ...translate }, + show: { opacity: 1, translateX: 0, translateY: 0 }, + }; + + return ( + <> + {cloneElement( + children, + getReferenceProps({ ref: refs.setReference, ...children.props }), + )} + + + {isTooltipOpen && ( + + {content} + + )} + + + ); +} diff --git a/src/lib/motion.ts b/src/lib/motion.ts index b1cd2e3..baf3d6b 100644 --- a/src/lib/motion.ts +++ b/src/lib/motion.ts @@ -30,8 +30,8 @@ export function slideX(from = -10, to = 0): Motion { export function slideY(from = -10, to = 0): Motion { return { - hidden: { Y: from }, - show: { Y: to }, + hidden: { y: from }, + show: { y: to }, }; }