@inceptionbg/iui 2.0.8 → 2.0.11

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 (143) hide show
  1. package/dist/icons/index.d.ts +2 -2
  2. package/dist/icons/index.js +1 -1
  3. package/dist/index.d.ts +305 -265
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/iui.css +1 -1
  7. package/idea/GridTable/GridTable.tsx +119 -0
  8. package/idea/GridTable/gridTable.scss +42 -0
  9. package/{src/components → idea}/Table/Components/Print/CustomTablePrint.tsx +2 -2
  10. package/{src/components → idea}/Table/Components/Print/TablePrint.tsx +2 -2
  11. package/{src/components → idea}/Table/Components/SetTableFilter.tsx +1 -1
  12. package/{src/components → idea}/Table/Components/TableOptions.tsx +4 -4
  13. package/idea/{Table2 → Table}/Table.tsx +151 -281
  14. package/idea/Table/hooks/useDefaultTemplate.ts +20 -0
  15. package/{src/components → idea}/Table/hooks/useTableKeyboard.ts +1 -2
  16. package/idea/Table/hooks/useTableSelect.ts +11 -0
  17. package/package.json +1 -1
  18. package/src/assets/icons/index.ts +1 -1
  19. package/src/assets/icons/light/faClipboardCheck.ts +15 -0
  20. package/src/assets/icons/light/faHouse.ts +15 -15
  21. package/src/assets/icons/light/faIdBadge.ts +15 -15
  22. package/src/assets/icons/light/faPen.ts +15 -0
  23. package/src/components/Button/IconButton.tsx +3 -1
  24. package/src/components/Dialog/Dialog.tsx +57 -123
  25. package/src/components/Dialog/components/DialogFooter.tsx +92 -0
  26. package/src/components/Dialog/hooks/useDialogKeyboard.ts +6 -5
  27. package/src/components/Header/Components/UserMenu.tsx +2 -4
  28. package/src/components/Header/Header.tsx +1 -1
  29. package/src/components/Inputs/DateInput/DateInput.tsx +107 -102
  30. package/src/components/Inputs/DateInput/components/DatePartInput.tsx +7 -3
  31. package/src/components/Inputs/InputWrapper.tsx +6 -1
  32. package/src/components/Inputs/PasswordInput.tsx +2 -1
  33. package/src/components/Inputs/SearchInput.tsx +9 -4
  34. package/src/components/Inputs/Select2/Select.tsx +64 -30
  35. package/src/components/Inputs/Select2/select.scss +13 -14
  36. package/src/components/Inputs/Selects/components/SelectWrapper.tsx +4 -2
  37. package/src/components/Inputs/Selects/utils/selectStyles.ts +9 -12
  38. package/src/components/Menu/Menu.tsx +10 -2
  39. package/src/components/Menu/MenuItem.tsx +11 -10
  40. package/src/components/Menu/hooks/useMenuPosition.tsx +49 -14
  41. package/src/components/Pullover/Pullover.tsx +138 -59
  42. package/src/components/Table/Table.tsx +78 -342
  43. package/src/components/Table/components/edit/TableEditRow.tsx +69 -0
  44. package/src/components/Table/components/filters/FilterItem.tsx +15 -0
  45. package/src/components/Table/components/filters/TableFilters.tsx +125 -0
  46. package/src/components/Table/components/footer/TableFooter.tsx +128 -0
  47. package/src/components/Table/components/header/TableHeader.tsx +42 -0
  48. package/src/components/Table/components/header/TableHeaderRow.tsx +47 -0
  49. package/src/components/Table/components/items/TableItemActions.tsx +66 -0
  50. package/src/components/Table/components/select/TableSelect.tsx +49 -0
  51. package/src/components/Table/components/sort/TableSort.tsx +52 -0
  52. package/src/components/Table/contexts/TableContext.tsx +123 -0
  53. package/src/components/Table/hooks/localHooks/useLocalTableColumns.tsx +73 -0
  54. package/src/components/Table/hooks/localHooks/useLocalTableData.tsx +78 -0
  55. package/src/components/Table/hooks/localHooks/useLocalTableKeyboard.ts +173 -0
  56. package/src/components/Table/hooks/localHooks/useLocalTablePagination.ts +12 -0
  57. package/src/components/Table/hooks/useTableEdit.tsx +111 -0
  58. package/src/components/Table/hooks/useTableFilterFields.tsx +150 -0
  59. package/src/components/Table/hooks/useTablePagination.ts +16 -0
  60. package/src/components/Table/hooks/useTableSearch.ts +29 -0
  61. package/src/components/Table/hooks/useTableSort.ts +8 -0
  62. package/src/components/Tooltip/Tooltip.tsx +1 -1
  63. package/src/components/Wrappers/PageLayout.tsx +9 -12
  64. package/src/hooks/useGetFocusableElements.ts +42 -0
  65. package/src/hooks/useLocalPopoverControl.ts +32 -0
  66. package/src/hooks/usePopupControl.ts +17 -0
  67. package/src/index.ts +56 -39
  68. package/src/styles/common/_typography.scss +1 -1
  69. package/src/styles/components/_accordions.scss +1 -1
  70. package/src/styles/components/_badge.scss +4 -3
  71. package/src/styles/components/_card.scss +1 -1
  72. package/src/styles/components/_dialog.scss +8 -8
  73. package/src/styles/components/_input.scss +1 -1
  74. package/src/styles/components/_inputCheckbox.scss +1 -1
  75. package/src/styles/components/_inputDateTime.scss +2 -2
  76. package/src/styles/components/_inputRadio.scss +1 -1
  77. package/src/styles/components/_inputSelect.scss +6 -4
  78. package/src/styles/components/_menu-v2.scss +1 -1
  79. package/src/styles/components/_menu.scss +23 -15
  80. package/src/styles/components/_pullover.scss +74 -18
  81. package/src/styles/components/_table.scss +151 -142
  82. package/src/styles/components/_widget.scss +1 -1
  83. package/src/styles/variables/_bp.scss +1 -0
  84. package/src/styles/variables/_variables.scss +4 -2
  85. package/src/types/IHeader.ts +13 -7
  86. package/src/types/IKeyboard.ts +2 -1
  87. package/src/types/IMenu.ts +1 -0
  88. package/src/types/IPopup.ts +17 -0
  89. package/src/types/ISelect.ts +1 -0
  90. package/src/types/ITable.ts +87 -80
  91. package/src/utils/fileUtils.ts +3 -3
  92. package/src/utils/i18n/i18nIUICyrilic.ts +4 -0
  93. package/src/utils/i18n/i18nIUILatin.ts +3 -1
  94. package/src/utils/i18n/i18nIUIMe.ts +4 -0
  95. package/src/utils/{ObjectUtils.ts → objectUtils.ts} +8 -8
  96. package/src/utils/popupUtils.ts +82 -0
  97. package/src/utils/{TableUtils.ts → tableUtils.ts} +5 -5
  98. package/src/utils/{Toasts.ts → toasts.ts} +6 -6
  99. package/src/utils/{UrlUtils.ts → urlUtils.ts} +4 -4
  100. package/idea/Table2/Components/Columns/ColumnsList.tsx +0 -56
  101. package/idea/Table2/Components/Columns/SetColumnsList.tsx +0 -107
  102. package/idea/Table2/Components/Edit/ItemActionsMenu.tsx +0 -87
  103. package/idea/Table2/Components/Edit/ItemEditOptionsButtons.tsx +0 -32
  104. package/idea/Table2/Components/Edit/TableEditRow.tsx +0 -56
  105. package/idea/Table2/Components/FilterItem.tsx +0 -20
  106. package/idea/Table2/Components/Header/TableHeader.tsx +0 -35
  107. package/idea/Table2/Components/Header/TableHeaderRow.tsx +0 -37
  108. package/idea/Table2/Components/Print/CustomTablePrint.tsx +0 -119
  109. package/idea/Table2/Components/Print/TablePrint.tsx +0 -208
  110. package/idea/Table2/Components/SetSortList.tsx +0 -33
  111. package/idea/Table2/Components/SetTableFilter.tsx +0 -90
  112. package/idea/Table2/Components/TableFooter.tsx +0 -107
  113. package/idea/Table2/Components/TableOptions.tsx +0 -211
  114. package/idea/Table2/Components/Templates/TemplateCreate.tsx +0 -75
  115. package/idea/Table2/Components/Templates/TemplateCreateDefault.tsx +0 -45
  116. package/idea/Table2/Components/Templates/TemplateList.tsx +0 -167
  117. package/idea/Table2/Components/Templates/repo/TemplateRepo.ts +0 -51
  118. package/idea/Table2/_table.scss +0 -300
  119. package/idea/Table2/hooks/useDefaultTemplate.ts +0 -22
  120. package/idea/Table2/hooks/useTableKeyboard.ts +0 -115
  121. package/src/assets/icons/light/faPenField.ts +0 -15
  122. package/src/assets/icons/solid/faMagnifyingGlass.ts +0 -15
  123. package/src/components/Dialog/DeleteItemDialog.tsx +0 -52
  124. package/src/components/Dialog/hooks/useDialogObserver.ts +0 -21
  125. /package/{src/components → idea}/Table/Components/Columns/ColumnsList.tsx +0 -0
  126. /package/{src/components → idea}/Table/Components/Columns/SetColumnsList.tsx +0 -0
  127. /package/{src/components → idea}/Table/Components/Edit/ItemActionsMenu.tsx +0 -0
  128. /package/{src/components → idea}/Table/Components/Edit/ItemEditOptionsButtons.tsx +0 -0
  129. /package/{src/components → idea}/Table/Components/Edit/TableEditRow.tsx +0 -0
  130. /package/{src/components → idea}/Table/Components/FilterItem.tsx +0 -0
  131. /package/{src/components → idea}/Table/Components/Header/TableHeader.tsx +0 -0
  132. /package/{src/components → idea}/Table/Components/Header/TableHeaderRow.tsx +0 -0
  133. /package/{src/components → idea}/Table/Components/SetSortList.tsx +0 -0
  134. /package/{src/components → idea}/Table/Components/TableFooter.tsx +0 -0
  135. /package/{src/components → idea}/Table/Components/Templates/TemplateCreate.tsx +0 -0
  136. /package/{src/components → idea}/Table/Components/Templates/TemplateCreateDefault.tsx +0 -0
  137. /package/{src/components → idea}/Table/Components/Templates/TemplateList.tsx +0 -0
  138. /package/{src/components → idea}/Table/Components/Templates/repo/TemplateRepo.ts +0 -0
  139. /package/src/utils/{DateUtils.ts → dateUtils.ts} +0 -0
  140. /package/src/utils/{LocalStorageHelper.ts → localStorageHelper.ts} +0 -0
  141. /package/src/utils/{NumberUtils.ts → numberUtils.ts} +0 -0
  142. /package/src/utils/{RootDir.ts → rootDir.ts} +0 -0
  143. /package/src/utils/{StringUtils.ts → stringUtils.ts} +0 -0
@@ -3,8 +3,10 @@ import { StylesConfig } from 'react-select';
3
3
  export const createSelectStyles = ({
4
4
  fontSize = 16,
5
5
  menuWidth,
6
+ size,
6
7
  }: {
7
8
  fontSize?: number;
9
+ size?: 's' | 'm';
8
10
  menuWidth?: 'fit-content' | 'max-content' | number;
9
11
  }) =>
10
12
  ({
@@ -14,7 +16,7 @@ export const createSelectStyles = ({
14
16
  borderWidth: 0,
15
17
  boxShadow: 'none',
16
18
  width: '100%',
17
- // minHeight: size === 's' ? 12 : 36,
19
+ minHeight: size === 's' ? 12 : 36,
18
20
  }),
19
21
  container: provided => ({
20
22
  ...provided,
@@ -23,9 +25,7 @@ export const createSelectStyles = ({
23
25
  valueContainer: provided => ({
24
26
  ...provided,
25
27
  fontSize,
26
- padding: '6px 0 6px 12px',
27
- // fontSize: /*size === 's' ? 12 : */ 14,
28
- // padding: /*size === 's' ? '0 4px' : */ state.isMulti ? '8px 14px' : '10px 14px',
28
+ padding: size === 's' ? '0 4px 0 8px' : '6px 0 6px 12px',
29
29
  gap: 4,
30
30
  width: '100%',
31
31
  }),
@@ -64,23 +64,20 @@ export const createSelectStyles = ({
64
64
  ...provided,
65
65
  padding: 0,
66
66
  margin: 0,
67
- // height: size === 's' ? 24 : 36,
68
67
  }),
69
68
  option: (provided, state) => ({
70
69
  ...provided,
71
70
  fontSize,
72
- // fontSize: /*size === 's' ? 12 : */ 14,
73
- // padding: size === 's' ? 4 : provided.padding,
71
+ padding: size === 's' ? 4 : provided.padding,
74
72
  backgroundColor: state.isSelected
75
73
  ? 'var(--primary) !important'
76
74
  : state.isFocused
77
- ? '#eee !important'
78
- : 'inherit',
75
+ ? '#eee !important'
76
+ : 'inherit',
79
77
  }),
80
78
  noOptionsMessage: provided => ({
81
79
  ...provided,
82
80
  fontSize,
83
- // fontSize: /*size === 's' ? 12 : */ 14,
84
81
  fontStyle: 'italic',
85
82
  textAlign: 'left',
86
83
  cursor: 'default',
@@ -97,8 +94,8 @@ export const createSelectStyles = ({
97
94
  indicatorSeparator: () => ({ display: 'none' }),
98
95
  dropdownIndicator: (provided, state) => ({
99
96
  ...provided,
100
- // padding: size === 's' ? '4px' : provided.padding,
97
+ padding: size === 's' ? '4px' : provided.padding,
101
98
  transition: 'all .2s ease',
102
99
  transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : null,
103
100
  }),
104
- } as StylesConfig);
101
+ }) as StylesConfig;
@@ -1,7 +1,7 @@
1
1
  import clsx from 'clsx';
2
2
  import { FC, ReactNode, RefObject, useRef } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
- import { rootDir } from '../../utils/RootDir';
4
+ import { rootDir } from '../../utils/rootDir';
5
5
  import { useMenuPosition } from './hooks/useMenuPosition';
6
6
  import { useBackgroundClose } from '../../hooks/useBackgroundClose';
7
7
  import { MenuItem } from './MenuItem';
@@ -55,7 +55,15 @@ export const Menu: FC<Props> = ({
55
55
  style={menuStyle}
56
56
  >
57
57
  {items?.map(item => (
58
- <MenuItem {...item} key={item.label} className={classNameItem} />
58
+ <MenuItem
59
+ {...item}
60
+ key={item.label}
61
+ className={classNameItem}
62
+ onClick={e => {
63
+ item.onClick?.(e);
64
+ !item.keepOpen && onClose();
65
+ }}
66
+ />
59
67
  ))}
60
68
  {children}
61
69
  </div>
@@ -13,26 +13,27 @@ export const MenuItem: FC<IMenuItem> = ({
13
13
  onClick,
14
14
  to,
15
15
  badge,
16
+ active,
16
17
  disabled,
17
18
  hidden,
18
19
  withDevider,
19
20
  className,
20
21
  }) =>
21
22
  hidden ? null : (
22
- <div>
23
+ <div
24
+ className={clsx(className, {
25
+ 'iui-menu-item': !className,
26
+ clickable: !!onClick || !!to,
27
+ disabled,
28
+ active,
29
+ 'with-devider': withDevider,
30
+ })}
31
+ >
23
32
  <ConditionalWrapper
24
33
  condition={!disabled && !!to}
25
34
  wrapper={children => <Link to={to!}>{children}</Link>}
26
35
  >
27
- <div
28
- className={clsx(className, {
29
- 'iui-menu-item': !className,
30
- clickable: !!onClick || !!to,
31
- disabled,
32
- 'with-devider': withDevider,
33
- })}
34
- onClick={disabled ? undefined : onClick}
35
- >
36
+ <div className="menu-item-content" onClick={disabled ? undefined : onClick}>
36
37
  {icon && (
37
38
  <div className="menu-item-icon">
38
39
  <FontAwesomeIcon icon={icon} rotation={iconRotation} />
@@ -1,5 +1,6 @@
1
- import { RefObject, useCallback, useEffect, useState } from 'react';
1
+ import { RefObject, useCallback, useLayoutEffect, useState } from 'react';
2
2
  import { IMenuPlacement } from '../../../types/IMenu';
3
+ import { IAnyObject } from '../../../types/IBasic';
3
4
 
4
5
  interface Offsets {
5
6
  offsetTop: number;
@@ -11,29 +12,50 @@ interface Props {
11
12
  placement: IMenuPlacement;
12
13
  containerRef: RefObject<Element | null>;
13
14
  menuRef: RefObject<Element | null>;
15
+ withMinWidth?: boolean;
14
16
  }
15
17
 
16
- export const useMenuPosition = ({ isOpen, placement, containerRef, menuRef }: Props) => {
18
+ export const useMenuPosition = ({
19
+ isOpen,
20
+ placement,
21
+ containerRef,
22
+ menuRef,
23
+ withMinWidth,
24
+ }: Props) => {
17
25
  const [offsets, setOffsets] = useState<Offsets>({
18
26
  offsetLeft: 0,
19
27
  offsetTop: 0,
20
28
  });
21
29
  const [maxHeight, setMaxHeight] = useState<number>();
30
+ const [minWidth, setMinWidth] = useState(200);
22
31
 
23
32
  const recalculatePosition = useCallback(() => {
24
33
  if (containerRef.current && menuRef.current) {
25
34
  const containerRect = containerRef.current.getBoundingClientRect();
26
35
  const menuRect = menuRef.current.getBoundingClientRect();
27
- const [placementY, placementX] = placement.split('-');
36
+ let [placementY, placementX] = placement.split('-');
37
+
38
+ setMinWidth(containerRect.width);
28
39
 
29
40
  let offsetTop = containerRect.bottom + 5;
30
- // let offsetTop = containerRect.bottom + document.documentElement.scrollTop + 5;
31
41
 
32
- const offsetLeft =
42
+ let offsetLeft =
33
43
  containerRect.left +
34
44
  (placementX === 'right' ? containerRect.width - menuRect.width : 0);
35
45
  // + document.documentElement.scrollLeft;
36
46
 
47
+ // Check for horizontal overflow and adjust placement
48
+ if (placementX === 'right' && offsetLeft < 0) {
49
+ placementX = 'left';
50
+ offsetLeft = containerRect.left;
51
+ } else if (
52
+ placementX === 'left' &&
53
+ offsetLeft + menuRect.width > window.innerWidth
54
+ ) {
55
+ placementX = 'right';
56
+ offsetLeft = containerRect.left + containerRect.width - menuRect.width;
57
+ }
58
+
37
59
  if (placementY === 'bottom') {
38
60
  setMaxHeight(window.innerHeight - containerRect.bottom - 20);
39
61
  }
@@ -45,10 +67,21 @@ export const useMenuPosition = ({ isOpen, placement, containerRef, menuRef }: Pr
45
67
  );
46
68
  }
47
69
 
70
+ // Check for vertical overflow and adjust placement
71
+ if (placementY === 'bottom' && offsetTop + menuRect.height > window.innerHeight) {
72
+ placementY = 'top';
73
+ offsetTop = containerRect.top - menuRect.height - 5;
74
+ setMaxHeight(containerRect.top - 20);
75
+ } else if (placementY === 'top' && offsetTop < 0) {
76
+ placementY = 'bottom';
77
+ offsetTop = containerRect.bottom + 5;
78
+ setMaxHeight(window.innerHeight - containerRect.bottom - 20);
79
+ }
80
+
48
81
  // check for space on the bottom
49
82
  if (
50
83
  (placementY === 'auto' && offsetTop + menuRect.height > window.innerHeight) ||
51
- placementY === 'top'
84
+ (placement.split('-')[0] === 'top' && placementY !== 'bottom')
52
85
  ) {
53
86
  offsetTop = containerRect.top - menuRect.height - 5; //+ document.documentElement.scrollTop
54
87
  }
@@ -57,14 +90,10 @@ export const useMenuPosition = ({ isOpen, placement, containerRef, menuRef }: Pr
57
90
  }
58
91
  }, [menuRef, containerRef, placement]);
59
92
 
60
- useEffect(() => {
93
+ useLayoutEffect(() => {
61
94
  isOpen
62
- ? window.addEventListener('resize', recalculatePosition)
63
- : window.removeEventListener('resize', recalculatePosition);
64
- // Disable scroll & fix page resize on menu open
65
- document.body.style.marginRight =
66
- isOpen && window.innerWidth > document.body.clientWidth ? '19px' : '';
67
- // document.body.style.overflow = isOpen ? 'hidden' : '';
95
+ ? document.addEventListener('resize', recalculatePosition)
96
+ : document.removeEventListener('resize', recalculatePosition);
68
97
 
69
98
  recalculatePosition();
70
99
  return () => {
@@ -72,9 +101,15 @@ export const useMenuPosition = ({ isOpen, placement, containerRef, menuRef }: Pr
72
101
  };
73
102
  }, [isOpen, recalculatePosition]);
74
103
 
75
- return {
104
+ const data: IAnyObject = {
76
105
  top: offsets.offsetTop + 'px',
77
106
  left: offsets.offsetLeft + 'px',
78
107
  maxHeight,
79
108
  };
109
+
110
+ if (withMinWidth) {
111
+ data.minWidth = minWidth;
112
+ }
113
+
114
+ return data;
80
115
  };
@@ -1,85 +1,164 @@
1
- import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
2
- import { FC, ReactNode, useCallback, useEffect, useRef } from 'react';
1
+ import { FC, KeyboardEventHandler, ReactNode, useEffect, useRef } from 'react';
3
2
  import { createPortal } from 'react-dom';
3
+ import { rootDir } from '../../utils/rootDir';
4
+ import clsx from 'clsx';
5
+ import { SearchInput } from '../Inputs/SearchInput';
6
+ import { useLocalPopoverControl } from '../../hooks/useLocalPopoverControl';
7
+ import { ConditionalWrapper } from '../Wrappers/ConditionalWrapper';
8
+ import { useGetFocusableElements } from '../../hooks/useGetFocusableElements';
9
+ import { IDialogFooterActions } from '../Dialog/components/DialogFooter';
10
+ import { Button } from '../Button/Button';
4
11
  import { useTranslation } from 'react-i18next';
5
- import { FormWrapper } from '../Wrappers/FormWrapper';
6
- import { rootDir } from '../../utils/RootDir';
7
- import { IButtonColor } from '../Button/Button';
12
+ import { onPopupKeyDown } from '../../utils/popupUtils';
13
+ import { ILocalPopupControl } from '../../types/IPopup';
14
+ import { Loader } from '../Loader/Loader';
8
15
 
9
16
  interface Props {
10
- isOpen: boolean;
11
- onClose: () => void;
12
- size: 's' | 'm' | 'l' | 'xl';
13
- form?: {
14
- isLoading: boolean;
15
- onSubmit: (data: any) => void;
16
- submitLabel?: string;
17
- submitDisabled?: boolean;
17
+ id?: string;
18
+ control: ILocalPopupControl;
19
+ header?: {
20
+ title: string;
21
+ onSearch?: (search: string) => void;
18
22
  };
19
- additionalButtons?: {
20
- label: string;
21
- icon?: IconDefinition;
22
- onClick: () => void;
23
- disabled?: boolean;
24
- hidden?: boolean;
25
- outlined?: boolean;
26
- color?: IButtonColor;
27
- }[];
23
+ isLoading?: boolean;
24
+ onFormSubmit?: () => void;
25
+ onCloseCallback?: () => void;
26
+ footer?: IDialogFooterActions;
27
+ size?: 'vw25' | 'vw50' | 'vw75' | 'vw100' | 'w500' | 'w600';
28
+ className?: string;
29
+ contentClassName?: string;
28
30
  children: ReactNode;
29
31
  }
30
32
 
31
33
  export const Pullover: FC<Props> = ({
32
- isOpen,
33
- onClose,
34
- size,
35
- form,
36
- additionalButtons,
34
+ id,
35
+ control,
36
+ header,
37
+ isLoading = false,
38
+ onFormSubmit,
39
+ onCloseCallback,
40
+ footer,
41
+ size = 'vw50',
42
+ className,
43
+ contentClassName,
37
44
  children,
38
45
  }) => {
39
- const ref = useRef<HTMLDivElement>(null);
40
46
  const { t } = useTranslation();
47
+ const searchRef = useRef<HTMLInputElement>(null);
41
48
 
42
- const handleClose = useCallback(() => {
43
- ref.current?.classList.add('closing');
44
- setTimeout(onClose, 200);
45
- }, [ref, onClose]);
49
+ const { elementRef, isOpen, onClose } = useLocalPopoverControl({
50
+ control,
51
+ onCloseCallback,
52
+ });
53
+
54
+ const focusableElements = useGetFocusableElements({
55
+ elementRef,
56
+ isOpen,
57
+ autoFocusIndex: header?.onSearch ? 1 : 0,
58
+ });
46
59
 
47
60
  useEffect(() => {
48
61
  if (isOpen) {
49
62
  setTimeout(() => {
50
- ref.current?.classList.add('open');
63
+ elementRef.current?.classList.add('open');
64
+ elementRef.current?.focus();
51
65
  });
52
- const handleClick = (e: KeyboardEvent) => {
53
- e.code === 'Escape' && handleClose();
54
- };
55
- window.addEventListener('keydown', handleClick);
56
- return () => window.removeEventListener('keydown', handleClick);
57
66
  }
58
- }, [isOpen, handleClose]);
67
+ }, [elementRef, isOpen]);
68
+
69
+ const onKeyDown: KeyboardEventHandler<HTMLDivElement> = event => {
70
+ onPopupKeyDown(event, {
71
+ onClose,
72
+ enter: {
73
+ onAction: () => {
74
+ footer?.confirmButton.onClick?.();
75
+ !footer?.confirmButton.keepOpen && onClose();
76
+ },
77
+ disabled: !footer?.confirmButton.onClick,
78
+ },
79
+ search: {
80
+ onAction: () => searchRef.current?.focus(),
81
+ disabled: !header?.onSearch,
82
+ },
83
+ focusableElements,
84
+ });
85
+ };
59
86
 
60
87
  return isOpen
61
88
  ? createPortal(
62
- <div ref={ref} className="pullover" onClick={handleClose}>
63
- <div className={`container ${size}`} onClick={e => e.stopPropagation()}>
64
- <div className="content">
65
- {form ? (
66
- <FormWrapper
67
- isLoading={form.isLoading}
68
- submitButton={{
69
- onSubmit: form.onSubmit,
70
- label: form.submitLabel,
71
- disabled: form.submitDisabled,
72
- }}
73
- otherButtons={[
74
- { label: t('Close'), onClick: handleClose },
75
- ...(additionalButtons?.filter(e => !e.hidden) || []),
76
- ]}
77
- >
78
- <div className="flex column gap-2 flex-grow">{children}</div>
79
- </FormWrapper>
80
- ) : (
81
- children
89
+ <div
90
+ id={id}
91
+ ref={elementRef}
92
+ className="pullover"
93
+ onClick={onClose}
94
+ aria-label="popup"
95
+ tabIndex={-1}
96
+ onKeyDown={onKeyDown}
97
+ >
98
+ <div className={clsx('container', size)} onClick={e => e.stopPropagation()}>
99
+ <div className={clsx('content', className)}>
100
+ {header && (
101
+ <div className="pullover-header">
102
+ <h3>{header.title}</h3>
103
+ {header.onSearch && (
104
+ <SearchInput
105
+ ref={searchRef}
106
+ onSearch={header.onSearch}
107
+ className="search-input"
108
+ />
109
+ )}
110
+ </div>
82
111
  )}
112
+ <ConditionalWrapper
113
+ condition={!!onFormSubmit}
114
+ wrapper={children => (
115
+ <form
116
+ className="pullover-form"
117
+ onSubmit={e => {
118
+ e.preventDefault();
119
+ e.stopPropagation();
120
+ onFormSubmit?.();
121
+ }}
122
+ >
123
+ {children}
124
+ </form>
125
+ )}
126
+ >
127
+ {/* CONTENT */}
128
+ <Loader isLoading={isLoading}>
129
+ <div className={clsx('pullover-content', contentClassName)}>
130
+ {children}
131
+ </div>
132
+ </Loader>
133
+
134
+ {/* FOOTER */}
135
+ {footer && (
136
+ <div className={clsx('pullover-footer', className)}>
137
+ {footer.additionalButton && (
138
+ <Button
139
+ {...footer.additionalButton}
140
+ disabled={footer.additionalButton.disabled || isLoading}
141
+ variant="simple"
142
+ className="mr-auto"
143
+ />
144
+ )}
145
+ <Button
146
+ label={t('Cancel')}
147
+ variant="outlined"
148
+ onClick={onClose}
149
+ disabled={isLoading}
150
+ />
151
+ <Button
152
+ {...footer.confirmButton}
153
+ label={footer.confirmButton.label ?? t('Confirm')}
154
+ disabled={footer.confirmButton.disabled || isLoading}
155
+ type={
156
+ (footer.confirmButton.type ?? onFormSubmit) ? 'submit' : 'button'
157
+ }
158
+ />
159
+ </div>
160
+ )}
161
+ </ConditionalWrapper>
83
162
  </div>
84
163
  </div>
85
164
  </div>,