From 5b0650674623e2efcdc369806e0d006574700e4a Mon Sep 17 00:00:00 2001 From: Alicia <aliciapazrojas@gmail.com> Date: Thu, 29 Feb 2024 18:52:44 -0600 Subject: [PATCH 1/5] Autocomplete feature --- .ruby-version | 2 +- Gemfile | 18 +++++----- Gemfile.lock | 3 +- app/components/search_bar/component.html.erb | 10 ++++-- app/controllers/autocompletes_controller.rb | 13 +++++++ .../controllers/autocomplete_controller.js | 34 +++++++++++++++++++ app/queries/locations/keyword_query.rb | 2 +- app/views/home/index.html.slim | 12 +++---- config/routes.rb | 2 ++ 9 files changed, 75 insertions(+), 21 deletions(-) create mode 100644 app/controllers/autocompletes_controller.rb create mode 100644 app/javascript/controllers/autocomplete_controller.js diff --git a/.ruby-version b/.ruby-version index b50214693..a0cd9f0cc 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.0.2 +3.1.0 \ No newline at end of file diff --git a/Gemfile b/Gemfile index 4ece8629c..a8baaf126 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '3.0.2' +ruby '3.1.0' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' gem 'rails', '~> 6.1.4' @@ -30,12 +30,13 @@ gem 'redis', '~> 4.0' gem 'devise' # User Auth -gem "recaptcha" -gem "invisible_captcha" +gem 'invisible_captcha' +gem 'recaptcha' +gem 'activerecord-import' gem 'active_storage_validations' gem 'aws-sdk-s3', require: false -gem "caxlsx" +gem 'caxlsx' gem 'clockwork' gem 'cocoon' gem 'draper' @@ -47,13 +48,12 @@ gem 'pagy' gem 'pg_search' gem 'pundit' gem 'rack-attack' +gem 'rollbar' gem 'roo', '~> 2.8.0' -gem "sidekiq", "<7" +gem 'scout_apm' +gem 'sidekiq', '<7' gem 'slim-rails' gem 'view_component' -gem "activerecord-import" -gem 'scout_apm' -gem 'rollbar' # Use Turbo for rails gem 'turbo-rails' @@ -116,7 +116,7 @@ group :test do gem 'shoulda-matchers', '~> 4.0' gem 'simplecov', require: false gem 'timecop' - gem 'webdrivers', "~> 5.2", require: false + gem 'webdrivers', '~> 5.2', require: false end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem diff --git a/Gemfile.lock b/Gemfile.lock index f6939353e..6b1122cba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -556,6 +556,7 @@ GEM PLATFORMS arm64-darwin-20 arm64-darwin-21 + arm64-darwin-23 x86_64-darwin-19 x86_64-darwin-20 x86_64-linux @@ -634,7 +635,7 @@ DEPENDENCIES webpacker (~> 5.0) RUBY VERSION - ruby 3.0.2p107 + ruby 3.1.0p0 BUNDLED WITH 2.3.22 diff --git a/app/components/search_bar/component.html.erb b/app/components/search_bar/component.html.erb index ab8385afc..c2b3a4512 100644 --- a/app/components/search_bar/component.html.erb +++ b/app/components/search_bar/component.html.erb @@ -1,7 +1,7 @@ <div id="search-bar" class="sticky inset-x-0 z-20 flex items-center justify-center px-6 py-4 bg-gradient-to-r from-blue-gradient-2 to-blue-gradient-1 top-20 md:top-22.75 md:gap-5"> <%# Keyword input %> - <div class="relative w-full max-w-xl"> + <div class="relative w-full max-w-xl" data-controller="autocomplete"> <span class="absolute inset-y-0 left-0 flex items-center px-3 pointer-events-none"> <%= inline_svg_tag "solid_search.svg", class: 'h-4 w-4 fill-current text-gray-2' %> </span> @@ -13,11 +13,15 @@ bg-gradient-to-r from-blue-gradient-2 to-blue-gradient-1 top-20 md:top-22.75 md: id: "search-keyword-input", placeholder: "Try \"Mental Health Nonprofits\"", data: { - action: "input->search#displayClearKeywordButton", - search_target: "keywordInput" + action: "input->search#displayClearKeywordButton input->autocomplete#search", + search_target: "keywordInput", + autocomplete_target: "input", } ) %> + <ul data-autocomplete-target="list"> + </ul> + <button type="button" class="absolute inset-y-0 right-0 px-3 <%= "hidden" unless params.dig('search', 'keyword').present? %>" diff --git a/app/controllers/autocompletes_controller.rb b/app/controllers/autocompletes_controller.rb new file mode 100644 index 000000000..d1a45cfd5 --- /dev/null +++ b/app/controllers/autocompletes_controller.rb @@ -0,0 +1,13 @@ +class AutocompletesController < ApplicationController + skip_before_action :authenticate_user! + + include Pundit + + skip_after_action :verify_policy_scoped + skip_after_action :verify_authorized + + def show + suggestions = Locations::KeywordQuery.call({ keyword: params[:query] }).pluck(:name) + render json: suggestions + end +end diff --git a/app/javascript/controllers/autocomplete_controller.js b/app/javascript/controllers/autocomplete_controller.js new file mode 100644 index 000000000..0d7e85f65 --- /dev/null +++ b/app/javascript/controllers/autocomplete_controller.js @@ -0,0 +1,34 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + static targets = ["input", "list"]; + + connect() { + console.log("Autocomplete controller connected") + this.inputTarget.addEventListener("input", this.search.bind(this)); + } + + async search() { + const query = this.inputTarget.value; + + if (query.length < 2) { + this.listTarget.innerHTML = ""; + return; + } + + const response = await fetch(`/autocomplete?query=${query}`); + const data = await response.json(); + + console.log(data) + this.renderList(data); + } + + renderList(data) { + this.listTarget.innerHTML = ""; + data.forEach(item => { + const listItem = document.createElement("li"); + listItem.textContent = item; + this.listTarget.appendChild(listItem); + }); + } +} diff --git a/app/queries/locations/keyword_query.rb b/app/queries/locations/keyword_query.rb index 258d8bf88..a3f0edeaf 100644 --- a/app/queries/locations/keyword_query.rb +++ b/app/queries/locations/keyword_query.rb @@ -5,7 +5,7 @@ class KeywordQuery < ApplicationQuery attr_reader :locations class << self - def call(params = {}, locations = Location.active) + def call(params = {}, locations = Location.active) scope = locations scope = by_keyword(scope, params[:keyword]) end diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim index a1f19a524..6d028ca83 100644 --- a/app/views/home/index.html.slim +++ b/app/views/home/index.html.slim @@ -1,5 +1,5 @@ main - section class="relative bg-electric-teal hidden" data-controller="banner" data-banner-target="banner" + section class="relative hidden bg-electric-teal" data-controller="banner" data-banner-target="banner" div class="flex max-w-3xl mx-auto py-7 md:py-8" div class="hidden md:block md:pl-4 lg:pl-0" = inline_svg_tag 'megaphone.svg', size: '65*70' @@ -22,8 +22,8 @@ main | Search for listings of nonprofit organizations based on your needs. = form_with model: @search, url: search_path, method: :get do |f| .c-form class="py-0 mx-auto sm:px-3 sm:flex-col sm:justify-center sm:max-w-4xl" - div class="relative w-full sm:max-w-xl mb-7 sm:mb-0" - = f.text_field :keyword, autocomplete: "search", class:"c-input pl-10 m-0 w-full", placeholder: "Try \"Mental Health Nonprofits\"" + div class="relative w-full sm:max-w-xl mb-7 sm:mb-0" data-controller="autocomplete" + = f.text_field :keyword, autocomplete: "search", class:"c-input pl-10 m-0 w-full", placeholder: "Try \"Mental Health Nonprofits\"", data: { "autocomplete-target": "input", } = inline_svg_tag 'search-icon.svg', class:"absolute top-1/3 left-4" div = f.submit "Search", class:"c-button mx-10 mt-6" @@ -54,7 +54,7 @@ main | Found a nonprofit that meets your needs criteria? Reach out through the contact information provided on their individual profiles. section class="flex flex-col items-center px-6 overflow-hidden" div class="relative max-w-xl pt-32 mb-14" - h2 class="mb-6 text-2xl uppercase text-center font-bold text-gray-2 dark:text-white" + h2 class="mb-6 text-2xl font-bold text-center uppercase text-gray-2 dark:text-white" | Browse by causes p class="text-center" | Use our discovery feature to find nonprofits serving your community. @@ -93,9 +93,9 @@ main h2 class="px-5 pt-20 text-2xl font-bold text-center sm:px-0 md:pt-28 text-blue-dark pb-11" | Are you a nonprofit organization interested in joining our listings? = link_to 'Add or Claim a Nonprofit', new_nonprofit_request_path, class:'c-button inline-block my-1 text-white bg-blue-dark' - div class="top-0 left-0 -z-1 hidden sm:block sm:absolute" + div class="top-0 left-0 hidden -z-1 sm:block sm:absolute" = inline_svg_tag 'lc-desk-blur-left.svg' - div class="top-0 right-0 -z-1 hidden sm:block sm:absolute" + div class="top-0 right-0 hidden -z-1 sm:block sm:absolute" = inline_svg_tag 'lc-desk-blur-right.svg' div class="absolute bottom-0 right-0 -z-1 sm:hidden" = inline_svg_tag 'lc-mob-blur-right.svg' diff --git a/config/routes.rb b/config/routes.rb index 4cad826eb..61f6dfb17 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,5 +69,7 @@ resource :donate, only: %i[show] resource :privacy_policy, only: %i[show] resource :infowindow, only: :new + resource :autocomplete, only: %i[show] + root to: 'home#index' end From 34993e8338b066fe62038de7cac5a0ccc4648b63 Mon Sep 17 00:00:00 2001 From: Alicia <aliciapazrojas@gmail.com> Date: Mon, 4 Mar 2024 14:37:29 -0600 Subject: [PATCH 2/5] Use stimulus autoocomplete --- app/components/search_bar/component.html.erb | 6 ++-- app/controllers/autocompletes_controller.rb | 4 +-- .../controllers/autocomplete_controller.js | 34 ------------------- app/javascript/controllers/index.js | 2 ++ app/views/autocompletes/show.html.erb | 3 ++ app/views/home/index.html.slim | 5 +-- package.json | 1 + yarn.lock | 5 +++ 8 files changed, 19 insertions(+), 41 deletions(-) delete mode 100644 app/javascript/controllers/autocomplete_controller.js create mode 100644 app/views/autocompletes/show.html.erb diff --git a/app/components/search_bar/component.html.erb b/app/components/search_bar/component.html.erb index c2b3a4512..57c5e6c47 100644 --- a/app/components/search_bar/component.html.erb +++ b/app/components/search_bar/component.html.erb @@ -1,7 +1,7 @@ <div id="search-bar" class="sticky inset-x-0 z-20 flex items-center justify-center px-6 py-4 bg-gradient-to-r from-blue-gradient-2 to-blue-gradient-1 top-20 md:top-22.75 md:gap-5"> <%# Keyword input %> - <div class="relative w-full max-w-xl" data-controller="autocomplete"> + <div class="relative w-full max-w-xl" data-controller="autocomplete" data-autocomplete-url-value="/autocomplete" role="combobox"> <span class="absolute inset-y-0 left-0 flex items-center px-3 pointer-events-none"> <%= inline_svg_tag "solid_search.svg", class: 'h-4 w-4 fill-current text-gray-2' %> </span> @@ -13,13 +13,13 @@ bg-gradient-to-r from-blue-gradient-2 to-blue-gradient-1 top-20 md:top-22.75 md: id: "search-keyword-input", placeholder: "Try \"Mental Health Nonprofits\"", data: { - action: "input->search#displayClearKeywordButton input->autocomplete#search", + action: "input->search#displayClearKeywordButton", search_target: "keywordInput", autocomplete_target: "input", } ) %> - <ul data-autocomplete-target="list"> + <ul class="absolute w-full px-1 bg-white rounded-sm" data-autocomplete-target="results" role="listbox"> </ul> <button diff --git a/app/controllers/autocompletes_controller.rb b/app/controllers/autocompletes_controller.rb index d1a45cfd5..bb6c0af4a 100644 --- a/app/controllers/autocompletes_controller.rb +++ b/app/controllers/autocompletes_controller.rb @@ -7,7 +7,7 @@ class AutocompletesController < ApplicationController skip_after_action :verify_authorized def show - suggestions = Locations::KeywordQuery.call({ keyword: params[:query] }).pluck(:name) - render json: suggestions + @suggestions = %w[1 s orange apple banana] + render layout: false end end diff --git a/app/javascript/controllers/autocomplete_controller.js b/app/javascript/controllers/autocomplete_controller.js deleted file mode 100644 index 0d7e85f65..000000000 --- a/app/javascript/controllers/autocomplete_controller.js +++ /dev/null @@ -1,34 +0,0 @@ -import { Controller } from "@hotwired/stimulus"; - -export default class extends Controller { - static targets = ["input", "list"]; - - connect() { - console.log("Autocomplete controller connected") - this.inputTarget.addEventListener("input", this.search.bind(this)); - } - - async search() { - const query = this.inputTarget.value; - - if (query.length < 2) { - this.listTarget.innerHTML = ""; - return; - } - - const response = await fetch(`/autocomplete?query=${query}`); - const data = await response.json(); - - console.log(data) - this.renderList(data); - } - - renderList(data) { - this.listTarget.innerHTML = ""; - data.forEach(item => { - const listItem = document.createElement("li"); - listItem.textContent = item; - this.listTarget.appendChild(listItem); - }); - } -} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 935f8d1c9..f0dc97211 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -4,10 +4,12 @@ import { Application } from "@hotwired/stimulus" import { definitionsFromContext } from "@hotwired/stimulus-webpack-helpers" import Carousel from 'stimulus-carousel' +import { Autocomplete } from "stimulus-autocomplete" const application = Application.start() application.register('carousel', Carousel) +application.register('autocomplete', Autocomplete) const context = require.context("controllers", true, /_controller\.js$/) const contextComponents = require.context("../../components", true, /_controller\.js$/) application.load( diff --git a/app/views/autocompletes/show.html.erb b/app/views/autocompletes/show.html.erb new file mode 100644 index 000000000..d1dded340 --- /dev/null +++ b/app/views/autocompletes/show.html.erb @@ -0,0 +1,3 @@ +<% @suggestions.each do |suggestion| %> +<li role="option" class="px-1 rounded-sm cursor-pointer hover:bg-blue-medium hover:text-white"><%= suggestion %></li> +<% end %> \ No newline at end of file diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim index 6d028ca83..36be31418 100644 --- a/app/views/home/index.html.slim +++ b/app/views/home/index.html.slim @@ -22,8 +22,9 @@ main | Search for listings of nonprofit organizations based on your needs. = form_with model: @search, url: search_path, method: :get do |f| .c-form class="py-0 mx-auto sm:px-3 sm:flex-col sm:justify-center sm:max-w-4xl" - div class="relative w-full sm:max-w-xl mb-7 sm:mb-0" data-controller="autocomplete" - = f.text_field :keyword, autocomplete: "search", class:"c-input pl-10 m-0 w-full", placeholder: "Try \"Mental Health Nonprofits\"", data: { "autocomplete-target": "input", } + div class="relative w-full sm:max-w-xl mb-7 sm:mb-0" data-controller="autocomplete" data-autocomplete-url-value="/autocomplete" role="combobox" + = f.text_field :keyword, autocomplete: "search", class:"c-input pl-10 m-0 w-full", placeholder: "Try \"Mental Health Nonprofits\"", data: { "autocomplete-target": "input" } + ul class="absolute w-full px-1 bg-white rounded-sm" data-autocomplete-target="results" role="listbox" = inline_svg_tag 'search-icon.svg', class:"absolute top-1/3 left-4" div = f.submit "Search", class:"c-button mx-10 mt-6" diff --git a/package.json b/package.json index 59ffe5c53..9383cdaf9 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "moment": "^2.29.4", "postcss": "^8", "selectize": "0.12.4", + "stimulus-autocomplete": "^3.1.0", "stimulus-carousel": "^4.0.0", "stimulus-use": "^0.50.0-2", "swiper": "6.8.4", diff --git a/yarn.lock b/yarn.lock index 0a3f68de4..b7efb92d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6899,6 +6899,11 @@ statuses@2.0.1: resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +stimulus-autocomplete@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/stimulus-autocomplete/-/stimulus-autocomplete-3.1.0.tgz#7c9292706556ed0a87abf60ea2688bf0ea1176a8" + integrity sha512-SmVViCdA8yCl99oV2kzllNOqYjx7wruY+1OjAVsDTkZMNFZG5j+SqDKHMYbu+dRFy/SWq/PParzwZHvLAgH+YA== + stimulus-carousel@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/stimulus-carousel/-/stimulus-carousel-4.0.0.tgz#231d8dbcb5ecefc5a252ae34982fd565f999e681" From 7193ff4809b71ffa9ec40abba4e44f4385346e7a Mon Sep 17 00:00:00 2001 From: Alicia <aliciapazrojas@gmail.com> Date: Mon, 4 Mar 2024 16:07:32 -0600 Subject: [PATCH 3/5] Use Tag model to fetch suggestions --- app/controllers/autocompletes_controller.rb | 2 +- app/models/tag.rb | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/controllers/autocompletes_controller.rb b/app/controllers/autocompletes_controller.rb index bb6c0af4a..71ae5cc85 100644 --- a/app/controllers/autocompletes_controller.rb +++ b/app/controllers/autocompletes_controller.rb @@ -7,7 +7,7 @@ class AutocompletesController < ApplicationController skip_after_action :verify_authorized def show - @suggestions = %w[1 s orange apple banana] + @suggestions = Tag.suggestions(params[:q]) render layout: false end end diff --git a/app/models/tag.rb b/app/models/tag.rb index faf2c0d3e..081467e35 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -11,5 +11,16 @@ # updated_at :datetime not null # class Tag < ApplicationRecord + include PgSearch::Model belongs_to :organization + + pg_search_scope :search_suggestions, against: :name, + using: { + tsearch: { prefix: true }, + trigram: { threshold: 0.2 } + } + + def self.suggestions(term) + search_suggestions(term).pluck(:name).uniq.map(&:downcase) + end end From 41b1857426fb9e9813188f6a25267aaa83d8e69f Mon Sep 17 00:00:00 2001 From: Alicia <aliciapazrojas@gmail.com> Date: Thu, 7 Mar 2024 14:27:09 -0600 Subject: [PATCH 4/5] Use autocomplete#index action --- ...utocompletes_controller.rb => autocomplete_controller.rb} | 4 ++-- app/views/autocomplete/index.html.erb | 5 +++++ app/views/autocompletes/show.html.erb | 3 --- config/routes.rb | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) rename app/controllers/{autocompletes_controller.rb => autocomplete_controller.rb} (77%) create mode 100644 app/views/autocomplete/index.html.erb delete mode 100644 app/views/autocompletes/show.html.erb diff --git a/app/controllers/autocompletes_controller.rb b/app/controllers/autocomplete_controller.rb similarity index 77% rename from app/controllers/autocompletes_controller.rb rename to app/controllers/autocomplete_controller.rb index 71ae5cc85..eedb95e75 100644 --- a/app/controllers/autocompletes_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -1,4 +1,4 @@ -class AutocompletesController < ApplicationController +class AutocompleteController < ApplicationController skip_before_action :authenticate_user! include Pundit @@ -6,7 +6,7 @@ class AutocompletesController < ApplicationController skip_after_action :verify_policy_scoped skip_after_action :verify_authorized - def show + def index @suggestions = Tag.suggestions(params[:q]) render layout: false end diff --git a/app/views/autocomplete/index.html.erb b/app/views/autocomplete/index.html.erb new file mode 100644 index 000000000..0b40461b9 --- /dev/null +++ b/app/views/autocomplete/index.html.erb @@ -0,0 +1,5 @@ +<% @suggestions.each do |suggestion| %> + <li role="option" class="px-1 rounded-sm cursor-pointer hover:bg-blue-medium hover:text-white"> + <%= suggestion %> + </li> +<% end %> \ No newline at end of file diff --git a/app/views/autocompletes/show.html.erb b/app/views/autocompletes/show.html.erb deleted file mode 100644 index d1dded340..000000000 --- a/app/views/autocompletes/show.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -<% @suggestions.each do |suggestion| %> -<li role="option" class="px-1 rounded-sm cursor-pointer hover:bg-blue-medium hover:text-white"><%= suggestion %></li> -<% end %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 61f6dfb17..401cb70a3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,7 +69,7 @@ resource :donate, only: %i[show] resource :privacy_policy, only: %i[show] resource :infowindow, only: :new - resource :autocomplete, only: %i[show] + resources :autocomplete, only: %i[index] root to: 'home#index' end From f89ae5fc10b67c5addb00433896bb75c9e247715 Mon Sep 17 00:00:00 2001 From: Alicia <aliciapazrojas@gmail.com> Date: Thu, 7 Mar 2024 14:32:11 -0600 Subject: [PATCH 5/5] Use url helpers --- app/components/search_bar/component.html.erb | 2 +- app/views/home/index.html.slim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/search_bar/component.html.erb b/app/components/search_bar/component.html.erb index 57c5e6c47..8049a49fe 100644 --- a/app/components/search_bar/component.html.erb +++ b/app/components/search_bar/component.html.erb @@ -1,7 +1,7 @@ <div id="search-bar" class="sticky inset-x-0 z-20 flex items-center justify-center px-6 py-4 bg-gradient-to-r from-blue-gradient-2 to-blue-gradient-1 top-20 md:top-22.75 md:gap-5"> <%# Keyword input %> - <div class="relative w-full max-w-xl" data-controller="autocomplete" data-autocomplete-url-value="/autocomplete" role="combobox"> + <div class="relative w-full max-w-xl" data-controller="autocomplete" data-autocomplete-url-value="<%= autocomplete_index_path %>" role="combobox"> <span class="absolute inset-y-0 left-0 flex items-center px-3 pointer-events-none"> <%= inline_svg_tag "solid_search.svg", class: 'h-4 w-4 fill-current text-gray-2' %> </span> diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim index 36be31418..827d11fa1 100644 --- a/app/views/home/index.html.slim +++ b/app/views/home/index.html.slim @@ -22,7 +22,7 @@ main | Search for listings of nonprofit organizations based on your needs. = form_with model: @search, url: search_path, method: :get do |f| .c-form class="py-0 mx-auto sm:px-3 sm:flex-col sm:justify-center sm:max-w-4xl" - div class="relative w-full sm:max-w-xl mb-7 sm:mb-0" data-controller="autocomplete" data-autocomplete-url-value="/autocomplete" role="combobox" + div class="relative w-full sm:max-w-xl mb-7 sm:mb-0" data-controller="autocomplete" data-autocomplete-url-value=autocomplete_index_path role="combobox" = f.text_field :keyword, autocomplete: "search", class:"c-input pl-10 m-0 w-full", placeholder: "Try \"Mental Health Nonprofits\"", data: { "autocomplete-target": "input" } ul class="absolute w-full px-1 bg-white rounded-sm" data-autocomplete-target="results" role="listbox" = inline_svg_tag 'search-icon.svg', class:"absolute top-1/3 left-4"