diff --git a/app/assets/stylesheets/_autocomplete.scss b/app/assets/stylesheets/_autocomplete.scss new file mode 100644 index 000000000..ce127ee57 --- /dev/null +++ b/app/assets/stylesheets/_autocomplete.scss @@ -0,0 +1,16 @@ +.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);