playbook_ui 13.25.0 → 13.26.0.pre.alpha.PBNTR291Dropdownrailsv22840

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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/index.js +1 -0
  3. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.scss +14 -0
  4. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta.html.erb +33 -0
  5. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_beta.md +24 -0
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_default.md +5 -0
  7. data/app/pb_kits/playbook/pb_advanced_table/docs/example.yml +2 -2
  8. data/app/pb_kits/playbook/pb_advanced_table/index.js +78 -0
  9. data/app/pb_kits/playbook/pb_advanced_table/table_body.rb +4 -2
  10. data/app/pb_kits/playbook/pb_advanced_table/table_row.html.erb +3 -2
  11. data/app/pb_kits/playbook/pb_avatar/Utilities/GetPlacementPropsHelper.tsx +44 -0
  12. data/app/pb_kits/playbook/pb_avatar/_avatar.tsx +86 -21
  13. data/app/pb_kits/playbook/pb_avatar/avatar.html.erb +26 -3
  14. data/app/pb_kits/playbook/pb_avatar/avatar.rb +41 -0
  15. data/app/pb_kits/playbook/pb_avatar/docs/_avatar_badge_component_overlay.html.erb +71 -0
  16. data/app/pb_kits/playbook/pb_avatar/docs/_avatar_badge_component_overlay.jsx +77 -0
  17. data/app/pb_kits/playbook/pb_avatar/docs/_avatar_circle_icon_component_overlay.html.erb +71 -0
  18. data/app/pb_kits/playbook/pb_avatar/docs/_avatar_circle_icon_component_overlay.jsx +77 -0
  19. data/app/pb_kits/playbook/pb_avatar/docs/_avatar_default.jsx +20 -0
  20. data/app/pb_kits/playbook/pb_avatar/docs/example.yml +4 -0
  21. data/app/pb_kits/playbook/pb_avatar/docs/index.js +2 -0
  22. data/app/pb_kits/playbook/pb_bar_graph/_bar_graph.tsx +1 -1
  23. data/app/pb_kits/playbook/pb_body/_body.tsx +1 -1
  24. data/app/pb_kits/playbook/pb_button/_button.scss +1 -1
  25. data/app/pb_kits/playbook/pb_checkbox/_checkbox.scss +49 -0
  26. data/app/pb_kits/playbook/pb_checkbox/_checkbox.tsx +3 -0
  27. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +2 -1
  28. data/app/pb_kits/playbook/pb_checkbox/checkbox.test.js +14 -0
  29. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_disabled.html.erb +23 -0
  30. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_disabled.jsx +29 -0
  31. data/app/pb_kits/playbook/pb_checkbox/docs/example.yml +2 -0
  32. data/app/pb_kits/playbook/pb_checkbox/docs/index.js +1 -0
  33. data/app/pb_kits/playbook/pb_currency/docs/_currency_alignment_swift.md +43 -0
  34. data/app/pb_kits/playbook/pb_currency/docs/_currency_props_swift.md +12 -0
  35. data/app/pb_kits/playbook/pb_currency/docs/_currency_size_swift.md +31 -0
  36. data/app/pb_kits/playbook/pb_currency/docs/example.yml +5 -0
  37. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_on_change.md +3 -1
  38. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_on_close.md +3 -1
  39. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_quick_pick_range_limit.md +1 -1
  40. data/app/pb_kits/playbook/pb_date_range_stacked/docs/_date_range_stacked_default_swift.md +14 -0
  41. data/app/pb_kits/playbook/pb_date_range_stacked/docs/_date_range_stacked_props_swift.md +9 -0
  42. data/app/pb_kits/playbook/pb_date_range_stacked/docs/example.yml +4 -0
  43. data/app/pb_kits/playbook/pb_dialog/_dialog.scss +4 -2
  44. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +102 -35
  45. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +95 -26
  46. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.html.erb +10 -0
  47. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.jsx +4 -22
  48. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.md +1 -0
  49. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subcomponent_structure.html.erb +17 -0
  50. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subcomponent_structure.jsx +42 -0
  51. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_subcomponent_structure.md +7 -0
  52. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete.jsx +84 -0
  53. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete.md +1 -0
  54. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete_and_custom_display.jsx +101 -0
  55. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_autocomplete_and_custom_display.md +1 -0
  56. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.html.erb +60 -0
  57. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.jsx +6 -4
  58. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.md +5 -0
  59. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_options.html.erb +45 -0
  60. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_options.jsx +6 -9
  61. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_options.md +1 -0
  62. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_padding.html.erb +17 -0
  63. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_padding.jsx +48 -0
  64. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_padding.md +1 -0
  65. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_trigger.html.erb +47 -0
  66. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_trigger.jsx +5 -5
  67. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_trigger.md +1 -0
  68. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_external_control.jsx +59 -0
  69. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_hook.jsx +72 -0
  70. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.html.erb +10 -0
  71. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.jsx +39 -0
  72. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_label.md +1 -0
  73. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +16 -2
  74. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +7 -0
  75. data/app/pb_kits/playbook/pb_dropdown/dropdown.html.erb +26 -0
  76. data/app/pb_kits/playbook/pb_dropdown/dropdown.rb +20 -0
  77. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +200 -10
  78. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.html.erb +21 -0
  79. data/app/pb_kits/playbook/pb_dropdown/dropdown_container.rb +19 -0
  80. data/app/pb_kits/playbook/pb_dropdown/dropdown_option.html.erb +27 -0
  81. data/app/pb_kits/playbook/pb_dropdown/dropdown_option.rb +22 -0
  82. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.html.erb +43 -0
  83. data/app/pb_kits/playbook/pb_dropdown/dropdown_trigger.rb +30 -0
  84. data/app/pb_kits/playbook/pb_dropdown/hooks/useDropdown.tsx +2 -2
  85. data/app/pb_kits/playbook/pb_dropdown/hooks/useHandleOnKeydown.tsx +14 -9
  86. data/app/pb_kits/playbook/pb_dropdown/index.js +153 -0
  87. data/app/pb_kits/playbook/pb_dropdown/keyboard_accessibility.js +77 -0
  88. data/app/pb_kits/playbook/pb_dropdown/scss_partials/_dropdown_animation.scss +18 -0
  89. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +22 -8
  90. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +60 -31
  91. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +130 -68
  92. data/app/pb_kits/playbook/pb_dropdown/utilities/clickOutsideHelper.tsx +41 -0
  93. data/app/pb_kits/playbook/pb_dropdown/utilities/index.ts +2 -0
  94. data/app/pb_kits/playbook/pb_dropdown/utilities/subComponentHelper.tsx +9 -7
  95. data/app/pb_kits/playbook/pb_loading_inline/_loading_inline.tsx +3 -1
  96. data/app/pb_kits/playbook/pb_loading_inline/docs/_loading_inline_custom.html.erb +13 -0
  97. data/app/pb_kits/playbook/pb_loading_inline/docs/_loading_inline_custom.jsx +26 -0
  98. data/app/pb_kits/playbook/pb_loading_inline/docs/{_loading_inline_light.html.erb → _loading_inline_default.html.erb} +2 -2
  99. data/app/pb_kits/playbook/pb_loading_inline/docs/{_loading_inline_light.jsx → _loading_inline_default.jsx} +2 -2
  100. data/app/pb_kits/playbook/pb_loading_inline/docs/example.yml +4 -2
  101. data/app/pb_kits/playbook/pb_loading_inline/docs/index.js +2 -1
  102. data/app/pb_kits/playbook/pb_loading_inline/loading_inline.html.erb +1 -1
  103. data/app/pb_kits/playbook/pb_loading_inline/loading_inline.rb +1 -0
  104. data/app/pb_kits/playbook/pb_loading_inline/loading_inline.test.js +14 -0
  105. data/app/pb_kits/playbook/pb_progress_simple/docs/_progress_simple_flex.html.erb +3 -0
  106. data/app/pb_kits/playbook/pb_progress_simple/docs/_progress_simple_flex.jsx +16 -0
  107. data/app/pb_kits/playbook/pb_progress_simple/docs/_progress_simple_flex.md +1 -0
  108. data/app/pb_kits/playbook/pb_progress_simple/docs/example.yml +2 -0
  109. data/app/pb_kits/playbook/pb_progress_simple/docs/index.js +1 -0
  110. data/app/pb_kits/playbook/pb_progress_simple/progress_simple.rb +1 -1
  111. data/app/pb_kits/playbook/pb_radio/_radio.scss +35 -0
  112. data/app/pb_kits/playbook/pb_radio/_radio.tsx +3 -0
  113. data/app/pb_kits/playbook/pb_radio/docs/_radio_alignment.jsx +4 -1
  114. data/app/pb_kits/playbook/pb_radio/docs/_radio_default.jsx +4 -1
  115. data/app/pb_kits/playbook/pb_radio/docs/_radio_disabled.html.erb +26 -0
  116. data/app/pb_kits/playbook/pb_radio/docs/_radio_disabled.jsx +31 -0
  117. data/app/pb_kits/playbook/pb_radio/docs/_radio_error.jsx +2 -1
  118. data/app/pb_kits/playbook/pb_radio/docs/example.yml +2 -0
  119. data/app/pb_kits/playbook/pb_radio/docs/index.js +1 -0
  120. data/app/pb_kits/playbook/pb_radio/radio.rb +5 -0
  121. data/app/pb_kits/playbook/pb_radio/radio.test.js +17 -0
  122. data/app/pb_kits/playbook/pb_section_separator/_section_separator.scss +6 -2
  123. data/app/pb_kits/playbook/pb_section_separator/_section_separator_mixin.scss +11 -1
  124. data/app/pb_kits/playbook/pb_tooltip/_tooltip.tsx +1 -1
  125. data/app/pb_kits/playbook/playbook-rails.js +6 -0
  126. data/dist/menu.yml +1 -1
  127. data/dist/playbook-rails.js +6 -6
  128. data/lib/playbook/version.rb +2 -2
  129. metadata +65 -8
  130. data/app/pb_kits/playbook/pb_advanced_table/docs/_description.md +0 -1
@@ -0,0 +1,26 @@
1
+ <%= content_tag(:div,
2
+ aria: object.aria,
3
+ class: object.classname,
4
+ data: object.data,
5
+ id: object.id,
6
+ **combined_html_options) do %>
7
+ <% if object.label.present? %>
8
+ <%= pb_rails("caption", props: {text: object.label, margin_bottom:"xs"}) %>
9
+ <% end %>
10
+ <div class="dropdown_wrapper" style="position: relative">
11
+ <input type="hidden" name="<%= object.name %>" id="dropdown-selected-option" value=""/>
12
+ <% if content.present? %>
13
+ <%= content.presence %>
14
+ <% else %>
15
+ <%= pb_rails("dropdown/dropdown_trigger") %>
16
+ <%= pb_rails("dropdown/dropdown_container") do %>
17
+ <% if object.options.present? %>
18
+ <% object.options.each do |option| %>
19
+ <%= pb_rails("dropdown/dropdown_option", props: {option: option}) %>
20
+ <% end %>
21
+ <% end %>
22
+ <% end %>
23
+ <% end %>
24
+ </div>
25
+ <% end %>
26
+
@@ -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
@@ -1,17 +1,207 @@
1
- import { renderKit } from '../utilities/test-utils'
1
+ import React from "react"
2
+ import { render, screen } from "../utilities/test-utils"
2
3
 
3
- import { Dropdown } from '../'
4
+ import { Dropdown, Icon } from '../'
4
5
 
5
- /* See these resources for more testing info:
6
- - https://github.com/testing-library/jest-dom#usage for useage and examples
7
- - https://jestjs.io/docs/en/using-matchers
8
- */
9
6
 
10
- test('generated scaffold test - update me', () => {
11
- const props = {
12
- data: { testid: 'default' }
7
+ const testId = 'dropdown'
8
+
9
+ const options = [
10
+ {
11
+ label: "United States",
12
+ value: "United States",
13
+ areaCode: "+1",
14
+ icon: "🇺🇸",
15
+ id: "United-states"
16
+ },
17
+ {
18
+ label: "Canada",
19
+ value: "Canada",
20
+ areaCode: "+1",
21
+ icon: "🇨🇦",
22
+ id: "canada"
23
+ },
24
+ {
25
+ label: "Pakistan",
26
+ value: "Pakistan",
27
+ areaCode: "+92",
28
+ icon: "🇵🇰",
29
+ id: "pakistan"
13
30
  }
31
+ ];
32
+
33
+ const DefaultDropdownKit = () => {
34
+ return (
35
+ <Dropdown
36
+ data={{ testid: testId }}
37
+ options={options}
38
+ >
39
+ {options.map((option) => (
40
+ <Dropdown.Option key={option.id}
41
+ option={option}
42
+ />
43
+ ))}
44
+ </Dropdown>
45
+ )
46
+ }
14
47
 
15
- const kit = renderKit(Dropdown , props)
48
+ test('generated default kit and classname', () => {
49
+ render(<DefaultDropdownKit/>)
50
+
51
+ const kit = screen.getByTestId(testId)
16
52
  expect(kit).toBeInTheDocument()
53
+ expect(kit).toHaveClass('pb_dropdown')
54
+ })
55
+
56
+ test('generated default Trigger and Container when none passed in', () => {
57
+ render(<DefaultDropdownKit/>)
58
+
59
+ const kit = screen.getByTestId(testId)
60
+
61
+ const trigger = kit.querySelector('.pb_dropdown_trigger')
62
+ expect(trigger).toBeInTheDocument()
63
+
64
+ const container = kit.querySelector('.pb_dropdown_container')
65
+ expect(container).toBeInTheDocument()
66
+
67
+ const defaultTrigger = kit.querySelector('.dropdown_trigger_wrapper_select_only')
68
+ expect(defaultTrigger).toBeInTheDocument()
69
+ })
70
+
71
+ test('generated Options', () => {
72
+ render(<DefaultDropdownKit/>)
73
+
74
+ const kit = screen.getByTestId(testId)
75
+ const option = kit.querySelector('.pb_dropdown_option_list')
76
+ expect(option).toBeInTheDocument()
77
+ })
78
+
79
+ test('generated customDisplay for trigger', () => {
80
+ render (
81
+ <Dropdown
82
+ data={{ testid: testId }}
83
+ options={options}
84
+ >
85
+ <Dropdown.Trigger
86
+ customDisplay={<Icon icon="flag" />}
87
+ />
88
+ {options.map((option) => (
89
+ <Dropdown.Option key={option.id}
90
+ option={option}
91
+ />
92
+ ))}
93
+ </Dropdown>
94
+ )
95
+
96
+ const kit = screen.getByTestId(testId)
97
+ const trigger = kit.querySelector('.pb_dropdown_trigger')
98
+ const customDisplay = trigger.querySelector('.fa-flag.pb_icon_kit.fa-fw')
99
+ expect(customDisplay).toBeInTheDocument()
17
100
  })
101
+
102
+ test('generated placeholder prop', () => {
103
+ render (
104
+ <Dropdown
105
+ data={{ testid: testId }}
106
+ options={options}
107
+ >
108
+ <Dropdown.Trigger
109
+ placeholder="Select a country"
110
+ />
111
+ {options.map((option) => (
112
+ <Dropdown.Option key={option.id}
113
+ option={option}
114
+ />
115
+ ))}
116
+ </Dropdown>
117
+ )
118
+
119
+ const kit = screen.getByTestId(testId)
120
+ const trigger = kit.querySelector('.pb_dropdown_trigger')
121
+ expect(trigger).toHaveTextContent('Select a country')
122
+
123
+ })
124
+
125
+ test('generated label prop', () => {
126
+ render (
127
+ <Dropdown
128
+ data={{ testid: testId }}
129
+ label="Countries"
130
+ options={options}
131
+ >
132
+ {options.map((option) => (
133
+ <Dropdown.Option key={option.id}
134
+ option={option}
135
+ />
136
+ ))}
137
+ </Dropdown>
138
+ )
139
+
140
+ const kit = screen.getByTestId(testId)
141
+ const label = kit.querySelector('.pb_caption_kit_md')
142
+ expect(label).toHaveTextContent('Countries')
143
+ })
144
+
145
+ test('generated custom option', () => {
146
+ render (
147
+ <Dropdown
148
+ data={{ testid: testId }}
149
+ options={options}
150
+ >
151
+ {options.map((option) => (
152
+ <Dropdown.Option key={option.id}
153
+ option={option}
154
+ >
155
+ <Icon icon={option.icon} />
156
+ </Dropdown.Option>
157
+ ))}
158
+ </Dropdown>
159
+ )
160
+
161
+ const kit = screen.getByTestId(testId)
162
+ const customOption = kit.querySelector('.pb_icon_kit_emoji')
163
+ expect(customOption).toBeInTheDocument()
164
+ })
165
+
166
+ test('generated custom Trigger', () => {
167
+ render (
168
+ <Dropdown
169
+ data={{ testid: testId }}
170
+ options={options}
171
+ >
172
+ <Dropdown.Trigger>
173
+ <Icon icon="home" />
174
+ </Dropdown.Trigger>
175
+ {options.map((option) => (
176
+ <Dropdown.Option key={option.id}
177
+ option={option}
178
+ />
179
+ ))}
180
+ </Dropdown>
181
+ )
182
+
183
+ const kit = screen.getByTestId(testId)
184
+ const trigger = kit.querySelector('.pb_dropdown_trigger')
185
+ const customTrigger = trigger.querySelector('.fa-home.pb_icon_kit.fa-fw')
186
+ expect(customTrigger).toBeInTheDocument()
187
+ })
188
+
189
+ test('selected option on click', () => {
190
+ render (
191
+ <Dropdown
192
+ data={{ testid: testId }}
193
+ options={options}
194
+ >
195
+ {options.map((option) => (
196
+ <Dropdown.Option key={option.id}
197
+ option={option}
198
+ />
199
+ ))}
200
+ </Dropdown>
201
+ )
202
+
203
+ const kit = screen.getByTestId(testId)
204
+ const option = kit.querySelector('.pb_dropdown_option_list')
205
+ option.click()
206
+ expect(option).toHaveClass('pb_dropdown_option_selected p_xs')
207
+ })
@@ -0,0 +1,21 @@
1
+ <%= content_tag(:div,
2
+ aria: object.aria,
3
+ class: object.classname,
4
+ data: object.data,
5
+ id: object.id,
6
+ style: object.container_style,
7
+ **combined_html_options) do %>
8
+ <%= pb_rails("list", props: {ordered: false, borderless: false}) do %>
9
+ <% if content.present? %>
10
+ <%= content.presence %>
11
+ <% else %>
12
+ <%= pb_rails("list/item", props: {
13
+ display: "flex",
14
+ justify_content: "center",
15
+ padding:"xs",
16
+ }) do %>
17
+ <%= pb_rails("body", props: {text: "No option"}) %>
18
+ <% end %>
19
+ <% end %>
20
+ <% end %>
21
+ <% 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,27 @@
1
+ <%= content_tag(:div,
2
+ aria: object.aria,
3
+ class: object.classname,
4
+ data: object.data,
5
+ id: object.option[:id],
6
+ **combined_html_options) do %>
7
+ <%= pb_rails("list/item", props: {
8
+ display: "flex",
9
+ justify_content: "center",
10
+ padding:"none",
11
+ cursor: "pointer"
12
+ }) do %>
13
+ <%= pb_rails("flex", props: {
14
+ align: "center",
15
+ classname:"dropdown_option_wrapper",
16
+ justify: "between",
17
+ padding_x:"sm",
18
+ padding_y:"xxs",
19
+ }) do %>
20
+ <% if content.present? %>
21
+ <%= content.presence %>
22
+ <% else %>
23
+ <%= pb_rails("body", props: {text: object.option[:label]}) %>
24
+ <% end %>
25
+ <% end %>
26
+ <% end %>
27
+ <% 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,43 @@
1
+ <%= content_tag(:div,
2
+ aria: object.aria,
3
+ class: object.classname,
4
+ data: object.data,
5
+ id: object.id,
6
+ **combined_html_options) do %>
7
+ <% if content.present? %>
8
+ <div style="display: inline-block" tabindex="0" data-dropdown-custom-trigger>
9
+ <%= content.presence %>
10
+ </div>
11
+ <% else %>
12
+ <%= pb_rails("flex", props: {
13
+ align: "center",
14
+ border_radius:"lg",
15
+ classname: object.trigger_wrapper_classes,
16
+ cursor: "pointer",
17
+ justify: "between",
18
+ padding_x:"sm",
19
+ padding_y:"xs",
20
+ html_options: {tabindex:"0"}
21
+ }) do %>
22
+ <%= pb_rails("flex/flex_item") do %>
23
+ <%= pb_rails("flex", props: {align: "center"}) do %>
24
+ <% if object.custom_display.present? %>
25
+ <%= pb_rails("flex", props: {align: "center"}) do %>
26
+ <div id="dropdown_trigger_custom_display" style="display: none;">
27
+ <%= object.custom_display %>
28
+ </div>
29
+ <%= pb_rails("body", props: {text: object.default_display_placeholder, id: "dropdown_trigger_display"}) %>
30
+ <% end %>
31
+ <% else %>
32
+ <%= pb_rails("body", props: {text: object.default_display_placeholder, id: "dropdown_trigger_display"}) %>
33
+ <% end %>
34
+ <% end %>
35
+ <% end %>
36
+ <%= pb_rails("body", props: {display: "flex"}) do %>
37
+ <%= pb_rails("icon", props: {icon: "chevron-down", cursor: "pointer", size:"sm", id: "dropdown_open_icon"}) %>
38
+ <%= pb_rails("icon", props: {icon: "chevron-up", cursor: "pointer", size:"sm", id: "dropdown_close_icon"}) %>
39
+ <% end %>
40
+ <% end %>
41
+ <% end %>
42
+ <% end %>
43
+
@@ -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
@@ -1,11 +1,11 @@
1
1
  import {useState} from 'react';
2
2
 
3
3
 
4
- const useDropdown = (initial=true) => {
4
+ const useDropdown = (initial= true) => {
5
5
 
6
6
  const [isDropDownClosed, setIsDropDownClosed] = useState(initial);
7
7
 
8
- const toggleDropdown = () => setIsDropDownClosed(!isDropDownClosed);
8
+ const toggleDropdown = () => setIsDropDownClosed((prev) => !prev);
9
9
 
10
10
  return [
11
11
  isDropDownClosed,
@@ -6,16 +6,21 @@ export const useHandleOnKeyDown = () => {
6
6
 
7
7
  const {
8
8
  autocomplete,
9
- focusedOptionIndex,
10
9
  filteredOptions,
11
- setFocusedOptionIndex,
10
+ focusedOptionIndex,
11
+ handleBackspace,
12
12
  handleOptionClick,
13
+ selected,
14
+ setFocusedOptionIndex,
13
15
  setIsDropDownClosed,
14
- handleBackspace,
15
- selected
16
16
  }= useContext(DropdownContext)
17
17
 
18
18
  return (e: React.KeyboardEvent) => {
19
+
20
+ if (e.key !== "Tab" && autocomplete && selected && selected.label) {
21
+ handleBackspace();
22
+ }
23
+
19
24
  switch (e.key) {
20
25
  case "Backspace":
21
26
  case "Delete":
@@ -43,13 +48,13 @@ const {
43
48
  e.preventDefault();
44
49
  handleOptionClick(filteredOptions[focusedOptionIndex]);
45
50
  setFocusedOptionIndex(-1)
51
+ } else if (focusedOptionIndex === -1) {
52
+ setIsDropDownClosed(false)
46
53
  }
47
54
  break;
48
- default:
49
- if (selected && selected.label) {
50
- e.preventDefault();
51
- handleBackspace();
52
- }
55
+ case "Tab":
56
+ setIsDropDownClosed(true);
57
+ setFocusedOptionIndex(-1)
53
58
  break;
54
59
  }
55
60
  }
@@ -0,0 +1,153 @@
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
+ }
105
+
106
+ get target() {
107
+ return this.element.parentNode.querySelector(CONTAINER_SELECTOR);
108
+ }
109
+
110
+ showElement(elem) {
111
+ elem.classList.remove("close");
112
+ elem.classList.add("open");
113
+ elem.style.height = elem.scrollHeight + "px";
114
+ }
115
+
116
+ hideElement(elem) {
117
+ elem.style.height = elem.scrollHeight + "px";
118
+ window.setTimeout(() => {
119
+ elem.classList.add("close");
120
+ elem.classList.remove("open");
121
+ this.resetFocus();
122
+ }, 0);
123
+ }
124
+
125
+ resetFocus() {
126
+ if (this.keyboardHandler) {
127
+ this.keyboardHandler.focusedOptionIndex = -1;
128
+ const options = this.element.querySelectorAll(OPTION_SELECTOR);
129
+ options.forEach((option) =>
130
+ option.classList.remove("pb_dropdown_option_focused")
131
+ );
132
+ }
133
+ }
134
+
135
+ toggleElement(elem) {
136
+ if (elem.classList.contains("open")) {
137
+ this.hideElement(elem);
138
+ this.updateArrowDisplay(false);
139
+ return;
140
+ }
141
+ this.showElement(elem);
142
+ this.updateArrowDisplay(true);
143
+ }
144
+
145
+ updateArrowDisplay(isOpen) {
146
+ const downArrow = this.element.querySelector(DOWN_ARROW_SELECTOR);
147
+ const upArrow = this.element.querySelector(UP_ARROW_SELECTOR);
148
+ if (downArrow && upArrow) {
149
+ downArrow.style.display = isOpen ? "none" : "inline-block";
150
+ upArrow.style.display = isOpen ? "inline-block" : "none";
151
+ }
152
+ }
153
+ }