@patternfly/react-core 5.3.0-prerelease.3 → 5.3.0-prerelease.4

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 (251) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/components/package.json +1 -1
  3. package/deprecated/package.json +1 -1
  4. package/dist/dynamic/components/AboutModal/package.json +1 -1
  5. package/dist/dynamic/components/Accordion/package.json +1 -1
  6. package/dist/dynamic/components/ActionList/package.json +1 -1
  7. package/dist/dynamic/components/Alert/package.json +1 -1
  8. package/dist/dynamic/components/Avatar/package.json +1 -1
  9. package/dist/dynamic/components/BackToTop/package.json +1 -1
  10. package/dist/dynamic/components/Backdrop/package.json +1 -1
  11. package/dist/dynamic/components/BackgroundImage/package.json +1 -1
  12. package/dist/dynamic/components/Badge/package.json +1 -1
  13. package/dist/dynamic/components/Banner/package.json +1 -1
  14. package/dist/dynamic/components/Brand/package.json +1 -1
  15. package/dist/dynamic/components/Breadcrumb/package.json +1 -1
  16. package/dist/dynamic/components/Button/package.json +1 -1
  17. package/dist/dynamic/components/CalendarMonth/package.json +1 -1
  18. package/dist/dynamic/components/Card/package.json +1 -1
  19. package/dist/dynamic/components/Checkbox/package.json +1 -1
  20. package/dist/dynamic/components/Chip/package.json +1 -1
  21. package/dist/dynamic/components/ClipboardCopy/package.json +1 -1
  22. package/dist/dynamic/components/CodeBlock/package.json +1 -1
  23. package/dist/dynamic/components/DataList/package.json +1 -1
  24. package/dist/dynamic/components/DatePicker/package.json +1 -1
  25. package/dist/dynamic/components/DescriptionList/package.json +1 -1
  26. package/dist/dynamic/components/Divider/package.json +1 -1
  27. package/dist/dynamic/components/DragDrop/package.json +1 -1
  28. package/dist/dynamic/components/Drawer/package.json +1 -1
  29. package/dist/dynamic/components/Dropdown/package.json +1 -1
  30. package/dist/dynamic/components/DualListSelector/package.json +1 -1
  31. package/dist/dynamic/components/EmptyState/package.json +1 -1
  32. package/dist/dynamic/components/ExpandableSection/package.json +1 -1
  33. package/dist/dynamic/components/FileUpload/package.json +1 -1
  34. package/dist/dynamic/components/Form/package.json +1 -1
  35. package/dist/dynamic/components/FormSelect/package.json +1 -1
  36. package/dist/dynamic/components/HelperText/package.json +1 -1
  37. package/dist/dynamic/components/Hint/package.json +1 -1
  38. package/dist/dynamic/components/Icon/package.json +1 -1
  39. package/dist/dynamic/components/InputGroup/package.json +1 -1
  40. package/dist/dynamic/components/JumpLinks/package.json +1 -1
  41. package/dist/dynamic/components/Label/package.json +1 -1
  42. package/dist/dynamic/components/List/package.json +1 -1
  43. package/dist/dynamic/components/LoginPage/package.json +1 -1
  44. package/dist/dynamic/components/Masthead/package.json +1 -1
  45. package/dist/dynamic/components/Menu/package.json +1 -1
  46. package/dist/dynamic/components/MenuToggle/package.json +1 -1
  47. package/dist/dynamic/components/Modal/package.json +1 -1
  48. package/dist/dynamic/components/MultipleFileUpload/package.json +1 -1
  49. package/dist/dynamic/components/Nav/package.json +1 -1
  50. package/dist/dynamic/components/NotificationBadge/package.json +1 -1
  51. package/dist/dynamic/components/NotificationDrawer/package.json +1 -1
  52. package/dist/dynamic/components/NumberInput/package.json +1 -1
  53. package/dist/dynamic/components/OverflowMenu/package.json +1 -1
  54. package/dist/dynamic/components/Page/package.json +1 -1
  55. package/dist/dynamic/components/Pagination/package.json +1 -1
  56. package/dist/dynamic/components/Panel/package.json +1 -1
  57. package/dist/dynamic/components/Popover/package.json +1 -1
  58. package/dist/dynamic/components/Progress/package.json +1 -1
  59. package/dist/dynamic/components/ProgressStepper/package.json +1 -1
  60. package/dist/dynamic/components/Radio/package.json +1 -1
  61. package/dist/dynamic/components/SearchInput/package.json +1 -1
  62. package/dist/dynamic/components/Select/package.json +1 -1
  63. package/dist/dynamic/components/Sidebar/package.json +1 -1
  64. package/dist/dynamic/components/SimpleList/package.json +1 -1
  65. package/dist/dynamic/components/Skeleton/package.json +1 -1
  66. package/dist/dynamic/components/SkipToContent/package.json +1 -1
  67. package/dist/dynamic/components/Slider/package.json +1 -1
  68. package/dist/dynamic/components/Spinner/package.json +1 -1
  69. package/dist/dynamic/components/Switch/package.json +1 -1
  70. package/dist/dynamic/components/Tabs/package.json +1 -1
  71. package/dist/dynamic/components/Text/package.json +1 -1
  72. package/dist/dynamic/components/TextArea/package.json +1 -1
  73. package/dist/dynamic/components/TextInput/package.json +1 -1
  74. package/dist/dynamic/components/TextInputGroup/package.json +1 -1
  75. package/dist/dynamic/components/Tile/package.json +1 -1
  76. package/dist/dynamic/components/TimePicker/package.json +1 -1
  77. package/dist/dynamic/components/Timestamp/package.json +1 -1
  78. package/dist/dynamic/components/Title/package.json +1 -1
  79. package/dist/dynamic/components/ToggleGroup/package.json +1 -1
  80. package/dist/dynamic/components/Toolbar/package.json +1 -1
  81. package/dist/dynamic/components/Tooltip/package.json +1 -1
  82. package/dist/dynamic/components/TreeView/package.json +1 -1
  83. package/dist/dynamic/components/Truncate/package.json +1 -1
  84. package/dist/dynamic/components/Wizard/hooks/package.json +1 -1
  85. package/dist/dynamic/components/Wizard/package.json +1 -1
  86. package/dist/dynamic/deprecated/components/ApplicationLauncher/package.json +1 -1
  87. package/dist/dynamic/deprecated/components/ContextSelector/package.json +1 -1
  88. package/dist/dynamic/deprecated/components/Dropdown/package.json +1 -1
  89. package/dist/dynamic/deprecated/components/OptionsMenu/package.json +1 -1
  90. package/dist/dynamic/deprecated/components/PageHeader/package.json +1 -1
  91. package/dist/dynamic/deprecated/components/Select/package.json +1 -1
  92. package/dist/dynamic/deprecated/components/Wizard/package.json +1 -1
  93. package/dist/dynamic/deprecated/components/package.json +1 -1
  94. package/dist/dynamic/helpers/FocusTrap/FocusTrap/package.json +1 -1
  95. package/dist/dynamic/helpers/GenerateId/GenerateId/package.json +1 -1
  96. package/dist/dynamic/helpers/KeyboardHandler/package.json +1 -1
  97. package/dist/dynamic/helpers/OUIA/ouia/package.json +1 -1
  98. package/dist/dynamic/helpers/Popper/Popper/package.json +1 -1
  99. package/dist/dynamic/helpers/constants/package.json +1 -1
  100. package/dist/dynamic/helpers/datetimeUtils/package.json +1 -1
  101. package/dist/dynamic/helpers/favorites/package.json +1 -1
  102. package/dist/dynamic/helpers/fileUtils/package.json +1 -1
  103. package/dist/dynamic/helpers/htmlConstants/package.json +1 -1
  104. package/dist/dynamic/helpers/package.json +1 -1
  105. package/dist/dynamic/helpers/resizeObserver/package.json +1 -1
  106. package/dist/dynamic/helpers/typeUtils/package.json +1 -1
  107. package/dist/dynamic/helpers/useInterval/package.json +1 -1
  108. package/dist/dynamic/helpers/useIsomorphicLayout/package.json +1 -1
  109. package/dist/dynamic/helpers/useUnmountEffect/package.json +1 -1
  110. package/dist/dynamic/helpers/util/package.json +1 -1
  111. package/dist/dynamic/layouts/Bullseye/package.json +1 -1
  112. package/dist/dynamic/layouts/Flex/package.json +1 -1
  113. package/dist/dynamic/layouts/Gallery/package.json +1 -1
  114. package/dist/dynamic/layouts/Grid/package.json +1 -1
  115. package/dist/dynamic/layouts/Level/package.json +1 -1
  116. package/dist/dynamic/layouts/Split/package.json +1 -1
  117. package/dist/dynamic/layouts/Stack/package.json +1 -1
  118. package/dist/dynamic/next/components/DualListSelector/package.json +1 -0
  119. package/dist/dynamic/next/components/Modal/package.json +1 -1
  120. package/dist/dynamic/next/components/package.json +1 -1
  121. package/dist/dynamic/styles/package.json +1 -1
  122. package/dist/esm/next/components/DualListSelector/DualListSelector.d.ts +23 -0
  123. package/dist/esm/next/components/DualListSelector/DualListSelector.d.ts.map +1 -0
  124. package/dist/esm/next/components/DualListSelector/DualListSelector.js +23 -0
  125. package/dist/esm/next/components/DualListSelector/DualListSelector.js.map +1 -0
  126. package/dist/esm/next/components/DualListSelector/DualListSelectorContext.d.ts +19 -0
  127. package/dist/esm/next/components/DualListSelector/DualListSelectorContext.d.ts.map +1 -0
  128. package/dist/esm/next/components/DualListSelector/DualListSelectorContext.js +5 -0
  129. package/dist/esm/next/components/DualListSelector/DualListSelectorContext.js.map +1 -0
  130. package/dist/esm/next/components/DualListSelector/DualListSelectorControl.d.ts +25 -0
  131. package/dist/esm/next/components/DualListSelector/DualListSelectorControl.d.ts.map +1 -0
  132. package/dist/esm/next/components/DualListSelector/DualListSelectorControl.js +18 -0
  133. package/dist/esm/next/components/DualListSelector/DualListSelectorControl.js.map +1 -0
  134. package/dist/esm/next/components/DualListSelector/DualListSelectorControlsWrapper.d.ts +15 -0
  135. package/dist/esm/next/components/DualListSelector/DualListSelectorControlsWrapper.d.ts.map +1 -0
  136. package/dist/esm/next/components/DualListSelector/DualListSelectorControlsWrapper.js +35 -0
  137. package/dist/esm/next/components/DualListSelector/DualListSelectorControlsWrapper.js.map +1 -0
  138. package/dist/esm/next/components/DualListSelector/DualListSelectorList.d.ts +8 -0
  139. package/dist/esm/next/components/DualListSelector/DualListSelectorList.d.ts.map +1 -0
  140. package/dist/esm/next/components/DualListSelector/DualListSelectorList.js +26 -0
  141. package/dist/esm/next/components/DualListSelector/DualListSelectorList.js.map +1 -0
  142. package/dist/esm/next/components/DualListSelector/DualListSelectorListItem.d.ts +29 -0
  143. package/dist/esm/next/components/DualListSelector/DualListSelectorListItem.d.ts.map +1 -0
  144. package/dist/esm/next/components/DualListSelector/DualListSelectorListItem.js +38 -0
  145. package/dist/esm/next/components/DualListSelector/DualListSelectorListItem.js.map +1 -0
  146. package/dist/esm/next/components/DualListSelector/DualListSelectorListWrapper.d.ts +24 -0
  147. package/dist/esm/next/components/DualListSelector/DualListSelectorListWrapper.d.ts.map +1 -0
  148. package/dist/esm/next/components/DualListSelector/DualListSelectorListWrapper.js +59 -0
  149. package/dist/esm/next/components/DualListSelector/DualListSelectorListWrapper.js.map +1 -0
  150. package/dist/esm/next/components/DualListSelector/DualListSelectorPane.d.ts +31 -0
  151. package/dist/esm/next/components/DualListSelector/DualListSelectorPane.d.ts.map +1 -0
  152. package/dist/esm/next/components/DualListSelector/DualListSelectorPane.js +27 -0
  153. package/dist/esm/next/components/DualListSelector/DualListSelectorPane.js.map +1 -0
  154. package/dist/esm/next/components/DualListSelector/DualListSelectorTree.d.ts +48 -0
  155. package/dist/esm/next/components/DualListSelector/DualListSelectorTree.d.ts.map +1 -0
  156. package/dist/esm/next/components/DualListSelector/DualListSelectorTree.js +15 -0
  157. package/dist/esm/next/components/DualListSelector/DualListSelectorTree.js.map +1 -0
  158. package/dist/esm/next/components/DualListSelector/DualListSelectorTreeItem.d.ts +32 -0
  159. package/dist/esm/next/components/DualListSelector/DualListSelectorTreeItem.d.ts.map +1 -0
  160. package/dist/esm/next/components/DualListSelector/DualListSelectorTreeItem.js +75 -0
  161. package/dist/esm/next/components/DualListSelector/DualListSelectorTreeItem.js.map +1 -0
  162. package/dist/esm/next/components/DualListSelector/index.d.ts +8 -0
  163. package/dist/esm/next/components/DualListSelector/index.d.ts.map +1 -0
  164. package/dist/esm/next/components/DualListSelector/index.js +8 -0
  165. package/dist/esm/next/components/DualListSelector/index.js.map +1 -0
  166. package/dist/esm/next/components/DualListSelector/treeUtils.d.ts +8 -0
  167. package/dist/esm/next/components/DualListSelector/treeUtils.d.ts.map +1 -0
  168. package/dist/esm/next/components/DualListSelector/treeUtils.js +68 -0
  169. package/dist/esm/next/components/DualListSelector/treeUtils.js.map +1 -0
  170. package/dist/esm/next/components/index.d.ts +1 -0
  171. package/dist/esm/next/components/index.d.ts.map +1 -1
  172. package/dist/esm/next/components/index.js +1 -0
  173. package/dist/esm/next/components/index.js.map +1 -1
  174. package/dist/js/next/components/DualListSelector/DualListSelector.d.ts +23 -0
  175. package/dist/js/next/components/DualListSelector/DualListSelector.d.ts.map +1 -0
  176. package/dist/js/next/components/DualListSelector/DualListSelector.js +26 -0
  177. package/dist/js/next/components/DualListSelector/DualListSelector.js.map +1 -0
  178. package/dist/js/next/components/DualListSelector/DualListSelectorContext.d.ts +19 -0
  179. package/dist/js/next/components/DualListSelector/DualListSelectorContext.d.ts.map +1 -0
  180. package/dist/js/next/components/DualListSelector/DualListSelectorContext.js +9 -0
  181. package/dist/js/next/components/DualListSelector/DualListSelectorContext.js.map +1 -0
  182. package/dist/js/next/components/DualListSelector/DualListSelectorControl.d.ts +25 -0
  183. package/dist/js/next/components/DualListSelector/DualListSelectorControl.d.ts.map +1 -0
  184. package/dist/js/next/components/DualListSelector/DualListSelectorControl.js +22 -0
  185. package/dist/js/next/components/DualListSelector/DualListSelectorControl.js.map +1 -0
  186. package/dist/js/next/components/DualListSelector/DualListSelectorControlsWrapper.d.ts +15 -0
  187. package/dist/js/next/components/DualListSelector/DualListSelectorControlsWrapper.d.ts.map +1 -0
  188. package/dist/js/next/components/DualListSelector/DualListSelectorControlsWrapper.js +39 -0
  189. package/dist/js/next/components/DualListSelector/DualListSelectorControlsWrapper.js.map +1 -0
  190. package/dist/js/next/components/DualListSelector/DualListSelectorList.d.ts +8 -0
  191. package/dist/js/next/components/DualListSelector/DualListSelectorList.d.ts.map +1 -0
  192. package/dist/js/next/components/DualListSelector/DualListSelectorList.js +30 -0
  193. package/dist/js/next/components/DualListSelector/DualListSelectorList.js.map +1 -0
  194. package/dist/js/next/components/DualListSelector/DualListSelectorListItem.d.ts +29 -0
  195. package/dist/js/next/components/DualListSelector/DualListSelectorListItem.d.ts.map +1 -0
  196. package/dist/js/next/components/DualListSelector/DualListSelectorListItem.js +42 -0
  197. package/dist/js/next/components/DualListSelector/DualListSelectorListItem.js.map +1 -0
  198. package/dist/js/next/components/DualListSelector/DualListSelectorListWrapper.d.ts +24 -0
  199. package/dist/js/next/components/DualListSelector/DualListSelectorListWrapper.d.ts.map +1 -0
  200. package/dist/js/next/components/DualListSelector/DualListSelectorListWrapper.js +63 -0
  201. package/dist/js/next/components/DualListSelector/DualListSelectorListWrapper.js.map +1 -0
  202. package/dist/js/next/components/DualListSelector/DualListSelectorPane.d.ts +31 -0
  203. package/dist/js/next/components/DualListSelector/DualListSelectorPane.d.ts.map +1 -0
  204. package/dist/js/next/components/DualListSelector/DualListSelectorPane.js +31 -0
  205. package/dist/js/next/components/DualListSelector/DualListSelectorPane.js.map +1 -0
  206. package/dist/js/next/components/DualListSelector/DualListSelectorTree.d.ts +48 -0
  207. package/dist/js/next/components/DualListSelector/DualListSelectorTree.d.ts.map +1 -0
  208. package/dist/js/next/components/DualListSelector/DualListSelectorTree.js +19 -0
  209. package/dist/js/next/components/DualListSelector/DualListSelectorTree.js.map +1 -0
  210. package/dist/js/next/components/DualListSelector/DualListSelectorTreeItem.d.ts +32 -0
  211. package/dist/js/next/components/DualListSelector/DualListSelectorTreeItem.d.ts.map +1 -0
  212. package/dist/js/next/components/DualListSelector/DualListSelectorTreeItem.js +78 -0
  213. package/dist/js/next/components/DualListSelector/DualListSelectorTreeItem.js.map +1 -0
  214. package/dist/js/next/components/DualListSelector/index.d.ts +8 -0
  215. package/dist/js/next/components/DualListSelector/index.d.ts.map +1 -0
  216. package/dist/js/next/components/DualListSelector/index.js +11 -0
  217. package/dist/js/next/components/DualListSelector/index.js.map +1 -0
  218. package/dist/js/next/components/DualListSelector/treeUtils.d.ts +8 -0
  219. package/dist/js/next/components/DualListSelector/treeUtils.d.ts.map +1 -0
  220. package/dist/js/next/components/DualListSelector/treeUtils.js +77 -0
  221. package/dist/js/next/components/DualListSelector/treeUtils.js.map +1 -0
  222. package/dist/js/next/components/index.d.ts +1 -0
  223. package/dist/js/next/components/index.d.ts.map +1 -1
  224. package/dist/js/next/components/index.js +1 -0
  225. package/dist/js/next/components/index.js.map +1 -1
  226. package/helpers/package.json +1 -1
  227. package/layouts/package.json +1 -1
  228. package/next/package.json +1 -1
  229. package/package.json +2 -2
  230. package/src/next/components/DualListSelector/DualListSelector.tsx +50 -0
  231. package/src/next/components/DualListSelector/DualListSelectorContext.ts +21 -0
  232. package/src/next/components/DualListSelector/DualListSelectorControl.tsx +66 -0
  233. package/src/next/components/DualListSelector/DualListSelectorControlsWrapper.tsx +87 -0
  234. package/src/next/components/DualListSelector/DualListSelectorList.tsx +57 -0
  235. package/src/next/components/DualListSelector/DualListSelectorListItem.tsx +105 -0
  236. package/src/next/components/DualListSelector/DualListSelectorListWrapper.tsx +124 -0
  237. package/src/next/components/DualListSelector/DualListSelectorPane.tsx +94 -0
  238. package/src/next/components/DualListSelector/DualListSelectorTree.tsx +110 -0
  239. package/src/next/components/DualListSelector/DualListSelectorTreeItem.tsx +184 -0
  240. package/src/next/components/DualListSelector/__tests__/DualListSelector.test.tsx +26 -0
  241. package/src/next/components/DualListSelector/__tests__/__snapshots__/DualListSelector.test.tsx.snap +122 -0
  242. package/src/next/components/DualListSelector/examples/DualListSelector.md +99 -0
  243. package/src/next/components/DualListSelector/examples/DualListSelectorBasic.tsx +157 -0
  244. package/src/next/components/DualListSelector/examples/DualListSelectorBasicSearch.tsx +214 -0
  245. package/src/next/components/DualListSelector/examples/DualListSelectorBasicTooltips.tsx +165 -0
  246. package/src/next/components/DualListSelector/examples/DualListSelectorComplexOptionsActions.tsx +347 -0
  247. package/src/next/components/DualListSelector/examples/DualListSelectorDragDrop.tsx +184 -0
  248. package/src/next/components/DualListSelector/examples/DualListSelectorTree.tsx +380 -0
  249. package/src/next/components/DualListSelector/index.ts +7 -0
  250. package/src/next/components/DualListSelector/treeUtils.ts +81 -0
  251. package/src/next/components/index.ts +1 -0
@@ -0,0 +1,380 @@
1
+ import React from 'react';
2
+ import {
3
+ DualListSelector,
4
+ DualListSelectorPane,
5
+ DualListSelectorList,
6
+ DualListSelectorControlsWrapper,
7
+ DualListSelectorControl,
8
+ DualListSelectorTree,
9
+ DualListSelectorTreeItemData,
10
+ SearchInput,
11
+ Button,
12
+ EmptyState,
13
+ EmptyStateVariant,
14
+ EmptyStateHeader,
15
+ EmptyStateFooter,
16
+ EmptyStateBody,
17
+ EmptyStateActions,
18
+ EmptyStateIcon
19
+ } from '@patternfly/react-core';
20
+ import AngleDoubleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-left-icon';
21
+ import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
22
+ import AngleDoubleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-right-icon';
23
+ import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
24
+ import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
25
+
26
+ interface FoodNode {
27
+ id: string;
28
+ text: string;
29
+ children?: FoodNode[];
30
+ }
31
+
32
+ interface ExampleProps {
33
+ data: FoodNode[];
34
+ }
35
+
36
+ export const DualListSelectorComposableTree: React.FunctionComponent<ExampleProps> = ({ data }: ExampleProps) => {
37
+ const [checkedLeafIds, setCheckedLeafIds] = React.useState<string[]>([]);
38
+ const [chosenLeafIds, setChosenLeafIds] = React.useState<string[]>(['beans', 'beef', 'chicken', 'tofu']);
39
+ const [chosenFilter, setChosenFilter] = React.useState<string>('');
40
+ const [availableFilter, setAvailableFilter] = React.useState<string>('');
41
+ let hiddenChosen: string[] = [];
42
+ let hiddenAvailable: string[] = [];
43
+
44
+ // helper function to build memoized lists
45
+ const buildTextById = (node: FoodNode): { [key: string]: string } => {
46
+ let textById = {};
47
+ if (!node) {
48
+ return textById;
49
+ }
50
+ textById[node.id] = node.text;
51
+ if (node.children) {
52
+ node.children.forEach((child) => {
53
+ textById = { ...textById, ...buildTextById(child) };
54
+ });
55
+ }
56
+ return textById;
57
+ };
58
+
59
+ // helper function to build memoized lists
60
+ const getDescendantLeafIds = (node: FoodNode): string[] => {
61
+ if (!node.children || !node.children.length) {
62
+ return [node.id];
63
+ } else {
64
+ let childrenIds: string[] = [];
65
+ node.children.forEach((child) => {
66
+ childrenIds = [...childrenIds, ...getDescendantLeafIds(child)];
67
+ });
68
+ return childrenIds;
69
+ }
70
+ };
71
+
72
+ // helper function to build memoized lists
73
+ const getLeavesById = (node: FoodNode): { [key: string]: string[] } => {
74
+ let leavesById = {};
75
+ if (!node.children || !node.children.length) {
76
+ leavesById[node.id] = [node.id];
77
+ } else {
78
+ node.children.forEach((child) => {
79
+ leavesById[node.id] = getDescendantLeafIds(node);
80
+ leavesById = { ...leavesById, ...getLeavesById(child) };
81
+ });
82
+ }
83
+ return leavesById;
84
+ };
85
+
86
+ // Builds a map of child leaf nodes by node id - memoized so that it only rebuilds the list if the data changes.
87
+ const { memoizedLeavesById, memoizedAllLeaves, memoizedNodeText } = React.useMemo(() => {
88
+ let leavesById = {};
89
+ let allLeaves: string[] = [];
90
+ let nodeTexts = {};
91
+ data.forEach((foodNode) => {
92
+ nodeTexts = { ...nodeTexts, ...buildTextById(foodNode) };
93
+ leavesById = { ...leavesById, ...getLeavesById(foodNode) };
94
+ allLeaves = [...allLeaves, ...getDescendantLeafIds(foodNode)];
95
+ });
96
+ return {
97
+ memoizedLeavesById: leavesById,
98
+ memoizedAllLeaves: allLeaves,
99
+ memoizedNodeText: nodeTexts
100
+ };
101
+ // eslint-disable-next-line react-hooks/exhaustive-deps
102
+ }, [data]);
103
+
104
+ const moveChecked = (toChosen: boolean) => {
105
+ setChosenLeafIds(
106
+ (prevChosenIds) =>
107
+ toChosen
108
+ ? [...prevChosenIds, ...checkedLeafIds] // add checked ids to chosen list
109
+ : [...prevChosenIds.filter((x) => !checkedLeafIds.includes(x))] // remove checked ids from chosen list
110
+ );
111
+
112
+ // uncheck checked ids that just moved
113
+ setCheckedLeafIds((prevChecked) =>
114
+ toChosen
115
+ ? [...prevChecked.filter((x) => chosenLeafIds.includes(x))]
116
+ : [...prevChecked.filter((x) => !chosenLeafIds.includes(x))]
117
+ );
118
+ };
119
+
120
+ const moveAll = (toChosen: boolean) => {
121
+ if (toChosen) {
122
+ setChosenLeafIds(memoizedAllLeaves);
123
+ } else {
124
+ setChosenLeafIds([]);
125
+ }
126
+ };
127
+
128
+ const areAllDescendantsSelected = (node: FoodNode, isChosen: boolean) =>
129
+ memoizedLeavesById[node.id].every(
130
+ (id) => checkedLeafIds.includes(id) && (isChosen ? chosenLeafIds.includes(id) : !chosenLeafIds.includes(id))
131
+ );
132
+ const areSomeDescendantsSelected = (node: FoodNode, isChosen: boolean) =>
133
+ memoizedLeavesById[node.id].some(
134
+ (id) => checkedLeafIds.includes(id) && (isChosen ? chosenLeafIds.includes(id) : !chosenLeafIds.includes(id))
135
+ );
136
+
137
+ const isNodeChecked = (node: FoodNode, isChosen: boolean) => {
138
+ if (areAllDescendantsSelected(node, isChosen)) {
139
+ return true;
140
+ }
141
+ if (areSomeDescendantsSelected(node, isChosen)) {
142
+ return false;
143
+ }
144
+ return false;
145
+ };
146
+
147
+ const onOptionCheck = (
148
+ event: React.MouseEvent | React.ChangeEvent<HTMLInputElement> | React.KeyboardEvent,
149
+ isChecked: boolean,
150
+ node: DualListSelectorTreeItemData,
151
+ isChosen: boolean
152
+ ) => {
153
+ const nodeIdsToCheck = memoizedLeavesById[node.id].filter((id) =>
154
+ isChosen
155
+ ? chosenLeafIds.includes(id) && !hiddenChosen.includes(id)
156
+ : !chosenLeafIds.includes(id) && !hiddenAvailable.includes(id)
157
+ );
158
+ if (isChosen) {
159
+ hiddenChosen = [];
160
+ } else {
161
+ hiddenAvailable = [];
162
+ }
163
+ setCheckedLeafIds((prevChecked) => {
164
+ const otherCheckedNodeNames = prevChecked.filter((id) => !nodeIdsToCheck.includes(id));
165
+ return !isChecked ? otherCheckedNodeNames : [...otherCheckedNodeNames, ...nodeIdsToCheck];
166
+ });
167
+ };
168
+
169
+ // builds a search input - used in each dual list selector pane
170
+ const buildSearchInput = (isChosen: boolean) => {
171
+ const onChange = (value) => (isChosen ? setChosenFilter(value) : setAvailableFilter(value));
172
+
173
+ return (
174
+ <SearchInput
175
+ value={isChosen ? chosenFilter : availableFilter}
176
+ onChange={(_event, value) => onChange(value)}
177
+ onClear={() => onChange('')}
178
+ />
179
+ );
180
+ };
181
+
182
+ // Builds the DualListSelectorTreeItems from the FoodNodes
183
+ const buildOptions = (
184
+ isChosen: boolean,
185
+ [node, ...remainingNodes]: FoodNode[],
186
+ hasParentMatch: boolean
187
+ ): DualListSelectorTreeItemData[] => {
188
+ if (!node) {
189
+ return [];
190
+ }
191
+
192
+ const isChecked = isNodeChecked(node, isChosen);
193
+
194
+ const filterValue = isChosen ? chosenFilter : availableFilter;
195
+ const descendentLeafIds = memoizedLeavesById[node.id];
196
+ const descendentsOnThisPane = isChosen
197
+ ? descendentLeafIds.filter((id) => chosenLeafIds.includes(id))
198
+ : descendentLeafIds.filter((id) => !chosenLeafIds.includes(id));
199
+
200
+ const hasMatchingChildren =
201
+ filterValue && descendentsOnThisPane.some((id) => memoizedNodeText[id].includes(filterValue));
202
+ const isFilterMatch = filterValue && node.text.includes(filterValue) && descendentsOnThisPane.length > 0;
203
+
204
+ // A node is displayed if either of the following is true:
205
+ // - There is no filter value and this node or its descendents belong on this pane
206
+ // - There is a filter value and this node or one of this node's descendents or ancestors match on this pane
207
+ const isDisplayed =
208
+ (!filterValue && descendentsOnThisPane.length > 0) ||
209
+ hasMatchingChildren ||
210
+ (hasParentMatch && descendentsOnThisPane.length > 0) ||
211
+ isFilterMatch;
212
+
213
+ if (!isDisplayed) {
214
+ if (isChosen) {
215
+ hiddenChosen.push(node.id);
216
+ } else {
217
+ hiddenAvailable.push(node.id);
218
+ }
219
+ }
220
+
221
+ return [
222
+ ...(isDisplayed
223
+ ? [
224
+ {
225
+ id: node.id,
226
+ text: node.text,
227
+ isChecked,
228
+ checkProps: { 'aria-label': `Select ${node.text}` },
229
+ hasBadge: node.children && node.children.length > 0,
230
+ badgeProps: { isRead: true },
231
+ defaultExpanded: isChosen ? !!chosenFilter : !!availableFilter,
232
+ children: node.children
233
+ ? buildOptions(isChosen, node.children, isFilterMatch || hasParentMatch)
234
+ : undefined
235
+ }
236
+ ]
237
+ : []),
238
+ ...(!isDisplayed && node.children && node.children.length
239
+ ? buildOptions(isChosen, node.children, hasParentMatch)
240
+ : []),
241
+ ...(remainingNodes ? buildOptions(isChosen, remainingNodes, hasParentMatch) : [])
242
+ ];
243
+ };
244
+
245
+ const buildPane = (isChosen: boolean): React.ReactNode => {
246
+ const options: DualListSelectorTreeItemData[] = buildOptions(isChosen, data, false);
247
+ const numOptions = isChosen ? chosenLeafIds.length : memoizedAllLeaves.length - chosenLeafIds.length;
248
+ const numSelected = checkedLeafIds.filter((id) =>
249
+ isChosen ? chosenLeafIds.includes(id) : !chosenLeafIds.includes(id)
250
+ ).length;
251
+ const status = `${numSelected} of ${numOptions} options selected`;
252
+ const filterApplied = isChosen ? chosenFilter !== '' : availableFilter !== '';
253
+ return (
254
+ <DualListSelectorPane
255
+ title={isChosen ? 'Chosen' : 'Available'}
256
+ status={status}
257
+ searchInput={buildSearchInput(isChosen)}
258
+ isChosen={isChosen}
259
+ listMinHeight="300px"
260
+ >
261
+ {filterApplied && options.length === 0 && (
262
+ <EmptyState variant={EmptyStateVariant.sm}>
263
+ <EmptyStateHeader
264
+ headingLevel="h4"
265
+ titleText="No results found"
266
+ icon={<EmptyStateIcon icon={SearchIcon} />}
267
+ />
268
+ <EmptyStateBody>No results match the filter criteria. Clear all filters and try again.</EmptyStateBody>
269
+ <EmptyStateFooter>
270
+ <EmptyStateActions>
271
+ <Button variant="link" onClick={() => (isChosen ? setChosenFilter('') : setAvailableFilter(''))}>
272
+ Clear all filters
273
+ </Button>
274
+ </EmptyStateActions>
275
+ </EmptyStateFooter>
276
+ </EmptyState>
277
+ )}
278
+ {options.length > 0 && (
279
+ <DualListSelectorList>
280
+ <DualListSelectorTree
281
+ data={options}
282
+ onOptionCheck={(e, isChecked, itemData) => onOptionCheck(e, isChecked, itemData, isChosen)}
283
+ />
284
+ </DualListSelectorList>
285
+ )}
286
+ </DualListSelectorPane>
287
+ );
288
+ };
289
+
290
+ return (
291
+ <DualListSelector isTree>
292
+ {buildPane(false)}
293
+ <DualListSelectorControlsWrapper>
294
+ <DualListSelectorControl
295
+ isDisabled={!checkedLeafIds.filter((x) => !chosenLeafIds.includes(x)).length}
296
+ onClick={() => moveChecked(true)}
297
+ aria-label="Add selected"
298
+ >
299
+ <AngleRightIcon />
300
+ </DualListSelectorControl>
301
+ <DualListSelectorControl
302
+ isDisabled={chosenLeafIds.length === memoizedAllLeaves.length}
303
+ onClick={() => moveAll(true)}
304
+ aria-label="Add all"
305
+ >
306
+ <AngleDoubleRightIcon />
307
+ </DualListSelectorControl>
308
+ <DualListSelectorControl
309
+ isDisabled={chosenLeafIds.length === 0}
310
+ onClick={() => moveAll(false)}
311
+ aria-label="Remove all"
312
+ >
313
+ <AngleDoubleLeftIcon />
314
+ </DualListSelectorControl>
315
+ <DualListSelectorControl
316
+ onClick={() => moveChecked(false)}
317
+ isDisabled={!checkedLeafIds.filter((x) => !!chosenLeafIds.includes(x)).length}
318
+ aria-label="Remove selected"
319
+ >
320
+ <AngleLeftIcon />
321
+ </DualListSelectorControl>
322
+ </DualListSelectorControlsWrapper>
323
+ {buildPane(true)}
324
+ </DualListSelector>
325
+ );
326
+ };
327
+
328
+ export const DualListSelectorComposableTreeExample: React.FunctionComponent = () => (
329
+ <DualListSelectorComposableTree
330
+ data={[
331
+ {
332
+ id: 'fruits',
333
+ text: 'Fruits',
334
+ children: [
335
+ { id: 'apple', text: 'Apple' },
336
+ {
337
+ id: 'berries',
338
+ text: 'Berries',
339
+ children: [
340
+ { id: 'blueberry', text: 'Blueberry' },
341
+ { id: 'strawberry', text: 'Strawberry' }
342
+ ]
343
+ },
344
+ { id: 'banana', text: 'Banana' }
345
+ ]
346
+ },
347
+ { id: 'bread', text: 'Bread' },
348
+ {
349
+ id: 'vegetables',
350
+ text: 'Vegetables',
351
+ children: [
352
+ { id: 'broccoli', text: 'Broccoli' },
353
+ { id: 'cauliflower', text: 'Cauliflower' }
354
+ ]
355
+ },
356
+ {
357
+ id: 'proteins',
358
+ text: 'Proteins',
359
+ children: [
360
+ { id: 'beans', text: 'Beans' },
361
+ {
362
+ id: 'meats',
363
+ text: 'Meats',
364
+ children: [
365
+ {
366
+ id: 'beef',
367
+ text: 'Beef'
368
+ },
369
+ {
370
+ id: 'chicken',
371
+ text: 'Chicken'
372
+ }
373
+ ]
374
+ },
375
+ { id: 'tofu', text: 'Tofu' }
376
+ ]
377
+ }
378
+ ]}
379
+ />
380
+ );
@@ -0,0 +1,7 @@
1
+ export * from './DualListSelector';
2
+ export * from './DualListSelectorControl';
3
+ export * from './DualListSelectorControlsWrapper';
4
+ export * from './DualListSelectorPane';
5
+ export * from './DualListSelectorList';
6
+ export * from './DualListSelectorListItem';
7
+ export * from './DualListSelectorTree';
@@ -0,0 +1,81 @@
1
+ import { DualListSelectorTreeItemData } from './DualListSelectorTree';
2
+
3
+ export function flattenTree(tree: DualListSelectorTreeItemData[]): string[] {
4
+ let result = [] as string[];
5
+ tree.forEach((item) => {
6
+ if (item.children) {
7
+ result = result.concat(flattenTree(item.children));
8
+ } else {
9
+ result.push(item.id);
10
+ }
11
+ });
12
+ return result;
13
+ }
14
+
15
+ export function flattenTreeWithFolders(tree: DualListSelectorTreeItemData[]): string[] {
16
+ let result = [] as string[];
17
+ tree.forEach((item) => {
18
+ result.push(item.id);
19
+ if (item.children) {
20
+ result = result.concat(flattenTreeWithFolders(item.children));
21
+ }
22
+ });
23
+ return result;
24
+ }
25
+
26
+ export function filterFolders(tree: DualListSelectorTreeItemData[], inputList: string[]): string[] {
27
+ let result = [] as string[];
28
+ tree.forEach((item) => {
29
+ if (item.children) {
30
+ result = result.concat(filterFolders(item.children, inputList));
31
+ } else {
32
+ if (inputList.includes(item.id)) {
33
+ result.push(item.id);
34
+ }
35
+ }
36
+ });
37
+ return result;
38
+ }
39
+
40
+ export function filterTreeItems(item: DualListSelectorTreeItemData, inputList: string[]): boolean {
41
+ if (inputList.includes(item.id)) {
42
+ return true;
43
+ }
44
+ if (item.children) {
45
+ return (
46
+ (item.children = item.children
47
+ .map((opt) => Object.assign({}, opt))
48
+ .filter((child) => filterTreeItems(child, inputList))).length > 0
49
+ );
50
+ }
51
+ }
52
+
53
+ export function filterTreeItemsWithoutFolders(item: DualListSelectorTreeItemData, inputList: string[]): boolean {
54
+ if (item.children) {
55
+ return (
56
+ (item.children = item.children
57
+ .map((opt) => Object.assign({}, opt))
58
+ .filter((child) =>
59
+ child.children ? filterTreeItemsWithoutFolders(child, inputList) : filterTreeItems(child, inputList)
60
+ )).length > 0
61
+ );
62
+ }
63
+
64
+ if (inputList.includes(item.id)) {
65
+ return true;
66
+ }
67
+ }
68
+
69
+ export function filterRestTreeItems(item: DualListSelectorTreeItemData, inputList: string[]): boolean {
70
+ if (item.children) {
71
+ const child =
72
+ (item.children = item.children
73
+ .map((opt) => Object.assign({}, opt))
74
+ .filter((child) => filterRestTreeItems(child, inputList))).length > 0;
75
+ return child;
76
+ }
77
+
78
+ if (!inputList.includes(item.id)) {
79
+ return true;
80
+ }
81
+ }
@@ -1 +1,2 @@
1
1
  export * from './Modal';
2
+ export * from './DualListSelector';