playbook_ui 15.5.0.pre.alpha.PLAY2581aggressivevalidation12698 → 15.5.0.pre.alpha.draggableask12812

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. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_background/_background.tsx +6 -6
  3. data/app/pb_kits/playbook/pb_background/background.test.js +5 -1
  4. data/app/pb_kits/playbook/pb_background/docs/_background_light.html.erb +1 -1
  5. data/app/pb_kits/playbook/pb_background/docs/_background_light.jsx +0 -1
  6. data/app/pb_kits/playbook/pb_background/docs/_background_light.md +1 -0
  7. data/app/pb_kits/playbook/pb_background/docs/example.yml +2 -2
  8. data/app/pb_kits/playbook/pb_dialog/index.js +10 -15
  9. data/app/pb_kits/playbook/pb_draggable/context/index.tsx +131 -1
  10. data/app/pb_kits/playbook/pb_home_address_street/_home_address_street.tsx +34 -22
  11. data/app/pb_kits/playbook/pb_home_address_street/city_emphasis.html.erb +16 -12
  12. data/app/pb_kits/playbook/pb_home_address_street/docs/_home_address_street_default.html.erb +1 -1
  13. data/app/pb_kits/playbook/pb_home_address_street/none_emphasis.html.erb +16 -12
  14. data/app/pb_kits/playbook/pb_home_address_street/street_emphasis.html.erb +16 -12
  15. data/app/pb_kits/playbook/pb_multiple_users/_multiple_users.scss +10 -0
  16. data/app/pb_kits/playbook/pb_multiple_users/_multiple_users.tsx +66 -15
  17. data/app/pb_kits/playbook/pb_multiple_users/docs/_multiple_users_with_tooltip.jsx +42 -0
  18. data/app/pb_kits/playbook/pb_multiple_users/docs/_multiple_users_with_tooltip.md +1 -0
  19. data/app/pb_kits/playbook/pb_multiple_users/docs/example.yml +1 -0
  20. data/app/pb_kits/playbook/pb_multiple_users/docs/index.js +1 -0
  21. data/app/pb_kits/playbook/pb_multiple_users/multiple_users.test.js +25 -0
  22. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_validation.html.erb +34 -4
  23. data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_validation.jsx +16 -7
  24. data/app/pb_kits/playbook/pb_typeahead/_typeahead.test.jsx +15 -0
  25. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +3 -0
  26. data/app/pb_kits/playbook/pb_typeahead/components/MultiValue.tsx +6 -1
  27. data/app/pb_kits/playbook/pb_typeahead/components/ValueContainer.tsx +34 -7
  28. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_input_display.html.erb +30 -0
  29. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_input_display.jsx +37 -0
  30. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_input_display.md +3 -0
  31. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +2 -0
  32. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +2 -1
  33. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +6 -1
  34. data/dist/chunks/_typeahead-C0TXfY_W.js +6 -0
  35. data/dist/chunks/vendor.js +2 -2
  36. data/dist/playbook-rails-react-bindings.js +1 -1
  37. data/dist/playbook-rails.js +1 -1
  38. data/dist/playbook.css +1 -1
  39. data/lib/playbook/version.rb +1 -1
  40. metadata +9 -3
  41. data/dist/chunks/_typeahead-C8dnmJBs.js +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae823d2ea188c8b8b0476223c92ffb8cbf0604e42028c011f6fe12b81a25b8d1
4
- data.tar.gz: 7d8758ea0c6fb712605df92777b66589a7d62c63b43cefb3e0cb0a4a104b9547
3
+ metadata.gz: 56097fbb1da0d4528e1d4beb87d0737331ed094cc2e29e3ebdee8384c8a2ba98
4
+ data.tar.gz: dd17076481fb9ad736c29ca19272f2c8c598fb996e858216971756d8405dd9e6
5
5
  SHA512:
6
- metadata.gz: b0bdb702a07ce7985616a28ee935a875208cf9e6292c78d4368408804bbd5a18743e52f642be425d0282b951035fbdbf89a51ced9f143ac314e9545e51069b7a
7
- data.tar.gz: 2b3897ff7f64c3d7a618ba0ae8056bcc817b81dbf65088e66b800fec33f23144966bdceec47386a8356cc04727c62d8db0e5f5d3f6f0f53be142d04545a3329f
6
+ metadata.gz: 8ed5946c8074670af44c1ebf4bfdff28144418e7eb199ea8bc755c3750f4d4ecd7ea9050c773e4adff13c55daed0ca16376e0f088ba2664795a8a58340bbaf27
7
+ data.tar.gz: c712a117f381ee38fbdcbb850003540978418541b04ef575e16c65964d2224b40a2399c9d61ff14613bdaa064e8241657dc48588a5babbc54df6c99d27ffeef3
@@ -102,16 +102,16 @@ const Background = (props: BackgroundProps): React.ReactElement => {
102
102
  useEffect(() => {
103
103
  const updateResponsiveProps = () => {
104
104
  setResponsiveProps({
105
- backgroundSize: getResponsiveValue(props.backgroundSize),
106
- backgroundPosition: getResponsiveValue(props.backgroundPosition),
107
- backgroundRepeat: getResponsiveValue(props.backgroundRepeat),
108
- backgroundColor: getResponsiveValue(props.backgroundColor),
109
- imageUrl: getResponsiveValue(props.imageUrl),
105
+ backgroundSize: getResponsiveValue(backgroundSize),
106
+ backgroundPosition: getResponsiveValue(backgroundPosition),
107
+ backgroundRepeat: getResponsiveValue(backgroundRepeat),
108
+ backgroundColor: getResponsiveValue(backgroundColor),
109
+ imageUrl: getResponsiveValue(imageUrl),
110
110
  });
111
111
  };
112
112
  window.addEventListener('resize', updateResponsiveProps);
113
113
  return () => window.removeEventListener('resize', updateResponsiveProps);
114
- }, [props]);
114
+ }, [backgroundSize, backgroundPosition, backgroundRepeat, backgroundColor, imageUrl]);
115
115
 
116
116
 
117
117
  // Extract currently applicable responsive values.
@@ -4,7 +4,6 @@ import Background from './_background'
4
4
 
5
5
  const props = {
6
6
  data: { testid: 'background' },
7
- backgroundColor: null,
8
7
  }
9
8
 
10
9
  it('Should be accessible', async () => {
@@ -42,3 +41,8 @@ test('applies correct overlay class when imageOverlay prop is provided', () => {
42
41
  const kit = renderKit(Background, props, { imageOverlay: 'opacity_6' });
43
42
  expect(kit).toHaveClass('imageoverlay_opacity_6');
44
43
  });
44
+
45
+ test('Sets backgroundColor to light as default when no backgroundColor prop is provided', () => {
46
+ const kit = renderKit(Background, props);
47
+ expect(kit).toHaveClass('pb_background_color_light');
48
+ });
@@ -1,3 +1,3 @@
1
- <%= pb_rails("background", props: { background_color: "light", padding: "xl" }) do %>
1
+ <%= pb_rails("background", props: { padding: "xl" }) do %>
2
2
 
3
3
  <% end %>
@@ -3,7 +3,6 @@ import Background from '../../pb_background/_background'
3
3
 
4
4
  const BackgroundLight = (props) => (
5
5
  <Background
6
- backgroundColor="light"
7
6
  padding="xl"
8
7
  {...props}
9
8
  />
@@ -0,0 +1 @@
1
+ By default, the Background kit sets background color to 'light' as seen here.
@@ -1,7 +1,7 @@
1
1
  examples:
2
2
 
3
3
  rails:
4
- - background_light: Light
4
+ - background_light: Default
5
5
  - background_white: White
6
6
  - background_gradient: Gradient
7
7
  - background_image: Image
@@ -11,7 +11,7 @@ examples:
11
11
  - background_size: Size
12
12
 
13
13
  react:
14
- - background_light: Light
14
+ - background_light: Default
15
15
  - background_white: White
16
16
  - background_gradient: Gradient
17
17
  - background_image: Image
@@ -143,30 +143,25 @@ export default class PbDialog extends PbEnhancedElement {
143
143
 
144
144
  // Close dialog box on outside click
145
145
  dialogs.forEach((dialogElement) => {
146
- const originalClickHandler = dialogElement._outsideClickHandler
147
- if (originalClickHandler) dialogElement.removeEventListener("click", originalClickHandler)
148
-
146
+ const originalMousedownHandler = dialogElement._outsideClickHandler
147
+ if (originalMousedownHandler) dialogElement.removeEventListener("mousedown", originalMousedownHandler)
149
148
  dialogElement._outsideClickHandler = (event) => {
150
149
  const dialogParentDataset = dialogElement.parentElement.dataset
151
150
  if (dialogParentDataset.overlayClick === "overlay_close") return
152
151
 
153
- // Get the dialog's bounding box (the actual content area)
154
- const rect = dialogElement.getBoundingClientRect()
155
- const clickedInDialog = (
156
- event.clientX >= rect.left &&
157
- event.clientX <= rect.right &&
158
- event.clientY >= rect.top &&
159
- event.clientY <= rect.bottom
160
- )
161
-
162
- // Only close if clicked outside the dialog content (on the backdrop)
163
- if (!clickedInDialog) {
152
+ const dialogModal = event.target.getBoundingClientRect()
153
+ const clickedOutsideDialogModal = event.clientX < dialogModal.left ||
154
+ event.clientX > dialogModal.right ||
155
+ event.clientY < dialogModal.top ||
156
+ event.clientY > dialogModal.bottom
157
+
158
+ if (clickedOutsideDialogModal) {
164
159
  dialogElement.close()
165
160
  event.stopPropagation()
166
161
  }
167
162
  }
168
163
 
169
- dialogElement.addEventListener("click", dialogElement._outsideClickHandler);
164
+ dialogElement.addEventListener("mousedown", dialogElement._outsideClickHandler);
170
165
  })
171
166
  }
172
167
  }
@@ -1,4 +1,4 @@
1
- import React, { createContext, useReducer, useContext, useEffect, useMemo } from "react";
1
+ import React, { createContext, useReducer, useContext, useEffect, useMemo, useRef } from "react";
2
2
  import { InitialStateType, ActionType, DraggableProviderType } from "./types";
3
3
 
4
4
  const initialState: InitialStateType = {
@@ -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
  }
@@ -119,6 +135,19 @@ export const DraggableProvider = ({
119
135
  enableCrossContainerPreview = false,
120
136
  }: DraggableProviderType) => {
121
137
  const [state, dispatch] = useReducer(reducer, initialState);
138
+
139
+ // Track drag state for global listener
140
+ const dragStateRef = useRef<{
141
+ isDragging: boolean;
142
+ draggedItemId: string;
143
+ originalContainer: string;
144
+ dropOccurred: boolean;
145
+ }>({
146
+ isDragging: false,
147
+ draggedItemId: '',
148
+ originalContainer: '',
149
+ dropOccurred: false,
150
+ });
122
151
 
123
152
  // Parse dropZone prop - handle both string format (backward compatibility) and object format
124
153
  let dropZoneType = 'ghost';
@@ -148,7 +177,94 @@ export const DraggableProvider = ({
148
177
  onReorder(state.items);
149
178
  }, [state.items]);
150
179
 
180
+ // Monitor for failed drops by detecting mouse/pointer release during drag (this is needed for cross container preview)
181
+ useEffect(() => {
182
+ if (!enableCrossContainerPreview) return;
183
+
184
+ const handleGlobalMouseUp = () => {
185
+ // If we're dragging and mouse is released, wait a bit to see if drop occurs
186
+ if (dragStateRef.current.isDragging) {
187
+ setTimeout(() => {
188
+ // If drop still hasn't occurred, reset
189
+ if (dragStateRef.current.isDragging && !dragStateRef.current.dropOccurred) {
190
+ dispatch({
191
+ type: 'RESET_DRAG_CONTAINER',
192
+ payload: {
193
+ itemId: dragStateRef.current.draggedItemId,
194
+ originalContainer: dragStateRef.current.originalContainer,
195
+ },
196
+ });
197
+ dispatch({ type: 'SET_IS_DRAGGING', payload: "" });
198
+ dispatch({ type: 'SET_ACTIVE_CONTAINER', payload: "" });
199
+ dispatch({ type: 'SET_DRAG_DATA', payload: { id: "", initialGroup: "", originId: "" } });
200
+
201
+ // Clear drag state
202
+ dragStateRef.current = {
203
+ isDragging: false,
204
+ draggedItemId: '',
205
+ originalContainer: '',
206
+ dropOccurred: false,
207
+ };
208
+ }
209
+ }, 50); // Small delay to let drop event fire if it's going to
210
+ }
211
+ };
212
+
213
+ document.addEventListener('mouseup', handleGlobalMouseUp);
214
+ document.addEventListener('pointerup', handleGlobalMouseUp);
215
+
216
+ return () => {
217
+ document.removeEventListener('mouseup', handleGlobalMouseUp);
218
+ document.removeEventListener('pointerup', handleGlobalMouseUp);
219
+ };
220
+ }, [enableCrossContainerPreview]);
221
+
222
+ // Detect when dragging stops (isDragging goes from truthy to empty)
223
+ const prevIsDraggingRef = useRef(state.isDragging);
224
+
225
+ useEffect(() => {
226
+ if (!enableCrossContainerPreview) return;
227
+
228
+ const wasDragging = prevIsDraggingRef.current;
229
+ const isNowDragging = state.isDragging;
230
+
231
+ // Drag just ended (was dragging, now not)
232
+ if (wasDragging && !isNowDragging) {
233
+
234
+ // If drop didn't occur, reset to original container
235
+ if (!dragStateRef.current.dropOccurred && dragStateRef.current.draggedItemId) {
236
+ dispatch({
237
+ type: 'RESET_DRAG_CONTAINER',
238
+ payload: {
239
+ itemId: dragStateRef.current.draggedItemId,
240
+ originalContainer: dragStateRef.current.originalContainer,
241
+ },
242
+ });
243
+ }
244
+
245
+ // Clear drag state
246
+ dragStateRef.current = {
247
+ isDragging: false,
248
+ draggedItemId: '',
249
+ originalContainer: '',
250
+ dropOccurred: false,
251
+ };
252
+ }
253
+
254
+ prevIsDraggingRef.current = isNowDragging;
255
+ }, [state.isDragging, enableCrossContainerPreview]);
256
+
151
257
  const handleDragStart = (id: string, container: string) => {
258
+ // Track drag in ref for global listener
259
+ if (enableCrossContainerPreview) {
260
+ dragStateRef.current = {
261
+ isDragging: true,
262
+ draggedItemId: id,
263
+ originalContainer: container,
264
+ dropOccurred: false,
265
+ };
266
+ }
267
+
152
268
  dispatch({ type: 'SET_DRAG_DATA', payload: { id: id, initialGroup: container, originId: providerId } });
153
269
  dispatch({ type: 'SET_IS_DRAGGING', payload: id });
154
270
  if (onDragStart) onDragStart(id, container);
@@ -202,9 +318,18 @@ export const DraggableProvider = ({
202
318
  const draggedItemId = state.dragData.id;
203
319
  const originalContainer = state.dragData.initialGroup;
204
320
 
321
+ // If enableCrossContainerPreview is true and no drop occurred, reset item to original container
322
+ if (enableCrossContainerPreview && !dropOccurred && draggedItemId && originalContainer) {
323
+ dispatch({ type: 'RESET_DRAG_CONTAINER', payload: { itemId: draggedItemId, originalContainer } });
324
+ }
325
+
205
326
  dispatch({ type: 'SET_IS_DRAGGING', payload: "" });
206
327
  dispatch({ type: 'SET_ACTIVE_CONTAINER', payload: "" });
207
328
  dispatch({ type: 'SET_DRAG_DATA', payload: { id: "", initialGroup: "", originId: "" } });
329
+
330
+ // Reset the drop flag
331
+ dropOccurred = false;
332
+
208
333
  if (onDragEnd) {
209
334
  if (!enableCrossContainerPreview) {
210
335
  onDragEnd();
@@ -237,6 +362,11 @@ export const DraggableProvider = ({
237
362
 
238
363
  const draggedItemId = state.dragData.id;
239
364
  const originalContainer = state.dragData.initialGroup;
365
+
366
+ // Mark drop as successful in ref for global listener
367
+ if (enableCrossContainerPreview) {
368
+ dragStateRef.current.dropOccurred = true;
369
+ }
240
370
 
241
371
  dispatch({ type: 'SET_IS_DRAGGING', payload: "" });
242
372
  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
- <Title
100
- className="pb_home_address_street_address"
101
- dark={dark}
102
- size={4}
103
- >
104
- {joinPresent([formatStreetAdr(address), houseStyle], ' · ')}
105
- </Title>
106
- <Title
107
- className="pb_home_address_street_address"
108
- dark={dark}
109
- size={4}
110
- >
111
- {titleize(addressCont)}
112
- </Title>
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
- <Body color="light">
121
- {joinPresent([formatStreetAdr(address), houseStyle], ' · ')}
122
- </Body>
123
- <Body color="light">{titleize(addressCont)}</Body>
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
- <Body dark={dark}>
145
- {joinPresent([formatStreetAdr(address), houseStyle], ' · ')}
146
- </Body>
147
- <Body dark={dark}>{formatStreetAdr(addressCont)}</Body>
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
- <%= pb_rails "body", props: {
2
- classname: "pb_home_address_street_address",
3
- color: "light",
4
- text: object.address_house_style,
5
- dark: object.dark
6
- } %>
7
- <%= pb_rails "body", props: {
8
- classname: "pb_home_address_street_address",
9
- color: "light",
10
- text: object.address_house_style2,
11
- dark: object.dark
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",
@@ -8,4 +8,4 @@
8
8
  state: "PA",
9
9
  zipcode: "19382",
10
10
  territory: "PHL",
11
- }) %>
11
+ }) %>
@@ -1,15 +1,19 @@
1
- <%= pb_rails "body", props: {
2
- classname: "pb_home_address_street_address",
3
- size: 4,
4
- text: object.address_house_style,
5
- dark: object.dark
6
- } %>
7
- <%= pb_rails "body", props: {
8
- classname: "pb_home_address_street_address",
9
- size: 4,
10
- text: object.address_house_style2,
11
- dark: object.dark
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
- <%= pb_rails "title", props: {
2
- classname: "pb_home_address_street_address",
3
- size: 4,
4
- text: object.address_house_style,
5
- dark: object.dark
6
- } %>
7
- <%= pb_rails "title", props: {
8
- classname: "pb_home_address_street_address",
9
- size: 4,
10
- text: object.address_house_style2,
11
- dark: object.dark
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
- {usersToDisplay.map((avatarData, index) => (
66
- <Avatar
67
- {...avatarData}
68
- className="pb_multiple_users_item"
69
- dark={dark}
70
- imageAlt={avatarData.name}
71
- key={index}
72
- size={size}
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
- { users.length > maxDisplayedUsers &&
77
- <div className={itemClasses}>
78
- {`+${users.length - 3}`}
79
- </div>
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
  }