playbook_ui 16.13.0.pre.rc.0 → 16.13.0.pre.rc.1
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/pb_form_validation.js +12 -7
- data/app/pb_kits/playbook/pb_text_input/text_input.html.erb +7 -9
- data/app/pb_kits/playbook/pb_text_input/text_input.rb +4 -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/dist/chunks/vendor.js +1 -1
- data/dist/menu.yml +0 -6
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/forms/builder/form_field_builder.rb +2 -0
- data/lib/playbook/version.rb +1 -1
- metadata +4 -17
- 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: 888568d7c9aa8f1d5944438d4bb6f2916650c9f7bd80d32c0d912a2b209118f5
|
|
4
|
+
data.tar.gz: c97e4908f8ae4da80bf23994a4994e4f5647fb05f426f913cd74710274826026
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bed14c811f50da03eaaef7847b5ad6310e41f7cf121ce61d22c45715222edcc28c8b6da11c02a598cdf1cfd175a5c3f19c4d2f8a327d866cad41375cd37c5c84
|
|
7
|
+
data.tar.gz: 1d58072414ac82162cec930707d1182ac3c4d0f8ca64d4febf5e521091e43c78b5d298678683112354a60ee8c40f3d36f43cfcebea60640a91fdd20c7dfb6465
|
|
@@ -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"}
|
|
@@ -4,6 +4,7 @@ import { debounce } from '../utilities/object'
|
|
|
4
4
|
// Kit selectors
|
|
5
5
|
const KIT_SELECTOR = '[class^="pb_"][class*="_kit"]'
|
|
6
6
|
const ERROR_MESSAGE_SELECTOR = '.pb_body_kit_negative'
|
|
7
|
+
const MESSAGE_CONTAINER_SELECTOR = '[data-pb-validation-container="true"]'
|
|
7
8
|
|
|
8
9
|
// Validation selectors
|
|
9
10
|
const FORM_SELECTOR = 'form[data-pb-form-validation="true"]'
|
|
@@ -63,8 +64,8 @@ class PbFormValidation extends PbEnhancedElement {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
showValidationMessage(target) {
|
|
66
|
-
const
|
|
67
|
-
const kitElement =
|
|
67
|
+
const messageContainer = this.messageContainerFor(target)
|
|
68
|
+
const kitElement = messageContainer.closest(KIT_SELECTOR)
|
|
68
69
|
|
|
69
70
|
// FIX: Add null check for kitElement
|
|
70
71
|
if (!kitElement) return
|
|
@@ -89,20 +90,24 @@ class PbFormValidation extends PbEnhancedElement {
|
|
|
89
90
|
errorMessageContainer.innerHTML = target.validationMessage
|
|
90
91
|
|
|
91
92
|
// add the error message element to the dom tree
|
|
92
|
-
|
|
93
|
+
messageContainer.appendChild(errorMessageContainer)
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
clearError(target) {
|
|
97
|
-
const
|
|
98
|
-
const kitElement =
|
|
98
|
+
const messageContainer = this.messageContainerFor(target)
|
|
99
|
+
const kitElement = messageContainer.closest(KIT_SELECTOR)
|
|
99
100
|
// Remove error class from kit element
|
|
100
101
|
if (kitElement) kitElement.classList.remove('error')
|
|
101
|
-
// Remove error message from parent element
|
|
102
|
-
const errorMessageContainer =
|
|
102
|
+
// Remove error message from the message container (which is the parent element most of thetime)
|
|
103
|
+
const errorMessageContainer = messageContainer.querySelector(ERROR_MESSAGE_SELECTOR)
|
|
103
104
|
if (errorMessageContainer) errorMessageContainer.remove()
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
messageContainerFor(target) {
|
|
108
|
+
return target.closest(MESSAGE_CONTAINER_SELECTOR) || target.parentElement
|
|
109
|
+
}
|
|
110
|
+
|
|
106
111
|
// Check if there are phone number input errors
|
|
107
112
|
hasPhoneNumberValidationErrors() {
|
|
108
113
|
const phoneNumberErrors = this.element.querySelectorAll(PHONE_NUMBER_VALIDATION_ERROR_SELECTOR)
|
|
@@ -11,19 +11,17 @@
|
|
|
11
11
|
</label>
|
|
12
12
|
<% end %>
|
|
13
13
|
|
|
14
|
-
<%= content_tag(:div, class: "#{add_on_class} text_input_wrapper") do %>
|
|
15
|
-
<%
|
|
16
|
-
|
|
17
|
-
<% elsif has_add_on? %>
|
|
14
|
+
<%= content_tag(:div, class: "#{add_on_class} text_input_wrapper", data: { pb_validation_container: true }) do %>
|
|
15
|
+
<% field = content.presence || input_tag %>
|
|
16
|
+
<% if has_add_on? %>
|
|
18
17
|
<%= pb_rails("text_input/add_on", props: object.add_on_props) do %>
|
|
19
|
-
<%=
|
|
18
|
+
<%= field %>
|
|
20
19
|
<% end %>
|
|
21
|
-
<% elsif mask.present? %>
|
|
22
|
-
<%= input_tag %>
|
|
23
|
-
<%= tag(:input, data: "sanitized-pb-input", id: sanitized_id, name: object.name, style: "display: none;") %>
|
|
24
20
|
<% else %>
|
|
25
|
-
<%=
|
|
21
|
+
<%= field %>
|
|
26
22
|
<% end %>
|
|
23
|
+
<%# A masked input gives up its name, so the sanitized value is submitted through this hidden input. %>
|
|
24
|
+
<%= sanitized_input_tag if mask.present? && content.blank? %>
|
|
27
25
|
<%= pb_rails("body", props: {dark: object.dark, status: "negative", text: object.error, id: object.error_id, aria: { atomic: "true", live: "polite" }, html_options: { role: "alert" }}) if object.error %>
|
|
28
26
|
<% end %>
|
|
29
27
|
<% end %>
|
|
@@ -92,8 +92,8 @@ const Timestamp = (props: TimestampProps): React.ReactElement => {
|
|
|
92
92
|
const finalUpdatedString = []
|
|
93
93
|
if (shouldShowUser) finalUpdatedString.push(`by ${text}`)
|
|
94
94
|
if (showDate && !showTime) finalUpdatedString.push(`on ${baseDateDisplay()}`)
|
|
95
|
-
if (showDate && showTime) finalUpdatedString.push(`on ${baseDateDisplay()} at ${
|
|
96
|
-
if (showTime && !showDate) finalUpdatedString.push(`at ${
|
|
95
|
+
if (showDate && showTime) finalUpdatedString.push(`on ${baseDateDisplay()} at ${fullTimeDisplay()}`)
|
|
96
|
+
if (showTime && !showDate) finalUpdatedString.push(`at ${fullTimeDisplay()}`)
|
|
97
97
|
return `Last updated ${finalUpdatedString.join(' ')}`
|
|
98
98
|
}
|
|
99
99
|
|
|
@@ -12,3 +12,22 @@
|
|
|
12
12
|
variant: "updated",
|
|
13
13
|
show_user: false
|
|
14
14
|
}) %>
|
|
15
|
+
|
|
16
|
+
<br>
|
|
17
|
+
|
|
18
|
+
<%= pb_rails("timestamp", props: {
|
|
19
|
+
show_timezone: true,
|
|
20
|
+
timestamp: DateTime.now,
|
|
21
|
+
timezone: "America/New_York",
|
|
22
|
+
variant: "updated",
|
|
23
|
+
}) %>
|
|
24
|
+
|
|
25
|
+
<br />
|
|
26
|
+
|
|
27
|
+
<%= pb_rails("timestamp", props: {
|
|
28
|
+
show_date: false,
|
|
29
|
+
show_timezone: true,
|
|
30
|
+
timestamp: DateTime.now,
|
|
31
|
+
timezone: "Asia/Hong_Kong",
|
|
32
|
+
variant: "updated",
|
|
33
|
+
}) %>
|
|
@@ -21,6 +21,27 @@ const TimestampUpdated = (props) => {
|
|
|
21
21
|
variant="updated"
|
|
22
22
|
{...props}
|
|
23
23
|
/>
|
|
24
|
+
|
|
25
|
+
<br />
|
|
26
|
+
|
|
27
|
+
<Timestamp
|
|
28
|
+
showTimezone
|
|
29
|
+
timestamp={todaysDate}
|
|
30
|
+
timezone="America/New_York"
|
|
31
|
+
variant="updated"
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
<br />
|
|
36
|
+
|
|
37
|
+
<Timestamp
|
|
38
|
+
showDate={false}
|
|
39
|
+
showTimezone
|
|
40
|
+
timestamp={todaysDate}
|
|
41
|
+
timezone="Asia/Hong_Kong"
|
|
42
|
+
variant="updated"
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
24
45
|
</div>
|
|
25
46
|
)
|
|
26
47
|
}
|
|
@@ -2,4 +2,6 @@ Use variant `updated` to show last updated at timestamp.
|
|
|
2
2
|
|
|
3
3
|
This variant can be customized using the optional `showUser`/`show_user` prop to show user as part of the text. When showing the user, pass in the user name using the `text` prop as shown.
|
|
4
4
|
|
|
5
|
-
`showUser`/`show_user` is set to false by default.
|
|
5
|
+
`showUser`/`show_user` is set to false by default.
|
|
6
|
+
|
|
7
|
+
This variant can also display timezone when the `showTimezone`/`show_timezone` and `timezone` props are used together as instructed in the Show Timezones example above.
|
|
@@ -271,3 +271,45 @@ describe("Timestamp Kit", () => {
|
|
|
271
271
|
expect(text?.textContent).toEqual("Last updated at 12:00a")
|
|
272
272
|
})
|
|
273
273
|
|
|
274
|
+
test("updated variant: shows timezone when showTimezone is enabled", () => {
|
|
275
|
+
render(
|
|
276
|
+
<Timestamp
|
|
277
|
+
data={{ testid: testId }}
|
|
278
|
+
showTimezone
|
|
279
|
+
timestamp={new Date()}
|
|
280
|
+
timezone="America/New_York"
|
|
281
|
+
variant="updated"
|
|
282
|
+
/>
|
|
283
|
+
)
|
|
284
|
+
const text = screen.getByTestId(testId).querySelector(".pb_caption_kit_xs")
|
|
285
|
+
expect(text?.textContent).toEqual("Last updated on Jan 1 at 12:00a EST")
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
test("updated variant: shows timezone with time only when showDate=false", () => {
|
|
289
|
+
render(
|
|
290
|
+
<Timestamp
|
|
291
|
+
data={{ testid: testId }}
|
|
292
|
+
showDate={false}
|
|
293
|
+
showTimezone
|
|
294
|
+
timestamp={new Date()}
|
|
295
|
+
timezone="America/New_York"
|
|
296
|
+
variant="updated"
|
|
297
|
+
/>
|
|
298
|
+
)
|
|
299
|
+
const text = screen.getByTestId(testId).querySelector(".pb_caption_kit_xs")
|
|
300
|
+
expect(text?.textContent).toEqual("Last updated at 12:00a EST")
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
test("updated variant: hides timezone when showTimezone is not provided", () => {
|
|
304
|
+
render(
|
|
305
|
+
<Timestamp
|
|
306
|
+
data={{ testid: testId }}
|
|
307
|
+
timestamp={new Date()}
|
|
308
|
+
timezone="America/New_York"
|
|
309
|
+
variant="updated"
|
|
310
|
+
/>
|
|
311
|
+
)
|
|
312
|
+
const text = screen.getByTestId(testId).querySelector(".pb_caption_kit_xs")
|
|
313
|
+
expect(text?.textContent).toEqual("Last updated on Jan 1 at 12:00a")
|
|
314
|
+
})
|
|
315
|
+
|