@thecb/components 9.2.2-beta.4 → 9.2.3-beta.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thecb/components",
3
- "version": "9.2.2-beta.4",
3
+ "version": "9.2.3-beta.3",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "typings": "dist/index.d.ts",
@@ -95,7 +95,8 @@ const Checkbox = ({
95
95
  checkboxMargin = "0 16px 0 0",
96
96
  extraStyles,
97
97
  textExtraStyles,
98
- dataQa = null
98
+ dataQa = null,
99
+ isRequired = false
99
100
  }) => {
100
101
  const [focused, setFocused] = useState(false);
101
102
 
@@ -142,6 +143,7 @@ const Checkbox = ({
142
143
  disabledStyles={themeValues.disabledStyles}
143
144
  disabledCheckedStyles={themeValues.disabledCheckedStyles}
144
145
  focusedStyles={themeValues.focusedStyles}
146
+ aria-required={isRequired}
145
147
  >
146
148
  <CheckboxIcon
147
149
  viewBox="0 0 24 24"
@@ -13,12 +13,13 @@ const groupId = "props";
13
13
  export const checkbox = () => (
14
14
  <Checkbox
15
15
  variant={select(variantsLabel, variants, defaultValue, groupId)}
16
- title={text("title", "Checkbox", "props")}
17
- name={text("name", "Checkbox", "props")}
16
+ title={text("title", "Checkbox Title", "props")}
17
+ name={text("name", "Checkbox Name", "props")}
18
18
  checked={boolean("checked", false, "props")}
19
19
  error={boolean("error", false, "props")}
20
20
  disabled={boolean("disabled", false, "props")}
21
21
  focused={boolean("focused", false, "props")}
22
+ isRequired={boolean("isRequired", true, "props")}
22
23
  />
23
24
  );
24
25
 
@@ -10,7 +10,8 @@ const CountryDropdown = ({
10
10
  fieldActions,
11
11
  showErrors,
12
12
  onChange,
13
- dataQa = null
13
+ dataQa = null,
14
+ isRequired = false
14
15
  }) => (
15
16
  <FormSelect
16
17
  options={options}
@@ -22,6 +23,7 @@ const CountryDropdown = ({
22
23
  showErrors={showErrors}
23
24
  onChange={onChange}
24
25
  autocompleteValue="country-name"
26
+ isRequired={isRequired}
25
27
  />
26
28
  );
27
29
  export default CountryDropdown;
@@ -30,6 +30,7 @@ const CountryFormWrapper = ({ fields, actions }) => (
30
30
  options={options}
31
31
  field={fields.country}
32
32
  fieldActions={actions.fields.country}
33
+ isRequired={true}
33
34
  />
34
35
  </Box>
35
36
  );
@@ -133,8 +133,10 @@ const Dropdown = ({
133
133
  ariaDescribedby,
134
134
  autocompleteValue, // browser autofill value, like country-name
135
135
  smoothScroll = true,
136
- ariaInvalid = false
136
+ ariaInvalid = false,
137
+ isRequired = false
137
138
  }) => {
139
+ const required = options.required || isRequired;
138
140
  const [inputValue, setInputValue] = useState("");
139
141
  const [optionsState, setOptionsState] = useState([]);
140
142
  const [filteredOptions, setFilteredOptions] = useState([]);
@@ -262,13 +264,13 @@ const Dropdown = ({
262
264
  useEffect(() => {
263
265
  if (autoEraseTypeAhead) {
264
266
  clearTimeout(timer);
265
- setTimer(setTimeout(() => setInputValue(""), 20000));
267
+ setTimer(setTimeout(() => setInputValue(""), 3000));
266
268
  }
267
269
  setFilteredOptions(
268
270
  options.filter(
269
271
  option =>
270
- option.value.toLowerCase().match(inputValue.toLowerCase()) ||
271
- option.text.toLowerCase().match(inputValue.toLowerCase())
272
+ option.value.toLowerCase().startsWith(inputValue.toLowerCase()) ||
273
+ option.text.toLowerCase().startsWith(inputValue.toLowerCase())
272
274
  )
273
275
  );
274
276
  }, [inputValue]);
@@ -324,7 +326,7 @@ const Dropdown = ({
324
326
  aria-labelledby={ariaLabelledby}
325
327
  aria-describedby={ariaDescribedby}
326
328
  aria-expanded={isOpen}
327
- aria-required={options.required}
329
+ aria-required={required}
328
330
  aria-invalid={ariaInvalid}
329
331
  background={isOpen ? themeValues.hoverColor : WHITE}
330
332
  borderRadius="2px"
@@ -356,7 +358,6 @@ const Dropdown = ({
356
358
  }}
357
359
  padding="12px"
358
360
  placeholder={getSelection()}
359
- required={options.required}
360
361
  role="combobox"
361
362
  themeValues={themeValues}
362
363
  title={hasTitles ? getSelection() : null}
@@ -364,6 +365,8 @@ const Dropdown = ({
364
365
  tabIndex={0}
365
366
  value={inputValue}
366
367
  width="100%"
368
+ /* Non-semantic elements need the aria-* version of the attribute */
369
+ aria-disabled={disabledValues.includes(inputValue)}
367
370
  />
368
371
  <IconWrapper open={isOpen} onClick={onClick}>
369
372
  <DropdownIcon />
@@ -378,6 +381,7 @@ const Dropdown = ({
378
381
  tabIndex={0}
379
382
  role="listbox"
380
383
  id={`${ariaLabelledby}_listbox`}
384
+ required={required}
381
385
  >
382
386
  <Stack childGap="0" as="ul">
383
387
  {filteredOptions.map((choice, i) => {
@@ -116,6 +116,7 @@ const FormInput = ({
116
116
  extraStyles,
117
117
  removeFromValue, // regex of characters to remove before setting value
118
118
  dataQa = null,
119
+ isRequired = false,
119
120
  ...props
120
121
  }) => {
121
122
  const [showPassword, setShowPassword] = useState(false);
@@ -221,6 +222,7 @@ const FormInput = ({
221
222
  $extraStyles={extraStyles}
222
223
  data-qa={dataQa || labelTextWhenNoError}
223
224
  autoComplete={autocompleteValue}
225
+ required={isRequired}
224
226
  {...props}
225
227
  />
226
228
  ) : (
@@ -247,6 +249,7 @@ const FormInput = ({
247
249
  $extraStyles={extraStyles}
248
250
  data-qa={dataQa || labelTextWhenNoError}
249
251
  autoComplete={autocompleteValue}
252
+ required={isRequired}
250
253
  {...props}
251
254
  />
252
255
  )}
@@ -1,4 +1,4 @@
1
- import React, { useState, useRef, useEffect } from "react";
1
+ import React, { useState, useMemo, useRef, useEffect } from "react";
2
2
  import Dropdown from "../dropdown";
3
3
  import Text from "../text";
4
4
  import { ERROR_COLOR } from "../../../constants/colors";
@@ -24,10 +24,25 @@ const FormSelect = ({
24
24
  autocompleteValue, // browser autofill value, like country-name
25
25
  smoothScroll = true, // whether the browser should animate scroll to selected item on first open
26
26
  dataQa = null,
27
- widthFitOptions = false
27
+ widthFitOptions = false,
28
+ isRequired = false
28
29
  }) => {
29
30
  const [open, setOpen] = useState(false);
30
31
  const dropdownRef = useRef(null);
32
+ const required = options?.required || isRequired;
33
+
34
+ const labelId = useMemo(
35
+ () => labelTextWhenNoError => createIdFromString(labelTextWhenNoError),
36
+ [labelTextWhenNoError]
37
+ );
38
+
39
+ const descriptionId = useMemo(
40
+ () => (field, labelTextWhenNoError) =>
41
+ field.hasErrors && field.dirty
42
+ ? labelId(labelTextWhenNoError) + "error-message"
43
+ : "",
44
+ [field, labelTextWhenNoError]
45
+ );
31
46
 
32
47
  const handleClickAway = event => {
33
48
  if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
@@ -45,8 +60,8 @@ const FormSelect = ({
45
60
  return (
46
61
  <SelectContainer
47
62
  ref={dropdownRef}
63
+ aria-role="group"
48
64
  disabled={disabled}
49
- aria-disabled={disabled}
50
65
  data-qa={dataQa}
51
66
  >
52
67
  <Box padding="0" minWidth="100%">
@@ -61,18 +76,15 @@ const FormSelect = ({
61
76
  &::first-letter {
62
77
  text-transform: uppercase;
63
78
  }`}
64
- id={createIdFromString(labelTextWhenNoError)}
79
+ id={labelId(labelTextWhenNoError)}
65
80
  >
66
81
  {labelTextWhenNoError}
67
82
  </Text>
68
83
  </Cluster>
69
84
  </Box>
70
85
  <Dropdown
71
- ariaLabelledby={createIdFromString(labelTextWhenNoError)}
72
- ariaDescribedby={createIdFromString(
73
- labelTextWhenNoError,
74
- "error message"
75
- )}
86
+ ariaLabelledby={labelId(labelTextWhenNoError)}
87
+ ariaDescribedby={descriptionId(field, labelTextWhenNoError)}
76
88
  maxHeight={dropdownMaxHeight}
77
89
  widthFitOptions={widthFitOptions}
78
90
  hasTitles={hasTitles}
@@ -94,6 +106,7 @@ const FormSelect = ({
94
106
  disabled={disabled}
95
107
  autocompleteValue={autocompleteValue}
96
108
  smoothScroll={smoothScroll}
109
+ isRequired={required}
97
110
  />
98
111
  <Stack direction="row" justify="space-between">
99
112
  {(field.hasErrors && field.dirty) || (field.hasErrors && showErrors) ? (
@@ -12,7 +12,7 @@ const { mapStateToProps, mapDispatchToProps, reducer } = createFormState({
12
12
  }
13
13
  });
14
14
  const errorMessages = {
15
- [required.error]: "thing is required"
15
+ [required.error]: "This field is required."
16
16
  };
17
17
  const options = [
18
18
  { value: "", text: "choose name" },
@@ -34,7 +34,7 @@ const story = page({
34
34
  const FormWrapper = props => (
35
35
  <FormSelect
36
36
  autocompleteValue={props.autocompleteValue}
37
- labelTextWhenNoError="Form Select"
37
+ labelTextWhenNoError="Form Select Label"
38
38
  errorMessages={errorMessages}
39
39
  options={options}
40
40
  field={props.fields.thing}
@@ -18,7 +18,7 @@ const Circle = styled.div`
18
18
  flex-shrink: 0;
19
19
  margin-right: 8px;
20
20
  width: 1.5rem;
21
- height 1.5rem;
21
+ height: 1.5rem;
22
22
  border: ${({ inactiveBorderColor }) => `1px solid ${inactiveBorderColor}`};
23
23
  border-radius: 50%;
24
24
  box-sizing: border-box;
@@ -30,21 +30,24 @@ const Circle = styled.div`
30
30
  display: block;
31
31
  background: ${({ activeColor }) => activeColor};
32
32
  border-radius: 50%;
33
- transform: scale(0);
34
- }
33
+ transform: scale(0);
34
+ }
35
35
  `;
36
36
 
37
37
  const InputAndLabelContainer = styled(Cluster)`
38
38
  overflow: visible;
39
+
39
40
  ${HiddenRadioInput}:checked + label ${Circle}:after {
40
41
  transform: scale(0.85);
41
42
  transition: transform 0.15s;
42
43
  }
44
+
43
45
  ${HiddenRadioInput}:checked + label ${Circle} {
44
- border: ${({ activeColor }) => `1px solid ${activeColor};`}
46
+ border: ${({ activeColor }) => "1px solid " + activeColor};
45
47
  }
46
- ${HiddenRadioInput}:focus + label ${Circle} { {
47
- box-shadow: ${({ activeColor }) => `0px 0px 2px 1px ${activeColor};`}
48
+
49
+ ${HiddenRadioInput}:focus + label ${Circle} {
50
+ box-shadow: ${({ activeColor }) => "0px 0px 2px 1px " + activeColor};
48
51
  }
49
52
  `;
50
53
 
@@ -9,11 +9,13 @@ const FormStateDropdown = ({
9
9
  field,
10
10
  fieldActions,
11
11
  showErrors,
12
- countryCode
12
+ countryCode,
13
+ isRequired = false
13
14
  }) => {
14
15
  const placeholder =
15
16
  countryCode === "US" ? placeHolderOptionUS : placeHolderOption;
16
17
  const options = [placeholder, ...getOptions(countryCode)];
18
+
17
19
  return (
18
20
  <FormSelect
19
21
  options={options}
@@ -23,6 +25,7 @@ const FormStateDropdown = ({
23
25
  errorMessages={errorMessages}
24
26
  showErrors={showErrors}
25
27
  autocompleteValue="address-level1"
28
+ isRequired={isRequired}
26
29
  />
27
30
  );
28
31
  };
@@ -39,6 +39,7 @@ const FormWrapper = ({
39
39
  countryCode={countryCode}
40
40
  labelTextWhenNoError={labelTextWhenNoError}
41
41
  showErrors={showErrors}
42
+ isRequired={true}
42
43
  />
43
44
  );
44
45
 
@@ -58,6 +58,7 @@ const AddressForm = ({
58
58
  labelTextWhenNoError="Country"
59
59
  errorMessages={countryErrorMessages}
60
60
  field={fields.country}
61
+ isRequired={true}
61
62
  onChange={value => {
62
63
  actions.fields.country.set(value);
63
64
  // temporary measure to not dirty fields until
@@ -81,6 +82,7 @@ const AddressForm = ({
81
82
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
82
83
  autocompleteValue="address-line1"
83
84
  dataQa="Address"
85
+ isRequired={true}
84
86
  />
85
87
  <FormInput
86
88
  labelTextWhenNoError="Apt, Suite, Unit, Floor, etc. (Optional)"
@@ -90,6 +92,7 @@ const AddressForm = ({
90
92
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
91
93
  autocompleteValue="address-line2"
92
94
  dataQa="Address Line 2"
95
+ isRequired={false}
93
96
  />
94
97
  <FormInput
95
98
  labelTextWhenNoError="City"
@@ -100,6 +103,7 @@ const AddressForm = ({
100
103
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
101
104
  autocompleteValue="address-level2"
102
105
  dataQa="City"
106
+ isRequired={true}
103
107
  />
104
108
  <StateProvinceDropdown
105
109
  labelTextWhenNoError={isUS ? "State" : "State or Province"}
@@ -110,6 +114,7 @@ const AddressForm = ({
110
114
  showErrors={showErrors}
111
115
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
112
116
  dataQa="State or Province"
117
+ isRequired={true}
113
118
  />
114
119
  <FormInput
115
120
  isNum={isUS}
@@ -122,6 +127,7 @@ const AddressForm = ({
122
127
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
123
128
  autocompleteValue="postal-code"
124
129
  dataQa="Zip code"
130
+ isRequired={true}
125
131
  />
126
132
  {showWalletCheckbox && (
127
133
  <Checkbox
@@ -76,6 +76,7 @@ const PaymentFormACH = ({
76
76
  showErrors={showErrors}
77
77
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
78
78
  autocompleteValue="name"
79
+ isRequired={true}
79
80
  />
80
81
  <FormInput
81
82
  labelTextWhenNoError="Routing number"
@@ -97,6 +98,7 @@ const PaymentFormACH = ({
97
98
  />
98
99
  )}
99
100
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
101
+ isRequired={true}
100
102
  />
101
103
  <FormInput
102
104
  labelTextWhenNoError="Confirm routing number"
@@ -106,6 +108,7 @@ const PaymentFormACH = ({
106
108
  fieldActions={actions.fields.confirmRoutingNumber}
107
109
  showErrors={showErrors}
108
110
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
111
+ isRequired={true}
109
112
  isNum
110
113
  />
111
114
  <FormInput
@@ -115,6 +118,7 @@ const PaymentFormACH = ({
115
118
  field={fields.accountNumber}
116
119
  fieldActions={actions.fields.accountNumber}
117
120
  showErrors={showErrors}
121
+ isRequired={true}
118
122
  isNum
119
123
  helperModal={() => (
120
124
  <AccountAndRoutingModal
@@ -132,6 +136,7 @@ const PaymentFormACH = ({
132
136
  <FormInput
133
137
  labelTextWhenNoError="Confirm account number"
134
138
  dataQa="Confirm account number"
139
+ isRequired={true}
135
140
  errorMessages={confirmAccountNumberErrors}
136
141
  field={fields.confirmAccountNumber}
137
142
  fieldActions={actions.fields.confirmAccountNumber}
@@ -142,6 +147,7 @@ const PaymentFormACH = ({
142
147
  {allowBankAccountType && (
143
148
  <FormSelect
144
149
  labelTextWhenNoError="Account type"
150
+ isRequired={true}
145
151
  dataQa="Account type"
146
152
  options={[
147
153
  { text: "Select account type", value: "" },
@@ -98,6 +98,7 @@ const PaymentFormCard = ({
98
98
  {!hideZipCode && (
99
99
  <CountryDropdown
100
100
  labelTextWhenNoError="Country"
101
+ isRequired={true}
101
102
  errorMessages={countryErrorMessages}
102
103
  field={fields.country}
103
104
  onChange={value => {
@@ -121,8 +122,10 @@ const PaymentFormCard = ({
121
122
  showErrors={showErrors}
122
123
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
123
124
  autocompleteValue="cc-name"
125
+ isRequired={true}
124
126
  />
125
127
  <FormInput
128
+ isRequired={true}
126
129
  labelTextWhenNoError="Credit card number"
127
130
  dataQa="Credit card number"
128
131
  errorMessages={creditCardNumberErrors}
@@ -150,6 +153,7 @@ const PaymentFormCard = ({
150
153
  isNum
151
154
  removeFromValue={/\//} // removes "/" from browser autofill
152
155
  autocompleteValue="cc-exp"
156
+ isRequired={true}
153
157
  />
154
158
  <FormInput
155
159
  labelTextWhenNoError="CVV"
@@ -166,6 +170,7 @@ const PaymentFormCard = ({
166
170
  }
167
171
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
168
172
  autocompleteValue="cc-csc"
173
+ isRequired={true}
169
174
  />
170
175
  </FormInputRow>
171
176
  {!hideZipCode && (
@@ -184,6 +189,7 @@ const PaymentFormCard = ({
184
189
  showErrors={showErrors}
185
190
  onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
186
191
  autocompleteValue="billing postal-code"
192
+ isRequired={true}
187
193
  />
188
194
  </Box>
189
195
  )}
@@ -7,7 +7,7 @@ import { noop } from "../../../util/general";
7
7
 
8
8
  const DefaultHeading = styled.div`
9
9
  font-size: 0.875rem;
10
- color: ${colors.CHARADE_GREY}
10
+ color: ${colors.CHARADE_GREY};
11
11
  margin: 0;
12
12
  padding: 8px 0px;
13
13
  `;
@@ -177,6 +177,7 @@ const RadioSection = ({
177
177
  : () => toggleOpenSection(section.id)
178
178
  }
179
179
  tabIndex="-1"
180
+ required={section?.required}
180
181
  />
181
182
  </Box>
182
183
  )}
@@ -49,9 +49,10 @@ const sections = [
49
49
  title: "New Card",
50
50
  content: <p>The form to add a credit card would go here.</p>,
51
51
  rightIconsLabel: cardIconsLabel,
52
- rightIcons: cardIcons
52
+ rightIcons: cardIcons,
53
+ required: true
53
54
  },
54
- { id: "bar", title: "Bar", content: <div>Content 1</div> },
55
+ { id: "bar", title: "Bar", content: <div>Content 1</div>, required: true },
55
56
  { id: "baz", title: "Baz", content: <div>Content 2</div> }
56
57
  ];
57
58
 
@@ -19,7 +19,8 @@ const RadioButton = ({
19
19
  ariaDescribedBy = "",
20
20
  themeValues,
21
21
  ariaLabelledBy = "",
22
- ariaLabel = null
22
+ ariaLabel = null,
23
+ required = false
23
24
  }) => {
24
25
  const buttonBorder = {
25
26
  onFocused: {
@@ -90,6 +91,8 @@ const RadioButton = ({
90
91
  type="radio"
91
92
  id={`radio-${name}`}
92
93
  disabled={disabled}
94
+ required={required}
95
+ aria-required={required}
93
96
  onClick={toggleRadio}
94
97
  aria-describedby={ariaDescribedBy}
95
98
  tabIndex="-1"