@hyphen/hyphen-components 7.10.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.
- package/dist/css/fonts.css +1 -1
- package/dist/css/index.css +292 -150
- package/dist/css/reset.css +1 -1
- package/dist/css/utilities.css +480 -38
- package/dist/css/variables.css +170 -25
- package/dist/hyphen-components.cjs.development.js +244 -311
- package/dist/hyphen-components.cjs.development.js.map +1 -1
- package/dist/hyphen-components.cjs.production.min.js +2 -2
- package/dist/hyphen-components.cjs.production.min.js.map +1 -1
- package/dist/hyphen-components.esm.js +198 -265
- package/dist/hyphen-components.esm.js.map +1 -1
- package/dist/index.d.ts +25 -60
- package/package.json +3 -3
- package/src/components/Alert/Alert.mdx +1 -1
- package/src/components/Alert/Alert.module.scss +29 -34
- package/src/components/Alert/Alert.stories.tsx +11 -3
- package/src/components/Alert/Alert.test.tsx +17 -11
- package/src/components/Alert/Alert.tsx +9 -16
- package/src/components/Badge/Badge.mdx +46 -4
- package/src/components/Badge/Badge.module.scss +235 -185
- package/src/components/Badge/Badge.stories.tsx +126 -36
- package/src/components/Badge/Badge.test.tsx +180 -21
- package/src/components/Badge/Badge.tsx +56 -21
- package/src/components/Drawer/Drawer.mdx +14 -16
- package/src/components/Formik/Formik.stories.tsx +3 -5
- package/src/components/Formik/FormikTimePicker/FormikTimePicker.test.tsx +34 -67
- package/src/components/Formik/FormikTimePicker/FormikTimePicker.tsx +0 -2
- package/src/components/Table/Table.stories.tsx +4 -4
- package/src/components/TimePicker/TimePicker.mdx +3 -11
- package/src/components/TimePicker/TimePicker.stories.tsx +61 -60
- package/src/components/TimePicker/TimePicker.test.tsx +76 -7
- package/src/components/TimePicker/TimePicker.tsx +37 -7
- package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
- package/src/docs/Colors.mdx +13 -0
- package/src/index.ts +0 -2
- package/src/components/Formik/FormikTimePickerNative/FormikTimePickerNative.test.tsx +0 -175
- package/src/components/Formik/FormikTimePickerNative/FormikTimePickerNative.tsx +0 -38
- package/src/components/TimePickerNative/TimePickerNative.mdx +0 -67
- package/src/components/TimePickerNative/TimePickerNative.stories.tsx +0 -150
- package/src/components/TimePickerNative/TimePickerNative.test.tsx +0 -117
- 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 {
|
|
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
|
-
'
|
|
7
|
-
'
|
|
8
|
-
'
|
|
15
|
+
'solid',
|
|
16
|
+
'soft',
|
|
17
|
+
'surface',
|
|
9
18
|
'outline',
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
'
|
|
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
|
|
27
|
-
const badge =
|
|
52
|
+
render(<Badge>hello</Badge>);
|
|
53
|
+
const badge = getBadge('hello');
|
|
28
54
|
expect(badge).toBeInTheDocument();
|
|
29
|
-
expect(badge.getAttribute('class')).toContain('
|
|
55
|
+
expect(badge.getAttribute('class')).toContain('soft');
|
|
30
56
|
});
|
|
31
57
|
|
|
32
|
-
test('it applies the default variant when none
|
|
58
|
+
test('it applies the default variant, color and radius when none are provided', () => {
|
|
33
59
|
render(<Badge>Badge</Badge>);
|
|
34
|
-
const 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(
|
|
37
|
-
expect(
|
|
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}
|
|
45
|
-
const 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}
|
|
58
|
-
const 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 =
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
| '
|
|
14
|
-
| '
|
|
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
|
-
| '
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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 {
|
|
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
|
|
307
|
+
label="Select Time"
|
|
310
308
|
name="timePicker"
|
|
311
309
|
id="timePicker"
|
|
312
|
-
component={
|
|
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, [
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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)).
|
|
112
|
-
|
|
99
|
+
expect(screen.getByLabelText(testLabelName)).toHaveValue(
|
|
100
|
+
expectedValue.toISOString()
|
|
101
|
+
);
|
|
113
102
|
});
|
|
114
103
|
});
|
|
115
104
|
|
|
116
|
-
describe('
|
|
117
|
-
test('it
|
|
118
|
-
render(
|
|
119
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
155
|
+
let value: string | undefined;
|
|
182
156
|
const mockedHandleChange = jest.fn((event) => {
|
|
183
|
-
|
|
157
|
+
event.persist();
|
|
184
158
|
});
|
|
185
159
|
|
|
186
|
-
const { getByLabelText
|
|
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
|
|
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
|
|
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
|
|
912
|
+
status: <Badge color="danger">danger</Badge>,
|
|
913
913
|
},
|
|
914
|
-
{ id: 2, color: 'blue', status: <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
|
|
918
|
+
status: <Badge color="success">success</Badge>,
|
|
919
919
|
},
|
|
920
920
|
];
|
|
921
921
|
return <Table rowKey="id" columns={columnConfig} rows={tableData} />;
|