@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.
Files changed (28) hide show
  1. package/dist/core/Button/index.d.ts +2 -2
  2. package/dist/core/IconButton/index.d.ts +2 -2
  3. package/dist/core/Overlay/index.d.ts +18 -0
  4. package/dist/core/OverlayPopper/index.d.ts +16 -0
  5. package/dist/hook/useFocusTrap.d.ts +13 -0
  6. package/dist/hook/useFocusZone.d.ts +15 -0
  7. package/dist/index.d.ts +8 -0
  8. package/dist/index.js +2523 -164
  9. package/esm/core/Button/index.js +2 -2
  10. package/esm/core/Dialog/index.js +8 -36
  11. package/esm/core/IconButton/index.js +1 -1
  12. package/esm/core/Overlay/index.js +92 -0
  13. package/esm/core/OverlayPopper/index.js +69 -0
  14. package/esm/hook/useFocusTrap.js +39 -0
  15. package/esm/hook/useFocusZone.js +35 -0
  16. package/esm/index.js +6 -0
  17. package/esm/node_modules/@floating-ui/core/dist/floating-ui.core.js +475 -0
  18. package/esm/node_modules/@floating-ui/dom/dist/floating-ui.dom.js +599 -0
  19. package/esm/node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.js +229 -0
  20. package/esm/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +121 -0
  21. package/esm/node_modules/@floating-ui/utils/dom/dist/floating-ui.utils.dom.js +128 -0
  22. package/esm/node_modules/@primer/behaviors/dist/esm/focus-trap.js +105 -0
  23. package/esm/node_modules/@primer/behaviors/dist/esm/focus-zone.js +436 -0
  24. package/esm/node_modules/@primer/behaviors/dist/esm/polyfills/event-listener-signal.js +38 -0
  25. package/esm/node_modules/@primer/behaviors/dist/esm/utils/iterate-focusable-elements.js +65 -0
  26. package/esm/node_modules/@primer/behaviors/dist/esm/utils/unique-id.js +6 -0
  27. package/esm/node_modules/@primer/behaviors/dist/esm/utils/user-agent.js +9 -0
  28. package/package.json +5 -2
@@ -180,7 +180,7 @@ const BaseButton = styled(UnstyledButton)(({
180
180
  }
181
181
  } : {})
182
182
  },
183
- tertiary: {
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
- tertiary: {
336
+ neutral: {
337
337
  color: color['icon/accent/gray']
338
338
  },
339
339
  outlined: {
@@ -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(Overlay, {
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 Overlay = styled.span`
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
- 'tertiary': {
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';