@thecb/components 10.12.2-beta.0 → 10.12.3-beta.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 (223) hide show
  1. package/README.md +4 -0
  2. package/dist/index.cjs.js +1476 -2489
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/index.d.ts +8 -6
  5. package/dist/index.esm.js +1476 -2488
  6. package/dist/index.esm.js.map +1 -1
  7. package/package.json +25 -13
  8. package/src/components/atoms/.DS_Store +0 -0
  9. package/src/components/atoms/alert/Alert.mdx +19 -0
  10. package/src/components/atoms/alert/Alert.stories.js +148 -26
  11. package/src/components/atoms/badge/Badge.js +2 -2
  12. package/src/components/atoms/badge/Badge.mdx +27 -0
  13. package/src/components/atoms/badge/Badge.stories.js +143 -29
  14. package/src/components/atoms/breadcrumb/Breadcrumb.mdx +21 -0
  15. package/src/components/atoms/breadcrumb/Breadcrumb.stories.js +38 -29
  16. package/src/components/atoms/button-with-action/ButtonWithAction.stories.js +108 -55
  17. package/src/components/atoms/button-with-link/ButtonWithLink.mdx +21 -0
  18. package/src/components/atoms/button-with-link/ButtonWithLink.stories.js +160 -31
  19. package/src/components/atoms/card/Card.js +13 -1
  20. package/src/components/atoms/card/Card.mdx +41 -0
  21. package/src/components/atoms/card/Card.stories.js +360 -0
  22. package/src/components/atoms/card/Card.theme.js +2 -0
  23. package/src/components/atoms/card/index.d.ts +1 -0
  24. package/src/components/atoms/checkbox/Checkbox.js +8 -14
  25. package/src/components/atoms/checkbox/Checkbox.mdx +15 -0
  26. package/src/components/atoms/checkbox/Checkbox.oldstories.js +30 -0
  27. package/src/components/atoms/checkbox/Checkbox.stories.js +148 -29
  28. package/src/components/atoms/country-dropdown/CountryDropdown.mdx +36 -0
  29. package/src/components/atoms/country-dropdown/CountryDropdown.stories.js +61 -27
  30. package/src/components/atoms/detail/Detail.js +0 -26
  31. package/src/components/atoms/detail/Detail.mdx +32 -0
  32. package/src/components/atoms/detail/Detail.stories.js +156 -0
  33. package/src/components/atoms/display-box/DisplayBox.mdx +11 -0
  34. package/src/components/atoms/display-box/DisplayBox.stories.js +65 -21
  35. package/src/components/atoms/display-card/DisplayCard.mdx +13 -0
  36. package/src/components/atoms/display-card/DisplayCard.stories.js +163 -22
  37. package/src/components/atoms/dropdown/Dropdown.mdx +65 -0
  38. package/src/components/atoms/dropdown/Dropdown.stories.js +91 -10
  39. package/src/components/atoms/form-layouts/FormInput.mdx +38 -0
  40. package/src/components/atoms/form-layouts/FormInput.stories.js +212 -26
  41. package/src/components/atoms/form-select/FormSelect.mdx +42 -0
  42. package/src/components/atoms/form-select/FormSelect.stories.js +55 -29
  43. package/src/components/atoms/formatted-address/FormattedAddress.mdx +13 -0
  44. package/src/components/atoms/formatted-address/FormattedAddress.stories.js +133 -27
  45. package/src/components/atoms/formatted-bank-account/FormattedBankAccount.mdx +17 -0
  46. package/src/components/atoms/formatted-bank-account/FormattedBankAccount.stories.js +57 -0
  47. package/src/components/atoms/formatted-credit-card/FormattedCreditCard.mdx +40 -0
  48. package/src/components/atoms/formatted-credit-card/FormattedCreditCard.stories.js +74 -0
  49. package/src/components/atoms/icons/Icons.mdx +40 -0
  50. package/src/components/atoms/icons/Icons.stories.js +325 -0
  51. package/src/components/atoms/icons/index.js +1 -5
  52. package/src/components/atoms/labeled-amount/LabeledAmount.mdx +23 -0
  53. package/src/components/atoms/labeled-amount/LabeledAmount.stories.js +110 -34
  54. package/src/components/atoms/line-item/LineItem.mdx +28 -0
  55. package/src/components/atoms/line-item/LineItem.stories.js +89 -22
  56. package/src/components/atoms/link/Link.mdx +19 -0
  57. package/src/components/atoms/link/Link.stories.js +155 -49
  58. package/src/components/atoms/loading/Loading.mdx +13 -0
  59. package/src/components/atoms/loading/Loading.stories.js +22 -0
  60. package/src/components/atoms/loading-line/LoadingLine.js +14 -10
  61. package/src/components/atoms/loading-line/LoadingLine.mdx +15 -0
  62. package/src/components/atoms/loading-line/LoadingLine.stories.js +132 -28
  63. package/src/components/atoms/nav-footer/NavFooter.mdx +15 -0
  64. package/src/components/atoms/nav-footer/NavFooter.stories.js +235 -22
  65. package/src/components/atoms/nav-header/NavHeader.mdx +13 -0
  66. package/src/components/atoms/nav-header/NavHeader.stories.js +122 -21
  67. package/src/components/atoms/nav-tabs/NavTabs.mdx +30 -0
  68. package/src/components/atoms/nav-tabs/NavTabs.stories.js +49 -0
  69. package/src/components/atoms/password-requirements/PasswordRequirements.mdx +39 -0
  70. package/src/components/atoms/password-requirements/PasswordRequirements.stories.js +108 -44
  71. package/src/components/atoms/placeholder/Placeholder.mdx +19 -0
  72. package/src/components/atoms/placeholder/Placeholder.stories.js +164 -36
  73. package/src/components/atoms/searchable-select/SearchableSelect.mdx +44 -0
  74. package/src/components/atoms/searchable-select/SearchableSelect.stories.js +103 -28
  75. package/src/components/atoms/state-province-dropdown/StateProvinceDropdown.mdx +36 -0
  76. package/src/components/atoms/state-province-dropdown/StateProvinceDropdown.stories.js +65 -40
  77. package/src/components/atoms/table/Table.mdx +71 -0
  78. package/src/components/atoms/table/Table.oldstories.js +84 -0
  79. package/src/components/atoms/table/Table.stories.js +59 -75
  80. package/src/components/atoms/table/TableRow.js +1 -0
  81. package/src/components/atoms/title/Title.js +0 -23
  82. package/src/components/atoms/title/Title.mdx +26 -0
  83. package/src/components/atoms/title/Title.stories.js +144 -0
  84. package/src/components/atoms/toggle-switch/ToggleSwitch.mdx +17 -0
  85. package/src/components/atoms/toggle-switch/ToggleSwitch.stories.js +103 -20
  86. package/src/components/atoms/toggle-switch/ToggleSwitch.theme.js +8 -5
  87. package/src/components/atoms/typeahead-input/TypeaheadInput.mdx +13 -0
  88. package/src/components/atoms/typeahead-input/TypeaheadInput.stories.js +63 -0
  89. package/src/components/molecules/address-form/AddressForm.mdx +18 -0
  90. package/src/components/molecules/address-form/AddressForm.stories.js +223 -20
  91. package/src/components/molecules/banner/Banner.mdx +23 -0
  92. package/src/components/molecules/banner/Banner.stories.js +122 -26
  93. package/src/components/molecules/change-password-form/ChangePasswordForm.mdx +15 -0
  94. package/src/components/molecules/change-password-form/ChangePasswordForm.stories.js +203 -19
  95. package/src/components/molecules/collapsible-section/CollapsibleSection.mdx +15 -0
  96. package/src/components/molecules/collapsible-section/CollapsibleSection.stories.js +210 -61
  97. package/src/components/molecules/edit-name-form/EditNameForm.mdx +13 -0
  98. package/src/components/molecules/edit-name-form/EditNameForm.stories.js +117 -0
  99. package/src/components/molecules/idle-modal/IdleModal.js +101 -0
  100. package/src/components/molecules/idle-modal/IdleModal.mdx +17 -0
  101. package/src/components/molecules/idle-modal/IdleModal.stories.js +180 -0
  102. package/src/components/molecules/idle-modal/index.d.ts +16 -0
  103. package/src/components/molecules/idle-modal/index.js +3 -0
  104. package/src/components/molecules/index.js +1 -0
  105. package/src/components/molecules/link-card/LinkCard.mdx +17 -0
  106. package/src/components/molecules/link-card/LinkCard.stories.js +287 -72
  107. package/src/components/molecules/login-form/LoginForm.mdx +16 -0
  108. package/src/components/molecules/login-form/LoginForm.stories.js +117 -21
  109. package/src/components/molecules/modal/Modal.mdx +17 -0
  110. package/src/components/molecules/modal/Modal.stories.js +342 -128
  111. package/src/components/molecules/module/Module.mdx +17 -0
  112. package/src/components/molecules/module/Module.stories.js +267 -25
  113. package/src/components/molecules/multiple-select-filter/MultipleSelectFilter.js +309 -77
  114. package/src/components/molecules/multiple-select-filter/{MultipleSelectFilter.stories.js → MultipleSelectFilter.oldstories.js} +4 -2
  115. package/src/components/molecules/multiple-select-filter/MultipleSelectFilter.styled.js +6 -6
  116. package/src/components/molecules/multiple-select-filter/index.d.ts +2 -2
  117. package/src/components/molecules/obligation/Obligation.mdx +23 -0
  118. package/src/components/molecules/obligation/Obligation.stories.js +460 -0
  119. package/src/components/molecules/obligation/icons/PropertyPersonalIcon.js +1 -1
  120. package/src/components/molecules/pagination/Pagination.mdx +15 -0
  121. package/src/components/molecules/pagination/Pagination.stories.js +177 -28
  122. package/src/components/molecules/popover/Popover.mdx +15 -0
  123. package/src/components/molecules/popover/Popover.stories.js +220 -0
  124. package/src/components/molecules/tabs/Tabs.mdx +17 -0
  125. package/src/components/molecules/tabs/Tabs.stories.js +135 -227
  126. package/src/components/molecules/toast-notification/Toast.mdx +15 -0
  127. package/src/components/molecules/toast-notification/Toast.stories.js +183 -0
  128. package/src/hooks/use-outside-click/index.js +5 -4
  129. package/src/stories/Button.stories.ts +53 -0
  130. package/src/stories/Button.tsx +48 -0
  131. package/src/stories/Configure.mdx +364 -0
  132. package/src/stories/Header.stories.ts +33 -0
  133. package/src/stories/Header.tsx +56 -0
  134. package/src/stories/Page.stories.ts +32 -0
  135. package/src/stories/Page.tsx +73 -0
  136. package/src/stories/assets/accessibility.png +0 -0
  137. package/src/stories/assets/accessibility.svg +5 -0
  138. package/src/stories/assets/addon-library.png +0 -0
  139. package/src/stories/assets/assets.png +0 -0
  140. package/src/stories/assets/avif-test-image.avif +0 -0
  141. package/src/stories/assets/context.png +0 -0
  142. package/src/stories/assets/discord.svg +15 -0
  143. package/src/stories/assets/docs.png +0 -0
  144. package/src/stories/assets/figma-plugin.png +0 -0
  145. package/src/stories/assets/github.svg +3 -0
  146. package/src/stories/assets/share.png +0 -0
  147. package/src/stories/assets/styling.png +0 -0
  148. package/src/stories/assets/testing.png +0 -0
  149. package/src/stories/assets/theming.png +0 -0
  150. package/src/stories/assets/tutorials.svg +12 -0
  151. package/src/stories/assets/youtube.svg +4 -0
  152. package/src/stories/button.css +30 -0
  153. package/src/stories/header.css +32 -0
  154. package/src/stories/page.css +69 -0
  155. package/src/util/idleTimerUtils.js +36 -0
  156. package/src/util/index.js +3 -1
  157. package/src/components/atoms/icons/CheckboxCheckmarkIcon.js +0 -45
  158. package/src/components/atoms/icons/PaymentStatusIcon.d.ts +0 -1
  159. package/src/components/atoms/icons/PaymentStatusIcon.js +0 -28
  160. package/src/components/atoms/icons/PersonIcon.d.ts +0 -1
  161. package/src/components/atoms/icons/PersonIcon.js +0 -28
  162. package/src/components/atoms/icons/icons.stories.js +0 -120
  163. package/src/components/molecules/edit-name-form/EdidNameForm.stories.js +0 -24
  164. package/src/components/molecules/multiple-select-filter/__private__/ActionLinkButton.js +0 -27
  165. package/src/components/molecules/multiple-select-filter/__private__/FilterButton.js +0 -89
  166. package/src/components/molecules/multiple-select-filter/__private__/FilterDropdown.js +0 -27
  167. package/src/components/molecules/multiple-select-filter/__private__/FilterableList.js +0 -146
  168. package/src/components/molecules/multiple-select-filter/__private__/FilterableListItem.js +0 -79
  169. package/src/components/molecules/multiple-select-filter/__private__/SearchBox.js +0 -41
  170. package/src/components/molecules/multiple-select-filter/__private__/useKeyboardNavigation.js +0 -84
  171. package/src/components/molecules/multiple-select-filter/__private__/util.js +0 -31
  172. package/src/components/molecules/toast-notification/ToastNotification.stories.js +0 -105
  173. /package/src/components/atoms/add-obligation/{AddObligation.stories.js → AddObligation.oldstories.js} +0 -0
  174. /package/src/components/atoms/amount-callout/{AmountCallout.stories.js → AmountCallout.oldstories.js} +0 -0
  175. /package/src/components/atoms/checkbox-list/{CheckboxList.stories.js → CheckboxList.oldstories.js} +0 -0
  176. /package/src/components/atoms/form-layouts/{FormLayouts.stories.js → FormLayouts.oldstories.js} +0 -0
  177. /package/src/components/atoms/hamburger-button/{HamburgerButton.stories.js → HamburgerButton.oldstories.js} +0 -0
  178. /package/src/components/atoms/heading/{Heading.stories.js → Heading.oldstories.js} +0 -0
  179. /package/src/components/atoms/layouts/examples/box-example/{BoxExample.stories.js → BoxExample.oldstories.js} +0 -0
  180. /package/src/components/atoms/layouts/examples/center-example/{CenterExample.stories.js → CenterExample.oldstories.js} +0 -0
  181. /package/src/components/atoms/layouts/examples/cluster-example/{ClusterExample.stories.js → ClusterExample.oldstories.js} +0 -0
  182. /package/src/components/atoms/layouts/examples/cover-example/{CoverExample.stories.js → CoverExample.oldstories.js} +0 -0
  183. /package/src/components/atoms/layouts/examples/frame-example/{FrameExample.stories.js → FrameExample.oldstories.js} +0 -0
  184. /package/src/components/atoms/layouts/examples/grid-example/{GridExample.stories.js → GridExample.oldstories.js} +0 -0
  185. /package/src/components/atoms/layouts/examples/imposter-example/{ImposterExample.stories.js → ImposterExample.oldstories.js} +0 -0
  186. /package/src/components/atoms/layouts/examples/motion-example/{MotionExample.stories.js → MotionExample.oldstories.js} +0 -0
  187. /package/src/components/atoms/layouts/examples/reel-example/{ReelExample.stories.js → ReelExample.oldstories.js} +0 -0
  188. /package/src/components/atoms/layouts/examples/sidebar-example/{SidebarExample.stories.js → SidebarExample.oldstories.js} +0 -0
  189. /package/src/components/atoms/layouts/examples/stack-example/{StackExample.stories.js → StackExample.oldstories.js} +0 -0
  190. /package/src/components/atoms/layouts/examples/switcher-example/{SwitcherExample.stories.js → SwitcherExample.oldstories.js} +0 -0
  191. /package/src/components/atoms/paragraph/{Paragraph.stories.js → Paragraph.oldstories.js} +0 -0
  192. /package/src/components/atoms/processing-fee/{ProcessingFee.stories.js → ProcessingFee.oldstories.js} +0 -0
  193. /package/src/components/atoms/search/{Search.stories.js → Search.oldstories.js} +0 -0
  194. /package/src/components/atoms/solid-divider/{SolidDivider.stories.js → SolidDivider.oldstories.js} +0 -0
  195. /package/src/components/atoms/sortable-table-heading/{SortableTableHeading.stories.js → SortableTableHeading.oldstories.js} +0 -0
  196. /package/src/components/atoms/spinner/{Spinner.stories.js → Spinner.oldstories.js} +0 -0
  197. /package/src/components/atoms/tab/{Tab.stories.js → Tab.oldstories.js} +0 -0
  198. /package/src/components/atoms/text/{Text.stories.js → Text.oldstories.js} +0 -0
  199. /package/src/components/atoms/typeahead-input/{TypeaheadIinput.stories.js → TypeaheadIinput.oldstories.js} +0 -0
  200. /package/src/components/atoms/wallet-name/{WalletName.stories.js → WalletName.oldstories.js} +0 -0
  201. /package/src/components/molecules/account-and-routing-modal/{AccountAndRoutingModal.stories.js → AccountAndRoutingModal.oldstories.js} +0 -0
  202. /package/src/components/molecules/editable-list/{EditableList.stories.js → EditableList.oldstories.js} +0 -0
  203. /package/src/components/molecules/email-form/{EmailForm.stories.js → EmailForm.oldstories.js} +0 -0
  204. /package/src/components/molecules/forgot-password-form/{ForgotPasswordForm.stories.js → ForgotPasswordForm.oldstories.js} +0 -0
  205. /package/src/components/molecules/highlight-tab-row/{HighlightTabRow.stories.js → HighlightTabRow.oldstories.js} +0 -0
  206. /package/src/components/molecules/obligation/modules/{AmountModule.stories.js → AmountModule.oldstories.js} +0 -0
  207. /package/src/components/molecules/payment-button-bar/{PaymentButtonBar.stories.js → PaymentButtonBar.oldstories.js} +0 -0
  208. /package/src/components/molecules/payment-details/{PaymentDetails.stories.js → PaymentDetails.oldstories.js} +0 -0
  209. /package/src/components/molecules/payment-form-ach/{PaymentFormACH.stories.js → PaymentFormACH.oldstories.js} +0 -0
  210. /package/src/components/molecules/payment-form-card/{PaymentFormCard.stories.js → PaymentFormCard.oldstories.js} +0 -0
  211. /package/src/components/molecules/periscope-dashboard-iframe/{PeriscopeDashBoardIframe.stories.js → PeriscopeDashBoardIframe.oldstories.js} +0 -0
  212. /package/src/components/molecules/phone-form/{PhoneForm.stories.js → PhoneForm.oldstories.js} +0 -0
  213. /package/src/components/molecules/popup-menu/{PopupMenu.stories.js → PopupMenu.oldstories.js} +0 -0
  214. /package/src/components/molecules/radio-group/{RadioGroup.stories.js → RadioGroup.oldstories.js} +0 -0
  215. /package/src/components/molecules/radio-section/{RadioSection.stories.js → RadioSection.oldstories.js} +0 -0
  216. /package/src/components/molecules/registration-form/{RegistrationForm.stories.js → RegistrationForm.oldstories.js} +0 -0
  217. /package/src/components/molecules/reset-confirmation-form/{ResetConfirmationForm.stories.js → ResetConfirmationForm.oldstories.js} +0 -0
  218. /package/src/components/molecules/reset-password-form/{ResetPasswordForm.stories.js → ResetPasswordForm.oldstories.js} +0 -0
  219. /package/src/components/molecules/reset-password-success/{ResetPasswordSuccess.stories.js → ResetPasswordSuccess.oldstories.js} +0 -0
  220. /package/src/components/molecules/tab-sidebar/{TabSidebar.stories.js → TabSidebar.oldstories.js} +0 -0
  221. /package/src/components/molecules/terms-and-conditions/{TermsAndConditions.stories.js → TermsAndConditions.oldstories.js} +0 -0
  222. /package/src/components/molecules/terms-and-conditions-modal/{TermsAndConditionsModal.stories.js → TermsAndConditionsModal.oldstories.js} +0 -0
  223. /package/src/components/molecules/workflow-tile/{WorkflowTile.stories.js → WorkflowTile.oldstories.js} +0 -0
@@ -1,24 +1,152 @@
1
- import React, { useState, useEffect, useRef } from "react";
1
+ import React, { useState, useEffect, useRef, forwardRef } from "react";
2
2
  import { fallbackValues } from "./MultipleSelectFilter.theme";
3
3
  import { themeComponent } from "../../../util/themeUtils";
4
- import { Box } from "../../atoms/layouts";
5
- import { GHOST_GREY, WHITE, CHARADE_GREY } from "../../../constants/colors";
4
+ import { Box, Center } from "../../atoms/layouts";
5
+ import { FormInput } from "../../atoms/form-layouts";
6
+ import ButtonWithAction from "../../atoms/button-with-action";
7
+ import Checkbox from "../../atoms/checkbox";
8
+ import { GHOST_GREY, WHITE } from "../../../constants/colors";
9
+ import { Text } from "../../atoms";
10
+ import DropdownIconV2 from "../../atoms/dropdown/DropdownIconV2";
11
+ import { FONT_WEIGHT_REGULAR } from "../../../constants/style_constants";
6
12
  import { noop } from "../../../util/general";
7
- import { FilterContainer } from "./MultipleSelectFilter.styled";
8
- import ActionLinkButton from "./__private__/ActionLinkButton";
9
- import FilterButton from "./__private__/FilterButton";
10
- import FilterDropdown from "./__private__/FilterDropdown";
11
- import SearchBox from "./__private__/SearchBox";
12
- import FilterableList from "./__private__/FilterableList";
13
- import useOutsideClickHook from "../../../hooks/use-outside-click";
13
+ import {
14
+ FilterContainer,
15
+ FilterButton,
16
+ FilterDropdown
17
+ } from "./MultipleSelectFilter.styled";
18
+
19
+ const ScrollableOptionsList = ({
20
+ id,
21
+ optionsList,
22
+ selectedOptions,
23
+ themeValues,
24
+ selectOption,
25
+ maxSelections,
26
+ name
27
+ }) => {
28
+ const [focusedIndex, setFocusedIndex] = useState(-1);
29
+ const checkboxRefs = useRef([]);
30
+ const isMaxSelectionReached =
31
+ maxSelections && maxSelections === selectedOptions?.length;
32
+ const isChecked = option =>
33
+ selectedOptions?.some(
34
+ selectedOption => selectedOption?.name === option?.name
35
+ );
36
+
37
+ useEffect(() => {
38
+ if (
39
+ focusedIndex !== -1 &&
40
+ checkboxRefs.current &&
41
+ checkboxRefs.current[focusedIndex]
42
+ ) {
43
+ checkboxRefs.current[focusedIndex].focus(); // move focus to the active option
44
+ }
45
+ }, [focusedIndex]);
46
+
47
+ const handleKeyDown = event => {
48
+ if (event.key === "ArrowDown") {
49
+ event.preventDefault();
50
+ setFocusedIndex(prevIndex =>
51
+ prevIndex < optionsList.length - 1 ? prevIndex + 1 : 0
52
+ );
53
+ } else if (event.key === "ArrowUp") {
54
+ event.preventDefault();
55
+ setFocusedIndex(prevIndex =>
56
+ prevIndex > 0 ? prevIndex - 1 : optionsList.length - 1
57
+ );
58
+ } else if (event.key === " ") {
59
+ event.preventDefault();
60
+ // Select option on spacebar press if the maximum selection hasn't been reached.
61
+ const validFocusedIndex = focusedIndex < 0 ? 0 : focusedIndex;
62
+ if (
63
+ !isMaxSelectionReached ||
64
+ (focusedIndex !== -1 && isChecked(optionsList[validFocusedIndex]))
65
+ ) {
66
+ selectOption(optionsList[validFocusedIndex]);
67
+ }
68
+ } else if (event.key === "Tab") {
69
+ // Reset focus when tabbing out of the list.
70
+ setTimeout(() => {
71
+ setFocusedIndex(-1);
72
+ }, 500);
73
+ }
74
+ };
75
+
76
+ return (
77
+ <Box
78
+ id={id}
79
+ role="listbox"
80
+ padding="0"
81
+ extraStyles={`
82
+ overflow-y: auto;
83
+ max-height: 250px;
84
+ display: flex;
85
+ flex-flow: column;
86
+ `}
87
+ onKeyDown={handleKeyDown}
88
+ >
89
+ {optionsList.map((option, index) => {
90
+ const checked = isChecked(option);
91
+ const isDisabled = isMaxSelectionReached && !checked;
92
+ const tabIndex =
93
+ index === focusedIndex || (index === 0 && focusedIndex === -1)
94
+ ? "0"
95
+ : "-1";
96
+ return (
97
+ <Box
98
+ padding="0"
99
+ key={index}
100
+ extraStyles={`
101
+ :hover,
102
+ :active,
103
+ :focus {
104
+ background-color: ${themeValues.primaryColor};
105
+ }
106
+ `}
107
+ >
108
+ <Checkbox
109
+ ref={el => (checkboxRefs.current[index] = el)}
110
+ title={option.name}
111
+ name={option.name}
112
+ checked={checked}
113
+ onChange={() => (isDisabled ? noop : selectOption(option))}
114
+ textExtraStyles={`font-size: 0.875rem; margin: 0;`}
115
+ disabled={isDisabled}
116
+ extraStyles={`
117
+ padding: 0.075rem 0.325rem;
118
+ margin: 0;
119
+ :hover,
120
+ :active,
121
+ :focus {
122
+ background-color: ${themeValues.primaryColor};
123
+ }
124
+ `}
125
+ checkboxMargin="0.3rem"
126
+ role="option"
127
+ checkboxExtraStyles={`
128
+ width: 1.375rem;
129
+ height: 1.375rem;
130
+ ${
131
+ checked && !isDisabled
132
+ ? `background: ` + themeValues.secondaryColor + `;`
133
+ : ""
134
+ }`}
135
+ tabIndex={tabIndex}
136
+ dataQa={`${name}-option-${index}`}
137
+ />
138
+ </Box>
139
+ );
140
+ })}
141
+ </Box>
142
+ );
143
+ };
14
144
 
15
145
  const MultipleSelectFilter = ({
16
146
  actions,
17
147
  autocompleteValue,
18
148
  btnContentOverride,
19
- btnExtraStyles,
20
149
  disabled,
21
- dropdownExtraStyles,
22
150
  extraStyles,
23
151
  fields,
24
152
  filterLabel,
@@ -31,34 +159,58 @@ const MultipleSelectFilter = ({
31
159
  options,
32
160
  placeholder = "Search",
33
161
  searchable = true,
162
+ selectedOptions,
163
+ setSelectedOptions,
34
164
  themeValues,
35
165
  truncateBtnTextWidth = "15rem"
36
166
  }) => {
167
+ const [itemList, setItemList] = useState([]);
37
168
  const [opened, setOpened] = useState(false);
38
- const [selectedOptions, setSelectedOptions] = useState([]);
39
- const [appliedOptions, setAppliedOptions] = useState([]);
40
- const openedRef = useRef(opened);
41
-
42
- const handleOnClose = () => {
43
- if (openedRef.current) {
44
- setOpened(false);
45
- actions.fields.searchTerm.set("");
46
- }
47
- };
48
- const containerRef = useOutsideClickHook(() => handleOnClose());
169
+ const containerRef = useRef(null);
49
170
  const dropdownRef = useRef(null);
50
171
  const filterButtonRef = useRef(null);
51
172
  const applyFilterButtonRef = useRef(null);
52
173
  const filterDropdownID = `${name}-filter-dropdown`;
53
- const listGroupID = `${name}-list-group`;
174
+ const checkboxListID = `${name}-checkbox-list`;
175
+
176
+ const backgroundColor = opened
177
+ ? themeValues.primaryColor
178
+ : selectedOptions?.length
179
+ ? themeValues.secondaryColor
180
+ : WHITE;
181
+ const contentColor = !opened && selectedOptions?.length ? WHITE : "#292A33";
182
+
183
+ const completeOptionsList = itemList
184
+ .slice()
185
+ .sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
186
+ const selectValues = items => items.map(item => item.value);
187
+ const selectOption = selectedOption => {
188
+ if (selectValues(selectedOptions).includes(selectedOption.value)) {
189
+ const fewerOptions = selectedOptions.filter(
190
+ option => option.value !== selectedOption.value
191
+ );
192
+ setSelectedOptions(fewerOptions);
193
+ } else {
194
+ const moreOptions = selectedOptions.concat(selectedOption);
195
+ setSelectedOptions(moreOptions);
196
+ }
197
+ };
198
+
199
+ useEffect(() => setItemList(options), [options]);
54
200
 
55
201
  useEffect(() => {
56
- openedRef.current = opened;
57
- if (!opened) {
58
- onApply(selectedOptions);
59
- setAppliedOptions(selectedOptions);
202
+ const filteredItems = options.filter(item =>
203
+ item?.name
204
+ ?.toLowerCase()
205
+ .includes(fields?.searchTerm?.rawValue?.toLowerCase())
206
+ );
207
+ // If no items are filtered, display the entire list of options.
208
+ if (filteredItems?.length) {
209
+ setItemList(filteredItems);
210
+ } else {
211
+ setItemList(options);
60
212
  }
61
- }, [opened]);
213
+ }, [fields.searchTerm.rawValue]);
62
214
 
63
215
  useEffect(() => {
64
216
  const handleKeyDown = event => {
@@ -81,11 +233,27 @@ const MultipleSelectFilter = ({
81
233
  filterButtonRef.current &&
82
234
  filterButtonRef.current.contains(event.target))
83
235
  ) {
84
- handleOnClose();
236
+ setOpened(false);
237
+ actions.fields.searchTerm.set("");
238
+ onApply(selectedOptions);
239
+ }
240
+ };
241
+ const handleClickOutside = event => {
242
+ if (
243
+ containerRef.current &&
244
+ !containerRef.current.contains(event.target) &&
245
+ dropdownRef.current &&
246
+ !dropdownRef.current.contains(event.target)
247
+ ) {
248
+ setOpened(false);
249
+ actions.fields.searchTerm.set("");
250
+ onApply(selectedOptions);
85
251
  }
86
252
  };
253
+ document.addEventListener("mousedown", handleClickOutside);
87
254
  document.addEventListener("keydown", handleKeyDown);
88
255
  return () => {
256
+ document.addEventListener("mousedown", handleClickOutside);
89
257
  document.removeEventListener("keydown", handleKeyDown);
90
258
  };
91
259
  }, []);
@@ -94,56 +262,101 @@ const MultipleSelectFilter = ({
94
262
  <FilterContainer ref={containerRef} extraStyles={`${extraStyles}`}>
95
263
  <FilterButton
96
264
  ref={filterButtonRef}
97
- btnContentOverride={btnContentOverride}
265
+ variant="tertiary"
98
266
  action={() => {
99
267
  actions.fields.searchTerm.set("");
100
268
  setOpened(!opened);
101
269
  }}
102
- opened={opened}
103
- backgroundColor={
104
- opened
105
- ? themeValues.primaryColor
106
- : selectedOptions?.length
107
- ? themeValues.secondaryColor
108
- : WHITE
109
- }
110
- contentColor={!opened && selectedOptions?.length ? WHITE : CHARADE_GREY}
111
- name={name}
112
- filterDropdownID={filterDropdownID}
113
- hasIcon={hasIcon}
114
- icon={Icon}
115
- truncateBtnTextWidth={truncateBtnTextWidth}
116
- filterLabel={filterLabel}
117
- selectedOptions={selectedOptions}
118
- extraStyles={btnExtraStyles}
119
- ></FilterButton>
270
+ aria-haspopup="listbox"
271
+ aria-expanded={opened}
272
+ aria-controls={filterDropdownID}
273
+ backgroundColor={backgroundColor}
274
+ dataQa={`${name}-filter-button`}
275
+ contentOverride
276
+ >
277
+ {btnContentOverride ? (
278
+ btnContentOverride
279
+ ) : (
280
+ <Center
281
+ as="span"
282
+ style={{ display: "flex", flexDirection: "row" }}
283
+ intrinsic
284
+ >
285
+ {hasIcon && <Icon color={contentColor} />}
286
+ <Center
287
+ as="span"
288
+ style={{
289
+ display: "flex",
290
+ flexDirection: "row",
291
+ padding: "0 0.5rem 0 0.25rem"
292
+ }}
293
+ intrinsic
294
+ >
295
+ <Text
296
+ variant="pS"
297
+ color={contentColor}
298
+ extraStyles={`
299
+ white-space: nowrap;
300
+ overflow: hidden;
301
+ text-overflow: ellipsis;
302
+ ${truncateBtnTextWidth && `max-width:` + truncateBtnTextWidth}
303
+ `}
304
+ >
305
+ {selectedOptions?.length
306
+ ? `${filterLabel ? filterLabel + ": " : ""}${
307
+ selectedOptions[0].name
308
+ }`
309
+ : `${filterLabel ? filterLabel : ""}`}
310
+ </Text>
311
+ <Text color={contentColor} variant="pS">
312
+ {selectedOptions?.length && selectedOptions?.length > 1
313
+ ? `, +${selectedOptions?.length - 1} More`
314
+ : ""}
315
+ </Text>
316
+ </Center>
317
+ <DropdownIconV2 color={contentColor} />
318
+ </Center>
319
+ )}
320
+ </FilterButton>
120
321
  <FilterDropdown
121
322
  id={filterDropdownID}
122
323
  ref={dropdownRef}
123
- ariaOwns={listGroupID}
124
- ariaControls={listGroupID}
125
- opened={opened}
126
- extraStyles={dropdownExtraStyles}
324
+ hidden={!opened}
325
+ role="combobox"
326
+ aria-expanded={opened}
327
+ aria-haspopup="listbox"
328
+ aria-owns={checkboxListID}
127
329
  >
128
- <SearchBox
129
- showSearchBox={searchable && options?.length > 8}
130
- autocompleteValue={autocompleteValue}
131
- fields={fields}
132
- actions={actions}
133
- placeholder={placeholder}
134
- disabled={disabled}
135
- ></SearchBox>
136
- <FilterableList
137
- id={listGroupID}
138
- options={options}
139
- appliedOptions={appliedOptions}
140
- themeValues={themeValues}
330
+ <Box padding="0 0 0.5rem">
331
+ {searchable && options?.length > 8 && (
332
+ <FormInput
333
+ autocompleteValue={autocompleteValue}
334
+ showFieldErrorRow={false}
335
+ errorMessages={{}}
336
+ field={fields.searchTerm}
337
+ fieldActions={actions.fields.searchTerm}
338
+ placeholder={placeholder}
339
+ disabled={disabled}
340
+ extraStyles={`
341
+ height: 2.875rem;
342
+ border: 0;
343
+ border-radius: 0;
344
+ padding: 0.45rem;
345
+ font-size: 0.875rem;
346
+ border-bottom: 1px solid ${GHOST_GREY};
347
+ `}
348
+ />
349
+ )}
350
+ </Box>
351
+ <ScrollableOptionsList
352
+ id={checkboxListID}
353
+ optionsList={completeOptionsList}
141
354
  selectedOptions={selectedOptions}
355
+ themeValues={themeValues}
356
+ selectOption={selectOption}
142
357
  maxSelections={maxSelections}
143
358
  name={name}
144
- setSelectedOptions={setSelectedOptions}
145
- searchTerm={fields?.searchTerm}
146
- ></FilterableList>
359
+ ></ScrollableOptionsList>
147
360
  <Box
148
361
  padding="0 0.5rem 0.0625rem 0.5rem"
149
362
  extraStyles={`
@@ -154,23 +367,42 @@ const MultipleSelectFilter = ({
154
367
  border-top: 1px solid ${GHOST_GREY};
155
368
  `}
156
369
  >
157
- <ActionLinkButton
370
+ <ButtonWithAction
158
371
  action={() => {
372
+ setOpened(false);
159
373
  setSelectedOptions([]);
160
- handleOnClose();
374
+ actions.fields.searchTerm.set("");
161
375
  onClear();
162
376
  }}
377
+ variant="tertiary"
378
+ extraStyles={`
379
+ padding: 0.2rem;
380
+ margin: 0.5rem;
381
+ min-height: auto;
382
+ min-width: auto;
383
+ `}
384
+ textExtraStyles={`font-weight: ${FONT_WEIGHT_REGULAR};`}
163
385
  text="Clear"
164
386
  dataQa={`${name}-clear-filters`}
165
- ariaLabel={"Clear all filters"}
166
- ></ActionLinkButton>
167
- <ActionLinkButton
387
+ ></ButtonWithAction>
388
+ <ButtonWithAction
168
389
  ref={applyFilterButtonRef}
169
- action={() => handleOnClose()}
390
+ action={() => {
391
+ setOpened(false);
392
+ actions.fields.searchTerm.set("");
393
+ onApply(selectedOptions);
394
+ }}
395
+ variant="tertiary"
396
+ extraStyles={`
397
+ padding: 0.2rem;
398
+ margin: 0.5rem;
399
+ min-height: auto;
400
+ min-width: auto;
401
+ `}
402
+ textExtraStyles={`font-weight: ${FONT_WEIGHT_REGULAR};`}
170
403
  text="Apply"
171
404
  dataQa={`${name}-apply-filters`}
172
- ariaLabel={"Apply all filters"}
173
- ></ActionLinkButton>
405
+ ></ButtonWithAction>
174
406
  </Box>
175
407
  </FilterDropdown>
176
408
  </FilterContainer>
@@ -41,6 +41,8 @@ const items = [
41
41
  ];
42
42
 
43
43
  const FormWrapper = props => {
44
+ const [selectedItems, setSelectedItems] = useState(props.selectedItems || []);
45
+
44
46
  return (
45
47
  <MultipleSelectFilter
46
48
  autocompleteValue={props.autocompleteValue}
@@ -53,8 +55,8 @@ const FormWrapper = props => {
53
55
  placeholder={"Find an agency"}
54
56
  fields={props.fields}
55
57
  actions={props.actions}
56
- btnExtraStyles={props.btnExtraStyles}
57
- dropdownExtraStyles={props.dropdownExtraStyles}
58
+ selectedOptions={selectedItems}
59
+ setSelectedOptions={setSelectedItems}
58
60
  />
59
61
  );
60
62
  };
@@ -5,9 +5,9 @@ import ButtonWithAction from "../../atoms/button-with-action";
5
5
 
6
6
  const StyledFilterContainer = styled(Box)`
7
7
  position: relative;
8
- overflow: visible;
9
8
  box-sizing: border-box;
10
9
  padding: 0;
10
+ ${({ extraStyles }) => extraStyles}
11
11
  `;
12
12
 
13
13
  export const FilterContainer = forwardRef((props, ref) => (
@@ -18,7 +18,7 @@ const StyledFilterDropdown = styled(Box)`
18
18
  position: absolute;
19
19
  top: calc(100% + 0.5rem);
20
20
  left: 0;
21
- width: 18.4rem;
21
+ width: 100%;
22
22
  background-color: white;
23
23
  z-index: 1000;
24
24
  border-radius: 0.25rem;
@@ -28,11 +28,11 @@ const StyledFilterDropdown = styled(Box)`
28
28
  max-width: 18.625rem;
29
29
  `;
30
30
 
31
- export const FilterDropdownContainer = forwardRef((props, ref) => (
31
+ export const FilterDropdown = forwardRef((props, ref) => (
32
32
  <StyledFilterDropdown ref={ref} {...props} />
33
33
  ));
34
34
 
35
- const FilterButton = styled(ButtonWithAction)`
35
+ const StyledFilterButton = styled(ButtonWithAction)`
36
36
  min-width: auto;
37
37
  min-height: 2.3125rem;
38
38
  margin: 0;
@@ -52,6 +52,6 @@ const FilterButton = styled(ButtonWithAction)`
52
52
  `}
53
53
  `;
54
54
 
55
- export const StyledFilterButton = forwardRef((props, ref) => (
56
- <FilterButton ref={ref} {...props} />
55
+ export const FilterButton = forwardRef((props, ref) => (
56
+ <StyledFilterButton ref={ref} {...props} />
57
57
  ));
@@ -10,9 +10,7 @@ export interface MultipleSelectFilterProps {
10
10
  actions: FieldActions;
11
11
  autocompleteValue?: boolean;
12
12
  btnContentOverride?: JSX.Element;
13
- btnExtraStyles?: string;
14
13
  disabled: boolean;
15
- dropdownExtraStyles?: string;
16
14
  extraStyles?: string;
17
15
  fields: {
18
16
  searchTerm: Field;
@@ -26,6 +24,8 @@ export interface MultipleSelectFilterProps {
26
24
  options: SearchableSelectOption[];
27
25
  placeholder?: string;
28
26
  searchable?: boolean;
27
+ selectedOptions: SearchableSelectOption[];
28
+ setSelectedOptions: (options: SearchableSelectOption[]) => void;
29
29
  themeValues?: any[];
30
30
  truncateBtnTextWidth?: string;
31
31
  }
@@ -0,0 +1,23 @@
1
+ import { Canvas, Meta, Title, Story, Controls } from '@storybook/blocks';
2
+
3
+ import * as ObligationStories from './Obligation.stories.js';
4
+
5
+ <Meta of={ObligationStories} />
6
+
7
+ <Title />
8
+
9
+ The Obligation is a purpose built component to display user accounts on NFE's User Profile. The component consumers configuration provided by FCS to dictate its icon and type. NFE uses this configuration to assemble the description and subDescription from the obligation's custom attributes. A full detail of the structure of the configuration object is beyond the scope of this documentation, but an example can be found in the story code, and further detail is available on Confluence within the implementation guides used by solutions engineers.
10
+
11
+ The component currently has two states. An active obligation uses data from the user lookup and FCS to provide account details and amount due as well as controls to interact with the obligation, such as for viewing account details or enabling/disabling autopay.
12
+
13
+ An inactive obligation is one whose data is not fully returned from lookup (this happens when our systems contain record of the account because of its association with a user, but the source system or source file ingested by FLS no longer provides that information, such as when a debt gets paid off). Inactive obligations look different and take some props to display a small amount of information to the user about what account used to be represented there.
14
+
15
+ If the current user is a customer management admin accessing the profile via the RevM impersonation feature, then most of the interaction controls with the Obligation will be disabled.
16
+
17
+ The Obligation was designed to be extensible to meet product needs in the future. New "modules" can be added to the modules directory that feature different collections of buttons or information display. Some work will need to be done to the main component code to support such additions.
18
+
19
+ <Controls />
20
+
21
+ <div style={{ marginBottom: "2em"}}>
22
+ <Story />
23
+ </div>