diff --git a/src/App/App.js b/src/App/App.js
index be59767b7..de443d9d4 100644
--- a/src/App/App.js
+++ b/src/App/App.js
@@ -6,7 +6,7 @@ const { useTranslation } = require('react-i18next');
const { Router } = require('stremio-router');
const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider } = require('stremio/services');
const { NotFound } = require('stremio/routes');
-const { ToastProvider, CONSTANTS, withCoreSuspender } = require('stremio/common');
+const { ToastProvider, TooltipProvider, CONSTANTS, withCoreSuspender } = require('stremio/common');
const ServicesToaster = require('./ServicesToaster');
const DeepLinkHandler = require('./DeepLinkHandler');
const ErrorDialog = require('./ErrorDialog');
@@ -159,13 +159,15 @@ const App = () => {
:
-
-
-
+
+
+
+
+
:
diff --git a/src/App/styles.less b/src/App/styles.less
index f4454424c..e5ee4a835 100644
--- a/src/App/styles.less
+++ b/src/App/styles.less
@@ -116,6 +116,18 @@ html {
}
}
+ .tooltip-container {
+ height: 2.5rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 1.5rem;
+ font-size: 1rem;
+ color: var(--primary-foreground-color);
+ border-radius: var(--border-radius);
+ background-color: var(--modal-background-color);
+ }
+
.router {
width: 100%;
height: 100%;
diff --git a/src/common/Tooltip/Tooltip.js b/src/common/Tooltip/Tooltip.js
new file mode 100644
index 000000000..025a4ab21
--- /dev/null
+++ b/src/common/Tooltip/Tooltip.js
@@ -0,0 +1,63 @@
+// Copyright (C) 2017-2023 Smart code 203358507
+
+const React = require('react');
+const PropTypes = require('prop-types');
+const useTooltip = require('./useTooltip');
+const styles = require('./styles');
+
+const createId = () => (Math.random() + 1).toString(36).substring(7);
+
+const Tooltip = ({ label, position, margin }) => {
+ const tooltip = useTooltip();
+
+ const id = React.useRef(createId());
+ const element = React.useRef(null);
+
+ const show = () => {
+ tooltip.toggle(id.current, true);
+ };
+
+ const hide = () => {
+ tooltip.toggle(id.current, false);
+ };
+
+ React.useEffect(() => {
+ if (element.current && element.current.parentElement) {
+ const parentElement = element.current.parentElement;
+ tooltip.add({
+ id: id.current,
+ label,
+ position,
+ margin,
+ parent: parentElement,
+ });
+
+ parentElement.addEventListener('mouseenter', show);
+ parentElement.addEventListener('mouseleave', hide);
+ }
+
+ return () => {
+ if (element.current && element.current.parentElement) {
+ const parentElement = element.current.parentElement;
+ parentElement.removeEventListener('mouseenter', show);
+ parentElement.removeEventListener('mouseleave', hide);
+
+ tooltip.remove(id.current);
+ }
+ };
+ }, []);
+
+ return (
+
+ );
+};
+
+Tooltip.displayName = 'Tooltip';
+
+Tooltip.propTypes = {
+ label: PropTypes.string.isRequired,
+ position: PropTypes.string.isRequired,
+ margin: PropTypes.number,
+};
+
+module.exports = Tooltip;
diff --git a/src/common/Tooltip/TooltipContext.js b/src/common/Tooltip/TooltipContext.js
new file mode 100644
index 000000000..f4da8a082
--- /dev/null
+++ b/src/common/Tooltip/TooltipContext.js
@@ -0,0 +1,8 @@
+// Copyright (C) 2017-2023 Smart code 203358507
+
+const { createContext } = require('react');
+
+const TooltipContext = createContext(null);
+
+module.exports = TooltipContext;
+
diff --git a/src/common/Tooltip/TooltipProvider.js b/src/common/Tooltip/TooltipProvider.js
new file mode 100644
index 000000000..c19780248
--- /dev/null
+++ b/src/common/Tooltip/TooltipProvider.js
@@ -0,0 +1,106 @@
+// 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 (
+
+ { children }
+
+ {
+ tooltips.map(({ ref, id, label, position, margin, active, parent }) => (
+
+ { label }
+
+ ))
+ }
+
+
+ );
+};
+
+TooltipProvider.propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string,
+};
+
+module.exports = TooltipProvider;
diff --git a/src/common/Tooltip/index.js b/src/common/Tooltip/index.js
new file mode 100644
index 000000000..a909daba2
--- /dev/null
+++ b/src/common/Tooltip/index.js
@@ -0,0 +1,11 @@
+// Copyright (C) 2017-2023 Smart code 203358507
+
+const TooltipProvider = require('./TooltipProvider');
+const Tooltip = require('./Tooltip');
+const useTooltip = require('./useTooltip');
+
+module.exports = {
+ TooltipProvider,
+ Tooltip,
+ useTooltip,
+};
diff --git a/src/common/Tooltip/styles.less b/src/common/Tooltip/styles.less
new file mode 100644
index 000000000..22bcb40ef
--- /dev/null
+++ b/src/common/Tooltip/styles.less
@@ -0,0 +1,20 @@
+// 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 {
+ position: fixed;
+ visibility: hidden;
+
+ &:global(.active) {
+ visibility: visible;
+ }
+}
\ No newline at end of file
diff --git a/src/common/Tooltip/useTooltip.js b/src/common/Tooltip/useTooltip.js
new file mode 100644
index 000000000..d787dca39
--- /dev/null
+++ b/src/common/Tooltip/useTooltip.js
@@ -0,0 +1,10 @@
+// Copyright (C) 2017-2023 Smart code 203358507
+
+const React = require('react');
+const TooltipContext = require('./TooltipContext');
+
+const useTooltip = () => {
+ return React.useContext(TooltipContext);
+};
+
+module.exports = useTooltip;
diff --git a/src/common/index.js b/src/common/index.js
index f267be575..56a41f8b5 100644
--- a/src/common/index.js
+++ b/src/common/index.js
@@ -23,6 +23,7 @@ const SharePrompt = require('./SharePrompt');
const Slider = require('./Slider');
const TextInput = require('./TextInput');
const { ToastProvider, useToast } = require('./Toast');
+const { TooltipProvider, Tooltip, useTooltip } = require('./Tooltip');
const comparatorWithPriorities = require('./comparatorWithPriorities');
const CONSTANTS = require('./CONSTANTS');
const { withCoreSuspender, useCoreSuspender } = require('./CoreSuspender');
@@ -70,6 +71,9 @@ module.exports = {
TextInput,
ToastProvider,
useToast,
+ TooltipProvider,
+ Tooltip,
+ useTooltip,
comparatorWithPriorities,
CONSTANTS,
withCoreSuspender,