playbook_ui 16.12.0.pre.alpha.PLAY3072railstextinputrequiredaddonmaskingbugs18157 → 16.12.0.pre.alpha.PLAY3100adddarkborderprob18326
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/_playbook.scss +0 -1
- data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +18 -3
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default_value_with_autocomplete.html.erb +10 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default_value_with_autocomplete.jsx +35 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_dropdown/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +35 -0
- data/app/pb_kits/playbook/pb_dropdown/hooks/useHandleOnKeydown.tsx +18 -1
- data/app/pb_kits/playbook/pb_dropdown/index.js +34 -0
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +8 -1
- data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +7 -6
- data/app/pb_kits/playbook/pb_form/docs/example.yml +0 -1
- data/app/pb_kits/playbook/pb_form_group/_form_group.scss +24 -10
- data/app/pb_kits/playbook/pb_form_group/docs/_form_group_typeahead.html.erb +19 -14
- data/app/pb_kits/playbook/pb_form_group/docs/example.yml +2 -2
- data/app/pb_kits/playbook/pb_highlight/_highlight.tsx +31 -13
- data/app/pb_kits/playbook/pb_highlight/_highlightChunks.ts +89 -0
- data/app/pb_kits/playbook/pb_highlight/highlight.test.jsx +263 -0
- data/app/pb_kits/playbook/pb_table/_table.tsx +4 -0
- data/app/pb_kits/playbook/pb_table/docs/_playground.json +2 -0
- data/app/pb_kits/playbook/pb_table/docs/_playground.overrides.json +1 -1
- data/app/pb_kits/playbook/pb_table/docs/_sections.yml +1 -0
- data/app/pb_kits/playbook/pb_table/docs/_table_contrast_border.html.erb +71 -0
- data/app/pb_kits/playbook/pb_table/docs/_table_contrast_border.jsx +93 -0
- data/app/pb_kits/playbook/pb_table/docs/_table_contrast_border.md +1 -0
- data/app/pb_kits/playbook/pb_table/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_table/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_table/kit.schema.json +8 -0
- data/app/pb_kits/playbook/pb_table/styles/_all.scss +2 -1
- data/app/pb_kits/playbook/pb_table/styles/_contrast_border.scss +87 -0
- data/app/pb_kits/playbook/pb_table/styles/_desktop_collapse.scss +2 -2
- data/app/pb_kits/playbook/pb_table/styles/_headers.scss +3 -3
- data/app/pb_kits/playbook/pb_table/styles/_hover.scss +2 -2
- data/app/pb_kits/playbook/pb_table/styles/_mobile.scss +2 -2
- data/app/pb_kits/playbook/pb_table/styles/_mobile_collapse.scss +2 -2
- data/app/pb_kits/playbook/pb_table/styles/_scroll.scss +2 -2
- data/app/pb_kits/playbook/pb_table/styles/_sticky_columns.scss +2 -2
- data/app/pb_kits/playbook/pb_table/styles/_structure.scss +1 -1
- data/app/pb_kits/playbook/pb_table/styles/_table-card.scss +2 -2
- data/app/pb_kits/playbook/pb_table/styles/_tablet_collapse.scss +2 -2
- data/app/pb_kits/playbook/pb_table/styles/_vertical_border.scss +9 -9
- data/app/pb_kits/playbook/pb_table/table.rb +7 -1
- data/app/pb_kits/playbook/pb_table/table.test.js +5 -0
- data/app/pb_kits/playbook/pb_timestamp/_timestamp.tsx +2 -2
- data/app/pb_kits/playbook/pb_timestamp/docs/_timestamp_updated.html.erb +19 -0
- data/app/pb_kits/playbook/pb_timestamp/docs/_timestamp_updated.jsx +21 -0
- data/app/pb_kits/playbook/pb_timestamp/docs/_timestamp_updated.md +3 -1
- data/app/pb_kits/playbook/pb_timestamp/timestamp.test.js +42 -0
- data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +3 -1
- data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +1 -0
- data/dist/chunks/{_typeahead-BrQbahtd.js → _typeahead-BcKnlPk3.js} +1 -1
- data/dist/chunks/vendor.js +2 -2
- data/dist/menu.yml +0 -6
- 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/tokens/colors.json +117 -1
- data/lib/playbook/tokens.rb +5 -4
- data/lib/playbook/version.rb +1 -1
- metadata +11 -19
- data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate_addon_currency.html.erb +0 -123
- data/app/pb_kits/playbook/pb_icon_button/_icon_button.scss +0 -78
- data/app/pb_kits/playbook/pb_icon_button/_icon_button.tsx +0 -75
- data/app/pb_kits/playbook/pb_icon_button/docs/_icon_button_click.jsx +0 -13
- data/app/pb_kits/playbook/pb_icon_button/docs/_icon_button_default.html.erb +0 -2
- data/app/pb_kits/playbook/pb_icon_button/docs/_icon_button_default.jsx +0 -17
- data/app/pb_kits/playbook/pb_icon_button/docs/_icon_button_sizes.html.erb +0 -27
- data/app/pb_kits/playbook/pb_icon_button/docs/_icon_button_sizes.jsx +0 -63
- data/app/pb_kits/playbook/pb_icon_button/docs/_playground.json +0 -135
- data/app/pb_kits/playbook/pb_icon_button/docs/_playground.overrides.json +0 -131
- data/app/pb_kits/playbook/pb_icon_button/docs/example.yml +0 -10
- data/app/pb_kits/playbook/pb_icon_button/docs/index.js +0 -3
- data/app/pb_kits/playbook/pb_icon_button/icon_button.html.erb +0 -17
- data/app/pb_kits/playbook/pb_icon_button/icon_button.rb +0 -25
- data/app/pb_kits/playbook/pb_icon_button/icon_button.test.jsx +0 -39
- data/app/pb_kits/playbook/pb_icon_button/kit.schema.json +0 -116
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a180c0a86cb56e3e65389829c05e8f2a80a188e736401dec91b92257b2ba8cc9
|
|
4
|
+
data.tar.gz: 616c39a14d5f72c03de3020fcc6a1ecdb4cffde6a452d922dcc8791a07b8fa3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d19e915b3c1c7f1b5c99307c7dcb402b30bb2852efe87a59642dc40ef56a38a305d98e97c03a9535868e077ade98df3278d73d1174af0b8b1fe46a04a19fc4d
|
|
7
|
+
data.tar.gz: bff090a80a6dc89f2d0097b38a14851d4b68f0ec417502324a144dc624c6852902b4c9072ef3710954fecf2803350d5f9fbc530dec4d2ef010ea8ccc83ab6b96
|
|
@@ -41,7 +41,6 @@
|
|
|
41
41
|
@import 'pb_highlight/highlight';
|
|
42
42
|
@import 'pb_home_address_street/home_address_street';
|
|
43
43
|
@import 'pb_icon/icon';
|
|
44
|
-
@import 'pb_icon_button/icon_button';
|
|
45
44
|
@import 'pb_icon_circle/icon_circle';
|
|
46
45
|
@import 'pb_icon_stat_value/icon_stat_value';
|
|
47
46
|
@import 'pb_icon_value/icon_value';
|
|
@@ -203,7 +203,6 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
203
203
|
: undefined;
|
|
204
204
|
const errorId = error ? `${selectId}-error` : undefined;
|
|
205
205
|
|
|
206
|
-
const [filterItem, setFilterItem] = useState("");
|
|
207
206
|
const initialSelected = useMemo(() => {
|
|
208
207
|
// Handle quickpick variant with string defaultValue (e.g., "This Month")
|
|
209
208
|
if (variant === "quickpick" && typeof defaultValue === "string" && defaultValue) {
|
|
@@ -226,6 +225,13 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
226
225
|
initialSelected
|
|
227
226
|
);
|
|
228
227
|
|
|
228
|
+
// Autocomplete displays the selection in the input; seed from defaultValue
|
|
229
|
+
const [filterItem, setFilterItem] = useState(() => {
|
|
230
|
+
if (!autocomplete || multiSelect) return "";
|
|
231
|
+
if (Array.isArray(initialSelected)) return "";
|
|
232
|
+
return (initialSelected as GenericObject)?.label || "";
|
|
233
|
+
});
|
|
234
|
+
|
|
229
235
|
const filterResetDefaultSerialized = useMemo(
|
|
230
236
|
() => serializeDropdownFilterResetDefault(variant, multiSelect, defaultValue, dropdownOptions),
|
|
231
237
|
[variant, multiSelect, defaultValue, dropdownOptions]
|
|
@@ -320,10 +326,17 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
320
326
|
}, [optionsWithBlankSelection, selectedArray, multiSelect]);
|
|
321
327
|
|
|
322
328
|
const filteredOptions = useMemo(() => {
|
|
329
|
+
// When the input shows the selected label, do not filter the list down to that one option
|
|
330
|
+
const selectedLabel =
|
|
331
|
+
!multiSelect &&
|
|
332
|
+
!Array.isArray(selected) &&
|
|
333
|
+
(selected as GenericObject)?.label;
|
|
334
|
+
const filterText =
|
|
335
|
+
selectedLabel && filterItem === selectedLabel ? "" : filterItem;
|
|
323
336
|
return availableOptions.filter((opt: GenericObject) =>
|
|
324
|
-
String(opt.label).toLowerCase().includes(
|
|
337
|
+
String(opt.label).toLowerCase().includes(filterText.toLowerCase())
|
|
325
338
|
);
|
|
326
|
-
}, [availableOptions, filterItem]);
|
|
339
|
+
}, [availableOptions, filterItem, multiSelect, selected]);
|
|
327
340
|
|
|
328
341
|
// For keyboard accessibility: Set focus within dropdown to selected item if it exists
|
|
329
342
|
useEffect(() => {
|
|
@@ -460,6 +473,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
460
473
|
setSelected({});
|
|
461
474
|
onSelect && onSelect(null);
|
|
462
475
|
setFocusedOptionIndex(-1);
|
|
476
|
+
setFilterItem("");
|
|
463
477
|
|
|
464
478
|
// Clear linked DatePickers as well if this is a quickpick variant with controls
|
|
465
479
|
if (variant === "quickpick") {
|
|
@@ -593,6 +607,7 @@ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
|
|
|
593
607
|
onSelect,
|
|
594
608
|
optionsWithBlankSelection,
|
|
595
609
|
selected,
|
|
610
|
+
setFilterItem,
|
|
596
611
|
setFocusedOptionIndex,
|
|
597
612
|
setIsDropDownClosed,
|
|
598
613
|
setIsInputFocused,
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
|
|
10
|
+
<%= pb_rails("dropdown", props: {options: options, default_value: options.last, autocomplete: true}) %>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import Dropdown from '../../pb_dropdown/_dropdown'
|
|
3
|
+
|
|
4
|
+
const DropdownDefaultValueWithAutocomplete = (props) => {
|
|
5
|
+
const options = [
|
|
6
|
+
{
|
|
7
|
+
label: "United States",
|
|
8
|
+
value: "unitedStates",
|
|
9
|
+
id: "us"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
label: "Canada",
|
|
13
|
+
value: "canada",
|
|
14
|
+
id: "ca"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
label: "Pakistan",
|
|
18
|
+
value: "pakistan",
|
|
19
|
+
id: "pk"
|
|
20
|
+
}
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<>
|
|
25
|
+
<Dropdown
|
|
26
|
+
autocomplete
|
|
27
|
+
defaultValue={options[2]}
|
|
28
|
+
options={options}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
</>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default DropdownDefaultValueWithAutocomplete
|
|
@@ -22,6 +22,7 @@ examples:
|
|
|
22
22
|
- dropdown_error: Dropdown with Error
|
|
23
23
|
- dropdown_default_value: Default Value
|
|
24
24
|
- dropdown_multi_select_with_default: Multi Select Default Value
|
|
25
|
+
- dropdown_default_value_with_autocomplete: Default Value with Autocomplete
|
|
25
26
|
- dropdown_blank_selection: Blank Selection
|
|
26
27
|
- dropdown_with_placeholder: Placeholder
|
|
27
28
|
- dropdown_separators_hidden: Separators Hidden
|
|
@@ -63,6 +64,7 @@ examples:
|
|
|
63
64
|
- dropdown_error: Dropdown with Error
|
|
64
65
|
- dropdown_default_value: Default Value
|
|
65
66
|
- dropdown_multi_select_with_default: Multi Select Default Value
|
|
67
|
+
- dropdown_default_value_with_autocomplete: Default Value with Autocomplete
|
|
66
68
|
- dropdown_blank_selection: Blank Selection
|
|
67
69
|
- dropdown_with_placeholder: Placeholder
|
|
68
70
|
- dropdown_clear_selection: Clear Selection
|
|
@@ -37,3 +37,4 @@ export { default as DropdownDisabledOption } from "./_dropdown_disabled_option.j
|
|
|
37
37
|
export { default as DropdownCustomDisplayDisabledOption } from "./_dropdown_custom_display_disabled_option.jsx";
|
|
38
38
|
export { default as DropdownDisabled } from "./_dropdown_disabled.jsx";
|
|
39
39
|
export { default as DropdownGroupedOptions } from "./_dropdown_grouped_options.jsx";
|
|
40
|
+
export { default as DropdownDefaultValueWithAutocomplete } from './_dropdown_default_value_with_autocomplete.jsx'
|
|
@@ -350,6 +350,41 @@ test("autocomplete prop to render input", () => {
|
|
|
350
350
|
expect(input).toBeInTheDocument()
|
|
351
351
|
})
|
|
352
352
|
|
|
353
|
+
test("defaultValue renders in autocomplete input", () => {
|
|
354
|
+
render(
|
|
355
|
+
<Dropdown
|
|
356
|
+
autocomplete
|
|
357
|
+
data={{ testid: testId }}
|
|
358
|
+
defaultValue={options[2]}
|
|
359
|
+
options={options}
|
|
360
|
+
/>
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
const kit = screen.getByTestId(testId)
|
|
364
|
+
const input = kit.querySelector('.dropdown_input')
|
|
365
|
+
expect(input).toBeInTheDocument()
|
|
366
|
+
expect(input).toHaveValue(options[2].label)
|
|
367
|
+
expect(input).toHaveAttribute("placeholder", "")
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
test("autocomplete defaultValue still shows all options when opened", () => {
|
|
371
|
+
render(
|
|
372
|
+
<Dropdown
|
|
373
|
+
autocomplete
|
|
374
|
+
data={{ testid: testId }}
|
|
375
|
+
defaultValue={options[2]}
|
|
376
|
+
options={options}
|
|
377
|
+
/>
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
const kit = screen.getByTestId(testId)
|
|
381
|
+
const optionEls = kit.querySelectorAll('.pb_dropdown_option_list, .pb_dropdown_option_selected')
|
|
382
|
+
expect(optionEls).toHaveLength(options.length)
|
|
383
|
+
expect(kit).toHaveTextContent(options[0].label)
|
|
384
|
+
expect(kit).toHaveTextContent(options[1].label)
|
|
385
|
+
expect(kit).toHaveTextContent(options[2].label)
|
|
386
|
+
})
|
|
387
|
+
|
|
353
388
|
test("searchbar prop to render TextInput in container", () => {
|
|
354
389
|
render (
|
|
355
390
|
<Dropdown
|
|
@@ -6,11 +6,13 @@ export const useHandleOnKeyDown = () => {
|
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
8
|
autocomplete,
|
|
9
|
+
filterItem,
|
|
9
10
|
filteredOptions,
|
|
10
11
|
focusedOptionIndex,
|
|
11
12
|
handleBackspace,
|
|
12
13
|
handleOptionClick,
|
|
13
14
|
selected,
|
|
15
|
+
setFilterItem,
|
|
14
16
|
setFocusedOptionIndex,
|
|
15
17
|
setIsDropDownClosed,
|
|
16
18
|
}= useContext(DropdownContext)
|
|
@@ -40,7 +42,22 @@ const {
|
|
|
40
42
|
return (e: React.KeyboardEvent) => {
|
|
41
43
|
|
|
42
44
|
if (e.key !== "Tab" && autocomplete && selected && selected.label) {
|
|
45
|
+
// Only when the input is showing the selected label (e.g. seeded defaultValue)
|
|
46
|
+
const replacingSelectedLabel = filterItem === selected.label;
|
|
43
47
|
handleBackspace();
|
|
48
|
+
|
|
49
|
+
if (replacingSelectedLabel && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
setFilterItem?.(e.key);
|
|
52
|
+
setIsDropDownClosed(false);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (replacingSelectedLabel && (e.key === "Backspace" || e.key === "Delete")) {
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
setFilterItem?.("");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
44
61
|
}
|
|
45
62
|
|
|
46
63
|
switch (e.key) {
|
|
@@ -72,4 +89,4 @@ const {
|
|
|
72
89
|
break;
|
|
73
90
|
}
|
|
74
91
|
}
|
|
75
|
-
};
|
|
92
|
+
};
|
|
@@ -541,6 +541,30 @@ export default class PbDropdown extends PbEnhancedElement {
|
|
|
541
541
|
}
|
|
542
542
|
}
|
|
543
543
|
|
|
544
|
+
// Keep the selected label in the autocomplete input, but do not treat it as a search filter
|
|
545
|
+
resetOptionFilterForSelectedLabel() {
|
|
546
|
+
if (this.isMultiSelect || !this.searchInput) return;
|
|
547
|
+
|
|
548
|
+
const selectedOption = Array.from(this.queryAllOptions()).find((opt) =>
|
|
549
|
+
opt.classList.contains("pb_dropdown_option_selected"),
|
|
550
|
+
);
|
|
551
|
+
if (!selectedOption) return;
|
|
552
|
+
|
|
553
|
+
try {
|
|
554
|
+
const selectedLabel = JSON.parse(
|
|
555
|
+
selectedOption.dataset.dropdownOptionLabel,
|
|
556
|
+
).label;
|
|
557
|
+
if (this.searchInput.value !== selectedLabel) return;
|
|
558
|
+
|
|
559
|
+
this.queryAllOptions().forEach((opt) => {
|
|
560
|
+
opt.style.display = "";
|
|
561
|
+
});
|
|
562
|
+
this.removeNoOptionsMessage();
|
|
563
|
+
} catch {
|
|
564
|
+
// ignore invalid option payloads
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
544
568
|
showNoOptionsMessage() {
|
|
545
569
|
if (this.target?.querySelector(".dropdown_no_options")) return;
|
|
546
570
|
|
|
@@ -923,6 +947,9 @@ export default class PbDropdown extends PbEnhancedElement {
|
|
|
923
947
|
|
|
924
948
|
showElement(elem) {
|
|
925
949
|
if (this.isDisabled) return;
|
|
950
|
+
|
|
951
|
+
// When autocomplete is showing the selected label (not an active search), show all options
|
|
952
|
+
this.resetOptionFilterForSelectedLabel();
|
|
926
953
|
|
|
927
954
|
this.ensureFloatingPortalConfig();
|
|
928
955
|
if (this.useMenuPortal) {
|
|
@@ -1096,6 +1123,13 @@ export default class PbDropdown extends PbEnhancedElement {
|
|
|
1096
1123
|
const optionData = JSON.parse(selectedOption.dataset.dropdownOptionLabel);
|
|
1097
1124
|
this.setTriggerElementText(optionData.label);
|
|
1098
1125
|
|
|
1126
|
+
// Autocomplete trigger has no [data-dropdown-trigger-display]; set the input value instead
|
|
1127
|
+
const autocompleteInput =
|
|
1128
|
+
this.searchInput || this.element.querySelector(SEARCH_INPUT_SELECTOR);
|
|
1129
|
+
if (autocompleteInput) {
|
|
1130
|
+
autocompleteInput.value = optionData.label;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1099
1133
|
// Handle quickpick variant: populate start/end date hidden inputs and sync DatePickers
|
|
1100
1134
|
if (optionData.formatted_start_date && optionData.formatted_end_date) {
|
|
1101
1135
|
const startDateId = this.element.dataset.startDateId;
|
|
@@ -52,8 +52,15 @@ const DropdownOption = (props: DropdownOptionProps) => {
|
|
|
52
52
|
} = useContext(DropdownContext);
|
|
53
53
|
|
|
54
54
|
const isItemMatchingFilter = (option: GenericObject | undefined) => {
|
|
55
|
+
// When the input is only showing the selected label (e.g. seeded defaultValue), do not filter
|
|
56
|
+
const selectedLabel =
|
|
57
|
+
!multiSelect &&
|
|
58
|
+
!Array.isArray(selected) &&
|
|
59
|
+
(selected as GenericObject)?.label;
|
|
60
|
+
const filterText =
|
|
61
|
+
selectedLabel && filterItem === selectedLabel ? "" : filterItem;
|
|
55
62
|
const label = typeof option?.label === 'string' ? option.label.toLowerCase() : option?.label;
|
|
56
|
-
return String(label).toLowerCase().includes(
|
|
63
|
+
return String(label).toLowerCase().includes(filterText.toLowerCase());
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
// When multiSelect, then if an option is selected, remove from dropdown
|
|
@@ -103,8 +103,9 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
|
|
|
103
103
|
"Select..."
|
|
104
104
|
);
|
|
105
105
|
|
|
106
|
+
const hideJoinedLabelInBody = Boolean(autocomplete && filterItem === joinedLabels);
|
|
106
107
|
const defaultDisplayPlaceholder = joinedLabels
|
|
107
|
-
? joinedLabels
|
|
108
|
+
? (hideJoinedLabelInBody ? "" : joinedLabels)
|
|
108
109
|
: autocomplete
|
|
109
110
|
? ""
|
|
110
111
|
: placeholder
|
|
@@ -252,9 +253,9 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
|
|
|
252
253
|
}}
|
|
253
254
|
key={`${isDropDownClosed ? "chevron-down" : "chevron-up"}`}
|
|
254
255
|
>
|
|
255
|
-
{(!blankSelection || selected?.value !== optionsWithBlankSelection?.[0]?.value) &&
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
{(!blankSelection || selected?.value !== optionsWithBlankSelection?.[0]?.value) &&
|
|
257
|
+
clearable !== false &&
|
|
258
|
+
selectedArray.length > 0 ? (
|
|
258
259
|
<div onClick={(e)=>{e.stopPropagation(); !disabled && handleBackspace()}}>
|
|
259
260
|
<Icon
|
|
260
261
|
cursor={disabled ? "default" : "pointer"}
|
|
@@ -264,8 +265,8 @@ const DropdownTrigger = (props: DropdownTriggerProps) => {
|
|
|
264
265
|
size="sm"
|
|
265
266
|
/>
|
|
266
267
|
</div>
|
|
267
|
-
|
|
268
|
-
|
|
268
|
+
) : (
|
|
269
|
+
<></>
|
|
269
270
|
)}
|
|
270
271
|
<Icon
|
|
271
272
|
cursor={disabled ? "default" : "pointer"}
|
|
@@ -202,23 +202,37 @@
|
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
& > [class^=pb_typeahead_kit]
|
|
205
|
+
& > [class^=pb_typeahead_kit],
|
|
206
|
+
& > [data-pb-react-component] > [class^=pb_typeahead_kit] {
|
|
206
207
|
min-width: 200px;
|
|
207
208
|
}
|
|
208
209
|
|
|
209
|
-
& > [class^=pb_typeahead_kit]:not(:last-child)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
210
|
+
& > [class^=pb_typeahead_kit]:not(:last-child),
|
|
211
|
+
& > [data-pb-react-component]:not(:last-child) > [class^=pb_typeahead_kit] {
|
|
212
|
+
[class^=pb_text_input_kit] .text_input_wrapper {
|
|
213
|
+
input,
|
|
214
|
+
.text_input,
|
|
215
|
+
.typeahead-kit-select__control {
|
|
216
|
+
border-bottom-right-radius: 0 !important;
|
|
217
|
+
border-top-right-radius: 0 !important;
|
|
218
|
+
border-right-width: 0 !important;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
[class^=pb_text_input_kit] .text_input_wrapper div.text_input {
|
|
214
223
|
min-height: 26px;
|
|
215
224
|
}
|
|
216
225
|
}
|
|
217
226
|
|
|
218
|
-
& > [class^=pb_typeahead_kit]:not(:first-child)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
227
|
+
& > [class^=pb_typeahead_kit]:not(:first-child),
|
|
228
|
+
& > [data-pb-react-component]:not(:first-child) > [class^=pb_typeahead_kit] {
|
|
229
|
+
[class^=pb_text_input_kit] .text_input_wrapper {
|
|
230
|
+
input,
|
|
231
|
+
.text_input,
|
|
232
|
+
.typeahead-kit-select__control {
|
|
233
|
+
border-bottom-left-radius: 0 !important;
|
|
234
|
+
border-top-left-radius: 0 !important;
|
|
235
|
+
}
|
|
222
236
|
}
|
|
223
237
|
}
|
|
224
238
|
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
<div>
|
|
2
|
+
|
|
3
|
+
<%
|
|
4
|
+
options = [
|
|
5
|
+
{ label: 'Orange', value: '#FFA500' },
|
|
6
|
+
{ label: 'Red', value: '#FF0000' },
|
|
7
|
+
{ label: 'Green', value: '#00FF00' },
|
|
8
|
+
{ label: 'Blue', value: '#0000FF' },
|
|
9
|
+
]
|
|
10
|
+
%>
|
|
2
11
|
<%= pb_rails("form_group") do %>
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
id: "form-group-typeahead-example",
|
|
14
|
-
label: "Products",
|
|
15
|
-
options: options,
|
|
16
|
-
}) %>
|
|
12
|
+
|
|
13
|
+
<%= pb_rails("typeahead", props: {
|
|
14
|
+
id: "typeahead-default",
|
|
15
|
+
placeholder: "All Colors",
|
|
16
|
+
options: options,
|
|
17
|
+
label: "Colors",
|
|
18
|
+
name: :foo,
|
|
19
|
+
is_multi: false,
|
|
20
|
+
})
|
|
21
|
+
%>
|
|
17
22
|
<%= pb_rails("button", props: {
|
|
18
23
|
id: "form-group-button",
|
|
19
24
|
text: "Add",
|
|
@@ -5,7 +5,7 @@ examples:
|
|
|
5
5
|
- form_group_button: Button
|
|
6
6
|
- form_group_full_width: Full Width
|
|
7
7
|
- form_group_date_picker: Date Picker
|
|
8
|
-
|
|
8
|
+
- form_group_typeahead: Typeahead
|
|
9
9
|
- form_group_select: Select
|
|
10
10
|
- form_group_selectable_card: Selectable Card
|
|
11
11
|
- form_group_selectable_card_icon: Selectable Card Icon
|
|
@@ -16,7 +16,7 @@ examples:
|
|
|
16
16
|
- form_group_button: Button
|
|
17
17
|
- form_group_full_width: Full Width
|
|
18
18
|
- form_group_date_picker: Date Picker
|
|
19
|
-
|
|
19
|
+
- form_group_typeahead: Typeahead
|
|
20
20
|
- form_group_select: Select
|
|
21
21
|
- form_group_selectable_card: Selectable Card
|
|
22
22
|
- form_group_selectable_card_icon: Selectable Card Icon
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
/* eslint-disable react/no-multi-comp, flowtype/space-before-type-colon */
|
|
2
|
-
import Highlighter from 'react-highlight-words'
|
|
3
1
|
import React from 'react'
|
|
4
2
|
import classnames from 'classnames'
|
|
5
3
|
import { globalProps, GlobalProps } from '../utilities/globalProps'
|
|
6
|
-
import { buildHtmlProps } from '../utilities/props'
|
|
4
|
+
import { buildDataProps, buildHtmlProps } from '../utilities/props'
|
|
5
|
+
import { findHighlightChunks } from './_highlightChunks'
|
|
7
6
|
|
|
8
7
|
type HighlightProps = {
|
|
9
8
|
className?: string,
|
|
@@ -26,20 +25,39 @@ const Highlight = (props: HighlightProps): React.ReactElement => {
|
|
|
26
25
|
text = '',
|
|
27
26
|
} = props
|
|
28
27
|
|
|
28
|
+
const dataProps = buildDataProps(data)
|
|
29
29
|
const htmlProps = buildHtmlProps(htmlOptions)
|
|
30
|
-
const highlightContent:
|
|
30
|
+
const highlightContent: string = (text || children || '') as string
|
|
31
|
+
const highlightClassName = classnames(globalProps(props), className)
|
|
32
|
+
const chunks = findHighlightChunks(highlightContent, highlightedText)
|
|
31
33
|
|
|
32
34
|
return (
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
data={data}
|
|
36
|
-
highlightClassName={classnames(globalProps(props), className)}
|
|
37
|
-
highlightTag="mark"
|
|
38
|
-
id={id}
|
|
39
|
-
searchWords={highlightedText}
|
|
40
|
-
textToHighlight={highlightContent}
|
|
35
|
+
<span
|
|
36
|
+
{...dataProps}
|
|
41
37
|
{...htmlProps}
|
|
42
|
-
|
|
38
|
+
id={id}
|
|
39
|
+
>
|
|
40
|
+
{chunks.map((chunk, index) => {
|
|
41
|
+
const chunkText = highlightContent.substring(chunk.start, chunk.end)
|
|
42
|
+
|
|
43
|
+
if (chunk.highlight) {
|
|
44
|
+
return (
|
|
45
|
+
<mark
|
|
46
|
+
className={highlightClassName}
|
|
47
|
+
key={index}
|
|
48
|
+
>
|
|
49
|
+
{chunkText}
|
|
50
|
+
</mark>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<span key={index}>
|
|
56
|
+
{chunkText}
|
|
57
|
+
</span>
|
|
58
|
+
)
|
|
59
|
+
})}
|
|
60
|
+
</span>
|
|
43
61
|
)
|
|
44
62
|
}
|
|
45
63
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
type HighlightChunk = {
|
|
2
|
+
start: number,
|
|
3
|
+
end: number,
|
|
4
|
+
highlight: boolean,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const escapeRegExp = (value: string): string =>
|
|
8
|
+
value.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&')
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Finds all case-insensitive, literal (auto-escaped) matches for the given
|
|
12
|
+
* search terms, merges overlapping/adjacent ranges, and returns a full set of
|
|
13
|
+
* highlight and non-highlight chunks covering the text.
|
|
14
|
+
*
|
|
15
|
+
* Mirrors highlight-words-core behavior used by react-highlight-words with
|
|
16
|
+
* autoEscape enabled and caseSensitive disabled.
|
|
17
|
+
*/
|
|
18
|
+
export const findHighlightChunks = (
|
|
19
|
+
textToHighlight: string,
|
|
20
|
+
searchWords: string[],
|
|
21
|
+
): HighlightChunk[] => {
|
|
22
|
+
const text = textToHighlight || ''
|
|
23
|
+
|
|
24
|
+
const matchRanges = searchWords
|
|
25
|
+
.filter((searchWord) => searchWord)
|
|
26
|
+
.reduce<{ start: number, end: number }[]>((chunks, searchWord) => {
|
|
27
|
+
const regex = new RegExp(escapeRegExp(searchWord), 'gi')
|
|
28
|
+
let match: RegExpExecArray | null
|
|
29
|
+
|
|
30
|
+
while ((match = regex.exec(text))) {
|
|
31
|
+
const start = match.index
|
|
32
|
+
const end = regex.lastIndex
|
|
33
|
+
|
|
34
|
+
if (end > start) {
|
|
35
|
+
chunks.push({ start, end })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Prevent zero-length match infinite loops (e.g. Firefox)
|
|
39
|
+
if (match.index === regex.lastIndex) {
|
|
40
|
+
regex.lastIndex++
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return chunks
|
|
45
|
+
}, [])
|
|
46
|
+
|
|
47
|
+
const combined = matchRanges
|
|
48
|
+
.sort((first, second) => first.start - second.start)
|
|
49
|
+
.reduce<{ start: number, end: number }[]>((processedChunks, nextChunk) => {
|
|
50
|
+
if (processedChunks.length === 0) {
|
|
51
|
+
return [nextChunk]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const prevChunk = processedChunks.pop() as { start: number, end: number }
|
|
55
|
+
|
|
56
|
+
// Merge overlapping or adjacent ranges (start <= prev end)
|
|
57
|
+
if (nextChunk.start <= prevChunk.end) {
|
|
58
|
+
processedChunks.push({
|
|
59
|
+
start: prevChunk.start,
|
|
60
|
+
end: Math.max(prevChunk.end, nextChunk.end),
|
|
61
|
+
})
|
|
62
|
+
} else {
|
|
63
|
+
processedChunks.push(prevChunk, nextChunk)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return processedChunks
|
|
67
|
+
}, [])
|
|
68
|
+
|
|
69
|
+
const allChunks: HighlightChunk[] = []
|
|
70
|
+
const append = (start: number, end: number, highlight: boolean) => {
|
|
71
|
+
if (end - start > 0) {
|
|
72
|
+
allChunks.push({ start, end, highlight })
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (combined.length === 0) {
|
|
77
|
+
append(0, text.length, false)
|
|
78
|
+
} else {
|
|
79
|
+
let lastIndex = 0
|
|
80
|
+
combined.forEach((chunk) => {
|
|
81
|
+
append(lastIndex, chunk.start, false)
|
|
82
|
+
append(chunk.start, chunk.end, true)
|
|
83
|
+
lastIndex = chunk.end
|
|
84
|
+
})
|
|
85
|
+
append(lastIndex, text.length, false)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return allChunks
|
|
89
|
+
}
|