@@ -0,0 +1,17 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import HiddenNotificationService from 'components/scroll-hidden-components/hidden-notification-service';
|
||||
|
||||
type PropsType = {
|
||||
tag: string;
|
||||
};
|
||||
|
||||
export default ({ tag }: PropsType) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
const instance = HiddenNotificationService.get(tag);
|
||||
instance.addBeacon(ref);
|
||||
return () => instance.removeBeacon(ref);
|
||||
}, [tag, ref]);
|
||||
|
||||
return <div ref={ref} />;
|
||||
};
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Observable from 'app/deprecated/Observable/Observable';
|
||||
|
||||
type NodeType = React.RefObject<HTMLDivElement | ChildNode> | React.MutableRefObject<any> | any;
|
||||
|
||||
type BeaconRef = {
|
||||
node: NodeType;
|
||||
top: number;
|
||||
};
|
||||
|
||||
type ScrollerRef = {
|
||||
node: NodeType | undefined;
|
||||
top: number | undefined;
|
||||
};
|
||||
|
||||
type InstanceType = { [key: string]: HiddenNotificationService };
|
||||
|
||||
let executionTimeout: any = 0;
|
||||
|
||||
class HiddenNotificationService extends Observable {
|
||||
beacons: BeaconRef[] = [];
|
||||
scroller: ScrollerRef = { node: undefined, top: undefined };
|
||||
state: any = {
|
||||
beaconTop: [],
|
||||
beaconBottom: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.detectHiddenBeacons = this.detectHiddenBeacons.bind(this);
|
||||
(window as any).HiddenNotificationService = this;
|
||||
}
|
||||
|
||||
public addBeacon(node: NodeType) {
|
||||
this.removeBeacon(node);
|
||||
|
||||
this.beacons.push({
|
||||
node,
|
||||
top: 0,
|
||||
});
|
||||
|
||||
if (this.scroller && this.scroller.top) {
|
||||
this.setBeaconsTop(this.scroller);
|
||||
}
|
||||
this.detectHiddenBeacons();
|
||||
}
|
||||
|
||||
public removeBeacon(node: NodeType) {
|
||||
this.beacons = this.beacons.filter(element => node !== element.node);
|
||||
this.detectHiddenBeacons();
|
||||
}
|
||||
|
||||
private setBeaconsTop(scroller: ScrollerRef) {
|
||||
this.beacons.forEach((beacon: BeaconRef) => {
|
||||
beacon.top =
|
||||
beacon.node.current.getBoundingClientRect().y -
|
||||
(scroller.node.getBoundingClientRect().y || 0) +
|
||||
scroller.node.scrollTop;
|
||||
});
|
||||
}
|
||||
|
||||
private detectHiddenBeacons() {
|
||||
if (!this.scroller.node) return;
|
||||
|
||||
clearTimeout(executionTimeout);
|
||||
executionTimeout = setTimeout(() => {
|
||||
if (!this.scroller.node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hidden: any = {
|
||||
beaconTop: [],
|
||||
beaconBottom: [],
|
||||
};
|
||||
|
||||
const visibleHeight = this.scroller.node.getBoundingClientRect().height;
|
||||
const scrollerTop = this.scroller.node.getBoundingClientRect().y || 0;
|
||||
|
||||
this.beacons.forEach((beacon: BeaconRef) => {
|
||||
const top = beacon.node.current.getBoundingClientRect().y - scrollerTop;
|
||||
if (top > visibleHeight) {
|
||||
hidden.beaconBottom.push(beacon);
|
||||
}
|
||||
if (top < 0) {
|
||||
hidden.beaconTop.push(beacon);
|
||||
}
|
||||
});
|
||||
|
||||
this.state = hidden;
|
||||
this.notify();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
public setScroller(node: NodeType) {
|
||||
this.scroller = { node, top: node.getBoundingClientRect().y };
|
||||
|
||||
node.addEventListener('scroll', this.detectHiddenBeacons, { passive: true });
|
||||
window.addEventListener('resize', this.detectHiddenBeacons);
|
||||
|
||||
this.setBeaconsTop(this.scroller);
|
||||
this.detectHiddenBeacons();
|
||||
}
|
||||
|
||||
public removeScroller() {
|
||||
window.removeEventListener('resize', this.detectHiddenBeacons);
|
||||
this.scroller = { node: undefined, top: undefined };
|
||||
if (this.scroller.node) {
|
||||
this.scroller.node.removeEventListener('scroll', this.detectHiddenBeacons);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class HiddenNotificationServiceManager {
|
||||
static instances: InstanceType = {};
|
||||
|
||||
public static get(tag: string) {
|
||||
if (!this.instances[tag]) {
|
||||
this.instances[tag] = new HiddenNotificationService();
|
||||
}
|
||||
|
||||
return this.instances[tag];
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import { ArrowUp, ArrowDown } from 'react-feather';
|
||||
import { Button } from 'antd';
|
||||
import Languages from 'app/features/global/services/languages-service';
|
||||
import './notifications.scss';
|
||||
|
||||
type PropsType = {
|
||||
icon?: React.ReactNode;
|
||||
iconSize?: number;
|
||||
position: 'top' | 'bottom';
|
||||
type?: 'primary' | 'secondary' | 'warning' | 'important' | undefined;
|
||||
children?: JSX.Element | string;
|
||||
onClick?: ((event: React.MouseEvent<HTMLElement, MouseEvent>) => void) | undefined;
|
||||
};
|
||||
|
||||
const HiddenNotificationsButton: FC<PropsType> = ({
|
||||
children,
|
||||
icon,
|
||||
iconSize,
|
||||
position,
|
||||
type,
|
||||
onClick,
|
||||
}) => {
|
||||
const setDefaultIcon = (): React.ReactNode => {
|
||||
const defaultSize = iconSize ? iconSize : 16;
|
||||
|
||||
switch (position) {
|
||||
case !icon && 'top':
|
||||
return <ArrowUp size={defaultSize} />;
|
||||
case !icon && 'bottom':
|
||||
return <ArrowDown size={defaultSize} />;
|
||||
case icon:
|
||||
case icon && 'top':
|
||||
case icon && 'bottom':
|
||||
return icon;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon={setDefaultIcon()}
|
||||
className={`hidden-notifications-button ${type ? type : 'primary'} ${position}`}
|
||||
onClick={onClick && onClick}
|
||||
>
|
||||
{(children && children) ||
|
||||
Languages.t('components.notifications.hidden_notifications_button.children')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default HiddenNotificationsButton;
|
||||
@@ -0,0 +1,100 @@
|
||||
.scroll-top-component,
|
||||
.scroll-bottom-component {
|
||||
background-color: transparent;
|
||||
max-width: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s;
|
||||
|
||||
&.hide {
|
||||
&.scroll-top-component {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
&.scroll-bottom-component {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-top-component {
|
||||
top: 0;
|
||||
}
|
||||
.scroll-bottom-component {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.scroll-children-component {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hidden-notifications-button {
|
||||
display: flex !important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
&.ant-btn {
|
||||
&,
|
||||
&:focus,
|
||||
&:active,
|
||||
&:hover {
|
||||
color: var(--white);
|
||||
}
|
||||
padding: 0px 12px;
|
||||
display: flex;
|
||||
margin: 4px;
|
||||
width: 70%;
|
||||
height: 24px;
|
||||
border-radius: 12px;
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.primary {
|
||||
&,
|
||||
&:focus,
|
||||
&:active,
|
||||
&:hover {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
&,
|
||||
&:focus,
|
||||
&:active,
|
||||
&:hover {
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
}
|
||||
|
||||
&.warning {
|
||||
&,
|
||||
&:focus,
|
||||
&:active,
|
||||
&:hover {
|
||||
background-color: var(--warning);
|
||||
}
|
||||
}
|
||||
|
||||
&.important {
|
||||
&,
|
||||
&:focus,
|
||||
&:active {
|
||||
background-color: var(--red);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--red);
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React, { FC, useEffect, useRef } from 'react';
|
||||
import HiddenNotificationService from 'components/scroll-hidden-components/hidden-notification-service';
|
||||
import animateScrollTo from 'animated-scroll-to';
|
||||
|
||||
import './notifications.scss';
|
||||
|
||||
type PropsType = {
|
||||
children: JSX.Element | any;
|
||||
scrollTopComponent?: JSX.Element;
|
||||
scrollBottomComponent?: JSX.Element;
|
||||
tag: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const ScrollWithHiddenComponents: FC<PropsType> = ({
|
||||
children,
|
||||
scrollTopComponent,
|
||||
scrollBottomComponent,
|
||||
tag,
|
||||
disabled,
|
||||
}) => {
|
||||
const ref = useRef<any>(null);
|
||||
const service = HiddenNotificationService.get(tag);
|
||||
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
service.setScroller(ref.current.firstChild);
|
||||
return () => service.removeScroller();
|
||||
}, [tag, ref, disabled]);
|
||||
/* eslint-enable react-hooks/exhaustive-deps */
|
||||
|
||||
const beacon: number[] = service.useWatcher(() => [
|
||||
service.state.beaconTop.length,
|
||||
service.state.beaconBottom.length,
|
||||
]);
|
||||
|
||||
const alignCenter = (node: any) =>
|
||||
node.current.offsetTop - (service.scroller.node.clientHeight + node.current.clientHeight) / 2;
|
||||
|
||||
const optionsScroller = {
|
||||
elementToScroll: service.scroller.node,
|
||||
};
|
||||
|
||||
const previousBeacon = () => {
|
||||
const previousNode = service.state.beaconTop.sort((a: any, b: any) => b.top - a.top)?.[0]?.node;
|
||||
if (previousNode) return animateScrollTo(alignCenter(previousNode), optionsScroller);
|
||||
};
|
||||
|
||||
const nextBeacon = () => {
|
||||
const nextNode = service.state.beaconBottom.sort((a: any, b: any) => a.top - b.top)?.[0]?.node;
|
||||
if (nextNode) return animateScrollTo(alignCenter(nextNode), optionsScroller);
|
||||
};
|
||||
|
||||
const hideScrollComponent = (beaconLength: number): string => (beaconLength > 0 ? '' : 'hide');
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={ref} className="scroll-children-component">
|
||||
{children}
|
||||
<div
|
||||
className={`scroll-top-component ${hideScrollComponent(beacon[0])}`}
|
||||
onClick={previousBeacon}
|
||||
>
|
||||
{scrollTopComponent}
|
||||
</div>
|
||||
<div
|
||||
className={`scroll-bottom-component ${hideScrollComponent(beacon[1])}`}
|
||||
onClick={nextBeacon}
|
||||
>
|
||||
{scrollBottomComponent}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollWithHiddenComponents;
|
||||
Reference in New Issue
Block a user