@transferwise/components 0.0.0-experimental-3ede9cc → 0.0.0-experimental-e477c03

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.
@@ -2,8 +2,16 @@ import classNames from 'classnames';
2
2
  import { useState } from 'react';
3
3
  import { useIntl } from 'react-intl';
4
4
 
5
- import { Input, SelectInput, SelectInputOptionContent, SelectInputProps } from '..';
6
- import { DateMode, MonthFormat, Size, SizeLarge, SizeMedium, SizeSmall } from '../common';
5
+ import { Body, Input, SelectInput, SelectInputOptionContent, SelectInputProps } from '..';
6
+ import {
7
+ DateMode,
8
+ MonthFormat,
9
+ Size,
10
+ SizeLarge,
11
+ SizeMedium,
12
+ SizeSmall,
13
+ Typography,
14
+ } from '../common';
7
15
  import { MDY, YMD, getMonthNames, isDateValid, isMonthAndYearFormat } from '../common/dateUtils';
8
16
  import { useInputAttributes } from '../inputs/contexts';
9
17
  import messages from './DateInput.messages';
@@ -93,8 +101,10 @@ const DateInput = ({
93
101
  };
94
102
 
95
103
  const [day, setDay] = useState(() => getInitialDate('day'));
104
+ const [displayDay, setDisplayDay] = useState(day?.toString());
96
105
  const [month, setMonth] = useState(() => getInitialDate('month'));
97
106
  const [year, setYear] = useState(() => getInitialDate('year'));
107
+ const [displayYear, setDisplayYear] = useState(year?.toString());
98
108
  const [lastBroadcastedValue, setLastBroadcastedValue] = useState<Date | null | undefined>(
99
109
  getDateObject,
100
110
  );
@@ -129,7 +139,7 @@ const DateInput = ({
129
139
  const getSelectElement = () => {
130
140
  return (
131
141
  <label className="d-flex flex-column">
132
- <span className="sr-only">{monthLabel}</span>
142
+ <Body type={Typography.BODY_DEFAULT}>{monthLabel}</Body>
133
143
  <SelectInput
134
144
  name="month"
135
145
  disabled={disabled}
@@ -147,13 +157,25 @@ const DateInput = ({
147
157
  );
148
158
  };
149
159
 
160
+ const isDayValid = (newDay: number, newMonth: number, newYear: number) => {
161
+ const maxDay = new Date(newYear, newMonth + 1, 0).getDate();
162
+ return newDay <= maxDay;
163
+ };
164
+
150
165
  const handleInternalValue = (newDay = day, newMonth = month, newYear = year) => {
151
- if (newDay == null || newMonth == null || newYear == null) {
166
+ if (newDay == null || newDay === 0 || newMonth == null || newYear == null || newYear === 0) {
167
+ broadcastNewValue(null);
168
+ return;
169
+ }
170
+ if (!isDayValid(newDay, newMonth, newYear)) {
152
171
  broadcastNewValue(null);
153
172
  return;
154
173
  }
155
174
 
156
175
  const dateValue = new Date(newYear, newMonth, newDay);
176
+ if (newYear < 100) {
177
+ dateValue.setFullYear(newYear);
178
+ }
157
179
 
158
180
  if (!isDateValid(dateValue)) {
159
181
  broadcastNewValue(null);
@@ -170,9 +192,12 @@ const DateInput = ({
170
192
  };
171
193
 
172
194
  const handleDayChange = (event: React.ChangeEvent<HTMLInputElement>) => {
173
- const { checkedDay } = checkDate(Number.parseInt(event.target.value, 10), month, year);
174
- setDay(checkedDay);
175
- handleInternalValue(checkedDay, month, year);
195
+ const newDayString = event.target.value.replace(/\D/g, '');
196
+ const newDayNumber = Number.parseInt(newDayString, 10);
197
+
198
+ setDay(newDayNumber);
199
+ setDisplayDay(newDayString);
200
+ handleInternalValue(newDayNumber, month, year);
176
201
  };
177
202
 
178
203
  const handleMonthChange = (selectedMonth: number | null) => {
@@ -181,30 +206,21 @@ const DateInput = ({
181
206
  handleInternalValue(day, null, year);
182
207
  return;
183
208
  }
184
- const { checkedDay } = checkDate(day, selectedMonth, year);
185
209
  setMonth(selectedMonth);
186
- if (day && checkedDay !== day) {
187
- setDay(checkedDay);
188
- }
189
- handleInternalValue(checkedDay, selectedMonth, year);
210
+ handleInternalValue(day, selectedMonth, year);
190
211
  };
191
212
 
192
213
  const handleYearChange = (event: React.ChangeEvent<HTMLInputElement>) => {
193
- const newValue = event.target.value;
194
- const slicedYear = newValue.length > 4 ? newValue.slice(0, 4) : newValue;
195
-
196
- if (slicedYear.toString().length === 4) {
197
- // Correct day based on year and month.
198
- const { checkedDay } = checkDate(day, month, Number.parseInt(newValue, 10));
199
-
200
- if (day && checkedDay !== day) {
201
- setDay(checkedDay);
202
- }
214
+ const newYearString = event.target.value.replace(/\D/g, '');
215
+ const newYearNumber = Number.parseInt(newYearString, 10);
203
216
 
204
- setYear(Number.parseInt(slicedYear, 10));
205
- handleInternalValue(checkedDay, month, Number.parseInt(slicedYear, 10));
217
+ if (newYearString.length >= 4 && newYearString.length <= 6) {
218
+ setYear(newYearNumber);
219
+ setDisplayYear(newYearString);
220
+ handleInternalValue(day, month, newYearNumber);
206
221
  } else {
207
- setYear(Number.parseInt(slicedYear, 10));
222
+ setYear(null);
223
+ setDisplayYear(newYearString);
208
224
  handleInternalValue(day, month, null);
209
225
  }
210
226
  };
@@ -216,29 +232,6 @@ const DateInput = ({
216
232
  }
217
233
  };
218
234
 
219
- const checkDate = (
220
- newDay: number | null = null,
221
- newMonth: number | null = 0,
222
- newYear: number | null = null,
223
- ) => {
224
- let checkedDay = newDay;
225
- const maxDay = new Date(newYear || 2000, newMonth != null ? newMonth + 1 : 1, 0).getDate();
226
-
227
- if (!newDay) {
228
- checkedDay = null;
229
- }
230
-
231
- if (newDay && newDay < 0) {
232
- checkedDay = 1;
233
- }
234
-
235
- if ((newDay && newMonth) || (newDay && newDay > 31)) {
236
- checkedDay = newDay > maxDay ? maxDay : newDay;
237
- }
238
-
239
- return { checkedDay, checkedMonth: newMonth, checkedYear: newYear };
240
- };
241
-
242
235
  const monthYearOnly = mode === DateMode.MONTH_YEAR;
243
236
 
244
237
  const monthWidth = classNames({
@@ -254,7 +247,7 @@ const DateInput = ({
254
247
  return (
255
248
  <div className="col-sm-3">
256
249
  <label>
257
- <span className="sr-only">{dayLabel}</span>
250
+ <Body type={Typography.BODY_DEFAULT}>{dayLabel}</Body>
258
251
  <div className={`input-group input-group-${size}`}>
259
252
  <Input
260
253
  type="text"
@@ -262,10 +255,12 @@ const DateInput = ({
262
255
  pattern="[0-9]*"
263
256
  name="day"
264
257
  autoComplete={dayAutoComplete}
265
- value={day || ''}
258
+ value={displayDay || ''}
266
259
  placeholder={placeholders?.day}
267
260
  disabled={disabled}
268
261
  min={1}
262
+ max={31}
263
+ maxLength={2}
269
264
  onChange={(event) => handleDayChange(event)}
270
265
  />
271
266
  </div>
@@ -278,7 +273,7 @@ const DateInput = ({
278
273
  return (
279
274
  <div className="col-sm-4">
280
275
  <label>
281
- <span className="sr-only">{yearLabel}</span>
276
+ <Body type={Typography.BODY_DEFAULT}>{yearLabel}</Body>
282
277
  <div className={`input-group input-group-${size}`}>
283
278
  <Input
284
279
  type="text"
@@ -287,9 +282,11 @@ const DateInput = ({
287
282
  name="year"
288
283
  autoComplete={yearAutoComplete}
289
284
  placeholder={placeholders?.year}
290
- value={year || ''}
285
+ value={displayYear || ''}
291
286
  disabled={disabled}
292
- min={1}
287
+ min={0}
288
+ max={9999}
289
+ maxLength={6}
293
290
  onChange={(event) => handleYearChange(event)}
294
291
  />
295
292
  </div>
@@ -23,13 +23,13 @@ export const WithStatusMessages = () => {
23
23
  const [value, setValue] = useState<string | undefined>('This is some text');
24
24
  return (
25
25
  <div>
26
- <Field label="Phone number" type={Sentiment.POSITIVE} message="Positive message">
26
+ <Field label="Phone number" sentiment={Sentiment.POSITIVE} message="Positive message">
27
27
  <Input value={value} onChange={({ target }) => setValue(target.value)} />
28
28
  </Field>
29
- <Field label="Phone number" type={Sentiment.WARNING} message="Warning message">
29
+ <Field label="Phone number" sentiment={Sentiment.WARNING} message="Warning message">
30
30
  <Input value={value} onChange={({ target }) => setValue(target.value)} />
31
31
  </Field>
32
- <Field label="Phone number" type={Sentiment.NEGATIVE} message="This is a required field">
32
+ <Field label="Phone number" sentiment={Sentiment.NEGATIVE} message="This is a required field">
33
33
  <Input value={value} onChange={({ target }) => setValue(target.value)} />
34
34
  </Field>
35
35
  <Field label="Phone number" message="This is a helpful message">
@@ -10,12 +10,12 @@ export default {
10
10
  };
11
11
 
12
12
  export const WithHelpAndErrorOnBlur = () => {
13
- const [value, setValue] = useState<string | undefined>('This is some text');
13
+ const [value, setValue] = useState('This is some text');
14
14
  const [error, setError] = useState<string | undefined>(undefined);
15
15
  return (
16
16
  <Field
17
17
  label="Phone number"
18
- type={error ? Sentiment.NEGATIVE : Sentiment.NEUTRAL}
18
+ sentiment={error ? Sentiment.NEGATIVE : Sentiment.NEUTRAL}
19
19
  message={error || 'Please include country code'}
20
20
  >
21
21
  <Input
@@ -20,7 +20,7 @@ export type FieldProps = {
20
20
  message?: React.ReactNode;
21
21
  /** @deprecated use `message` and `type={Sentiment.NEGATIVE}` prop instead */
22
22
  error?: React.ReactNode;
23
- type?: `${Sentiment.NEGATIVE | Sentiment.NEUTRAL | Sentiment.POSITIVE | Sentiment.WARNING}`;
23
+ sentiment?: `${Sentiment.NEGATIVE | Sentiment.NEUTRAL | Sentiment.POSITIVE | Sentiment.WARNING}`;
24
24
  className?: string;
25
25
  children?: React.ReactNode;
26
26
  };
@@ -29,14 +29,14 @@ export const Field = ({
29
29
  id,
30
30
  label,
31
31
  message: propMessage,
32
- type: propType,
32
+ sentiment: propType,
33
33
  className,
34
34
  children,
35
35
  ...props
36
36
  }: FieldProps) => {
37
- const type = props.error ? Sentiment.NEGATIVE : propType;
37
+ const sentiment = props.error ? Sentiment.NEGATIVE : propType;
38
38
  const message = props.error || props.hint || propMessage;
39
- const hasError = type === Sentiment.NEGATIVE;
39
+ const hasError = sentiment === Sentiment.NEGATIVE;
40
40
 
41
41
  const labelId = useId();
42
42
 
@@ -54,10 +54,10 @@ export const Field = ({
54
54
  className={classNames(
55
55
  'form-group d-block',
56
56
  {
57
- 'has-success': type === Sentiment.POSITIVE,
58
- 'has-warning': type === Sentiment.WARNING,
57
+ 'has-success': sentiment === Sentiment.POSITIVE,
58
+ 'has-warning': sentiment === Sentiment.WARNING,
59
59
  'has-error': hasError,
60
- 'has-info': type === Sentiment.NEUTRAL,
60
+ 'has-info': sentiment === Sentiment.NEUTRAL,
61
61
  },
62
62
  className,
63
63
  )}
@@ -67,7 +67,7 @@ export const Field = ({
67
67
  {children}
68
68
  </Label>
69
69
  {message && (
70
- <InlineAlert type={type} id={descriptionId}>
70
+ <InlineAlert type={sentiment} id={descriptionId}>
71
71
  {message}
72
72
  </InlineAlert>
73
73
  )}
@@ -1,17 +0,0 @@
1
- import { Field } from '../field/Field';
2
- import { mockMatchMedia, mockResizeObserver, render, screen } from '../test-utils';
3
- import DateInput from './DateInput';
4
-
5
- mockMatchMedia();
6
- mockResizeObserver();
7
-
8
- describe('DateInput', () => {
9
- it('supports `Field` for labeling', () => {
10
- render(
11
- <Field label="Date of birth">
12
- <DateInput onChange={() => {}} />
13
- </Field>,
14
- );
15
- expect(screen.getAllByRole('group')[0]).toHaveAccessibleName(/^Date of birth/);
16
- });
17
- });