diff --git a/apps/site/assets/css/_autocomplete-theme.scss b/apps/site/assets/css/_autocomplete-theme.scss index c2e4f3d4e8..1184fc861b 100644 --- a/apps/site/assets/css/_autocomplete-theme.scss +++ b/apps/site/assets/css/_autocomplete-theme.scss @@ -1,137 +1,139 @@ @import '@algolia/autocomplete-theme-classic'; -.aa-Autocomplete { - --aa-icon-color-rgb: 22, 92, 150; // $brand-primary; - --aa-primary-color-rgb: 22, 92, 150; // $brand-primary; - --aa-search-input-height: 2.25rem; -} +// WIP as we upgrade all of the search bars +%shared-autocomplete { + .aa-Autocomplete { + --aa-icon-color-rgb: 22, 92, 150; // $brand-primary; + --aa-primary-color-rgb: 22, 92, 150; // $brand-primary; + } -// right now #header-desktop is the only search using the new library, so it's -// the only one styled here. the #header-desktop can be removed once we migrate -// the rest over -#header-desktop { - .c-search-bar__autocomplete { - .aa-Label { - margin-bottom: unset; - } + .aa-Label { + margin-bottom: unset; + } - .aa-Input { - // stylelint-disable-next-line declaration-no-important - height: var(--aa-search-input-height) !important; - } + .aa-InputWrapperPrefix { + order: 3; // move search icon to end. + } - .aa-InputWrapperPrefix { - order: 3; // move search icon to end. - } + .aa-InputWrapper { + order: 1; + } - .aa-InputWrapper { - order: 1; - padding-left: 1rem; - } + .aa-InputWrapperSuffix { + order: 2; + } + + .aa-Form { + border: 3px solid $brand-primary; - .aa-InputWrapperSuffix { - order: 2; + &:focus-within { + border-color: $brand-primary-light; + box-shadow: unset; } + } - .aa-Form { - border: 3px solid $brand-primary; - border-radius: .5rem; + .aa-LoadingIndicator, + .aa-SubmitButton { + padding-left: var(--aa-spacing-half); + width: calc(var(--aa-spacing) * 1.25 + var(--aa-icon-size) - 1px); + } - &:focus-within { - border-color: $brand-primary-lightest; - box-shadow: unset; - } - } + .aa-ClearButton { + @include fa-icon-solid($fa-var-times-circle); + color: rgba(var(--aa-icon-color-rgb), var(--aa-icon-color-alpha)); + // hide default icon + .aa-ClearIcon { display: none; } + } - .aa-LoadingIndicator, - .aa-SubmitButton { - padding-left: var(--aa-spacing-half); - width: calc(var(--aa-spacing) * 1.25 + var(--aa-icon-size) - 1px); - } + .aa-SubmitButton { + @include fa-icon-solid($fa-var-search); + color: rgba(var(--aa-icon-color-rgb), var(--aa-icon-color-alpha)); + // hide default icon + .aa-SubmitIcon { display: none; } + } - .aa-ClearButton { - @include fa-icon-solid($fa-var-times-circle); - color: rgba(var(--aa-icon-color-rgb), var(--aa-icon-color-alpha)); - padding-right: var(--aa-spacing-half); - // hide default icon - .aa-ClearIcon { display: none; } - } + .aa-GradientBottom, + .aa-GradientTop { all: unset; } - .aa-SubmitButton { - @include fa-icon-solid($fa-var-search); - color: rgba(var(--aa-icon-color-rgb), var(--aa-icon-color-alpha)); - // hide default icon - .aa-SubmitIcon { display: none; } + .aa-ItemContent { + mark { + padding: 0; } + > * { + margin-right: .25rem; + } + } - .aa-GradientBottom, - .aa-GradientTop { all: unset; } + .aa-ItemContentTitle { + display: unset; + margin: unset; + overflow: visible; + text-overflow: unset; + white-space: normal; } - .c-search-bar__autocomplete-results { - .aa-ItemContent { - mark { - padding: 0; - } - > * { - margin-right: .25rem; - } - } + .aa-PanelLayout { + padding: unset; + } + + .aa-Panel { + margin-top: .25rem; + z-index: var(--aa-base-z-index); + } + + .aa-Item { + border-bottom: $border; + border-radius: 0; + font-weight: normal; - .aa-ItemContentTitle { - display: unset; - margin: unset; - overflow: visible; - text-overflow: unset; - white-space: normal; + &:hover { + background-color: $brand-primary-lightest; } - .aa-PanelLayout { - padding: unset; + em { + font-style: inherit; + font-weight: bold; } - .aa-Panel { - // stylelint-disable-next-line declaration-no-important - top: 3.25rem !important; - z-index: var(--aa-base-z-index); + > a, + > button { + color: currentColor; + display: flex; + font-weight: inherit; + gap: .25rem; + min-width: 0; + padding: calc(#{$base-spacing} / 2) $base-spacing; } - .aa-Item { - border-bottom: $border; - border-radius: 0; - font-weight: normal; - - &:hover { - background-color: $brand-primary-lightest; - } - - em { - font-style: inherit; - font-weight: bold; - } - - > a, - > button { - color: currentColor; - display: flex; - font-weight: inherit; - gap: .25rem; - min-width: 0; - padding: calc(#{$base-spacing} / 2) $base-spacing; - } - - a:hover { - text-decoration: none; - } - - [class*=c-svg__icon] { - width: 1em; - } + a:hover { + text-decoration: none; } - .aa-ItemContent, - .aa-ItemContentBody { - display: unset; + [class*=c-svg__icon] { + width: 1em; } } + + .aa-ItemContent, + .aa-ItemContentBody { + display: unset; + } +} + +#header-desktop { + --aa-search-input-height: 2.25rem; + + @extend %shared-autocomplete; + + .aa-Form { + border-radius: .5rem; + } + + .aa-InputWrapper { + padding-left: 1rem; + } + + .aa-ClearButton { + padding-right: var(--aa-spacing-half); + } } diff --git a/apps/site/assets/css/_header.scss b/apps/site/assets/css/_header.scss index 92bb22dbbb..b6233c0a45 100644 --- a/apps/site/assets/css/_header.scss +++ b/apps/site/assets/css/_header.scss @@ -64,14 +64,15 @@ @include media-breakpoint-up(sm) { .search-wrapper { + align-items: center; + display: flex; flex-grow: 1; height: 100%; - > div { height: 100%; } + justify-content: flex-end; } } } -.header-navbar.new, .m-menu__search { .search { align-items: center; @@ -82,7 +83,6 @@ } .c-form__input-container { border-radius: 8px; - height: 44px; } button.c-form__submit-btn { background-color: unset; @@ -98,10 +98,10 @@ } .header-navbar.new .search { + display: block; padding-left: 24px; - .c-form__input-container { - max-width: 350px; - } + width: 100%; + @include media-breakpoint-only(xs) { display: none; } diff --git a/apps/site/assets/ts/ui/__tests__/__snapshots__/autocomplete-test.ts.snap b/apps/site/assets/ts/ui/__tests__/__snapshots__/autocomplete-test.ts.snap index 10629e0c98..e0bde17eaa 100644 --- a/apps/site/assets/ts/ui/__tests__/__snapshots__/autocomplete-test.ts.snap +++ b/apps/site/assets/ts/ui/__tests__/__snapshots__/autocomplete-test.ts.snap @@ -12,6 +12,7 @@ exports[`Algolia v1 autocomplete matches snapshot 1`] = ` data-algolia="routes,stops,drupal" data-geolocation="" data-locations="" + data-placeholder="Search for routes, info, and more" >
diff --git a/apps/site/assets/ts/ui/autocomplete/helpers.ts b/apps/site/assets/ts/ui/autocomplete/helpers.ts index 873dbbb4fd..c57d60b0c2 100644 --- a/apps/site/assets/ts/ui/autocomplete/helpers.ts +++ b/apps/site/assets/ts/ui/autocomplete/helpers.ts @@ -2,7 +2,7 @@ import { OnStateChangeProps } from "@algolia/autocomplete-js"; import { ContentItem, Item, RouteItem, StopItem } from "./__autocomplete"; import { isLGDown } from "../../helpers/media-breakpoints"; -function isStopItem(x: Item): x is StopItem { +export function isStopItem(x: Item): x is StopItem { return Object.keys(x).includes("stop"); } @@ -37,7 +37,7 @@ export function transitNearMeURL( }`; } -export const onStateChange: (props: OnStateChangeProps) => void = ({ +const navStateChange: (props: OnStateChangeProps) => void = ({ state }) => { // grey out the page and disable scrolling when search is open @@ -49,3 +49,10 @@ export const onStateChange: (props: OnStateChangeProps) => void = ({ } } }; + +export const STATE_CHANGE_HANDLERS: Record< + string, + (props: OnStateChangeProps) => void +> = { + nav: navStateChange +}; diff --git a/apps/site/assets/ts/ui/autocomplete/index.ts b/apps/site/assets/ts/ui/autocomplete/index.ts index 4fbce874cb..d276eb6a2f 100644 --- a/apps/site/assets/ts/ui/autocomplete/index.ts +++ b/apps/site/assets/ts/ui/autocomplete/index.ts @@ -3,7 +3,7 @@ import { createElement, Fragment } from "react"; import { render } from "react-dom"; import getSources from "./sources"; import { Item } from "./__autocomplete"; -import { onStateChange } from "./helpers"; +import { STATE_CHANGE_HANDLERS } from "./helpers"; // replace the default Preact-based renderer used by AutocompleteJS const reactRenderer = { @@ -33,11 +33,12 @@ function setupAlgoliaAutocomplete(wrapper: HTMLElement): void { input: "c-form__input-container" }, openOnFocus: true, - onStateChange, + onStateChange: + STATE_CHANGE_HANDLERS[`${container.dataset.stateChangeListener}`], onSubmit({ state }) { window.Turbolinks.visit(`/search?query=${state.query}`); }, - placeholder: "Search for routes, info, and more", + placeholder: container.dataset.placeholder, getSources: params => getSources(container.dataset, params), renderer: reactRenderer }; diff --git a/apps/site/assets/ts/ui/autocomplete/templates/algolia.tsx b/apps/site/assets/ts/ui/autocomplete/templates/algolia.tsx index bcdc0affdd..eec63a1c3c 100644 --- a/apps/site/assets/ts/ui/autocomplete/templates/algolia.tsx +++ b/apps/site/assets/ts/ui/autocomplete/templates/algolia.tsx @@ -3,7 +3,12 @@ import React from "react"; import { get, uniqueId } from "lodash"; import { SourceTemplates } from "@algolia/autocomplete-js"; import { AutocompleteItem, Item } from "../__autocomplete"; -import { getTitleAttribute, isContentItem, isRouteItem } from "../helpers"; +import { + getTitleAttribute, + isContentItem, + isRouteItem, + isStopItem +} from "../helpers"; import { contentIcon, getFeatureIcons, @@ -76,10 +81,12 @@ const AlgoliaItemTemplate: SourceTemplates["item"] = ({ ))} - {components.Highlight({ - hit: item, - attribute - })} + + {components.Highlight({ + hit: item, + attribute + })} +   {isRouteItem(item) && item.route.type === 3 && ( diff --git a/apps/site/lib/site_web/components.ex b/apps/site/lib/site_web/components.ex index 4179b83b45..02be863f0c 100644 --- a/apps/site/lib/site_web/components.ex +++ b/apps/site/lib/site_web/components.ex @@ -25,6 +25,17 @@ defmodule SiteWeb.Components do doc: "Enable prompt for user geolocation." ) + attr(:placeholder, :string, + required: false, + doc: "Placeholder text for empty search bar.", + default: "Search for routes, info, and more" + ) + + attr(:state_change_listener, :string, + doc: "Name of event listener that responds to Autocomplete.js state changes", + default: nil + ) + @doc """ Instantiates a search box using Algolia's Autocomplete.js library, configured to search our application's Algolia indexes, AWS Location Service, and @@ -58,7 +69,12 @@ defmodule SiteWeb.Components do raise "Nothing to search! Please enable at least one search type." end - assigns = assign(assigns, :valid_indexes, valid_algolia_indexes) + assigns = + assign( + assigns, + :valid_indexes, + if(length(valid_algolia_indexes) > 0, do: Enum.join(valid_algolia_indexes, ",")) + ) ~H"""
-
+
""" end diff --git a/apps/site/lib/site_web/templates/layout/_new_header.html.heex b/apps/site/lib/site_web/templates/layout/_new_header.html.heex index daa36567da..21fde1d115 100644 --- a/apps/site/lib/site_web/templates/layout/_new_header.html.heex +++ b/apps/site/lib/site_web/templates/layout/_new_header.html.heex @@ -14,12 +14,13 @@ <%= render "_new_nav_desktop.html", assigns %>
- diff --git a/apps/site/test/site_web/components_test.exs b/apps/site/test/site_web/components_test.exs index 40a2b5cd81..5df2ef259e 100644 --- a/apps/site/test/site_web/components_test.exs +++ b/apps/site/test/site_web/components_test.exs @@ -26,8 +26,8 @@ defmodule SiteWeb.ComponentsTest do test "renders with AlgoliaAutocomplete hook and appropriate data attributes" do assert """
-
-
+
+
""" =~ render_component( @@ -35,5 +35,18 @@ defmodule SiteWeb.ComponentsTest do %{id: "testID", locations: true, algolia_indexes: [:stops, :routes]} ) end + + test "can indicate a state change listener by name" do + assert """ +
+
+
+
+ """ =~ + render_component( + &algolia_autocomplete/1, + %{id: "test_with_listener", locations: true, state_change_listener: "nav"} + ) + end end end