mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 12:56:41 +07:00
refactored hooks directory
This commit is contained in:
parent
efa3ba902d
commit
f76a5a4798
106 changed files with 122 additions and 144 deletions
33
src/hooks/dom/useClickOutsideFocus.ts
Normal file
33
src/hooks/dom/useClickOutsideFocus.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Handles blur by detecting clicks outside refs.
|
||||
*/
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCloseOnEscape } from '~/hooks';
|
||||
|
||||
export const useClickOutsideFocus = () => {
|
||||
const ref = useRef<HTMLElement>();
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
|
||||
const activate = useCallback(() => setIsActive(true), [setIsActive]);
|
||||
const deactivate = useCallback(() => setIsActive(false), [setIsActive]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isActive || !ref.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deactivator = (event: MouseEvent) => {
|
||||
if (!ref.current?.contains(event.target as Node)) {
|
||||
deactivate();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mouseup', deactivator);
|
||||
|
||||
return () => document.removeEventListener('mouseup', deactivator);
|
||||
}, [deactivate, isActive]);
|
||||
|
||||
useCloseOnEscape(deactivate);
|
||||
|
||||
return { ref, isActive, activate, deactivate };
|
||||
};
|
49
src/hooks/dom/useDragDetector.tsx
Normal file
49
src/hooks/dom/useDragDetector.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
import React, { createContext, FC, useCallback, useContext, useEffect, useState } from 'react';
|
||||
|
||||
const DragContext = createContext({
|
||||
isDragging: false,
|
||||
setIsDragging: (val: boolean) => {},
|
||||
});
|
||||
|
||||
export const DragDetectorProvider: FC = ({ children }) => {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
return (
|
||||
<DragContext.Provider value={{ isDragging, setIsDragging }}>{children}</DragContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useDragDetector = () => {
|
||||
const { isDragging, setIsDragging } = useContext(DragContext);
|
||||
|
||||
const onStopDragging = useCallback(() => setIsDragging(false), [setIsDragging]);
|
||||
|
||||
useEffect(() => {
|
||||
const addClass = () => setIsDragging(true);
|
||||
|
||||
const removeClass = event => {
|
||||
// Small hack to ignore intersection with child elements
|
||||
if (event.pageX !== 0 && event.pageY !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsDragging(false);
|
||||
};
|
||||
|
||||
document.addEventListener('dragenter', addClass);
|
||||
document.addEventListener('dragover', addClass);
|
||||
document.addEventListener('dragleave', removeClass);
|
||||
document.addEventListener('blur', removeClass);
|
||||
document.addEventListener('drop', onStopDragging);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('dragenter', addClass);
|
||||
document.removeEventListener('dragover', addClass);
|
||||
document.removeEventListener('dragleave', removeClass);
|
||||
document.removeEventListener('blur', removeClass);
|
||||
document.removeEventListener('drop', onStopDragging);
|
||||
};
|
||||
}, [onStopDragging, setIsDragging]);
|
||||
|
||||
return { isDragging, onStopDragging };
|
||||
};
|
18
src/hooks/dom/useFocusEvent.ts
Normal file
18
src/hooks/dom/useFocusEvent.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { useCallback, useState } from 'react';
|
||||
|
||||
export const useFocusEvent = (initialState = false) => {
|
||||
const [focused, setFocused] = useState(initialState);
|
||||
|
||||
const onFocus = useCallback(
|
||||
event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
setFocused(true);
|
||||
},
|
||||
[setFocused]
|
||||
);
|
||||
const onBlur = useCallback(() => setTimeout(() => setFocused(false), 300), [setFocused]);
|
||||
|
||||
return { focused, onBlur, onFocus };
|
||||
};
|
49
src/hooks/dom/useFormatWrapper.ts
Normal file
49
src/hooks/dom/useFormatWrapper.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
/** wraps text inside textarea with tags */
|
||||
export const useFormatWrapper = (
|
||||
target: HTMLTextAreaElement,
|
||||
onChange: (val: string) => void,
|
||||
prefix = '',
|
||||
suffix = ''
|
||||
) => {
|
||||
return useCallback(
|
||||
event => {
|
||||
event.preventDefault();
|
||||
wrapTextInsideInput(target, prefix, suffix, onChange);
|
||||
},
|
||||
[target, onChange, prefix, suffix]
|
||||
);
|
||||
};
|
||||
|
||||
/** wraps text inside textarea with tags */
|
||||
export const wrapTextInsideInput = (
|
||||
target: HTMLTextAreaElement,
|
||||
prefix: string,
|
||||
suffix: string,
|
||||
onChange: (val: string) => void
|
||||
) => {
|
||||
if (!target) return;
|
||||
|
||||
const start = target.selectionStart;
|
||||
const end = target.selectionEnd;
|
||||
const selection = target.value.substring(start, end);
|
||||
|
||||
const replacement = prefix + selection + suffix;
|
||||
|
||||
onChange(
|
||||
target.value.substring(0, start) +
|
||||
replacement +
|
||||
target.value.substring(end, target.value.length)
|
||||
);
|
||||
|
||||
target.focus();
|
||||
|
||||
setTimeout(() => {
|
||||
if (start === end) {
|
||||
target.selectionEnd = end + prefix.length;
|
||||
} else {
|
||||
target.selectionEnd = end + prefix.length + suffix.length;
|
||||
}
|
||||
}, 0);
|
||||
};
|
17
src/hooks/dom/useInfiniteLoader.ts
Normal file
17
src/hooks/dom/useInfiniteLoader.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
export const useInfiniteLoader = (loader: () => void, isLoading?: boolean) => {
|
||||
const onLoadMore = useCallback(() => {
|
||||
const pos = window.scrollY + window.innerHeight - document.body.scrollHeight;
|
||||
|
||||
if (isLoading || pos < -600) return;
|
||||
|
||||
loader();
|
||||
}, [loader, isLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', onLoadMore);
|
||||
|
||||
return () => window.removeEventListener('scroll', onLoadMore);
|
||||
}, [onLoadMore]);
|
||||
};
|
27
src/hooks/dom/useInputPasteUpload.ts
Normal file
27
src/hooks/dom/useInputPasteUpload.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { useCallback, useEffect } from 'react';
|
||||
import { getImageFromPaste } from '~/utils/uploader';
|
||||
|
||||
// useInputPasteUpload attaches event listener to input, that calls onUpload if user pasted any image
|
||||
export const useInputPasteUpload = (
|
||||
input: HTMLTextAreaElement | HTMLInputElement | undefined,
|
||||
onUpload: (files: File[]) => void
|
||||
) => {
|
||||
const onPaste = useCallback(
|
||||
async event => {
|
||||
const image = await getImageFromPaste(event);
|
||||
|
||||
if (!image) return;
|
||||
|
||||
onUpload([image]);
|
||||
},
|
||||
[onUpload]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!input) return;
|
||||
|
||||
input.addEventListener('paste', onPaste);
|
||||
|
||||
return () => input.removeEventListener('paste', onPaste);
|
||||
}, [input, onPaste]);
|
||||
};
|
38
src/hooks/dom/usePopperModifiers.ts
Normal file
38
src/hooks/dom/usePopperModifiers.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { useMemo } from 'react';
|
||||
import { Modifier } from 'react-popper';
|
||||
|
||||
const sameWidth = {
|
||||
name: 'sameWidth',
|
||||
enabled: true,
|
||||
phase: 'beforeWrite',
|
||||
requires: ['computeStyles'],
|
||||
fn: ({ state }: { state: any }) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
state.styles.popper.width = `${state.rects.reference.width}px`;
|
||||
},
|
||||
effect: ({ state }: { state: any }) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
state.elements.popper.style.width = `${state.elements.reference.offsetWidth}px`;
|
||||
},
|
||||
};
|
||||
|
||||
export const usePopperModifiers = (offsetX = 0, offsetY = 10, justify?: boolean): Modifier<any>[] =>
|
||||
useMemo(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [offsetX, offsetY],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
padding: 10,
|
||||
},
|
||||
},
|
||||
...(justify ? [sameWidth] : []),
|
||||
] as Modifier<any>[],
|
||||
[offsetX, offsetY, justify]
|
||||
);
|
8
src/hooks/dom/useResizeHandler.ts
Normal file
8
src/hooks/dom/useResizeHandler.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { useEffect } from 'react';
|
||||
|
||||
export const useResizeHandler = (onResize: () => any) => {
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', onResize);
|
||||
return () => window.removeEventListener('resize', onResize);
|
||||
}, [onResize]);
|
||||
};
|
19
src/hooks/dom/useScrollToTop.ts
Normal file
19
src/hooks/dom/useScrollToTop.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { useEffect } from 'react';
|
||||
import { NEW_COMMENT_CLASSNAME } from '~/constants/comment';
|
||||
|
||||
export const useScrollToTop = (deps?: any[]) => {
|
||||
useEffect(() => {
|
||||
const targetElement = document.querySelector(`.${NEW_COMMENT_CLASSNAME}`);
|
||||
|
||||
if (!targetElement) {
|
||||
window.scrollTo(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const bounds = targetElement.getBoundingClientRect();
|
||||
window.scrollTo({
|
||||
top: bounds.top - 100,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}, deps || []);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue