@khanacademy/wonder-blocks-birthday-picker 1.0.0 → 1.2.1

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/CHANGELOG.md CHANGED
@@ -1,12 +1,45 @@
1
1
  # @khanacademy/wonder-blocks-birthday-picker
2
2
 
3
+ ## 1.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [246a921d]
8
+ - @khanacademy/wonder-blocks-core@4.3.0
9
+ - @khanacademy/wonder-blocks-dropdown@2.6.6
10
+ - @khanacademy/wonder-blocks-icon@1.2.26
11
+ - @khanacademy/wonder-blocks-layout@1.4.8
12
+ - @khanacademy/wonder-blocks-typography@1.1.30
13
+
14
+ ## 1.2.0
15
+
16
+ ### Minor Changes
17
+
18
+ - 5536d12e: Add `monthYearOnly` prop to hide the `day` field from the UI. Passes the first day of the selected month for this case.
19
+
20
+ ### Patch Changes
21
+
22
+ - @khanacademy/wonder-blocks-dropdown@2.6.5
23
+
24
+ ## 1.1.0
25
+
26
+ ### Minor Changes
27
+
28
+ - 17fa61fd: Add `disabled` prop
29
+
30
+ ### Patch Changes
31
+
32
+ - Updated dependencies [77e7523c]
33
+ - @khanacademy/wonder-blocks-dropdown@2.6.4
34
+
3
35
  ## 1.0.0
36
+
4
37
  ### Major Changes
5
38
 
6
- - 11c87db3: - Added `wonder-blocks-birthday-picker`.
7
- - Dropdown: Updated some styles from `backgroundColor` to `background` to avoid some warnings on unit tests.
39
+ - 11c87db3: - Added `wonder-blocks-birthday-picker`.
40
+ - Dropdown: Updated some styles from `backgroundColor` to `background` to avoid some warnings on unit tests.
8
41
 
9
42
  ### Patch Changes
10
43
 
11
- - Updated dependencies [11c87db3]
12
- - @khanacademy/wonder-blocks-dropdown@2.6.3
44
+ - Updated dependencies [11c87db3]
45
+ - @khanacademy/wonder-blocks-dropdown@2.6.3
package/dist/es/index.js CHANGED
@@ -18,7 +18,12 @@ const defaultLabels = Object.freeze({
18
18
  month: "Month",
19
19
  year: "Year",
20
20
  day: "Day"
21
- });
21
+ }); // Default minWidth value when we include the full DOB.
22
+
23
+ const FIELD_MIN_WIDTH_FULL = 110; // Alternative minWidth value when we hide the day field.
24
+ // See: https://www.figma.com/file/uJZi9ZvuEz5N8GJ3HqKFAa/(2021)-Account-records?node-id=20%3A398
25
+
26
+ const FIELD_MIN_WIDTH_MONTH_YEAR = 167;
22
27
  /**
23
28
  * Birthday Picker. Similar to a datepicker, but specifically for birthdates.
24
29
  * We don't want to show a calendar in this case as it can be quite tedious to
@@ -127,11 +132,12 @@ class BirthdayPicker extends React.Component {
127
132
 
128
133
  getStateFromDefault() {
129
134
  const {
130
- defaultValue
135
+ defaultValue,
136
+ monthYearOnly
131
137
  } = this.props;
132
138
  const initialState = {
133
139
  month: null,
134
- day: null,
140
+ day: monthYearOnly ? "1" : null,
135
141
  year: null,
136
142
  error: null
137
143
  }; // merge custom labels with the default ones
@@ -191,32 +197,48 @@ class BirthdayPicker extends React.Component {
191
197
  }, error)));
192
198
  }
193
199
 
194
- render() {
200
+ renderMonth() {
195
201
  const {
196
- month,
197
- day,
198
- year
202
+ disabled,
203
+ monthYearOnly
204
+ } = this.props;
205
+ const {
206
+ month
199
207
  } = this.state;
200
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, {
201
- testId: "birthday-picker",
202
- style: {
203
- flexDirection: "row"
204
- }
205
- }, /*#__PURE__*/React.createElement(SingleSelect, {
208
+ const minWidth = monthYearOnly ? FIELD_MIN_WIDTH_MONTH_YEAR : FIELD_MIN_WIDTH_FULL;
209
+ return /*#__PURE__*/React.createElement(SingleSelect, {
210
+ disabled: disabled,
206
211
  placeholder: this.labels.month,
207
212
  onChange: this.handleMonthChange,
208
213
  selectedValue: month,
209
214
  style: {
210
- minWidth: 110
215
+ minWidth
211
216
  },
212
217
  testId: "birthday-picker-month"
213
218
  }, moment.monthsShort().map((month, i) => /*#__PURE__*/React.createElement(OptionItem, {
214
219
  key: month,
215
220
  label: month,
216
221
  value: String(i)
217
- }))), /*#__PURE__*/React.createElement(Strut, {
222
+ })));
223
+ }
224
+
225
+ maybeRenderDay() {
226
+ const {
227
+ disabled,
228
+ monthYearOnly
229
+ } = this.props;
230
+ const {
231
+ day
232
+ } = this.state; // Hide the day field if the month/year only mode is enabled.
233
+
234
+ if (monthYearOnly) {
235
+ return null;
236
+ }
237
+
238
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Strut, {
218
239
  size: Spacing.xSmall_8
219
240
  }), /*#__PURE__*/React.createElement(SingleSelect, {
241
+ disabled: disabled,
220
242
  placeholder: this.labels.day,
221
243
  onChange: this.handleDayChange,
222
244
  selectedValue: day,
@@ -228,21 +250,43 @@ class BirthdayPicker extends React.Component {
228
250
  key: String(day + 1),
229
251
  label: String(day + 1),
230
252
  value: String(day + 1)
231
- }))), /*#__PURE__*/React.createElement(Strut, {
232
- size: Spacing.xSmall_8
233
- }), /*#__PURE__*/React.createElement(SingleSelect, {
253
+ }))));
254
+ }
255
+
256
+ renderYear() {
257
+ const {
258
+ disabled,
259
+ monthYearOnly
260
+ } = this.props;
261
+ const {
262
+ year
263
+ } = this.state;
264
+ const minWidth = monthYearOnly ? FIELD_MIN_WIDTH_MONTH_YEAR : FIELD_MIN_WIDTH_FULL;
265
+ return /*#__PURE__*/React.createElement(SingleSelect, {
266
+ disabled: disabled,
234
267
  placeholder: this.labels.year,
235
268
  onChange: this.handleYearChange,
236
269
  selectedValue: year,
237
270
  style: {
238
- minWidth: 110
271
+ minWidth
239
272
  },
240
273
  testId: "birthday-picker-year"
241
274
  }, Array.from(Array(120)).map((_, yearOffset) => /*#__PURE__*/React.createElement(OptionItem, {
242
275
  key: String(CUR_YEAR - yearOffset),
243
276
  label: String(CUR_YEAR - yearOffset),
244
277
  value: String(CUR_YEAR - yearOffset)
245
- })))), this.maybeRenderError());
278
+ })));
279
+ }
280
+
281
+ render() {
282
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, {
283
+ testId: "birthday-picker",
284
+ style: {
285
+ flexDirection: "row"
286
+ }
287
+ }, this.renderMonth(), this.maybeRenderDay(), /*#__PURE__*/React.createElement(Strut, {
288
+ size: Spacing.xSmall_8
289
+ }), this.renderYear()), this.maybeRenderError());
246
290
  }
247
291
 
248
292
  }
package/dist/index.js CHANGED
@@ -176,7 +176,12 @@ const defaultLabels = Object.freeze({
176
176
  month: "Month",
177
177
  year: "Year",
178
178
  day: "Day"
179
- });
179
+ }); // Default minWidth value when we include the full DOB.
180
+
181
+ const FIELD_MIN_WIDTH_FULL = 110; // Alternative minWidth value when we hide the day field.
182
+ // See: https://www.figma.com/file/uJZi9ZvuEz5N8GJ3HqKFAa/(2021)-Account-records?node-id=20%3A398
183
+
184
+ const FIELD_MIN_WIDTH_MONTH_YEAR = 167;
180
185
  /**
181
186
  * Birthday Picker. Similar to a datepicker, but specifically for birthdates.
182
187
  * We don't want to show a calendar in this case as it can be quite tedious to
@@ -285,11 +290,12 @@ class BirthdayPicker extends react__WEBPACK_IMPORTED_MODULE_1__["Component"] {
285
290
 
286
291
  getStateFromDefault() {
287
292
  const {
288
- defaultValue
293
+ defaultValue,
294
+ monthYearOnly
289
295
  } = this.props;
290
296
  const initialState = {
291
297
  month: null,
292
- day: null,
298
+ day: monthYearOnly ? "1" : null,
293
299
  year: null,
294
300
  error: null
295
301
  }; // merge custom labels with the default ones
@@ -351,32 +357,48 @@ class BirthdayPicker extends react__WEBPACK_IMPORTED_MODULE_1__["Component"] {
351
357
  }, error)));
352
358
  }
353
359
 
354
- render() {
360
+ renderMonth() {
355
361
  const {
356
- month,
357
- day,
358
- year
362
+ disabled,
363
+ monthYearOnly
364
+ } = this.props;
365
+ const {
366
+ month
359
367
  } = this.state;
360
- return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](react__WEBPACK_IMPORTED_MODULE_1__["Fragment"], null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_2__["View"], {
361
- testId: "birthday-picker",
362
- style: {
363
- flexDirection: "row"
364
- }
365
- }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_dropdown__WEBPACK_IMPORTED_MODULE_8__["SingleSelect"], {
368
+ const minWidth = monthYearOnly ? FIELD_MIN_WIDTH_MONTH_YEAR : FIELD_MIN_WIDTH_FULL;
369
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_dropdown__WEBPACK_IMPORTED_MODULE_8__["SingleSelect"], {
370
+ disabled: disabled,
366
371
  placeholder: this.labels.month,
367
372
  onChange: this.handleMonthChange,
368
373
  selectedValue: month,
369
374
  style: {
370
- minWidth: 110
375
+ minWidth
371
376
  },
372
377
  testId: "birthday-picker-month"
373
378
  }, moment__WEBPACK_IMPORTED_MODULE_0___default.a.monthsShort().map((month, i) => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_dropdown__WEBPACK_IMPORTED_MODULE_8__["OptionItem"], {
374
379
  key: month,
375
380
  label: month,
376
381
  value: String(i)
377
- }))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_layout__WEBPACK_IMPORTED_MODULE_3__["Strut"], {
382
+ })));
383
+ }
384
+
385
+ maybeRenderDay() {
386
+ const {
387
+ disabled,
388
+ monthYearOnly
389
+ } = this.props;
390
+ const {
391
+ day
392
+ } = this.state; // Hide the day field if the month/year only mode is enabled.
393
+
394
+ if (monthYearOnly) {
395
+ return null;
396
+ }
397
+
398
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](react__WEBPACK_IMPORTED_MODULE_1__["Fragment"], null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_layout__WEBPACK_IMPORTED_MODULE_3__["Strut"], {
378
399
  size: _khanacademy_wonder_blocks_spacing__WEBPACK_IMPORTED_MODULE_4___default.a.xSmall_8
379
400
  }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_dropdown__WEBPACK_IMPORTED_MODULE_8__["SingleSelect"], {
401
+ disabled: disabled,
380
402
  placeholder: this.labels.day,
381
403
  onChange: this.handleDayChange,
382
404
  selectedValue: day,
@@ -388,21 +410,43 @@ class BirthdayPicker extends react__WEBPACK_IMPORTED_MODULE_1__["Component"] {
388
410
  key: String(day + 1),
389
411
  label: String(day + 1),
390
412
  value: String(day + 1)
391
- }))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_layout__WEBPACK_IMPORTED_MODULE_3__["Strut"], {
392
- size: _khanacademy_wonder_blocks_spacing__WEBPACK_IMPORTED_MODULE_4___default.a.xSmall_8
393
- }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_dropdown__WEBPACK_IMPORTED_MODULE_8__["SingleSelect"], {
413
+ }))));
414
+ }
415
+
416
+ renderYear() {
417
+ const {
418
+ disabled,
419
+ monthYearOnly
420
+ } = this.props;
421
+ const {
422
+ year
423
+ } = this.state;
424
+ const minWidth = monthYearOnly ? FIELD_MIN_WIDTH_MONTH_YEAR : FIELD_MIN_WIDTH_FULL;
425
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_dropdown__WEBPACK_IMPORTED_MODULE_8__["SingleSelect"], {
426
+ disabled: disabled,
394
427
  placeholder: this.labels.year,
395
428
  onChange: this.handleYearChange,
396
429
  selectedValue: year,
397
430
  style: {
398
- minWidth: 110
431
+ minWidth
399
432
  },
400
433
  testId: "birthday-picker-year"
401
434
  }, Array.from(Array(120)).map((_, yearOffset) => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_dropdown__WEBPACK_IMPORTED_MODULE_8__["OptionItem"], {
402
435
  key: String(CUR_YEAR - yearOffset),
403
436
  label: String(CUR_YEAR - yearOffset),
404
437
  value: String(CUR_YEAR - yearOffset)
405
- })))), this.maybeRenderError());
438
+ })));
439
+ }
440
+
441
+ render() {
442
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](react__WEBPACK_IMPORTED_MODULE_1__["Fragment"], null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_2__["View"], {
443
+ testId: "birthday-picker",
444
+ style: {
445
+ flexDirection: "row"
446
+ }
447
+ }, this.renderMonth(), this.maybeRenderDay(), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_khanacademy_wonder_blocks_layout__WEBPACK_IMPORTED_MODULE_3__["Strut"], {
448
+ size: _khanacademy_wonder_blocks_spacing__WEBPACK_IMPORTED_MODULE_4___default.a.xSmall_8
449
+ }), this.renderYear()), this.maybeRenderError());
406
450
  }
407
451
 
408
452
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-birthday-picker",
3
- "version": "1.0.0",
3
+ "version": "1.2.1",
4
4
  "design": "v1",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -15,12 +15,12 @@
15
15
  "dependencies": {
16
16
  "@babel/runtime": "^7.16.3",
17
17
  "@khanacademy/wonder-blocks-color": "^1.1.20",
18
- "@khanacademy/wonder-blocks-core": "^4.2.0",
19
- "@khanacademy/wonder-blocks-dropdown": "^2.6.3",
20
- "@khanacademy/wonder-blocks-icon": "^1.2.24",
21
- "@khanacademy/wonder-blocks-layout": "^1.4.6",
18
+ "@khanacademy/wonder-blocks-core": "^4.3.0",
19
+ "@khanacademy/wonder-blocks-dropdown": "^2.6.6",
20
+ "@khanacademy/wonder-blocks-icon": "^1.2.26",
21
+ "@khanacademy/wonder-blocks-layout": "^1.4.8",
22
22
  "@khanacademy/wonder-blocks-spacing": "^3.0.5",
23
- "@khanacademy/wonder-blocks-typography": "^1.1.28"
23
+ "@khanacademy/wonder-blocks-typography": "^1.1.30"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "aphrodite": "^1.2.5",
@@ -2,37 +2,13 @@
2
2
  import {defaultLabels} from "../birthday-picker.js";
3
3
 
4
4
  export default {
5
- defaultValue: {
6
- control: {type: "text"},
7
- description:
8
- "The default value to populate the birthdate with. Should be in the format: `YYYY-MM-DD` (e.g. 2021-05-26). It's only used to populate the initial value as this is an uncontrolled component.",
9
- table: {
10
- type: {
11
- summary: "string",
12
- },
13
- },
14
- },
15
5
  labels: {
16
- control: {type: "object"},
17
- description:
18
- "The object containing the custom labels used inside this component.",
19
6
  defaultValue: defaultLabels,
20
- table: {
21
- type: {
22
- summary: "Labels",
23
- detail: "{month?: string, year?: string, day?: string, errorMessage?: string}",
24
- },
25
- },
26
7
  },
27
8
  onChange: {
28
9
  action: "onChanged",
29
- description:
30
- "Listen for changes to the birthdate. Could be a string in the `YYYY-MM-DD` format or `null`.",
31
10
  table: {
32
11
  category: "Events",
33
- type: {
34
- summary: "(date: ?string) => mixed",
35
- },
36
12
  },
37
13
  },
38
14
  };
@@ -64,6 +64,10 @@ BirthdayPickerWithDefaultValue.parameters = {
64
64
  chromatic: {
65
65
  disableSnapshot: true,
66
66
  },
67
+ docs: {
68
+ storyDescription:
69
+ "This component is empty by default, but we can pass in a defined birthday by using the `defaultValue` prop.",
70
+ },
67
71
  };
68
72
 
69
73
  export const InvalidBirthdayPicker: StoryComponentType = Template.bind({});
@@ -73,6 +77,13 @@ InvalidBirthdayPicker.args = {
73
77
  defaultValue: "2030-07-19",
74
78
  };
75
79
 
80
+ InvalidBirthdayPicker.parameters = {
81
+ docs: {
82
+ storyDescription:
83
+ "In case the birthday is invalid, we display an error message indicating this problem.",
84
+ },
85
+ };
86
+
76
87
  export const BirthdayPickerWithCustomLabels: StoryComponentType = Template.bind(
77
88
  {},
78
89
  );
@@ -87,3 +98,42 @@ BirthdayPickerWithCustomLabels.args = {
87
98
  errorMessage: "Por favor seleccione una fecha válida.",
88
99
  },
89
100
  };
101
+
102
+ BirthdayPickerWithCustomLabels.parameters = {
103
+ docs: {
104
+ storyDescription:
105
+ "We can pass custom labels to the component. This can be helpful when we need to pass in translated strings. The default labels are: `Day`, `Month`, `Year` used as placeholders and 'Please select a valid birthdate.' to indicate an `errorMessage` when the validation fails. For more info about how to use this, refer to the `labels` prop in the Props table documentation above.",
106
+ },
107
+ };
108
+
109
+ export const DisabledBirthdayPicker: StoryComponentType = Template.bind({});
110
+
111
+ DisabledBirthdayPicker.args = {
112
+ onChange: () => {},
113
+ disabled: true,
114
+ };
115
+
116
+ DisabledBirthdayPicker.parameters = {
117
+ docs: {
118
+ storyDescription:
119
+ "A BirthdayPicker can be disabled by passing the `disabled` prop. This will disable all the dropdown controls and prevent them from being interacted with.",
120
+ },
121
+ };
122
+
123
+ export const BirthdayPickerWithYearAndMonthOnly: StoryComponentType =
124
+ Template.bind({});
125
+
126
+ BirthdayPickerWithYearAndMonthOnly.args = {
127
+ monthYearOnly: true,
128
+ onChange: (date: ?string) => {
129
+ // eslint-disable-next-line no-console
130
+ console.log("Date selected: ", date);
131
+ },
132
+ };
133
+
134
+ BirthdayPickerWithYearAndMonthOnly.parameters = {
135
+ docs: {
136
+ storyDescription:
137
+ "A BirthdayPicker can be configured to only show the year and month dropdowns. This can be useful when we want to display and collect a birthday that doesn't require the full DOB for privacy reasons.",
138
+ },
139
+ };
@@ -34,6 +34,18 @@ describe("BirthdayPicker", () => {
34
34
  expect(yearPicker).toHaveTextContent("Year");
35
35
  });
36
36
 
37
+ it("renders without the day field if monthYearOnly is set", () => {
38
+ // Arrange
39
+
40
+ // Act
41
+ render(<BirthdayPicker monthYearOnly={true} onChange={() => {}} />);
42
+
43
+ const dayPicker = screen.queryByTestId("birthday-picker-day");
44
+
45
+ // Assert
46
+ expect(dayPicker).not.toBeInTheDocument();
47
+ });
48
+
37
49
  it("renders with a valid default value", () => {
38
50
  // Arrange
39
51
  const date = moment(today);
@@ -122,6 +134,22 @@ describe("BirthdayPicker", () => {
122
134
  expect(yearPicker).toHaveTextContent("Year");
123
135
  });
124
136
 
137
+ it("renders correctly the disabled state", () => {
138
+ // Arrange
139
+
140
+ // Act
141
+ render(<BirthdayPicker disabled={true} onChange={() => {}} />);
142
+
143
+ const monthPicker = screen.getByTestId("birthday-picker-month");
144
+ const dayPicker = screen.getByTestId("birthday-picker-day");
145
+ const yearPicker = screen.getByTestId("birthday-picker-year");
146
+
147
+ // Assert
148
+ expect(monthPicker).toBeDisabled();
149
+ expect(dayPicker).toBeDisabled();
150
+ expect(yearPicker).toBeDisabled();
151
+ });
152
+
125
153
  it("renders an error with an invalid default value", async () => {
126
154
  // Arrange
127
155
  const defaultValue = "2021-02-31";
@@ -153,9 +181,9 @@ describe("BirthdayPicker", () => {
153
181
  // Arrange
154
182
  const onChange = jest.fn();
155
183
 
156
- // Act
157
184
  render(<BirthdayPicker onChange={onChange} />);
158
185
 
186
+ // Act
159
187
  userEvent.click(screen.getByTestId("birthday-picker-month"));
160
188
  const monthOption = await screen.findByRole("option", {
161
189
  name: "Jul",
@@ -242,7 +270,6 @@ describe("BirthdayPicker", () => {
242
270
  // Arrange
243
271
  const onChange = jest.fn();
244
272
 
245
- // Act
246
273
  render(
247
274
  <BirthdayPicker
248
275
  defaultValue="2017-07-17"
@@ -250,6 +277,7 @@ describe("BirthdayPicker", () => {
250
277
  />,
251
278
  );
252
279
 
280
+ // Act
253
281
  userEvent.click(screen.getByTestId("birthday-picker-month"));
254
282
  const monthOption = await screen.findByRole("option", {
255
283
  name: "Aug",
@@ -311,7 +339,6 @@ describe("BirthdayPicker", () => {
311
339
  // Arrange
312
340
  const onChange = jest.fn();
313
341
 
314
- // Act
315
342
  render(
316
343
  <BirthdayPicker
317
344
  defaultValue="2021-02-31"
@@ -319,6 +346,7 @@ describe("BirthdayPicker", () => {
319
346
  />,
320
347
  );
321
348
 
349
+ // Act
322
350
  userEvent.click(screen.getByTestId("birthday-picker-year"));
323
351
  const yearOption = await screen.findByRole("option", {
324
352
  name: "2020",
@@ -330,6 +358,68 @@ describe("BirthdayPicker", () => {
330
358
  // Assert
331
359
  expect(onChange).toHaveBeenCalledTimes(1);
332
360
  });
361
+
362
+ it("onChange triggers the first day of the month when monthYearOnly is set", async () => {
363
+ // Arrange
364
+ const onChange = jest.fn();
365
+
366
+ render(<BirthdayPicker monthYearOnly={true} onChange={onChange} />);
367
+
368
+ // Act
369
+ userEvent.click(screen.getByTestId("birthday-picker-month"));
370
+ const monthOption = await screen.findByRole("option", {
371
+ name: "Aug",
372
+ });
373
+ userEvent.click(monthOption, undefined, {
374
+ skipPointerEventsCheck: true,
375
+ });
376
+
377
+ userEvent.click(screen.getByTestId("birthday-picker-year"));
378
+ const yearOption = await screen.findByRole("option", {
379
+ name: "2018",
380
+ });
381
+ userEvent.click(yearOption, undefined, {
382
+ skipPointerEventsCheck: true,
383
+ });
384
+
385
+ // Assert
386
+ // Verify that we passed the first day of the month
387
+ expect(onChange).toHaveBeenCalledWith("2018-08-01");
388
+ });
389
+
390
+ it("onChange triggers the passed-in day intact when defaultValue and monthYearOnly are set", async () => {
391
+ // Arrange
392
+ const onChange = jest.fn();
393
+
394
+ render(
395
+ <BirthdayPicker
396
+ defaultValue="2017-07-17"
397
+ monthYearOnly={true}
398
+ onChange={onChange}
399
+ />,
400
+ );
401
+
402
+ // Act
403
+ userEvent.click(screen.getByTestId("birthday-picker-month"));
404
+ const monthOption = await screen.findByRole("option", {
405
+ name: "Aug",
406
+ });
407
+ userEvent.click(monthOption, undefined, {
408
+ skipPointerEventsCheck: true,
409
+ });
410
+
411
+ userEvent.click(screen.getByTestId("birthday-picker-year"));
412
+ const yearOption = await screen.findByRole("option", {
413
+ name: "2018",
414
+ });
415
+ userEvent.click(yearOption, undefined, {
416
+ skipPointerEventsCheck: true,
417
+ });
418
+
419
+ // Assert
420
+ // Verify that we passed the same day originally passed in.
421
+ expect(onChange).toHaveBeenCalledWith("2018-08-17");
422
+ });
333
423
  });
334
424
 
335
425
  describe("labels", () => {
@@ -39,11 +39,26 @@ type Props = {|
39
39
  */
40
40
  defaultValue?: string,
41
41
 
42
+ /**
43
+ * Whether the birthdate fields are disabled.
44
+ */
45
+ disabled?: boolean,
46
+
42
47
  /**
43
48
  * The object containing the custom labels used inside this component.
44
49
  */
45
50
  labels?: Labels,
46
51
 
52
+ /**
53
+ * Whether we want to hide the day field.
54
+ *
55
+ * **NOTE:** We will set the day to the _first_ day of the _selected_ month
56
+ * if the day field is hidden. Please make sure to modify the passed date
57
+ * value to fit different needs (e.g. if you want to set the _first_ day of
58
+ * the _following_ month instead).
59
+ */
60
+ monthYearOnly?: boolean,
61
+
47
62
  /**
48
63
  * Listen for changes to the birthdate. Could be a string in the YYYY-MM-DD
49
64
  * format or `null`.
@@ -82,6 +97,13 @@ export const defaultLabels: Labels = Object.freeze({
82
97
  day: "Day",
83
98
  });
84
99
 
100
+ // Default minWidth value when we include the full DOB.
101
+ const FIELD_MIN_WIDTH_FULL = 110;
102
+
103
+ // Alternative minWidth value when we hide the day field.
104
+ // See: https://www.figma.com/file/uJZi9ZvuEz5N8GJ3HqKFAa/(2021)-Account-records?node-id=20%3A398
105
+ const FIELD_MIN_WIDTH_MONTH_YEAR = 167;
106
+
85
107
  /**
86
108
  * Birthday Picker. Similar to a datepicker, but specifically for birthdates.
87
109
  * We don't want to show a calendar in this case as it can be quite tedious to
@@ -129,10 +151,10 @@ export default class BirthdayPicker extends React.Component<Props, State> {
129
151
  * Calculates the initial state values based on the default value.
130
152
  */
131
153
  getStateFromDefault(): State {
132
- const {defaultValue} = this.props;
154
+ const {defaultValue, monthYearOnly} = this.props;
133
155
  const initialState: State = {
134
156
  month: null,
135
- day: null,
157
+ day: monthYearOnly ? "1" : null,
136
158
  year: null,
137
159
  error: null,
138
160
  };
@@ -247,59 +269,99 @@ export default class BirthdayPicker extends React.Component<Props, State> {
247
269
  );
248
270
  }
249
271
 
250
- render(): React.Element<any> {
251
- const {month, day, year} = this.state;
272
+ renderMonth(): React.Node {
273
+ const {disabled, monthYearOnly} = this.props;
274
+ const {month} = this.state;
275
+ const minWidth = monthYearOnly
276
+ ? FIELD_MIN_WIDTH_MONTH_YEAR
277
+ : FIELD_MIN_WIDTH_FULL;
278
+
279
+ return (
280
+ <SingleSelect
281
+ disabled={disabled}
282
+ placeholder={this.labels.month}
283
+ onChange={this.handleMonthChange}
284
+ selectedValue={month}
285
+ style={{minWidth}}
286
+ testId="birthday-picker-month"
287
+ >
288
+ {moment.monthsShort().map((month, i) => (
289
+ <OptionItem key={month} label={month} value={String(i)} />
290
+ ))}
291
+ </SingleSelect>
292
+ );
293
+ }
294
+
295
+ maybeRenderDay(): ?React.Node {
296
+ const {disabled, monthYearOnly} = this.props;
297
+ const {day} = this.state;
298
+
299
+ // Hide the day field if the month/year only mode is enabled.
300
+ if (monthYearOnly) {
301
+ return null;
302
+ }
303
+
304
+ return (
305
+ <>
306
+ <Strut size={Spacing.xSmall_8} />
307
+ <SingleSelect
308
+ disabled={disabled}
309
+ placeholder={this.labels.day}
310
+ onChange={this.handleDayChange}
311
+ selectedValue={day}
312
+ style={{minWidth: 100}}
313
+ testId="birthday-picker-day"
314
+ >
315
+ {Array.from(Array(31)).map((_, day) => (
316
+ <OptionItem
317
+ key={String(day + 1)}
318
+ label={String(day + 1)}
319
+ value={String(day + 1)}
320
+ />
321
+ ))}
322
+ </SingleSelect>
323
+ </>
324
+ );
325
+ }
326
+
327
+ renderYear(): React.Node {
328
+ const {disabled, monthYearOnly} = this.props;
329
+ const {year} = this.state;
330
+ const minWidth = monthYearOnly
331
+ ? FIELD_MIN_WIDTH_MONTH_YEAR
332
+ : FIELD_MIN_WIDTH_FULL;
333
+
334
+ return (
335
+ <SingleSelect
336
+ disabled={disabled}
337
+ placeholder={this.labels.year}
338
+ onChange={this.handleYearChange}
339
+ selectedValue={year}
340
+ style={{minWidth}}
341
+ testId="birthday-picker-year"
342
+ >
343
+ {Array.from(Array(120)).map((_, yearOffset) => (
344
+ <OptionItem
345
+ key={String(CUR_YEAR - yearOffset)}
346
+ label={String(CUR_YEAR - yearOffset)}
347
+ value={String(CUR_YEAR - yearOffset)}
348
+ />
349
+ ))}
350
+ </SingleSelect>
351
+ );
352
+ }
252
353
 
354
+ render(): React.Element<any> {
253
355
  return (
254
356
  <>
255
357
  <View testId="birthday-picker" style={{flexDirection: "row"}}>
256
- <SingleSelect
257
- placeholder={this.labels.month}
258
- onChange={this.handleMonthChange}
259
- selectedValue={month}
260
- style={{minWidth: 110}}
261
- testId="birthday-picker-month"
262
- >
263
- {moment.monthsShort().map((month, i) => (
264
- <OptionItem
265
- key={month}
266
- label={month}
267
- value={String(i)}
268
- />
269
- ))}
270
- </SingleSelect>
271
- <Strut size={Spacing.xSmall_8} />
272
- <SingleSelect
273
- placeholder={this.labels.day}
274
- onChange={this.handleDayChange}
275
- selectedValue={day}
276
- style={{minWidth: 100}}
277
- testId="birthday-picker-day"
278
- >
279
- {Array.from(Array(31)).map((_, day) => (
280
- <OptionItem
281
- key={String(day + 1)}
282
- label={String(day + 1)}
283
- value={String(day + 1)}
284
- />
285
- ))}
286
- </SingleSelect>
358
+ {this.renderMonth()}
359
+
360
+ {this.maybeRenderDay()}
361
+
287
362
  <Strut size={Spacing.xSmall_8} />
288
- <SingleSelect
289
- placeholder={this.labels.year}
290
- onChange={this.handleYearChange}
291
- selectedValue={year}
292
- style={{minWidth: 110}}
293
- testId="birthday-picker-year"
294
- >
295
- {Array.from(Array(120)).map((_, yearOffset) => (
296
- <OptionItem
297
- key={String(CUR_YEAR - yearOffset)}
298
- label={String(CUR_YEAR - yearOffset)}
299
- value={String(CUR_YEAR - yearOffset)}
300
- />
301
- ))}
302
- </SingleSelect>
363
+
364
+ {this.renderYear()}
303
365
  </View>
304
366
  {this.maybeRenderError()}
305
367
  </>