playbook_ui 3.4.0 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +1 -0
  3. data/app/pb_kits/playbook/index.js +1 -0
  4. data/app/pb_kits/playbook/packs/examples.js +3 -0
  5. data/app/pb_kits/playbook/pb_body/_body_mixins.scss +6 -0
  6. data/app/pb_kits/playbook/pb_checkbox/_checkbox.html.erb +1 -1
  7. data/app/pb_kits/playbook/pb_checkbox/_checkbox.jsx +11 -3
  8. data/app/pb_kits/playbook/pb_checkbox/_checkbox.scss +12 -0
  9. data/app/pb_kits/playbook/pb_checkbox/checkbox.rb +10 -1
  10. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_dark_error.html.erb +7 -0
  11. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_dark_error.jsx +18 -0
  12. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_error.html.erb +6 -0
  13. data/app/pb_kits/playbook/pb_checkbox/docs/_checkbox_error.jsx +17 -0
  14. data/app/pb_kits/playbook/pb_checkbox/docs/example.yml +4 -0
  15. data/app/pb_kits/playbook/pb_checkbox/docs/index.js +2 -0
  16. data/app/pb_kits/playbook/pb_enhanced_element/element_observer.js +67 -0
  17. data/app/pb_kits/playbook/pb_enhanced_element/index.js +62 -0
  18. data/app/pb_kits/playbook/pb_form/docs/_form_form_with.html.erb +1 -1
  19. data/app/pb_kits/playbook/pb_form/docs/_form_simple_form.html.erb +1 -1
  20. data/app/pb_kits/playbook/pb_form/form_builder/simple_form_builder.rb +1 -1
  21. data/app/pb_kits/playbook/pb_home_address_street/_home_address_street.html.erb +1 -1
  22. data/app/pb_kits/playbook/pb_home_address_street/_home_address_street.jsx +3 -1
  23. data/app/pb_kits/playbook/pb_home_address_street/docs/_home_address_street_dark.html.erb +1 -0
  24. data/app/pb_kits/playbook/pb_home_address_street/docs/_home_address_street_dark.jsx +1 -0
  25. data/app/pb_kits/playbook/pb_home_address_street/docs/_home_address_street_default.html.erb +1 -0
  26. data/app/pb_kits/playbook/pb_home_address_street/docs/_home_address_street_default.jsx +1 -0
  27. data/app/pb_kits/playbook/pb_home_address_street/home_address_street.rb +1 -0
  28. data/app/pb_kits/playbook/pb_list/_item.html.erb +5 -3
  29. data/app/pb_kits/playbook/pb_list/_list.html.erb +7 -3
  30. data/app/pb_kits/playbook/pb_list/item.rb +2 -0
  31. data/app/pb_kits/playbook/pb_list/list.rb +2 -0
  32. data/app/pb_kits/playbook/pb_radio/_radio.html.erb +1 -1
  33. data/app/pb_kits/playbook/pb_radio/_radio.jsx +7 -2
  34. data/app/pb_kits/playbook/pb_radio/_radio.scss +15 -5
  35. data/app/pb_kits/playbook/pb_radio/docs/_radio_dark_error.html.erb +7 -0
  36. data/app/pb_kits/playbook/pb_radio/docs/_radio_dark_error.jsx +17 -0
  37. data/app/pb_kits/playbook/pb_radio/docs/_radio_error.html.erb +6 -0
  38. data/app/pb_kits/playbook/pb_radio/docs/_radio_error.jsx +16 -0
  39. data/app/pb_kits/playbook/pb_radio/docs/example.yml +4 -3
  40. data/app/pb_kits/playbook/pb_radio/docs/index.js +2 -0
  41. data/app/pb_kits/playbook/pb_radio/radio.rb +16 -8
  42. data/app/pb_kits/playbook/pb_select/_select.html.erb +2 -0
  43. data/app/pb_kits/playbook/pb_select/_select.jsx +27 -16
  44. data/app/pb_kits/playbook/pb_select/_select.scss +22 -1
  45. data/app/pb_kits/playbook/pb_select/docs/_select_dark_error.html.erb +25 -0
  46. data/app/pb_kits/playbook/pb_select/docs/_select_dark_error.jsx +37 -0
  47. data/app/pb_kits/playbook/pb_select/docs/_select_error.html.erb +24 -0
  48. data/app/pb_kits/playbook/pb_select/docs/_select_error.jsx +35 -0
  49. data/app/pb_kits/playbook/pb_select/docs/example.yml +4 -0
  50. data/app/pb_kits/playbook/pb_select/docs/index.js +2 -0
  51. data/app/pb_kits/playbook/pb_select/select.rb +14 -11
  52. data/app/pb_kits/playbook/pb_text_input/_text_input.html.erb +7 -1
  53. data/app/pb_kits/playbook/pb_text_input/_text_input.jsx +22 -9
  54. data/app/pb_kits/playbook/pb_text_input/_text_input.scss +19 -0
  55. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_dark_error.html.erb +14 -0
  56. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_dark_error.jsx +18 -0
  57. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_error.html.erb +2 -0
  58. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_error.jsx +17 -0
  59. data/app/pb_kits/playbook/pb_text_input/docs/example.yml +4 -0
  60. data/app/pb_kits/playbook/pb_text_input/docs/index.js +2 -0
  61. data/app/pb_kits/playbook/pb_text_input/text_input.rb +12 -3
  62. data/app/pb_kits/playbook/pb_textarea/_textarea.html.erb +6 -0
  63. data/app/pb_kits/playbook/pb_textarea/_textarea.jsx +15 -5
  64. data/app/pb_kits/playbook/pb_textarea/_textarea.scss +14 -0
  65. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_dark_error.html.erb +6 -0
  66. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_dark_error.jsx +20 -0
  67. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_error.html.erb +1 -0
  68. data/app/pb_kits/playbook/pb_textarea/docs/_textarea_error.jsx +19 -0
  69. data/app/pb_kits/playbook/pb_textarea/docs/example.yml +4 -0
  70. data/app/pb_kits/playbook/pb_textarea/docs/index.js +2 -0
  71. data/app/pb_kits/playbook/pb_textarea/textarea.rb +13 -6
  72. data/app/pb_kits/playbook/pb_typeahead/_typeahead.html.erb +16 -0
  73. data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +52 -0
  74. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_default.html.erb +51 -0
  75. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +3 -0
  76. data/app/pb_kits/playbook/pb_typeahead/index.js +158 -0
  77. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +28 -0
  78. data/app/pb_kits/playbook/tokens/_colors.scss +2 -0
  79. data/lib/playbook/version.rb +1 -1
  80. metadata +30 -2
@@ -17,6 +17,15 @@
17
17
  @include pb_textarea_focus_light;
18
18
  }
19
19
 
20
+ &.error {
21
+ [class*=pb_body_kit] {
22
+ margin-top: $space_xs / 2;
23
+ }
24
+ > textarea {
25
+ border-color: $error;
26
+ }
27
+ }
28
+
20
29
  &[class*=_dark] {
21
30
  textarea::placeholder {
22
31
  @include pb_body_light_dark;
@@ -27,5 +36,10 @@
27
36
  textarea:focus {
28
37
  @include pb_textarea_focus_dark;
29
38
  }
39
+ &.error {
40
+ > textarea {
41
+ border-color: $error_dark;
42
+ }
43
+ }
30
44
  }
31
45
  }
@@ -0,0 +1,6 @@
1
+ <%= pb_rails("textarea", props: {
2
+ dark: true,
3
+ error: "This field has an error!",
4
+ label: "Label",
5
+ rows: 4
6
+ }) %>
@@ -0,0 +1,20 @@
1
+ import React from 'react'
2
+ import { Textarea } from '../..'
3
+
4
+ const TextareaDarkError = () => {
5
+ return (
6
+ <div>
7
+ <Textarea
8
+ dark
9
+ error="This field has an error!"
10
+ label="Label"
11
+ name="comment"
12
+ placeholder="Placeholder text"
13
+ value="Default value text"
14
+ />
15
+
16
+ </div>
17
+ )
18
+ }
19
+
20
+ export default TextareaDarkError
@@ -0,0 +1 @@
1
+ <%= pb_rails("textarea", props: { error: "This field has an error!", label: "Label", rows: 4}) %>
@@ -0,0 +1,19 @@
1
+ import React from 'react'
2
+ import { Textarea } from '../..'
3
+
4
+ const TextareaError = () => {
5
+ return (
6
+ <div>
7
+ <Textarea
8
+ error="This field has an error!"
9
+ label="Label"
10
+ name="comment"
11
+ placeholder="Placeholder text"
12
+ value="Default value text"
13
+ />
14
+
15
+ </div>
16
+ )
17
+ }
18
+
19
+ export default TextareaError
@@ -4,6 +4,8 @@ examples:
4
4
  - textarea_default: Default
5
5
  - textarea_custom: Custom Textarea Input
6
6
  - textarea_dark: Dark
7
+ - textarea_error: Textarea w/ Error
8
+ - textarea_dark_error: Textarea w/ Error
7
9
 
8
10
 
9
11
 
@@ -11,5 +13,7 @@ examples:
11
13
  - textarea_default: Default
12
14
  - textarea_custom: Custom Textarea Input
13
15
  - textarea_dark: Dark
16
+ - textarea_error: Textarea w/ Error
17
+ - textarea_dark_error: Textarea w/ Error
14
18
 
15
19
 
@@ -1,3 +1,5 @@
1
1
  export { default as TextareaDefault } from './_textarea_default.jsx'
2
2
  export { default as TextareaCustom } from './_textarea_custom.jsx'
3
3
  export { default as TextareaDark } from './_textarea_dark.jsx'
4
+ export { default as TextareaError } from './_textarea_error.jsx'
5
+ export { default as TextareaDarkError } from './_textarea_dark_error.jsx'
@@ -7,24 +7,31 @@ module Playbook
7
7
 
8
8
  partial "pb_textarea/textarea"
9
9
 
10
+ prop :dark, type: Playbook::Props::Boolean,
11
+ default: false
12
+ prop :error
10
13
  prop :object
11
- prop :method
12
14
  prop :label
13
- prop :placeholder
14
- prop :value
15
+ prop :method
15
16
  prop :name
16
- prop :dark, type: Playbook::Props::Boolean,
17
- default: false
17
+ prop :placeholder
18
18
  prop :rows, type: Playbook::Props::Number,
19
19
  default: 4
20
+ prop :value
20
21
 
21
22
  def classname
22
- generate_classname("pb_textarea_kit", dark_class)
23
+ generate_classname("pb_textarea_kit", dark_class) + error_class
23
24
  end
24
25
 
26
+ private
27
+
25
28
  def dark_class
26
29
  dark ? "dark" : nil
27
30
  end
31
+
32
+ def error_class
33
+ error ? " error" : ""
34
+ end
28
35
  end
29
36
  end
30
37
  end
@@ -0,0 +1,16 @@
1
+ <%= content_tag(:div,
2
+ id: object.id,
3
+ data: object.data,
4
+ class: object.classname) do %>
5
+ <div class="pb_typeahead_wrapper">
6
+ <%= pb_rails("text_input", props: { type: "search", label: object.label, placeholder: object.placeholder }) %>
7
+ <%= pb_rails("list", props: { ordered: false, dark: false, borderless: false, xpadding: true, role: "status", aria: { live: "polite" }, data: { pb_typeahead_kit_results: true } }) do %>
8
+ <% end %>
9
+ </div>
10
+
11
+ <template data-pb-typeahead-kit-result-option>
12
+ <%= pb_rails("list/item") do %>
13
+ <button type="button" data-result-option-item><%= tag(:slot, name: :content) %></button>
14
+ <% end %>
15
+ </template>
16
+ <% end %>
@@ -0,0 +1,52 @@
1
+ [class^=pb_typeahead_kit] {
2
+ .pb_typeahead_wrapper {
3
+ position: relative;
4
+ }
5
+
6
+ .text_input_wrapper {
7
+ margin-bottom: 0;
8
+ }
9
+
10
+ .pb_item_kit {
11
+ padding: ($space_xs + 2) 0;
12
+
13
+ &:hover {
14
+ background-color: $bg_light;
15
+ }
16
+ }
17
+
18
+ &:focus-within [class^=pb_list_kit] {
19
+ max-height: 18em;
20
+ overflow-y: auto;
21
+ overscroll-behavior: contain;
22
+ position: absolute;
23
+ left: 0;
24
+ right: 0;
25
+ background: white;
26
+ box-shadow: $shadow_deep;
27
+ z-index: 100;
28
+ }
29
+
30
+ &:not(:focus-within) [class^=pb_list_kit] {
31
+ display: none;
32
+ }
33
+
34
+ [class^=pb_list_kit] > [data-pb-typeahead-kit-results] > li {
35
+ &:focus-within {
36
+ background: $bg_light;
37
+ }
38
+
39
+ > button {
40
+ background: none;
41
+ color: inherit;
42
+ border: none;
43
+ padding: 0;
44
+ font: inherit;
45
+ cursor: pointer;
46
+ outline: inherit;
47
+ width: 100%;
48
+ height: 100%;
49
+ text-align: left;
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,51 @@
1
+ <%= pb_rails("typeahead", props: { data: { typeahead_example: true } }) %>
2
+
3
+ <br><br><br>
4
+
5
+ <%= pb_rails("card", props: { padding: "xl", data: { typeahead_example_selected_option: true } }) do %>
6
+ <%= pb_rails("body") do %>
7
+ Use the above input to search for users on Github, and see the results as you type.
8
+ <% end %>
9
+
10
+ <%= pb_rails("body") do %>
11
+ When you make a selection, you will see it apear in the list below
12
+ <% end %>
13
+
14
+ <div data-selected-option></div>
15
+ <% end %>
16
+
17
+ <template data-typeahead-example-result-option>
18
+ <%= pb_rails("user", props: {
19
+ name: tag(:slot, name: "name"),
20
+ orientation: "horizontal",
21
+ align: "left",
22
+ avatar_url: "placeholder"
23
+ }) %>
24
+ </template>
25
+
26
+ <%= javascript_tag defer: "defer" do %>
27
+ document.addEventListener("pb-typeahead-kit-search", function(event) {
28
+ if (!event.target.dataset.typeaheadExample) return;
29
+
30
+ fetch(`https://api.github.com/search/users?q=${encodeURIComponent(event.detail.searchingFor)}`)
31
+ .then(response => response.json())
32
+ .then((result) => {
33
+ const resultOptionTemplate = document.querySelector("[data-typeahead-example-result-option]")
34
+
35
+ event.detail.setResults((result.items || []).map((user) => {
36
+ const wrapper = resultOptionTemplate.content.cloneNode(true)
37
+ wrapper.querySelector('slot[name="name"]').replaceWith(user.login)
38
+ wrapper.querySelector('img').setAttribute("src", user.avatar_url)
39
+ return wrapper
40
+ }))
41
+ })
42
+ })
43
+
44
+
45
+ document.addEventListener("pb-typeahead-kit-result-option-selected", function(event) {
46
+ if (!event.target.dataset.typeaheadExample) return;
47
+
48
+ document.querySelector("[data-typeahead-example-selected-option] [data-selected-option]").innerHTML = ""
49
+ document.querySelector("[data-typeahead-example-selected-option] [data-selected-option]").appendChild(event.detail.selected)
50
+ })
51
+ <% end %>
@@ -0,0 +1,3 @@
1
+ examples:
2
+ rails:
3
+ - typeahead_default: Default
@@ -0,0 +1,158 @@
1
+ import PbEnhancedElement from '../pb_enhanced_element'
2
+ import { debounce } from 'lodash'
3
+
4
+ export default class PbTypeahead extends PbEnhancedElement {
5
+ static get selector() {
6
+ return '[data-pb-typeahead-kit]'
7
+ }
8
+
9
+ connect() {
10
+ this.element.addEventListener('keydown', (event) => this.handleKeydown(event))
11
+ this.searchInput.addEventListener('focus', () => this.debouncedSearch())
12
+ this.searchInput.addEventListener('input', () => this.debouncedSearch())
13
+ this.resultsElement.addEventListener('click', (event) => this.optionSelected(event))
14
+ }
15
+
16
+ handleKeydown(event) {
17
+ if (event.key === 'ArrowUp') {
18
+ event.preventDefault()
19
+ this.focusPreviousOption()
20
+ } else if (event.key === 'ArrowDown') {
21
+ event.preventDefault()
22
+ this.focusNextOption()
23
+ }
24
+ }
25
+
26
+ search() {
27
+ if (this.searchTerm.length < this.searchTermMinimumLength) return this.clearResults()
28
+ if (this.resultsOptionCache.has(this.searchTerm)) return this.showResults()
29
+
30
+ const searchTerm = this.searchTerm
31
+ const search = {
32
+ searchingFor: searchTerm,
33
+ setResults: (results) => {
34
+ this.resultsCacheUpdate(searchTerm, results)
35
+ },
36
+ }
37
+
38
+ this.element.dispatchEvent(new CustomEvent('pb-typeahead-kit-search', { bubbles: true, detail: search }))
39
+ }
40
+
41
+ resultsCacheUpdate(searchTerm, results) {
42
+ if (this.resultsOptionCache.has(searchTerm)) this.resultsOptionCache.delete(searchTerm)
43
+ if (this.resultsOptionCache.size > 32) this.resultsOptionCache.delete(this.resultsOptionCache.keys[0])
44
+
45
+ this.resultsOptionCache.set(searchTerm, results)
46
+ this.showResults()
47
+ }
48
+
49
+ resultsCacheClear() {
50
+ this.resultsOptionCache.clear()
51
+ }
52
+
53
+ get debouncedSearch() {
54
+ return this._debouncedSearch = (
55
+ this._debouncedSearch ||
56
+ debounce(this.search, this.searchDebounceTimeout).bind(this)
57
+ )
58
+ }
59
+
60
+ showResults() {
61
+ if (!this.resultsOptionCache.has(this.searchTerm)) return
62
+
63
+ this.clearResults()
64
+ for (const result of this.resultsOptionCache.get(this.searchTerm)) {
65
+ this.resultsElement.appendChild(this.newResultOption(result.cloneNode(true)))
66
+ }
67
+ for (const result of this.resultsElement.querySelectorAll('[data-result-option-item]')) {
68
+ result.addEventListener('mousedown', (event) => this.optionSelected(event))
69
+ }
70
+ }
71
+
72
+ optionSelected(event) {
73
+ const resultOption = event.target.closest('[data-result-option-item]')
74
+ if (!resultOption) return
75
+
76
+ this.resultsCacheClear()
77
+ this.searchInputClear()
78
+ this.clearResults()
79
+
80
+ this.element.dispatchEvent(new CustomEvent('pb-typeahead-kit-result-option-selected', { bubbles: true, detail: { selected: resultOption, typeahead: this } }))
81
+ }
82
+
83
+ clearResults() {
84
+ this.resultsElement.innerHTML = ''
85
+ }
86
+
87
+ newResultOption(content) {
88
+ const resultOption = this.resultOptionTemplate.content.cloneNode(true)
89
+ resultOption.querySelector('slot[name="content"]').replaceWith(content)
90
+ return resultOption
91
+ }
92
+
93
+ focusPreviousOption() {
94
+ const currentIndex = this.resultOptionItems.indexOf(this.currentSelectedResultOptionItem)
95
+ const previousIndex = currentIndex - 1
96
+ const previousOptionItem = (
97
+ this.resultOptionItems[previousIndex] ||
98
+ this.resultOptionItems[this.resultOptionItems.length - 1]
99
+ )
100
+ previousOptionItem.focus()
101
+ }
102
+
103
+ focusNextOption() {
104
+ const currentIndex = this.resultOptionItems.indexOf(this.currentSelectedResultOptionItem)
105
+ const nextIndex = currentIndex + 1
106
+ const nextOptionItem = (
107
+ this.resultOptionItems[nextIndex] ||
108
+ this.resultOptionItems[0]
109
+ )
110
+ nextOptionItem.focus()
111
+ }
112
+
113
+ get resultOptionItems() {
114
+ return Array.from(this.resultsElement.querySelectorAll('[data-result-option-item]'))
115
+ }
116
+
117
+ get currentSelectedResultOptionItem() {
118
+ return document.activeElement.closest('[data-result-option-item]')
119
+ }
120
+
121
+ get searchInput() {
122
+ return this._searchInput = (this._searchInput || this.element.querySelector('input[type="search"]'))
123
+ }
124
+
125
+ get searchTerm() {
126
+ return this.searchInput.value
127
+ }
128
+
129
+ searchInputClear() {
130
+ this.searchInput.value = ''
131
+ }
132
+
133
+ get searchTermMinimumLength() {
134
+ return this.element.dataset.pbTypeaheadKitSearchTermMinimumLength
135
+ }
136
+
137
+ get searchDebounceTimeout() {
138
+ return this.element.dataset.pbTypeaheadKitSearchDebounceTimeout
139
+ }
140
+
141
+ get resultsElement() {
142
+ return this._resultsElement = (this._resultsElement || this.element.querySelector('[data-pb-typeahead-kit-results]'))
143
+ }
144
+
145
+ get resultOptionTemplate() {
146
+ return this._resultOptionTemplate = (
147
+ this._resultOptionTemplate ||
148
+ this.element.querySelector('template[data-pb-typeahead-kit-result-option]')
149
+ )
150
+ }
151
+
152
+ get resultsOptionCache() {
153
+ return this._resultsOptionCache = (
154
+ this._resultsOptionCache ||
155
+ new Map
156
+ )
157
+ }
158
+ }
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Playbook
4
+ module PbTypeahead
5
+ class Typeahead
6
+ include Playbook::Props
7
+
8
+ prop :label
9
+ prop :placeholder
10
+ prop :search_term_minimum_length, default: 3
11
+ prop :search_debounce_timeout, default: 250
12
+
13
+ partial "pb_typeahead/typeahead"
14
+
15
+ def classname
16
+ generate_classname("pb_typeahead_kit")
17
+ end
18
+
19
+ def data
20
+ Hash(values[:data]).merge(
21
+ pb_typeahead_kit: true,
22
+ pb_typeahead_kit_search_term_minimum_length: search_term_minimum_length,
23
+ pb_typeahead_kit_search_debounce_timeout: search_debounce_timeout,
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end