@teamturing/react-kit 2.11.0 → 2.13.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.
- package/dist/core/Button/index.d.ts +2 -2
- package/dist/core/IconButton/index.d.ts +2 -2
- package/dist/core/Overlay/index.d.ts +18 -0
- package/dist/core/OverlayPopper/index.d.ts +16 -0
- package/dist/hook/useFocusTrap.d.ts +13 -0
- package/dist/hook/useFocusZone.d.ts +15 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +2523 -164
- package/esm/core/Button/index.js +2 -2
- package/esm/core/Dialog/index.js +8 -36
- package/esm/core/IconButton/index.js +1 -1
- package/esm/core/Overlay/index.js +92 -0
- package/esm/core/OverlayPopper/index.js +69 -0
- package/esm/hook/useFocusTrap.js +39 -0
- package/esm/hook/useFocusZone.js +35 -0
- package/esm/index.js +6 -0
- package/esm/node_modules/@floating-ui/core/dist/floating-ui.core.js +475 -0
- package/esm/node_modules/@floating-ui/dom/dist/floating-ui.dom.js +599 -0
- package/esm/node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.js +229 -0
- package/esm/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +121 -0
- package/esm/node_modules/@floating-ui/utils/dom/dist/floating-ui.utils.dom.js +128 -0
- package/esm/node_modules/@primer/behaviors/dist/esm/focus-trap.js +105 -0
- package/esm/node_modules/@primer/behaviors/dist/esm/focus-zone.js +436 -0
- package/esm/node_modules/@primer/behaviors/dist/esm/polyfills/event-listener-signal.js +38 -0
- package/esm/node_modules/@primer/behaviors/dist/esm/utils/iterate-focusable-elements.js +65 -0
- package/esm/node_modules/@primer/behaviors/dist/esm/utils/unique-id.js +6 -0
- package/esm/node_modules/@primer/behaviors/dist/esm/utils/user-agent.js +9 -0
- package/package.json +5 -2
package/esm/core/Button/index.js
CHANGED
|
@@ -180,7 +180,7 @@ const BaseButton = styled(UnstyledButton)(({
|
|
|
180
180
|
}
|
|
181
181
|
} : {})
|
|
182
182
|
},
|
|
183
|
-
|
|
183
|
+
neutral: {
|
|
184
184
|
'backgroundColor': color['bg/neutral'],
|
|
185
185
|
'color': color['text/neutral'],
|
|
186
186
|
'& svg': {
|
|
@@ -333,7 +333,7 @@ const BaseSpinner = styled(Spinner)(variant({
|
|
|
333
333
|
secondary: {
|
|
334
334
|
color: color['icon/primary']
|
|
335
335
|
},
|
|
336
|
-
|
|
336
|
+
neutral: {
|
|
337
337
|
color: color['icon/accent/gray']
|
|
338
338
|
},
|
|
339
339
|
outlined: {
|
package/esm/core/Dialog/index.js
CHANGED
|
@@ -6,19 +6,13 @@ import '../../packages/token-studio/esm/token/typography/index.js';
|
|
|
6
6
|
import styled from 'styled-components';
|
|
7
7
|
import IconButton from '../IconButton/index.js';
|
|
8
8
|
import View from '../View/index.js';
|
|
9
|
+
import useFocusTrap from '../../hook/useFocusTrap.js';
|
|
9
10
|
import { sx } from '../../utils/styled-system/index.js';
|
|
10
11
|
import MotionView from '../MotionView/index.js';
|
|
11
12
|
import { j as jsxRuntimeExports } from '../../node_modules/react/jsx-runtime.js';
|
|
12
13
|
import { AnimatePresence } from '../../node_modules/framer-motion/dist/es/components/AnimatePresence/index.js';
|
|
13
14
|
import { cubicBezier } from '../../node_modules/framer-motion/dist/es/easing/cubic-bezier.js';
|
|
14
15
|
|
|
15
|
-
function visible(el) {
|
|
16
|
-
return !el.hidden && (!el.type || el.type !== 'hidden') && (el.offsetWidth > 0 || el.offsetHeight > 0);
|
|
17
|
-
}
|
|
18
|
-
function focusable(el) {
|
|
19
|
-
const inputEl = el;
|
|
20
|
-
return inputEl.tabIndex >= 0 && !inputEl.disabled && visible(inputEl);
|
|
21
|
-
}
|
|
22
16
|
const Dialog = ({
|
|
23
17
|
children,
|
|
24
18
|
isOpen,
|
|
@@ -38,41 +32,19 @@ const Dialog = ({
|
|
|
38
32
|
handleDismiss?.();
|
|
39
33
|
}
|
|
40
34
|
}, [handleDismiss, dialogRef, overlayRef]);
|
|
41
|
-
const getFocusableItem = useCallback((e, movement) => {
|
|
42
|
-
if (dialogRef.current) {
|
|
43
|
-
const items = Array.from(dialogRef.current.querySelectorAll('*')).filter(focusable);
|
|
44
|
-
if (items.length === 0) return;
|
|
45
|
-
e.preventDefault();
|
|
46
|
-
const focusedElement = document.activeElement;
|
|
47
|
-
if (!focusedElement) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const index = items.indexOf(focusedElement);
|
|
51
|
-
const offsetIndex = index + movement;
|
|
52
|
-
const fallbackIndex = movement === 1 ? 0 : items.length - 1;
|
|
53
|
-
const focusableItem = items[offsetIndex] || items[fallbackIndex];
|
|
54
|
-
return focusableItem;
|
|
55
|
-
}
|
|
56
|
-
}, [dialogRef]);
|
|
57
|
-
const handleTab = useCallback(e => {
|
|
58
|
-
const movement = e.shiftKey ? -1 : 1;
|
|
59
|
-
const focusableItem = getFocusableItem(e, movement);
|
|
60
|
-
if (!focusableItem) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
focusableItem.focus();
|
|
64
|
-
}, [getFocusableItem]);
|
|
65
35
|
const handleKeyDown = useCallback(event => {
|
|
66
36
|
switch (event.key) {
|
|
67
|
-
case 'Tab':
|
|
68
|
-
handleTab(event);
|
|
69
|
-
break;
|
|
70
37
|
case 'Escape':
|
|
71
38
|
handleDismiss?.();
|
|
72
39
|
event.stopPropagation();
|
|
73
40
|
break;
|
|
74
41
|
}
|
|
75
42
|
}, [handleDismiss]);
|
|
43
|
+
useFocusTrap({
|
|
44
|
+
containerRef: dialogRef,
|
|
45
|
+
initialFocusRef: closeButtonRef,
|
|
46
|
+
disabled: !isOpen
|
|
47
|
+
});
|
|
76
48
|
useEffect(() => {
|
|
77
49
|
if (isOpen) {
|
|
78
50
|
document.addEventListener('click', handleOutsideClick);
|
|
@@ -118,7 +90,7 @@ const Dialog = ({
|
|
|
118
90
|
height: '100%',
|
|
119
91
|
zIndex: 9999
|
|
120
92
|
},
|
|
121
|
-
children: [/*#__PURE__*/jsxRuntimeExports.jsx(
|
|
93
|
+
children: [/*#__PURE__*/jsxRuntimeExports.jsx(Blanket, {
|
|
122
94
|
ref: overlayRef
|
|
123
95
|
}), /*#__PURE__*/jsxRuntimeExports.jsxs(BaseDialog, {
|
|
124
96
|
ref: dialogRef,
|
|
@@ -162,7 +134,7 @@ const Dialog = ({
|
|
|
162
134
|
}) : null
|
|
163
135
|
});
|
|
164
136
|
};
|
|
165
|
-
const
|
|
137
|
+
const Blanket = styled.span`
|
|
166
138
|
&:before {
|
|
167
139
|
position: fixed;
|
|
168
140
|
top: 0;
|
|
@@ -109,7 +109,7 @@ const BaseIconButton = styled(UnstyledButton)(({
|
|
|
109
109
|
color: color['icon/disabled']
|
|
110
110
|
} : {})
|
|
111
111
|
},
|
|
112
|
-
'
|
|
112
|
+
'neutral': {
|
|
113
113
|
'backgroundColor': color['bg/neutral'],
|
|
114
114
|
'color': color['icon/accent/gray'],
|
|
115
115
|
'&:hover:not(:disabled)': {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { forwardRef, useRef, useImperativeHandle, useCallback, useEffect } from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import '../../node_modules/styled-system/dist/index.esm.js';
|
|
4
|
+
import { forcePixelValue } from '../../utils/forcePixelValue.js';
|
|
5
|
+
import { sx } from '../../utils/styled-system/index.js';
|
|
6
|
+
import { j as jsxRuntimeExports } from '../../node_modules/react/jsx-runtime.js';
|
|
7
|
+
import { variant } from '../../node_modules/@styled-system/variant/dist/index.esm.js';
|
|
8
|
+
import { iterateFocusableElements } from '../../node_modules/@primer/behaviors/dist/esm/utils/iterate-focusable-elements.js';
|
|
9
|
+
|
|
10
|
+
const Overlay = ({
|
|
11
|
+
children,
|
|
12
|
+
isOpen,
|
|
13
|
+
onDismiss,
|
|
14
|
+
size = 'm',
|
|
15
|
+
ignoreOutsideClickRefs = [],
|
|
16
|
+
...props
|
|
17
|
+
}, ref) => {
|
|
18
|
+
const overlayRef = useRef(null);
|
|
19
|
+
useImperativeHandle(ref, () => overlayRef.current);
|
|
20
|
+
const handleDismiss = useCallback(() => onDismiss?.(), [onDismiss]);
|
|
21
|
+
const handleOutsideClick = useCallback(e => {
|
|
22
|
+
if (overlayRef.current && e.target instanceof Node && !overlayRef.current.contains(e.target) && ignoreOutsideClickRefs && !ignoreOutsideClickRefs.some(({
|
|
23
|
+
current
|
|
24
|
+
}) => current?.contains(e.target))) {
|
|
25
|
+
handleDismiss?.();
|
|
26
|
+
}
|
|
27
|
+
}, [handleDismiss, overlayRef]);
|
|
28
|
+
const handleKeyDown = useCallback(event => {
|
|
29
|
+
switch (event.key) {
|
|
30
|
+
case 'Escape':
|
|
31
|
+
handleDismiss?.();
|
|
32
|
+
event.stopPropagation();
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}, [handleDismiss]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (overlayRef.current) {
|
|
38
|
+
const firstItem = iterateFocusableElements(overlayRef.current).next().value;
|
|
39
|
+
firstItem?.focus();
|
|
40
|
+
}
|
|
41
|
+
}, [isOpen]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (isOpen) {
|
|
44
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
45
|
+
return () => {
|
|
46
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}, [isOpen, handleKeyDown]);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (isOpen) {
|
|
52
|
+
document.addEventListener('click', handleOutsideClick);
|
|
53
|
+
return () => {
|
|
54
|
+
document.removeEventListener('click', handleOutsideClick);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}, [isOpen, handleOutsideClick]);
|
|
58
|
+
return isOpen ? /*#__PURE__*/jsxRuntimeExports.jsx(BaseOverlay, {
|
|
59
|
+
ref: overlayRef,
|
|
60
|
+
size: size,
|
|
61
|
+
...props,
|
|
62
|
+
children: children
|
|
63
|
+
}) : null;
|
|
64
|
+
};
|
|
65
|
+
const BaseOverlay = styled.div`
|
|
66
|
+
position: absolute;
|
|
67
|
+
box-shadow: ${({
|
|
68
|
+
theme
|
|
69
|
+
}) => theme.shadows['shadow/overlay']};
|
|
70
|
+
background-color: ${({
|
|
71
|
+
theme
|
|
72
|
+
}) => theme.colors['surface/overlay']};
|
|
73
|
+
border-radius: ${({
|
|
74
|
+
theme
|
|
75
|
+
}) => forcePixelValue(theme.radii.s)};
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
margin: auto;
|
|
78
|
+
z-index: 99999;
|
|
79
|
+
|
|
80
|
+
${variant({
|
|
81
|
+
prop: 'size',
|
|
82
|
+
variants: {
|
|
83
|
+
m: {
|
|
84
|
+
width: forcePixelValue(180)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
})}
|
|
88
|
+
${sx}
|
|
89
|
+
`;
|
|
90
|
+
var Overlay$1 = /*#__PURE__*/forwardRef(Overlay);
|
|
91
|
+
|
|
92
|
+
export { Overlay$1 as default };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useFloating } from '../../node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.js';
|
|
2
|
+
import space from '../../packages/token-studio/esm/token/space/index.js';
|
|
3
|
+
import '../../packages/token-studio/esm/token/typography/index.js';
|
|
4
|
+
import { Children, cloneElement } from 'react';
|
|
5
|
+
import useFocusTrap from '../../hook/useFocusTrap.js';
|
|
6
|
+
import useFocusZone from '../../hook/useFocusZone.js';
|
|
7
|
+
import useToggleHandler from '../../hook/useToggleHandler.js';
|
|
8
|
+
import Overlay from '../Overlay/index.js';
|
|
9
|
+
import { j as jsxRuntimeExports } from '../../node_modules/react/jsx-runtime.js';
|
|
10
|
+
import { autoUpdate } from '../../node_modules/@floating-ui/dom/dist/floating-ui.dom.js';
|
|
11
|
+
import { offset, flip, shift } from '../../node_modules/@floating-ui/core/dist/floating-ui.core.js';
|
|
12
|
+
|
|
13
|
+
const OverlayPopper = ({
|
|
14
|
+
children: propChildren,
|
|
15
|
+
renderOverlay,
|
|
16
|
+
placement = 'bottom-start',
|
|
17
|
+
focusZoneSettings,
|
|
18
|
+
focusTrapSettings
|
|
19
|
+
}) => {
|
|
20
|
+
const {
|
|
21
|
+
refs,
|
|
22
|
+
floatingStyles,
|
|
23
|
+
isPositioned
|
|
24
|
+
} = useFloating({
|
|
25
|
+
placement,
|
|
26
|
+
whileElementsMounted: autoUpdate,
|
|
27
|
+
middleware: [offset(space[1]), flip(), shift()],
|
|
28
|
+
strategy: 'fixed'
|
|
29
|
+
});
|
|
30
|
+
const {
|
|
31
|
+
state: isOpen,
|
|
32
|
+
toggle: toggleOverlay,
|
|
33
|
+
off: closeOverlay
|
|
34
|
+
} = useToggleHandler({
|
|
35
|
+
initialState: false
|
|
36
|
+
});
|
|
37
|
+
const children = Children.map(propChildren, child => /*#__PURE__*/cloneElement(child, {
|
|
38
|
+
onClick: toggleOverlay,
|
|
39
|
+
tabIndex: 0,
|
|
40
|
+
...{
|
|
41
|
+
ref: refs.setReference
|
|
42
|
+
}
|
|
43
|
+
}));
|
|
44
|
+
useFocusZone({
|
|
45
|
+
containerRef: refs.floating,
|
|
46
|
+
disabled: !isOpen || !isPositioned,
|
|
47
|
+
...focusZoneSettings
|
|
48
|
+
});
|
|
49
|
+
useFocusTrap({
|
|
50
|
+
containerRef: refs.floating,
|
|
51
|
+
disabled: !isOpen || !isPositioned,
|
|
52
|
+
...focusTrapSettings
|
|
53
|
+
});
|
|
54
|
+
return /*#__PURE__*/jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, {
|
|
55
|
+
children: [children, /*#__PURE__*/jsxRuntimeExports.jsx(Overlay, {
|
|
56
|
+
ref: refs.setFloating,
|
|
57
|
+
isOpen: isOpen,
|
|
58
|
+
onDismiss: closeOverlay,
|
|
59
|
+
ignoreOutsideClickRefs: [refs.reference],
|
|
60
|
+
style: floatingStyles,
|
|
61
|
+
children: renderOverlay({
|
|
62
|
+
isOpen,
|
|
63
|
+
closeOverlay
|
|
64
|
+
})
|
|
65
|
+
})]
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export { OverlayPopper as default };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import useProvidedOrCreatedRef from './useProvidedOrCreatedRef.js';
|
|
3
|
+
import { focusTrap } from '../node_modules/@primer/behaviors/dist/esm/focus-trap.js';
|
|
4
|
+
|
|
5
|
+
const useFocusTrap = (settings, dependencies = []) => {
|
|
6
|
+
const containerRef = useProvidedOrCreatedRef(settings?.containerRef);
|
|
7
|
+
const initialFocusRef = useProvidedOrCreatedRef(settings?.initialFocusRef);
|
|
8
|
+
const disabled = settings?.disabled;
|
|
9
|
+
const abortController = React__default.useRef();
|
|
10
|
+
const previousFocusedElement = React__default.useRef(null);
|
|
11
|
+
if (!previousFocusedElement.current && !settings?.disabled) {
|
|
12
|
+
previousFocusedElement.current = document.activeElement;
|
|
13
|
+
}
|
|
14
|
+
const disableTrap = () => {
|
|
15
|
+
abortController.current?.abort();
|
|
16
|
+
if (settings?.restoreFocusOnCleanUp && previousFocusedElement.current instanceof HTMLElement) {
|
|
17
|
+
previousFocusedElement.current.focus();
|
|
18
|
+
previousFocusedElement.current = null;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
React__default.useEffect(() => {
|
|
22
|
+
if (containerRef.current instanceof HTMLElement) {
|
|
23
|
+
if (!disabled) {
|
|
24
|
+
abortController.current = focusTrap(containerRef.current, initialFocusRef.current ?? undefined);
|
|
25
|
+
return () => {
|
|
26
|
+
disableTrap();
|
|
27
|
+
};
|
|
28
|
+
} else {
|
|
29
|
+
disableTrap();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}, [containerRef, initialFocusRef, disabled, ...dependencies]);
|
|
33
|
+
return {
|
|
34
|
+
containerRef,
|
|
35
|
+
initialFocusRef
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { useFocusTrap as default };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useRef, useEffect } from 'react';
|
|
2
|
+
import useProvidedOrCreatedRef from './useProvidedOrCreatedRef.js';
|
|
3
|
+
import { focusZone } from '../node_modules/@primer/behaviors/dist/esm/focus-zone.js';
|
|
4
|
+
export { FocusKeys } from '../node_modules/@primer/behaviors/dist/esm/focus-zone.js';
|
|
5
|
+
|
|
6
|
+
const useFocusZone = (settings = {}, dependencies = []) => {
|
|
7
|
+
const containerRef = useProvidedOrCreatedRef(settings.containerRef);
|
|
8
|
+
const useActiveDescendant = !!settings.activeDescendantFocus;
|
|
9
|
+
const passedActiveDescendantRef = typeof settings.activeDescendantFocus === 'boolean' || !settings.activeDescendantFocus ? undefined : settings.activeDescendantFocus;
|
|
10
|
+
const activeDescendantControlRef = useProvidedOrCreatedRef(passedActiveDescendantRef);
|
|
11
|
+
const disabled = settings.disabled;
|
|
12
|
+
const abortController = useRef();
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (containerRef.current instanceof HTMLElement && (!useActiveDescendant || activeDescendantControlRef.current instanceof HTMLElement)) {
|
|
15
|
+
if (!disabled) {
|
|
16
|
+
const defaultSettings = {
|
|
17
|
+
...settings,
|
|
18
|
+
activeDescendantControl: activeDescendantControlRef.current ?? undefined
|
|
19
|
+
};
|
|
20
|
+
abortController.current = focusZone(containerRef.current, defaultSettings);
|
|
21
|
+
return () => {
|
|
22
|
+
abortController.current?.abort();
|
|
23
|
+
};
|
|
24
|
+
} else {
|
|
25
|
+
abortController.current?.abort();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}, [disabled, ...dependencies]);
|
|
29
|
+
return {
|
|
30
|
+
containerRef,
|
|
31
|
+
activeDescendantControlRef
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export { useFocusZone as default };
|
package/esm/index.js
CHANGED
|
@@ -11,6 +11,8 @@ export { default as IconToggleButton } from './core/IconToggleButton/index.js';
|
|
|
11
11
|
export { default as Image } from './core/Image/index.js';
|
|
12
12
|
export { default as ItemList } from './core/ItemList/index.js';
|
|
13
13
|
export { default as MotionView } from './core/MotionView/index.js';
|
|
14
|
+
export { default as Overlay } from './core/Overlay/index.js';
|
|
15
|
+
export { default as OverlayPopper } from './core/OverlayPopper/index.js';
|
|
14
16
|
export { default as Space } from './core/Space/index.js';
|
|
15
17
|
export { default as Spinner } from './core/Spinner/index.js';
|
|
16
18
|
export { default as Stack } from './core/Stack/index.js';
|
|
@@ -26,6 +28,10 @@ export { default as View } from './core/View/index.js';
|
|
|
26
28
|
export { default as UnstyledButton } from './core/_UnstyledButton.js';
|
|
27
29
|
export { default as EnigmaUI } from './enigma/EnigmaUI/index.js';
|
|
28
30
|
export { default as useDevice } from './hook/useDevice.js';
|
|
31
|
+
export { default as useDialogHandler } from './hook/useDialogHandler.js';
|
|
32
|
+
export { default as useFocusTrap } from './hook/useFocusTrap.js';
|
|
33
|
+
export { default as useFocusZone } from './hook/useFocusZone.js';
|
|
34
|
+
export { default as useMediaQuery } from './hook/useMediaQuery.js';
|
|
29
35
|
export { default as useOutsideClick } from './hook/useOutsideClick.js';
|
|
30
36
|
export { default as useProvidedOrCreatedRef } from './hook/useProvidedOrCreatedRef.js';
|
|
31
37
|
export { default as useResize } from './hook/useResize.js';
|