playbook_ui 17.1.0 → 17.2.0.pre.rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +32 -0
  3. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +18 -9
  4. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options.html.erb +34 -0
  5. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options.md +4 -0
  6. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options_with_autocomplete.html.erb +35 -0
  7. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options_with_autocomplete.md +1 -0
  8. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options_with_multi_select.html.erb +35 -0
  9. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options_with_multi_select.md +1 -0
  10. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_react_hook.jsx +45 -0
  11. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_react_hook.md +1 -0
  12. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_react_hook.jsx +36 -0
  13. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_react_hook.md +1 -0
  14. data/app/pb_kits/playbook/pb_dropdown/docs/_playground.json +2 -0
  15. data/app/pb_kits/playbook/pb_dropdown/docs/_playground.overrides.json +2 -1
  16. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +6 -0
  17. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +2 -0
  18. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +13 -1
  19. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +205 -2
  20. data/app/pb_kits/playbook/pb_dropdown/dropdown_index.test.js +372 -0
  21. data/app/pb_kits/playbook/pb_dropdown/index.js +240 -1
  22. data/app/pb_kits/playbook/pb_dropdown/kit.schema.json +40 -6
  23. data/app/pb_kits/playbook/pb_dropdown/subcomponents/MultiSelectTriggerDisplay.tsx +2 -2
  24. data/app/pb_kits/playbook/pb_file_upload/_file_upload.scss +5 -0
  25. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_custom_message.html.erb +32 -0
  26. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_custom_message_rails.md +3 -0
  27. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_input_options.html.erb +3 -0
  28. data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_input_options.md +1 -0
  29. data/app/pb_kits/playbook/pb_file_upload/docs/_playground.json +1 -0
  30. data/app/pb_kits/playbook/pb_file_upload/docs/example.yml +2 -0
  31. data/app/pb_kits/playbook/pb_file_upload/file_upload.html.erb +14 -5
  32. data/app/pb_kits/playbook/pb_file_upload/file_upload.rb +3 -0
  33. data/app/pb_kits/playbook/pb_file_upload/kit.schema.json +4 -2
  34. data/app/pb_kits/playbook/pb_icon/docs/_icon_css_api.html.erb +3 -0
  35. data/app/pb_kits/playbook/pb_icon/docs/_icon_css_api.jsx +13 -0
  36. data/app/pb_kits/playbook/pb_icon/docs/_icon_css_api.md +1 -0
  37. data/app/pb_kits/playbook/pb_icon/docs/example.yml +2 -0
  38. data/app/pb_kits/playbook/pb_icon/docs/index.js +1 -0
  39. data/app/pb_kits/playbook/pb_link/_link.tsx +12 -2
  40. data/app/pb_kits/playbook/pb_link/docs/_link_rel.html.erb +16 -0
  41. data/app/pb_kits/playbook/pb_link/docs/_link_rel.jsx +30 -0
  42. data/app/pb_kits/playbook/pb_link/docs/_link_rel.md +3 -0
  43. data/app/pb_kits/playbook/pb_link/docs/_playground.json +6 -2
  44. data/app/pb_kits/playbook/pb_link/docs/_playground.overrides.json +2 -1
  45. data/app/pb_kits/playbook/pb_link/docs/example.yml +2 -0
  46. data/app/pb_kits/playbook/pb_link/docs/index.js +2 -1
  47. data/app/pb_kits/playbook/pb_link/kit.schema.json +7 -0
  48. data/app/pb_kits/playbook/pb_link/link.html.erb +2 -2
  49. data/app/pb_kits/playbook/pb_link/link.rb +7 -0
  50. data/app/pb_kits/playbook/pb_link/link.test.jsx +29 -0
  51. data/dist/chunks/_detail-B4-co8rv.js +5 -0
  52. data/dist/chunks/{_pb_line_graph-CUJ7NJIr.js → _pb_line_graph-BGyCJjgG.js} +1 -1
  53. data/dist/chunks/_table-SGw4kUye.js +1 -0
  54. data/dist/chunks/_typeahead-DeIqOMbJ.js +1 -0
  55. data/dist/chunks/{globalProps-DDcpyjvz.js → globalProps-K1DCn3il.js} +1 -1
  56. data/dist/chunks/lib-7vm9BMqp.js +29 -0
  57. data/dist/chunks/{time_picker_helper-B_FN8E9Y.js → time_picker_helper-BZutIzB3.js} +1 -1
  58. data/dist/chunks/vendor.js +2 -2
  59. data/dist/playbook-rails-react-bindings.js +1 -1
  60. data/dist/playbook-rails.js +1 -1
  61. data/dist/playbook.css +1 -1
  62. data/lib/playbook/version.rb +2 -2
  63. metadata +35 -10
  64. data/dist/chunks/_detail-CyTyC7gJ.js +0 -5
  65. data/dist/chunks/_table-DDKdCER5.js +0 -1
  66. data/dist/chunks/_typeahead-DdVBwRQT.js +0 -1
  67. data/dist/chunks/lib-D57mygNT.js +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b282160685aeb7935309b54bddb2856ff20b4957a8ca3455a268ab52693bb8e7
4
- data.tar.gz: 31b055f1c7c84e23d99b25cb980a5ad370f53088e265308ccd4e72a0b74500ba
3
+ metadata.gz: 02e9d48053efc7bc4dde6c77d9b396a21e3b35f8343a35eb334f816c5e7423f8
4
+ data.tar.gz: a275c089998b99dfd64fb86754fd9db545214fb12bde587411e8100cedb1bf88
5
5
  SHA512:
6
- metadata.gz: ce34582a3e72b462fa81e781a464f4f1352aaad7a37ffbace981816b5ec899397a6c29d8aa487af44d0fe20bda2f2d8280574bbf5a0a31548eed4c8e576976d8
7
- data.tar.gz: 41690ad445b535cab92bb9a8c7a1e3c23e382b2e3661def036d7b85b16b7d2f6ec5c4419b22cdabdfc96f289231be50a4241d583e716a038a19efd82dfd63b3d
6
+ metadata.gz: d0dc9ada45182473c6d16c6f04b3a961ab99dfc02f8c476ed274ffec5ae0b44df9836fc598b123a44cf5f02d43d420efd63ca5253ef4fa18b20835b23eada81d
7
+ data.tar.gz: e794e5364b4ac28a62091f95be02f5ed01e8c5108fbfd86ee70cd11bee5d58241688ec797a8c2576ba591f7379cbfb2ff9a8ebe7aad5834e1e5d5f2ccfd7b599
@@ -1076,6 +1076,30 @@
1076
1076
  }
1077
1077
  }
1078
1078
 
1079
+ // Firefox-specific fix for vertical borders disappearing upon row hover
1080
+ .pb_table.vertical-border .pb_advanced_table_body {
1081
+ tr:not(.virtualized-table-row) td:not(:last-child):not(.last-cell),
1082
+ .pb_table_tr:not(.virtualized-table-row) .pb_table_td:not(:last-child):not(.last-cell) {
1083
+ position: relative;
1084
+ border-right-color: transparent !important;
1085
+
1086
+ &::before {
1087
+ content: "";
1088
+ position: absolute;
1089
+ top: 0;
1090
+ right: -1px;
1091
+ bottom: -1px;
1092
+ width: 1px;
1093
+ background-color: var(--table-border-color);
1094
+ }
1095
+
1096
+ // The base kit gives the first column's divider the column group color
1097
+ &:first-child::before {
1098
+ background-color: var(--column-border-color);
1099
+ }
1100
+ }
1101
+ }
1102
+
1079
1103
  &[class*="column-group-border-"] {
1080
1104
  .pb_advanced_table_header {
1081
1105
  th[colspan]:not([colspan="1"]):not(:last-child),
@@ -1122,6 +1146,14 @@
1122
1146
  }
1123
1147
  }
1124
1148
 
1149
+ // --table-border-color stays light in dark mode, matches token of base kit's dark vertical border
1150
+ .pb_table.vertical-border .pb_advanced_table_body {
1151
+ tr:not(.virtualized-table-row) td:not(:last-child):not(.last-cell)::before,
1152
+ .pb_table_tr:not(.virtualized-table-row) .pb_table_td:not(:last-child):not(.last-cell)::before {
1153
+ background-color: $border_dark;
1154
+ }
1155
+ }
1156
+
1125
1157
  &[class*="column-group-border-"] {
1126
1158
  .pb_advanced_table_header {
1127
1159
  th[colspan]:not([colspan="1"]):not(:last-child),
@@ -112,6 +112,8 @@ type DropdownProps = {
112
112
  isClosed?: boolean;
113
113
  label?: string;
114
114
  multiSelect?: boolean;
115
+ name?: string;
116
+ onChange?: (event: { target: { name?: string; value: any } }) => void;
115
117
  onSelect?: (arg: GenericObject) => null;
116
118
  options?: GenericObject;
117
119
  placeholder?: string;
@@ -156,6 +158,8 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
156
158
  label,
157
159
  multiSelect = false,
158
160
  formPillProps,
161
+ name,
162
+ onChange,
159
163
  onSelect,
160
164
  options,
161
165
  placeholder,
@@ -414,6 +418,10 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
414
418
  setIsDropDownClosed(false);
415
419
  };
416
420
 
421
+ const handleSelectionChange = (value: any) => {
422
+ onSelect && onSelect(value);
423
+ onChange && onChange({ target: { name, value } });
424
+ };
417
425
 
418
426
  const handleOptionClick = (clickedItem: GenericObject) => {
419
427
  if (disabled) return;
@@ -426,7 +434,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
426
434
  const next = exists
427
435
  ? list.filter((option) => option.value !== clickedItem.value)
428
436
  : [...list, clickedItem];
429
- onSelect && onSelect(next);
437
+ handleSelectionChange(next);
430
438
  return next;
431
439
  });
432
440
  setFilterItem("");
@@ -439,7 +447,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
439
447
  if (shouldCloseOnClick) {
440
448
  setIsDropDownClosed(true);
441
449
  }
442
- onSelect && onSelect(clickedItem);
450
+ handleSelectionChange(clickedItem);
443
451
 
444
452
  // Sync with DatePickers if this is a quickpick variant
445
453
  if (variant === "quickpick" && Array.isArray(clickedItem.value)) {
@@ -468,10 +476,10 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
468
476
  if (disabled) return;
469
477
  if (multiSelect) {
470
478
  setSelected([]);
471
- onSelect && onSelect([]);
479
+ handleSelectionChange([]);
472
480
  } else {
473
481
  setSelected({});
474
- onSelect && onSelect(null);
482
+ handleSelectionChange(null);
475
483
  setFocusedOptionIndex(-1);
476
484
  setFilterItem("");
477
485
 
@@ -505,10 +513,10 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
505
513
  clearSelected: () => {
506
514
  if (multiSelect) {
507
515
  setSelected([]);
508
- onSelect && onSelect([]);
516
+ handleSelectionChange([]);
509
517
  } else {
510
518
  setSelected({});
511
- onSelect && onSelect(null);
519
+ handleSelectionChange(null);
512
520
  }
513
521
  setFilterItem("");
514
522
  setIsDropDownClosed(true);
@@ -522,16 +530,16 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
522
530
  clearSelected: () => {
523
531
  if (multiSelect) {
524
532
  setSelected([]);
525
- onSelect && onSelect([]);
533
+ handleSelectionChange([]);
526
534
  } else {
527
535
  setSelected({});
528
- onSelect && onSelect(null);
536
+ handleSelectionChange(null);
529
537
  }
530
538
  setFilterItem("");
531
539
  setIsDropDownClosed(true);
532
540
  },
533
541
  };
534
- }, [multiSelect, onSelect, setSelected, setFilterItem, setIsDropDownClosed]);
542
+ }, [multiSelect, handleSelectionChange, setSelected, setFilterItem, setIsDropDownClosed]);
535
543
 
536
544
  useImperativeHandle(ref, () => imperativeRef.current);
537
545
 
@@ -594,6 +602,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
594
602
  handleBackspace,
595
603
  handleChange,
596
604
  handleOptionClick,
605
+ handleSelectionChange,
597
606
  handleWrapperClick,
598
607
  inputRef,
599
608
  inputWrapperRef,
@@ -0,0 +1,34 @@
1
+ <%= pb_rails("select", props: {
2
+ id: "color_context_dropdown",
3
+ label: "Choose a Color",
4
+ name: "color_name",
5
+ options: [
6
+ { value: "red", value_text: "Red" },
7
+ { value: "blue", value_text: "Blue" },
8
+ { value: "green", value_text: "Green" },
9
+ ],
10
+ }) %>
11
+
12
+ <%= pb_rails("dropdown", props: {
13
+ id: "dropdown-dynamic-options",
14
+ label: "Pick a Shade",
15
+ name: "shade_name",
16
+ context_selector: "color_context_dropdown",
17
+ options_by_context: {
18
+ "red" => [
19
+ { id: "scarlet", label: "Scarlet", value: "scarlet" },
20
+ { id: "mahogany", label: "Mahogany", value: "mahogany" },
21
+ { id: "crimson", label: "Crimson", value: "crimson" },
22
+ ],
23
+ "blue" => [
24
+ { id: "sky", label: "Sky Blue", value: "sky" },
25
+ { id: "cerulean", label: "Cerulean", value: "cerulean" },
26
+ { id: "navy", label: "Navy", value: "navy" },
27
+ ],
28
+ "green" => [
29
+ { id: "emerald", label: "Emerald", value: "emerald" },
30
+ { id: "mint", label: "Mint", value: "mint" },
31
+ { id: "olive", label: "Olive", value: "olive" },
32
+ ],
33
+ },
34
+ }) %>
@@ -0,0 +1,4 @@
1
+ You can set up a dropdown to update its options dynamically based on another input. To achieve this:
2
+ - Give the dropdown a unique `id` so it can be targeted by events and linked to a controlling input.
3
+ - Use `context_selector` to point at the controlling select’s `id`. On connect, and whenever that select’s value changes, the Dropdown reads the current value as its context.
4
+ - Use `options_by_context` to pass a hash of option lists. Keys must match the possible values of the controlling select; each key maps to an array of `{ id, label, value }` options that replace the Dropdown’s options for that context.
@@ -0,0 +1,35 @@
1
+ <%= pb_rails("select", props: {
2
+ id: "color_context_dropdown_autocomplete",
3
+ label: "Choose a Color",
4
+ name: "color_name_autocomplete",
5
+ options: [
6
+ { value: "red", value_text: "Red" },
7
+ { value: "blue", value_text: "Blue" },
8
+ { value: "green", value_text: "Green" },
9
+ ],
10
+ }) %>
11
+
12
+ <%= pb_rails("dropdown", props: {
13
+ id: "dropdown-dynamic-options-autocomplete",
14
+ label: "Pick a Shade",
15
+ name: "shade_name_autocomplete",
16
+ autocomplete: true,
17
+ context_selector: "color_context_dropdown_autocomplete",
18
+ options_by_context: {
19
+ "red" => [
20
+ { id: "scarlet", label: "Scarlet", value: "scarlet" },
21
+ { id: "mahogany", label: "Mahogany", value: "mahogany" },
22
+ { id: "crimson", label: "Crimson", value: "crimson" },
23
+ ],
24
+ "blue" => [
25
+ { id: "sky", label: "Sky Blue", value: "sky" },
26
+ { id: "cerulean", label: "Cerulean", value: "cerulean" },
27
+ { id: "navy", label: "Navy", value: "navy" },
28
+ ],
29
+ "green" => [
30
+ { id: "emerald", label: "Emerald", value: "emerald" },
31
+ { id: "mint", label: "Mint", value: "mint" },
32
+ { id: "olive", label: "Olive", value: "olive" },
33
+ ],
34
+ },
35
+ }) %>
@@ -0,0 +1 @@
1
+ Dynamic options also work with `autocomplete`. Use the same `context_selector` and `options_by_context` setup as Dynamic Options, and set `autocomplete: true` so the Dropdown can be filtered by typing.
@@ -0,0 +1,35 @@
1
+ <%= pb_rails("select", props: {
2
+ id: "color_context_dropdown_multi",
3
+ label: "Choose a Color",
4
+ name: "color_name_multi",
5
+ options: [
6
+ { value: "red", value_text: "Red" },
7
+ { value: "blue", value_text: "Blue" },
8
+ { value: "green", value_text: "Green" },
9
+ ],
10
+ }) %>
11
+
12
+ <%= pb_rails("dropdown", props: {
13
+ id: "dropdown-dynamic-options-multi",
14
+ label: "Pick Shades",
15
+ name: "shade_names",
16
+ multi_select: true,
17
+ context_selector: "color_context_dropdown_multi",
18
+ options_by_context: {
19
+ "red" => [
20
+ { id: "scarlet", label: "Scarlet", value: "scarlet" },
21
+ { id: "mahogany", label: "Mahogany", value: "mahogany" },
22
+ { id: "crimson", label: "Crimson", value: "crimson" },
23
+ ],
24
+ "blue" => [
25
+ { id: "sky", label: "Sky Blue", value: "sky" },
26
+ { id: "cerulean", label: "Cerulean", value: "cerulean" },
27
+ { id: "navy", label: "Navy", value: "navy" },
28
+ ],
29
+ "green" => [
30
+ { id: "emerald", label: "Emerald", value: "emerald" },
31
+ { id: "mint", label: "Mint", value: "mint" },
32
+ { id: "olive", label: "Olive", value: "olive" },
33
+ ],
34
+ },
35
+ }) %>
@@ -0,0 +1 @@
1
+ Dynamic options also work with `multi_select`. Use the same `context_selector` and `options_by_context` setup as Dynamic Options, and set `multi_select: true` to allow selecting multiple options.
@@ -0,0 +1,45 @@
1
+ import React from 'react'
2
+
3
+ import Dropdown from '../_dropdown'
4
+ import Title from '../../pb_title/_title'
5
+ import { useForm } from 'react-hook-form'
6
+
7
+ const options = [
8
+ { label: 'United States', value: 'unitedStates', id: 'us' },
9
+ { label: 'United Kingdom', value: 'unitedKingdom', id: 'gb' },
10
+ { label: 'Canada', value: 'canada', id: 'ca' },
11
+ { label: 'Pakistan', value: 'pakistan', id: 'pk' },
12
+ { label: 'India', value: 'india', id: 'in' },
13
+ { label: 'Australia', value: 'australia', id: 'au' },
14
+ { label: 'New Zealand', value: 'new Zealand', id: 'nz' },
15
+ { label: 'Italy', value: 'italy', id: 'it' },
16
+ { label: 'Spain', value: 'spain', id: 'es' },
17
+ ]
18
+
19
+ const DropdownMultiSelectReactHook = (props) => {
20
+ const { register, watch } = useForm()
21
+
22
+ const selectedCountries = watch('countries')
23
+
24
+ return (
25
+ <>
26
+ <Dropdown
27
+ label="Countries"
28
+ multiSelect
29
+ options={options}
30
+ {...props}
31
+ {...register('countries')}
32
+ />
33
+ <Title
34
+ marginTop="sm"
35
+ size={4}
36
+ text="Selected Countries"
37
+ />
38
+ {selectedCountries && selectedCountries.map(country => (
39
+ <p key={country.id}>{`${country.label} - ${country.value}`}</p>
40
+ ))}
41
+ </>
42
+ )
43
+ }
44
+
45
+ export default DropdownMultiSelectReactHook
@@ -0,0 +1 @@
1
+ You can pass `react-hook-form` props to a multi-select Dropdown. Spread `register` onto Dropdown with `multiSelect` to keep the selected options array in form state.
@@ -0,0 +1,36 @@
1
+ import React from 'react'
2
+
3
+ import Dropdown from '../_dropdown'
4
+ import Title from '../../pb_title/_title'
5
+ import { useForm } from 'react-hook-form'
6
+
7
+ const options = [
8
+ { label: 'United States', value: 'unitedStates', id: 'us' },
9
+ { label: 'Canada', value: 'canada', id: 'ca' },
10
+ { label: 'Pakistan', value: 'pakistan', id: 'pk' },
11
+ ]
12
+
13
+ const DropdownReactHook = (props) => {
14
+ const { register, watch } = useForm()
15
+
16
+ const selectedCountry = watch('country')
17
+
18
+ return (
19
+ <>
20
+ <Dropdown
21
+ label="Countries"
22
+ options={options}
23
+ {...props}
24
+ {...register('country')}
25
+ />
26
+ <Title
27
+ marginTop="sm"
28
+ size={4}
29
+ text="Selected Country"
30
+ />
31
+ <p>{selectedCountry && `${selectedCountry.label} - ${selectedCountry.value}`}</p>
32
+ </>
33
+ )
34
+ }
35
+
36
+ export default DropdownReactHook
@@ -0,0 +1 @@
1
+ You can pass `react-hook-form` props to the Dropdown kit. Spread `register` onto a single-select Dropdown to keep the selected option in form state.
@@ -88,6 +88,7 @@
88
88
  "name": "Content",
89
89
  "props": [
90
90
  "label",
91
+ "name",
91
92
  "placeholder",
92
93
  "options",
93
94
  "defaultValue",
@@ -130,6 +131,7 @@
130
131
  {
131
132
  "name": "Events",
132
133
  "props": [
134
+ "onChange",
133
135
  "onSelect"
134
136
  ]
135
137
  }
@@ -70,6 +70,7 @@
70
70
  "name": "Content",
71
71
  "props": [
72
72
  "label",
73
+ "name",
73
74
  "placeholder",
74
75
  "options",
75
76
  "defaultValue",
@@ -111,7 +112,7 @@
111
112
  },
112
113
  {
113
114
  "name": "Events",
114
- "props": ["onSelect"]
115
+ "props": ["onChange", "onSelect"]
115
116
  }
116
117
  ],
117
118
  "presets": [
@@ -39,6 +39,9 @@ examples:
39
39
  - dropdown_disabled: Disabled Input
40
40
  - dropdown_grouped_options: Grouped Options
41
41
  - dropdown_custom_event_type: Custom Event Type
42
+ - dropdown_dynamic_options: Dynamic Options
43
+ - dropdown_dynamic_options_with_autocomplete: Dynamic Options with Autocomplete
44
+ - dropdown_dynamic_options_with_multi_select: Dynamic Options with Multi Select
42
45
 
43
46
  react:
44
47
  - dropdown_default: Default
@@ -81,3 +84,6 @@ examples:
81
84
  - dropdown_required_indicator: Required Indicator
82
85
  - dropdown_disabled: Disabled Input
83
86
  - dropdown_grouped_options: Grouped Options
87
+ - dropdown_react_hook: React Hook
88
+ - dropdown_multi_select_react_hook: React Hook Multi Select
89
+
@@ -1,4 +1,6 @@
1
1
  export { default as DropdownDefault } from './_dropdown_default.jsx'
2
+ export { default as DropdownReactHook } from './_dropdown_react_hook.jsx'
3
+ export { default as DropdownMultiSelectReactHook } from './_dropdown_multi_select_react_hook.jsx'
2
4
  export { default as DropdownWithCustomDisplay } from './_dropdown_with_custom_display.jsx'
3
5
  export { default as DropdownWithCustomOptions } from './_dropdown_with_custom_options.jsx'
4
6
  export { default as DropdownWithCustomTrigger } from './_dropdown_with_custom_trigger.jsx'
@@ -59,6 +59,14 @@ module Playbook
59
59
  default: false
60
60
  prop :custom_event_type, type: Playbook::Props::String,
61
61
  default: ""
62
+ prop :options_by_context, type: Playbook::Props::HashProp,
63
+ default: {}
64
+ prop :context_selector, type: Playbook::Props::String,
65
+ default: ""
66
+ prop :clear_on_context_change, type: Playbook::Props::Boolean,
67
+ default: true
68
+ prop :options_event_type, type: Playbook::Props::String,
69
+ default: ""
62
70
 
63
71
  def data
64
72
  Hash(prop(:data)).merge(
@@ -73,7 +81,11 @@ module Playbook
73
81
  end_date_id: variant == "quickpick" ? end_date_id : nil,
74
82
  controls_start_id: variant == "quickpick" && controls_start_id.present? ? controls_start_id : nil,
75
83
  controls_end_id: variant == "quickpick" && controls_end_id.present? ? controls_end_id : nil,
76
- custom_event_type: custom_event_type.presence
84
+ custom_event_type: custom_event_type.presence,
85
+ pb_dropdown_options_by_context: options_by_context.present? ? options_by_context.to_json : nil,
86
+ pb_dropdown_context_selector: context_selector.presence,
87
+ pb_dropdown_clear_on_context_change: clear_on_context_change,
88
+ options_event_type: options_event_type.presence
77
89
  ).compact
78
90
  end
79
91
 
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from "react"
2
- import { render, screen, fireEvent, waitFor } from "../utilities/test-utils"
2
+ import { render, screen, fireEvent, waitFor, act } from "../utilities/test-utils"
3
3
 
4
4
  import { Dropdown, Icon, IconCircle } from 'playbook-ui'
5
5
  import DateTime from "../pb_kit/dateTime.ts"
@@ -995,4 +995,207 @@ test('disabled prop disables autocomplete input', () => {
995
995
  const input = kit.querySelector('.dropdown_input')
996
996
 
997
997
  expect(input).toBeDisabled()
998
- })
998
+ })
999
+
1000
+ test('onChange uses react-hook-form event shape', () => {
1001
+ const onChange = jest.fn()
1002
+
1003
+ render(
1004
+ <Dropdown
1005
+ data={{ testid: testId }}
1006
+ name="color"
1007
+ onChange={onChange}
1008
+ options={options}
1009
+ />
1010
+ )
1011
+
1012
+ const kit = screen.getByTestId(testId)
1013
+ fireEvent.click(kit.querySelectorAll('.pb_dropdown_option_list')[0])
1014
+
1015
+ expect(onChange).toHaveBeenCalledWith({
1016
+ target: { name: 'color', value: options[0] },
1017
+ })
1018
+ })
1019
+
1020
+ test('react-hook-form onChange receives selected options for multiSelect', () => {
1021
+ const onChange = jest.fn()
1022
+
1023
+ render(
1024
+ <Dropdown
1025
+ data={{ testid: testId }}
1026
+ multiSelect
1027
+ name="languages"
1028
+ onChange={onChange}
1029
+ options={options}
1030
+ />
1031
+ )
1032
+
1033
+ const kit = screen.getByTestId(testId)
1034
+ fireEvent.click(kit.querySelectorAll('.pb_dropdown_option_list')[0])
1035
+ fireEvent.click(kit.querySelectorAll('.pb_dropdown_option_list')[0])
1036
+
1037
+ expect(onChange.mock.calls[0][0]).toEqual({
1038
+ target: { name: 'languages', value: [options[0]] },
1039
+ })
1040
+ expect(onChange.mock.calls[1][0]).toEqual({
1041
+ target: { name: 'languages', value: [options[0], options[1]] },
1042
+ })
1043
+ })
1044
+
1045
+ test('onSelect still fires when onChange is provided', () => {
1046
+ const onSelect = jest.fn()
1047
+ const onChange = jest.fn()
1048
+
1049
+ render(
1050
+ <Dropdown
1051
+ data={{ testid: testId }}
1052
+ name="country"
1053
+ onChange={onChange}
1054
+ onSelect={onSelect}
1055
+ options={options}
1056
+ />
1057
+ )
1058
+
1059
+ const kit = screen.getByTestId(testId)
1060
+ fireEvent.click(kit.querySelectorAll('.pb_dropdown_option_list')[0])
1061
+
1062
+ expect(onSelect).toHaveBeenCalledWith(options[0])
1063
+ expect(onChange).toHaveBeenCalledWith({
1064
+ target: { name: 'country', value: options[0] },
1065
+ })
1066
+ })
1067
+
1068
+ test('onSelect-only single select still receives the option object', () => {
1069
+ const onSelect = jest.fn()
1070
+
1071
+ render(
1072
+ <Dropdown
1073
+ data={{ testid: testId }}
1074
+ onSelect={onSelect}
1075
+ options={options}
1076
+ />
1077
+ )
1078
+
1079
+ const kit = screen.getByTestId(testId)
1080
+ fireEvent.click(kit.querySelectorAll('.pb_dropdown_option_list')[0])
1081
+
1082
+ expect(onSelect).toHaveBeenCalledTimes(1)
1083
+ expect(onSelect).toHaveBeenCalledWith(options[0])
1084
+ })
1085
+
1086
+ test('onSelect-only multiSelect still receives the selected options array', () => {
1087
+ const onSelect = jest.fn()
1088
+
1089
+ render(
1090
+ <Dropdown
1091
+ data={{ testid: testId }}
1092
+ multiSelect
1093
+ onSelect={onSelect}
1094
+ options={options}
1095
+ />
1096
+ )
1097
+
1098
+ const kit = screen.getByTestId(testId)
1099
+ fireEvent.click(kit.querySelectorAll('.pb_dropdown_option_list')[0])
1100
+ fireEvent.click(kit.querySelectorAll('.pb_dropdown_option_list')[0])
1101
+
1102
+ expect(onSelect.mock.calls[0][0]).toEqual([options[0]])
1103
+ expect(onSelect.mock.calls[1][0]).toEqual([options[0], options[1]])
1104
+ })
1105
+
1106
+ test('onSelect-only clearSelected still receives null', () => {
1107
+ const onSelect = jest.fn()
1108
+ const dropdownRef = React.createRef()
1109
+
1110
+ render(
1111
+ <Dropdown
1112
+ data={{ testid: testId }}
1113
+ defaultValue={options[0]}
1114
+ onSelect={onSelect}
1115
+ options={options}
1116
+ ref={dropdownRef}
1117
+ />
1118
+ )
1119
+
1120
+ act(() => {
1121
+ dropdownRef.current.clearSelected()
1122
+ })
1123
+
1124
+ expect(onSelect).toHaveBeenCalledWith(null)
1125
+ })
1126
+
1127
+ test('onSelect-only clear icon still receives null', () => {
1128
+ const onSelect = jest.fn()
1129
+
1130
+ render(
1131
+ <Dropdown
1132
+ data={{ testid: testId }}
1133
+ defaultValue={options[0]}
1134
+ onSelect={onSelect}
1135
+ options={options}
1136
+ />
1137
+ )
1138
+
1139
+ const kit = screen.getByTestId(testId)
1140
+ fireEvent.click(kit.querySelector('[aria-label="times icon"]').closest('div'))
1141
+
1142
+ expect(onSelect).toHaveBeenCalledWith(null)
1143
+ })
1144
+
1145
+ test('onSelect-only removing a multiSelect pill still receives remaining options', () => {
1146
+ const onSelect = jest.fn()
1147
+
1148
+ render(
1149
+ <Dropdown
1150
+ data={{ testid: testId }}
1151
+ defaultValue={[options[0], options[1]]}
1152
+ multiSelect
1153
+ onSelect={onSelect}
1154
+ options={options}
1155
+ />
1156
+ )
1157
+
1158
+ const kit = screen.getByTestId(testId)
1159
+ fireEvent.click(kit.querySelector('.pb_form_pill_close'))
1160
+
1161
+ expect(onSelect).toHaveBeenCalledWith([options[1]])
1162
+ })
1163
+
1164
+ test('autocomplete typing does not fire onSelect or onChange', () => {
1165
+ const onSelect = jest.fn()
1166
+ const onChange = jest.fn()
1167
+
1168
+ render(
1169
+ <Dropdown
1170
+ autocomplete
1171
+ data={{ testid: testId }}
1172
+ onChange={onChange}
1173
+ onSelect={onSelect}
1174
+ options={options}
1175
+ />
1176
+ )
1177
+
1178
+ const kit = screen.getByTestId(testId)
1179
+ fireEvent.change(kit.querySelector('.dropdown_input'), { target: { value: 'Can' } })
1180
+
1181
+ expect(onSelect).not.toHaveBeenCalled()
1182
+ expect(onChange).not.toHaveBeenCalled()
1183
+ })
1184
+
1185
+ test('autocomplete selection still fires onSelect', () => {
1186
+ const onSelect = jest.fn()
1187
+
1188
+ render(
1189
+ <Dropdown
1190
+ autocomplete
1191
+ data={{ testid: testId }}
1192
+ onSelect={onSelect}
1193
+ options={options}
1194
+ />
1195
+ )
1196
+
1197
+ const kit = screen.getByTestId(testId)
1198
+ fireEvent.click(kit.querySelectorAll('.pb_dropdown_option_list')[1])
1199
+
1200
+ expect(onSelect).toHaveBeenCalledWith(options[1])
1201
+ })