@hyphen/hyphen-components 7.9.0 → 8.0.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 (41) hide show
  1. package/dist/css/fonts.css +1 -1
  2. package/dist/css/index.css +292 -150
  3. package/dist/css/reset.css +1 -1
  4. package/dist/css/utilities.css +480 -38
  5. package/dist/css/variables.css +170 -25
  6. package/dist/hyphen-components.cjs.development.js +244 -311
  7. package/dist/hyphen-components.cjs.development.js.map +1 -1
  8. package/dist/hyphen-components.cjs.production.min.js +2 -2
  9. package/dist/hyphen-components.cjs.production.min.js.map +1 -1
  10. package/dist/hyphen-components.esm.js +198 -265
  11. package/dist/hyphen-components.esm.js.map +1 -1
  12. package/dist/index.d.ts +25 -60
  13. package/package.json +3 -3
  14. package/src/components/Alert/Alert.mdx +1 -1
  15. package/src/components/Alert/Alert.module.scss +29 -34
  16. package/src/components/Alert/Alert.stories.tsx +11 -3
  17. package/src/components/Alert/Alert.test.tsx +17 -11
  18. package/src/components/Alert/Alert.tsx +9 -16
  19. package/src/components/Badge/Badge.mdx +46 -4
  20. package/src/components/Badge/Badge.module.scss +235 -185
  21. package/src/components/Badge/Badge.stories.tsx +126 -36
  22. package/src/components/Badge/Badge.test.tsx +180 -21
  23. package/src/components/Badge/Badge.tsx +56 -21
  24. package/src/components/Drawer/Drawer.mdx +14 -16
  25. package/src/components/Formik/Formik.stories.tsx +3 -5
  26. package/src/components/Formik/FormikTimePicker/FormikTimePicker.test.tsx +34 -67
  27. package/src/components/Formik/FormikTimePicker/FormikTimePicker.tsx +0 -2
  28. package/src/components/Table/Table.stories.tsx +4 -4
  29. package/src/components/TimePicker/TimePicker.mdx +3 -11
  30. package/src/components/TimePicker/TimePicker.stories.tsx +61 -60
  31. package/src/components/TimePicker/TimePicker.test.tsx +76 -7
  32. package/src/components/TimePicker/TimePicker.tsx +37 -7
  33. package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
  34. package/src/docs/Colors.mdx +13 -0
  35. package/src/index.ts +0 -2
  36. package/src/components/Formik/FormikTimePickerNative/FormikTimePickerNative.test.tsx +0 -175
  37. package/src/components/Formik/FormikTimePickerNative/FormikTimePickerNative.tsx +0 -38
  38. package/src/components/TimePickerNative/TimePickerNative.mdx +0 -67
  39. package/src/components/TimePickerNative/TimePickerNative.stories.tsx +0 -150
  40. package/src/components/TimePickerNative/TimePickerNative.test.tsx +0 -117
  41. package/src/components/TimePickerNative/TimePickerNative.tsx +0 -93
@@ -1,48 +1,82 @@
1
1
  import React from 'react';
2
2
  import { render, screen } from '@testing-library/react';
3
- import { Badge, BadgeSize, BadgeVariant } from './Badge';
3
+ import {
4
+ Badge,
5
+ BadgeHue,
6
+ BadgeRadius,
7
+ BadgeSemanticColor,
8
+ BadgeSize,
9
+ BadgeVariant,
10
+ } from './Badge';
11
+ import { Icon } from '../Icon/Icon';
12
+ import { Spinner } from '../Spinner/Spinner';
4
13
 
5
14
  export const BADGE_VARIANTS: BadgeVariant[] = [
6
- 'default',
7
- 'secondary',
8
- 'danger',
15
+ 'solid',
16
+ 'soft',
17
+ 'surface',
9
18
  'outline',
10
- 'light-grey',
11
- 'dark-grey',
12
- 'inverse',
19
+ ];
20
+
21
+ export const BADGE_HUES: BadgeHue[] = [
22
+ 'grey',
23
+ 'blue',
13
24
  'green',
14
25
  'yellow',
15
- 'blue',
16
26
  'red',
17
27
  'purple',
18
28
  'orange',
19
- 'hyphen',
29
+ 'brand',
20
30
  ];
21
31
 
32
+ export const BADGE_SEMANTIC_COLORS: Record<BadgeSemanticColor, BadgeHue> = {
33
+ danger: 'red',
34
+ success: 'green',
35
+ warning: 'yellow',
36
+ info: 'blue',
37
+ };
38
+
39
+ export const BADGE_RADII: BadgeRadius[] = ['none', 'sm', 'md', 'lg', 'full'];
40
+
22
41
  export const BADGE_SIZES: BadgeSize[] = ['sm', 'md', 'lg'];
23
42
 
43
+ /**
44
+ * Badge wraps bare text in a label span, so the element rendering the text is not the
45
+ * badge root. Climb to the root the same way Button's tests do.
46
+ */
47
+ const getBadge = (text: string): HTMLElement =>
48
+ screen.getByText(text).closest('.badge') as HTMLElement;
49
+
24
50
  describe('Badge', () => {
25
51
  test('Badge correctly renders with base props', () => {
26
- render(<Badge message="hello" />);
27
- const badge = screen.getByText('hello');
52
+ render(<Badge>hello</Badge>);
53
+ const badge = getBadge('hello');
28
54
  expect(badge).toBeInTheDocument();
29
- expect(badge.getAttribute('class')).toContain('default');
55
+ expect(badge.getAttribute('class')).toContain('soft');
30
56
  });
31
57
 
32
- test('it applies the default variant when none is provided', () => {
58
+ test('it applies the default variant, color and radius when none are provided', () => {
33
59
  render(<Badge>Badge</Badge>);
34
- const badge = screen.getByText('Badge');
60
+ const badge = getBadge('Badge');
61
+
62
+ expect(badge.getAttribute('class')).toContain('soft');
63
+ expect(badge.getAttribute('class')).toContain('color-grey');
64
+ expect(badge.getAttribute('class')).toContain('radius-full');
65
+ });
66
+
67
+ test('it renders falsy but valid children such as 0', () => {
68
+ render(<Badge>{0}</Badge>);
35
69
 
36
- expect(badge.getAttribute('class')).toContain('default');
37
- expect(badge.getAttribute('class')).not.toContain('light-grey');
70
+ expect(screen.getByText('0')).toBeInTheDocument();
71
+ expect(getBadge('0')).toBeInTheDocument();
38
72
  });
39
73
 
40
74
  describe('Variants', () => {
41
75
  BADGE_VARIANTS.map((variant) =>
42
76
  describe(`${variant}`, () => {
43
77
  test(`it has a ${variant} class applied to it`, () => {
44
- render(<Badge variant={variant} message={`${variant} Badge`} />);
45
- const badge = screen.getByText(`${variant} Badge`);
78
+ render(<Badge variant={variant}>{`${variant} Badge`}</Badge>);
79
+ const badge = getBadge(`${variant} Badge`);
46
80
 
47
81
  expect(badge.getAttribute('class')).toContain(variant);
48
82
  });
@@ -50,12 +84,137 @@ describe('Badge', () => {
50
84
  );
51
85
  });
52
86
 
87
+ describe('Colors', () => {
88
+ BADGE_HUES.map((color) =>
89
+ describe(`${color}`, () => {
90
+ test(`it has a color-${color} class applied to it`, () => {
91
+ render(<Badge color={color}>{`${color} Badge`}</Badge>);
92
+ const badge = getBadge(`${color} Badge`);
93
+
94
+ expect(badge.getAttribute('class')).toContain(`color-${color}`);
95
+ });
96
+ })
97
+ );
98
+
99
+ describe('semantic aliases', () => {
100
+ (
101
+ Object.entries(BADGE_SEMANTIC_COLORS) as [BadgeSemanticColor, BadgeHue][]
102
+ ).map(([semanticColor, hue]) =>
103
+ test(`${semanticColor} resolves to the ${hue} hue`, () => {
104
+ render(<Badge color={semanticColor}>{`${semanticColor} Badge`}</Badge>);
105
+ const badge = getBadge(`${semanticColor} Badge`);
106
+
107
+ expect(badge.getAttribute('class')).toContain(`color-${hue}`);
108
+ expect(badge.getAttribute('class')).not.toContain(
109
+ `color-${semanticColor}`
110
+ );
111
+ })
112
+ );
113
+ });
114
+
115
+ test('it does not forward color to Box as a font color utility class', () => {
116
+ render(<Badge color="danger">badge</Badge>);
117
+ const badge = getBadge('badge');
118
+
119
+ expect(badge.getAttribute('class')).not.toContain('font-color');
120
+ });
121
+ });
122
+
123
+ describe('Radius', () => {
124
+ BADGE_RADII.map((radius) =>
125
+ describe(`${radius}`, () => {
126
+ test(`it has a radius-${radius} class applied to it`, () => {
127
+ render(<Badge radius={radius}>{`${radius} Badge`}</Badge>);
128
+ const badge = getBadge(`${radius} Badge`);
129
+
130
+ expect(badge.getAttribute('class')).toContain(`radius-${radius}`);
131
+ });
132
+ })
133
+ );
134
+
135
+ test('it does not forward radius to Box as a border radius utility class', () => {
136
+ render(<Badge radius="sm">badge</Badge>);
137
+ const badge = getBadge('badge');
138
+
139
+ expect(badge.getAttribute('class')).not.toContain('br-sm');
140
+ });
141
+ });
142
+
143
+ describe('Nested graphics', () => {
144
+ test('it wraps bare text in a label element', () => {
145
+ render(<Badge>Verified</Badge>);
146
+ const label = screen.getByText('Verified');
147
+
148
+ expect(label.tagName).toBe('SPAN');
149
+ expect(label.getAttribute('class')).toContain('label');
150
+ expect(label.closest('.badge')).toBeInTheDocument();
151
+ });
152
+
153
+ test('it does not wrap element children', () => {
154
+ render(
155
+ <Badge>
156
+ <Icon name="star" />
157
+ </Badge>
158
+ );
159
+ const icon = screen.getByTestId('icon-testid--star');
160
+
161
+ expect(icon.closest('.badge')).toBeInTheDocument();
162
+ expect(icon.parentElement?.getAttribute('class')).not.toContain('label');
163
+ });
164
+
165
+ test('it renders a leading icon before the label', () => {
166
+ render(
167
+ <Badge>
168
+ <Icon name="c-check" />
169
+ Verified
170
+ </Badge>
171
+ );
172
+ const badge = getBadge('Verified');
173
+
174
+ expect(screen.getByTestId('icon-testid--c-check')).toBeInTheDocument();
175
+ expect(badge.firstElementChild).toBe(
176
+ screen.getByTestId('icon-testid--c-check')
177
+ );
178
+ expect(badge.lastElementChild).toBe(screen.getByText('Verified'));
179
+ });
180
+
181
+ test('it renders a trailing icon after the label', () => {
182
+ render(
183
+ <Badge>
184
+ Favorite
185
+ <Icon name="star" />
186
+ </Badge>
187
+ );
188
+ const badge = getBadge('Favorite');
189
+
190
+ expect(badge.firstElementChild).toBe(screen.getByText('Favorite'));
191
+ expect(badge.lastElementChild).toBe(
192
+ screen.getByTestId('icon-testid--star')
193
+ );
194
+ });
195
+
196
+ test('it renders a nested Spinner', () => {
197
+ render(
198
+ <Badge color="danger">
199
+ <Spinner />
200
+ Delete
201
+ </Badge>
202
+ );
203
+ const badge = getBadge('Delete');
204
+
205
+ expect(screen.getByTestId('spinner-testid')).toBeInTheDocument();
206
+ expect(badge.firstElementChild).toContainElement(
207
+ screen.getByTestId('spinner-testid')
208
+ );
209
+ });
210
+ });
211
+
53
212
  describe('Sizes', () => {
54
213
  BADGE_SIZES.map((size) =>
55
214
  describe(`${size}`, () => {
56
215
  test(`it has a ${size} class applied to it`, () => {
57
- render(<Badge size={size} message={`${size} Badge`} />);
58
- const badge = screen.getByText(`${size} Badge`);
216
+ render(<Badge size={size}>{`${size} Badge`}</Badge>);
217
+ const badge = getBadge(`${size} Badge`);
59
218
 
60
219
  expect(badge.getAttribute('class')).toContain(`size-${size}`);
61
220
  });
@@ -76,7 +235,7 @@ describe('Badge', () => {
76
235
  </Badge>
77
236
  );
78
237
 
79
- const badge = screen.getByText('badge');
238
+ const badge = getBadge('badge');
80
239
 
81
240
  expect(badge.getAttribute('class')).toContain('size-sm');
82
241
  expect(badge.getAttribute('class')).toContain('size-md-tablet');
@@ -1,4 +1,4 @@
1
- import React, { ReactNode, forwardRef } from 'react';
1
+ import React, { Children, ReactNode, forwardRef } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import { ResponsiveProp } from '../../types';
4
4
  import { generateResponsiveClasses } from '../../lib/generateResponsiveClasses';
@@ -7,33 +7,65 @@ import { Box, BoxProps } from '../Box/Box';
7
7
 
8
8
  export type BadgeSize = 'sm' | 'md' | 'lg';
9
9
 
10
- export type BadgeVariant =
11
- | 'default'
12
- | 'secondary'
13
- | 'danger'
14
- | 'outline'
15
- | 'light-grey'
16
- | 'dark-grey'
17
- | 'inverse'
10
+ export type BadgeVariant = 'solid' | 'soft' | 'surface' | 'outline';
11
+
12
+ export type BadgeHue =
13
+ | 'grey'
14
+ | 'blue'
18
15
  | 'green'
19
16
  | 'yellow'
20
- | 'blue'
21
17
  | 'red'
22
18
  | 'purple'
23
19
  | 'orange'
24
- | 'hyphen';
20
+ | 'brand';
21
+
22
+ export type BadgeSemanticColor = 'danger' | 'success' | 'warning' | 'info';
23
+
24
+ export type BadgeColor = BadgeHue | BadgeSemanticColor;
25
+
26
+ export type BadgeRadius = 'none' | 'sm' | 'md' | 'lg' | 'full';
25
27
 
26
- export interface BadgeProps extends BoxProps {
28
+ /**
29
+ * Semantic color names resolve to a hue, so `color="danger"` and `color="red"` render identically.
30
+ * Retheming a semantic color is a change here rather than at every call site.
31
+ */
32
+ const BADGE_COLOR_ALIASES: Record<BadgeSemanticColor, BadgeHue> = {
33
+ danger: 'red',
34
+ success: 'green',
35
+ warning: 'yellow',
36
+ info: 'blue',
37
+ };
38
+
39
+ /**
40
+ * Wraps bare text in an element so the stylesheet can tell a label from a nested graphic.
41
+ * Text nodes are invisible to `:first-child` / `:last-child`, so without this an icon could
42
+ * not be identified as leading or trailing.
43
+ */
44
+ const renderBadgeChildren = (children: ReactNode) =>
45
+ Children.map(children, (child) =>
46
+ typeof child === 'string' || typeof child === 'number' ? (
47
+ <span className={styles.label}>{child}</span>
48
+ ) : (
49
+ child
50
+ )
51
+ );
52
+
53
+ export interface BadgeProps extends Omit<BoxProps, 'color' | 'radius'> {
27
54
  /**
28
- * @deprecated Use children instead. The text message or ReactNode to be rendered in the badge.
55
+ * The color of the badge. Accepts a hue, or one of the semantic names
56
+ * (`danger`, `success`, `warning`, `info`) which map onto `red`, `green`, `yellow` and `blue`.
29
57
  */
30
- message?: string | ReactNode;
58
+ color?: BadgeColor;
59
+ /**
60
+ * The roundness of the badge's corners.
61
+ */
62
+ radius?: BadgeRadius;
31
63
  /**
32
64
  * The size of the badge.
33
65
  */
34
66
  size?: BadgeSize | ResponsiveProp<BadgeSize>;
35
67
  /**
36
- * The type/color of the badge to show.
68
+ * The visual style of the badge. Use `color` to set its color.
37
69
  */
38
70
  variant?: BadgeVariant;
39
71
  }
@@ -42,8 +74,9 @@ export const Badge = forwardRef<HTMLDivElement, BadgeProps>(
42
74
  (
43
75
  {
44
76
  className = '',
45
- message = '',
46
- variant = 'default',
77
+ color = 'grey',
78
+ radius = 'full',
79
+ variant = 'soft',
47
80
  size = 'md',
48
81
  children,
49
82
  ...restProps
@@ -54,13 +87,15 @@ export const Badge = forwardRef<HTMLDivElement, BadgeProps>(
54
87
  (c) => styles[c]
55
88
  );
56
89
 
90
+ const hue = BADGE_COLOR_ALIASES[color as BadgeSemanticColor] ?? color;
91
+
57
92
  const badgeClasses: string = classNames(
58
93
  styles.badge,
59
94
  className,
60
95
  responsiveClasses,
61
- {
62
- [styles[variant]]: variant,
63
- }
96
+ styles[variant],
97
+ styles[`color-${hue}`],
98
+ styles[`radius-${radius}`]
64
99
  );
65
100
 
66
101
  return (
@@ -72,7 +107,7 @@ export const Badge = forwardRef<HTMLDivElement, BadgeProps>(
72
107
  direction="row"
73
108
  {...restProps}
74
109
  >
75
- {children || message}
110
+ {renderBadgeChildren(children)}
76
111
  </Box>
77
112
  );
78
113
  }
@@ -67,12 +67,11 @@ When `placement` is set to `top` or `bottom`, the `width` prop is ignored and th
67
67
 
68
68
  In cases where content in the drawer is supplemental to content on the main area of the page, use `hideOverlay` to allow for interaction between the areas (e.g. copy-and-paste text from main area into a form in the drawer).
69
69
 
70
- <Alert
71
- variant="warning"
72
- title="Focus Management"
73
- hasIcon
74
- message="If you decide to use `hideOverlay`, then you must also manage focus. Focus and page scrolling will not be locked if `hideOverlay` is true. Also, the 'Esc' key button will no longer automatically close the Drawer."
75
- />
70
+ <Alert variant="warning" title="Focus Management" hasIcon>
71
+ If you decide to use `hideOverlay`, then you must also manage focus. Focus and
72
+ page scrolling will not be locked if `hideOverlay` is true. Also, the 'Esc' key
73
+ button will no longer automatically close the Drawer.
74
+ </Alert>
76
75
 
77
76
  <Canvas withSource="open" of={Stories.HiddenOverlay} />
78
77
 
@@ -81,11 +80,10 @@ In cases where content in the drawer is supplemental to content on the main area
81
80
 
82
81
  By default the first focusable element will receive focus when the drawer opens, but you can provide a ref to focus instead.
83
82
 
84
- <Alert
85
- hasIcon
86
- message="Without the initialFocusRef prop, the drawer will automatically focus on the first focusable element in it's children."
87
- variant="info"
88
- />
83
+ <Alert hasIcon variant="info">
84
+ Without the initialFocusRef prop, the drawer will automatically focus on the
85
+ first focusable element in it's children.
86
+ </Alert>
89
87
 
90
88
  <Canvas withSource="open" of={Stories.InitialFocusRef} />
91
89
 
@@ -93,10 +91,10 @@ By default the first focusable element will receive focus when the drawer opens,
93
91
 
94
92
  Render the Drawer within a containing div using `containerRef`.
95
93
 
96
- <Alert
97
- hasIcon
98
- message="When choosing to use a Drawer within a containing div, use dangerouslyBypassScrollLock to allow the content outside of the containing div to remain interactive."
99
- variant="info"
100
- />
94
+ <Alert hasIcon variant="info">
95
+ When choosing to use a Drawer within a containing div, use
96
+ dangerouslyBypassScrollLock to allow the content outside of the containing div
97
+ to remain interactive.
98
+ </Alert>
101
99
 
102
100
  <Canvas withSource="open" of={Stories.ContainedDrawer} />
@@ -10,7 +10,7 @@ import { FormikTextareaInput } from './FormikTextareaInput/FormikTextareaInput';
10
10
  import { FormikSwitch } from './FormikSwitch/FormikSwitch';
11
11
  import { Button } from '../Button/Button';
12
12
  import { Box } from '../Box/Box';
13
- import { FormikTimePickerNative } from './FormikTimePickerNative/FormikTimePickerNative';
13
+ import { FormikTimePicker } from './FormikTimePicker/FormikTimePicker';
14
14
  import { FormikSelectInputInset } from './FormikSelectInputInset/FormikSelectInputInset';
15
15
  import { FormikTextareaInputInset } from './FormikTextareaInputInset/FormikTextareaInputInset';
16
16
  import { FormikTextInputInset } from './FormikTextInputInset/FormikTextInputInset';
@@ -49,7 +49,6 @@ type FormValues = {
49
49
  colors2: string;
50
50
  sizes: string | null;
51
51
  timePicker: string | null;
52
- timePickerNative: string | null;
53
52
  dateInput: Date | undefined;
54
53
  message: string;
55
54
  country: string;
@@ -207,7 +206,6 @@ export const FormikForm = () =>
207
206
  colors2: '',
208
207
  sizes: null,
209
208
  timePicker: null,
210
- timePickerNative: null,
211
209
  dateInput: new Date(2017, 4, 21),
212
210
  message: '',
213
211
  country: '',
@@ -306,10 +304,10 @@ export const FormikForm = () =>
306
304
  isRequired
307
305
  />
308
306
  <Field
309
- label="Select Time With Native Select"
307
+ label="Select Time"
310
308
  name="timePicker"
311
309
  id="timePicker"
312
- component={FormikTimePickerNative}
310
+ component={FormikTimePicker}
313
311
  isRequired
314
312
  />
315
313
  <Field
@@ -1,12 +1,10 @@
1
1
  import React from 'react';
2
2
  import { render, fireEvent, screen, waitFor } from '@testing-library/react';
3
- import selectEvent from 'react-select-event';
4
3
  import { Formik, Form, Field, FormikValues, getIn, setIn } from 'formik';
5
4
  import { FormikTimePicker } from './FormikTimePicker';
6
5
 
7
6
  const testLabelName = 'test select';
8
7
 
9
- type Option = { value: string; label: string };
10
8
  const handleValidation = (testValueKey: string) => (values: FormikValues) =>
11
9
  getIn(values, testValueKey)?.length > 1
12
10
  ? {}
@@ -17,10 +15,9 @@ const renderForm = (
17
15
  props: {
18
16
  placeholder?: string;
19
17
  hideLabel?: boolean;
20
- isMulti?: boolean;
21
18
  isRequired?: unknown;
22
19
  isDisabled?: boolean;
23
- onChange?: jest.Mock<void, [any]>; // eslint-disable-line
20
+ onChange?: jest.Mock<void, [React.ChangeEvent<HTMLSelectElement>]>; // eslint-disable-line
24
21
  interval?: number;
25
22
  },
26
23
  testValueKey = testLabelName
@@ -33,7 +30,7 @@ const renderForm = (
33
30
  onSubmit={() => {}} // eslint-disable-line
34
31
  >
35
32
  {() => (
36
- <Form>
33
+ <Form noValidate>
37
34
  <Field
38
35
  label={testValueKey}
39
36
  name={testValueKey}
@@ -93,62 +90,39 @@ describe('FormikTimePicker', () => {
93
90
 
94
91
  describe('Single select, pre-selected', () => {
95
92
  test('it renders with value pre-selected', () => {
96
- render(
97
- renderForm(
98
- { label: '12:00 AM', value: '2020-10-23T04:30:00.120Z' },
99
- {}
100
- )
101
- );
93
+ const initialValue = new Date(2020, 0, 1, 0, 0, 0, 120).toISOString();
94
+ const expectedValue = new Date();
95
+ expectedValue.setHours(0, 0, 0, 0);
102
96
 
103
- expect(screen.getByText('12:00 AM')).toBeInTheDocument();
104
- });
105
- });
106
-
107
- describe('Multi select, no selection', () => {
108
- test('it renders input with a label, and with a default placeholder', () => {
109
- render(renderForm(undefined, { isMulti: true }));
97
+ render(renderForm(initialValue, {}));
110
98
 
111
- expect(screen.getByLabelText(testLabelName)).toBeInTheDocument();
112
- expect(screen.getByText('HH:MM')).toBeInTheDocument();
99
+ expect(screen.getByLabelText(testLabelName)).toHaveValue(
100
+ expectedValue.toISOString()
101
+ );
113
102
  });
114
103
  });
115
104
 
116
- describe('Multi select, with multiple items selected', () => {
117
- test('it renders input with a label, and with two items selected', () => {
118
- render(
119
- renderForm(
120
- [
121
- { label: '12:00 AM', value: '2020-10-23T04:30:00.120Z' },
122
- { label: '12:15 AM', value: '2020-10-23T04:45:00.120Z' },
123
- ],
124
- { isMulti: true }
125
- )
126
- );
127
-
128
- expect(screen.getByLabelText(testLabelName)).toBeInTheDocument();
129
- expect(screen.queryByText('HH:MM')).toBeNull();
130
- expect(screen.getByText('12:00 AM')).toBeInTheDocument();
131
- expect(screen.getByText('12:15 AM')).toBeInTheDocument();
105
+ describe('Is Required', () => {
106
+ test('it sets aria-required on the input', () => {
107
+ render(renderForm(undefined, { isRequired: true }));
108
+ const inputElement = screen.getByLabelText(testLabelName);
109
+ expect(inputElement).toHaveAttribute('aria-required', 'true');
132
110
  });
133
111
  });
134
112
 
135
113
  describe('Is Disabled', () => {
136
114
  test('it disables the input', () => {
137
- const { container } = render(
138
- renderForm(undefined, { isDisabled: true })
139
- );
140
-
141
- const disabledInput = container.querySelector(
142
- '.react-select__control[aria-disabled="true"]'
143
- );
115
+ render(renderForm(undefined, { isDisabled: true }));
144
116
 
145
- expect(disabledInput).toBeInTheDocument();
117
+ expect(screen.getByLabelText(testLabelName)).toBeDisabled();
146
118
  });
147
119
  });
148
120
 
149
121
  describe('Is Invalid, with a helpful message', () => {
150
122
  test('it renders the helpful message', async () => {
151
- const { getByText } = render(renderForm([], { isRequired: true }));
123
+ const { getByText } = render(
124
+ renderForm(undefined, { isRequired: true })
125
+ );
152
126
  const submitButton = getByText('submit');
153
127
 
154
128
  fireEvent.click(submitButton);
@@ -178,34 +152,17 @@ describe('FormikTimePicker', () => {
178
152
  describe('Callback Handling', () => {
179
153
  describe('onChange', () => {
180
154
  test("Custom onChange event fires callback function, overwriting Formik's onChange", async () => {
181
- let value: Option | undefined;
155
+ let value: string | undefined;
182
156
  const mockedHandleChange = jest.fn((event) => {
183
- value = event.target.value;
157
+ event.persist();
184
158
  });
185
159
 
186
- const { getByLabelText, container, getByText } = render(
160
+ const { getByLabelText } = render(
187
161
  renderForm(value, { onChange: mockedHandleChange })
188
162
  );
189
163
  const selectInput = getByLabelText(testLabelName);
190
- /**
191
- * This class is specific to react-select, combined with our custom classNamePrefix prop.
192
- * While this is an implementation detail there appears to be
193
- * no clearer path to test our own component which depends on react-select
194
- */
195
- const selectInputWrapper = container.querySelector(
196
- '.react-select__control'
197
- );
198
-
199
- fireEvent.focus(selectInput);
200
- if (selectInputWrapper) {
201
- fireEvent.mouseDown(selectInputWrapper);
202
- }
203
- const option = await waitFor(() => getByText('12:00 AM'), {
204
- container,
205
- });
206
- fireEvent.click(option);
164
+ fireEvent.change(selectInput, { target: { value: 'hello' } });
207
165
  expect(mockedHandleChange).toHaveBeenCalledTimes(1);
208
- expect(value?.label).toEqual('12:00 AM');
209
166
  });
210
167
 
211
168
  test('it fires onChange callback on change', async () => {
@@ -215,10 +172,20 @@ describe('FormikTimePicker', () => {
215
172
  renderForm(undefined, { onChange: mockedHandleChange })
216
173
  );
217
174
 
218
- await selectEvent.select(getByLabelText(testLabelName), '12:00 AM');
175
+ await fireEvent.change(getByLabelText(testLabelName));
219
176
 
220
177
  expect(mockedHandleChange).toBeCalledTimes(1);
221
178
  });
179
+
180
+ test('it uses Formik onChange when no custom callback is provided', () => {
181
+ render(renderForm(undefined, {}));
182
+ const select = screen.getByLabelText(testLabelName);
183
+ const option = screen.getByText('12:15 AM') as HTMLOptionElement;
184
+
185
+ fireEvent.change(select, { target: { value: option.value } });
186
+
187
+ expect(select).toHaveValue(option.value);
188
+ });
222
189
  });
223
190
  });
224
191
  });
@@ -21,7 +21,6 @@ export interface FormikTimePickerProps
21
21
  export const FormikTimePicker: FC<FormikTimePickerProps> = ({
22
22
  field: { name, onBlur, onChange: formikOnChange, value },
23
23
  form: { touched, errors },
24
- options,
25
24
  onChange,
26
25
  ...props
27
26
  }) => (
@@ -32,6 +31,5 @@ export const FormikTimePicker: FC<FormikTimePickerProps> = ({
32
31
  onChange={onChange ?? formikOnChange}
33
32
  value={value}
34
33
  error={getIn(touched, name) && getIn(errors, name)}
35
- options={options}
36
34
  />
37
35
  );
@@ -903,19 +903,19 @@ export const ComponentAsColumnHeader = () =>
903
903
  const columnConfig: ColumnType[] = [
904
904
  { heading: 'ID', dataKey: 'id' },
905
905
  { heading: 'Color', dataKey: 'color' },
906
- { heading: <Badge message="Status" />, dataKey: 'status' },
906
+ { heading: <Badge>Status</Badge>, dataKey: 'status' },
907
907
  ];
908
908
  const tableData = [
909
909
  {
910
910
  id: 1,
911
911
  color: 'red',
912
- status: <Badge variant="red">danger</Badge>,
912
+ status: <Badge color="danger">danger</Badge>,
913
913
  },
914
- { id: 2, color: 'blue', status: <Badge variant="blue">info</Badge> },
914
+ { id: 2, color: 'blue', status: <Badge color="info">info</Badge> },
915
915
  {
916
916
  id: 3,
917
917
  color: 'green',
918
- status: <Badge variant="green">success</Badge>,
918
+ status: <Badge color="success">success</Badge>,
919
919
  },
920
920
  ];
921
921
  return <Table rowKey="id" columns={columnConfig} rows={tableData} />;