react hover indicators
implemented in channel list element
This commit is contained in:
parent
f7433c23be
commit
26139af512
@ -1,9 +1,9 @@
|
||||
import React, { FC, ReactNode, RefObject } from 'react';
|
||||
import React, { FC, ReactNode, RefObject, useRef } from 'react';
|
||||
import { IAlignment } from '../../require/elements-util';
|
||||
import ReactHelper from '../../require/react-helper';
|
||||
|
||||
export interface ContextProps {
|
||||
rootRef: RefObject<HTMLDivElement>;
|
||||
rootRef?: RefObject<HTMLDivElement>;
|
||||
relativeToRef?: RefObject<HTMLElement>;
|
||||
relativeToPos?: { x: number, y: number };
|
||||
alignment: IAlignment;
|
||||
@ -14,12 +14,14 @@ export interface ContextProps {
|
||||
const Context: FC<ContextProps> = (props: ContextProps) => {
|
||||
const { rootRef, relativeToRef, relativeToPos, alignment, children } = props;
|
||||
|
||||
const myRootRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [ className ] = ReactHelper.useAlignment(
|
||||
rootRef, relativeToRef ?? null, relativeToPos ?? null, alignment, 'context react'
|
||||
rootRef ?? myRootRef, relativeToRef ?? null, relativeToPos ?? null, alignment, 'context react'
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={rootRef} className={className}>
|
||||
<div ref={rootRef ?? myRootRef} className={className}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
45
src/client/webapp/elements/contexts/context-hover-basic.tsx
Normal file
45
src/client/webapp/elements/contexts/context-hover-basic.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import React, { FC, ReactNode, RefObject, useMemo } from 'react';
|
||||
import { ShouldNeverHappenError } from '../../data-types';
|
||||
import Context from './components/context';
|
||||
|
||||
export enum BasicHoverSide {
|
||||
LEFT = 'left',
|
||||
RIGHT = 'right',
|
||||
TOP = 'top',
|
||||
BOTTOM = 'bottom',
|
||||
}
|
||||
|
||||
export interface BasicHoverProps {
|
||||
side: BasicHoverSide;
|
||||
relativeToRef: RefObject<HTMLElement>;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const BasicHover: FC<BasicHoverProps> = (props: BasicHoverProps) => {
|
||||
const { side, relativeToRef, children } = props;
|
||||
|
||||
const alignment = useMemo(() => {
|
||||
switch (side) {
|
||||
case BasicHoverSide.LEFT:
|
||||
return { centerY: 'centerY', right: 'left' };
|
||||
case BasicHoverSide.RIGHT:
|
||||
return { centerY: 'centerY', left: 'right' };
|
||||
case BasicHoverSide.TOP:
|
||||
return { bottom: 'top', centerX: 'centerX' };
|
||||
case BasicHoverSide.BOTTOM:
|
||||
return { top: 'bottom', centerX: 'centerX' };
|
||||
default:
|
||||
throw new ShouldNeverHappenError('invalid side');
|
||||
}
|
||||
}, [ side ]);
|
||||
|
||||
return (
|
||||
<Context alignment={alignment} relativeToRef={relativeToRef}>
|
||||
<div className={'basic-hover ' + side}>
|
||||
{children}
|
||||
</div>
|
||||
</Context>
|
||||
);
|
||||
}
|
||||
|
||||
export default BasicHover;
|
@ -0,0 +1,18 @@
|
||||
@import "../../../styles/theme.scss";
|
||||
|
||||
.channel .channel-gear-hover {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
|
||||
.tab {
|
||||
color: $background-floating; // svg
|
||||
}
|
||||
|
||||
.text {
|
||||
color: $text-normal;
|
||||
background-color: $background-floating;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@ import CombinedGuild from '../../../guild-combined';
|
||||
import ChannelOverlay from '../../overlays/overlay-channel';
|
||||
import BaseElements from '../../require/base-elements';
|
||||
import ElementsUtil from '../../require/elements-util';
|
||||
import ReactHelper from '../../require/react-helper';
|
||||
import BasicHover, { BasicHoverSide } from '../../contexts/context-hover-basic';
|
||||
|
||||
export interface ChannelElementProps {
|
||||
guild: CombinedGuild;
|
||||
@ -25,6 +27,19 @@ const ChannelElement: FC<ChannelElementProps> = (props: ChannelElementProps) =>
|
||||
|
||||
const baseClassName = activeChannel?.id === channel.id ? 'channel text active' : 'channel text';
|
||||
|
||||
const [ modifyContextHover, modifyMouseEnterCallable, modifyMouseLeaveCallable ] = ReactHelper.useContextHover(
|
||||
() => {
|
||||
return (
|
||||
<BasicHover side={BasicHoverSide.RIGHT} relativeToRef={modifyRef}>
|
||||
<div className="channel-gear-hover">
|
||||
<div className="tab">{BaseElements.TAB_LEFT}</div>
|
||||
<div className="text">Modify Channel</div>
|
||||
</div>
|
||||
</BasicHover>
|
||||
);
|
||||
}, [ modifyRef ]
|
||||
);
|
||||
|
||||
const setSelfActiveChannel = useCallback((event: MouseEvent<HTMLDivElement>) => {
|
||||
if (modifyRef.current?.contains(event.target as Node)) return; // ignore "modify" button clicks
|
||||
setActiveChannel(channel);
|
||||
@ -39,7 +54,11 @@ const ChannelElement: FC<ChannelElementProps> = (props: ChannelElementProps) =>
|
||||
<div className={baseClassName} onClick={setSelfActiveChannel}>
|
||||
<div className="icon">{BaseElements.TEXT_CHANNEL_ICON}</div>
|
||||
<div className="name">{channel.name}</div>
|
||||
<div className="modify" ref={modifyRef} onClick={launchModify}>{BaseElements.COG}</div>
|
||||
<div
|
||||
className="modify" ref={modifyRef} onClick={launchModify}
|
||||
onMouseEnter={modifyMouseEnterCallable} onMouseLeave={modifyMouseLeaveCallable}
|
||||
>{BaseElements.COG}</div>
|
||||
{modifyContextHover}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -230,6 +230,11 @@ export default class BaseElements {
|
||||
Z` }
|
||||
};
|
||||
|
||||
static TAB_LEFT = (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="20" viewBox="0 0 8 12">
|
||||
<path fill="currentColor" d="M 0,6 L 8,12 L 8,0 Z"></path>
|
||||
</svg>
|
||||
);
|
||||
static Q_TAB_LEFT = {
|
||||
ns: 'http://www.w3.org/2000/svg', tag: 'svg', width: 10, height: 20, viewBox: '0 0 8 12', content:
|
||||
{ ns: 'http://www.w3.org/2000/svg', tag: 'path', fill: 'currentColor', //'fill-rule': 'evenodd', 'clip-rule': 'evenodd',
|
||||
|
@ -440,7 +440,7 @@ export default class ReactHelper {
|
||||
}, []);
|
||||
const contextMenu = useMemo(() => {
|
||||
return createContextMenu(close);
|
||||
}, [ close, ...createContextMenuDeps ]);
|
||||
}, [ close, createContextMenu, ...createContextMenuDeps ]);
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
setIsOpen(oldIsOpen => !!contextMenu && !oldIsOpen);
|
||||
@ -451,4 +451,28 @@ export default class ReactHelper {
|
||||
|
||||
return [ isOpen ? contextMenu : null, toggle, close, open, isOpen ];
|
||||
}
|
||||
|
||||
static useContextHover(
|
||||
createContextHover: () => ReactNode,
|
||||
createContextHoverDeps: DependencyList
|
||||
): [
|
||||
contextHover: ReactNode,
|
||||
mouseEnterCallable: () => void,
|
||||
mouseLeaveCallable: () => void
|
||||
] {
|
||||
const [ isOpen, setIsOpen ] = useState<boolean>(false);
|
||||
|
||||
const contextHover = useMemo(() => {
|
||||
return createContextHover();
|
||||
}, [ createContextHover, ...createContextHoverDeps ]);
|
||||
|
||||
const mouseEnterCallable = useCallback(() => {
|
||||
setIsOpen(true);
|
||||
}, []);
|
||||
const mouseLeaveCallable = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
}, []);
|
||||
|
||||
return [ isOpen ? contextHover : null, mouseEnterCallable, mouseLeaveCallable ];
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
|
||||
.context {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
|
||||
// Since useEffect gets called after the element is rendered, hide it until
|
||||
// it gets aligned
|
||||
|
Loading…
Reference in New Issue
Block a user