@teamturing/react-kit 2.22.3 → 2.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,9 +4,10 @@ import { FocusTrapHookSettings } from '../../hook/useFocusTrap';
4
4
  import { FocusZoneHookSettings } from '../../hook/useFocusZone';
5
5
  import { OverlayProps } from '../Overlay';
6
6
  type Props = {
7
- children: ReactNode | ((popperProps: HTMLAttributes<HTMLElement>, { isOpen, openOverlay }: {
7
+ children: ReactNode | ((popperProps: HTMLAttributes<HTMLElement>, { isOpen, openOverlay, closeOverlay }: {
8
8
  isOpen: boolean;
9
9
  openOverlay: () => void;
10
+ closeOverlay: () => void;
10
11
  }) => ReactNode);
11
12
  renderOverlay: (overlayProps: OverlayProps & {
12
13
  ref?: ForwardedRef<HTMLDivElement>;
@@ -16,12 +17,13 @@ type Props = {
16
17
  }, { elements }: {
17
18
  elements: UseFloatingReturn['elements'];
18
19
  }) => ReactNode;
20
+ triggeredBy?: 'click' | 'hover';
19
21
  placement?: Placement;
20
22
  focusZoneSettings?: Partial<FocusZoneHookSettings>;
21
23
  focusTrapSettings?: Partial<FocusTrapHookSettings>;
22
24
  onOpen?: () => void;
23
25
  onClose?: () => void;
24
26
  };
25
- declare const OverlayPopper: ({ children: propChildren, renderOverlay, placement, focusZoneSettings, focusTrapSettings, onOpen, onClose, }: Props) => import("react/jsx-runtime").JSX.Element;
27
+ declare const OverlayPopper: ({ children: propChildren, renderOverlay, triggeredBy, placement, focusZoneSettings, focusTrapSettings, onOpen, onClose, }: Props) => import("react/jsx-runtime").JSX.Element;
26
28
  export default OverlayPopper;
27
29
  export type { Props as OverlayPopperProps };
@@ -0,0 +1,10 @@
1
+ type Options<T extends (...args: any) => void> = {
2
+ func: T;
3
+ /**
4
+ * ms
5
+ */
6
+ delay?: number;
7
+ };
8
+ declare const useDelayedFunction: <T extends (...args: any) => void>({ func, delay }: Options<T>) => T;
9
+ export default useDelayedFunction;
10
+ export type { Options as UseDelayedFunctionOptions };
@@ -0,0 +1,10 @@
1
+ /// <reference types="react" />
2
+ type Options<T extends HTMLElement = HTMLDivElement> = {
3
+ targetRef?: React.RefObject<T>;
4
+ };
5
+ declare const useHover: <T extends HTMLElement = HTMLDivElement>({ targetRef }: Options<T>) => {
6
+ ref: import("react").RefObject<T>;
7
+ hovered: boolean;
8
+ };
9
+ export default useHover;
10
+ export type { Options as UseHoverOptions };
@@ -0,0 +1,12 @@
1
+ import { RefObject } from 'react';
2
+ type Options<T extends HTMLElement> = {
3
+ resetOnExit?: boolean;
4
+ targetRef?: RefObject<T>;
5
+ };
6
+ declare const useMousePosition: <T extends HTMLElement = any>({ targetRef, resetOnExit }: Options<T>) => {
7
+ x: number;
8
+ y: number;
9
+ ref: RefObject<T>;
10
+ };
11
+ export default useMousePosition;
12
+ export type { Options as UseMousePositionOptions };
package/dist/index.d.ts CHANGED
@@ -92,10 +92,13 @@ export { default as EnigmaUI } from './enigma/EnigmaUI';
92
92
  /**
93
93
  * hooks
94
94
  */
95
+ export { default as useDelayedFunction } from './hook/useDelayedFunction';
95
96
  export { default as useDevice } from './hook/useDevice';
96
97
  export { default as useFocusTrap } from './hook/useFocusTrap';
97
98
  export { default as useFocusZone } from './hook/useFocusZone';
99
+ export { default as useHover } from './hook/useHover';
98
100
  export { default as useMediaQuery } from './hook/useMediaQuery';
101
+ export { default as useMousePosition } from './hook/useMousePosition';
99
102
  export { default as useOutsideClick } from './hook/useOutsideClick';
100
103
  export { default as useProvidedOrCreatedRef } from './hook/useProvidedOrCreatedRef';
101
104
  export { default as useRelocation } from './hook/useRelocation';
package/dist/index.js CHANGED
@@ -20835,6 +20835,22 @@ function useFloating(options) {
20835
20835
  }), [data, update, refs, elements, floatingStyles]);
20836
20836
  }
20837
20837
 
20838
+ const useDelayedFunction = ({
20839
+ func,
20840
+ delay
20841
+ }) => {
20842
+ const timeout = React.useRef(-1);
20843
+ const delayedFunc = (...args) => {
20844
+ if (delay === 0 || delay === undefined) {
20845
+ func(...args);
20846
+ } else {
20847
+ timeout.current = window.setTimeout(func, delay);
20848
+ }
20849
+ };
20850
+ React.useEffect(() => () => window.clearTimeout(timeout.current), []);
20851
+ return delayedFunc;
20852
+ };
20853
+
20838
20854
  const useFocusZone = (settings = {}, dependencies = []) => {
20839
20855
  const containerRef = useProvidedOrCreatedRef(settings.containerRef);
20840
20856
  const useActiveDescendant = !!settings.activeDescendantFocus;
@@ -20864,6 +20880,50 @@ const useFocusZone = (settings = {}, dependencies = []) => {
20864
20880
  };
20865
20881
  };
20866
20882
 
20883
+ const useMousePosition = ({
20884
+ targetRef,
20885
+ resetOnExit = false
20886
+ }) => {
20887
+ const [position, setPosition] = React.useState({
20888
+ x: 0,
20889
+ y: 0
20890
+ });
20891
+ const ref = useProvidedOrCreatedRef(targetRef);
20892
+ const setMousePosition = event => {
20893
+ if (ref.current) {
20894
+ const rect = event.currentTarget.getBoundingClientRect();
20895
+ const x = Math.max(0, Math.round(event.pageX - rect.left - (window.pageXOffset || window.scrollX)));
20896
+ const y = Math.max(0, Math.round(event.pageY - rect.top - (window.pageYOffset || window.scrollY)));
20897
+ setPosition({
20898
+ x,
20899
+ y
20900
+ });
20901
+ } else {
20902
+ setPosition({
20903
+ x: event.clientX,
20904
+ y: event.clientY
20905
+ });
20906
+ }
20907
+ };
20908
+ const resetMousePosition = () => setPosition({
20909
+ x: 0,
20910
+ y: 0
20911
+ });
20912
+ React.useEffect(() => {
20913
+ const element = ref?.current ? ref.current : document;
20914
+ element.addEventListener('mousemove', setMousePosition);
20915
+ if (resetOnExit) element.addEventListener('mouseleave', resetMousePosition);
20916
+ return () => {
20917
+ element.removeEventListener('mousemove', setMousePosition);
20918
+ if (resetOnExit) element.removeEventListener('mouseleave', resetMousePosition);
20919
+ };
20920
+ }, [ref.current]);
20921
+ return {
20922
+ ref,
20923
+ ...position
20924
+ };
20925
+ };
20926
+
20867
20927
  const useToggleState = ({
20868
20928
  initialState = false
20869
20929
  }) => {
@@ -20883,6 +20943,7 @@ const useToggleState = ({
20883
20943
  const OverlayPopper = ({
20884
20944
  children: propChildren,
20885
20945
  renderOverlay,
20946
+ triggeredBy = 'click',
20886
20947
  placement = 'bottom-start',
20887
20948
  focusZoneSettings,
20888
20949
  focusTrapSettings,
@@ -20904,6 +20965,12 @@ const OverlayPopper = ({
20904
20965
  const [isOpen, toggleOverlay, openOverlay, closeOverlay] = useToggleState({
20905
20966
  initialState: false
20906
20967
  });
20968
+ const {
20969
+ x,
20970
+ y
20971
+ } = useMousePosition({
20972
+ targetRef: refs.floating
20973
+ });
20907
20974
  const handleOverlayToggle = () => {
20908
20975
  if (!isOpen) onOpen?.();else onClose?.();
20909
20976
  toggleOverlay();
@@ -20916,28 +20983,40 @@ const OverlayPopper = ({
20916
20983
  onClose?.();
20917
20984
  closeOverlay();
20918
20985
  };
20986
+ const delayedHandleOverlayClose = useDelayedFunction({
20987
+ func: handleOverlayClose,
20988
+ delay: 150
20989
+ });
20919
20990
  const handleDismiss = () => {
20920
20991
  handleOverlayClose();
20921
20992
  };
20922
20993
  const defaultPopperProps = {
20923
- onClick: handleOverlayToggle,
20924
- onKeyDown: e => {
20925
- if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
20926
- e.preventDefault();
20927
- handleOverlayOpen();
20928
- }
20929
- e.stopPropagation();
20930
- },
20931
20994
  tabIndex: 0,
20995
+ ...(triggeredBy === 'click' ? {
20996
+ onClick: handleOverlayToggle,
20997
+ onKeyDown: e => {
20998
+ if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
20999
+ e.preventDefault();
21000
+ handleOverlayOpen();
21001
+ }
21002
+ e.stopPropagation();
21003
+ }
21004
+ } : triggeredBy === 'hover' ? {
21005
+ onMouseEnter: handleOverlayOpen,
21006
+ onMouseLeave: () => {
21007
+ if (x === 0 && y === 0) {
21008
+ delayedHandleOverlayClose();
21009
+ }
21010
+ }
21011
+ } : {}),
20932
21012
  ...{
20933
21013
  ref: refs.setReference
20934
21014
  }
20935
21015
  };
20936
- const children = isFunction$1(propChildren) ? propChildren({
20937
- ...defaultPopperProps
20938
- }, {
21016
+ const children = isFunction$1(propChildren) ? propChildren(defaultPopperProps, {
20939
21017
  isOpen,
20940
- openOverlay: handleOverlayOpen
21018
+ openOverlay: handleOverlayOpen,
21019
+ closeOverlay: handleOverlayClose
20941
21020
  }) : React.Children.map(propChildren, child => /*#__PURE__*/React.cloneElement(child, {
20942
21021
  ...defaultPopperProps
20943
21022
  }));
@@ -20960,7 +21039,9 @@ const OverlayPopper = ({
20960
21039
  style: {
20961
21040
  ...floatingStyles
20962
21041
  },
20963
- onDismiss: handleDismiss
21042
+ onDismiss: handleDismiss,
21043
+ onMouseEnter: openOverlay,
21044
+ onMouseLeave: delayedHandleOverlayClose
20964
21045
  }, {
20965
21046
  isOpen,
20966
21047
  closeOverlay: handleOverlayClose
@@ -26000,6 +26081,30 @@ const useDevice = () => {
26000
26081
  return deviceState || {};
26001
26082
  };
26002
26083
 
26084
+ const useHover = ({
26085
+ targetRef
26086
+ }) => {
26087
+ const [hovered, setHovered] = React.useState(false);
26088
+ const ref = useProvidedOrCreatedRef(targetRef);
26089
+ const onMouseEnter = React.useCallback(() => setHovered(true), []);
26090
+ const onMouseLeave = React.useCallback(() => setHovered(false), []);
26091
+ React.useEffect(() => {
26092
+ if (ref.current) {
26093
+ ref.current.addEventListener('mouseenter', onMouseEnter);
26094
+ ref.current.addEventListener('mouseleave', onMouseLeave);
26095
+ return () => {
26096
+ ref.current?.removeEventListener('mouseenter', onMouseEnter);
26097
+ ref.current?.removeEventListener('mouseleave', onMouseLeave);
26098
+ };
26099
+ }
26100
+ return undefined;
26101
+ }, []);
26102
+ return {
26103
+ ref,
26104
+ hovered
26105
+ };
26106
+ };
26107
+
26003
26108
  /**
26004
26109
  * 특정 컴포넌트을 제외한 바깥쪽을 클릭했을 때를 핸들링하기 위한 훅입니다.
26005
26110
  */
@@ -26078,10 +26183,13 @@ exports.lineClamp = lineClamp;
26078
26183
  exports.sx = sx;
26079
26184
  exports.textDecoration = textDecoration;
26080
26185
  exports.theme = theme;
26186
+ exports.useDelayedFunction = useDelayedFunction;
26081
26187
  exports.useDevice = useDevice;
26082
26188
  exports.useFocusTrap = useFocusTrap;
26083
26189
  exports.useFocusZone = useFocusZone;
26190
+ exports.useHover = useHover;
26084
26191
  exports.useMediaQuery = useMediaQuery;
26192
+ exports.useMousePosition = useMousePosition;
26085
26193
  exports.useOutsideClick = useOutsideClick;
26086
26194
  exports.useProvidedOrCreatedRef = useProvidedOrCreatedRef;
26087
26195
  exports.useRelocation = useRelocation;
@@ -2,8 +2,10 @@ import { useFloating } from '../../node_modules/@floating-ui/react-dom/dist/floa
2
2
  import { isFunction } from '../../packages/utils/esm/isFunction.js';
3
3
  import { Children, cloneElement } from 'react';
4
4
  import { useTheme } from 'styled-components';
5
+ import useDelayedFunction from '../../hook/useDelayedFunction.js';
5
6
  import useFocusTrap from '../../hook/useFocusTrap.js';
6
7
  import useFocusZone from '../../hook/useFocusZone.js';
8
+ import useMousePosition from '../../hook/useMousePosition.js';
7
9
  import useToggleState from '../../hook/useToggleState.js';
8
10
  import { j as jsxRuntimeExports } from '../../node_modules/react/jsx-runtime.js';
9
11
  import { autoUpdate } from '../../node_modules/@floating-ui/dom/dist/floating-ui.dom.js';
@@ -12,6 +14,7 @@ import { offset, flip, shift } from '../../node_modules/@floating-ui/core/dist/f
12
14
  const OverlayPopper = ({
13
15
  children: propChildren,
14
16
  renderOverlay,
17
+ triggeredBy = 'click',
15
18
  placement = 'bottom-start',
16
19
  focusZoneSettings,
17
20
  focusTrapSettings,
@@ -33,6 +36,12 @@ const OverlayPopper = ({
33
36
  const [isOpen, toggleOverlay, openOverlay, closeOverlay] = useToggleState({
34
37
  initialState: false
35
38
  });
39
+ const {
40
+ x,
41
+ y
42
+ } = useMousePosition({
43
+ targetRef: refs.floating
44
+ });
36
45
  const handleOverlayToggle = () => {
37
46
  if (!isOpen) onOpen?.();else onClose?.();
38
47
  toggleOverlay();
@@ -45,28 +54,40 @@ const OverlayPopper = ({
45
54
  onClose?.();
46
55
  closeOverlay();
47
56
  };
57
+ const delayedHandleOverlayClose = useDelayedFunction({
58
+ func: handleOverlayClose,
59
+ delay: 150
60
+ });
48
61
  const handleDismiss = () => {
49
62
  handleOverlayClose();
50
63
  };
51
64
  const defaultPopperProps = {
52
- onClick: handleOverlayToggle,
53
- onKeyDown: e => {
54
- if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
55
- e.preventDefault();
56
- handleOverlayOpen();
57
- }
58
- e.stopPropagation();
59
- },
60
65
  tabIndex: 0,
66
+ ...(triggeredBy === 'click' ? {
67
+ onClick: handleOverlayToggle,
68
+ onKeyDown: e => {
69
+ if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
70
+ e.preventDefault();
71
+ handleOverlayOpen();
72
+ }
73
+ e.stopPropagation();
74
+ }
75
+ } : triggeredBy === 'hover' ? {
76
+ onMouseEnter: handleOverlayOpen,
77
+ onMouseLeave: () => {
78
+ if (x === 0 && y === 0) {
79
+ delayedHandleOverlayClose();
80
+ }
81
+ }
82
+ } : {}),
61
83
  ...{
62
84
  ref: refs.setReference
63
85
  }
64
86
  };
65
- const children = isFunction(propChildren) ? propChildren({
66
- ...defaultPopperProps
67
- }, {
87
+ const children = isFunction(propChildren) ? propChildren(defaultPopperProps, {
68
88
  isOpen,
69
- openOverlay: handleOverlayOpen
89
+ openOverlay: handleOverlayOpen,
90
+ closeOverlay: handleOverlayClose
70
91
  }) : Children.map(propChildren, child => /*#__PURE__*/cloneElement(child, {
71
92
  ...defaultPopperProps
72
93
  }));
@@ -89,7 +110,9 @@ const OverlayPopper = ({
89
110
  style: {
90
111
  ...floatingStyles
91
112
  },
92
- onDismiss: handleDismiss
113
+ onDismiss: handleDismiss,
114
+ onMouseEnter: openOverlay,
115
+ onMouseLeave: delayedHandleOverlayClose
93
116
  }, {
94
117
  isOpen,
95
118
  closeOverlay: handleOverlayClose
@@ -0,0 +1,19 @@
1
+ import { useRef, useEffect } from 'react';
2
+
3
+ const useDelayedFunction = ({
4
+ func,
5
+ delay
6
+ }) => {
7
+ const timeout = useRef(-1);
8
+ const delayedFunc = (...args) => {
9
+ if (delay === 0 || delay === undefined) {
10
+ func(...args);
11
+ } else {
12
+ timeout.current = window.setTimeout(func, delay);
13
+ }
14
+ };
15
+ useEffect(() => () => window.clearTimeout(timeout.current), []);
16
+ return delayedFunc;
17
+ };
18
+
19
+ export { useDelayedFunction as default };
@@ -0,0 +1,28 @@
1
+ import { useState, useCallback, useEffect } from 'react';
2
+ import useProvidedOrCreatedRef from './useProvidedOrCreatedRef.js';
3
+
4
+ const useHover = ({
5
+ targetRef
6
+ }) => {
7
+ const [hovered, setHovered] = useState(false);
8
+ const ref = useProvidedOrCreatedRef(targetRef);
9
+ const onMouseEnter = useCallback(() => setHovered(true), []);
10
+ const onMouseLeave = useCallback(() => setHovered(false), []);
11
+ useEffect(() => {
12
+ if (ref.current) {
13
+ ref.current.addEventListener('mouseenter', onMouseEnter);
14
+ ref.current.addEventListener('mouseleave', onMouseLeave);
15
+ return () => {
16
+ ref.current?.removeEventListener('mouseenter', onMouseEnter);
17
+ ref.current?.removeEventListener('mouseleave', onMouseLeave);
18
+ };
19
+ }
20
+ return undefined;
21
+ }, []);
22
+ return {
23
+ ref,
24
+ hovered
25
+ };
26
+ };
27
+
28
+ export { useHover as default };
@@ -0,0 +1,48 @@
1
+ import { useState, useEffect } from 'react';
2
+ import useProvidedOrCreatedRef from './useProvidedOrCreatedRef.js';
3
+
4
+ const useMousePosition = ({
5
+ targetRef,
6
+ resetOnExit = false
7
+ }) => {
8
+ const [position, setPosition] = useState({
9
+ x: 0,
10
+ y: 0
11
+ });
12
+ const ref = useProvidedOrCreatedRef(targetRef);
13
+ const setMousePosition = event => {
14
+ if (ref.current) {
15
+ const rect = event.currentTarget.getBoundingClientRect();
16
+ const x = Math.max(0, Math.round(event.pageX - rect.left - (window.pageXOffset || window.scrollX)));
17
+ const y = Math.max(0, Math.round(event.pageY - rect.top - (window.pageYOffset || window.scrollY)));
18
+ setPosition({
19
+ x,
20
+ y
21
+ });
22
+ } else {
23
+ setPosition({
24
+ x: event.clientX,
25
+ y: event.clientY
26
+ });
27
+ }
28
+ };
29
+ const resetMousePosition = () => setPosition({
30
+ x: 0,
31
+ y: 0
32
+ });
33
+ useEffect(() => {
34
+ const element = ref?.current ? ref.current : document;
35
+ element.addEventListener('mousemove', setMousePosition);
36
+ if (resetOnExit) element.addEventListener('mouseleave', resetMousePosition);
37
+ return () => {
38
+ element.removeEventListener('mousemove', setMousePosition);
39
+ if (resetOnExit) element.removeEventListener('mouseleave', resetMousePosition);
40
+ };
41
+ }, [ref.current]);
42
+ return {
43
+ ref,
44
+ ...position
45
+ };
46
+ };
47
+
48
+ export { useMousePosition as default };
package/esm/index.js CHANGED
@@ -41,10 +41,13 @@ export { default as Tooltip } from './core/Tooltip/index.js';
41
41
  export { default as View } from './core/View/index.js';
42
42
  export { default as UnstyledButton } from './core/_UnstyledButton.js';
43
43
  export { default as EnigmaUI } from './enigma/EnigmaUI/index.js';
44
+ export { default as useDelayedFunction } from './hook/useDelayedFunction.js';
44
45
  export { default as useDevice } from './hook/useDevice.js';
45
46
  export { default as useFocusTrap } from './hook/useFocusTrap.js';
46
47
  export { default as useFocusZone } from './hook/useFocusZone.js';
48
+ export { default as useHover } from './hook/useHover.js';
47
49
  export { default as useMediaQuery } from './hook/useMediaQuery.js';
50
+ export { default as useMousePosition } from './hook/useMousePosition.js';
48
51
  export { default as useOutsideClick } from './hook/useOutsideClick.js';
49
52
  export { default as useProvidedOrCreatedRef } from './hook/useProvidedOrCreatedRef.js';
50
53
  export { default as useRelocation } from './hook/useRelocation.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamturing/react-kit",
3
- "version": "2.22.3",
3
+ "version": "2.23.0",
4
4
  "description": "React components, hooks for create teamturing web application",
5
5
  "author": "Sungchang Park <psch300@gmail.com> (https://github.com/psch300)",
6
6
  "homepage": "https://github.com/weareteamturing/bombe#readme",
@@ -66,5 +66,5 @@
66
66
  "react-textarea-autosize": "^8.5.3",
67
67
  "styled-system": "^5.1.5"
68
68
  },
69
- "gitHead": "8954829533862df7f2899b1f9bc80d689b5afab8"
69
+ "gitHead": "b1e684052cbf2fb69649fd93db8a1b76cca52ad7"
70
70
  }