playbook_ui 16.0.0.pre.alpha.PLAY2722advancedtableinlinerowloadingrails13598 → 16.0.0.pre.alpha.PLAY2744dropdownquickpickpt113758

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_background/docs/_background_responsive.jsx +30 -0
  3. data/app/pb_kits/playbook/pb_background/docs/_background_responsive.md +1 -0
  4. data/app/pb_kits/playbook/pb_background/docs/example.yml +1 -0
  5. data/app/pb_kits/playbook/pb_background/docs/index.js +1 -0
  6. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +3 -1
  7. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +34 -1
  8. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection_rails.md +3 -0
  9. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_blank_selection_react.md +3 -0
  10. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_clearable.html.erb +52 -0
  11. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_clearable.jsx +72 -0
  12. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_clearable.md +5 -0
  13. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_placeholder.html.erb +9 -0
  14. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_placeholder.jsx +33 -0
  15. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_placeholder.md +3 -0
  16. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +4 -0
  17. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +3 -1
  18. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +1 -1
  19. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +4 -0
  20. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +94 -0
  21. data/app/pb_kits/playbook/pb_dropdown/index.js +47 -3
  22. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +2 -1
  23. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +3 -1
  24. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_input_display.html.erb +74 -0
  25. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_input_display.jsx +87 -0
  26. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_input_display.md +3 -0
  27. data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +35 -33
  28. data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +1 -0
  29. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +33 -6
  30. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.jsx +35 -0
  31. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_required_indicator.md +3 -0
  32. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.html.erb +10 -0
  33. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.jsx +21 -0
  34. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_required_indicator.md +3 -0
  35. data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +3 -0
  36. data/app/pb_kits/playbook/pb_rich_text_editor/docs/index.js +2 -0
  37. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.rb +5 -0
  38. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.test.js +33 -18
  39. data/app/pb_kits/playbook/pb_table/docs/_sections.yml +68 -0
  40. data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +29 -11
  41. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_input_options.html.erb +39 -0
  42. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_input_options.md +3 -0
  43. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_required_indicator.html.erb +5 -0
  44. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_required_indicator.jsx +25 -0
  45. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_required_indicator.md +3 -0
  46. data/app/pb_kits/playbook/pb_textarea/docs/example.yml +4 -1
  47. data/app/pb_kits/playbook/pb_textarea/docs/index.js +1 -0
  48. data/app/pb_kits/playbook/pb_textarea/index.ts +12 -5
  49. data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +10 -10
  50. data/app/pb_kits/playbook/pb_textarea/textarea.rb +30 -0
  51. data/app/pb_kits/playbook/pb_textarea/textarea.test.js +18 -1
  52. data/app/pb_kits/playbook/utilities/test/globalProps/globalProps.integration.test.js +936 -0
  53. data/dist/chunks/{_pb_line_graph-hxi01lk7.js → _pb_line_graph-BgKF_zz1.js} +1 -1
  54. data/dist/chunks/{_typeahead-BgLnlhzP.js → _typeahead-B9a6ZsEP.js} +1 -1
  55. data/dist/chunks/{globalProps-DgYwLYNx.js → globalProps-BhVYCqRf.js} +1 -1
  56. data/dist/chunks/{lib-NLxTo8OB.js → lib-DD34ZrWL.js} +1 -1
  57. data/dist/chunks/vendor.js +2 -2
  58. data/dist/playbook-rails-react-bindings.js +1 -1
  59. data/dist/playbook-rails.js +1 -1
  60. data/dist/playbook.css +1 -1
  61. data/lib/playbook/version.rb +1 -1
  62. metadata +31 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad5a72672942b10c55a459ee056b0b5adebb0a967fe978116869a1d874d1671b
4
- data.tar.gz: c9a3b651ae3a916c2ccb06a629d7ac35a840ef96dc24adb65dc4529cd8f7782d
3
+ metadata.gz: 77d2aa38ac1c38d9be4fa1b827ac346255f9742aa6e4c4569845b3f8335c57cd
4
+ data.tar.gz: e1034bf5c9ea8bc54b2e4c606f68dc0c0beea877674ac459aa767232bbdab299
5
5
  SHA512:
6
- metadata.gz: 3745bb4d7dc1afdfedc43625d2ae41ac2d9c9a03ae357e9f7670ac3de0bb6340e58167f8da8bba19a393fb887b2ecd79b505af94583dd54e182ea62d2e301778
7
- data.tar.gz: 475688418a55f76517357a17d0052df9e467f6190b3ec54a6d5128fd541ce04bd06e11e78f54f964dfc710b9132abdf068ca336299787ee60b662e8e9b71c85a
6
+ metadata.gz: fa2c94e31f5bacca5366fcf5c4438b0f740d85df8cf642af32b63ff588bf4f8c5b31b3016e981a44143068349b3ef833d1bd0a72c920d67d6fce157c11378cce
7
+ data.tar.gz: f2a531ff9faca9f3524227c518482938e6c0ae648582befe352fcdfd3a11220c315b9a873e505ba8fd7bf4265b3d671c42d413606204405b76a74e35aa4dc4cb
@@ -0,0 +1,30 @@
1
+ import React from 'react'
2
+ import Background from '../../pb_background/_background'
3
+
4
+ const BackgroundResponsive = (props) => (
5
+ <>
6
+ <Background
7
+ alt="colorful background"
8
+ backgroundColor={{ xs: "primary", sm: "warning", md: "success", lg: "error", xl: "category_1" }}
9
+ className="background lazyload"
10
+ padding="xl"
11
+ {...props}
12
+ />
13
+ <br/>
14
+ <Background
15
+ alt="colorful background"
16
+ className="background lazyload"
17
+ imageUrl={{
18
+ xs: "https://unsplash.it/500/400/?image=633",
19
+ sm: "https://images.unsplash.com/photo-1528459801416-a9e53bbf4e17?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1000&q=80",
20
+ md: "https://unsplash.it/500/400/?image=633",
21
+ lg: "https://images.unsplash.com/photo-1528459801416-a9e53bbf4e17?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1000&q=80",
22
+ xl: "https://unsplash.it/500/400/?image=633"
23
+ }}
24
+ padding="xl"
25
+ {...props}
26
+ />
27
+ </>
28
+ )
29
+
30
+ export default BackgroundResponsive
@@ -0,0 +1 @@
1
+ The `backgroundColor`, `backgroundSize`, `backgroundPosition`, `backgroundRepeat`, and `imageUrl` props support responsive sizes. To use them, pass an object to the prop containing your values relative to responsive break points. To test this here, resize your browser window to responsively change these Backgrounds' `backgroundColor` and `imageUrl`.
@@ -20,3 +20,4 @@ examples:
20
20
  - background_category: Category
21
21
  - background_size: Size
22
22
  - background_overlay: Overlay
23
+ - background_responsive: Responsive
@@ -7,3 +7,4 @@ export { default as BackgroundStatusSubtle } from './_background_status_subtle.j
7
7
  export { default as BackgroundCategory } from './_background_category.jsx'
8
8
  export { default as BackgroundSize } from './_background_size.jsx'
9
9
  export { default as BackgroundOverlay } from './_background_overlay.jsx'
10
+ export { default as BackgroundResponsive } from './_background_responsive.jsx'
@@ -68,7 +68,9 @@
68
68
  .pb_dropdown_container {
69
69
  position: absolute;
70
70
  background-color: $white;
71
- overflow: hidden;
71
+ overflow-y: auto;
72
+ overflow-x: hidden;
73
+ max-height: 18em;
72
74
  box-shadow: $shadow_deep;
73
75
  border-radius: $border_rad_heavier;
74
76
  border: 1px solid $border_light;
@@ -36,6 +36,7 @@ type DropdownProps = {
36
36
  blankSelection?: string;
37
37
  children?: React.ReactChild[] | React.ReactChild | React.ReactElement[];
38
38
  className?: string;
39
+ clearable?: boolean;
39
40
  customQuickPickDates?: CustomQuickPickDates;
40
41
  formPillProps?: GenericObject;
41
42
  dark?: boolean;
@@ -49,6 +50,7 @@ type DropdownProps = {
49
50
  multiSelect?: boolean;
50
51
  onSelect?: (arg: GenericObject) => null;
51
52
  options?: GenericObject;
53
+ placeholder?: string;
52
54
  separators?: boolean;
53
55
  variant?: "default" | "subtle" | "quickpick";
54
56
  rangeEndsToday?: boolean;
@@ -74,6 +76,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
74
76
  blankSelection = '',
75
77
  children,
76
78
  className,
79
+ clearable = true,
77
80
  customQuickPickDates,
78
81
  dark = false,
79
82
  data = {},
@@ -87,6 +90,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
87
90
  formPillProps,
88
91
  onSelect,
89
92
  options,
93
+ placeholder,
90
94
  rangeEndsToday = false,
91
95
  controlsStartId,
92
96
  controlsEndId,
@@ -211,6 +215,34 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
211
215
  }
212
216
  }, [isDropDownClosed]);
213
217
 
218
+ // Auto-position dropdown above/below based on available space
219
+ useEffect(() => {
220
+ if (!isDropDownClosed && dropdownContainerRef.current) {
221
+ const container = dropdownContainerRef.current;
222
+ const wrapper = container.closest('.dropdown_wrapper') as HTMLElement;
223
+ if (!wrapper) return;
224
+
225
+ const wrapperRect = wrapper.getBoundingClientRect();
226
+ const h = container.getBoundingClientRect().height || container.scrollHeight;
227
+ const spaceBelow = window.innerHeight - wrapperRect.bottom;
228
+ const spaceAbove = wrapperRect.top;
229
+
230
+ // If not enough space below but enough space above, position above
231
+ if (spaceBelow < h + 10 && spaceAbove >= h + 10) {
232
+ container.style.top = "auto";
233
+ container.style.bottom = "calc(100% + 5px)";
234
+ container.style.marginTop = "0";
235
+ container.style.marginBottom = "0";
236
+ } else {
237
+ // Default: position below
238
+ container.style.top = "";
239
+ container.style.bottom = "";
240
+ container.style.marginTop = "";
241
+ container.style.marginBottom = "";
242
+ }
243
+ }
244
+ }, [isDropDownClosed, dropdownContainerRef]);
245
+
214
246
 
215
247
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
216
248
  setFilterItem(e.target.value);
@@ -375,6 +407,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
375
407
  value={{
376
408
  activeStyle,
377
409
  autocomplete,
410
+ clearable,
378
411
  dropdownContainerRef,
379
412
  filteredOptions,
380
413
  filterItem,
@@ -426,7 +459,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
426
459
  </>
427
460
  ) : (
428
461
  <>
429
- <DropdownTrigger />
462
+ <DropdownTrigger placeholder={placeholder} />
430
463
  <DropdownContainer>
431
464
  {optionsWithBlankSelection &&
432
465
  optionsWithBlankSelection?.map((option: GenericObject) => (
@@ -0,0 +1,3 @@
1
+ The `blank_selection` prop adds a blank option at the top of the dropdown options list. This allows users to explicitly clear their selection by choosing the blank option.
2
+
3
+ The blank selection option appears as the first item in the dropdown and has an empty value (`id: ""`, `value: ""`). When selected, it effectively clears the dropdown selection.
@@ -0,0 +1,3 @@
1
+ The `blankSelection` prop adds a blank option at the top of the dropdown options list. This allows users to explicitly clear their selection by choosing the blank option.
2
+
3
+ The blank selection option appears as the first item in the dropdown and has an empty value (`id: ""`, `value: ""`). When selected, it effectively clears the dropdown selection.
@@ -0,0 +1,52 @@
1
+ <%
2
+ options = [
3
+ { label: 'United States', value: 'unitedStates', id: 'us' },
4
+ { label: 'Canada', value: 'canada', id: 'ca' },
5
+ { label: 'Pakistan', value: 'pakistan', id: 'pk' },
6
+ ]
7
+ %>
8
+
9
+ <%= pb_rails("dropdown", props: {
10
+ id: "date-range-quickpick-reset-closeable",
11
+ label: "Quick Pick",
12
+ variant: "quickpick",
13
+ clearable: false
14
+ }) %>
15
+
16
+ <%= pb_rails("button", props: {
17
+ margin_y: "md",
18
+ text: "Reset",
19
+ html_options: {
20
+ onclick: "handleReset()"
21
+ }
22
+ }) %>
23
+
24
+ <%= pb_rails("dropdown", props: {
25
+ id: "closeable-default",
26
+ options: options,
27
+ clearable: false,
28
+ default_value: options.last,
29
+ margin_bottom: "md",
30
+ label: "Default"
31
+ }) %>
32
+
33
+ <%= pb_rails("dropdown", props: {
34
+ id: "closeable-subtle",
35
+ options: options,
36
+ clearable: false,
37
+ default_value: options.second,
38
+ variant: "subtle",
39
+ separators: false,
40
+ label: "Subtle"
41
+ }) %>
42
+
43
+ <script>
44
+ function handleReset() {
45
+ const dropdown = document.querySelector("#date-range-quickpick-reset-closeable[data-pb-dropdown]");
46
+ const instance = dropdown?._pbDropdownInstance;
47
+
48
+ if (instance) {
49
+ instance.clearSelection();
50
+ }
51
+ }
52
+ </script>
@@ -0,0 +1,72 @@
1
+ import React, { useRef } from 'react'
2
+
3
+ import Button from '../../pb_button/_button'
4
+ import Dropdown from '../../pb_dropdown/_dropdown'
5
+
6
+ const DropdownWithClearable = (props) => {
7
+ const dropdownRef = useRef(null)
8
+
9
+ const options = [
10
+ {
11
+ label: "United States",
12
+ value: "unitedStates",
13
+ id: "us"
14
+ },
15
+ {
16
+ label: "Canada",
17
+ value: "canada",
18
+ id: "ca"
19
+ },
20
+ {
21
+ label: "Pakistan",
22
+ value: "pakistan",
23
+ id: "pk"
24
+ }
25
+ ]
26
+
27
+ const handleReset = () => {
28
+ if (dropdownRef.current) {
29
+ dropdownRef.current.clearSelected()
30
+ }
31
+ }
32
+
33
+ return (
34
+ <>
35
+ <Dropdown
36
+ clearable={false}
37
+ label="Quick Pick"
38
+ onSelect={() => {}}
39
+ ref={dropdownRef}
40
+ variant="quickpick"
41
+ {...props}
42
+ />
43
+ <Button
44
+ marginY="md"
45
+ onClick={handleReset}
46
+ text="Reset"
47
+ />
48
+
49
+ <Dropdown
50
+ clearable={false}
51
+ defaultValue={options[options.length - 1]}
52
+ label="Default"
53
+ marginBottom="md"
54
+ options={options}
55
+ variant="default"
56
+ {...props}
57
+ />
58
+
59
+ <Dropdown
60
+ clearable={false}
61
+ defaultValue={options[1]}
62
+ label="Subtle"
63
+ options={options}
64
+ separators={false}
65
+ variant="subtle"
66
+ {...props}
67
+ />
68
+ </>
69
+ )
70
+ }
71
+
72
+ export default DropdownWithClearable
@@ -0,0 +1,5 @@
1
+ The `clearable` prop controls whether the clear (X) button appears in the dropdown. When set to `false`, the clear button is hidden.
2
+
3
+ This is useful in two scenarios:
4
+ 1. When you have a separate "Reset" or "Defaults" button that handles clearing the selection (as shown in the Quick Pick example)
5
+ 2. When you don't want to provide any way to clear the selection (as shown in the Default and Subtle examples)
@@ -0,0 +1,9 @@
1
+ <%
2
+ options = [
3
+ { label: 'United States', value: 'unitedStates', id: 'us' },
4
+ { label: 'Canada', value: 'canada', id: 'ca' },
5
+ { label: 'Pakistan', value: 'pakistan', id: 'pk' },
6
+ ]
7
+ %>
8
+
9
+ <%= pb_rails("dropdown", props: { options: options, placeholder: "Choose a country" }) %>
@@ -0,0 +1,33 @@
1
+ import React from 'react'
2
+ import Dropdown from '../../pb_dropdown/_dropdown'
3
+
4
+ const DropdownWithPlaceholder = (props) => {
5
+
6
+ const options = [
7
+ {
8
+ label: "United States",
9
+ value: "unitedStates",
10
+ id: "us"
11
+ },
12
+ {
13
+ label: "Canada",
14
+ value: "canada",
15
+ id: "ca"
16
+ },
17
+ {
18
+ label: "Pakistan",
19
+ value: "pakistan",
20
+ id: "pk"
21
+ }
22
+ ];
23
+
24
+ return (
25
+ <Dropdown
26
+ options={options}
27
+ placeholder="Choose a country"
28
+ {...props}
29
+ />
30
+ )
31
+ }
32
+
33
+ export default DropdownWithPlaceholder
@@ -0,0 +1,3 @@
1
+ The `placeholder` prop allows you to customize the placeholder text that appears when no option is selected in the dropdown.
2
+
3
+ The placeholder prop works with all dropdown variants (`default`, `subtle`, and `quickpick`). When no option is selected, the placeholder text is displayed. When an option is selected, the placeholder is replaced by the selected option's label. The default placeholder text is "Select..." if no placeholder is provided.
@@ -21,7 +21,9 @@ examples:
21
21
  - dropdown_default_value: Default Value
22
22
  - dropdown_multi_select_with_default: Multi Select Default Value
23
23
  - dropdown_blank_selection: Blank Selection
24
+ - dropdown_with_placeholder: Placeholder
24
25
  - dropdown_separators_hidden: Separators Hidden
26
+ - dropdown_with_clearable: Clearable
25
27
  - dropdown_quickpick_rails: Quick Pick Variant
26
28
  - dropdown_quickpick_range_end_rails: Quick Pick Variant (Range Ends Today)
27
29
  - dropdown_quickpick_default_dates: Quick Pick Variant (Default Dates)
@@ -52,7 +54,9 @@ examples:
52
54
  - dropdown_default_value: Default Value
53
55
  - dropdown_multi_select_with_default: Multi Select Default Value
54
56
  - dropdown_blank_selection: Blank Selection
57
+ - dropdown_with_placeholder: Placeholder
55
58
  - dropdown_clear_selection: Clear Selection
59
+ - dropdown_with_clearable: Clearable
56
60
  - dropdown_separators_hidden: Separators Hidden
57
61
  - dropdown_with_external_control: useDropdown Hook
58
62
  - dropdown_quickpick: Quick Pick Variant
@@ -11,6 +11,7 @@ export { default as DropdownSubcomponentStructure } from './_dropdown_subcompone
11
11
  export { default as DropdownError } from './_dropdown_error.jsx'
12
12
  export { default as DropdownDefaultValue } from './_dropdown_default_value.jsx'
13
13
  export { default as DropdownBlankSelection } from './_dropdown_blank_selection.jsx'
14
+ export { default as DropdownWithPlaceholder } from './_dropdown_with_placeholder.jsx'
14
15
  export { default as DropdownClearSelection } from './_dropdown_clear_selection.jsx'
15
16
  export { default as DropdownSubtleVariant } from './_dropdown_subtle_variant.jsx'
16
17
  export { default as DropdownSeparatorsHidden } from './_dropdown_separators_hidden.jsx'
@@ -27,4 +28,5 @@ export { default as DropdownQuickpick } from './_dropdown_quickpick.jsx'
27
28
  export { default as DropdownQuickpickRangeEnd } from './_dropdown_quickpick_range_end.jsx'
28
29
  export { default as DropdownQuickpickDefaultDates } from './_dropdown_quickpick_default_dates.jsx'
29
30
  export { default as DropdownQuickpickWithDatePickers } from './_dropdown_quickpick_with_date_pickers.jsx'
30
- export { default as DropdownQuickpickCustom } from './_dropdown_quickpick_custom.jsx'
31
+ export { default as DropdownQuickpickCustom } from './_dropdown_quickpick_custom.jsx'
32
+ export { default as DropdownWithClearable } from './_dropdown_with_clearable.jsx'
@@ -18,7 +18,7 @@
18
18
  <%= content.presence %>
19
19
  <%= pb_rails("body", props: { status: "negative", text: object.error }) %>
20
20
  <% else %>
21
- <%= pb_rails("dropdown/dropdown_trigger", props:{ autocomplete: object.autocomplete, multi_select:object.multi_select }) %>
21
+ <%= pb_rails("dropdown/dropdown_trigger", props:{ autocomplete: object.autocomplete, multi_select:object.multi_select, placeholder: object.placeholder }) %>
22
22
  <%= pb_rails("dropdown/dropdown_container", props: { searchbar: object.searchbar }) do %>
23
23
  <% if options_with_blank.present? %>
24
24
  <% options_with_blank.each do |option| %>
@@ -36,6 +36,8 @@ module Playbook
36
36
  default: ""
37
37
  prop :controls_start_id, type: Playbook::Props::String,
38
38
  default: ""
39
+ prop :clearable, type: Playbook::Props::Boolean,
40
+ default: true
39
41
  prop :start_date_id, type: Playbook::Props::String,
40
42
  default: "start_date_id"
41
43
  prop :start_date_name, type: Playbook::Props::String,
@@ -44,12 +46,14 @@ module Playbook
44
46
  default: "end_date_id"
45
47
  prop :end_date_name, type: Playbook::Props::String,
46
48
  default: "end_date_name"
49
+ prop :placeholder, type: Playbook::Props::String
47
50
 
48
51
  def data
49
52
  Hash(prop(:data)).merge(
50
53
  pb_dropdown: true,
51
54
  pb_dropdown_multi_select: multi_select,
52
55
  pb_dropdown_variant: variant,
56
+ pb_dropdown_clearable: clearable,
53
57
  form_pill_props: form_pill_props.to_json,
54
58
  start_date_id: variant == "quickpick" ? start_date_id : nil,
55
59
  end_date_id: variant == "quickpick" ? end_date_id : nil,
@@ -122,6 +122,80 @@ test('generated placeholder prop', () => {
122
122
 
123
123
  })
124
124
 
125
+ test('placeholder prop passed directly to Dropdown', () => {
126
+ render(
127
+ <Dropdown
128
+ data={{ testid: testId }}
129
+ options={options}
130
+ placeholder="Choose a country"
131
+ />
132
+ )
133
+
134
+ const kit = screen.getByTestId(testId)
135
+ const trigger = kit.querySelector('.pb_dropdown_trigger')
136
+ expect(trigger).toHaveTextContent('Choose a country')
137
+ })
138
+
139
+ test('placeholder works with default variant', () => {
140
+ render(
141
+ <Dropdown
142
+ data={{ testid: testId }}
143
+ options={options}
144
+ placeholder="Select an option"
145
+ variant="default"
146
+ />
147
+ )
148
+
149
+ const kit = screen.getByTestId(testId)
150
+ const trigger = kit.querySelector('.pb_dropdown_trigger')
151
+ expect(trigger).toHaveTextContent('Select an option')
152
+ })
153
+
154
+ test('placeholder works with subtle variant', () => {
155
+ render(
156
+ <Dropdown
157
+ data={{ testid: testId }}
158
+ options={options}
159
+ placeholder="Pick an option"
160
+ separators={false}
161
+ variant="subtle"
162
+ />
163
+ )
164
+
165
+ const kit = screen.getByTestId(testId)
166
+ expect(kit).toHaveClass('pb_dropdown_subtle_separators_hidden')
167
+ const trigger = kit.querySelector('.pb_dropdown_trigger')
168
+ expect(trigger).toHaveTextContent('Pick an option')
169
+ })
170
+
171
+ test('placeholder works with quickpick variant', () => {
172
+ render(
173
+ <Dropdown
174
+ data={{ testid: testId }}
175
+ placeholder="Select a date range"
176
+ variant="quickpick"
177
+ />
178
+ )
179
+
180
+ const kit = screen.getByTestId(testId)
181
+ expect(kit).toHaveClass('pb_dropdown_quickpick')
182
+ const trigger = kit.querySelector('.pb_dropdown_trigger')
183
+ expect(trigger).toHaveTextContent('Select a date range')
184
+ })
185
+
186
+ test('placeholder shows default "Select..." when not provided', () => {
187
+ render(
188
+ <Dropdown
189
+ data={{ testid: testId }}
190
+ options={options}
191
+ />
192
+ )
193
+
194
+ const kit = screen.getByTestId(testId)
195
+ const trigger = kit.querySelector('.pb_dropdown_trigger')
196
+ expect(trigger).toHaveTextContent('Select...')
197
+ })
198
+
125
199
  test('generated label prop', () => {
126
200
  render (
127
201
  <Dropdown
@@ -466,7 +540,27 @@ test("quickpick clears selection when clicking X icon", () => {
466
540
  expect(trigger).toHaveTextContent("Select...")
467
541
  })
468
542
 
543
+ test("quickpick hides clear icon when clearable is false", () => {
544
+ render(
545
+ <Dropdown
546
+ clearable={false}
547
+ data={{ testid: testId }}
548
+ defaultValue="This Week"
549
+ variant="quickpick"
550
+ />
551
+ )
552
+
553
+ const kit = screen.getByTestId(testId)
554
+ const trigger = kit.querySelector('.pb_dropdown_trigger')
555
+
556
+ expect(trigger).toHaveTextContent("This Week")
557
+
558
+ const clearIcon = kit.querySelector('[aria-label="times icon"]')
559
+ expect(clearIcon).not.toBeInTheDocument()
560
+ })
561
+
469
562
  test("quickpick returns date array values when option selected", () => {
563
+ // eslint-disable-next-line react/no-multi-comp
470
564
  const TestComponent = () => {
471
565
  const [selected, setSelected] = useState(null)
472
566
  return (
@@ -48,6 +48,7 @@ export default class PbDropdown extends PbEnhancedElement {
48
48
  this.updatePills();
49
49
 
50
50
  this.clearBtn = this.element.querySelector(CLEAR_ICON_SELECTOR);
51
+ this.isClearable = this.element.dataset.pbDropdownClearable !== "false";
51
52
  if (this.clearBtn) {
52
53
  this.clearBtn.style.display = "none";
53
54
  this.clearBtn.addEventListener("click", (e) => {
@@ -60,6 +61,10 @@ export default class PbDropdown extends PbEnhancedElement {
60
61
 
61
62
  updateClearButton() {
62
63
  if (!this.clearBtn) return;
64
+ if (!this.isClearable) {
65
+ this.clearBtn.style.display = "none";
66
+ return;
67
+ }
63
68
  const hasSelection = this.isMultiSelect
64
69
  ? this.selectedOptions.size > 0
65
70
  : Boolean(this.element.querySelector(DROPDOWN_INPUT).value);
@@ -111,13 +116,43 @@ export default class PbDropdown extends PbEnhancedElement {
111
116
  const el = this.target;
112
117
  el.style.height = "auto";
113
118
  requestAnimationFrame(() => {
114
- const newHeight = el.scrollHeight + "px";
119
+ // Calculate 18em in pixels (matches SCSS max-height: 18em)
120
+ const fontSize = parseFloat(getComputedStyle(el).fontSize) || 16;
121
+ const maxHeight = fontSize * 18;
122
+ const scrollHeight = el.scrollHeight;
123
+ const newHeight = Math.min(scrollHeight, maxHeight);
115
124
  el.offsetHeight; // force reflow
116
- el.style.height = newHeight;
125
+ el.style.height = newHeight + "px";
117
126
  });
118
127
  }
119
128
  }
120
129
 
130
+ adjustDropdownPosition(container) {
131
+ if (!container) return;
132
+
133
+ const wrapper = this.element.querySelector(".dropdown_wrapper");
134
+ if (!wrapper) return;
135
+
136
+ const wrapperRect = wrapper.getBoundingClientRect();
137
+ const h = container.getBoundingClientRect().height || container.scrollHeight;
138
+ const spaceBelow = window.innerHeight - wrapperRect.bottom;
139
+ const spaceAbove = wrapperRect.top;
140
+
141
+ // If not enough space below but enough space above, position above
142
+ if (spaceBelow < h + 10 && spaceAbove >= h + 10) {
143
+ container.style.top = "auto";
144
+ container.style.bottom = "calc(100% + 5px)";
145
+ container.style.marginTop = "0";
146
+ container.style.marginBottom = "0";
147
+ } else {
148
+ // Default: position below
149
+ container.style.top = "";
150
+ container.style.bottom = "";
151
+ container.style.marginTop = "";
152
+ container.style.marginBottom = "";
153
+ }
154
+ }
155
+
121
156
  handleSearch(term = "") {
122
157
  const lcTerm = term.toLowerCase();
123
158
  let hasMatch = false
@@ -365,7 +400,16 @@ export default class PbDropdown extends PbEnhancedElement {
365
400
  showElement(elem) {
366
401
  elem.classList.remove("close");
367
402
  elem.classList.add("open");
368
- elem.style.height = elem.scrollHeight + "px";
403
+
404
+ // Calculate height respecting max-height constraint (18em)
405
+ const fontSize = parseFloat(getComputedStyle(elem).fontSize) || 16;
406
+ const maxHeight = fontSize * 18; // matches SCSS max-height: 18em
407
+ const scrollHeight = elem.scrollHeight;
408
+ const height = Math.min(scrollHeight, maxHeight);
409
+ elem.style.height = height + "px";
410
+
411
+ // Auto-position dropdown above if not enough space below
412
+ this.adjustDropdownPosition(elem);
369
413
  }
370
414
 
371
415
  hideElement(elem) {
@@ -44,6 +44,7 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
44
44
 
45
45
  const {
46
46
  autocomplete,
47
+ clearable,
47
48
  filterItem,
48
49
  handleBackspace,
49
50
  handleChange,
@@ -225,7 +226,7 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
225
226
  key={`${isDropDownClosed ? "chevron-down" : "chevron-up"}`}
226
227
  >
227
228
  {
228
- selectedArray.length > 0 && (
229
+ clearable !== false && selectedArray.length > 0 && (
229
230
  <div onClick={(e)=>{e.stopPropagation();handleBackspace()}}>
230
231
  <Icon
231
232
  cursor="pointer"
@@ -1,6 +1,8 @@
1
1
  <%= pb_form_with(scope: :example, url: "", method: :get, validate: true) do |form| %>
2
2
  <%= form.text_field :example_text_field, props: { label: true, required: true, required_indicator: true } %>
3
3
  <%= form.text_field :example_text_field_2, props: { label: "Text Field Custom Label", required: true, required_indicator: true } %>
4
+ <%= form.text_area :example_text_area, props: { label: true, required: true, required_indicator: true } %>
5
+ <%= form.text_area :example_text_area_2, props: { label: "Textarea Custom Label", required: true, required_indicator: true } %>
4
6
  <%= form.email_field :example_email_field, props: { label: true, required: true, required_indicator: true } %>
5
7
  <%= form.number_field :example_number_field, props: { label: true, required: true, required_indicator: true } %>
6
8
  <%= form.search_field :example_search_field, props: { label: true, required: true, required_indicator: true } %>
@@ -11,4 +13,4 @@
11
13
  <%= action.submit %>
12
14
  <%= action.button props: { type: "reset", text: "Cancel", variant: "secondary" } %>
13
15
  <% end %>
14
- <% end %>
16
+ <% end %>