mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
refactor(Tooltip): improve logic
This commit is contained in:
parent
ea23012884
commit
aa4dcb7410
13 changed files with 180 additions and 127 deletions
|
|
@ -6,7 +6,7 @@ const classnames = require('classnames');
|
|||
const { default: Icon } = require('@stremio/stremio-icons/react');
|
||||
const Button = require('stremio/common/Button');
|
||||
const styles = require('./styles');
|
||||
const { Tooltip } = require('stremio/common/Tooltip');
|
||||
const { Tooltip } = require('stremio/common/Tooltips');
|
||||
|
||||
const ActionButton = ({ className, icon, label, tooltip, ...props }) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,106 +0,0 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useState, createRef } = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classNames = require('classnames');
|
||||
const TooltipContext = require('./TooltipContext');
|
||||
const styles = require('./styles');
|
||||
|
||||
const TooltipProvider = ({ children, className }) => {
|
||||
const [tooltips, setTooltips] = useState([]);
|
||||
|
||||
const add = ({ id, label, position, margin = 15, parent }) => {
|
||||
const ref = createRef(null);
|
||||
|
||||
const tooltip = {
|
||||
ref,
|
||||
id,
|
||||
label,
|
||||
position,
|
||||
margin: margin,
|
||||
active: false,
|
||||
parent,
|
||||
};
|
||||
|
||||
setTooltips((tooltips) => ([
|
||||
...tooltips,
|
||||
tooltip,
|
||||
]));
|
||||
};
|
||||
|
||||
const remove = (id) => {
|
||||
setTooltips((tooltips) => (
|
||||
tooltips.filter((tooltip) => tooltip.id !== id)
|
||||
));
|
||||
};
|
||||
|
||||
const toggle = (id, state) => {
|
||||
setTooltips((tooltips) => (
|
||||
tooltips.map((tooltip) => {
|
||||
if (tooltip.id === id) {
|
||||
tooltip.active = state;
|
||||
}
|
||||
return tooltip;
|
||||
})
|
||||
));
|
||||
};
|
||||
|
||||
const style = (ref, position, margin, active, parent) => {
|
||||
if (!active) return {};
|
||||
|
||||
const tooltipHeight = ref.current?.offsetHeight ?? 0;
|
||||
const tooltipWidth = ref.current?.offsetWidth ?? 0;
|
||||
const parentBounds = parent.getBoundingClientRect();
|
||||
|
||||
switch (position) {
|
||||
case 'top':
|
||||
return {
|
||||
top: `${parentBounds.top - tooltipHeight - margin}px`,
|
||||
left: `${(parentBounds.left + (parentBounds.width / 2)) - (tooltipWidth / 2)}px`,
|
||||
};
|
||||
case 'bottom':
|
||||
return {
|
||||
top: `${parentBounds.top + parentBounds.height + margin}px`,
|
||||
left: `${(parentBounds.left + (parentBounds.width / 2)) - (tooltipWidth / 2)}px`,
|
||||
};
|
||||
case 'left':
|
||||
return {
|
||||
top: `${parentBounds.top + (parentBounds.height / 2) - (tooltipHeight / 2)}px`,
|
||||
left: `${(parentBounds.left - tooltipWidth - margin)}px`,
|
||||
};
|
||||
case 'right':
|
||||
return {
|
||||
top: `${parentBounds.top + (parentBounds.height / 2) - (tooltipHeight / 2)}px`,
|
||||
left: `${(parentBounds.left + parentBounds.width + margin)}px`,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipContext.Provider value={{ add, remove, toggle }}>
|
||||
{ children }
|
||||
<div className={'tooltips-container'}>
|
||||
{
|
||||
tooltips.map(({ ref, id, label, position, margin, active, parent }) => (
|
||||
<div
|
||||
key={id}
|
||||
ref={ref}
|
||||
className={classNames(className, styles['tooltip-container'], { 'active': active })}
|
||||
style={style(ref, position, margin, active, parent)}
|
||||
>
|
||||
{ label }
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</TooltipContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
TooltipProvider.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
module.exports = TooltipProvider;
|
||||
|
|
@ -2,25 +2,35 @@
|
|||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const useTooltip = require('./useTooltip');
|
||||
const useTooltip = require('../useTooltip');
|
||||
const styles = require('./styles');
|
||||
|
||||
const createId = () => (Math.random() + 1).toString(36).substring(7);
|
||||
|
||||
const Tooltip = ({ label, position, margin }) => {
|
||||
const Tooltip = ({ label, position, margin = 15 }) => {
|
||||
const tooltip = useTooltip();
|
||||
|
||||
const id = React.useRef(createId());
|
||||
const element = React.useRef(null);
|
||||
|
||||
const onMouseEnter = () => {
|
||||
tooltip.toggle(id.current, true);
|
||||
tooltip.update(id.current, {
|
||||
active: true,
|
||||
});
|
||||
};
|
||||
|
||||
const onMouseLeave = () => {
|
||||
tooltip.toggle(id.current, false);
|
||||
tooltip.update(id.current, {
|
||||
active: false,
|
||||
});
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
tooltip.update(id.current, {
|
||||
label,
|
||||
});
|
||||
}, [label]);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (element.current && element.current.parentElement) {
|
||||
const parentElement = element.current.parentElement;
|
||||
|
|
@ -45,15 +55,13 @@ const Tooltip = ({ label, position, margin }) => {
|
|||
tooltip.remove(id.current);
|
||||
}
|
||||
};
|
||||
}, [label]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={element} className={styles['tooltip']} />
|
||||
<div ref={element} className={styles['tooltip-placeholder']} />
|
||||
);
|
||||
};
|
||||
|
||||
Tooltip.displayName = 'Tooltip';
|
||||
|
||||
Tooltip.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
position: PropTypes.string.isRequired,
|
||||
6
src/common/Tooltips/Tooltip/index.js
Normal file
6
src/common/Tooltips/Tooltip/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const Tooltip = require('./Tooltip');
|
||||
|
||||
module.exports = Tooltip;
|
||||
|
||||
11
src/common/Tooltips/Tooltip/styles.less
Normal file
11
src/common/Tooltips/Tooltip/styles.less
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
.tooltip-placeholder {
|
||||
z-index: -1;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
72
src/common/Tooltips/TooltipItem/TooltipItem.js
Normal file
72
src/common/Tooltips/TooltipItem/TooltipItem.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classNames = require('classnames');
|
||||
const styles = require('./styles');
|
||||
|
||||
const TooltipItem = React.memo(({ className, active, label, position, margin, parent }) => {
|
||||
const ref = React.useRef(null);
|
||||
const timeout = React.useRef(null);
|
||||
|
||||
const [style, setStyle] = React.useState({});
|
||||
|
||||
React.useEffect(() => {
|
||||
clearTimeout(timeout.current);
|
||||
|
||||
timeout.current = setTimeout(() => {
|
||||
if (active && ref.current) {
|
||||
const tooltipBounds = ref.current.getBoundingClientRect();
|
||||
const parentBounds = parent.getBoundingClientRect();
|
||||
|
||||
switch (position) {
|
||||
case 'top':
|
||||
return setStyle({
|
||||
top: `${parentBounds.top - tooltipBounds.height - margin}px`,
|
||||
left: `${(parentBounds.left + (parentBounds.width / 2)) - (tooltipBounds.width / 2)}px`,
|
||||
});
|
||||
case 'bottom':
|
||||
return setStyle({
|
||||
top: `${parentBounds.top + parentBounds.height + margin}px`,
|
||||
left: `${(parentBounds.left + (parentBounds.width / 2)) - (tooltipBounds.width / 2)}px`,
|
||||
});
|
||||
case 'left':
|
||||
return setStyle({
|
||||
top: `${parentBounds.top + (parentBounds.height / 2) - (tooltipBounds.height / 2)}px`,
|
||||
left: `${(parentBounds.left - tooltipBounds.width - margin)}px`,
|
||||
});
|
||||
case 'right':
|
||||
return setStyle({
|
||||
top: `${parentBounds.top + (parentBounds.height / 2) - (tooltipBounds.height / 2)}px`,
|
||||
left: `${(parentBounds.left + parentBounds.width + margin)}px`,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () => clearTimeout(timeout.current);
|
||||
}, [active, position, margin, parent, label]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={classNames(className, styles['tooltip-item'], { 'active': active })}
|
||||
style={style}
|
||||
>
|
||||
{ label }
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
TooltipItem.displayName = 'TooltipItem';
|
||||
|
||||
TooltipItem.propTypes = {
|
||||
className: PropTypes.string,
|
||||
active: PropTypes.bool,
|
||||
label: PropTypes.string,
|
||||
position: PropTypes.string,
|
||||
margin: PropTypes.number,
|
||||
parent: PropTypes.instanceOf(HTMLElement),
|
||||
};
|
||||
|
||||
module.exports = TooltipItem;
|
||||
6
src/common/Tooltips/TooltipItem/index.js
Normal file
6
src/common/Tooltips/TooltipItem/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const TooltipItem = require('./TooltipItem');
|
||||
|
||||
module.exports = TooltipItem;
|
||||
|
||||
|
|
@ -1,16 +1,6 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
.tooltip {
|
||||
z-index: -1;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.tooltip-container {
|
||||
.tooltip-item {
|
||||
position: fixed;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
66
src/common/Tooltips/TooltipProvider.js
Normal file
66
src/common/Tooltips/TooltipProvider.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (C) 2017-2023 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const TooltipContext = require('./TooltipContext');
|
||||
const TooltipItem = require('./TooltipItem');
|
||||
|
||||
const TooltipProvider = ({ children, className }) => {
|
||||
const [tooltips, setTooltips] = React.useState([]);
|
||||
|
||||
const add = (options) => {
|
||||
const tooltip = {
|
||||
...options,
|
||||
active: false,
|
||||
};
|
||||
|
||||
setTooltips((tooltips) => ([
|
||||
...tooltips,
|
||||
tooltip,
|
||||
]));
|
||||
};
|
||||
|
||||
const remove = (id) => {
|
||||
setTooltips((tooltips) => (
|
||||
tooltips.filter((tooltip) => tooltip.id !== id)
|
||||
));
|
||||
};
|
||||
|
||||
const update = (id, state) => {
|
||||
setTooltips((tooltips) => (
|
||||
tooltips.map((tooltip) => {
|
||||
if (tooltip.id === id) {
|
||||
tooltip = {
|
||||
...tooltip,
|
||||
...state,
|
||||
};
|
||||
}
|
||||
return tooltip;
|
||||
})
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipContext.Provider value={{ add, remove, update }}>
|
||||
{ children }
|
||||
<div className={'tooltips-items-container'}>
|
||||
{
|
||||
tooltips.map(({ id, ...tooltip }) => (
|
||||
<TooltipItem
|
||||
key={id}
|
||||
className={className}
|
||||
{...tooltip}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</TooltipContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
TooltipProvider.propTypes = {
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
module.exports = TooltipProvider;
|
||||
|
|
@ -23,7 +23,7 @@ const SharePrompt = require('./SharePrompt');
|
|||
const Slider = require('./Slider');
|
||||
const TextInput = require('./TextInput');
|
||||
const { ToastProvider, useToast } = require('./Toast');
|
||||
const { TooltipProvider, Tooltip } = require('./Tooltip');
|
||||
const { TooltipProvider, Tooltip } = require('./Tooltips');
|
||||
const comparatorWithPriorities = require('./comparatorWithPriorities');
|
||||
const CONSTANTS = require('./CONSTANTS');
|
||||
const { withCoreSuspender, useCoreSuspender } = require('./CoreSuspender');
|
||||
|
|
|
|||
Loading…
Reference in a new issue