playbook_ui 16.4.0.pre.alpha.play2838formcustomvalidationsconsistency15153 → 16.4.0.pre.alpha.play2838formcustomvalidationsconsistency15196
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 +19 -0
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +3 -0
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.rb +7 -0
- data/app/pb_kits/playbook/pb_advanced_table/scss_partials/advanced_table_sticky_mixin.scss +6 -2
- data/app/pb_kits/playbook/pb_button/docs/_button_full_width_rails.md +19 -0
- data/app/pb_kits/playbook/pb_button/docs/_button_full_width_react.md +23 -0
- data/app/pb_kits/playbook/pb_circle_icon_button/_circle_icon_button.scss +5 -0
- data/app/pb_kits/playbook/pb_form/pb_form_validation.js +67 -14
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +3 -1
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_placeholder.html.erb +109 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_placeholder.jsx +127 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_placeholder.md +1 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.rb +3 -0
- data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +27 -0
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_with_highlight.jsx +20 -8
- data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_with_highlight.md +3 -0
- data/dist/chunks/_typeahead-BNp_YiTh.js +1 -0
- 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/pb_forms_helper.rb +20 -2
- data/lib/playbook/version.rb +1 -1
- metadata +9 -4
- data/app/pb_kits/playbook/pb_button/docs/_button_full_width.md +0 -1
- data/dist/chunks/_typeahead-Bh0RF1X-.js +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 86ebb202571d08c83e9c57eb23414f38c9b981fd7447511524aae6212a6121a5
|
|
4
|
+
data.tar.gz: 0d6de802cd0b66ac46899ab8b21aa57aa81e2865df6cdcd7dd4868d16b16aac8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4db3ecfcf536c5a10d212451418d22bd51b83a3568bfaf2bdc56a7ed0a82650ee0e5598fe1254c5aecb436fcb9ee998774fcd533f52c808df027961e905f5aa7
|
|
7
|
+
data.tar.gz: f349ae9d1ad82c6ae2b958fc213c1b4e17d37f0d750da01c71c21d0e313369b8e11c85b5a44faa302e19e562f18d0c0efa464f010fa94e5109ae61ed7855a632
|
|
@@ -674,12 +674,18 @@
|
|
|
674
674
|
@each $color_name, $color_value in $border_color_options {
|
|
675
675
|
&.column-group-border-#{$color_name} {
|
|
676
676
|
@if $theme == "light" {
|
|
677
|
+
&:not(.advanced-table-no-table-container) {
|
|
678
|
+
@include advanced-table-sticky-wrapper-frame($color_value);
|
|
679
|
+
}
|
|
677
680
|
@include advanced-table-sticky-mixin(
|
|
678
681
|
$color_value,
|
|
679
682
|
$white,
|
|
680
683
|
lighten($silver, $opacity_7)
|
|
681
684
|
);
|
|
682
685
|
} @else if $theme == "dark" {
|
|
686
|
+
&:not(.advanced-table-no-table-container) {
|
|
687
|
+
@include advanced-table-sticky-wrapper-frame($color_value);
|
|
688
|
+
}
|
|
683
689
|
@include advanced-table-sticky-mixin(
|
|
684
690
|
$color_value,
|
|
685
691
|
$bg_dark_card,
|
|
@@ -715,6 +721,10 @@
|
|
|
715
721
|
width: 100%;
|
|
716
722
|
@include scrollbar-styling;
|
|
717
723
|
|
|
724
|
+
&:not(.advanced-table-no-table-container) {
|
|
725
|
+
@include advanced-table-sticky-wrapper-frame($border_light);
|
|
726
|
+
}
|
|
727
|
+
|
|
718
728
|
// These are the responsive borders that should NOT inherit the custom color
|
|
719
729
|
@include advanced-table-sticky-mixin(
|
|
720
730
|
$border_light,
|
|
@@ -1008,6 +1018,11 @@
|
|
|
1008
1018
|
.sticky-left {
|
|
1009
1019
|
background-color: $bg_dark;
|
|
1010
1020
|
}
|
|
1021
|
+
|
|
1022
|
+
&:not(.advanced-table-no-table-container) {
|
|
1023
|
+
@include advanced-table-sticky-wrapper-frame($border_dark);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1011
1026
|
@include advanced-table-sticky-mixin(
|
|
1012
1027
|
$border_dark,
|
|
1013
1028
|
$bg_dark_card,
|
|
@@ -1026,6 +1041,10 @@
|
|
|
1026
1041
|
}
|
|
1027
1042
|
}
|
|
1028
1043
|
|
|
1044
|
+
&:not(.advanced-table-no-table-container) {
|
|
1045
|
+
@include advanced-table-sticky-wrapper-frame($border_dark);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1029
1048
|
// These are the responsive borders that should NOT inherit the custom color
|
|
1030
1049
|
@include advanced-table-sticky-mixin(
|
|
1031
1050
|
$border_dark,
|
|
@@ -127,6 +127,8 @@ const AdvancedTable = (props: AdvancedTableProps) => {
|
|
|
127
127
|
fullScreenControl,
|
|
128
128
|
} = props;
|
|
129
129
|
|
|
130
|
+
const noTableCardContainer = tableProps?.container === false;
|
|
131
|
+
|
|
130
132
|
// Component refs
|
|
131
133
|
const tableWrapperRef = useRef<HTMLDivElement>(null);
|
|
132
134
|
|
|
@@ -281,6 +283,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
|
|
|
281
283
|
'hidden-action-bar': (selectableRows || columnVisibilityControl) && !isActionBarVisible,
|
|
282
284
|
},
|
|
283
285
|
{'advanced-table-sticky-left-columns': stickyLeftColumn && stickyLeftColumn.length > 0},
|
|
286
|
+
{ 'advanced-table-no-table-container': noTableCardContainer },
|
|
284
287
|
columnGroupBorderColor ? `column-group-border-${columnGroupBorderColor}` : '',
|
|
285
288
|
scrollBarNone ? 'advanced-table-hide-scrollbar' : '',
|
|
286
289
|
globalProps(props),
|
|
@@ -48,9 +48,16 @@ module Playbook
|
|
|
48
48
|
hidden_action_bar_class,
|
|
49
49
|
]
|
|
50
50
|
additional_classes << "column-group-border-#{column_group_border_color}" if column_group_border_color != "none"
|
|
51
|
+
additional_classes << "advanced-table-no-table-container" if no_table_card_container?
|
|
51
52
|
generate_classname("pb_advanced_table", *additional_classes, separator: " ")
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
def no_table_card_container?
|
|
56
|
+
return false unless table_props.is_a?(Hash)
|
|
57
|
+
|
|
58
|
+
table_props[:container] == false || table_props["container"] == false
|
|
59
|
+
end
|
|
60
|
+
|
|
54
61
|
def responsive_classname
|
|
55
62
|
responsive == "scroll" ? "advanced-table-responsive-scroll" : "advanced-table-responsive-none"
|
|
56
63
|
end
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// Acts as outer “card frame” on the advanced-table wrapper (table-card from Table) is included from `_advanced_table.scss` only when `:not(.advanced-table-no-table-container)`/container: false is not present.
|
|
2
|
+
@mixin advanced-table-sticky-wrapper-frame($border-color) {
|
|
3
|
+
border-radius: 4px;
|
|
4
|
+
box-shadow: 1px 0 0 0px $border-color, -1px 0 0 0px $border-color;
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
@mixin advanced-table-sticky-mixin(
|
|
2
8
|
$border-color,
|
|
3
9
|
$bg-main,
|
|
@@ -5,8 +11,6 @@
|
|
|
5
11
|
$highlight: #E5EEFA,
|
|
6
12
|
$highlight-dark: #202850,
|
|
7
13
|
) {
|
|
8
|
-
border-radius: 4px;
|
|
9
|
-
box-shadow: 1px 0 0 0px $border-color, -1px 0 0 0px $border-color;
|
|
10
14
|
display: block;
|
|
11
15
|
[class^="pb_table"].table-sm.table-card thead tr th:first-child,
|
|
12
16
|
[class^="pb_table"].table-sm:not(.no-hover).table-card
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
This button is used many times for mobile or other things like cards and sidebars.
|
|
2
|
+
|
|
3
|
+
### Responsive `display` and `full_width`
|
|
4
|
+
|
|
5
|
+
`full_width` applies block styling that includes `display: flex` on the **same element** as the button. The **`display` global prop** also sets `display` (via utility classes, often with `!important`).
|
|
6
|
+
|
|
7
|
+
Putting **both** on one button means **two systems control `display` on one node**, which can cause wrong visibility (e.g. both a header and a full-width mobile button showing) or confusing cascade behavior.
|
|
8
|
+
|
|
9
|
+
**Recommended:** Put responsive `display` on a **parent** (e.g. `Flex`, `Card`, or a plain wrapper) and keep `full_width` only on the `Button` inside. The wrapper handles show/hide by breakpoint; the button only handles full-width layout.
|
|
10
|
+
|
|
11
|
+
```erb
|
|
12
|
+
<%= pb_rails("flex", props: {
|
|
13
|
+
display: { xs: "flex", default: "none" },
|
|
14
|
+
orientation: "column",
|
|
15
|
+
width: "100%",
|
|
16
|
+
}) do %>
|
|
17
|
+
<%= pb_rails("button", props: { full_width: true, text: "Add" }) %>
|
|
18
|
+
<% end %>
|
|
19
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
This button is used many times for mobile or other things like cards and sidebars.
|
|
2
|
+
|
|
3
|
+
### Responsive `display` and `full_width`
|
|
4
|
+
|
|
5
|
+
`full_width` applies block styling that includes `display: flex` on the **same element** as the button. The **`display` global prop** also sets `display` (via utility classes, often with `!important`).
|
|
6
|
+
|
|
7
|
+
Putting **both** on one button means **two systems control `display` on one node**, which can cause wrong visibility (e.g. both a header and a full-width mobile button showing) or confusing cascade behavior.
|
|
8
|
+
|
|
9
|
+
**Recommended:** Put responsive `display` on a **parent** (e.g. `Flex`, `Card`, or a plain wrapper) and keep `fullWidth` only on the `Button` inside. The wrapper handles show/hide by breakpoint; the button only handles full-width layout.
|
|
10
|
+
|
|
11
|
+
```jsx
|
|
12
|
+
import { Flex, Button } from "playbook-ui"
|
|
13
|
+
|
|
14
|
+
const Example = () => (
|
|
15
|
+
<Flex
|
|
16
|
+
display={{ xs: "flex", default: "none" }}
|
|
17
|
+
orientation="column"
|
|
18
|
+
width="100%"
|
|
19
|
+
>
|
|
20
|
+
<Button fullWidth text="Add" />
|
|
21
|
+
</Flex>
|
|
22
|
+
)
|
|
23
|
+
```
|
|
@@ -22,6 +22,12 @@ class PbFormValidation extends PbEnhancedElement {
|
|
|
22
22
|
return FORM_SELECTOR
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
static start() {
|
|
26
|
+
if (this.__pbStarted) return
|
|
27
|
+
this.__pbStarted = true
|
|
28
|
+
super.start()
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
connect() {
|
|
26
32
|
this.boundFields = new WeakSet()
|
|
27
33
|
this.bindValidationListeners()
|
|
@@ -65,18 +71,22 @@ class PbFormValidation extends PbEnhancedElement {
|
|
|
65
71
|
const isTimePickerInput = field.closest('.pb_time_picker')
|
|
66
72
|
if (isTimePickerInput) return
|
|
67
73
|
|
|
68
|
-
// Reset
|
|
74
|
+
// Reset any previous custom validity before checking validity.
|
|
69
75
|
field.setCustomValidity('')
|
|
70
76
|
const kitElement = this.getKitElement(field)
|
|
71
|
-
const message = this.getValidationMessage(field, kitElement)
|
|
72
|
-
if (message) field.setCustomValidity(message)
|
|
73
77
|
|
|
74
78
|
if (field.validity.valid) {
|
|
75
79
|
this.clearError(field)
|
|
76
|
-
|
|
77
|
-
foundInvalid = true
|
|
78
|
-
this.showValidationMessage(field)
|
|
80
|
+
return
|
|
79
81
|
}
|
|
82
|
+
|
|
83
|
+
// Only set custom message when invalid (otherwise the field becomes
|
|
84
|
+
// permanently invalid).
|
|
85
|
+
const message = this.getValidationMessage(field, kitElement)
|
|
86
|
+
if (message) field.setCustomValidity(message)
|
|
87
|
+
|
|
88
|
+
foundInvalid = true
|
|
89
|
+
this.showValidationMessage(field)
|
|
80
90
|
})
|
|
81
91
|
|
|
82
92
|
return foundInvalid
|
|
@@ -143,15 +153,16 @@ class PbFormValidation extends PbEnhancedElement {
|
|
|
143
153
|
showValidationMessage(target) {
|
|
144
154
|
const { parentElement } = target
|
|
145
155
|
const kitElement = this.getKitElement(target)
|
|
146
|
-
if (!kitElement) return
|
|
147
156
|
|
|
148
|
-
const isPhoneNumberInput = kitElement
|
|
149
|
-
|
|
150
|
-
const isTimePickerInput = kitElement.classList.contains('pb_time_picker')
|
|
157
|
+
const isPhoneNumberInput = kitElement?.classList?.contains('pb_phone_number_input') || false
|
|
158
|
+
const isTimePickerInput = kitElement?.classList?.contains('pb_time_picker') || false
|
|
151
159
|
|
|
152
160
|
// ensure clean error message state
|
|
153
161
|
this.clearError(target)
|
|
154
|
-
kitElement.classList.add('error')
|
|
162
|
+
if (kitElement) kitElement.classList.add('error')
|
|
163
|
+
|
|
164
|
+
const controlWrapper = this.getControlWrapper(target, kitElement)
|
|
165
|
+
if (controlWrapper) controlWrapper.classList.add('error')
|
|
155
166
|
|
|
156
167
|
if (!isPhoneNumberInput && !isTimePickerInput) {
|
|
157
168
|
const errorParent = this.getErrorParent(target, kitElement, parentElement)
|
|
@@ -183,6 +194,9 @@ class PbFormValidation extends PbEnhancedElement {
|
|
|
183
194
|
const kitElement = this.getKitElement(target)
|
|
184
195
|
if (kitElement) kitElement.classList.remove('error')
|
|
185
196
|
|
|
197
|
+
const controlWrapper = this.getControlWrapper(target, kitElement)
|
|
198
|
+
if (controlWrapper) controlWrapper.classList.remove('error')
|
|
199
|
+
|
|
186
200
|
const errorParent = this.getErrorParent(target, kitElement, parentElement)
|
|
187
201
|
const messageEl = errorParent.querySelector(`[${FORM_VALIDATION_MESSAGE_ATTR}="true"]`)
|
|
188
202
|
if (messageEl) {
|
|
@@ -201,6 +215,9 @@ class PbFormValidation extends PbEnhancedElement {
|
|
|
201
215
|
const candidate =
|
|
202
216
|
target.closest('.text_input_wrapper') ||
|
|
203
217
|
target.closest('.pb_select_kit_wrapper') ||
|
|
218
|
+
target.closest('.dropdown_wrapper') ||
|
|
219
|
+
target.closest('.input_wrapper') ||
|
|
220
|
+
target.closest('.pb_typeahead_wrapper') ||
|
|
204
221
|
kitElement ||
|
|
205
222
|
parentElement
|
|
206
223
|
|
|
@@ -211,6 +228,18 @@ class PbFormValidation extends PbEnhancedElement {
|
|
|
211
228
|
return candidate
|
|
212
229
|
}
|
|
213
230
|
|
|
231
|
+
getControlWrapper(target, kitElement) {
|
|
232
|
+
// Some kits apply error styles to a specific wrapper, not the outer kit element.
|
|
233
|
+
return (
|
|
234
|
+
target.closest('.dropdown_wrapper') ||
|
|
235
|
+
target.closest('.pb_select_kit_wrapper') ||
|
|
236
|
+
target.closest('.text_input_wrapper') ||
|
|
237
|
+
kitElement?.querySelector?.('.dropdown_wrapper') ||
|
|
238
|
+
kitElement?.querySelector?.('.pb_select_kit_wrapper') ||
|
|
239
|
+
null
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
214
243
|
isReactTypeaheadField(el) {
|
|
215
244
|
return !!(
|
|
216
245
|
el?.closest?.('[data-pb-react-component="Typeahead"]') ||
|
|
@@ -229,7 +258,15 @@ class PbFormValidation extends PbEnhancedElement {
|
|
|
229
258
|
}
|
|
230
259
|
|
|
231
260
|
getKitElement(target) {
|
|
232
|
-
return
|
|
261
|
+
return (
|
|
262
|
+
target.closest(KIT_SELECTOR) ||
|
|
263
|
+
target.parentElement?.closest(KIT_SELECTOR) ||
|
|
264
|
+
// Some kits don't expose a *_kit class but do expose data hooks.
|
|
265
|
+
target.closest('[data-pb-select]') ||
|
|
266
|
+
target.closest('[data-pb-date-picker]') ||
|
|
267
|
+
target.closest('[data-pb-typeahead-kit]') ||
|
|
268
|
+
null
|
|
269
|
+
)
|
|
233
270
|
}
|
|
234
271
|
|
|
235
272
|
getValidationMessage(target, kitElement) {
|
|
@@ -237,7 +274,9 @@ class PbFormValidation extends PbEnhancedElement {
|
|
|
237
274
|
|
|
238
275
|
const wrapperWithMessage =
|
|
239
276
|
target.closest?.('[data-validation-message]') ||
|
|
240
|
-
target.closest?.('[data-pb-select]')
|
|
277
|
+
target.closest?.('[data-pb-select]') ||
|
|
278
|
+
target.closest?.('.dropdown_wrapper') ||
|
|
279
|
+
target.closest?.('.pb_select_kit_wrapper')
|
|
241
280
|
const fromWrapper = wrapperWithMessage?.dataset?.validationMessage
|
|
242
281
|
|
|
243
282
|
const fromKit = kitElement?.dataset?.validationMessage
|
|
@@ -266,4 +305,18 @@ class PbFormValidation extends PbEnhancedElement {
|
|
|
266
305
|
}
|
|
267
306
|
}
|
|
268
307
|
|
|
269
|
-
window.PbFormValidation = PbFormValidation
|
|
308
|
+
window.PbFormValidation = PbFormValidation
|
|
309
|
+
|
|
310
|
+
// In consuming Rails apps, `DOMContentLoaded` may not fire on navigation when
|
|
311
|
+
// Turbo is enabled. Autostart ensures validation works consistently across repos
|
|
312
|
+
// as long as this bundle is loaded.
|
|
313
|
+
const __pbStartFormValidation = () => {
|
|
314
|
+
if (!window.PbFormValidation || typeof window.PbFormValidation.start !== 'function') return
|
|
315
|
+
if (window.__pbFormValidationStarted) return
|
|
316
|
+
window.__pbFormValidationStarted = true
|
|
317
|
+
window.PbFormValidation.start()
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
document.addEventListener('DOMContentLoaded', __pbStartFormValidation)
|
|
321
|
+
document.addEventListener('turbo:load', __pbStartFormValidation)
|
|
322
|
+
document.addEventListener('turbo:render', __pbStartFormValidation)
|
|
@@ -51,6 +51,7 @@ type MultiLevelSelectProps = {
|
|
|
51
51
|
treeData?: { [key: string]: string }[] | any;
|
|
52
52
|
onChange?: (event: { target: { name?: string; value: any } }) => void;
|
|
53
53
|
onSelect?: (prop: { [key: string]: any }) => void;
|
|
54
|
+
placeholder?: string;
|
|
54
55
|
selectedIds?: string[] | any;
|
|
55
56
|
variant?: "multi" | "single";
|
|
56
57
|
wrapped?: boolean;
|
|
@@ -100,6 +101,7 @@ const MultiLevelSelect = forwardRef<HTMLInputElement, MultiLevelSelectProps>(
|
|
|
100
101
|
treeData,
|
|
101
102
|
onChange = () => null,
|
|
102
103
|
onSelect = () => null,
|
|
104
|
+
placeholder: placeholderText = "Start typing...",
|
|
103
105
|
selectedIds,
|
|
104
106
|
variant = "multi",
|
|
105
107
|
wrapped,
|
|
@@ -675,7 +677,7 @@ const MultiLevelSelect = forwardRef<HTMLInputElement, MultiLevelSelectProps>(
|
|
|
675
677
|
? `${itemsSelectedLength()} ${
|
|
676
678
|
itemsSelectedLength() === 1 ? "item" : "items"
|
|
677
679
|
} selected`
|
|
678
|
-
:
|
|
680
|
+
: placeholderText
|
|
679
681
|
}
|
|
680
682
|
required={required}
|
|
681
683
|
value={singleSelectedItem.value || filterItem}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<%
|
|
2
|
+
tree_base = [{
|
|
3
|
+
label: "Power Home Remodeling",
|
|
4
|
+
value: "powerHomeRemodeling",
|
|
5
|
+
id: "100",
|
|
6
|
+
expanded: true,
|
|
7
|
+
children: [
|
|
8
|
+
{
|
|
9
|
+
label: "People",
|
|
10
|
+
value: "people",
|
|
11
|
+
id: "101",
|
|
12
|
+
expanded: true,
|
|
13
|
+
children: [
|
|
14
|
+
{
|
|
15
|
+
label: "Talent Acquisition",
|
|
16
|
+
value: "talentAcquisition",
|
|
17
|
+
id: "102",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
label: "Business Affairs",
|
|
21
|
+
value: "business Affairs",
|
|
22
|
+
id: "103",
|
|
23
|
+
children: [
|
|
24
|
+
{
|
|
25
|
+
label: "Initiatives",
|
|
26
|
+
value: "initiatives",
|
|
27
|
+
id: "104",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
label: "Learning & Development",
|
|
31
|
+
value: "learningAndDevelopment",
|
|
32
|
+
id: "105",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
label: "People Experience",
|
|
38
|
+
value: "peopleExperience",
|
|
39
|
+
id: "106",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
label: "Contact Center",
|
|
45
|
+
value: "contactCenter",
|
|
46
|
+
id: "107",
|
|
47
|
+
children: [
|
|
48
|
+
{
|
|
49
|
+
label: "Appointment Management",
|
|
50
|
+
value: "appointmentManagement",
|
|
51
|
+
id: "108",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
label: "Customer Service",
|
|
55
|
+
value: "customerService",
|
|
56
|
+
id: "109",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
label: "Energy",
|
|
60
|
+
value: "energy",
|
|
61
|
+
id: "110",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
}]
|
|
67
|
+
|
|
68
|
+
prefix_mls_ids = nil
|
|
69
|
+
prefix_mls_ids = ->(nodes, pfx) {
|
|
70
|
+
nodes.map do |n|
|
|
71
|
+
h = n.dup
|
|
72
|
+
h[:id] = "#{pfx}#{n[:id]}"
|
|
73
|
+
h[:children] = prefix_mls_ids.call(n[:children], pfx) if n[:children].present?
|
|
74
|
+
h
|
|
75
|
+
end
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
tree_multi = prefix_mls_ids.call(tree_base, "phm_")
|
|
79
|
+
tree_return_all = prefix_mls_ids.call(tree_base, "phr_")
|
|
80
|
+
tree_single = prefix_mls_ids.call(tree_base, "phs_")
|
|
81
|
+
%>
|
|
82
|
+
|
|
83
|
+
<%= pb_rails("multi_level_select", props: {
|
|
84
|
+
id: "multi-level-select-placeholder-multi-rails",
|
|
85
|
+
label: "Multi (default)",
|
|
86
|
+
margin_bottom: "sm",
|
|
87
|
+
name: "placeholder_multi",
|
|
88
|
+
tree_data: tree_multi,
|
|
89
|
+
placeholder: "Search or choose options…",
|
|
90
|
+
}) %>
|
|
91
|
+
|
|
92
|
+
<%= pb_rails("multi_level_select", props: {
|
|
93
|
+
id: "multi-level-select-placeholder-return-all-rails",
|
|
94
|
+
label: "Multi (return all selected)",
|
|
95
|
+
margin_bottom: "sm",
|
|
96
|
+
name: "placeholder_return_all",
|
|
97
|
+
placeholder: "Departments...",
|
|
98
|
+
return_all_selected: true,
|
|
99
|
+
tree_data: tree_return_all,
|
|
100
|
+
}) %>
|
|
101
|
+
|
|
102
|
+
<%= pb_rails("multi_level_select", props: {
|
|
103
|
+
id: "multi-level-select-placeholder-single-rails",
|
|
104
|
+
label: "Single",
|
|
105
|
+
name: "placeholder_single",
|
|
106
|
+
placeholder: "Select one option…",
|
|
107
|
+
tree_data: tree_single,
|
|
108
|
+
variant: "single",
|
|
109
|
+
}) %>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import MultiLevelSelect from "../_multi_level_select";
|
|
4
|
+
|
|
5
|
+
const treeTemplate = [
|
|
6
|
+
{
|
|
7
|
+
label: "Power Home Remodeling",
|
|
8
|
+
value: "powerHomeRemodeling",
|
|
9
|
+
id: "powerhome1",
|
|
10
|
+
expanded: true,
|
|
11
|
+
children: [
|
|
12
|
+
{
|
|
13
|
+
label: "People",
|
|
14
|
+
value: "people",
|
|
15
|
+
id: "people1",
|
|
16
|
+
expanded: true,
|
|
17
|
+
children: [
|
|
18
|
+
{
|
|
19
|
+
label: "Talent Acquisition",
|
|
20
|
+
value: "talentAcquisition",
|
|
21
|
+
id: "talent1",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: "Business Affairs",
|
|
25
|
+
value: "businessAffairs",
|
|
26
|
+
id: "business1",
|
|
27
|
+
children: [
|
|
28
|
+
{
|
|
29
|
+
label: "Initiatives",
|
|
30
|
+
value: "initiatives",
|
|
31
|
+
id: "initiative1",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: "Learning & Development",
|
|
35
|
+
value: "learningAndDevelopment",
|
|
36
|
+
id: "development1",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
label: "People Experience",
|
|
42
|
+
value: "peopleExperience",
|
|
43
|
+
id: "experience1",
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
label: "Contact Center",
|
|
49
|
+
value: "contactCenter",
|
|
50
|
+
id: "contact1",
|
|
51
|
+
children: [
|
|
52
|
+
{
|
|
53
|
+
label: "Appointment Management",
|
|
54
|
+
value: "appointmentManagement",
|
|
55
|
+
id: "appointment1",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
label: "Customer Service",
|
|
59
|
+
value: "customerService",
|
|
60
|
+
id: "customer1",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
label: "Energy",
|
|
64
|
+
value: "energy",
|
|
65
|
+
id: "energy1",
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
function prefixTreeIds(nodes, prefix) {
|
|
74
|
+
return nodes.map((node) => ({
|
|
75
|
+
...node,
|
|
76
|
+
id: `${prefix}${node.id}`,
|
|
77
|
+
children:
|
|
78
|
+
node.children && node.children.length > 0
|
|
79
|
+
? prefixTreeIds(node.children, prefix)
|
|
80
|
+
: node.children,
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const treeDataMulti = prefixTreeIds(treeTemplate, "phm_");
|
|
85
|
+
const treeDataReturnAll = prefixTreeIds(treeTemplate, "phr_");
|
|
86
|
+
const treeDataSingle = prefixTreeIds(treeTemplate, "phs_");
|
|
87
|
+
|
|
88
|
+
const MultiLevelSelectPlaceholder = () => (
|
|
89
|
+
<>
|
|
90
|
+
<MultiLevelSelect
|
|
91
|
+
id="multi-level-select-placeholder-multi"
|
|
92
|
+
label="Multi (default)"
|
|
93
|
+
marginBottom="sm"
|
|
94
|
+
name="placeholder_multi"
|
|
95
|
+
onSelect={(selectedNodes) =>
|
|
96
|
+
console.log("Multi — default", selectedNodes)
|
|
97
|
+
}
|
|
98
|
+
placeholder="Search or choose options…"
|
|
99
|
+
treeData={treeDataMulti}
|
|
100
|
+
/>
|
|
101
|
+
<MultiLevelSelect
|
|
102
|
+
id="multi-level-select-placeholder-return-all"
|
|
103
|
+
label="Multi (return all selected)"
|
|
104
|
+
marginBottom="sm"
|
|
105
|
+
name="placeholder_return_all"
|
|
106
|
+
onSelect={(selectedNodes) =>
|
|
107
|
+
console.log("Multi — return all selected", selectedNodes)
|
|
108
|
+
}
|
|
109
|
+
placeholder="Departments..."
|
|
110
|
+
returnAllSelected
|
|
111
|
+
treeData={treeDataReturnAll}
|
|
112
|
+
/>
|
|
113
|
+
<MultiLevelSelect
|
|
114
|
+
id="multi-level-select-placeholder-single"
|
|
115
|
+
label="Single"
|
|
116
|
+
name="placeholder_single"
|
|
117
|
+
onSelect={(selectedNodes) =>
|
|
118
|
+
console.log("Single", selectedNodes)
|
|
119
|
+
}
|
|
120
|
+
placeholder="Select one option…"
|
|
121
|
+
treeData={treeDataSingle}
|
|
122
|
+
variant="single"
|
|
123
|
+
/>
|
|
124
|
+
</>
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
export default MultiLevelSelectPlaceholder;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Use the `placeholder` prop to customize the initial text shown in the input when nothing is selected. The default is `Start typing...`.
|
|
@@ -18,6 +18,7 @@ examples:
|
|
|
18
18
|
- multi_level_select_disabled_options_parent: Disabled Parent Option (Return All Selected)
|
|
19
19
|
- multi_level_select_single_disabled: Disabled Options (Single Select)
|
|
20
20
|
- multi_level_select_required_indicator: Required Indicator
|
|
21
|
+
- multi_level_select_placeholder: Placeholder
|
|
21
22
|
|
|
22
23
|
react:
|
|
23
24
|
- multi_level_select_default: Default
|
|
@@ -40,3 +41,4 @@ examples:
|
|
|
40
41
|
- multi_level_select_single_disabled: Disabled Options (Single Select)
|
|
41
42
|
- multi_level_select_required_indicator: Required Indicator
|
|
42
43
|
- multi_level_select_react_reset_key: Reset with Key (React)
|
|
44
|
+
- multi_level_select_placeholder: Placeholder
|
|
@@ -18,3 +18,4 @@ export { default as MultiLevelSelectLabel } from "./_multi_level_select_label.js
|
|
|
18
18
|
export { default as MultiLevelSelectSingleDisabled } from "./_multi_level_select_single_disabled.jsx"
|
|
19
19
|
export { default as MultiLevelSelectRequiredIndicator } from "./_multi_level_select_required_indicator.jsx"
|
|
20
20
|
export { default as MultiLevelSelectReactResetKey } from "./_multi_level_select_react_reset_key.jsx"
|
|
21
|
+
export { default as MultiLevelSelectPlaceholder } from "./_multi_level_select_placeholder.jsx"
|
|
@@ -32,6 +32,8 @@ module Playbook
|
|
|
32
32
|
default: ""
|
|
33
33
|
prop :label, type: Playbook::Props::String,
|
|
34
34
|
default: ""
|
|
35
|
+
prop :placeholder, type: Playbook::Props::String,
|
|
36
|
+
default: "Start typing..."
|
|
35
37
|
prop :required_indicator, type: Playbook::Props::Boolean,
|
|
36
38
|
default: false
|
|
37
39
|
prop :show_checked_children, type: Playbook::Props::Boolean,
|
|
@@ -50,6 +52,7 @@ module Playbook
|
|
|
50
52
|
inputDisplay: input_display,
|
|
51
53
|
name: name,
|
|
52
54
|
label: label,
|
|
55
|
+
placeholder: placeholder,
|
|
53
56
|
treeData: tree_data,
|
|
54
57
|
required: required,
|
|
55
58
|
requiredIndicator: required_indicator,
|
|
@@ -173,6 +173,33 @@ describe('MultiLevelSelect', () => {
|
|
|
173
173
|
expect(label).toHaveTextContent("Select Location")
|
|
174
174
|
expect(label).not.toHaveTextContent("*")
|
|
175
175
|
})
|
|
176
|
+
|
|
177
|
+
test('should use default placeholder when none is passed', () => {
|
|
178
|
+
render(
|
|
179
|
+
<MultiLevelSelect
|
|
180
|
+
data={{ testid: testId }}
|
|
181
|
+
id="mls-placeholder-default"
|
|
182
|
+
treeData={treeData}
|
|
183
|
+
/>
|
|
184
|
+
)
|
|
185
|
+
expect(
|
|
186
|
+
screen.getByPlaceholderText('Start typing...')
|
|
187
|
+
).toBeInTheDocument()
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test('should use custom placeholder when passed', () => {
|
|
191
|
+
render(
|
|
192
|
+
<MultiLevelSelect
|
|
193
|
+
data={{ testid: testId }}
|
|
194
|
+
id="mls-placeholder-custom"
|
|
195
|
+
placeholder="Choose items…"
|
|
196
|
+
treeData={treeData}
|
|
197
|
+
/>
|
|
198
|
+
)
|
|
199
|
+
expect(
|
|
200
|
+
screen.getByPlaceholderText('Choose items…')
|
|
201
|
+
).toBeInTheDocument()
|
|
202
|
+
})
|
|
176
203
|
})
|
|
177
204
|
|
|
178
205
|
describe('MultiLevelSelect multi variant', () => {
|