mirror of
https://github.com/remvze/moodist.git
synced 2025-12-17 08:54:13 +00:00
feat: create reusable tooltip
This commit is contained in:
parent
0888aaa0f0
commit
c637e2d631
5 changed files with 149 additions and 64 deletions
|
|
@ -1,19 +1,8 @@
|
||||||
import { useState } from 'react';
|
|
||||||
import { BiUndo, BiTrash } from 'react-icons/bi/index';
|
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 { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
|
||||||
|
import { Tooltip } from '@/components/tooltip';
|
||||||
|
|
||||||
import { useSoundStore } from '@/store';
|
import { useSoundStore } from '@/store';
|
||||||
import { cn } from '@/helpers/styles';
|
import { cn } from '@/helpers/styles';
|
||||||
import { fade, mix, slideX } from '@/lib/motion';
|
import { fade, mix, slideX } from '@/lib/motion';
|
||||||
|
|
@ -21,28 +10,6 @@ import { fade, mix, slideX } from '@/lib/motion';
|
||||||
import styles from './unselect.module.css';
|
import styles from './unselect.module.css';
|
||||||
|
|
||||||
export function UnselectButton() {
|
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 noSelected = useSoundStore(state => state.noSelected());
|
||||||
const restoreHistory = useSoundStore(state => state.restoreHistory);
|
const restoreHistory = useSoundStore(state => state.restoreHistory);
|
||||||
const hasHistory = useSoundStore(state => !!state.history);
|
const hasHistory = useSoundStore(state => !!state.history);
|
||||||
|
|
@ -59,13 +26,20 @@ export function UnselectButton() {
|
||||||
exit="hidden"
|
exit="hidden"
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
variants={variants}
|
variants={variants}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
hasHistory
|
||||||
|
? 'Restore unselected sounds.'
|
||||||
|
: 'Unselect all sounds.'
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
disabled={noSelected && !hasHistory}
|
disabled={noSelected && !hasHistory}
|
||||||
ref={refs.setReference}
|
|
||||||
{...getReferenceProps}
|
|
||||||
aria-label={
|
aria-label={
|
||||||
hasHistory ? 'Restore Unselected Sounds' : 'Unselect All Sounds'
|
hasHistory
|
||||||
|
? 'Restore Unselected Sounds'
|
||||||
|
: 'Unselect All Sounds'
|
||||||
}
|
}
|
||||||
className={cn(
|
className={cn(
|
||||||
styles.unselectButton,
|
styles.unselectButton,
|
||||||
|
|
@ -79,19 +53,10 @@ export function UnselectButton() {
|
||||||
>
|
>
|
||||||
{hasHistory ? <BiUndo /> : <BiTrash />}
|
{hasHistory ? <BiUndo /> : <BiTrash />}
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
{isTooltipOpen && (
|
|
||||||
<div
|
|
||||||
ref={refs.setFloating}
|
|
||||||
style={floatingStyles}
|
|
||||||
{...getFloatingProps({ className: styles.tooltip })}
|
|
||||||
>
|
|
||||||
{hasHistory ? 'Restore unselected sounds.' : 'Unselect all sounds.'}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
src/components/tooltip/index.ts
Normal file
1
src/components/tooltip/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { Tooltip } from './tooltip';
|
||||||
8
src/components/tooltip/tooltip.module.css
Normal file
8
src/components/tooltip/tooltip.module.css
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
111
src/components/tooltip/tooltip.tsx
Normal file
111
src/components/tooltip/tooltip.tsx
Normal file
|
|
@ -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 }),
|
||||||
|
)}
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{isTooltipOpen && (
|
||||||
|
<motion.div
|
||||||
|
animate="show"
|
||||||
|
exit="hidden"
|
||||||
|
initial="hidden"
|
||||||
|
ref={refs.setFloating}
|
||||||
|
style={floatingStyles}
|
||||||
|
variants={variants}
|
||||||
|
{...getFloatingProps({
|
||||||
|
className: styles.tooltip,
|
||||||
|
style: { left: x ?? 0, position: strategy, top: y ?? 0 },
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -30,8 +30,8 @@ export function slideX(from = -10, to = 0): Motion {
|
||||||
|
|
||||||
export function slideY(from = -10, to = 0): Motion {
|
export function slideY(from = -10, to = 0): Motion {
|
||||||
return {
|
return {
|
||||||
hidden: { Y: from },
|
hidden: { y: from },
|
||||||
show: { Y: to },
|
show: { y: to },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue