diff --git a/examples/nuxt-app/layers/fixture-api/README.md b/examples/nuxt-app/layers/fixture-api/README.md index fcc835c60e..8bd0b85231 100644 --- a/examples/nuxt-app/layers/fixture-api/README.md +++ b/examples/nuxt-app/layers/fixture-api/README.md @@ -9,3 +9,5 @@ No backend is needed, the API is stubbed with flat file json fixtures (also used 2. [_fixture/tide-site](http://localhost:3000/_fixture/tide-site) Fixtures page content, but uses back end for site content 3. [_fixture/page-exposed](http://localhost:3000/_fixture/page-exposed) Fixtures both page and site content, expose all layout slots + +4. [_fixture/document](http://localhost:3000/_fixture/document) Edge case example for cheerio rendering diff --git a/examples/nuxt-app/public/placeholders/mapbg.png b/examples/nuxt-app/public/placeholders/mapbg.png new file mode 100644 index 0000000000..1f86771dfc Binary files /dev/null and b/examples/nuxt-app/public/placeholders/mapbg.png differ diff --git a/examples/nuxt-app/test/features/maps/map-search-banner.feature b/examples/nuxt-app/test/features/maps/map-search-banner.feature new file mode 100644 index 0000000000..71f27317ff --- /dev/null +++ b/examples/nuxt-app/test/features/maps/map-search-banner.feature @@ -0,0 +1,54 @@ +Feature: Map search banner + + Background: + Given the site endpoint returns fixture "/site/reference" with status 200 + + @mockserver + Example: Map search banner - Content + Given the page endpoint for path "/search-bar-example" returns fixture "/maps/example-map-search-banner" with status 200 + And I am using a "macbook-16" device + Then I visit the page "/search-bar-example" + Then the location search banner should have the following content + | title | description | inputLabel | placeholder | image | + | Test search heading | Test introduction text | Test label | Test placeholder | /placeholders/mapbg.png | + + @mockserver + Scenario: Map search banner - Custom suggestions + Given I load the page fixture with "/maps/example-map-search-banner" + Given the page endpoint for path "/search-bar-example" returns the loaded fixture + Given the "/api/tide/elasticsearch/elasticsearch_index_develop_node/_search" network request is stubbed with fixture "/maps/simple-map-results" and status 200 as alias "searchReq" + Given I visit the page "/search-bar-example" + And I wait 2 seconds + And I type "bays" into the location search bar + And I wait 2 seconds + And the search suggestions displayed should include + | Test location - testValue - 1 | + | Test location - testValue - 2 | + | With magic key | + And I click the search suggestion labelled "Test location - testValue - 1" + Then the current path should be "/test/target/url" + Then the URL should reflect that the location has the following: + | key | value | + | id | 1 | + | name | Test location - testValue - 1 | + + @mockserver + Scenario: Map search banner - Suggestions with ArcGIS magic keys + Given the ArcGIS findAddressCandidates endpoint returns "/maps/arcgis-address-candidates" fixture + Given I load the page fixture with "/maps/example-map-search-banner" + Given the page endpoint for path "/search-bar-example" returns the loaded fixture + Given the "/api/tide/elasticsearch/elasticsearch_index_develop_node/_search" network request is stubbed with fixture "/maps/simple-map-results" and status 200 as alias "searchReq" + Given I visit the page "/search-bar-example" + And I wait 2 seconds + And I type "bays" into the location search bar + And I wait 2 seconds + And the search suggestions displayed should include + | Test location - testValue - 1 | + | Test location - testValue - 2 | + | With magic key | + And I click the search suggestion labelled "With magic key" + Then the current path should be "/test/target/url" + Then the URL should reflect that the location has the following: + | key | value | + | id | fake1234 | + | name | 1234 Fake St Fakeville Vic 3000 | diff --git a/examples/nuxt-app/test/fixtures/maps/example-map-search-banner.json b/examples/nuxt-app/test/fixtures/maps/example-map-search-banner.json new file mode 100644 index 0000000000..6653565fb1 --- /dev/null +++ b/examples/nuxt-app/test/fixtures/maps/example-map-search-banner.json @@ -0,0 +1,134 @@ +{ + "title": "Test location search banner", + "changed": "2024-11-11T14:25:56+11:00", + "created": "2024-10-28T17:42:15+11:00", + "type": "landing_page", + "nid": "1cf34a40-130f-42a5-bd83-612cfa71c889", + "_sectionId": "2138", + "sidebar": { + "contacts": [], + "whatsNext": [], + "siteSectionNav": null + }, + "status": "published", + "topicTags": [ + { + "text": "Education", + "url": "/topic/education" + } + ], + "siteSection": null, + "meta": { + "url": "/early-learning-victoria", + "langcode": "en", + "description": "We're opening 50 new early learning and childcare centres across Victoria. These will be government owned and operated, providing long day care and integrated kinder programs.", + "additional": [ + { + "tag": "meta", + "attributes": { + "name": "title", + "content": "Early Learning Victoria | l" + } + }, + { + "tag": "link", + "attributes": { + "rel": "canonical", + "href": "https://content.vic.gov.au/early-learning-victoria" + } + }, + { + "tag": "meta", + "attributes": { + "property": "og:locale", + "content": "en-AU" + } + } + ], + "keywords": "", + "image": null + }, + "showContentRating": true, + "summary": "Test", + "showHeroAcknowledgement": false, + "showInPageNav": false, + "showHeroImageCaption": false, + "showTopicTags": true, + "inPageNavHeadingLevel": "h2", + "background": "alt", + "header": { + "title": "Test location search banner", + "summary": "Test", + "links": { + "title": "", + "items": [], + "more": null + }, + "backgroundImageCaption": null, + "theme": "default", + "logoImage": null, + "backgroundImage": { + "src": "/placeholders/medium.png", + "alt": "", + "title": "", + "width": 10667, + "height": 4500, + "focalPoint": { + "x": 5334, + "y": 2520 + } + }, + "cornerTop": { + "src": "/placeholders/medium.png", + "alt": "", + "title": "", + "width": 804, + "height": 491, + "focalPoint": { + "x": 402, + "y": 246 + } + }, + "cornerBottom": { + "src": "/placeholders/medium.png", + "alt": "", + "title": "", + "width": 804, + "height": 284, + "focalPoint": { + "x": 402, + "y": 142 + } + }, + "primaryAction": null, + "secondaryAction": null, + "secondaryActionLabel": null + }, + "primaryCampaign": null, + "secondaryCampaign": null, + "headerComponents": [ + { + "uuid": "27b0781b-2533-40ba-a9da-28fefc41d83e", + "component": "TideLandingPageMapSearchBanner", + "id": "3434418", + "props": { + "title": "Test search heading", + "intro": "Test introduction text", + "label": "Test label", + "placeholder": "Test placeholder", + "image": { + "src": "/placeholders/mapbg.png", + "alt": "Example map image" + }, + "searchUrl": "/test/target/url", + "suggestionsConfig": { + "function": "exampleSuggestionsFn", + "args": { + "testArg": "testValue" + } + } + } + } + ], + "bodyComponents": [] +} diff --git a/packages/ripple-test-utils/step_definitions/components/index.ts b/packages/ripple-test-utils/step_definitions/components/index.ts index 555fff06e7..0dbe9c8721 100644 --- a/packages/ripple-test-utils/step_definitions/components/index.ts +++ b/packages/ripple-test-utils/step_definitions/components/index.ts @@ -21,3 +21,4 @@ import './content-collection' import './custom-collection' import './social-share' import './data-driven-component' +import './location-search-banner' diff --git a/packages/ripple-test-utils/step_definitions/components/location-search-banner.ts b/packages/ripple-test-utils/step_definitions/components/location-search-banner.ts new file mode 100644 index 0000000000..e3629131f4 --- /dev/null +++ b/packages/ripple-test-utils/step_definitions/components/location-search-banner.ts @@ -0,0 +1,18 @@ +import { Then, DataTable } from '@badeball/cypress-cucumber-preprocessor' + +Then( + 'the location search banner should have the following content', + (dataTable: DataTable) => { + const data = dataTable.hashes()[0] + + cy.get(`[data-component-type="TideLandingPageMapSearchBanner"]`).as( + 'component' + ) + cy.get(`@component`).should('contain', data.description) + cy.get('@component').within(() => { + cy.get(`h2`).should('have.text', data.title) + cy.get(`img`).should('have.attr', 'src', data.image) + cy.get(`input`).should('have.attr', 'placeholder', data.placeholder) + }) + } +) diff --git a/packages/ripple-tide-landing-page/app.config.ts b/packages/ripple-tide-landing-page/app.config.ts index eedaf4ea11..c1a08e9de8 100644 --- a/packages/ripple-tide-landing-page/app.config.ts +++ b/packages/ripple-tide-landing-page/app.config.ts @@ -6,6 +6,7 @@ export default defineAppConfig({ dataDrivenComponents: { // add key of field_data_driven_component and value of component name to render // eg: find_a_council_map: 'VicCouncilLookup' + location_search_banner: 'TideLandingPageMapSearchBanner' } } }) diff --git a/packages/ripple-tide-landing-page/components/global/TideLandingPage/MapSearchBanner.vue b/packages/ripple-tide-landing-page/components/global/TideLandingPage/MapSearchBanner.vue new file mode 100644 index 0000000000..2a7975f96a --- /dev/null +++ b/packages/ripple-tide-landing-page/components/global/TideLandingPage/MapSearchBanner.vue @@ -0,0 +1,63 @@ + + + diff --git a/packages/ripple-tide-search/components/global/TideSearchAddressLookup.vue b/packages/ripple-tide-search/components/global/TideSearchAddressLookup.vue index b912331925..77f87e5b0d 100644 --- a/packages/ripple-tide-search/components/global/TideSearchAddressLookup.vue +++ b/packages/ripple-tide-search/components/global/TideSearchAddressLookup.vue @@ -1,56 +1,25 @@ diff --git a/packages/ripple-tide-search/components/global/TideSearchStandaloneLocationBar.vue b/packages/ripple-tide-search/components/global/TideSearchStandaloneLocationBar.vue new file mode 100644 index 0000000000..251639f567 --- /dev/null +++ b/packages/ripple-tide-search/components/global/TideSearchStandaloneLocationBar.vue @@ -0,0 +1,76 @@ + + + diff --git a/packages/ripple-tide-search/composables/useTideSearch.ts b/packages/ripple-tide-search/composables/useTideSearch.ts index f19383fd78..53b2df49ed 100644 --- a/packages/ripple-tide-search/composables/useTideSearch.ts +++ b/packages/ripple-tide-search/composables/useTideSearch.ts @@ -8,7 +8,8 @@ import { useRuntimeConfig, navigateTo, getSingleQueryStringValue, - scrollToElementTopWithOffset + scrollToElementTopWithOffset, + getScopedQueryParams } from '#imports' import type { TideSearchListingConfig, @@ -703,19 +704,6 @@ export default ({ }, {}) } - /** - * Get a scoped set of query parameters - * i.e., custom location[] and search[] parameters - */ - const getScopedQueryParams = ( - scope: string, - params: { [key: string]: any } - ) => { - return Object.entries(params || {}).reduce((obj, [key, value]) => { - return { ...obj, [`${scope}[${key}]`]: value } - }, {}) - } - /** * Updates the URL to trigger a new search, always returns to page 1 to avoid empty pages */ diff --git a/packages/ripple-tide-search/utils/search.ts b/packages/ripple-tide-search/utils/search.ts index 50d72bd234..6365193a67 100644 --- a/packages/ripple-tide-search/utils/search.ts +++ b/packages/ripple-tide-search/utils/search.ts @@ -96,3 +96,16 @@ export const getActiveFiltersTally = ( return acc + 1 }, 0) } + +/** + * Get a scoped set of query parameters + * i.e., custom location[] and search[] parameters + */ +export const getScopedQueryParams = ( + scope: string, + params: { [key: string]: any } +) => { + return Object.entries(params || {}).reduce((obj, [key, value]) => { + return { ...obj, [`${scope}[${key}]`]: value } + }, {}) +} diff --git a/packages/ripple-ui-core/src/components/header/RplHeader.css b/packages/ripple-ui-core/src/components/header/RplHeader.css index c5e6a2926d..3470af419b 100644 --- a/packages/ripple-ui-core/src/components/header/RplHeader.css +++ b/packages/ripple-ui-core/src/components/header/RplHeader.css @@ -371,7 +371,7 @@ @media (--rpl-bp-xl) { /* magic number: offset is equal to one column taking into account the cell spacing, this means the image starts at col 8. i.e. calc((120rem / 12) + (var(--rpl-sp-7) / 11)) */ --local-image-offset: 103px; - --local-image-width: 50vw; + --local-image-width: 50%; } } } diff --git a/packages/ripple-ui-core/src/components/search-banner/RplSearchBanner.css b/packages/ripple-ui-core/src/components/search-banner/RplSearchBanner.css new file mode 100644 index 0000000000..06ccf86137 --- /dev/null +++ b/packages/ripple-ui-core/src/components/search-banner/RplSearchBanner.css @@ -0,0 +1,76 @@ +@import '@dpc-sdp/ripple-ui-core/style/breakpoints'; + +.rpl-search-banner { + position: relative; + background-color: var(--rpl-clr-neutral-100); + border-bottom: var(--rpl-border-1) solid var(--rpl-clr-neutral-300); + + .rpl-grid { + row-gap: 0; + } +} + +.rpl-search-banner__search { + padding-block: var(--rpl-sp-5) var(--rpl-sp-7); + + @media (--rpl-bp-m) { + padding-block: var(--rpl-sp-8) var(--rpl-sp-9); + } + + @media (--rpl-bp-l) { + padding-block: var(--rpl-sp-10) var(--rpl-sp-11); + } +} + +.rpl-search-banner--image .rpl-search-banner__search .rpl-container { + @media (--rpl-bp-m) { + padding-right: var(--rpl-sp-6); + } + + @media (--rpl-bp-xl) { + padding-right: var(--rpl-sp-7); + } +} + +.rpl-search-banner__search-inner { + margin-bottom: var(--rpl-sp-6); + max-width: var(--rpl-content-max-width); +} + +.rpl-search-banner__media { + --local-media-height: 137px; + --local-media-offset: 0; + --local-media-width: 100%; + --local-media-columns: calc((5 / 12) * 100%); + + width: calc(var(--local-media-width) - var(--local-media-offset)); + min-height: var(--local-search-banner-media-height); + + @media (--rpl-bp-s) { + --local-search-banner-media-height: 246px; + } + + @media (--rpl-bp-m) { + --local-media-offset: var(--rpl-sp-2); + --local-media-width: var(--local-media-columns); + + position: absolute; + right: 0; + height: 100%; + min-height: auto; + } + + @media (--rpl-bp-l) { + --local-media-offset: var(--rpl-sp-1); + } + + @media (--rpl-bp-xl) { + /* magic number: offset is equal to one column taking into account the cell spacing, this means the image starts at col 8 */ + --local-media-offset: 103px; + --local-media-width: 50%; + } +} + +.rpl-search-banner__image { + width: 100%; +} diff --git a/packages/ripple-ui-core/src/components/search-banner/RplSearchBanner.stories.mdx b/packages/ripple-ui-core/src/components/search-banner/RplSearchBanner.stories.mdx new file mode 100644 index 0000000000..33c3c80d7e --- /dev/null +++ b/packages/ripple-ui-core/src/components/search-banner/RplSearchBanner.stories.mdx @@ -0,0 +1,65 @@ +import { + Canvas, + Meta, + Story, + ArgsTable +} from '@storybook/addon-docs' +import RplSearchBanner from './RplSearchBanner.vue' +import { a11yStoryCheck } from './../../../stories/interactions.js' + +export const SingleTemplate = (args) => ({ + components: { RplSearchBanner }, + setup() { + return { + args + } + }, + template: `` +}) + + + +# Search banner + + + +## Default + +Simple banner with no image + + + + {SingleTemplate.bind()} + + + +## Image + +Banner with image to the top (mobile) and right (desktop) + + + + {SingleTemplate.bind()} + + diff --git a/packages/ripple-ui-core/src/components/search-banner/RplSearchBanner.vue b/packages/ripple-ui-core/src/components/search-banner/RplSearchBanner.vue new file mode 100644 index 0000000000..96621a1bd5 --- /dev/null +++ b/packages/ripple-ui-core/src/components/search-banner/RplSearchBanner.vue @@ -0,0 +1,59 @@ + + + + +