playbook_ui 15.5.0.pre.alpha.PLAY2581aggressivevalidation12698 → 15.5.0.pre.alpha.draggableask12772
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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/pb_draggable/context/index.tsx +28 -0
- data/app/pb_kits/playbook/pb_home_address_street/_home_address_street.tsx +34 -22
- data/app/pb_kits/playbook/pb_home_address_street/city_emphasis.html.erb +16 -12
- data/app/pb_kits/playbook/pb_home_address_street/docs/_home_address_street_default.html.erb +1 -1
- data/app/pb_kits/playbook/pb_home_address_street/none_emphasis.html.erb +16 -12
- data/app/pb_kits/playbook/pb_home_address_street/street_emphasis.html.erb +16 -12
- data/app/pb_kits/playbook/pb_multiple_users/_multiple_users.scss +10 -0
- data/app/pb_kits/playbook/pb_multiple_users/_multiple_users.tsx +66 -15
- data/app/pb_kits/playbook/pb_multiple_users/docs/_multiple_users_with_tooltip.jsx +42 -0
- data/app/pb_kits/playbook/pb_multiple_users/docs/_multiple_users_with_tooltip.md +1 -0
- data/app/pb_kits/playbook/pb_multiple_users/docs/example.yml +1 -0
- data/app/pb_kits/playbook/pb_multiple_users/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_multiple_users/multiple_users.test.js +25 -0
- data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +10 -44
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +15 -0
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +3 -0
- data/app/pb_kits/playbook/pb_typeahead/components/MultiValue.tsx +6 -1
- data/app/pb_kits/playbook/pb_typeahead/components/ValueContainer.tsx +34 -7
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_input_display.html.erb +30 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_input_display.jsx +37 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_input_display.md +3 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/index.js +2 -1
- data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +6 -1
- data/dist/chunks/{_typeahead-C8dnmJBs.js → _typeahead-B4GUo5ik.js} +2 -2
- data/dist/chunks/vendor.js +2 -2
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 92a93fad43c2ffd951dfa2745d0ec9d274d88b255b8308cc63dc46e2013b32a9
|
|
4
|
+
data.tar.gz: 90097889aad0c7a8fed5ed59646450a92451efa3ebc1516df42947751bff1470
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb1e65b5882d2691a9cacac7be696c4c213b6ce7e4b69d5475daab6cedead5182d36eb41cf6056d4b323d9420e6fcb2da5b7e2a897edcb1fb928f240c797ad8a
|
|
7
|
+
data.tar.gz: 958b3259b370b7b5bd53c06a040757f9b98b0ee80dc62c56e1416016f9752f9c1136981da6da21a16d15622bfe32a81e54cc44d31a46ffba4624a8a875e4e703
|
|
@@ -8,6 +8,9 @@ const initialState: InitialStateType = {
|
|
|
8
8
|
activeContainer: ""
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// Track if a successful drop occurred to distinguish from dragEnd without drop
|
|
12
|
+
let dropOccurred = false;
|
|
13
|
+
|
|
11
14
|
const reducer = (state: InitialStateType, action: ActionType) => {
|
|
12
15
|
switch (action.type) {
|
|
13
16
|
case 'SET_ITEMS':
|
|
@@ -92,6 +95,19 @@ const reducer = (state: InitialStateType, action: ActionType) => {
|
|
|
92
95
|
return { ...state, items: newItems };
|
|
93
96
|
}
|
|
94
97
|
|
|
98
|
+
// Reset item back to its original container (e.g., when drag ends without valid drop)
|
|
99
|
+
case "RESET_DRAG_CONTAINER": {
|
|
100
|
+
const { itemId, originalContainer } = action.payload;
|
|
101
|
+
return {
|
|
102
|
+
...state,
|
|
103
|
+
items: state.items.map(item =>
|
|
104
|
+
item.id === itemId
|
|
105
|
+
? { ...item, container: originalContainer }
|
|
106
|
+
: item
|
|
107
|
+
)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
95
111
|
default:
|
|
96
112
|
return state;
|
|
97
113
|
}
|
|
@@ -202,9 +218,18 @@ export const DraggableProvider = ({
|
|
|
202
218
|
const draggedItemId = state.dragData.id;
|
|
203
219
|
const originalContainer = state.dragData.initialGroup;
|
|
204
220
|
|
|
221
|
+
// If enableCrossContainerPreview is true and no drop occurred, reset item to original container
|
|
222
|
+
if (enableCrossContainerPreview && !dropOccurred && draggedItemId && originalContainer) {
|
|
223
|
+
dispatch({ type: 'RESET_DRAG_CONTAINER', payload: { itemId: draggedItemId, originalContainer } });
|
|
224
|
+
}
|
|
225
|
+
|
|
205
226
|
dispatch({ type: 'SET_IS_DRAGGING', payload: "" });
|
|
206
227
|
dispatch({ type: 'SET_ACTIVE_CONTAINER', payload: "" });
|
|
207
228
|
dispatch({ type: 'SET_DRAG_DATA', payload: { id: "", initialGroup: "", originId: "" } });
|
|
229
|
+
|
|
230
|
+
// Reset the drop flag
|
|
231
|
+
dropOccurred = false;
|
|
232
|
+
|
|
208
233
|
if (onDragEnd) {
|
|
209
234
|
if (!enableCrossContainerPreview) {
|
|
210
235
|
onDragEnd();
|
|
@@ -237,6 +262,9 @@ export const DraggableProvider = ({
|
|
|
237
262
|
|
|
238
263
|
const draggedItemId = state.dragData.id;
|
|
239
264
|
const originalContainer = state.dragData.initialGroup;
|
|
265
|
+
|
|
266
|
+
// Mark that a successful drop occurred
|
|
267
|
+
dropOccurred = true;
|
|
240
268
|
|
|
241
269
|
dispatch({ type: 'SET_IS_DRAGGING', payload: "" });
|
|
242
270
|
dispatch({ type: 'SET_ACTIVE_CONTAINER', payload: "" });
|
|
@@ -96,20 +96,24 @@ const HomeAddressStreet = (props: HomeAddressStreetProps): React.ReactElement =>
|
|
|
96
96
|
{hasAllEmptyProps && '—'}
|
|
97
97
|
{emphasis == 'street' && !hasAllEmptyProps &&
|
|
98
98
|
<div>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
99
|
+
{(address || houseStyle) && (
|
|
100
|
+
<Title
|
|
101
|
+
className="pb_home_address_street_address"
|
|
102
|
+
dark={dark}
|
|
103
|
+
size={4}
|
|
104
|
+
>
|
|
105
|
+
{joinPresent([formatStreetAdr(address), houseStyle], ' · ')}
|
|
106
|
+
</Title>
|
|
107
|
+
)}
|
|
108
|
+
{addressCont && (
|
|
109
|
+
<Title
|
|
110
|
+
className="pb_home_address_street_address"
|
|
111
|
+
dark={dark}
|
|
112
|
+
size={4}
|
|
113
|
+
>
|
|
114
|
+
{titleize(addressCont)}
|
|
115
|
+
</Title>
|
|
116
|
+
)}
|
|
113
117
|
<Body color="light">
|
|
114
118
|
{`${city ? `${titleize(city)}, ` : ''}${uppercaseState}${zipcode ? ` ${zipcode}` : ''}`}
|
|
115
119
|
</Body>
|
|
@@ -117,10 +121,14 @@ const HomeAddressStreet = (props: HomeAddressStreetProps): React.ReactElement =>
|
|
|
117
121
|
}
|
|
118
122
|
{emphasis == 'city' && !hasAllEmptyProps &&
|
|
119
123
|
<div>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
{(address || houseStyle) && (
|
|
125
|
+
<Body color="light">
|
|
126
|
+
{joinPresent([formatStreetAdr(address), houseStyle], ' · ')}
|
|
127
|
+
</Body>
|
|
128
|
+
)}
|
|
129
|
+
{addressCont && (
|
|
130
|
+
<Body color="light">{titleize(addressCont)}</Body>
|
|
131
|
+
)}
|
|
124
132
|
<div>
|
|
125
133
|
<Title
|
|
126
134
|
className="pb_home_address_street_address"
|
|
@@ -141,10 +149,14 @@ const HomeAddressStreet = (props: HomeAddressStreetProps): React.ReactElement =>
|
|
|
141
149
|
}
|
|
142
150
|
{emphasis == 'none' && !hasAllEmptyProps &&
|
|
143
151
|
<div>
|
|
144
|
-
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
{(address || houseStyle) && (
|
|
153
|
+
<Body dark={dark}>
|
|
154
|
+
{joinPresent([formatStreetAdr(address), houseStyle], ' · ')}
|
|
155
|
+
</Body>
|
|
156
|
+
)}
|
|
157
|
+
{addressCont && (
|
|
158
|
+
<Body dark={dark}>{formatStreetAdr(addressCont)}</Body>
|
|
159
|
+
)}
|
|
148
160
|
<div>
|
|
149
161
|
<Body
|
|
150
162
|
color="light"
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
<% if object.address_house_style.present? %>
|
|
2
|
+
<%= pb_rails "body", props: {
|
|
3
|
+
classname: "pb_home_address_street_address",
|
|
4
|
+
color: "light",
|
|
5
|
+
text: object.address_house_style,
|
|
6
|
+
dark: object.dark
|
|
7
|
+
} %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% if object.address_house_style2.present? %>
|
|
10
|
+
<%= pb_rails "body", props: {
|
|
11
|
+
classname: "pb_home_address_street_address",
|
|
12
|
+
color: "light",
|
|
13
|
+
text: object.address_house_style2,
|
|
14
|
+
dark: object.dark
|
|
15
|
+
} %>
|
|
16
|
+
<% end %>
|
|
13
17
|
<div>
|
|
14
18
|
<%= pb_rails "title", props: {
|
|
15
19
|
tag: "span",
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
<% if object.address_house_style.present? %>
|
|
2
|
+
<%= pb_rails "body", props: {
|
|
3
|
+
classname: "pb_home_address_street_address",
|
|
4
|
+
size: 4,
|
|
5
|
+
text: object.address_house_style,
|
|
6
|
+
dark: object.dark
|
|
7
|
+
} %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% if object.address_house_style2.present? %>
|
|
10
|
+
<%= pb_rails "body", props: {
|
|
11
|
+
classname: "pb_home_address_street_address",
|
|
12
|
+
size: 4,
|
|
13
|
+
text: object.address_house_style2,
|
|
14
|
+
dark: object.dark
|
|
15
|
+
} %>
|
|
16
|
+
<% end %>
|
|
13
17
|
<%= pb_rails "body", props: {
|
|
14
18
|
color: "light",
|
|
15
19
|
text: object.city_state_zip,
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
<% if object.address_house_style.present? %>
|
|
2
|
+
<%= pb_rails "title", props: {
|
|
3
|
+
classname: "pb_home_address_street_address",
|
|
4
|
+
size: 4,
|
|
5
|
+
text: object.address_house_style,
|
|
6
|
+
dark: object.dark
|
|
7
|
+
} %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% if object.address_house_style2.present? %>
|
|
10
|
+
<%= pb_rails "title", props: {
|
|
11
|
+
classname: "pb_home_address_street_address",
|
|
12
|
+
size: 4,
|
|
13
|
+
text: object.address_house_style2,
|
|
14
|
+
dark: object.dark
|
|
15
|
+
} %>
|
|
16
|
+
<% end %>
|
|
13
17
|
<%= pb_rails "body", props: {
|
|
14
18
|
color: "light",
|
|
15
19
|
text: object.city_state_zip,
|
|
@@ -36,6 +36,16 @@ $pb_multiple_users_size_xxs: map-get($avatar-sizes, "xxs");
|
|
|
36
36
|
height: $pb_multiple_users_size_xxs;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
|
|
40
|
+
.user_tooltip {
|
|
41
|
+
margin-left: $pb_multiple_users_overlap !important;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.user_count_tooltip {
|
|
45
|
+
margin-left: $pb_multiple_users_overlap !important;
|
|
46
|
+
position: relative;
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
.pb_multiple_users_item {
|
|
40
50
|
margin-left: $pb_multiple_users_overlap;
|
|
41
51
|
margin-right: 0;
|
|
@@ -5,17 +5,19 @@ import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../uti
|
|
|
5
5
|
import { globalProps } from '../utilities/globalProps'
|
|
6
6
|
|
|
7
7
|
import Avatar from '../pb_avatar/_avatar'
|
|
8
|
+
import Tooltip from '../pb_tooltip/_tooltip'
|
|
8
9
|
|
|
9
10
|
type MultipleUsersProps = {
|
|
10
11
|
aria?: { [key: string]: string },
|
|
11
12
|
className?: string,
|
|
12
13
|
dark?: boolean,
|
|
13
14
|
data?: { [key: string]: string },
|
|
14
|
-
htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
|
|
15
|
+
htmlOptions?: { [key: string]: string | number | boolean | (() => void) },
|
|
15
16
|
id?: string,
|
|
16
17
|
maxDisplayedUsers?: number,
|
|
17
18
|
reverse?: boolean,
|
|
18
19
|
size?: "md" | "lg" | "sm" | "xl" | "xs" | "xxs",
|
|
20
|
+
withTooltip?: boolean,
|
|
19
21
|
users: Array<{ [key: string]: string }>,
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -30,6 +32,7 @@ const MultipleUsers = (props: MultipleUsersProps): React.ReactElement => {
|
|
|
30
32
|
maxDisplayedUsers = 4,
|
|
31
33
|
reverse = false,
|
|
32
34
|
size = 'xs',
|
|
35
|
+
withTooltip = false,
|
|
33
36
|
users,
|
|
34
37
|
} = props
|
|
35
38
|
|
|
@@ -62,22 +65,70 @@ const MultipleUsers = (props: MultipleUsersProps): React.ReactElement => {
|
|
|
62
65
|
className={classes}
|
|
63
66
|
id={id}
|
|
64
67
|
>
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
{withTooltip ?
|
|
69
|
+
<>
|
|
70
|
+
{usersToDisplay.map((avatarData, index) => (
|
|
71
|
+
<Tooltip
|
|
72
|
+
key={"user_tooltip_" + index}
|
|
73
|
+
placement='top'
|
|
74
|
+
text={avatarData.tooltip ? avatarData.tooltip : ''}
|
|
75
|
+
zIndex={10}
|
|
76
|
+
>
|
|
77
|
+
<Avatar
|
|
78
|
+
{...avatarData}
|
|
79
|
+
className={"pb_multiple_users_item" + (withTooltip ? " user_tooltip" : "")}
|
|
80
|
+
dark={dark}
|
|
81
|
+
imageAlt={avatarData.name}
|
|
82
|
+
key={index}
|
|
83
|
+
size={size}
|
|
84
|
+
/>
|
|
85
|
+
</Tooltip>
|
|
86
|
+
))}
|
|
75
87
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
{users.length > maxDisplayedUsers &&
|
|
89
|
+
<Tooltip
|
|
90
|
+
placement='top'
|
|
91
|
+
text={
|
|
92
|
+
<div>
|
|
93
|
+
{
|
|
94
|
+
usersToDisplay.length < users.length ?
|
|
95
|
+
users.slice(displayCount).map((u, i) => (
|
|
96
|
+
<div key={i}>{u.tooltip}</div>
|
|
97
|
+
))
|
|
98
|
+
:
|
|
99
|
+
''
|
|
100
|
+
}
|
|
101
|
+
</div>
|
|
102
|
+
}
|
|
103
|
+
zIndex={10}
|
|
104
|
+
>
|
|
105
|
+
<div className={itemClasses + (withTooltip ? " user_count_tooltip" : "")}>
|
|
106
|
+
{`+${users.length - displayCount}`}
|
|
107
|
+
</div>
|
|
108
|
+
</Tooltip>
|
|
109
|
+
}
|
|
110
|
+
</>
|
|
111
|
+
:
|
|
112
|
+
<>
|
|
113
|
+
{usersToDisplay.map((avatarData, index) => (
|
|
114
|
+
<Avatar
|
|
115
|
+
{...avatarData}
|
|
116
|
+
className="pb_multiple_users_item"
|
|
117
|
+
dark={dark}
|
|
118
|
+
imageAlt={avatarData.name}
|
|
119
|
+
key={index}
|
|
120
|
+
size={size}
|
|
121
|
+
/>
|
|
122
|
+
))}
|
|
123
|
+
|
|
124
|
+
{users.length > maxDisplayedUsers &&
|
|
125
|
+
<div className={itemClasses}>
|
|
126
|
+
{`+${users.length - 3}`}
|
|
127
|
+
</div>
|
|
128
|
+
}
|
|
129
|
+
</>
|
|
80
130
|
}
|
|
131
|
+
|
|
81
132
|
</div>
|
|
82
133
|
)
|
|
83
134
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import MultipleUsers from '../../pb_multiple_users/_multiple_users'
|
|
3
|
+
|
|
4
|
+
const MultipleUsersWithTooltip = (props) => {
|
|
5
|
+
return (
|
|
6
|
+
<div>
|
|
7
|
+
<MultipleUsers
|
|
8
|
+
users={[
|
|
9
|
+
{
|
|
10
|
+
name: 'Patrick Welch',
|
|
11
|
+
imageUrl: 'https://randomuser.me/api/portraits/men/9.jpg',
|
|
12
|
+
tooltip: "Patrick Welch - Online"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: 'Lucille Sanchez',
|
|
16
|
+
imageUrl: 'https://randomuser.me/api/portraits/women/6.jpg',
|
|
17
|
+
tooltip: "Lucille Sanchez - Offline"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'Beverly Reyes',
|
|
21
|
+
imageUrl: 'https://randomuser.me/api/portraits/women/74.jpg',
|
|
22
|
+
tooltip: "Beverly Reyes - Online"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'Keith Craig',
|
|
26
|
+
imageUrl: 'https://randomuser.me/api/portraits/men/40.jpg',
|
|
27
|
+
tooltip: "Keith Craig - Away"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'Alicia Cooper',
|
|
31
|
+
imageUrl: 'https://randomuser.me/api/portraits/women/46.jpg',
|
|
32
|
+
tooltip: "Alicia Cooper - Busy"
|
|
33
|
+
},
|
|
34
|
+
]}
|
|
35
|
+
withTooltip
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
``
|
|
42
|
+
export default MultipleUsersWithTooltip
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Use the `withTooltip` boolean prop to enable setting user-specific tooltip content via the `tooltip` property in the users array.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { default as MultipleUsersDefault } from './_multiple_users_default.jsx'
|
|
2
2
|
export { default as MultipleUsersReverse } from './_multiple_users_reverse.jsx'
|
|
3
3
|
export { default as MultipleUsersSize } from './_multiple_users_size.jsx'
|
|
4
|
+
export { default as MultipleUsersWithTooltip } from './_multiple_users_with_tooltip.jsx'
|
|
@@ -49,4 +49,29 @@ test('should render aria-label', () => {
|
|
|
49
49
|
|
|
50
50
|
const kit = screen.getByTestId(testId)
|
|
51
51
|
expect(kit).toHaveAttribute('aria-label', testId)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('should render withTooltip prop', () => {
|
|
55
|
+
render(
|
|
56
|
+
<MultipleUsers
|
|
57
|
+
data={{ testid: testId }}
|
|
58
|
+
users={[
|
|
59
|
+
{
|
|
60
|
+
name: 'Patrick Welch',
|
|
61
|
+
imageUrl: 'https://randomuser.me/api/portraits/men/9.jpg',
|
|
62
|
+
tooltip: "Patrick Welch - Online"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'Lucille Sanchez',
|
|
66
|
+
imageUrl: 'https://randomuser.me/api/portraits/women/6.jpg',
|
|
67
|
+
tooltip: "Lucille Sanchez - Offline"
|
|
68
|
+
},
|
|
69
|
+
]}
|
|
70
|
+
withTooltip
|
|
71
|
+
/>
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const kit = screen.getByTestId(testId)
|
|
75
|
+
const childWithTooltip = kit.querySelector('.pb_tooltip_kit')
|
|
76
|
+
expect(childWithTooltip).not.toBeNull()
|
|
52
77
|
})
|
|
@@ -110,25 +110,13 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
|
|
|
110
110
|
const inputRef = useRef<HTMLInputElement | null>(null)
|
|
111
111
|
const itiRef = useRef<any>(null);
|
|
112
112
|
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
|
113
|
-
const hasBlurredRef = useRef<boolean>(false);
|
|
114
|
-
const formSubmittedRef = useRef<boolean>(false);
|
|
115
113
|
const [inputValue, setInputValue] = useState(value)
|
|
116
114
|
const [error, setError] = useState(props.error || "")
|
|
117
115
|
const [dropDownIsOpen, setDropDownIsOpen] = useState(false)
|
|
118
116
|
const [selectedData, setSelectedData] = useState()
|
|
119
117
|
const [hasTyped, setHasTyped] = useState(false)
|
|
120
|
-
const [hasBlurred, setHasBlurred] = useState(false)
|
|
121
118
|
const [formSubmitted, setFormSubmitted] = useState(false)
|
|
122
119
|
const [hasStartedValidating, setHasStartedValidating] = useState(false)
|
|
123
|
-
|
|
124
|
-
// Keep refs in sync with state for use in event listeners
|
|
125
|
-
useEffect(() => {
|
|
126
|
-
hasBlurredRef.current = hasBlurred
|
|
127
|
-
}, [hasBlurred])
|
|
128
|
-
|
|
129
|
-
useEffect(() => {
|
|
130
|
-
formSubmittedRef.current = formSubmitted
|
|
131
|
-
}, [formSubmitted])
|
|
132
120
|
|
|
133
121
|
// Only sync initial error from props, not continuous updates
|
|
134
122
|
// Once validation starts, internal validation takes over
|
|
@@ -155,8 +143,8 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
|
|
|
155
143
|
}
|
|
156
144
|
|
|
157
145
|
// Determine which error to display
|
|
158
|
-
// Show internal errors
|
|
159
|
-
const shouldShowInternalError = (
|
|
146
|
+
// Show internal errors on blur (hasTyped) or on form submission (formSubmitted)
|
|
147
|
+
const shouldShowInternalError = (hasTyped || formSubmitted) && required && error
|
|
160
148
|
const displayError = shouldShowInternalError ? error : ""
|
|
161
149
|
|
|
162
150
|
useEffect(() => {
|
|
@@ -271,9 +259,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
|
|
|
271
259
|
return
|
|
272
260
|
}
|
|
273
261
|
|
|
274
|
-
|
|
275
|
-
// Use refs here since state updates are async and we need current values
|
|
276
|
-
if (!hasBlurredRef.current && !formSubmittedRef.current) return
|
|
262
|
+
if (!hasTyped && !error) return
|
|
277
263
|
|
|
278
264
|
// Run validation checks
|
|
279
265
|
if (itiRef.current) isValid(itiRef.current.isValidNumber())
|
|
@@ -294,7 +280,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
|
|
|
294
280
|
if (phoneNumberContainer && phoneNumberContainer === wrapperRef.current) {
|
|
295
281
|
const invalidInputName = target.name || target.getAttribute('name')
|
|
296
282
|
if (invalidInputName === name) {
|
|
297
|
-
formSubmittedRef.current = true
|
|
298
283
|
setFormSubmitted(true)
|
|
299
284
|
// Trigger validation when form is submitted
|
|
300
285
|
validateErrors()
|
|
@@ -320,9 +305,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
|
|
|
320
305
|
setInputValue("")
|
|
321
306
|
setError("")
|
|
322
307
|
setHasTyped(false)
|
|
323
|
-
hasBlurredRef.current = false
|
|
324
|
-
setHasBlurred(false)
|
|
325
|
-
formSubmittedRef.current = false
|
|
326
308
|
setFormSubmitted(false)
|
|
327
309
|
setHasStartedValidating(false)
|
|
328
310
|
// Only clear validation state if field was required
|
|
@@ -340,7 +322,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
|
|
|
340
322
|
|
|
341
323
|
if (required && isEmpty) {
|
|
342
324
|
setError('Missing phone number')
|
|
343
|
-
formSubmittedRef.current = true
|
|
344
325
|
setFormSubmitted(true)
|
|
345
326
|
return 'Missing phone number'
|
|
346
327
|
}
|
|
@@ -397,7 +378,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
|
|
|
397
378
|
|
|
398
379
|
// Set the error state so the validation attribute gets added
|
|
399
380
|
setError(errorMessage)
|
|
400
|
-
formSubmittedRef.current = true
|
|
401
381
|
setFormSubmitted(true)
|
|
402
382
|
setHasTyped(true)
|
|
403
383
|
|
|
@@ -421,7 +401,6 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
|
|
|
421
401
|
|
|
422
402
|
// Reset form submitted state when user types
|
|
423
403
|
if (formSubmitted) {
|
|
424
|
-
formSubmittedRef.current = false
|
|
425
404
|
setFormSubmitted(false)
|
|
426
405
|
}
|
|
427
406
|
|
|
@@ -437,15 +416,11 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
|
|
|
437
416
|
|
|
438
417
|
setSelectedData(phoneNumberData)
|
|
439
418
|
onChange(phoneNumberData)
|
|
440
|
-
|
|
441
|
-
// Don't call isValid callback on change - only on blur or form submission
|
|
442
|
-
// This prevents triggering validation while typing
|
|
443
|
-
// Use refs to get current values in case this is called from event listener
|
|
444
|
-
if (hasBlurredRef.current || formSubmittedRef.current) {
|
|
445
|
-
isValid(itiRef.current.isValidNumber())
|
|
446
|
-
}
|
|
419
|
+
isValid(itiRef.current.isValidNumber())
|
|
447
420
|
|
|
448
|
-
//
|
|
421
|
+
// Trigger validation after onChange for React Hook Form
|
|
422
|
+
// This ensures validation state is up-to-date
|
|
423
|
+
setTimeout(() => validateErrors(), 0)
|
|
449
424
|
}
|
|
450
425
|
|
|
451
426
|
// Separating Concerns as React Docs Recommend
|
|
@@ -507,12 +482,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
|
|
|
507
482
|
|
|
508
483
|
setSelectedData(phoneNumberData)
|
|
509
484
|
onChange(phoneNumberData)
|
|
510
|
-
|
|
511
|
-
// Don't call isValid callback on change - only on blur or form submission
|
|
512
|
-
// Use refs to check current blur state in the event listener (closure issue)
|
|
513
|
-
if (hasBlurredRef.current || formSubmittedRef.current) {
|
|
514
|
-
isValid(telInputInit.isValidNumber())
|
|
515
|
-
}
|
|
485
|
+
isValid(telInputInit.isValidNumber())
|
|
516
486
|
})
|
|
517
487
|
}
|
|
518
488
|
}
|
|
@@ -522,16 +492,12 @@ const PhoneNumberInput = (props: PhoneNumberInputProps, ref?: React.Ref<unknown>
|
|
|
522
492
|
dark,
|
|
523
493
|
"data-phone-number": JSON.stringify(selectedData),
|
|
524
494
|
disabled,
|
|
525
|
-
error:
|
|
495
|
+
error: hasTyped ? error : props.error || displayError,
|
|
526
496
|
type: 'tel',
|
|
527
497
|
id,
|
|
528
498
|
label,
|
|
529
499
|
name,
|
|
530
|
-
onBlur:
|
|
531
|
-
hasBlurredRef.current = true
|
|
532
|
-
setHasBlurred(true)
|
|
533
|
-
validateErrors()
|
|
534
|
-
},
|
|
500
|
+
onBlur: validateErrors,
|
|
535
501
|
onChange: formatAsYouType ? undefined : handleOnChange,
|
|
536
502
|
value: inputValue
|
|
537
503
|
}
|
|
@@ -276,3 +276,18 @@ test('multi-value badges have tabIndex and focus class when focused', () => {
|
|
|
276
276
|
})
|
|
277
277
|
})
|
|
278
278
|
|
|
279
|
+
test('input display none shows number of selected items', () => {
|
|
280
|
+
render(
|
|
281
|
+
<Typeahead
|
|
282
|
+
data={{ testid: 'input-display-none-test' }}
|
|
283
|
+
defaultValue={[options[0], options[1]]}
|
|
284
|
+
inputDisplay="none"
|
|
285
|
+
isMulti
|
|
286
|
+
options={options}
|
|
287
|
+
/>
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
const kit = screen.getByTestId('input-display-none-test')
|
|
291
|
+
const inputDisplayDiv = kit.querySelector(".pb_typeahead_selection_count")
|
|
292
|
+
expect(inputDisplayDiv).toHaveTextContent("2 items selected")
|
|
293
|
+
})
|
|
@@ -43,6 +43,7 @@ type TypeaheadProps = {
|
|
|
43
43
|
error?: string,
|
|
44
44
|
htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
|
|
45
45
|
id?: string,
|
|
46
|
+
inputDisplay?: "pills" | "none",
|
|
46
47
|
label?: string,
|
|
47
48
|
loadOptions?: string | Noop,
|
|
48
49
|
getOptionLabel?: string | (() => string),
|
|
@@ -89,6 +90,7 @@ const Typeahead = forwardRef<HTMLInputElement, TypeaheadProps>(({
|
|
|
89
90
|
getOptionValue,
|
|
90
91
|
htmlOptions = {},
|
|
91
92
|
id,
|
|
93
|
+
inputDisplay = "pills",
|
|
92
94
|
name,
|
|
93
95
|
loadOptions = noop,
|
|
94
96
|
marginBottom = "sm",
|
|
@@ -238,6 +240,7 @@ const Typeahead = forwardRef<HTMLInputElement, TypeaheadProps>(({
|
|
|
238
240
|
getOptionValue: isString(getOptionValue) ? get(window, getOptionValue) : getOptionValue,
|
|
239
241
|
defaultOptions: true,
|
|
240
242
|
id: id || uniqueId(),
|
|
243
|
+
inputDisplay: inputDisplay,
|
|
241
244
|
inline: false,
|
|
242
245
|
isClearable: true,
|
|
243
246
|
isSearchable: true,
|