playbook_ui 14.14.0.pre.alpha.play1755pbcontenttag6327 → 14.14.0.pre.alpha.play1755pbcontenttag6460

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_currency/_currency.tsx +46 -31
  3. data/app/pb_kits/playbook/pb_currency/currency.html.erb +15 -8
  4. data/app/pb_kits/playbook/pb_currency/currency.rb +17 -2
  5. data/app/pb_kits/playbook/pb_currency/docs/_currency_null_display.html.erb +22 -0
  6. data/app/pb_kits/playbook/pb_currency/docs/_currency_null_display.jsx +34 -0
  7. data/app/pb_kits/playbook/pb_currency/docs/_currency_null_display_rails.md +1 -0
  8. data/app/pb_kits/playbook/pb_currency/docs/_currency_null_display_react.md +1 -0
  9. data/app/pb_kits/playbook/pb_currency/docs/example.yml +2 -0
  10. data/app/pb_kits/playbook/pb_currency/docs/index.js +2 -1
  11. data/app/pb_kits/playbook/pb_drawer/_drawer.scss +32 -8
  12. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_behavior.html.erb +8 -0
  13. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_borders.html.erb +33 -0
  14. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_breakpoints.html.erb +0 -0
  15. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_default.html.erb +20 -1
  16. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_menu.html.erb +24 -0
  17. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_overlay.html.erb +21 -0
  18. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_overlay.md +1 -0
  19. data/app/pb_kits/playbook/pb_drawer/docs/_drawer_sizes.html.erb +49 -0
  20. data/app/pb_kits/playbook/pb_drawer/docs/example.yml +5 -0
  21. data/app/pb_kits/playbook/pb_drawer/drawer.html.erb +20 -12
  22. data/app/pb_kits/playbook/pb_drawer/drawer.rb +49 -1
  23. data/app/pb_kits/playbook/pb_drawer/index.js +257 -0
  24. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +1 -1
  25. data/app/pb_kits/playbook/pb_overlay/_overlay.scss +13 -0
  26. data/app/pb_kits/playbook/pb_overlay/_overlay.tsx +7 -1
  27. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_hide_scroll_bar.html.erb +11 -0
  28. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_hide_scroll_bar.jsx +37 -0
  29. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_hide_scroll_bar_rails.md +1 -0
  30. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_hide_scroll_bar_react.md +1 -0
  31. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_vertical_dynamic_multi_directional.html.erb +11 -0
  32. data/app/pb_kits/playbook/pb_overlay/docs/_overlay_vertical_dynamic_multi_directional.md +1 -0
  33. data/app/pb_kits/playbook/pb_overlay/docs/example.yml +3 -0
  34. data/app/pb_kits/playbook/pb_overlay/docs/index.js +1 -0
  35. data/app/pb_kits/playbook/pb_overlay/index.js +61 -0
  36. data/app/pb_kits/playbook/pb_overlay/overlay.html.erb +5 -3
  37. data/app/pb_kits/playbook/pb_overlay/overlay.rb +16 -1
  38. data/app/pb_kits/playbook/pb_overlay/overlay.test.jsx +12 -0
  39. data/app/pb_kits/playbook/pb_select/index.js +38 -0
  40. data/app/pb_kits/playbook/pb_select/select.html.erb +1 -1
  41. data/app/pb_kits/playbook/pb_select/select.rb +8 -0
  42. data/app/pb_kits/playbook/pb_tooltip/_tooltip.scss +0 -3
  43. data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_delay_rails.html.erb +39 -0
  44. data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_delay_rails.md +3 -0
  45. data/app/pb_kits/playbook/pb_tooltip/docs/_tooltip_interaction.html.erb +26 -0
  46. data/app/pb_kits/playbook/pb_tooltip/docs/example.yml +2 -0
  47. data/app/pb_kits/playbook/pb_tooltip/floating_ui.js +282 -0
  48. data/app/pb_kits/playbook/pb_tooltip/index.js +1 -1
  49. data/app/pb_kits/playbook/pb_tooltip/tooltip.rb +10 -2
  50. data/dist/chunks/{_typeahead-PqkcDf1H.js → _typeahead-BGTMEmj7.js} +2 -2
  51. data/dist/chunks/_weekday_stacked-BBzZIYVP.js +45 -0
  52. data/dist/chunks/{lib-D3us1bGD.js → lib-Fr78pbpF.js} +1 -1
  53. data/dist/chunks/{pb_form_validation-BpihMSOQ.js → pb_form_validation-CN51bfsD.js} +1 -1
  54. data/dist/chunks/vendor.js +1 -1
  55. data/dist/menu.yml +2 -2
  56. data/dist/playbook-doc.js +1 -1
  57. data/dist/playbook-rails-react-bindings.js +1 -1
  58. data/dist/playbook-rails.js +1 -1
  59. data/dist/playbook.css +1 -1
  60. data/lib/playbook/version.rb +1 -1
  61. metadata +30 -6
  62. data/dist/chunks/_weekday_stacked-C3in3hCo.js +0 -45
@@ -0,0 +1,257 @@
1
+ import PbEnhancedElement from "../pb_enhanced_element"
2
+
3
+ export default class PbDrawer extends PbEnhancedElement {
4
+ static get selector() {
5
+ return ".pb_drawer_wrapper"
6
+ }
7
+
8
+ connect() {
9
+ this.handleToggleClick = this.handleToggleClick.bind(this)
10
+ this.handleOutsideClick = this.handleOutsideClick.bind(this)
11
+ this.handleResize = this.handleResize.bind(this)
12
+
13
+ this._toggleTriggers = Array.from(document.querySelectorAll("[data-open-drawer]"))
14
+ this._toggleTriggers.forEach(el => {
15
+ el.addEventListener("click", this.handleToggleClick)
16
+ })
17
+
18
+ this._wrappers = Array.from(document.querySelectorAll(".pb_drawer_wrapper"))
19
+ this._wrappers.forEach(el => {
20
+ el.addEventListener("mousedown", this.handleOutsideClick)
21
+ })
22
+
23
+ this._withinElementDrawers = Array.from(
24
+ document.querySelectorAll(".pb_drawer_within_element_rails[data-breakpoint]")
25
+ )
26
+
27
+ window.addEventListener("resize", this.handleResize)
28
+ this.handleResize()
29
+ }
30
+
31
+ disconnect() {
32
+ this._toggleTriggers.forEach(el => {
33
+ el.removeEventListener("click", this.handleToggleClick)
34
+ })
35
+ this._wrappers.forEach(el => {
36
+ el.removeEventListener("mousedown", this.handleOutsideClick)
37
+ })
38
+ window.removeEventListener("resize", this.handleResize)
39
+ }
40
+
41
+ getOverlay(wrapper) {
42
+ if (wrapper.id && wrapper.id.startsWith("drawer-wrapper-")) {
43
+ const overlayId = wrapper.id.replace("drawer-wrapper-", "drawer-overlay-")
44
+ return document.getElementById(overlayId)
45
+ }
46
+ return wrapper.querySelector(".pb_drawer_overlay") || wrapper.querySelector(".pb_drawer_no_overlay")
47
+ }
48
+
49
+ handleToggleClick(event) {
50
+ const trigger = event.currentTarget
51
+ const drawerId = trigger.dataset.openDrawer
52
+ const dialog = document.getElementById(drawerId)
53
+ if (!dialog) return
54
+
55
+ if (dialog.classList.contains("pb_drawer_within_element_rails")) {
56
+ if (dialog.classList.contains("open")) {
57
+ this.closeWithinElementDrawer(dialog)
58
+ dialog.dataset.manualOpen = "false"
59
+ } else {
60
+ this.openWithinElementDrawer(dialog)
61
+ dialog.dataset.manualOpen = "true"
62
+ }
63
+ return
64
+ }
65
+
66
+ const wrapperId = `drawer-wrapper-${drawerId}`
67
+ const wrapper = document.getElementById(wrapperId)
68
+ if (!wrapper) return
69
+
70
+ if (wrapper !== this.element) return
71
+
72
+ if (wrapper.classList.contains("open")) {
73
+ this.closeDrawer(wrapper, dialog)
74
+ wrapper.dataset.manualOpen = "false"
75
+ } else {
76
+ this.openDrawer(wrapper, dialog)
77
+ wrapper.dataset.manualOpen = "true"
78
+ }
79
+ }
80
+
81
+ openWithinElementDrawer(dialog) {
82
+ if (dialog.classList.contains("open")) return
83
+ dialog.style.maxHeight = "0px"
84
+ dialog.offsetHeight
85
+ dialog.classList.add("open")
86
+ // Get trigger's height and add it to the final height
87
+ const trigger = document.querySelector(`[data-open-drawer="${dialog.id}"]`)
88
+ const triggerHeight = trigger ? trigger.offsetHeight : 0
89
+ const finalHeight = (dialog.scrollHeight + triggerHeight) + "px"
90
+ dialog.style.maxHeight = finalHeight
91
+ dialog.addEventListener("transitionend", function handleOpenEnd(e) {
92
+ if (e.propertyName === "max-height") {
93
+ dialog.style.maxHeight = "none"
94
+ dialog.removeEventListener("transitionend", handleOpenEnd)
95
+ }
96
+ })
97
+ }
98
+
99
+ closeWithinElementDrawer(dialog) {
100
+ if (!dialog.classList.contains("open")) return
101
+ const currentHeight = dialog.scrollHeight
102
+ dialog.style.maxHeight = currentHeight + "px"
103
+ dialog.offsetHeight
104
+ dialog.classList.remove("open")
105
+ dialog.style.maxHeight = "0px"
106
+ dialog.addEventListener("transitionend", function handleCloseEnd(e) {
107
+ if (e.propertyName === "max-height") {
108
+ dialog.removeEventListener("transitionend", handleCloseEnd)
109
+ dialog.style.maxHeight = "0px"
110
+ }
111
+ })
112
+ }
113
+
114
+ openDrawer(wrapper, dialog) {
115
+ const behavior = wrapper.dataset.behavior
116
+ const size = wrapper.dataset.size
117
+ const placement = wrapper.dataset.placement
118
+ this.handlePushOpen(behavior, size, placement)
119
+
120
+ wrapper.style.display = ""
121
+ const overlay = this.getOverlay(wrapper)
122
+ if (overlay) overlay.style.display = ""
123
+
124
+ wrapper.classList.add("open")
125
+ dialog.classList.add("open")
126
+ }
127
+
128
+ closeDrawer(wrapper, dialog) {
129
+ const behavior = wrapper.dataset.behavior
130
+ this.handlePushClose(behavior)
131
+
132
+ if (wrapper.className.includes("open")) wrapper.style.display = "none"
133
+ const overlay = this.getOverlay(wrapper)
134
+ if (overlay && wrapper.className.includes("open")) overlay.style.display = "none"
135
+
136
+ wrapper.classList.remove("open")
137
+ dialog.classList.remove("open")
138
+ }
139
+
140
+ handleOutsideClick(event) {
141
+ const wrapper = event.currentTarget
142
+ const dialog = wrapper.querySelector(".pb_drawer")
143
+ const overlay = this.getOverlay(wrapper)
144
+
145
+ if (dialog && dialog.classList.contains("pb_drawer_within_element_rails")) {
146
+ return
147
+ }
148
+
149
+ if (wrapper.dataset.overlayClick === "overlay_close" && event.target === overlay) {
150
+ this.closeDrawer(wrapper, dialog)
151
+ event.stopPropagation()
152
+ return
153
+ }
154
+
155
+ const dialogRect = dialog.getBoundingClientRect()
156
+ const clickedOutside =
157
+ event.clientX < dialogRect.left ||
158
+ event.clientX > dialogRect.right ||
159
+ event.clientY < dialogRect.top ||
160
+ event.clientY > dialogRect.bottom
161
+
162
+ if (clickedOutside) {
163
+ this.closeDrawer(wrapper, dialog)
164
+ event.stopPropagation()
165
+ }
166
+ }
167
+
168
+ handleResize() {
169
+ const breakpointValues = {
170
+ none: 0,
171
+ xs: 575,
172
+ sm: 768,
173
+ md: 992,
174
+ lg: 1200,
175
+ xl: 1400,
176
+ }
177
+
178
+ // Process wrappers
179
+ this._wrappers.forEach(wrapper => {
180
+ const bp = wrapper.dataset.breakpoint || "none"
181
+ if (bp === "none") return
182
+
183
+ const threshold = breakpointValues[bp] || 0
184
+ const dialog = wrapper.querySelector(".pb_drawer")
185
+ const trigger = dialog ? document.querySelector(`[data-open-drawer="${dialog.id}"]`) : null
186
+
187
+ if (window.innerWidth >= threshold) {
188
+ if (!wrapper.classList.contains("open")) {
189
+ this.openDrawer(wrapper, dialog)
190
+ }
191
+ if (trigger) trigger.style.display = "none"
192
+ } else {
193
+ if (trigger) trigger.style.display = ""
194
+ if (wrapper.classList.contains("open") && wrapper.dataset.manualOpen !== "true") {
195
+ this.closeDrawer(wrapper, dialog)
196
+ }
197
+ }
198
+ })
199
+
200
+ // Process within element drawers
201
+ this._withinElementDrawers.forEach(drawer => {
202
+ const bp = drawer.dataset.breakpoint || "none"
203
+ if (bp === "none") return
204
+
205
+ const threshold = breakpointValues[bp] || 0
206
+ const trigger = document.querySelector(`[data-open-drawer="${drawer.id}"]`)
207
+
208
+ if (window.innerWidth >= threshold) {
209
+ if (!drawer.classList.contains("open")) {
210
+ this.openWithinElementDrawer(drawer)
211
+ }
212
+ if (trigger) trigger.style.display = "none"
213
+ } else {
214
+ if (trigger) trigger.style.display = ""
215
+ if (drawer.classList.contains("open") && drawer.dataset.manualOpen !== "true") {
216
+ this.closeWithinElementDrawer(drawer)
217
+ }
218
+ }
219
+ })
220
+ }
221
+
222
+ handlePushOpen(behavior, size, placement) {
223
+ if (behavior !== "push") return
224
+
225
+ const sizeMap = {
226
+ xl: "365px",
227
+ lg: "300px",
228
+ md: "250px",
229
+ sm: "200px",
230
+ xs: "64px",
231
+ full: "100%",
232
+ }
233
+
234
+ const body = document.querySelector("body")
235
+ if (!body) return
236
+
237
+ if (placement === "left") {
238
+ body.style.cssText = `margin-left: ${sizeMap[size]} !important; margin-right: '' !important;`
239
+ } else if (placement === "right") {
240
+ body.style.cssText = `margin-right: ${sizeMap[size]} !important; margin-left: '' !important;`
241
+ }
242
+ body.classList.add("PBDrawer__Body--open")
243
+ }
244
+
245
+ handlePushClose(behavior) {
246
+ if (behavior !== "push") return
247
+
248
+ const body = document.querySelector("body")
249
+ if (!body) return
250
+
251
+ if (body.classList.contains("PBDrawer__Body--open")) {
252
+ body.classList.add("PBDrawer__Body--close")
253
+ }
254
+ body.style.cssText = ""
255
+ body.classList.remove("PBDrawer__Body--open")
256
+ }
257
+ }
@@ -32,7 +32,7 @@
32
32
  <%= form.url_field :example_url_field_validation, props: { label: true, required: true } %>
33
33
  <%= form.text_area :example_text_area_validation, props: { label: true, required: true } %>
34
34
  <%= form.dropdown_field :example_dropdown_validation, props: { label: true, options: example_dropdown_options, required: true } %>
35
- <%= form.select :example_select_validation, [ ["Yes", 1], ["No", 2] ], props: { label: true, blank_selection: "Select One...", required: true } %>
35
+ <%= form.select :example_select_validation, [ ["Yes", 1], ["No", 2] ], props: { label: true, blank_selection: "Select One...", required: true, validation_message: "Please, select an option." } %>
36
36
  <%= form.collection_select :example_collection_select_validation, example_collection, :value, :name, props: { label: true, blank_selection: "Select One...", required: true } %>
37
37
  <%= form.check_box :example_checkbox, props: { text: "Example Checkbox", label: true, required: true } %>
38
38
  <%= form.date_picker :example_date_picker_2, props: { label: true, required: true } %>
@@ -69,4 +69,17 @@ $overlay_colors: (
69
69
  pointer-events: none;
70
70
  z-index: 1;
71
71
  }
72
+
73
+ &.overlay-hide-scrollbar {
74
+ & [class*="overflow_x_auto"],
75
+ & [class*="overflow_y_auto"],
76
+ & [class*="overflow_auto"] {
77
+ &::-webkit-scrollbar {
78
+ display: none !important;
79
+ }
80
+
81
+ -ms-overflow-style: none !important;
82
+ scrollbar-width: none !important;
83
+ }
84
+ }
72
85
  }
@@ -11,6 +11,7 @@ export type OverlayChildrenProps = {
11
11
  dynamic?: boolean,
12
12
  position: string,
13
13
  size: string,
14
+ scrollBarNone?: boolean,
14
15
  }
15
16
 
16
17
  type OverlayProps = {
@@ -23,6 +24,8 @@ type OverlayProps = {
23
24
  htmlOptions?: { [key: string]: string | number | boolean | (() => void) },
24
25
  id?: string,
25
26
  layout: { [key: string]: string },
27
+ scrollBarNone?: boolean,
28
+
26
29
  }
27
30
 
28
31
  const Overlay = (props: OverlayProps) => {
@@ -36,11 +39,12 @@ const Overlay = (props: OverlayProps) => {
36
39
  htmlOptions = {},
37
40
  id,
38
41
  layout = { "bottom": "full" },
42
+ scrollBarNone = false,
39
43
  } = props
40
44
 
41
45
  const ariaProps = buildAriaProps(aria)
42
46
  const dataProps = buildDataProps(data)
43
- const classes = classnames(buildCss('pb_overlay'), globalProps(props), className)
47
+ const classes = classnames(buildCss('pb_overlay'), { 'overlay-hide-scrollbar': scrollBarNone }, globalProps(props), className )
44
48
  const htmlProps = buildHtmlProps(htmlOptions)
45
49
  const dynamicInlineProps = globalInlineProps(props)
46
50
 
@@ -68,12 +72,14 @@ const Overlay = (props: OverlayProps) => {
68
72
  children,
69
73
  color,
70
74
  position: getPosition(),
75
+ scrollBarNone,
71
76
  size: getSize()
72
77
  }) : OverlayToken({
73
78
  children,
74
79
  color,
75
80
  dynamic: dynamic,
76
81
  position: getPosition(),
82
+ scrollBarNone,
77
83
  size: getSize()
78
84
  })
79
85
  }
@@ -0,0 +1,11 @@
1
+ <%= pb_rails("overlay", props: { layout: { "x": "xl" }, color: "card_light", scroll_bar_none: true }) do %>
2
+ <%= pb_rails("flex", props: { column_gap: "lg", orientation: "row", overflow_x: "auto" }) do %>
3
+ <% 15.times do %>
4
+ <%= pb_rails("flex/flex_item") do %>
5
+ <%= pb_rails("card") do %>
6
+ Card content
7
+ <% end %>
8
+ <% end %>
9
+ <% end %>
10
+ <% end %>
11
+ <% end %>
@@ -0,0 +1,37 @@
1
+ import React from 'react'
2
+ import {
3
+ Overlay,
4
+ Card,
5
+ Flex,
6
+ FlexItem,
7
+ } from 'playbook-ui'
8
+
9
+ const InlineCardsExample = () => {
10
+ return (
11
+ <Flex
12
+ columnGap="lg"
13
+ orientation="row"
14
+ overflowX="auto"
15
+ >
16
+ {Array.from({ length: 15 }, (_, index) => (
17
+ <FlexItem key={index}>
18
+ <Card>{"Card Content"}</Card>
19
+ </FlexItem>
20
+ ))}
21
+ </Flex>
22
+ )
23
+ }
24
+
25
+ const OverlayHideScrollBar = () => (
26
+ <>
27
+ <Overlay
28
+ color="card_light"
29
+ layout={{"x": "xl"}}
30
+ scrollBarNone
31
+ >
32
+ <InlineCardsExample />
33
+ </Overlay>
34
+ </>
35
+ )
36
+
37
+ export default OverlayHideScrollBar
@@ -0,0 +1 @@
1
+ Pass the `scroll_bar_none` prop to hide the scrollbar from view. This is particularly helpful for small containers where the scrollbar may occupy too much space.
@@ -0,0 +1 @@
1
+ Pass the `scrollBarNone` prop to hide the scrollbar from view. This is particularly helpful for small containers where the scrollbar may occupy too much space.
@@ -0,0 +1,11 @@
1
+ <%= pb_rails("overlay", props: { layout: { "x": "xl" }, color: "card_light", dynamic: true }) do %>
2
+ <%= pb_rails("flex", props: { column_gap: "lg", orientation: "row", overflow_x: "auto" }) do %>
3
+ <% 15.times do %>
4
+ <%= pb_rails("flex/flex_item") do %>
5
+ <%= pb_rails("card") do %>
6
+ Card content
7
+ <% end %>
8
+ <% end %>
9
+ <% end %>
10
+ <% end %>
11
+ <% end %>
@@ -0,0 +1 @@
1
+ Pass the `dynamic` prop to make the overlay render while the scrollbar isn't at either end on the scrollbar.
@@ -4,8 +4,11 @@ examples:
4
4
  - overlay_multi_directional: Multi-directional
5
5
  - overlay_vertical_dynamic_multi_directional: Vertical Dynamic Multi-directional
6
6
  - overlay_toggle: Toggle
7
+ - overlay_hide_scroll_bar: Hide Scroll Bar
7
8
 
8
9
  rails:
9
10
  - overlay_default: Default
10
11
  - overlay_multi_directional: Multi-directional
12
+ - overlay_vertical_dynamic_multi_directional: Vertical Dynamic Multi-directional
11
13
  - overlay_toggle: Toggle
14
+ - overlay_hide_scroll_bar: Hide Scroll Bar
@@ -2,3 +2,4 @@ export { default as OverlayDefault } from './_overlay_default.jsx'
2
2
  export { default as OverlayMultiDirectional } from './_overlay_multi_directional.jsx'
3
3
  export { default as OverlayToggle } from './_overlay_toggle.jsx'
4
4
  export { default as OverlayVerticalDynamicMultiDirectional } from './_overlay_vertical_dynamic_multi_directional.jsx'
5
+ export { default as OverlayHideScrollBar } from './_overlay_hide_scroll_bar.jsx'
@@ -0,0 +1,61 @@
1
+ import PbEnhancedElement from '../pb_enhanced_element'
2
+
3
+ const OVERLAY_SELECTOR = '[data-pb-overlay]'
4
+ const OVERLAY_SCROLL_ELEMENT = '[data-overlay-scroll-element]'
5
+ const PREVIOUS_OVERLAY_CLASSNAME = '[data-previous-overlay-classname]'
6
+ const SUBSEQUENT_OVERLAY_CLASSNAME = '[data-subsequent-overlay-classname]'
7
+
8
+ export default class PbOverlay extends PbEnhancedElement {
9
+ static get selector() {
10
+ return OVERLAY_SELECTOR
11
+ }
12
+
13
+ get target() {
14
+ return this.element.querySelector(OVERLAY_SCROLL_ELEMENT).children[0]
15
+ }
16
+
17
+ connect() {
18
+ this.handleOverlayDynamic()
19
+ }
20
+
21
+ handleOverlayDynamic() {
22
+ const isOverlayDynamic = this.element.dataset?.overlayDynamic
23
+
24
+ if (isOverlayDynamic) {
25
+ const previousOverlayElement = this.element.querySelector(PREVIOUS_OVERLAY_CLASSNAME)
26
+ const previousOverlayClassname = previousOverlayElement?.dataset?.previousOverlayClassname
27
+ const subsequentOverlayElement = this.element.querySelector(SUBSEQUENT_OVERLAY_CLASSNAME)
28
+ const subsequentOverlayClassname = subsequentOverlayElement?.dataset?.subsequentOverlayClassname
29
+
30
+ const handleScrollChange = (target) => {
31
+ const { scrollLeft, scrollWidth, clientWidth } = target
32
+ const isScrollAtStart = scrollLeft === 0
33
+ const isScrollAtEnd = scrollLeft + clientWidth >= scrollWidth - 1
34
+
35
+ if (isScrollAtStart) {
36
+ previousOverlayElement.classList.remove(previousOverlayClassname)
37
+ } else {
38
+ previousOverlayElement.classList.add(previousOverlayClassname)
39
+ }
40
+
41
+ if (isScrollAtEnd) {
42
+ subsequentOverlayElement.classList.remove(subsequentOverlayClassname)
43
+ } else {
44
+ subsequentOverlayElement.classList.add(subsequentOverlayClassname)
45
+ }
46
+ }
47
+
48
+ this.target.addEventListener('scroll', (event) => {
49
+ handleScrollChange(event.target)
50
+ })
51
+
52
+ handleScrollChange(this.target)
53
+ }
54
+ }
55
+
56
+ disconnect() {
57
+ if (this.element.dataset?.overlayDynamic) {
58
+ this.target.removeEventListener('scroll')
59
+ }
60
+ }
61
+ }
@@ -16,12 +16,14 @@ id: object.id,
16
16
  <% end %>
17
17
 
18
18
  <% else %>
19
- <div class="<%= previous_overlay_class_name %>"></div>
19
+ <div class="<%= previous_overlay_class_name %>" data-previous-overlay-classname="<%= previous_overlay_class_name %>"></div>
20
20
 
21
- <%= content.presence %>
21
+ <div data-overlay-scroll-element="true">
22
+ <%= content.presence %>
23
+ </div>
22
24
 
23
25
  <% if has_subsequent_overlay %>
24
- <div class="<%= subsequent_overlay_class_name %>"></div>
26
+ <div class="<%= subsequent_overlay_class_name %>" data-subsequent-overlay-classname="<%= subsequent_overlay_class_name %>"></div>
25
27
  <% end %>
26
28
  <% end %>
27
29
  <% end %>
@@ -8,9 +8,13 @@ module Playbook
8
8
  default: "card_light"
9
9
  prop :layout, type: Playbook::Props::HashProp,
10
10
  default: { "bottom": "full" }
11
+ prop :dynamic, type: Playbook::Props::Boolean,
12
+ default: false
13
+ prop :scroll_bar_none, type: Playbook::Props::Boolean,
14
+ default: false
11
15
 
12
16
  def classname
13
- generate_classname("pb_overlay")
17
+ generate_classname("pb_overlay", hide_scroll_bar_class)
14
18
  end
15
19
 
16
20
  def position
@@ -105,6 +109,17 @@ module Playbook
105
109
  "bg_dark": "#0a0527",
106
110
  }
107
111
  end
112
+
113
+ def data_attributes
114
+ data ||= {}
115
+ data.merge!("data-pb-overlay" => true)
116
+ data.merge!("data-overlay-dynamic" => true) if dynamic
117
+ data
118
+ end
119
+
120
+ def hide_scroll_bar_class
121
+ scroll_bar_none ? " overlay-hide-scrollbar" : ""
122
+ end
108
123
  end
109
124
  end
110
125
  end
@@ -64,3 +64,15 @@ test('should render children', () => {
64
64
  const kit = screen.getByTestId(testId)
65
65
  expect(kit).toHaveTextContent(props.children)
66
66
  })
67
+
68
+ test('should add overlay-hide-scrollbar class when scrollBarNone is true', () => {
69
+ const props = {
70
+ children,
71
+ data: { testid: testId },
72
+ scrollBarNone: true
73
+ }
74
+
75
+ render(<Overlay {...props} />)
76
+ const kit = screen.getByTestId(testId)
77
+ expect(kit).toHaveClass('overlay-hide-scrollbar')
78
+ })
@@ -0,0 +1,38 @@
1
+ import PbEnhancedElement from "../pb_enhanced_element";
2
+
3
+ const SELECT_WRAPPER_SELECTOR = "[data-pb-select]";
4
+ const SELECT_VALIDATION_MESSAGE_CLASS = ".pb_body_kit_negative";
5
+
6
+ export default class PbSelect extends PbEnhancedElement {
7
+ static get selector() {
8
+ return SELECT_WRAPPER_SELECTOR;
9
+ }
10
+
11
+ connect() {
12
+ this.setValidationMessage();
13
+ }
14
+
15
+ setValidationMessage() {
16
+ const validationMessage = this.element.dataset?.validationMessage;
17
+
18
+ if (validationMessage) {
19
+ const selectElement = this.element.querySelector("select");
20
+ const setErrorTextContent = (text, timeout) => {
21
+ setTimeout(() => {
22
+ const errorMessageElement = this.element.querySelector(SELECT_VALIDATION_MESSAGE_CLASS);
23
+ if (errorMessageElement) {
24
+ errorMessageElement.textContent = text;
25
+ } else {
26
+ setErrorTextContent(text, 100);
27
+ }
28
+ }, timeout);
29
+ };
30
+
31
+ selectElement.addEventListener("change", (e) => {
32
+ if (!e.target.checkValidity()) {
33
+ setErrorTextContent(validationMessage, 300);
34
+ }
35
+ });
36
+ }
37
+ }
38
+ }
@@ -1,4 +1,4 @@
1
- <%= pb_content_tag do %>
1
+ <%= pb_content_tag(:div, class: object.classnames) do %>
2
2
  <% if object.label %>
3
3
  <label class="pb_select_kit_label" for="<%= object.name %>">
4
4
  <%= pb_rails("caption", props: { text: object.label, dark: object.dark }) %>
@@ -21,6 +21,7 @@ module Playbook
21
21
  prop :options, type: Playbook::Props::HashArray, required: false, default: []
22
22
  prop :show_arrow, type: Playbook::Props::Boolean, default: false
23
23
  prop :required, type: Playbook::Props::Boolean, default: false
24
+ prop :validation_message, type: Playbook::Props::String, default: ""
24
25
 
25
26
  def classnames
26
27
  classname + inline_class + compact_class + show_arrow_class
@@ -88,6 +89,13 @@ module Playbook
88
89
  "app/pb_kits/playbook/utilities/icons/angle-down.svg"
89
90
  end
90
91
 
92
+ def data_attributes
93
+ data = attributes[:data] || {}
94
+ data.merge!("data-pb-select" => true)
95
+ data.merge!("data-validation-message" => validation_message) if validation_message.present?
96
+ data
97
+ end
98
+
91
99
  private
92
100
 
93
101
  def error_class
@@ -141,7 +141,6 @@ $tooltip_shadow: rgba(60, 106, 172, 0.18);
141
141
 
142
142
  &[data-popper-placement="right"] {
143
143
  box-shadow: -8px 0 28px 0 $tooltip_shadow;
144
- margin: 0 0 0 $space_sm;
145
144
  .arrow {
146
145
  left: -18px;
147
146
  right: auto;
@@ -156,7 +155,6 @@ $tooltip_shadow: rgba(60, 106, 172, 0.18);
156
155
 
157
156
  &[data-popper-placement="bottom"] {
158
157
  box-shadow: 0 -12px 28px 0 $tooltip_shadow;
159
- margin: $space_sm 0 0 0;
160
158
  .arrow {
161
159
  top: -18px;
162
160
  margin-bottom: 0;
@@ -169,7 +167,6 @@ $tooltip_shadow: rgba(60, 106, 172, 0.18);
169
167
 
170
168
  &[data-popper-placement="left"] {
171
169
  box-shadow: 8px 0 28px 0 $tooltip_shadow;
172
- margin: 0 $space_sm 0 0;
173
170
  .arrow {
174
171
  margin-bottom: 0;
175
172
  right: -18px;