playbook_ui 13.30.0.pre.alpha.PLAY1328fixtimelinekitglobalpropsreact3096 → 13.30.0.pre.alpha.play1367contenttagnoninputkits3159

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta_sort.html.erb +59 -0
  3. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta_sort.md +6 -0
  4. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +3 -2
  5. data/app/pb_kits/playbook/pb_advanced_table/table_header.html.erb +4 -4
  6. data/app/pb_kits/playbook/pb_background/background.html.erb +2 -11
  7. data/app/pb_kits/playbook/pb_body/body.html.erb +1 -6
  8. data/app/pb_kits/playbook/pb_bread_crumbs/bread_crumb_item.html.erb +1 -6
  9. data/app/pb_kits/playbook/pb_bread_crumbs/bread_crumbs.html.erb +1 -6
  10. data/app/pb_kits/playbook/pb_caption/caption.html.erb +1 -6
  11. data/app/pb_kits/playbook/pb_card/_card.tsx +4 -3
  12. data/app/pb_kits/playbook/pb_card/card.html.erb +1 -7
  13. data/app/pb_kits/playbook/pb_collapsible/index.js +6 -1
  14. data/app/pb_kits/playbook/pb_date_time_stacked/docs/_date_time_stacked_default_swift.md +33 -0
  15. data/app/pb_kits/playbook/pb_date_time_stacked/docs/_date_time_stacked_props_swift.md +18 -0
  16. data/app/pb_kits/playbook/pb_date_time_stacked/docs/example.yml +6 -1
  17. data/app/pb_kits/playbook/pb_detail/detail.html.erb +1 -6
  18. data/app/pb_kits/playbook/pb_dialog/dialog.html.erb +30 -35
  19. data/app/pb_kits/playbook/pb_dialog/dialog_body.html.erb +2 -7
  20. data/app/pb_kits/playbook/pb_dialog/dialog_footer.html.erb +1 -5
  21. data/app/pb_kits/playbook/pb_dialog/dialog_header.html.erb +1 -6
  22. data/app/pb_kits/playbook/pb_draggable/_draggable.scss +2 -4
  23. data/app/pb_kits/playbook/pb_draggable/context/index.tsx +70 -50
  24. data/app/pb_kits/playbook/pb_draggable/context/types.ts +26 -0
  25. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_default.jsx +2 -1
  26. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_default.md +1 -1
  27. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_multiple_containers.jsx +5 -1
  28. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_cards.jsx +11 -4
  29. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_cards.md +4 -2
  30. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_list.jsx +2 -2
  31. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_list.md +7 -1
  32. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_selectable_list.jsx +6 -4
  33. data/app/pb_kits/playbook/pb_draggable/docs/_draggable_with_selectable_list.md +7 -0
  34. data/app/pb_kits/playbook/pb_draggable/draggable.test.jsx +142 -15
  35. data/app/pb_kits/playbook/pb_draggable/subcomponents/DraggableItem.tsx +6 -5
  36. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +18 -0
  37. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +228 -218
  38. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_error.html.erb +9 -0
  39. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_error.jsx +34 -0
  40. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +2 -0
  41. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +2 -1
  42. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +14 -12
  43. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +7 -0
  44. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +17 -2
  45. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/_fixed_confirmation_toast.tsx +9 -3
  46. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_custom_icon.html.erb +32 -0
  47. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_custom_icon.jsx +48 -0
  48. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/example.yml +2 -0
  49. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/index.js +1 -0
  50. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.rb +16 -11
  51. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.test.js +72 -0
  52. data/app/pb_kits/playbook/pb_flex/flex.html.erb +1 -5
  53. data/app/pb_kits/playbook/pb_hashtag/hashtag.html.erb +1 -6
  54. data/app/pb_kits/playbook/pb_highlight/highlight.html.erb +1 -5
  55. data/app/pb_kits/playbook/pb_home_address_street/home_address_street.html.erb +1 -5
  56. data/app/pb_kits/playbook/pb_icon/_icon.tsx +37 -6
  57. data/app/pb_kits/playbook/pb_icon/docs/_icon_default.html.erb +1 -1
  58. data/app/pb_kits/playbook/pb_icon/docs/_icon_default.jsx +2 -3
  59. data/app/pb_kits/playbook/pb_icon/icon.rb +21 -1
  60. data/app/pb_kits/playbook/pb_icon/icon_aliases.json +39 -0
  61. data/app/pb_kits/playbook/pb_list/_list.tsx +4 -4
  62. data/app/pb_kits/playbook/pb_list/_list_item.tsx +7 -3
  63. data/app/pb_kits/playbook/pb_nav/docs/_collapsible_nav.html.erb +1 -1
  64. data/app/pb_kits/playbook/pb_nav/docs/_collapsible_nav.jsx +1 -0
  65. data/app/pb_kits/playbook/pb_nav/docs/_collapsible_nav.md +3 -1
  66. data/app/pb_kits/playbook/pb_nav/item.html.erb +1 -1
  67. data/app/pb_kits/playbook/pb_nav/item.rb +1 -0
  68. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_default.html.erb +24 -0
  69. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_default.md +4 -4
  70. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_multi_directional.html.erb +11 -0
  71. data/app/pb_kits/playbook/pb_overlay/docs/example.yml +4 -0
  72. data/app/pb_kits/playbook/pb_overlay/overlay.html.erb +27 -0
  73. data/app/pb_kits/playbook/pb_overlay/overlay.rb +110 -0
  74. data/app/pb_kits/playbook/pb_selectable_list/_item.tsx +7 -3
  75. data/app/pb_kits/playbook/pb_selectable_list/_selectable_list.tsx +3 -3
  76. data/app/pb_kits/playbook/pb_table/table_header.html.erb +16 -2
  77. data/app/pb_kits/playbook/utilities/globalProps.ts +1 -0
  78. data/dist/menu.yml +2 -2
  79. data/dist/playbook-rails.js +7 -7
  80. data/lib/playbook/version.rb +1 -1
  81. metadata +18 -2
@@ -4,6 +4,7 @@ import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from "../uti
4
4
  import { globalProps } from "../utilities/globalProps";
5
5
  import { GenericObject } from "../types";
6
6
 
7
+ import Body from '../pb_body/_body';
7
8
  import Caption from "../pb_caption/_caption";
8
9
 
9
10
  import DropdownContainer from "./subcomponents/DropdownContainer";
@@ -13,236 +14,245 @@ import DropdownTrigger from "./subcomponents/DropdownTrigger";
13
14
  import useDropdown from "./hooks/useDropdown";
14
15
 
15
16
  import {
16
- separateChildComponents,
17
- prepareSubcomponents,
18
- handleClickOutside,
17
+ separateChildComponents,
18
+ prepareSubcomponents,
19
+ handleClickOutside,
19
20
  } from "./utilities";
20
21
 
21
22
  type DropdownProps = {
22
- aria?: { [key: string]: string };
23
- autocomplete?: boolean;
24
- children?: React.ReactChild[] | React.ReactChild | React.ReactElement[];
25
- className?: string;
26
- dark?: boolean;
27
- data?: { [key: string]: string };
28
- htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
29
- id?: string;
30
- isClosed?: boolean;
31
- label?: string;
32
- onSelect?: (arg: GenericObject) => null;
33
- options: GenericObject;
34
- triggerRef?: any;
23
+ aria?: { [key: string]: string };
24
+ autocomplete?: boolean;
25
+ children?: React.ReactChild[] | React.ReactChild | React.ReactElement[];
26
+ className?: string;
27
+ dark?: boolean;
28
+ data?: { [key: string]: string };
29
+ error?: string;
30
+ htmlOptions?: { [key: string]: string | number | boolean | (() => void) },
31
+ id?: string;
32
+ isClosed?: boolean;
33
+ label?: string;
34
+ onSelect?: (arg: GenericObject) => null;
35
+ options: GenericObject;
36
+ triggerRef?: any;
35
37
  };
36
38
 
37
39
  const Dropdown = (props: DropdownProps) => {
38
- const {
39
- aria = {},
40
- autocomplete = false,
41
- children,
42
- className,
43
- dark = false,
44
- data = {},
45
- htmlOptions = {},
46
- id,
47
- isClosed = true,
48
- label,
49
- onSelect,
50
- options,
51
- triggerRef
52
- } = props;
53
-
54
- const ariaProps = buildAriaProps(aria);
55
- const dataProps = buildDataProps(data);
56
- const htmlProps = buildHtmlProps(htmlOptions);
57
- const classes = classnames(
58
- buildCss("pb_dropdown"),
59
- globalProps(props),
60
- className
61
- );
62
-
63
- const [isDropDownClosed, setIsDropDownClosed, toggleDropdown] = useDropdown(isClosed);
64
-
65
- const [filterItem, setFilterItem] = useState("");
66
- const [selected, setSelected] = useState<GenericObject>({});
67
- const [isInputFocused, setIsInputFocused] = useState(false);
68
- const [hasTriggerSubcomponent, setHasTriggerSubcomponent] = useState(true);
69
- const [hasContainerSubcomponent, setHasContainerSubcomponent] =
70
- useState(true);
71
- //state for keyboard events
72
- const [focusedOptionIndex, setFocusedOptionIndex] = useState(-1);
73
-
74
- const dropdownRef = useRef(null);
75
- const inputRef = useRef(null);
76
- const inputWrapperRef = useRef(null);
77
- const dropdownContainerRef = useRef(null);
78
-
79
- const { trigger, container, otherChildren } =
80
- separateChildComponents(children);
81
-
82
- useEffect(() => {
83
- // Set the parent element of the trigger to relative to allow for absolute positioning of the dropdown
84
- //Only needed for when useDropdown hook used with external trigger
85
- if (triggerRef?.current) {
86
- const parentElement = triggerRef.current.parentNode;
87
- if (parentElement) {
88
- parentElement.style.position = 'relative';
89
- }
90
- }
91
- // Handle clicks outside the dropdown
92
- const handleClick = handleClickOutside({
93
- inputWrapperRef,
94
- dropdownContainerRef,
95
- setIsDropDownClosed,
96
- setFocusedOptionIndex,
97
- setIsInputFocused,
98
- });
99
-
100
- window.addEventListener("click", handleClick);
101
- return () => {
102
- window.removeEventListener("click", handleClick);
103
- };
104
- }, []);
105
-
106
- useEffect(() => {
107
- setHasTriggerSubcomponent(!!trigger);
108
- setHasContainerSubcomponent(!!container);
109
- }, []);
110
-
111
- // dropdown to toggle with external control
112
- useEffect(()=> {
113
- setIsDropDownClosed(isClosed)
114
- },[isClosed])
115
-
116
- const filteredOptions = options?.filter((option: GenericObject) => {
117
- const label = typeof option.label === 'string' ? option.label.toLowerCase() : option.label;
118
- return String(label).toLowerCase().includes(filterItem.toLowerCase());
119
- }
120
- );
121
-
122
- // For keyboard accessibility: Set focus within dropdown to selected item if it exists
123
- useEffect(() => {
124
- if (!isDropDownClosed) {
125
- let newIndex = 0;
126
- if (selected && selected?.label) {
127
- const selectedIndex = filteredOptions.findIndex((option: GenericObject) => option.label === selected.label);
128
- if (selectedIndex >= 0) {
129
- newIndex = selectedIndex;
40
+ const {
41
+ aria = {},
42
+ autocomplete = false,
43
+ children,
44
+ className,
45
+ dark = false,
46
+ data = {},
47
+ error,
48
+ htmlOptions = {},
49
+ id,
50
+ isClosed = true,
51
+ label,
52
+ onSelect,
53
+ options,
54
+ triggerRef
55
+ } = props;
56
+
57
+ const ariaProps = buildAriaProps(aria);
58
+ const dataProps = buildDataProps(data);
59
+ const htmlProps = buildHtmlProps(htmlOptions);
60
+ const classes = classnames(
61
+ buildCss("pb_dropdown"),
62
+ globalProps(props),
63
+ className
64
+ );
65
+
66
+ const [isDropDownClosed, setIsDropDownClosed, toggleDropdown] = useDropdown(isClosed);
67
+
68
+ const [filterItem, setFilterItem] = useState("");
69
+ const [selected, setSelected] = useState<GenericObject>({});
70
+ const [isInputFocused, setIsInputFocused] = useState(false);
71
+ const [hasTriggerSubcomponent, setHasTriggerSubcomponent] = useState(true);
72
+ const [hasContainerSubcomponent, setHasContainerSubcomponent] =
73
+ useState(true);
74
+ //state for keyboard events
75
+ const [focusedOptionIndex, setFocusedOptionIndex] = useState(-1);
76
+
77
+ const dropdownRef = useRef(null);
78
+ const inputRef = useRef(null);
79
+ const inputWrapperRef = useRef(null);
80
+ const dropdownContainerRef = useRef(null);
81
+
82
+ const { trigger, container, otherChildren } =
83
+ separateChildComponents(children);
84
+
85
+ useEffect(() => {
86
+ // Set the parent element of the trigger to relative to allow for absolute positioning of the dropdown
87
+ //Only needed for when useDropdown hook used with external trigger
88
+ if (triggerRef?.current) {
89
+ const parentElement = triggerRef.current.parentNode;
90
+ if (parentElement) {
91
+ parentElement.style.position = 'relative';
130
92
  }
131
93
  }
132
- setFocusedOptionIndex(newIndex);
94
+ // Handle clicks outside the dropdown
95
+ const handleClick = handleClickOutside({
96
+ inputWrapperRef,
97
+ dropdownContainerRef,
98
+ setIsDropDownClosed,
99
+ setFocusedOptionIndex,
100
+ setIsInputFocused,
101
+ });
102
+
103
+ window.addEventListener("click", handleClick);
104
+ return () => {
105
+ window.removeEventListener("click", handleClick);
106
+ };
107
+ }, []);
108
+
109
+ useEffect(() => {
110
+ setHasTriggerSubcomponent(!!trigger);
111
+ setHasContainerSubcomponent(!!container);
112
+ }, []);
113
+
114
+ // dropdown to toggle with external control
115
+ useEffect(() => {
116
+ setIsDropDownClosed(isClosed)
117
+ }, [isClosed])
118
+
119
+ const filteredOptions = options?.filter((option: GenericObject) => {
120
+ const label = typeof option.label === 'string' ? option.label.toLowerCase() : option.label;
121
+ return String(label).toLowerCase().includes(filterItem.toLowerCase());
133
122
  }
134
- }, [isDropDownClosed]);
135
-
136
-
137
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
138
- setFilterItem(e.target.value);
139
- setIsDropDownClosed(false);
140
- };
141
-
142
- const handleOptionClick = (selectedItem: GenericObject) => {
143
- setSelected(selectedItem);
144
- setFilterItem("");
145
- setIsDropDownClosed(true);
146
- onSelect && onSelect(selectedItem);
147
- };
148
-
149
- const handleWrapperClick = () => {
150
- autocomplete && inputRef.current.focus();
151
- toggleDropdown();
152
- };
153
-
154
- const handleBackspace = () => {
155
- setSelected({});
156
- onSelect && onSelect(null);
157
- setFocusedOptionIndex(-1);
158
- };
159
-
160
- const componentsToRender = prepareSubcomponents({
161
- children,
162
- hasTriggerSubcomponent,
163
- hasContainerSubcomponent,
164
- trigger,
165
- container,
166
- otherChildren,
167
- dark
168
- });
169
-
170
-
171
- return (
172
- <div {...ariaProps}
173
- {...dataProps}
174
- {...htmlProps}
175
- className={classes}
176
- id={id}
177
- style={triggerRef ? { position: "absolute"} : { position: "relative"}}
178
- >
179
- <DropdownContext.Provider
180
- value={{
181
- autocomplete,
182
- dropdownContainerRef,
183
- filteredOptions,
184
- filterItem,
185
- focusedOptionIndex,
186
- handleBackspace,
187
- handleChange,
188
- handleOptionClick,
189
- handleWrapperClick,
190
- inputRef,
191
- inputWrapperRef,
192
- isDropDownClosed,
193
- isInputFocused,
194
- options,
195
- selected,
196
- setFocusedOptionIndex,
197
- setIsDropDownClosed,
198
- setIsInputFocused,
199
- setSelected,
200
- toggleDropdown,
201
- triggerRef
202
- }}
203
- >
204
- {label &&
205
- <Caption
206
- dark={dark}
207
- marginBottom="xs"
208
- text={label}
209
- />
123
+ );
124
+
125
+ // For keyboard accessibility: Set focus within dropdown to selected item if it exists
126
+ useEffect(() => {
127
+ if (!isDropDownClosed) {
128
+ let newIndex = 0;
129
+ if (selected && selected?.label) {
130
+ const selectedIndex = filteredOptions.findIndex((option: GenericObject) => option.label === selected.label);
131
+ if (selectedIndex >= 0) {
132
+ newIndex = selectedIndex;
133
+ }
134
+ }
135
+ setFocusedOptionIndex(newIndex);
210
136
  }
211
- <div className="dropdown_wrapper"
212
- onBlur={() => {
213
- // Debounce to delay the execution to prevent jumpiness in Focus state
214
- setTimeout(() => {
215
- if (!dropdownRef.current.contains(document.activeElement)) {
216
- setIsInputFocused(false);
217
- }
218
- }, 0);
219
- }}
220
- onFocus={() => setIsInputFocused(true)}
221
- ref={dropdownRef}
137
+ }, [isDropDownClosed]);
138
+
139
+
140
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
141
+ setFilterItem(e.target.value);
142
+ setIsDropDownClosed(false);
143
+ };
144
+
145
+ const handleOptionClick = (selectedItem: GenericObject) => {
146
+ setSelected(selectedItem);
147
+ setFilterItem("");
148
+ setIsDropDownClosed(true);
149
+ onSelect && onSelect(selectedItem);
150
+ };
151
+
152
+ const handleWrapperClick = () => {
153
+ autocomplete && inputRef.current.focus();
154
+ toggleDropdown();
155
+ };
156
+
157
+ const handleBackspace = () => {
158
+ setSelected({});
159
+ onSelect && onSelect(null);
160
+ setFocusedOptionIndex(-1);
161
+ };
162
+
163
+ const componentsToRender = prepareSubcomponents({
164
+ children,
165
+ hasTriggerSubcomponent,
166
+ hasContainerSubcomponent,
167
+ trigger,
168
+ container,
169
+ otherChildren,
170
+ dark
171
+ });
172
+
173
+
174
+ return (
175
+ <div {...ariaProps}
176
+ {...dataProps}
177
+ {...htmlProps}
178
+ className={classes}
179
+ id={id}
180
+ style={triggerRef ? { position: "absolute" } : { position: "relative" }}
222
181
  >
223
- {children ? (
224
- <>
225
- {componentsToRender.map((component, index) => (
226
- <React.Fragment key={index}>{component}</React.Fragment>
227
- ))}
228
- </>
229
- ) : (
230
- <>
231
- <DropdownTrigger />
232
- <DropdownContainer>
233
- {options &&
234
- options?.map((option: GenericObject) => (
235
- <Dropdown.Option key={option.id}
236
- option={option}
237
- />
238
- ))}
239
- </DropdownContainer>
240
- </>
241
- )}
182
+ <DropdownContext.Provider
183
+ value={{
184
+ autocomplete,
185
+ dropdownContainerRef,
186
+ filteredOptions,
187
+ filterItem,
188
+ focusedOptionIndex,
189
+ handleBackspace,
190
+ handleChange,
191
+ handleOptionClick,
192
+ handleWrapperClick,
193
+ inputRef,
194
+ inputWrapperRef,
195
+ isDropDownClosed,
196
+ isInputFocused,
197
+ options,
198
+ selected,
199
+ setFocusedOptionIndex,
200
+ setIsDropDownClosed,
201
+ setIsInputFocused,
202
+ setSelected,
203
+ toggleDropdown,
204
+ triggerRef
205
+ }}
206
+ >
207
+ {label &&
208
+ <Caption
209
+ dark={dark}
210
+ marginBottom="xs"
211
+ text={label}
212
+ />
213
+ }
214
+ <div className={`dropdown_wrapper ${error ? 'error' : ''}`}
215
+ onBlur={() => {
216
+ // Debounce to delay the execution to prevent jumpiness in Focus state
217
+ setTimeout(() => {
218
+ if (!dropdownRef.current.contains(document.activeElement)) {
219
+ setIsInputFocused(false);
220
+ }
221
+ }, 0);
222
+ }}
223
+ onFocus={() => setIsInputFocused(true)}
224
+ ref={dropdownRef}
225
+ >
226
+ {children ? (
227
+ <>
228
+ {componentsToRender.map((component, index) => (
229
+ <React.Fragment key={index}>{component}</React.Fragment>
230
+ ))}
231
+ </>
232
+ ) : (
233
+ <>
234
+ <DropdownTrigger />
235
+ <DropdownContainer>
236
+ {options &&
237
+ options?.map((option: GenericObject) => (
238
+ <Dropdown.Option key={option.id}
239
+ option={option}
240
+ />
241
+ ))}
242
+ </DropdownContainer>
243
+ </>
244
+ )}
245
+
246
+ {error &&
247
+ <Body
248
+ status="negative"
249
+ text={error}
250
+ />
251
+ }
252
+ </div>
253
+ </DropdownContext.Provider>
242
254
  </div>
243
- </DropdownContext.Provider>
244
- </div>
245
- )
255
+ )
246
256
  };
247
257
 
248
258
  Dropdown.Option = DropdownOption;
@@ -0,0 +1,9 @@
1
+ <%
2
+ options = [
3
+ { label: 'United States', value: 'United States', id: 'us' },
4
+ { label: 'Canada', value: 'Canada', id: 'ca' },
5
+ { label: 'Pakistan', value: 'Pakistan', id: 'pk' },
6
+ ]
7
+ %>
8
+
9
+ <%= pb_rails("dropdown", props: { error: "Please make a valid selection", options: options }) %>
@@ -0,0 +1,34 @@
1
+ import React, { useState } from 'react'
2
+ import { Dropdown } from '../../'
3
+
4
+ const DropdownError = (props) => {
5
+ const [selectedOption, setSelectedOption] = useState()
6
+ const error = selectedOption?.value ? null : "Please make a valid selection"
7
+ const options = [
8
+ {
9
+ label: "United States",
10
+ value: "United States",
11
+ },
12
+ {
13
+ label: "Canada",
14
+ value: "Canada",
15
+ },
16
+ {
17
+ label: "Pakistan",
18
+ value: "Pakistan",
19
+ }
20
+ ]
21
+
22
+ return (
23
+ <>
24
+ <Dropdown
25
+ error={error}
26
+ onSelect={(selectedItem) => setSelectedOption(selectedItem)}
27
+ options={options}
28
+ {...props}
29
+ />
30
+ </>
31
+ )
32
+ }
33
+
34
+ export default DropdownError
@@ -7,6 +7,7 @@ examples:
7
7
  - dropdown_with_custom_display_rails: Custom Display
8
8
  - dropdown_with_custom_trigger_rails: Custom Trigger
9
9
  - dropdown_with_custom_padding: Custom Option Padding
10
+ - dropdown_error: Dropdown with Error
10
11
 
11
12
  react:
12
13
  - dropdown_default: Default
@@ -16,6 +17,7 @@ examples:
16
17
  - dropdown_with_custom_display: Custom Display
17
18
  - dropdown_with_custom_trigger: Custom Trigger
18
19
  - dropdown_with_custom_padding: Custom Option Padding
20
+ - dropdown_error: Dropdown with Error
19
21
  # - dropdown_with_autocomplete: Autocomplete
20
22
  # - dropdown_with_autocomplete_and_custom_display: Autocomplete with Custom Display
21
23
  # - dropdown_with_external_control: useDropdown Hook
@@ -8,4 +8,5 @@ export { default as DropdownWithCustomPadding } from './_dropdown_with_custom_pa
8
8
  export { default as DropdownWithLabel } from './_dropdown_with_label.jsx'
9
9
  export { default as DropdownWithExternalControl } from './_dropdown_with_external_control.jsx'
10
10
  export { default as DropdownWithHook } from './_dropdown_with_hook.jsx'
11
- export { default as DropdownSubcomponentStructure } from './_dropdown_subcomponent_structure.jsx'
11
+ export { default as DropdownSubcomponentStructure } from './_dropdown_subcomponent_structure.jsx'
12
+ export { default as DropdownError } from './_dropdown_error.jsx'
@@ -7,20 +7,22 @@
7
7
  <% if object.label.present? %>
8
8
  <%= pb_rails("caption", props: {text: object.label, margin_bottom:"xs"}) %>
9
9
  <% end %>
10
- <div class="dropdown_wrapper" style="position: relative">
11
- <input type="hidden" name="<%= object.name %>" id="dropdown-selected-option" value=""/>
12
- <% if content.present? %>
13
- <%= content.presence %>
14
- <% else %>
15
- <%= pb_rails("dropdown/dropdown_trigger") %>
16
- <%= pb_rails("dropdown/dropdown_container") do %>
17
- <% if object.options.present? %>
18
- <% object.options.each do |option| %>
19
- <%= pb_rails("dropdown/dropdown_option", props: {option: option}) %>
10
+ <div class="dropdown_wrapper<%= error_class %>" style="position: relative">
11
+ <input type="hidden" name="<%= object.name %>" id="dropdown-selected-option" value=""/>
12
+ <% if content.present? %>
13
+ <%= content.presence %>
14
+ <%= pb_rails("body", props: { status: "negative", text: object.error }) %>
15
+ <% else %>
16
+ <%= pb_rails("dropdown/dropdown_trigger") %>
17
+ <%= pb_rails("dropdown/dropdown_container") do %>
18
+ <% if object.options.present? %>
19
+ <% object.options.each do |option| %>
20
+ <%= pb_rails("dropdown/dropdown_option", props: {option: option}) %>
21
+ <% end %>
20
22
  <% end %>
21
23
  <% end %>
24
+
25
+ <%= pb_rails("body", props: { status: "negative", text: object.error }) %>
22
26
  <% end %>
23
- <% end %>
24
27
  </div>
25
28
  <% end %>
26
-
@@ -7,6 +7,7 @@ module Playbook
7
7
  default: []
8
8
  prop :label, type: Playbook::Props::String
9
9
  prop :name, type: Playbook::Props::String
10
+ prop :error, type: Playbook::Props::String
10
11
 
11
12
  def data
12
13
  Hash(prop(:data)).merge(pb_dropdown: true)
@@ -15,6 +16,12 @@ module Playbook
15
16
  def classname
16
17
  generate_classname("pb_dropdown")
17
18
  end
19
+
20
+ private
21
+
22
+ def error_class
23
+ error.present? ? " error" : ""
24
+ end
18
25
  end
19
26
  end
20
27
  end
@@ -170,7 +170,7 @@ test('generated custom Trigger', () => {
170
170
  options={options}
171
171
  >
172
172
  <Dropdown.Trigger>
173
- <Icon icon="home" />
173
+ <Icon icon="elephant" />
174
174
  </Dropdown.Trigger>
175
175
  {options.map((option) => (
176
176
  <Dropdown.Option key={option.id}
@@ -182,7 +182,7 @@ test('generated custom Trigger', () => {
182
182
 
183
183
  const kit = screen.getByTestId(testId)
184
184
  const trigger = kit.querySelector('.pb_dropdown_trigger')
185
- const customTrigger = trigger.querySelector('.fa-home.pb_icon_kit.fa-fw')
185
+ const customTrigger = trigger.querySelector('.fa-elephant.pb_icon_kit.fa-fw')
186
186
  expect(customTrigger).toBeInTheDocument()
187
187
  })
188
188
 
@@ -204,4 +204,19 @@ test('selected option on click', () => {
204
204
  const option = kit.querySelector('.pb_dropdown_option_list')
205
205
  option.click()
206
206
  expect(option).toHaveClass('pb_dropdown_option_selected')
207
+ })
208
+
209
+ test('show error message', () => {
210
+ const errorMessage = 'Please make a valid selection'
211
+
212
+ render (
213
+ <Dropdown
214
+ data={{ testid: testId }}
215
+ error={errorMessage}
216
+ options={options}
217
+ />
218
+ )
219
+
220
+ const kit = screen.getByTestId(testId)
221
+ expect(kit).toHaveTextContent(errorMessage)
207
222
  })