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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +32 -0
- data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +18 -9
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options.html.erb +34 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options.md +4 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options_with_autocomplete.html.erb +35 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options_with_autocomplete.md +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options_with_multi_select.html.erb +35 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options_with_multi_select.md +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_react_hook.jsx +45 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_multi_select_react_hook.md +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_react_hook.jsx +36 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_react_hook.md +1 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_playground.json +2 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_playground.overrides.json +2 -1
- data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +6 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/index.js +2 -0
- data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +13 -1
- data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +205 -2
- data/app/pb_kits/playbook/pb_dropdown/dropdown_index.test.js +372 -0
- data/app/pb_kits/playbook/pb_dropdown/index.js +240 -1
- data/app/pb_kits/playbook/pb_dropdown/kit.schema.json +40 -6
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/MultiSelectTriggerDisplay.tsx +2 -2
- data/app/pb_kits/playbook/pb_file_upload/_file_upload.scss +5 -0
- data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_custom_message.html.erb +32 -0
- data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_custom_message_rails.md +3 -0
- data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_input_options.html.erb +3 -0
- data/app/pb_kits/playbook/pb_file_upload/docs/_file_upload_input_options.md +1 -0
- data/app/pb_kits/playbook/pb_file_upload/docs/_playground.json +1 -0
- data/app/pb_kits/playbook/pb_file_upload/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_file_upload/file_upload.html.erb +14 -5
- data/app/pb_kits/playbook/pb_file_upload/file_upload.rb +3 -0
- data/app/pb_kits/playbook/pb_file_upload/kit.schema.json +4 -2
- data/app/pb_kits/playbook/pb_icon/docs/_icon_css_api.html.erb +3 -0
- data/app/pb_kits/playbook/pb_icon/docs/_icon_css_api.jsx +13 -0
- data/app/pb_kits/playbook/pb_icon/docs/_icon_css_api.md +1 -0
- data/app/pb_kits/playbook/pb_icon/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_icon/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_link/_link.tsx +12 -2
- data/app/pb_kits/playbook/pb_link/docs/_link_rel.html.erb +16 -0
- data/app/pb_kits/playbook/pb_link/docs/_link_rel.jsx +30 -0
- data/app/pb_kits/playbook/pb_link/docs/_link_rel.md +3 -0
- data/app/pb_kits/playbook/pb_link/docs/_playground.json +6 -2
- data/app/pb_kits/playbook/pb_link/docs/_playground.overrides.json +2 -1
- data/app/pb_kits/playbook/pb_link/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_link/docs/index.js +2 -1
- data/app/pb_kits/playbook/pb_link/kit.schema.json +7 -0
- data/app/pb_kits/playbook/pb_link/link.html.erb +2 -2
- data/app/pb_kits/playbook/pb_link/link.rb +7 -0
- data/app/pb_kits/playbook/pb_link/link.test.jsx +29 -0
- data/dist/chunks/_detail-B4-co8rv.js +5 -0
- data/dist/chunks/{_pb_line_graph-CUJ7NJIr.js → _pb_line_graph-BGyCJjgG.js} +1 -1
- data/dist/chunks/_table-SGw4kUye.js +1 -0
- data/dist/chunks/_typeahead-DeIqOMbJ.js +1 -0
- data/dist/chunks/{globalProps-DDcpyjvz.js → globalProps-K1DCn3il.js} +1 -1
- data/dist/chunks/lib-7vm9BMqp.js +29 -0
- data/dist/chunks/{time_picker_helper-B_FN8E9Y.js → time_picker_helper-BZutIzB3.js} +1 -1
- data/dist/chunks/vendor.js +2 -2
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/version.rb +2 -2
- metadata +35 -10
- data/dist/chunks/_detail-CyTyC7gJ.js +0 -5
- data/dist/chunks/_table-DDKdCER5.js +0 -1
- data/dist/chunks/_typeahead-DdVBwRQT.js +0 -1
- data/dist/chunks/lib-D57mygNT.js +0 -29
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 02e9d48053efc7bc4dde6c77d9b396a21e3b35f8343a35eb334f816c5e7423f8
|
|
4
|
+
data.tar.gz: a275c089998b99dfd64fb86754fd9db545214fb12bde587411e8100cedb1bf88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
479
|
+
handleSelectionChange([]);
|
|
472
480
|
} else {
|
|
473
481
|
setSelected({});
|
|
474
|
-
|
|
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
|
-
|
|
516
|
+
handleSelectionChange([]);
|
|
509
517
|
} else {
|
|
510
518
|
setSelected({});
|
|
511
|
-
|
|
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
|
-
|
|
533
|
+
handleSelectionChange([]);
|
|
526
534
|
} else {
|
|
527
535
|
setSelected({});
|
|
528
|
-
|
|
536
|
+
handleSelectionChange(null);
|
|
529
537
|
}
|
|
530
538
|
setFilterItem("");
|
|
531
539
|
setIsDropDownClosed(true);
|
|
532
540
|
},
|
|
533
541
|
};
|
|
534
|
-
}, [multiSelect,
|
|
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.
|
data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options_with_autocomplete.html.erb
ADDED
|
@@ -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.
|
data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_dynamic_options_with_multi_select.html.erb
ADDED
|
@@ -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.
|
|
@@ -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
|
+
})
|