govuk_publishing_components 58.1.1 → 58.2.0

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/select-with-search/cross-icon.svg +6 -0
  3. data/app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-search-tracker.js +4 -0
  4. data/app/assets/javascripts/govuk_publishing_components/components/select-with-search.js +57 -0
  5. data/app/assets/stylesheets/govuk_publishing_components/_all_components.scss +1 -0
  6. data/app/assets/stylesheets/govuk_publishing_components/components/_select-with-search.scss +168 -0
  7. data/app/assets/stylesheets/govuk_publishing_components/components/_select.scss +6 -0
  8. data/app/views/govuk_publishing_components/components/_select.html.erb +22 -23
  9. data/app/views/govuk_publishing_components/components/_select_with_search.html.erb +14 -0
  10. data/app/views/govuk_publishing_components/components/docs/select.yml +11 -0
  11. data/app/views/govuk_publishing_components/components/docs/select_with_search.yml +196 -0
  12. data/lib/govuk_publishing_components/presenters/select_helper.rb +8 -5
  13. data/lib/govuk_publishing_components/presenters/select_with_search_helper.rb +92 -0
  14. data/lib/govuk_publishing_components/version.rb +1 -1
  15. data/lib/govuk_publishing_components.rb +1 -0
  16. data/node_modules/choices.js/LICENSE +21 -0
  17. data/node_modules/choices.js/README.md +1360 -0
  18. data/node_modules/choices.js/package.json +173 -0
  19. data/node_modules/choices.js/public/assets/scripts/choices.js +5230 -0
  20. data/node_modules/choices.js/public/assets/scripts/choices.min.js +2 -0
  21. data/node_modules/choices.js/public/assets/scripts/choices.mjs +5222 -0
  22. data/node_modules/choices.js/public/assets/scripts/choices.search-basic.js +4748 -0
  23. data/node_modules/choices.js/public/assets/scripts/choices.search-basic.min.js +2 -0
  24. data/node_modules/choices.js/public/assets/scripts/choices.search-basic.mjs +4740 -0
  25. data/node_modules/choices.js/public/assets/scripts/choices.search-kmp.js +3631 -0
  26. data/node_modules/choices.js/public/assets/scripts/choices.search-kmp.min.js +2 -0
  27. data/node_modules/choices.js/public/assets/scripts/choices.search-kmp.mjs +3623 -0
  28. data/node_modules/choices.js/public/assets/scripts/choices.search-prefix.js +3590 -0
  29. data/node_modules/choices.js/public/assets/scripts/choices.search-prefix.min.js +2 -0
  30. data/node_modules/choices.js/public/assets/scripts/choices.search-prefix.mjs +3582 -0
  31. data/node_modules/choices.js/public/assets/styles/base.css +180 -0
  32. data/node_modules/choices.js/public/assets/styles/base.css.map +1 -0
  33. data/node_modules/choices.js/public/assets/styles/base.min.css +1 -0
  34. data/node_modules/choices.js/public/assets/styles/choices.css +338 -0
  35. data/node_modules/choices.js/public/assets/styles/choices.css.map +1 -0
  36. data/node_modules/choices.js/public/assets/styles/choices.min.css +1 -0
  37. data/node_modules/choices.js/public/types/src/index.d.ts +6 -0
  38. data/node_modules/choices.js/public/types/src/scripts/actions/choices.d.ts +30 -0
  39. data/node_modules/choices.js/public/types/src/scripts/actions/groups.d.ts +8 -0
  40. data/node_modules/choices.js/public/types/src/scripts/actions/items.d.ts +17 -0
  41. data/node_modules/choices.js/public/types/src/scripts/choices.d.ts +210 -0
  42. data/node_modules/choices.js/public/types/src/scripts/components/container.d.ts +36 -0
  43. data/node_modules/choices.js/public/types/src/scripts/components/dropdown.d.ts +21 -0
  44. data/node_modules/choices.js/public/types/src/scripts/components/index.d.ts +7 -0
  45. data/node_modules/choices.js/public/types/src/scripts/components/input.d.ts +37 -0
  46. data/node_modules/choices.js/public/types/src/scripts/components/list.d.ts +14 -0
  47. data/node_modules/choices.js/public/types/src/scripts/components/wrapped-element.d.ts +21 -0
  48. data/node_modules/choices.js/public/types/src/scripts/components/wrapped-input.d.ts +3 -0
  49. data/node_modules/choices.js/public/types/src/scripts/components/wrapped-select.d.ts +20 -0
  50. data/node_modules/choices.js/public/types/src/scripts/constants.d.ts +1 -0
  51. data/node_modules/choices.js/public/types/src/scripts/defaults.d.ts +4 -0
  52. data/node_modules/choices.js/public/types/src/scripts/interfaces/action-type.d.ts +13 -0
  53. data/node_modules/choices.js/public/types/src/scripts/interfaces/build-flags.d.ts +11 -0
  54. data/node_modules/choices.js/public/types/src/scripts/interfaces/choice-full.d.ts +23 -0
  55. data/node_modules/choices.js/public/types/src/scripts/interfaces/class-names.d.ts +61 -0
  56. data/node_modules/choices.js/public/types/src/scripts/interfaces/event-choice.d.ts +7 -0
  57. data/node_modules/choices.js/public/types/src/scripts/interfaces/event-type.d.ts +14 -0
  58. data/node_modules/choices.js/public/types/src/scripts/interfaces/group-full.d.ts +10 -0
  59. data/node_modules/choices.js/public/types/src/scripts/interfaces/index.d.ts +14 -0
  60. data/node_modules/choices.js/public/types/src/scripts/interfaces/input-choice.d.ts +15 -0
  61. data/node_modules/choices.js/public/types/src/scripts/interfaces/input-group.d.ts +10 -0
  62. data/node_modules/choices.js/public/types/src/scripts/interfaces/item.d.ts +17 -0
  63. data/node_modules/choices.js/public/types/src/scripts/interfaces/keycode-map.d.ts +13 -0
  64. data/node_modules/choices.js/public/types/src/scripts/interfaces/options.d.ts +566 -0
  65. data/node_modules/choices.js/public/types/src/scripts/interfaces/passed-element-type.d.ts +7 -0
  66. data/node_modules/choices.js/public/types/src/scripts/interfaces/passed-element.d.ts +95 -0
  67. data/node_modules/choices.js/public/types/src/scripts/interfaces/position-options-type.d.ts +1 -0
  68. data/node_modules/choices.js/public/types/src/scripts/interfaces/search.d.ts +11 -0
  69. data/node_modules/choices.js/public/types/src/scripts/interfaces/state.d.ts +10 -0
  70. data/node_modules/choices.js/public/types/src/scripts/interfaces/store.d.ts +64 -0
  71. data/node_modules/choices.js/public/types/src/scripts/interfaces/string-pre-escaped.d.ts +3 -0
  72. data/node_modules/choices.js/public/types/src/scripts/interfaces/string-untrusted.d.ts +4 -0
  73. data/node_modules/choices.js/public/types/src/scripts/interfaces/templates.d.ts +29 -0
  74. data/node_modules/choices.js/public/types/src/scripts/interfaces/types.d.ts +18 -0
  75. data/node_modules/choices.js/public/types/src/scripts/lib/choice-input.d.ts +9 -0
  76. data/node_modules/choices.js/public/types/src/scripts/lib/html-guard-statements.d.ts +4 -0
  77. data/node_modules/choices.js/public/types/src/scripts/lib/utils.d.ts +31 -0
  78. data/node_modules/choices.js/public/types/src/scripts/reducers/choices.d.ts +8 -0
  79. data/node_modules/choices.js/public/types/src/scripts/reducers/groups.d.ts +8 -0
  80. data/node_modules/choices.js/public/types/src/scripts/reducers/items.d.ts +9 -0
  81. data/node_modules/choices.js/public/types/src/scripts/search/fuse.d.ts +14 -0
  82. data/node_modules/choices.js/public/types/src/scripts/search/index.d.ts +3 -0
  83. data/node_modules/choices.js/public/types/src/scripts/search/kmp.d.ts +11 -0
  84. data/node_modules/choices.js/public/types/src/scripts/search/prefix-filter.d.ts +11 -0
  85. data/node_modules/choices.js/public/types/src/scripts/store/store.d.ts +59 -0
  86. data/node_modules/choices.js/public/types/src/scripts/templates.d.ts +8 -0
  87. data/node_modules/choices.js/src/entry.js +3 -0
  88. data/node_modules/choices.js/src/icons/cross-inverse.svg +1 -0
  89. data/node_modules/choices.js/src/icons/cross.svg +1 -0
  90. data/node_modules/choices.js/src/index.ts +8 -0
  91. data/node_modules/choices.js/src/scripts/actions/choices.ts +59 -0
  92. data/node_modules/choices.js/src/scripts/actions/groups.ts +14 -0
  93. data/node_modules/choices.js/src/scripts/actions/items.ts +34 -0
  94. data/node_modules/choices.js/src/scripts/choices.ts +2364 -0
  95. data/node_modules/choices.js/src/scripts/components/container.ts +157 -0
  96. data/node_modules/choices.js/src/scripts/components/dropdown.ts +50 -0
  97. data/node_modules/choices.js/src/scripts/components/index.ts +8 -0
  98. data/node_modules/choices.js/src/scripts/components/input.ts +146 -0
  99. data/node_modules/choices.js/src/scripts/components/list.ts +89 -0
  100. data/node_modules/choices.js/src/scripts/components/wrapped-element.ts +89 -0
  101. data/node_modules/choices.js/src/scripts/components/wrapped-input.ts +3 -0
  102. data/node_modules/choices.js/src/scripts/components/wrapped-select.ts +115 -0
  103. data/node_modules/choices.js/src/scripts/constants.ts +1 -0
  104. data/node_modules/choices.js/src/scripts/defaults.ts +93 -0
  105. data/node_modules/choices.js/src/scripts/interfaces/action-type.ts +15 -0
  106. data/node_modules/choices.js/src/scripts/interfaces/build-flags.ts +17 -0
  107. data/node_modules/choices.js/src/scripts/interfaces/choice-full.ts +30 -0
  108. data/node_modules/choices.js/src/scripts/interfaces/class-names.ts +61 -0
  109. data/node_modules/choices.js/src/scripts/interfaces/event-choice.ts +9 -0
  110. data/node_modules/choices.js/src/scripts/interfaces/event-type.ts +16 -0
  111. data/node_modules/choices.js/src/scripts/interfaces/group-full.ts +12 -0
  112. data/node_modules/choices.js/src/scripts/interfaces/index.ts +14 -0
  113. data/node_modules/choices.js/src/scripts/interfaces/input-choice.ts +17 -0
  114. data/node_modules/choices.js/src/scripts/interfaces/input-group.ts +11 -0
  115. data/node_modules/choices.js/src/scripts/interfaces/item.ts +17 -0
  116. data/node_modules/choices.js/src/scripts/interfaces/keycode-map.ts +13 -0
  117. data/node_modules/choices.js/src/scripts/interfaces/options.ts +619 -0
  118. data/node_modules/choices.js/src/scripts/interfaces/passed-element-type.ts +9 -0
  119. data/node_modules/choices.js/src/scripts/interfaces/passed-element.ts +96 -0
  120. data/node_modules/choices.js/src/scripts/interfaces/position-options-type.ts +1 -0
  121. data/node_modules/choices.js/src/scripts/interfaces/search.ts +12 -0
  122. data/node_modules/choices.js/src/scripts/interfaces/state.ts +12 -0
  123. data/node_modules/choices.js/src/scripts/interfaces/store.ts +84 -0
  124. data/node_modules/choices.js/src/scripts/interfaces/string-pre-escaped.ts +3 -0
  125. data/node_modules/choices.js/src/scripts/interfaces/string-untrusted.ts +5 -0
  126. data/node_modules/choices.js/src/scripts/interfaces/templates.ts +66 -0
  127. data/node_modules/choices.js/src/scripts/interfaces/types.ts +21 -0
  128. data/node_modules/choices.js/src/scripts/lib/choice-input.ts +88 -0
  129. data/node_modules/choices.js/src/scripts/lib/html-guard-statements.ts +7 -0
  130. data/node_modules/choices.js/src/scripts/lib/utils.ts +230 -0
  131. data/node_modules/choices.js/src/scripts/reducers/choices.ts +86 -0
  132. data/node_modules/choices.js/src/scripts/reducers/groups.ts +32 -0
  133. data/node_modules/choices.js/src/scripts/reducers/items.ts +86 -0
  134. data/node_modules/choices.js/src/scripts/search/fuse.ts +59 -0
  135. data/node_modules/choices.js/src/scripts/search/index.ts +17 -0
  136. data/node_modules/choices.js/src/scripts/search/kmp.ts +87 -0
  137. data/node_modules/choices.js/src/scripts/search/prefix-filter.ts +42 -0
  138. data/node_modules/choices.js/src/scripts/store/store.ts +184 -0
  139. data/node_modules/choices.js/src/scripts/templates.ts +409 -0
  140. data/node_modules/choices.js/src/styles/base.scss +189 -0
  141. data/node_modules/choices.js/src/styles/choices.scss +414 -0
  142. data/node_modules/choices.js/src/tsconfig.json +22 -0
  143. metadata +134 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2fbc187d41fb43b4005ecda265842cd3c9dde603272971d6429d674a4fe99d3
4
- data.tar.gz: 17a7f6abba141f6fbb5e12520c8195fedebae2ae55efbe721857431aadefa76c
3
+ metadata.gz: 8766d04b5c4c4d7fab4d972b014cc0ac72f541321146789b75c64414b60aeb08
4
+ data.tar.gz: d6915e235caf1ea065a3f6f001ef34d797f76187cd58f199ba8ff20e242fb062
5
5
  SHA512:
6
- metadata.gz: d296de9af1c9e495442d31f08bdf5107e657abb510d09e1362d526d741e4f47521cceb2707195d5bca801cfc56ba17d5f8c612da99cb1b0519dd4da457ee5f6e
7
- data.tar.gz: 53ed8e3164202b9d51541016ec88d460559856d78814a01ddea790b5f93858ec485ef02561ad6f08349527121e21e942a60790fd6e72bff42ac73755a8150e50
6
+ metadata.gz: 1aaa22f84475dd4bd8b5bd26d6af93a3e456c999ff9eeffd4631dac8d87b50e7dfed42db8b845cff27408812d7314d9acfb8440109887e137b9208b13ccce8c2
7
+ data.tar.gz: bb8b3b01adf12c73c771b6366abf277fb263d33149a853188785e308389b9d6c8202c6fcf3301c037a3ac602b65861a4bcdcb210646736dd9dfeb9e3109424a0
@@ -0,0 +1,6 @@
1
+ <svg width='21' height='21' viewBox='0 0 21 21' xmlns='http://www.w3.org/2000/svg'>
2
+ <g fill='#000' fill-rule='evenodd'>
3
+ <path d='M2.592.044l18.364 18.364-2.548 2.548L.044 2.592z' />
4
+ <path d='M0 18.364L18.364 0l2.548 2.548L2.548 20.912z' />
5
+ </g>
6
+ </svg>
@@ -9,6 +9,7 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
9
9
  this.$module = $module
10
10
  this.$searchInput = this.$module.querySelector('input[type="search"]')
11
11
 
12
+ this.searchInputName = this.$module.dataset.ga4SearchInputName
12
13
  this.type = this.$module.dataset.ga4SearchType
13
14
  this.url = this.$module.dataset.ga4SearchUrl
14
15
  this.section = this.$module.dataset.ga4SearchSection
@@ -44,6 +45,9 @@ window.GOVUK.Modules = window.GOVUK.Modules || {};
44
45
 
45
46
  setTriggeredAction (event) {
46
47
  if (event.target.type === 'search') {
48
+ // if `searchInputName` set then only set `triggeredAction` to `search`
49
+ // if changed `input[type=search]` matches `input[name=searchInputName]`
50
+ if (this.searchInputName && event.target.name !== this.searchInputName) return
47
51
  this.triggeredAction = 'search'
48
52
  } else if (this.triggeredAction !== 'search') {
49
53
  // The 'search' action always takes precedence over the 'filter' action, so only set the
@@ -0,0 +1,57 @@
1
+ //= require choices.js/public/assets/scripts/choices.min.js
2
+ 'use strict'
3
+ window.GOVUK = window.GOVUK || {}
4
+ window.GOVUK.Modules = window.GOVUK.Modules || {}
5
+ ;(function (Modules) {
6
+ function SelectWithSearch (module) {
7
+ this.module = module
8
+ }
9
+
10
+ SelectWithSearch.prototype.init = function () {
11
+ if (!this.module.matches('select')) {
12
+ console.error('Module is not a select element')
13
+ return
14
+ }
15
+
16
+ const placeholderOption = this.module.querySelector(
17
+ 'option[value=""]:first-child'
18
+ )
19
+
20
+ if (placeholderOption && placeholderOption.textContent === '') {
21
+ placeholderOption.textContent = this.module.multiple
22
+ ? 'Select all that apply'
23
+ : 'Select one'
24
+ }
25
+
26
+ const ariaDescribedBy = this.module.getAttribute('aria-describedby') || ''
27
+
28
+ this.choices = new window.Choices(this.module, {
29
+ allowHTML: true,
30
+ searchPlaceholderValue: 'Search in list',
31
+ shouldSort: false, // show options and groups in the order they were given
32
+ itemSelectText: '',
33
+ searchResultLimit: 100,
34
+ removeItemButton: this.module.multiple,
35
+ labelId: this.module.id + '-label ' + ariaDescribedBy,
36
+ callbackOnInit: function () {
37
+ // For the multiple select, move the input field to
38
+ // the top of the feedback area, so that the selected
39
+ // 'lozenges' appear afterwards in a more natural flow
40
+ if (this.dropdown.type === 'select-multiple') {
41
+ const inner = this.containerInner.element
42
+ const input = this.input.element
43
+ inner.prepend(input)
44
+ }
45
+ },
46
+ // https://fusejs.io/api/options.html
47
+ fuseOptions: {
48
+ ignoreLocation: true, // matches any part of the string
49
+ threshold: 0 // only matches when characters are sequential
50
+ }
51
+ })
52
+
53
+ this.module.choices = this.choices
54
+ }
55
+
56
+ Modules.SelectWithSearch = SelectWithSearch
57
+ })(window.GOVUK.Modules)
@@ -75,6 +75,7 @@
75
75
  @import "components/search";
76
76
  @import "components/secondary-navigation";
77
77
  @import "components/select";
78
+ @import "components/select-with-search";
78
79
  @import "components/service-navigation";
79
80
  @import "components/share-links";
80
81
  @import "components/signup-link";
@@ -0,0 +1,168 @@
1
+ // overload the choices.js variables
2
+
3
+ $font-size: 19px;
4
+
5
+ $choices-bg-color: govuk-colour("white") !default;
6
+ $choices-font-size-lg: $font-size !default;
7
+ $choices-font-size-md: $font-size !default;
8
+ $choices-font-size-sm: $font-size !default;
9
+ $choices-primary-color: #ffffff !default; // can't use mixin here because of Choices.js Sass functions
10
+ $choices-text-color: govuk-colour("black");
11
+ $choices-icon-cross: url("select-with-search/cross-icon.svg");
12
+ $choices-border-radius: 0 !default;
13
+ $choices-border-radius-item: 0 !default;
14
+ $choices-z-index: 2 !default;
15
+ $choices-button-dimension: 12px !default;
16
+
17
+ @import "govuk_publishing_components/individual_component_support";
18
+ @import "mixins/prefixed-transform";
19
+ @import "govuk/components/label/label";
20
+ @import "choices.js/src/styles/choices";
21
+
22
+ .gem-c-select-with-search {
23
+ .choices * {
24
+ // Something inside .choices needs this – I'm not sure what yet
25
+ box-sizing: border-box;
26
+ font-family: $govuk-font-family;
27
+ }
28
+
29
+ .choices[data-type*="select-one"]::after {
30
+ @include govuk-shape-arrow($direction: down, $base: 10px, $display: inline-block);
31
+ @include prefixed-transform($translateY: -50%);
32
+ margin: 0;
33
+ }
34
+
35
+ .choices.is-open[data-type*="select-one"]::after {
36
+ margin: 0;
37
+ bottom: govuk-em(1px, $font-size);
38
+ @include prefixed-transform($translateY: -50%, $rotate: 180deg);
39
+ }
40
+
41
+ .choices[data-type*="select-multiple"] .choices__button,
42
+ .choices[data-type*="text"] .choices__button {
43
+ border-color: govuk-colour("mid-grey");
44
+ border-right: 1px solid govuk-colour("mid-grey");
45
+ padding: govuk-spacing(2) govuk-spacing(4) govuk-spacing(2) govuk-spacing(2);
46
+ margin-right: 0;
47
+
48
+ &:hover {
49
+ background-color: govuk-colour("mid-grey");
50
+ border-color: govuk-colour("dark-grey");
51
+ box-shadow: 0 $govuk-border-width-form-element 0 govuk-colour("dark-grey");
52
+ }
53
+
54
+ &:focus {
55
+ background-color: $govuk-focus-colour;
56
+ box-shadow: 0 $govuk-border-width-form-element 0 $govuk-focus-text-colour;
57
+ }
58
+ }
59
+
60
+ .choices.is-disabled {
61
+ .choices__item[data-deletable] {
62
+ background-color: govuk-colour("white");
63
+ }
64
+
65
+ .choices__button {
66
+ display: none;
67
+ }
68
+ }
69
+
70
+ .choices__input {
71
+ display: block;
72
+ margin-bottom: 0;
73
+ }
74
+
75
+ .choices__inner {
76
+ padding: govuk-spacing(1);
77
+ border: $govuk-border-width-form-element solid govuk-colour("black");
78
+ }
79
+
80
+ &.govuk-form-group--error .choices:not(.is-active):not(.is-focused):not(.is-open) .choices__inner {
81
+ border-color: $govuk-error-colour;
82
+ }
83
+
84
+ .choices.is-focused,
85
+ .choices.is-open {
86
+ overflow: visible;
87
+ }
88
+
89
+ .choices.is-flipped .choices__list {
90
+ border-radius: 0; // this isn't set by a variable unlike all other border radius :(
91
+ border-width: $govuk-border-width-form-element;
92
+ }
93
+
94
+ .choices.is-focused .choices__inner,
95
+ .choices.is-open .choices__inner {
96
+ outline: $govuk-focus-width solid $govuk-focus-colour;
97
+ // Ensure outline appears outside of the element
98
+ outline-offset: 0;
99
+ // Double the border by adding its width again. Use `box-shadow` to do
100
+ // this instead of changing `border-width` (which changes element size)
101
+ // and since `outline` is already used for the yellow focus state.
102
+ box-shadow: inset 0 0 0 $govuk-border-width-form-element;
103
+ }
104
+
105
+ .choices__list--multiple {
106
+ display: block;
107
+
108
+ &:not(:empty) {
109
+ margin-block-start: 6px;
110
+ border-block-start: 1px solid $govuk-border-colour;
111
+ padding-block-end: 5px;
112
+ }
113
+ }
114
+
115
+ .choices__list--multiple .choices__item {
116
+ display: inline-flex;
117
+ align-items: center;
118
+ border: 0;
119
+ padding: 0 0 0 govuk-spacing(2);
120
+ margin: govuk-spacing(2) govuk-spacing(2) 0 0;
121
+ background-color: govuk-colour("light-grey");
122
+ box-shadow: 0 $govuk-border-width-form-element 0 govuk-colour("mid-grey");
123
+ line-height: 1;
124
+ color: $govuk-text-colour;
125
+
126
+ .is-disabled & {
127
+ opacity: 0.5;
128
+ }
129
+ }
130
+
131
+ // Dropdown
132
+ .choices__list--dropdown,
133
+ .choices__list[aria-expanded] {
134
+ border: $govuk-border-width-form-element solid govuk-colour("black");
135
+ border-top-width: 0;
136
+
137
+ .is-flipped & {
138
+ border-top-width: $govuk-border-width-form-element;
139
+ border-bottom-width: 0;
140
+ }
141
+ }
142
+
143
+ .choices__list--dropdown .choices__item,
144
+ .choices__list[aria-expanded] .choices__item {
145
+ position: relative;
146
+ border-bottom: 1px solid govuk-colour("mid-grey");
147
+
148
+ &:last-child {
149
+ border-bottom: 0;
150
+ }
151
+ }
152
+
153
+ .choices__list--dropdown .choices__item--selectable.is-highlighted,
154
+ .choices__list[aria-expanded] .choices__item--selectable.is-highlighted {
155
+ background-color: govuk-colour("blue");
156
+ border-color: govuk-colour("blue");
157
+ color: govuk-colour("white");
158
+ outline: none;
159
+ }
160
+
161
+ .choices__heading {
162
+ @include govuk-typography-weight-bold;
163
+ color: govuk-colour("black"); // Choices.js doesn't use a variable for this color for some reason :(
164
+ padding: govuk-spacing(6) govuk-spacing(2) govuk-spacing(2);
165
+ border-bottom: 1px solid govuk-colour("mid-grey");
166
+ cursor: default;
167
+ }
168
+ }
@@ -1,6 +1,12 @@
1
1
  @import "govuk_publishing_components/individual_component_support";
2
2
  @import "govuk/components/select/select";
3
3
 
4
+ // Increase height of selects that have a `multiple`
5
+ // attribute otherwise they are too small to be useful.
6
+ .govuk-select[multiple] {
7
+ height: auto;
8
+ }
9
+
4
10
  .gem-c-select__select--full-width {
5
11
  min-width: 100%;
6
12
  max-width: 100%;
@@ -8,42 +8,41 @@
8
8
  is_page_heading ||= false
9
9
  data_attributes ||= {}
10
10
  aria_controls ||= nil
11
+ heading_size ||= false
12
+ multiple ||= false
13
+ include_blank ||= false
11
14
 
12
- shared_helper = GovukPublishingComponents::Presenters::SharedHelper.new(local_assigns)
13
- heading_size = false unless shared_helper.valid_heading_size?(heading_size)
14
- select_helper = GovukPublishingComponents::Presenters::SelectHelper.new(local_assigns)
15
+ select_helper ||= GovukPublishingComponents::Presenters::SelectHelper.new(local_assigns)
15
16
 
16
17
  aria_attributes = {
17
18
  controls: aria_controls,
18
19
  describedby: select_helper.describedby
19
20
  }
20
21
  %>
21
- <% if select_helper.options.any? && id && label %>
22
+ <% if select_helper.options_markup.present? && id && label %>
22
23
  <%= content_tag :div, class: select_helper.css_classes do %>
23
- <% if is_page_heading %>
24
- <% add_gem_component_stylesheet("heading") %>
25
- <%= render "govuk_publishing_components/components/heading", {
26
- text: label_tag(id, label, class: select_helper.label_classes),
27
- heading_level: 1
28
- } %>
29
- <% else %>
30
- <%= label_tag(id, label, class: select_helper.label_classes) %>
31
- <% end %>
32
-
33
- <% if select_helper.hint %>
34
- <%= render "govuk_publishing_components/components/hint", {
35
- id: select_helper.hint_id,
36
- text: hint
37
- } %>
38
- <% end %>
24
+ <%= render "govuk_publishing_components/components/label", {
25
+ id: "#{id}-label",
26
+ html_for: id,
27
+ text: label,
28
+ heading_size:,
29
+ is_page_heading:,
30
+ hint_text: select_helper.hint,
31
+ hint_id: select_helper.hint_id,
32
+ } %>
39
33
 
40
- <% if select_helper.error_message %>
34
+ <% if select_helper.error_items.any? %>
41
35
  <%= render "govuk_publishing_components/components/error_message", {
42
36
  id: select_helper.error_id,
43
- text: select_helper.error_message
37
+ items: select_helper.error_items,
44
38
  } %>
45
39
  <% end %>
46
40
 
47
- <%= select_tag name, options_for_select(select_helper.option_markup, select_helper.selected_option), id: id, class: select_helper.select_classes, aria: aria_attributes, data: data_attributes %>
41
+ <%# Create null input so that the value is cleared if no options are selected %>
42
+ <%# https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-select-label-Gotcha %>
43
+ <% if multiple %>
44
+ <%= hidden_field_tag name, nil %>
45
+ <% end %>
46
+ <%= select_tag name, select_helper.options_markup, id: id, class: select_helper.select_classes, aria: aria_attributes, data: data_attributes, multiple: %>
48
47
  <% end %>
49
48
  <% end %>
@@ -0,0 +1,14 @@
1
+ <%
2
+ add_gem_component_stylesheet("select-with-search")
3
+
4
+ # select_helper.select_classes generates "gem-c-select-with-search"
5
+ select_helper = GovukPublishingComponents::Presenters::SelectWithSearchHelper.new(local_assigns)
6
+
7
+ component_helper = GovukPublishingComponents::Presenters::ComponentWrapperHelper.new(local_assigns)
8
+ component_helper.add_data_attribute({ module: "select-with-search" })
9
+ %>
10
+ <%= render "govuk_publishing_components/components/select", {
11
+ **local_assigns,
12
+ **component_helper.all_attributes,
13
+ select_helper:
14
+ }.with_indifferent_access %>
@@ -174,3 +174,14 @@ examples:
174
174
  value: option1
175
175
  - text: Option two
176
176
  value: option2
177
+ with_multiple:
178
+ data:
179
+ id: dropdown-multiple-id
180
+ id: dropdown-multiple
181
+ label: My Dropdown
182
+ multiple: true
183
+ options:
184
+ - text: Option one
185
+ value: option1
186
+ - text: Option two
187
+ value: option2
@@ -0,0 +1,196 @@
1
+ name: Select with search (experimental)
2
+ description: A dropdown select with search
3
+ body: |
4
+ A Javascript enhanced dropdown select. This component is progressively enhanced, if
5
+ Javascript is unavailable then it will use the select component.
6
+
7
+ This is an experimental component as it currently fails WCAG compliance for keyboard navigation. The total number of options are not
8
+ announced when using Voice Over.
9
+ accessibility_criteria: |
10
+ - accept focus
11
+ - be focusable with a keyboard
12
+ - be usable with a keyboard
13
+ - indicate when it has focus
14
+ examples:
15
+ default:
16
+ data:
17
+ id: dropdown-default
18
+ label: My Dropdown
19
+ options:
20
+ - text: Option one
21
+ value: option1
22
+ - text: Option two
23
+ value: option2
24
+ - text: Option three
25
+ value: option3
26
+ with_blank_option:
27
+ description: Include a blank option
28
+ data:
29
+ id: dropdown-with-blank
30
+ label: With blank option
31
+ include_blank: true
32
+ options:
33
+ - text: Option one
34
+ value: option1
35
+ - text: Option two
36
+ value: option2
37
+ - text: Option three
38
+ value: option3
39
+ with_grouped_options:
40
+ description: Options can be grouped
41
+ data:
42
+ id: dropdown-with-grouped-options
43
+ label: Select a city
44
+ grouped_options:
45
+ - - England
46
+ - - text: Bath
47
+ value: bath
48
+ - text: Bristol
49
+ value: bristol
50
+ - text: London
51
+ value: london
52
+ - text: Manchester
53
+ value: manchester
54
+ - - Northern Ireland
55
+ - - text: Bangor
56
+ value: bangor
57
+ - text: Belfast
58
+ value: belfast
59
+ - - Scotland
60
+ - - text: Dundee
61
+ value: dundee
62
+ - text: Edinburgh
63
+ value: edinburgh
64
+ - text: Glasgow
65
+ value: glasgow
66
+ - - Wales
67
+ - - text: Cardiff
68
+ value: cardiff
69
+ - text: Swansea
70
+ value: swansea
71
+ with_grouped_options_and_blank_option:
72
+ description: Options can be grouped and include a blank option
73
+ data:
74
+ id: dropdown-with-grouped-options-and-blank
75
+ label: Select a city
76
+ include_blank: true
77
+ grouped_options:
78
+ - - England
79
+ - - text: Bath
80
+ value: bath
81
+ - text: Bristol
82
+ value: bristol
83
+ - text: London
84
+ value: london
85
+ - text: Manchester
86
+ value: manchester
87
+ - - Northern Ireland
88
+ - - text: Bangor
89
+ value: bangor
90
+ - text: Belfast
91
+ value: belfast
92
+ - - Scotland
93
+ - - text: Dundee
94
+ value: dundee
95
+ - text: Edinburgh
96
+ value: edinburgh
97
+ - text: Glasgow
98
+ value: glasgow
99
+ - - Wales
100
+ - - text: Cardiff
101
+ value: cardiff
102
+ - text: Swansea
103
+ value: swansea
104
+ with_different_id_and_name:
105
+ description: If no name is provided, name defaults to the (required) value of id.
106
+ data:
107
+ id: dropdown-with-different-id-and-name
108
+ label: My Dropdown
109
+ name: dropdown[1]
110
+ options:
111
+ - text: Option one
112
+ value: option1
113
+ - text: Option two
114
+ value: option2
115
+ with_data_attributes:
116
+ data:
117
+ id: dropdown-with-data-attributes
118
+ data_attributes:
119
+ module: not-a-module
120
+ loose: moose
121
+ label: Select your country
122
+ options:
123
+ - text: France
124
+ value: fr
125
+ - text: Germany
126
+ value: de
127
+ - text: United Kingdom
128
+ value: uk
129
+ with_preselect:
130
+ data:
131
+ id: dropdown-with-preselect
132
+ label: Option 2 preselected
133
+ options:
134
+ - text: Option one
135
+ value: option1
136
+ - text: Option two
137
+ value: option2
138
+ selected: true
139
+ - text: Option three
140
+ value: option3
141
+ with_hint:
142
+ description: When a hint is included the `aria-describedby` attribute of the select is included to point to the hint. When an error and a hint are present, that attribute includes the IDs of both the hint and the error.
143
+ data:
144
+ id: dropdown-with-hint
145
+ label: Choose your preferred thing
146
+ hint: You might need some more information here
147
+ hint_id: optional-hint-id
148
+ options:
149
+ - text: Something
150
+ value: option1
151
+ - text: Something else
152
+ value: option2
153
+ with_error:
154
+ description: An arbitrary number of separate error items can be passed to the component.
155
+ data:
156
+ id: dropdown-with-error
157
+ label: How will you be travelling to the conference?
158
+ error_items:
159
+ - text: Please choose an option
160
+ include_blank: true
161
+ options:
162
+ - text: Public transport
163
+ value: option1
164
+ - text: Will make own arrangements
165
+ value: option2
166
+ with_custom_label_size:
167
+ description: Make the label different sizes. Valid options are `s`, `m`, `l` and `xl`.
168
+ data:
169
+ id: dropdown-with-custom-label-size
170
+ label: Bigger!
171
+ heading_size: xl
172
+ options:
173
+ - text: Option one
174
+ value: option1
175
+ - text: Option two
176
+ value: option2
177
+ - text: Option three
178
+ value: option3
179
+ with_multiple_select_enabled:
180
+ description: Allow multiple items to be selected and de-selected.
181
+ data:
182
+ id: dropdown-with-multiple
183
+ label: Select your country
184
+ include_blank: true
185
+ multiple: true
186
+ options:
187
+ - text: France
188
+ value: fr
189
+ selected: false
190
+ - text: Germany
191
+ value: de
192
+ selected: false
193
+ - text: The United Kingdom of Great Britain and Northern Ireland
194
+ value: uk
195
+ - text: Democratic Republic of the Congo
196
+ value: cg
@@ -1,23 +1,26 @@
1
1
  module GovukPublishingComponents
2
2
  module Presenters
3
3
  class SelectHelper
4
- attr_reader :options, :option_markup, :selected_option, :error_message, :error_id, :hint, :hint_id, :describedby
4
+ include ActionView::Helpers::FormOptionsHelper
5
+
6
+ attr_reader :options, :options_markup, :error_items, :error_id, :hint, :hint_id, :describedby
5
7
 
6
8
  def initialize(local_assigns)
7
9
  @options = local_assigns[:options] || []
8
- @error_message = local_assigns[:error_message]
10
+ @error_items = local_assigns[:error_items] || []
11
+ @error_items << { text: local_assigns[:error_message] } if local_assigns[:error_message]
9
12
  @error_id = local_assigns[:error_id] || nil
10
13
  @hint = local_assigns[:hint] || nil
11
14
  @hint_id = local_assigns[:hint_id] || nil
12
15
  @heading_size = local_assigns[:heading_size]
13
16
  @full_width = local_assigns[:full_width] || false
14
- @option_markup = get_options
17
+ @options_markup = options_for_select(get_options, @selected_option)
15
18
  @describedby = get_describedby
16
19
  end
17
20
 
18
21
  def css_classes
19
22
  classes = %w[govuk-form-group gem-c-select]
20
- classes << "govuk-form-group--error" if @error_message
23
+ classes << "govuk-form-group--error" if @error_items.present?
21
24
  classes
22
25
  end
23
26
 
@@ -66,7 +69,7 @@ module GovukPublishingComponents
66
69
  def get_describedby
67
70
  describedby = %w[]
68
71
 
69
- if @error_message || @error_id
72
+ if @error_items.present? || @error_id
70
73
  @error_id ||= "error-#{SecureRandom.hex(4)}"
71
74
  describedby << @error_id
72
75
  end