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

Sign up to get free protection for your applications and to get access to all the features.
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
+ }