@thecb/components 9.3.0-beta.1 → 9.3.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 (74) hide show
  1. package/dist/index.cjs.js +850 -616
  2. package/dist/index.cjs.js.map +1 -1
  3. package/dist/index.d.ts +171 -60
  4. package/dist/index.esm.js +847 -616
  5. package/dist/index.esm.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/.DS_Store +0 -0
  8. package/src/components/atoms/button-with-action/ButtonWithAction.js +76 -70
  9. package/src/components/atoms/checkbox/Checkbox.js +9 -4
  10. package/src/components/atoms/checkbox/Checkbox.stories.js +3 -3
  11. package/src/components/atoms/country-dropdown/CountryDropdown.js +2 -2
  12. package/src/components/atoms/country-dropdown/CountryDropdown.stories.js +0 -1
  13. package/src/components/atoms/dropdown/Dropdown.js +77 -44
  14. package/src/components/atoms/dropdown/Dropdown.theme.js +8 -2
  15. package/src/components/atoms/form-layouts/FormInput.js +2 -3
  16. package/src/components/atoms/form-select/FormSelect.js +25 -34
  17. package/src/components/atoms/form-select/FormSelect.stories.js +2 -2
  18. package/src/components/atoms/icons/AccountNumberImage.js +2 -0
  19. package/src/components/atoms/icons/BankIcon.js +2 -0
  20. package/src/components/atoms/icons/CheckmarkIcon.js +2 -0
  21. package/src/components/atoms/icons/GenericCard.js +2 -0
  22. package/src/components/atoms/icons/GenericCardLarge.js +2 -0
  23. package/src/components/atoms/icons/KebabMenuIcon.d.ts +1 -0
  24. package/src/components/atoms/icons/KebabMenuIcon.js +38 -0
  25. package/src/components/atoms/icons/RoutingNumberImage.js +2 -0
  26. package/src/components/atoms/icons/TrashIcon.js +42 -40
  27. package/src/components/atoms/icons/icons.stories.js +3 -1
  28. package/src/components/atoms/icons/index.d.ts +1 -0
  29. package/src/components/atoms/icons/index.js +3 -1
  30. package/src/components/atoms/state-province-dropdown/StateProvinceDropdown.js +0 -1
  31. package/src/components/molecules/address-form/AddressForm.js +1 -2
  32. package/src/components/molecules/email-form/EmailForm.js +3 -1
  33. package/src/components/molecules/index.d.ts +1 -0
  34. package/src/components/molecules/index.js +1 -0
  35. package/src/components/molecules/modal/Modal.js +2 -1
  36. package/src/components/molecules/payment-form-ach/PaymentFormACH.js +4 -5
  37. package/src/components/molecules/payment-form-card/PaymentFormCard.js +5 -1
  38. package/src/components/molecules/phone-form/PhoneForm.js +3 -1
  39. package/src/components/molecules/popover/Popover.js +1 -1
  40. package/src/components/molecules/popup-menu/PopupMenu.js +152 -0
  41. package/src/components/molecules/popup-menu/PopupMenu.stories.js +40 -0
  42. package/src/components/molecules/popup-menu/PopupMenu.styled.js +20 -0
  43. package/src/components/molecules/popup-menu/PopupMenu.theme.js +11 -0
  44. package/src/components/molecules/popup-menu/index.d.ts +25 -0
  45. package/src/components/molecules/popup-menu/index.js +3 -0
  46. package/src/components/molecules/popup-menu/popup-menu-item/PopupMenuItem.js +79 -0
  47. package/src/components/molecules/popup-menu/popup-menu-item/PopupMenuItem.styled.js +27 -0
  48. package/src/components/molecules/popup-menu/popup-menu-item/PopupMenuItem.theme.js +23 -0
  49. package/src/components/molecules/radio-section/RadioSection.js +9 -2
  50. package/src/components/molecules/radio-section/RadioSection.stories.js +4 -2
  51. package/src/components/molecules/radio-section/radio-button/RadioButton.js +3 -1
  52. package/src/components/molecules/terms-and-conditions/TermsAndConditions.stories.js +3 -1
  53. package/src/components/molecules/terms-and-conditions/TermsAndConditionsControlV1.js +0 -1
  54. package/src/components/molecules/terms-and-conditions/TermsAndConditionsControlV2.js +5 -2
  55. package/src/components/molecules/toast-notification/ToastNotification.js +75 -0
  56. package/src/components/molecules/toast-notification/ToastNotification.stories.js +67 -0
  57. package/src/components/molecules/toast-notification/index.d.ts +18 -0
  58. package/src/components/molecules/toast-notification/index.js +3 -0
  59. package/src/constants/colors.d.ts +1 -0
  60. package/src/constants/colors.js +5 -1
  61. package/src/hooks/index.js +3 -0
  62. package/src/hooks/use-toast-notification/index.d.ts +23 -0
  63. package/src/hooks/use-toast-notification/index.js +38 -0
  64. package/src/index.d.ts +2 -1
  65. package/src/index.js +2 -1
  66. package/src/types/common/ToastVariants.ts +6 -0
  67. package/src/types/common/index.ts +1 -0
  68. package/src/util/index.js +10 -2
  69. package/dist/src/apps/checkout/pages/payment/sub-pages/payment-amount/PaymentAmount_old.js +0 -49322
  70. package/src/components/.DS_Store +0 -0
  71. package/src/components/atoms/.DS_Store +0 -0
  72. package/src/components/atoms/icons/.DS_Store +0 -0
  73. /package/src/{util/useOutsideClick.js → hooks/use-outside-click/index.js} +0 -0
  74. /package/src/{util/useScrollTo.js → hooks/use-scroll-to/index.js} +0 -0
@@ -9,6 +9,8 @@ const CheckmarkIcon = () => (
9
9
  version="1.1"
10
10
  xmlns="http://www.w3.org/2000/svg"
11
11
  xmlnsXlink="http://www.w3.org/1999/xlink"
12
+ role="img"
13
+ aria-label="Successful payment, green checkmark"
12
14
  >
13
15
  <g
14
16
  id="Cityville-Checkout---Desktop---Logged-In"
@@ -7,6 +7,8 @@ const GenericCard = () => (
7
7
  viewBox="0 0 28 18"
8
8
  fill="none"
9
9
  xmlns="http://www.w3.org/2000/svg"
10
+ role="img"
11
+ aria-label="Card Payment"
10
12
  >
11
13
  <rect width="28" height="18" rx="2" fill="#15749D" />
12
14
  <path
@@ -8,6 +8,8 @@ const GenericCardLarge = () => {
8
8
  viewBox="0 0 36 24"
9
9
  fill="none"
10
10
  xmlns="http://www.w3.org/2000/svg"
11
+ role="img"
12
+ aria-label="Card Payment"
11
13
  >
12
14
  <rect width="36" height="24" rx="2" fill="#15749D" />
13
15
  <path
@@ -0,0 +1 @@
1
+ export const KebabMenuIcon: JSX.Element;
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+
3
+ const KebabMenuIcon = () => {
4
+ return (
5
+ <svg
6
+ xmlns="http://www.w3.org/2000/svg"
7
+ width="21"
8
+ height="32"
9
+ viewBox="0 0 21 32"
10
+ fill="none"
11
+ >
12
+ <path
13
+ d="M0 4C0 1.79086 1.79086 0 4 0L17 0C19.2091 0 21 1.79086 21 4V28C21 30.2091 19.2091 32 17 32H4C1.79086 32 0 30.2091 0 28L0 4Z"
14
+ fill="none"
15
+ />
16
+ <path
17
+ fillRule="evenodd"
18
+ clipRule="evenodd"
19
+ d="M10.5 6C9.39333 6 8.5 6.89333 8.5 8C8.5 9.10667 9.39333 10 10.5 10C11.6067 10 12.5 9.10667 12.5 8C12.5 6.89333 11.6067 6 10.5 6Z"
20
+ fill="#3B5BDB"
21
+ />
22
+ <path
23
+ fillRule="evenodd"
24
+ clipRule="evenodd"
25
+ d="M10.5 14C9.39333 14 8.5 14.8933 8.5 16C8.5 17.1067 9.39333 18 10.5 18C11.6067 18 12.5 17.1067 12.5 16C12.5 14.8933 11.6067 14 10.5 14Z"
26
+ fill="#3B5BDB"
27
+ />
28
+ <path
29
+ fillRule="evenodd"
30
+ clipRule="evenodd"
31
+ d="M10.5 22C9.39333 22 8.5 22.9067 8.5 24C8.5 25.0933 9.40667 26 10.5 26C11.5933 26 12.5 25.0933 12.5 24C12.5 22.9067 11.6067 22 10.5 22Z"
32
+ fill="#3B5BDB"
33
+ />
34
+ </svg>
35
+ );
36
+ };
37
+
38
+ export default KebabMenuIcon;
@@ -7,6 +7,8 @@ const RoutingNumberImage = () => {
7
7
  width="371"
8
8
  height="164"
9
9
  viewBox="0 0 371 164"
10
+ role="img"
11
+ aria-label="A check with the routing number highlighted in the lower left hand corner"
10
12
  >
11
13
  <g fill="none" fillRule="evenodd" stroke="none" strokeWidth="1">
12
14
  <g transform="translate(-365 -522)">
@@ -2,46 +2,48 @@ import React from "react";
2
2
  import { fallbackValues } from "./Icons.theme";
3
3
  import { themeComponent } from "../../../util/themeUtils";
4
4
 
5
- const TrashIcon = ({ themeValues }) => (
6
- <svg
7
- width="20px"
8
- height="20px"
9
- viewBox="0 0 20 20"
10
- version="1.1"
11
- xmlns="http://www.w3.org/2000/svg"
12
- xmlnsXlink="http://www.w3.org/1999/xlink"
13
- >
14
- <defs>
15
- <path
16
- d="M15,7 L14,15.5714286 C14,16.3571429 13.25,17 12.3333333,17 L12.3333333,17 L7.66666667,17 C6.75,17 6,16.3571429 6,15.5714286 L6,15.5714286 L5,7 L15,7 Z M12.1428571,3 L13,4 L16,4 L16,6 L4,6 L4,4 L7,4 L7.85714286,3 L12.1428571,3 Z"
17
- id="trash-path-1"
18
- ></path>
19
- </defs>
20
- <g
21
- id="trash-Icons-/-Small-/-20px-S-/-Trash---Mobile---20px"
22
- stroke="none"
23
- strokeWidth="1"
24
- fill="none"
25
- fillRule="evenodd"
5
+ const TrashIcon = ({ themeValues, iconFill }) => {
6
+ return (
7
+ <svg
8
+ width="20px"
9
+ height="20px"
10
+ viewBox="0 0 20 20"
11
+ version="1.1"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ xmlnsXlink="http://www.w3.org/1999/xlink"
26
14
  >
27
- <mask id="trash-mask-2" fill="white">
28
- <use xlinkHref="#trash-path-1"></use>
29
- </mask>
30
- <use
31
- id="trash-Mask"
32
- fill={themeValues.singleIconColor}
33
- fillRule="nonzero"
34
- xlinkHref="#trash-path-1"
35
- ></use>
36
- <polygon
37
- id="trash-Path"
38
- fill={themeValues.singleIconColor}
39
- fillRule="nonzero"
40
- mask="url(#trash-mask-2)"
41
- points="0 0 20 0 20 20 0 20"
42
- ></polygon>
43
- </g>
44
- </svg>
45
- );
15
+ <defs>
16
+ <path
17
+ d="M15,7 L14,15.5714286 C14,16.3571429 13.25,17 12.3333333,17 L12.3333333,17 L7.66666667,17 C6.75,17 6,16.3571429 6,15.5714286 L6,15.5714286 L5,7 L15,7 Z M12.1428571,3 L13,4 L16,4 L16,6 L4,6 L4,4 L7,4 L7.85714286,3 L12.1428571,3 Z"
18
+ id="trash-path-1"
19
+ ></path>
20
+ </defs>
21
+ <g
22
+ id="trash-Icons-/-Small-/-20px-S-/-Trash---Mobile---20px"
23
+ stroke="none"
24
+ strokeWidth="1"
25
+ fill="none"
26
+ fillRule="evenodd"
27
+ >
28
+ <mask id="trash-mask-2" fill="white">
29
+ <use xlinkHref="#trash-path-1"></use>
30
+ </mask>
31
+ <use
32
+ id="trash-Mask"
33
+ fill={iconFill ?? themeValues.singleIconColor}
34
+ fillRule="nonzero"
35
+ xlinkHref="#trash-path-1"
36
+ ></use>
37
+ <polygon
38
+ id="trash-Path"
39
+ fill={iconFill ?? themeValues.singleIconColor}
40
+ fillRule="nonzero"
41
+ mask="url(#trash-mask-2)"
42
+ points="0 0 20 0 20 20 0 20"
43
+ ></polygon>
44
+ </g>
45
+ </svg>
46
+ );
47
+ };
46
48
 
47
49
  export default themeComponent(TrashIcon, "Icons", fallbackValues, "primary");
@@ -38,7 +38,8 @@ import {
38
38
  SuccessfulIcon,
39
39
  VoidedIcon,
40
40
  StatusUnknownIcon,
41
- AutopayIcon
41
+ AutopayIcon,
42
+ KebabMenuIcon
42
43
  } from "./index";
43
44
 
44
45
  const story = page({
@@ -84,3 +85,4 @@ export const successfulIcon = () => <SuccessfulIcon />;
84
85
  export const voidedIcon = () => <VoidedIcon />;
85
86
  export const statusUnknownIcon = () => <StatusUnknownIcon />;
86
87
  export const autopayIcon = () => <AutopayIcon />;
88
+ export const kebabMenuIcon = () => <KebabMenuIcon />;
@@ -21,3 +21,4 @@ export * from "./SuccessfulIconMedium";
21
21
  export * from "./SuccessfulIconSmall";
22
22
  export * from "./XCircleIconMedium";
23
23
  export * from "./XCircleIconSmall";
24
+ export * from "./KebabMenuIcon";
@@ -86,6 +86,7 @@ import ArrowLeftCircleIconMedium from "./ArrowLeftCircleIconMedium";
86
86
  import ChargebackIconMedium from "./ChargebackIconMedium";
87
87
  import ChargebackReversalIconMedium from "./ChargebackReversalIconMedium";
88
88
  import PlusCircleIcon from "./PlusCircleIcon";
89
+ import KebabMenuIcon from "./KebabMenuIcon";
89
90
 
90
91
  export {
91
92
  AccountsIcon,
@@ -175,5 +176,6 @@ export {
175
176
  ArrowLeftCircleIconMedium,
176
177
  ChargebackIconMedium,
177
178
  ChargebackReversalIconMedium,
178
- PlusCircleIcon
179
+ PlusCircleIcon,
180
+ KebabMenuIcon
179
181
  };
@@ -15,7 +15,6 @@ const FormStateDropdown = ({
15
15
  const placeholder =
16
16
  countryCode === "US" ? placeHolderOptionUS : placeHolderOption;
17
17
  const options = [placeholder, ...getOptions(countryCode)];
18
-
19
18
  return (
20
19
  <FormSelect
21
20
  options={options}
@@ -58,7 +58,6 @@ const AddressForm = ({
58
58
  labelTextWhenNoError="Country"
59
59
  errorMessages={countryErrorMessages}
60
60
  field={fields.country}
61
- isRequired={true}
62
61
  onChange={value => {
63
62
  actions.fields.country.set(value);
64
63
  // temporary measure to not dirty fields until
@@ -72,6 +71,7 @@ const AddressForm = ({
72
71
  }}
73
72
  showErrors={showErrors}
74
73
  dataQa="Country"
74
+ isRequired={true}
75
75
  />
76
76
  <FormInput
77
77
  labelTextWhenNoError="Address"
@@ -92,7 +92,6 @@ const AddressForm = ({
92
92
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
93
93
  autocompleteValue="address-line2"
94
94
  dataQa="Address Line 2"
95
- isRequired={false}
96
95
  />
97
96
  <FormInput
98
97
  labelTextWhenNoError="City"
@@ -19,7 +19,8 @@ const EmailForm = ({
19
19
  handleSubmit = noop,
20
20
  showWalletCheckbox,
21
21
  saveToWallet,
22
- walletCheckboxMarked
22
+ walletCheckboxMarked,
23
+ isRequired = false
23
24
  }) => {
24
25
  if (clearOnDismount) {
25
26
  useEffect(() => () => actions.form.clear(), []);
@@ -48,6 +49,7 @@ const EmailForm = ({
48
49
  isEmail
49
50
  autocompleteValue="email"
50
51
  dataQa="Email address"
52
+ isRequired={isRequired}
51
53
  />
52
54
  {showWalletCheckbox && (
53
55
  <Checkbox
@@ -4,3 +4,4 @@ export * from "./editable-table";
4
4
  export * from "./footer-with-subfooter";
5
5
  export * from "./popover";
6
6
  export * from "./radio-group";
7
+ export * from "./toast-notification";
@@ -37,5 +37,6 @@ export { default as TabSidebar } from "./tab-sidebar";
37
37
  export { default as TermsAndConditions } from "./terms-and-conditions";
38
38
  export { default as TermsAndConditionsModal } from "./terms-and-conditions-modal";
39
39
  export { default as Timeout } from "./timeout";
40
+ export { default as ToastNotification } from "./toast-notification";
40
41
  export { default as WelcomeModule } from "./welcome-module";
41
42
  export { default as WorkflowTile } from "./workflow-tile";
@@ -67,7 +67,8 @@ const Modal = ({
67
67
  alignItems: "center"
68
68
  }}
69
69
  dialogStyle={{
70
- width: customWidth || "615px"
70
+ width: customWidth || "615px",
71
+ overflow: "auto"
71
72
  }}
72
73
  underlayClickExits={underlayClickExits}
73
74
  >
@@ -108,8 +108,8 @@ const PaymentFormACH = ({
108
108
  fieldActions={actions.fields.confirmRoutingNumber}
109
109
  showErrors={showErrors}
110
110
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
111
- isRequired={true}
112
111
  isNum
112
+ isRequired={true}
113
113
  />
114
114
  <FormInput
115
115
  labelTextWhenNoError="Account number"
@@ -118,7 +118,6 @@ const PaymentFormACH = ({
118
118
  field={fields.accountNumber}
119
119
  fieldActions={actions.fields.accountNumber}
120
120
  showErrors={showErrors}
121
- isRequired={true}
122
121
  isNum
123
122
  helperModal={() => (
124
123
  <AccountAndRoutingModal
@@ -132,22 +131,22 @@ const PaymentFormACH = ({
132
131
  />
133
132
  )}
134
133
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
134
+ isRequired={true}
135
135
  />
136
136
  <FormInput
137
137
  labelTextWhenNoError="Confirm account number"
138
138
  dataQa="Confirm account number"
139
- isRequired={true}
140
139
  errorMessages={confirmAccountNumberErrors}
141
140
  field={fields.confirmAccountNumber}
142
141
  fieldActions={actions.fields.confirmAccountNumber}
143
142
  showErrors={showErrors}
144
143
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
144
+ isRequired={true}
145
145
  isNum
146
146
  />
147
147
  {allowBankAccountType && (
148
148
  <FormSelect
149
149
  labelTextWhenNoError="Account type"
150
- isRequired={true}
151
150
  dataQa="Account type"
152
151
  options={[
153
152
  { text: "Select account type", value: "" },
@@ -158,6 +157,7 @@ const PaymentFormACH = ({
158
157
  showErrors={showErrors}
159
158
  errorMessages={accountTypeErrors}
160
159
  field={fields.accountType}
160
+ isRequired={true}
161
161
  />
162
162
  )}
163
163
  {!hideDefaultPayment && (
@@ -188,7 +188,6 @@ const PaymentFormACH = ({
188
188
  showCheckbox={false}
189
189
  description="View"
190
190
  terms={termsContent}
191
- isRequired={true}
192
191
  />
193
192
  </Cover>
194
193
  )}
@@ -98,7 +98,6 @@ const PaymentFormCard = ({
98
98
  {!hideZipCode && (
99
99
  <CountryDropdown
100
100
  labelTextWhenNoError="Country"
101
- isRequired={true}
102
101
  errorMessages={countryErrorMessages}
103
102
  field={fields.country}
104
103
  onChange={value => {
@@ -122,6 +121,7 @@ const PaymentFormCard = ({
122
121
  showErrors={showErrors}
123
122
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
124
123
  autocompleteValue="cc-name"
124
+ isRequired={true}
125
125
  />
126
126
  <FormInput
127
127
  labelTextWhenNoError="Credit card number"
@@ -134,6 +134,7 @@ const PaymentFormCard = ({
134
134
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
135
135
  isNum
136
136
  autocompleteValue="cc-number"
137
+ isRequired={true}
137
138
  />
138
139
  <FormInputRow
139
140
  breakpoint={isMobile ? "1000rem" : "21rem"}
@@ -151,6 +152,7 @@ const PaymentFormCard = ({
151
152
  isNum
152
153
  removeFromValue={/\//} // removes "/" from browser autofill
153
154
  autocompleteValue="cc-exp"
155
+ isRequired={true}
154
156
  />
155
157
  <FormInput
156
158
  labelTextWhenNoError="CVV"
@@ -167,6 +169,7 @@ const PaymentFormCard = ({
167
169
  }
168
170
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
169
171
  autocompleteValue="cc-csc"
172
+ isRequired={true}
170
173
  />
171
174
  </FormInputRow>
172
175
  {!hideZipCode && (
@@ -185,6 +188,7 @@ const PaymentFormCard = ({
185
188
  showErrors={showErrors}
186
189
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
187
190
  autocompleteValue="billing postal-code"
191
+ isRequired={true}
188
192
  />
189
193
  </Box>
190
194
  )}
@@ -19,7 +19,8 @@ const PhoneForm = ({
19
19
  handleSubmit = noop,
20
20
  showWalletCheckbox,
21
21
  saveToWallet,
22
- walletCheckboxMarked
22
+ walletCheckboxMarked,
23
+ isRequired = false
23
24
  }) => {
24
25
  if (clearOnDismount) {
25
26
  useEffect(() => () => actions.form.clear(), []);
@@ -43,6 +44,7 @@ const PhoneForm = ({
43
44
  autocompleteValue="tel-national"
44
45
  dataQa="Phone number"
45
46
  isNum={true}
47
+ isRequired={isRequired}
46
48
  />
47
49
  {showWalletCheckbox && (
48
50
  <Checkbox
@@ -4,7 +4,7 @@ import Text from "../../atoms/text";
4
4
  import Paragraph from "../../atoms/paragraph";
5
5
  import { Box } from "../../atoms/layouts";
6
6
  import ButtonWithAction from "../../atoms/button-with-action";
7
- import { useOutsideClick } from "../../../util";
7
+ import { useOutsideClick } from "../../../hooks";
8
8
  import { noop } from "../../../util/general";
9
9
  import { fallbackValues } from "./Popover.theme";
10
10
 
@@ -0,0 +1,152 @@
1
+ import React, { useState, useRef, useEffect } from "react";
2
+ import { themeComponent } from "../../../util/themeUtils";
3
+ import Text from "../../atoms/text";
4
+ import { Box } from "../../atoms/layouts";
5
+ import PopupMenuItem from "./popup-menu-item/PopupMenuItem";
6
+ import { fallbackValues } from "./PopupMenu.theme";
7
+ import { PopupMenuContainer, PopupMenuTriggerButton } from "./PopupMenu.styled";
8
+
9
+ const PopupMenu = ({
10
+ menuId = "popup-menu",
11
+ menuItems = [],
12
+ themeValues,
13
+ triggerText = "trigger text",
14
+ hasIcon = false,
15
+ icon: Icon,
16
+ iconHelpText = "", // for screen-readers, required if using an icon for trigger
17
+ menuFocus,
18
+ containerExtraStyles,
19
+ textExtraStyles,
20
+ minWidth = "250px",
21
+ maxWidth = "300px",
22
+ height = "auto",
23
+ position,
24
+ transform = "none",
25
+ buttonExtraStyles,
26
+ popupExtraStyles
27
+ }) => {
28
+ const {
29
+ hoverColor,
30
+ activeColor,
31
+ menuTriggerColor,
32
+ backgroundColor
33
+ } = themeValues;
34
+ const { top = `${height}px`, right = "auto", bottom = "auto", left = "0" } =
35
+ position ?? {};
36
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
37
+ const menuRef = useRef();
38
+ const triggerRef = useRef();
39
+ const toggleMenu = menuState => setIsMenuOpen(menuState);
40
+
41
+ useEffect(() => {
42
+ const checkIfClickedOutside = e => {
43
+ // If the menu is open and the clicked target is not within the menu or the trigger
44
+ if (
45
+ isMenuOpen &&
46
+ menuRef.current &&
47
+ !menuRef.current.contains(e.target) &&
48
+ triggerRef.current &&
49
+ !triggerRef.current.contains(e.target)
50
+ ) {
51
+ toggleMenu(false);
52
+ }
53
+ };
54
+
55
+ document.addEventListener("click", checkIfClickedOutside);
56
+
57
+ return () => {
58
+ document.removeEventListener("click", checkIfClickedOutside);
59
+ };
60
+ }, [isMenuOpen]);
61
+
62
+ return (
63
+ <PopupMenuContainer extraStyles={containerExtraStyles}>
64
+ <PopupMenuTriggerButton
65
+ ref={triggerRef}
66
+ action={() => {
67
+ toggleMenu(!isMenuOpen);
68
+ }}
69
+ onKeyDown={e => {
70
+ if (e.key === "Escape") {
71
+ toggleMenu(false);
72
+ }
73
+ }}
74
+ contentOverride
75
+ variant="smallGhost"
76
+ tabIndex="0"
77
+ id={menuId}
78
+ borderRadius="8px"
79
+ aria-haspopup="true"
80
+ aria-expanded={isMenuOpen}
81
+ aria-controls={`${menuId}-container`}
82
+ extraStyles={buttonExtraStyles}
83
+ >
84
+ {hasIcon && (
85
+ <>
86
+ <Icon />
87
+ <Box padding="0" srOnly>
88
+ <Text id={`btn${menuId}_info`}>{iconHelpText}</Text>
89
+ </Box>
90
+ </>
91
+ )}
92
+ {!hasIcon && (
93
+ <Text
94
+ color={menuTriggerColor}
95
+ extraStyles={`&:active { color: ${activeColor}; } &:hover { color: ${hoverColor} }; ${textExtraStyles}`}
96
+ >
97
+ {triggerText}
98
+ </Text>
99
+ )}
100
+ </PopupMenuTriggerButton>
101
+ <Box
102
+ as="div"
103
+ id={`${menuId}-container`}
104
+ ref={menuRef}
105
+ onKeyDown={e => {
106
+ if (e.key === "Escape") {
107
+ toggleMenu(false);
108
+ }
109
+ }}
110
+ background={backgroundColor}
111
+ borderRadius="8px"
112
+ boxShadow={`
113
+ 0px 7px 32px 0px rgba(41, 42, 51, 0.2),
114
+ 0px 1px 4px 0px rgba(41, 42, 51, 0.2),
115
+ 0px 1px 8px -1px rgba(41, 42, 51, 0.3);
116
+ `}
117
+ role="menu"
118
+ aria-labelledby={menuId}
119
+ tabIndex={menuFocus && isMenuOpen ? "0" : "-1"}
120
+ minWidth={minWidth}
121
+ maxWidth={maxWidth}
122
+ extraStyles={`
123
+ display: ${isMenuOpen ? "block" : "none"};
124
+ position: absolute;
125
+ padding: 8px 8px 3px 8px;
126
+ top: ${top};
127
+ left: ${left};
128
+ right: ${right};
129
+ bottom: ${bottom};
130
+ height: ${height};
131
+ transform: ${transform};
132
+ ${popupExtraStyles};
133
+ `}
134
+ >
135
+ {menuItems.map((item, index) => (
136
+ <PopupMenuItem
137
+ key={index}
138
+ id={`${menuId}-item-${index}`}
139
+ closeMenuCallback={() => {
140
+ toggleMenu(false);
141
+ // focus back to trigger button when menu closes
142
+ triggerRef.current.focus();
143
+ }}
144
+ {...item}
145
+ />
146
+ ))}
147
+ </Box>
148
+ </PopupMenuContainer>
149
+ );
150
+ };
151
+
152
+ export default themeComponent(PopupMenu, "PopupMenu", fallbackValues);
@@ -0,0 +1,40 @@
1
+ import React from "react";
2
+ import { KebabMenuIcon, TrashIcon } from "../../atoms";
3
+ import PopupMenu from "./PopupMenu";
4
+ import page from "../../../../.storybook/page";
5
+ import { noop } from "../../../util/general";
6
+
7
+ const menuItems = [
8
+ {
9
+ text: "Account Details",
10
+ action: noop
11
+ },
12
+ {
13
+ text: "Remove",
14
+ action: noop,
15
+ isDeleteAction: true,
16
+ hasIcon: true,
17
+ icon: TrashIcon
18
+ }
19
+ ];
20
+
21
+ const story = page({
22
+ title: "Components|Molecules/PopupMenu",
23
+ Component: PopupMenu
24
+ });
25
+
26
+ export const popupMenu = () => (
27
+ <PopupMenu
28
+ hasIcon="true"
29
+ menuItems={menuItems}
30
+ icon={KebabMenuIcon}
31
+ minWidth={"50px"}
32
+ maxWidth={"208px"}
33
+ position={{ top: "0", left: "auto", right: "63px" }}
34
+ menuId={"menuId"}
35
+ containerExtraStyles={`margin-bottom: 100px;`}
36
+ buttonExtraStyles={`margin: 0 0 0 auto;`}
37
+ popupExtraStyles={`padding: 8px 8px 3px 8px;`}
38
+ />
39
+ );
40
+ export default story;
@@ -0,0 +1,20 @@
1
+ import styled from "styled-components";
2
+ import { ButtonWithAction } from "../../atoms";
3
+ import { Box } from "../../atoms";
4
+
5
+ export const PopupMenuContainer = styled(Box)`
6
+ display: flex;
7
+ position: relative;
8
+ padding: 0;
9
+ `;
10
+
11
+ export const PopupMenuTriggerButton = styled(ButtonWithAction)`
12
+ padding: 10px 15px;
13
+ min-width: auto;
14
+ &:active,
15
+ &:focus {
16
+ outline: none;
17
+ border: 1px solid rgba(196, 206, 244, 1);
18
+ background-color: rgba(235, 239, 251, 1);
19
+ }
20
+ `;
@@ -0,0 +1,11 @@
1
+ const hoverColor = "#116285";
2
+ const activeColor = "#0E506D";
3
+ const menuTriggerColor = "#15749D";
4
+ const backgroundColor = "white";
5
+
6
+ export const fallbackValues = {
7
+ hoverColor,
8
+ activeColor,
9
+ menuTriggerColor,
10
+ backgroundColor
11
+ };