playbook_ui 13.25.0.pre.alpha.PBNTR272Dropdownkitv42769 → 13.25.0.pre.alpha.PBNTR291Dropdownrailsv22812

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_bar_graph/_bar_graph.tsx +1 -1
  3. data/app/pb_kits/playbook/pb_checkbox/_checkbox.scss +49 -0
  4. data/app/pb_kits/playbook/pb_checkbox/_checkbox.tsx +3 -0
  5. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +2 -1
  6. data/app/pb_kits/playbook/pb_checkbox/checkbox.test.js +14 -0
  7. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_disabled.html.erb +23 -0
  8. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_disabled.jsx +29 -0
  9. data/app/pb_kits/playbook/pb_checkbox/docs/example.yml +2 -0
  10. data/app/pb_kits/playbook/pb_checkbox/docs/index.js +1 -0
  11. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_on_change.md +3 -1
  12. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_on_close.md +3 -1
  13. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_range_limit.md +1 -1
  14. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +41 -58
  15. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +15 -28
  16. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.html.erb +10 -0
  17. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.jsx +1 -7
  18. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.md +1 -1
  19. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subcomponent_structure.html.erb +17 -0
  20. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subcomponent_structure.jsx +42 -0
  21. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subcomponent_structure.md +7 -0
  22. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete.jsx +2 -5
  23. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete_and_custom_display.jsx +1 -2
  24. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.html.erb +60 -0
  25. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.jsx +1 -1
  26. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.md +1 -1
  27. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_options.html.erb +45 -0
  28. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_options.md +1 -1
  29. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_padding.html.erb +17 -0
  30. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_trigger.html.erb +47 -0
  31. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_external_control.jsx +1 -4
  32. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_hook.jsx +1 -4
  33. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +10 -0
  34. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +10 -2
  35. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +1 -0
  36. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +21 -0
  37. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +20 -0
  38. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +3 -3
  39. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.html.erb +15 -0
  40. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.rb +19 -0
  41. data/app/pb_kits/playbook/pb_dropdown/dropdown_option.html.erb +22 -0
  42. data/app/pb_kits/playbook/pb_dropdown/dropdown_option.rb +22 -0
  43. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +38 -0
  44. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +30 -0
  45. data/app/pb_kits/playbook/pb_dropdown/index.js +154 -0
  46. data/app/pb_kits/playbook/pb_dropdown/keyboard_accessibility.js +77 -0
  47. data/app/pb_kits/playbook/pb_dropdown/scss_partials/_dropdown_animation.scss +18 -0
  48. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +9 -7
  49. data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +41 -0
  50. data/app/pb_kits/playbook/pb_dropdown/utilities/index.ts +2 -0
  51. data/app/pb_kits/playbook/pb_progress_simple/docs/_progress_simple_flex.html.erb +3 -0
  52. data/app/pb_kits/playbook/pb_progress_simple/docs/_progress_simple_flex.jsx +16 -0
  53. data/app/pb_kits/playbook/pb_progress_simple/docs/_progress_simple_flex.md +1 -0
  54. data/app/pb_kits/playbook/pb_progress_simple/docs/example.yml +2 -0
  55. data/app/pb_kits/playbook/pb_progress_simple/docs/index.js +1 -0
  56. data/app/pb_kits/playbook/pb_progress_simple/progress_simple.rb +1 -1
  57. data/app/pb_kits/playbook/pb_radio/_radio.scss +35 -0
  58. data/app/pb_kits/playbook/pb_radio/_radio.tsx +3 -0
  59. data/app/pb_kits/playbook/pb_radio/docs/_radio_alignment.jsx +4 -1
  60. data/app/pb_kits/playbook/pb_radio/docs/_radio_default.jsx +4 -1
  61. data/app/pb_kits/playbook/pb_radio/docs/_radio_disabled.html.erb +26 -0
  62. data/app/pb_kits/playbook/pb_radio/docs/_radio_disabled.jsx +31 -0
  63. data/app/pb_kits/playbook/pb_radio/docs/_radio_error.jsx +2 -1
  64. data/app/pb_kits/playbook/pb_radio/docs/example.yml +2 -0
  65. data/app/pb_kits/playbook/pb_radio/docs/index.js +1 -0
  66. data/app/pb_kits/playbook/pb_radio/radio.rb +5 -0
  67. data/app/pb_kits/playbook/pb_radio/radio.test.js +17 -0
  68. data/app/pb_kits/playbook/playbook-rails.js +3 -0
  69. data/dist/playbook-rails.js +5 -5
  70. data/lib/playbook/version.rb +1 -1
  71. metadata +31 -2
@@ -0,0 +1,60 @@
1
+ <%
2
+ options = [
3
+ {
4
+ label: "Jasper Furniss",
5
+ value: "Jasper Furniss",
6
+ territory: "PHL",
7
+ title: "Senior UX Engineer",
8
+ id: "jasper-furniss",
9
+ status: "Offline"
10
+ },
11
+ {
12
+ label: "Ramon Ruiz",
13
+ value: "Ramon Ruiz",
14
+ territory: "PHL",
15
+ title: "Senior UX Designer",
16
+ id: "ramon-ruiz",
17
+ status: "Away"
18
+ },
19
+ {
20
+ label: "Jason Cypret",
21
+ value: "Jason Cypret",
22
+ territory: "PHL",
23
+ title: "VP of User Experience",
24
+ id: "jason-cypret",
25
+ status: "Online"
26
+ },
27
+ {
28
+ label: "Courtney Long",
29
+ value: "Courtney Long",
30
+ territory: "PHL",
31
+ title: "UX Design Mentor",
32
+ id: "courtney-long",
33
+ status: "Online"
34
+ }
35
+ ]
36
+
37
+ %>
38
+
39
+ <%
40
+ custom_display = capture do
41
+ pb_rails("avatar", props: { name: "Courtney Long", size: "xs" })
42
+ end
43
+ %>
44
+
45
+
46
+ <%= pb_rails("dropdown", props: {options: options}) do %>
47
+ <%= pb_rails("dropdown/dropdown_trigger", props: {placeholder: "Select a User", custom_display: custom_display}) %>
48
+ <%= pb_rails("dropdown/dropdown_container") do %>
49
+ <% options.each do |option| %>
50
+ <%= pb_rails("dropdown/dropdown_option", props: {option: option}) do %>
51
+ <%= pb_rails("flex/flex_item") do %>
52
+ <%= pb_rails("user", props: {name: option[:label], align:"left", avatar: true, orientation:"horizontal", territory:option[:territory], title: option[:title]}) %>
53
+ <% end %>
54
+ <%= pb_rails("flex/flex_item") do %>
55
+ <%= pb_rails("badge", props: {rounded: true, dark: true, text: option[:status], variant: option[:status] == "Offline" ? "neutral" : option[:status] == "Online" ? "success" : "warning" }) %>
56
+ <% end %>
57
+ <% end %>
58
+ <% end %>
59
+ <% end %>
60
+ <% end %>
@@ -17,7 +17,7 @@ const DropdownWithCustomDisplay = (props) => {
17
17
  label: "Ramon Ruiz",
18
18
  value: "Ramon Ruiz",
19
19
  territory: "PHL",
20
- title: "Senior UX Desinger",
20
+ title: "Senior UX Designer",
21
21
  id: "ramon-ruiz",
22
22
  status: "Away"
23
23
  },
@@ -2,4 +2,4 @@ The `customDisplay` prop can be used to customize the display of the selected it
2
2
 
3
3
  The `placeholder` prop can also be used to customize the placeholder text for the default Trigger.
4
4
 
5
- The `onSelect` prop is a function that gives the dev one argument: the selected option. In this example we are using the `onSelect` to set a state with the selected option and using it to customize the `customDisplay`.
5
+ The `onSelect` prop is a function that gives the dev one argument: the selected option. In this example we are using the `onSelect` to set a state with the selected option and using it to customize the `customDisplay`.
@@ -0,0 +1,45 @@
1
+ <%
2
+ options = [
3
+ {
4
+ label: "United States",
5
+ value: "United States",
6
+ areaCode: "+1",
7
+ icon: "🇺🇸",
8
+ id: "us"
9
+ },
10
+ {
11
+ label: "Canada",
12
+ value: "Canada",
13
+ areaCode: "+1",
14
+ icon: "🇨🇦",
15
+ id: "ca"
16
+ },
17
+ {
18
+ label: "Pakistan",
19
+ value: "Pakistan",
20
+ areaCode: "+92",
21
+ icon: "🇵🇰",
22
+ id: "pk"
23
+ }
24
+ ]
25
+
26
+ %>
27
+
28
+ <%= pb_rails("dropdown", props: {options: options}) do %>
29
+ <%= pb_rails("dropdown/dropdown_trigger") %>
30
+ <%= pb_rails("dropdown/dropdown_container") do %>
31
+ <% options.each do |option| %>
32
+ <%= pb_rails("dropdown/dropdown_option", props: {option: option}) do %>
33
+ <%= pb_rails("flex/flex_item") do %>
34
+ <%= pb_rails("flex") do %>
35
+ <%= pb_rails("icon", props: {icon: option[:icon]}) %>
36
+ <%= pb_rails("body", props: {text: option[:label], padding_left:"xs"}) %>
37
+ <% end %>
38
+ <% end %>
39
+ <%= pb_rails("flex/flex_item") do %>
40
+ <%= pb_rails("body", props: {color:"light", text: option[:areaCode]}) %>
41
+ <% end %>
42
+ <% end %>
43
+ <% end %>
44
+ <% end %>
45
+ <% end %>
@@ -1 +1 @@
1
- The Dropdown also allows for custom options that can be passed in as children to the `Dropdown.Option` subcomponent. If no children are passed in the `Dropdown.Option`, the kit will render each option as text by default.
1
+ The Dropdown also allows for custom options that can be passed in as children to the `Dropdown.Option` subcomponent. If no children are passed to `Dropdown.Option`, the kit will render each option as text within a Body kit by default.
@@ -0,0 +1,17 @@
1
+ <%
2
+ options = [
3
+ { label: 'United States', value: 'United States', 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}) do %>
11
+ <%= pb_rails("dropdown/dropdown_trigger") %>
12
+ <%= pb_rails("dropdown/dropdown_container") do %>
13
+ <% options.each do |option| %>
14
+ <%= pb_rails("dropdown/dropdown_option", props: {option: option, padding:"sm"}) %>
15
+ <% end %>
16
+ <% end %>
17
+ <% end %>
@@ -0,0 +1,47 @@
1
+ <%
2
+ options = [
3
+ {
4
+ label: "United States",
5
+ value: "United States",
6
+ areaCode: "+1",
7
+ icon: "🇺🇸",
8
+ id: "us"
9
+ },
10
+ {
11
+ label: "Canada",
12
+ value: "Canada",
13
+ areaCode: "+1",
14
+ icon: "🇨🇦",
15
+ id: "ca"
16
+ },
17
+ {
18
+ label: "Pakistan",
19
+ value: "Pakistan",
20
+ areaCode: "+92",
21
+ icon: "🇵🇰",
22
+ id: "pk"
23
+ }
24
+ ]
25
+
26
+ %>
27
+
28
+ <%= pb_rails("dropdown", props: {options: options}) do %>
29
+ <%= pb_rails("dropdown/dropdown_trigger") do %>
30
+ <%= pb_rails("icon_circle", props: {icon:"flag", cursor: "pointer", variant:"royal"}) %>
31
+ <% end %>
32
+ <%= pb_rails("dropdown/dropdown_container", props:{max_width:"xs"}) do %>
33
+ <% options.each do |option| %>
34
+ <%= pb_rails("dropdown/dropdown_option", props: {option: option}) do %>
35
+ <%= pb_rails("flex/flex_item") do %>
36
+ <%= pb_rails("flex") do %>
37
+ <%= pb_rails("icon", props: {icon: option[:icon]}) %>
38
+ <%= pb_rails("body", props: {text: option[:label], padding_left:"xs"}) %>
39
+ <% end %>
40
+ <% end %>
41
+ <%= pb_rails("flex/flex_item") do %>
42
+ <%= pb_rails("body", props: {color:"light", text: option[:areaCode]}) %>
43
+ <% end %>
44
+ <% end %>
45
+ <% end %>
46
+ <% end %>
47
+ <% end %>
@@ -1,9 +1,7 @@
1
- import React, { useState } from 'react'
1
+ import React from 'react'
2
2
  import { Dropdown, useDropdown, Button } from '../../'
3
3
 
4
4
  const DropdownWithExternalControl = (props) => {
5
- // eslint-disable-next-line no-unused-vars
6
- const [selectedOption, setSelectedOption] = useState();
7
5
  const [isDropDownClosed, setIsDropdownClosed] = useDropdown(true);
8
6
 
9
7
  const options = [
@@ -45,7 +43,6 @@ const [isDropDownClosed, setIsDropdownClosed] = useDropdown(true);
45
43
 
46
44
  <Dropdown
47
45
  isClosed={isDropDownClosed}
48
- onSelect={(selectedItem) => setSelectedOption(selectedItem)}
49
46
  options={options}
50
47
  {...props}
51
48
  >
@@ -1,9 +1,7 @@
1
- import React, { useState, useRef } from 'react'
1
+ import React, { useRef } from 'react'
2
2
  import { Dropdown, useDropdown, CircleIconButton, Icon, Body, FlexItem, Flex } from '../..'
3
3
 
4
4
  const DropdownWithHook = (props) => {
5
- // eslint-disable-next-line no-unused-vars
6
- const [selectedOption, setSelectedOption] = useState();
7
5
  const [isDropDownClosed, setIsDropdownClosed] = useDropdown(true);
8
6
  const buttonRef = useRef(null);
9
7
 
@@ -41,7 +39,6 @@ const buttonRef = useRef(null);
41
39
  />
42
40
  <Dropdown
43
41
  isClosed={isDropDownClosed}
44
- onSelect={(selectedItem) => setSelectedOption(selectedItem)}
45
42
  options={options}
46
43
  triggerRef={buttonRef}
47
44
  {...props}
@@ -0,0 +1,10 @@
1
+ <%
2
+ options = [
3
+ { label: 'United States', value: 'United States', 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, label: "Select a Country"}) %>
@@ -1,8 +1,16 @@
1
1
  examples:
2
-
3
-
2
+ rails:
3
+ - dropdown_default: Default
4
+ - dropdown_subcomponent_structure: Subcomponent Structure
5
+ - dropdown_with_label: With Label
6
+ - dropdown_with_custom_options: Custom Options
7
+ - dropdown_with_custom_display: Custom Display
8
+ - dropdown_with_custom_trigger: Custom Trigger
9
+ - dropdown_with_custom_padding: Custom Padding for Dropdown Options
10
+
4
11
  react:
5
12
  - dropdown_default: Default
13
+ - dropdown_subcomponent_structure: Subcomponent Structure
6
14
  - dropdown_with_label: With Label
7
15
  - dropdown_with_custom_options: Custom Options
8
16
  - dropdown_with_custom_display: Custom Display
@@ -8,3 +8,4 @@ export { default as DropdownWithCustomPadding } from './_dropdown_with_custom_pa
8
8
  export { default as DropdownWithLabel } from './_dropdown_with_label.jsx'
9
9
  export { default as DropdownWithExternalControl } from './_dropdown_with_external_control.jsx'
10
10
  export { default as DropdownWithHook } from './_dropdown_with_hook.jsx'
11
+ export { default as DropdownSubcomponentStructure } from './_dropdown_subcomponent_structure.jsx'
@@ -0,0 +1,21 @@
1
+ <%= pb_content_tag do %>
2
+ <% if object.label.present? %>
3
+ <%= pb_rails("caption", props: {text: object.label, margin_bottom:"xs"}) %>
4
+ <% end %>
5
+ <div class="dropdown_wrapper" style="position: relative">
6
+ <input type="hidden" name="<%= object.name %>" id="dropdown-selected-option" value=""/>
7
+ <% if content.present? %>
8
+ <%= content.presence %>
9
+ <% else %>
10
+ <%= pb_rails("dropdown/dropdown_trigger") %>
11
+ <%= pb_rails("dropdown/dropdown_container") do %>
12
+ <% if object.options.present? %>
13
+ <% object.options.each do |option| %>
14
+ <%= pb_rails("dropdown/dropdown_option", props: {option: option}) %>
15
+ <% end %>
16
+ <% end %>
17
+ <% end %>
18
+ <% end %>
19
+ </div>
20
+ <% end %>
21
+
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Playbook
4
+ module PbDropdown
5
+ class Dropdown < Playbook::KitBase
6
+ prop :options, type: Playbook::Props::Array,
7
+ default: []
8
+ prop :label, type: Playbook::Props::String
9
+ prop :name, type: Playbook::Props::String
10
+
11
+ def data
12
+ Hash(prop(:data)).merge(pb_dropdown: true)
13
+ end
14
+
15
+ def classname
16
+ generate_classname("pb_dropdown")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -72,7 +72,7 @@ test('generated Options', () => {
72
72
  render(<DefaultDropdownKit/>)
73
73
 
74
74
  const kit = screen.getByTestId(testId)
75
- const option = kit.querySelector('.pb_dropdown_option')
75
+ const option = kit.querySelector('.pb_dropdown_option_list')
76
76
  expect(option).toBeInTheDocument()
77
77
  })
78
78
 
@@ -201,7 +201,7 @@ test('selected option on click', () => {
201
201
  )
202
202
 
203
203
  const kit = screen.getByTestId(testId)
204
- const option = kit.querySelector('.pb_dropdown_option')
204
+ const option = kit.querySelector('.pb_dropdown_option_list')
205
205
  option.click()
206
- expect(option).toHaveClass('pb_dropdown_option dropdown_option_selected p_xs')
206
+ expect(option).toHaveClass('pb_dropdown_option_selected p_xs')
207
207
  })
@@ -0,0 +1,15 @@
1
+ <%= pb_content_tag(:div, style: object.container_style) do %>
2
+ <%= pb_rails("list", props: {ordered: false, borderless: false}) do %>
3
+ <% if content.present? %>
4
+ <%= content.presence %>
5
+ <% else %>
6
+ <%= pb_rails("list/item", props: {
7
+ display: "flex",
8
+ justify_content: "center",
9
+ padding:"xs",
10
+ }) do %>
11
+ <%= pb_rails("body", props: {text: "No option"}) %>
12
+ <% end %>
13
+ <% end %>
14
+ <% end %>
15
+ <% end %>
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Playbook
4
+ module PbDropdown
5
+ class DropdownContainer < Playbook::KitBase
6
+ def classname
7
+ generate_classname("pb_dropdown_container", "close", separator: " ")
8
+ end
9
+
10
+ def container_style
11
+ "position: absolute"
12
+ end
13
+
14
+ def data
15
+ Hash(prop(:data)).merge(dropdown_container: true)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ <%= pb_content_tag(:div, id:object.option[:id]) do %>
2
+ <%= pb_rails("list/item", props: {
3
+ display: "flex",
4
+ justify_content: "center",
5
+ padding:"none",
6
+ cursor: "pointer"
7
+ }) do %>
8
+ <%= pb_rails("flex", props: {
9
+ align: "center",
10
+ classname:"dropdown_option_wrapper",
11
+ justify: "between",
12
+ padding_x:"sm",
13
+ padding_y:"xxs",
14
+ }) do %>
15
+ <% if content.present? %>
16
+ <%= content.presence %>
17
+ <% else %>
18
+ <%= pb_rails("body", props: {text: object.option[:label]}) %>
19
+ <% end %>
20
+ <% end %>
21
+ <% end %>
22
+ <% end %>
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Playbook
4
+ module PbDropdown
5
+ class DropdownOption < Playbook::KitBase
6
+ prop :option, type: Playbook::Props::String
7
+ prop :id, type: Playbook::Props::String
8
+
9
+ def data
10
+ Hash(prop(:data)).merge("dropdown_option_label": option)
11
+ end
12
+
13
+ def padding_helper
14
+ " p_xs"
15
+ end
16
+
17
+ def classname
18
+ generate_classname("pb_dropdown_option", "list") + padding_helper
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,38 @@
1
+ <%= pb_content_tag do %>
2
+ <% if content.present? %>
3
+ <div style="display: inline-block" tabindex="0" data-dropdown-custom-trigger>
4
+ <%= content.presence %>
5
+ </div>
6
+ <% else %>
7
+ <%= pb_rails("flex", props: {
8
+ align: "center",
9
+ border_radius:"lg",
10
+ classname: object.trigger_wrapper_classes,
11
+ cursor: "pointer",
12
+ justify: "between",
13
+ padding_x:"sm",
14
+ padding_y:"xs",
15
+ html_options: {tabindex:"0"}
16
+ }) do %>
17
+ <%= pb_rails("flex/flex_item") do %>
18
+ <%= pb_rails("flex", props: {align: "center"}) do %>
19
+ <% if object.custom_display.present? %>
20
+ <%= pb_rails("flex", props: {align: "center"}) do %>
21
+ <div id="dropdown_trigger_custom_display" style="display: none;">
22
+ <%= object.custom_display %>
23
+ </div>
24
+ <%= pb_rails("body", props: {text: object.default_display_placeholder, id: "dropdown_trigger_display"}) %>
25
+ <% end %>
26
+ <% else %>
27
+ <%= pb_rails("body", props: {text: object.default_display_placeholder, id: "dropdown_trigger_display"}) %>
28
+ <% end %>
29
+ <% end %>
30
+ <% end %>
31
+ <%= pb_rails("body", props: {display: "flex"}) do %>
32
+ <%= pb_rails("icon", props: {icon: "chevron-down", cursor: "pointer", size:"sm", id: "dropdown_open_icon"}) %>
33
+ <%= pb_rails("icon", props: {icon: "chevron-up", cursor: "pointer", size:"sm", id: "dropdown_close_icon"}) %>
34
+ <% end %>
35
+ <% end %>
36
+ <% end %>
37
+ <% end %>
38
+
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Playbook
4
+ module PbDropdown
5
+ class DropdownTrigger < Playbook::KitBase
6
+ prop :options, type: Playbook::Props::Array,
7
+ default: []
8
+ prop :id, type: Playbook::Props::String,
9
+ default: ""
10
+ prop :placeholder, type: Playbook::Props::String
11
+ prop :custom_display
12
+
13
+ def data
14
+ Hash(prop(:data)).merge(dropdown_trigger: true)
15
+ end
16
+
17
+ def classname
18
+ generate_classname("pb_dropdown_trigger")
19
+ end
20
+
21
+ def default_display_placeholder
22
+ placeholder || "Select..."
23
+ end
24
+
25
+ def trigger_wrapper_classes
26
+ generate_classname("dropdown_trigger_wrapper", "select_only")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,154 @@
1
+ import PbEnhancedElement from "../pb_enhanced_element";
2
+ import { PbDropdownKeyboard } from "./keyboard_accessibility";
3
+
4
+ const DROPDOWN_SELECTOR = "[data-pb-dropdown]";
5
+ const TRIGGER_SELECTOR = "[data-dropdown-trigger]";
6
+ const CONTAINER_SELECTOR = "[data-dropdown-container]";
7
+ const DOWN_ARROW_SELECTOR = "#dropdown_open_icon";
8
+ const UP_ARROW_SELECTOR = "#dropdown_close_icon";
9
+ const OPTION_SELECTOR = "[data-dropdown-option-label]";
10
+ const CUSTOM_DISPLAY_SELECTOR = "[data-dropdown-custom-trigger]";
11
+
12
+ export default class PbDropdown extends PbEnhancedElement {
13
+ static get selector() {
14
+ return DROPDOWN_SELECTOR;
15
+ }
16
+
17
+ connect() {
18
+ this.keyboardHandler = new PbDropdownKeyboard(this);
19
+ this.bindEventListeners();
20
+ this.updateArrowDisplay(false);
21
+ }
22
+
23
+ bindEventListeners() {
24
+ const customTrigger =
25
+ this.element.querySelector(CUSTOM_DISPLAY_SELECTOR) || this.element;
26
+ customTrigger.addEventListener("click", () =>
27
+ this.toggleElement(this.target)
28
+ );
29
+
30
+ this.target.addEventListener("click", this.handleOptionClick.bind(this));
31
+ document.addEventListener(
32
+ "click",
33
+ this.handleDocumentClick.bind(this),
34
+ true
35
+ );
36
+ }
37
+
38
+ handleOptionClick(event) {
39
+ const option = event.target.closest(OPTION_SELECTOR);
40
+ const hiddenInput = this.element.querySelector("#dropdown-selected-option");
41
+ if (option) {
42
+ const value = option.dataset.dropdownOptionLabel;
43
+ hiddenInput.value = JSON.parse(value).id;
44
+ this.onOptionSelected(value, option);
45
+ }
46
+ }
47
+
48
+ handleDocumentClick(event) {
49
+ if (this.isClickOutside(event) && this.target.classList.contains("open")) {
50
+ this.hideElement(this.target);
51
+ this.updateArrowDisplay(false);
52
+ }
53
+ }
54
+
55
+ isClickOutside(event) {
56
+ const customTrigger = this.element.querySelector(CUSTOM_DISPLAY_SELECTOR);
57
+ if (customTrigger) {
58
+ return !customTrigger.contains(event.target);
59
+ } else {
60
+ const triggerElement = this.element.querySelector(TRIGGER_SELECTOR);
61
+ const containerElement =
62
+ this.element.parentNode.querySelector(CONTAINER_SELECTOR);
63
+
64
+ const isOutsideTrigger = triggerElement
65
+ ? !triggerElement.contains(event.target)
66
+ : true;
67
+ const isOutsideContainer = containerElement
68
+ ? !containerElement.contains(event.target)
69
+ : true;
70
+
71
+ return isOutsideTrigger && isOutsideContainer;
72
+ }
73
+ }
74
+
75
+ onOptionSelected(value, selectedOption) {
76
+ const triggerElement = this.element.querySelector(
77
+ "#dropdown_trigger_display"
78
+ );
79
+ const customDisplayElement = this.element.querySelector(
80
+ "#dropdown_trigger_custom_display"
81
+ );
82
+ if (triggerElement) {
83
+ const selectedLabel = JSON.parse(value).label;
84
+ triggerElement.textContent = selectedLabel;
85
+ if (customDisplayElement) {
86
+ customDisplayElement.style.display = "block";
87
+ customDisplayElement.style.paddingRight = "8px";
88
+ }
89
+ }
90
+
91
+ const customTrigger = this.element.querySelector(CUSTOM_DISPLAY_SELECTOR);
92
+ if (customTrigger) {
93
+ if (this.target.classList.contains("open")) {
94
+ this.hideElement(this.target);
95
+ this.updateArrowDisplay(false);
96
+ }
97
+ }
98
+
99
+ const options = this.element.querySelectorAll(OPTION_SELECTOR);
100
+ options.forEach((option) => {
101
+ option.classList.remove("pb_dropdown_option_selected");
102
+ });
103
+ selectedOption.classList.add("pb_dropdown_option_selected");
104
+ console.log(`Selected value: ${value}`);
105
+ }
106
+
107
+ get target() {
108
+ return this.element.parentNode.querySelector(CONTAINER_SELECTOR);
109
+ }
110
+
111
+ showElement(elem) {
112
+ elem.classList.remove("close");
113
+ elem.classList.add("open");
114
+ elem.style.height = elem.scrollHeight + "px";
115
+ }
116
+
117
+ hideElement(elem) {
118
+ elem.style.height = elem.scrollHeight + "px";
119
+ window.setTimeout(() => {
120
+ elem.classList.add("close");
121
+ elem.classList.remove("open");
122
+ this.resetFocus();
123
+ }, 0);
124
+ }
125
+
126
+ resetFocus() {
127
+ if (this.keyboardHandler) {
128
+ this.keyboardHandler.focusedOptionIndex = -1;
129
+ const options = this.element.querySelectorAll(OPTION_SELECTOR);
130
+ options.forEach((option) =>
131
+ option.classList.remove("pb_dropdown_option_focused")
132
+ );
133
+ }
134
+ }
135
+
136
+ toggleElement(elem) {
137
+ if (elem.classList.contains("open")) {
138
+ this.hideElement(elem);
139
+ this.updateArrowDisplay(false);
140
+ return;
141
+ }
142
+ this.showElement(elem);
143
+ this.updateArrowDisplay(true);
144
+ }
145
+
146
+ updateArrowDisplay(isOpen) {
147
+ const downArrow = this.element.querySelector(DOWN_ARROW_SELECTOR);
148
+ const upArrow = this.element.querySelector(UP_ARROW_SELECTOR);
149
+ if (downArrow && upArrow) {
150
+ downArrow.style.display = isOpen ? "none" : "inline-block";
151
+ upArrow.style.display = isOpen ? "inline-block" : "none";
152
+ }
153
+ }
154
+ }