From 1f4488e3b3b2a9ef4bd49cd1e6480e5c8b685b0d Mon Sep 17 00:00:00 2001 From: Theodor Vararu Date: Fri, 27 Sep 2024 12:16:33 +0300 Subject: [PATCH] Add autocomplete stimulus controller This is based on the implementation in the prototype and customises the accessible-autocomplete with some of the available options. --- app/assets/stylesheets/_autocomplete.scss | 44 ++++++++++++++ app/assets/stylesheets/application.scss | 1 + .../controllers/autocomplete_controller.js | 60 +++++++++++++++++++ app/javascript/controllers/index.js | 3 + app/views/layouts/application.html.erb | 1 + .../consent_forms/edit/school.html.erb | 22 +++---- config/initializers/assets.rb | 4 ++ package.json | 1 + yarn.lock | 5 ++ 9 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 app/assets/stylesheets/_autocomplete.scss create mode 100644 app/javascript/controllers/autocomplete_controller.js diff --git a/app/assets/stylesheets/_autocomplete.scss b/app/assets/stylesheets/_autocomplete.scss new file mode 100644 index 000000000..585ba109c --- /dev/null +++ b/app/assets/stylesheets/_autocomplete.scss @@ -0,0 +1,44 @@ +// Ensure the autocomplete uses the correct typeface +.autocomplete__wrapper { + font-family: $govuk-font-family; +} + +.autocomplete__input { + font-family: inherit; +} + +// Style the autocomplete if there’s an error +.nhsuk-form-group--error { + .autocomplete__input { + border-color: $govuk-error-colour; + } + + .autocomplete__input--focused { + border-color: $govuk-input-border-colour; + } +} + +.autocomplete__dropdown-arrow-down { + // Ensure dropdown arrow can be clicked + // https://github.com/alphagov/accessible-autocomplete/issues/202 + pointer-events: none; + // Ensure dropdown arrow can be seen + z-index: 0; +} + +.autocomplete__input { + background-color: $color_nhsuk-white; +} + +.autocomplete__option { + margin-bottom: 0; +} + +.autocomplete__option-hint { + color: $nhsuk-secondary-text-color; + + .autocomplete__option:hover &, + .autocomplete__option:focus & { + color: $color_nhsuk-grey-5; + } +} diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 8e13efa63..579107a62 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -33,6 +33,7 @@ $color_app-dark-orange: darken( // Application components @import "action_list"; +@import "autocomplete"; @import "button"; @import "button-group"; @import "card"; diff --git a/app/javascript/controllers/autocomplete_controller.js b/app/javascript/controllers/autocomplete_controller.js new file mode 100644 index 000000000..5ddf32d70 --- /dev/null +++ b/app/javascript/controllers/autocomplete_controller.js @@ -0,0 +1,60 @@ +import { Controller } from "@hotwired/stimulus"; +import accessibleAutocomplete from "accessible-autocomplete"; + +const enhanceOption = (option) => { + return { + name: option.label, + append: option.getAttribute("data-append"), + hint: option.getAttribute("data-hint"), + }; +}; + +const suggestion = (value, options) => { + const option = options.find(({ name }) => name === value); + if (option) { + const html = option.append ? `${value} – ${option.append}` : value; + return option.hint + ? `${html}
${option.hint}` + : html; + } else { + return "No results found"; + } +}; + +const autocomplete = ($module) => { + if (!$module) { + return; + } + + const params = $module.dataset; + + const selectOptions = Array.from($module.options); + const options = selectOptions.map((option) => enhanceOption(option)); + + accessibleAutocomplete.enhanceSelectElement({ + autoselect: params.autoselect === "true", + defaultValue: params.defaultValue || "", + displayMenu: params.displayMenu, + minLength: params.minLength ? parseInt(params.minLength) : 0, + selectElement: $module, + showAllValues: params.showAllValues === "true", + showNoOptionsFound: params.showNoOptionsFound === "true", + templates: { + suggestion: (value) => suggestion(value, options), + }, + onConfirm: (value) => { + const selectedOption = [].filter.call( + selectOptions, + (option) => (option.textContent || option.innerText) === value, + )[0]; + if (selectedOption) selectedOption.selected = true; + }, + }); +}; + +// Connects to data-module="autocomplete" +export default class extends Controller { + connect() { + autocomplete(this.element); + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index b43fa97c8..b3b849730 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -4,6 +4,9 @@ import { application } from "./application"; +import AutocompleteController from "./autocomplete_controller"; +application.register("autocomplete", AutocompleteController); + import AutosubmitController from "./autosubmit_controller"; application.register("autosubmit", AutosubmitController); diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 0b283b93a..16818ec74 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -6,6 +6,7 @@ <%= page_title(@service_name) %> + <%= stylesheet_link_tag "accessible-autocomplete/dist/accessible-autocomplete.min", "data-turbo-track": Rails.env.development? ? "" : "reload" %> <%= stylesheet_link_tag "application", "data-turbo-track": Rails.env.development? ? "" : "reload" %> <%= javascript_include_tag "application", "data-turbo-track": Rails.env.development? ? "" : "reload", defer: true %> diff --git a/app/views/parent_interface/consent_forms/edit/school.html.erb b/app/views/parent_interface/consent_forms/edit/school.html.erb index a73cdaf98..59bfc72d6 100644 --- a/app/views/parent_interface/consent_forms/edit/school.html.erb +++ b/app/views/parent_interface/consent_forms/edit/school.html.erb @@ -16,21 +16,13 @@ If you've moved recently, it's important to mention this.

- <%= render DfE::Autocomplete::View.new( - f, - attribute_name: :location_id, - form_field: f.govuk_select( - :location_id, - options_for_select( - dfe_autocomplete_options( - @consent_form.eligible_schools.map { - OpenStruct.new(name: _1.name, value: _1.id) - } - ), - f.object.id, - ), - label: { text: "Select a school" }, - ), + <%= f.govuk_collection_select( + :location_id, + @consent_form.eligible_schools, + :id, + :name, + label: { text: "Select a school" }, + data: { module: "autocomplete" }, ) %> <%= f.govuk_submit "Continue" %> diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 101a2902e..a162798f4 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -12,3 +12,7 @@ # application.js, application.css, and all non-JS/CSS in the app/assets # folder are already added. # Rails.application.config.assets.precompile += %w( admin.js admin.css ) +Rails.application.config.assets.paths << Rails.root.join("node_modules") +Rails.application.config.assets.precompile += %w[ + accessible-autocomplete/dist/accessible-autocomplete.min.css +] diff --git a/package.json b/package.json index c1162b1f0..b8a17dac1 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "dependencies": { "@hotwired/stimulus": "^3.2.2", "@hotwired/turbo-rails": "^8.0.10", + "accessible-autocomplete": "^3.0.1", "esbuild": "^0.24.0", "govuk-frontend": "^5.6.0", "idb": "^8.0.0", diff --git a/yarn.lock b/yarn.lock index 59430d238..8d9fad902 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1895,6 +1895,11 @@ abab@^2.0.6: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== +accessible-autocomplete@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/accessible-autocomplete/-/accessible-autocomplete-3.0.1.tgz#8ddf4934d0b4ba6acb28de486dd505e062cdc86e" + integrity sha512-xMshgc2LT5addvvfCTGzIkRrvhbOFeylFSnSMfS/PdjvvvElZkakCwxO3/yJYBWyi1hi3tZloqOJQ5kqqJtH4g== + acorn-globals@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3"