Merged in fix/SW-1041-design-feedback-select-hotel (pull request #986)

fix(SW-1041): fix UI design feedback

* fix(SW-1041): fix UI design feedback

* fix(SW-1041): small fix

* fix(SW-1041): add filter and sort badge

* fix(SW-1041): update activefilter when entering map view

* fix(SW-1041): create hook with activefilters

* fix(SW-1041): hook only sets filter

* fix(SW-1041): fix padding breadcrumbs

* fix(SW-1041): rename hook

* fix(SW-1041): fix double scroll


Approved-by: Pontus Dreij
Approved-by: Niclas Edenvin
This commit is contained in:
Bianca Widstam
2024-11-28 07:42:52 +00:00
parent 4a11833b4d
commit 8f3d203b70
22 changed files with 205 additions and 95 deletions

View File

@@ -72,7 +72,7 @@
.header nav {
display: block;
max-width: var(--max-width-navigation);
padding-left: 0;
padding: 0;
}
.sorter {

View File

@@ -3,6 +3,4 @@
flex-direction: column;
gap: var(--Spacing-x2);
margin-bottom: var(--Spacing-x2);
max-height: 100vh;
overflow-y: auto;
}

View File

@@ -59,22 +59,42 @@
.content {
flex-direction: column;
gap: var(--Spacing-x3);
display: flex;
height: 100%;
}
.sorter {
padding: var(--Spacing-x2);
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2) var(--Spacing-x-half)
var(--Spacing-x2);
flex: 0 0 auto;
}
.badge {
background-color: var(--Base-Text-Accent);
border-radius: var(--Corner-radius-xLarge);
width: 20px;
height: 20px;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
}
.filters {
padding: var(--Spacing-x2);
padding-top: calc(var(--Spacing-x3) + var(--Spacing-x-half));
flex: 1 1 auto;
overflow-y: auto;
}
.filters ul {
margin-top: var(--Spacing-x3);
}
.filters ul li {
padding-bottom: var(--Spacing-x1);
}
.header {
text-align: right;
padding: var(--Spacing-x-one-and-half);
@@ -93,11 +113,15 @@
padding: 0;
}
.divider {
display: none;
}
.footer {
display: flex;
flex-direction: column-reverse;
flex-direction: column;
gap: var(--Spacing-x1);
padding: var(--Spacing-x2);
padding: var(--Spacing-x3) var(--Spacing-x2);
flex: 0 0 auto;
border-top: 1px solid var(--Base-Border-Subtle);
}
@@ -112,6 +136,11 @@
overflow-y: auto;
}
.divider {
display: block;
padding: 0 var(--Spacing-x3);
}
.header {
display: grid;
grid-template-columns: auto 1fr;
@@ -139,6 +168,10 @@
overflow-y: unset;
}
.sorter {
padding: var(--Spacing-x2);
}
.sorter,
.filters,
.footer,
@@ -158,19 +191,27 @@
padding: var(--Spacing-x2) var(--Spacing-x3);
}
.filters aside h1 {
margin-bottom: var(--Spacing-x2);
.filters aside > form {
gap: var(--Spacing-x2);
}
.filters aside > div:last-child {
margin-top: var(--Spacing-x4);
padding-bottom: 0;
.filters aside form > div:last-child {
margin-top: var(--Spacing-x2);
}
.filters aside ul {
display: grid;
grid-template-columns: 1fr 1fr;
margin-top: var(--Spacing-x3);
margin-top: var(--Spacing-x1);
}
.filters ul li:hover {
background: var(--UI-Input-Controls-Surface-Hover);
border-radius: var(--Corner-radius-Medium,);
outline: none;
}
.filters ul li {
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
}
}
@media screen and (min-width: 1024) {

View File

@@ -13,7 +13,9 @@ import { useHotelFilterStore } from "@/stores/hotel-filters"
import { CloseLargeIcon, FilterIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import useInitializeFiltersFromUrl from "@/hooks/useInitializeFiltersFromUrl"
import HotelFilter from "../HotelFilter"
import HotelSorter from "../HotelSorter"
@@ -26,15 +28,20 @@ export default function FilterAndSortModal({
filters,
}: FilterAndSortModalProps) {
const intl = useIntl()
useInitializeFiltersFromUrl()
const resultCount = useHotelFilterStore((state) => state.resultCount)
const setFilters = useHotelFilterStore((state) => state.setFilters)
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
return (
<>
<DialogTrigger>
<Button intent="secondary" size="small" theme="base">
<FilterIcon color="burgundy" />
<Button intent="secondary" size="small" theme="base" variant="icon">
<FilterIcon color="baseTextHighcontrast" />
{intl.formatMessage({ id: "Filter and sort" })}
{activeFilters.length > 0 && (
<Footnote className={styles.badge}>{activeFilters.length}</Footnote>
)}
</Button>
<ModalOverlay className={styles.overlay} isDismissable>
<Modal className={styles.modal}>
@@ -60,7 +67,9 @@ export default function FilterAndSortModal({
<div className={styles.sorter}>
<HotelSorter />
</div>
<Divider color="subtle" className="divider" />
<div className={styles.divider}>
<Divider color="subtle" />
</div>
<div className={styles.filters}>
<HotelFilter filters={filters} />
</div>
@@ -76,7 +85,6 @@ export default function FilterAndSortModal({
{ count: resultCount }
)}
</Button>
<Button
onClick={() => setFilters([])}
intent="text"

View File

@@ -26,4 +26,5 @@
align-items: center;
justify-content: center;
forced-color-adjust: none;
background: var(--UI-Input-Controls-Surface-Normal);
}

View File

@@ -20,6 +20,9 @@
.facilities ul {
margin-top: var(--Spacing-x2);
}
.facilities:last-child {
padding-bottom: 0;
}
.filter {
display: grid;

View File

@@ -8,6 +8,7 @@ import { useHotelFilterStore } from "@/stores/hotel-filters"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import Title from "@/components/TempDesignSystem/Text/Title"
import useInitializeFiltersFromUrl from "@/hooks/useInitializeFiltersFromUrl"
import FilterCheckbox from "./FilterCheckbox"
@@ -20,19 +21,9 @@ export default function HotelFilter({ className, filters }: HotelFiltersProps) {
const searchParams = useSearchParams()
const pathname = usePathname()
const toggleFilter = useHotelFilterStore((state) => state.toggleFilter)
const setFilters = useHotelFilterStore((state) => state.setFilters)
useInitializeFiltersFromUrl()
const activeFilters = useHotelFilterStore((state) => state.activeFilters)
// Initialize the filters from the URL
useEffect(() => {
const filtersFromUrl = searchParams.get("filters")
if (filtersFromUrl) {
setFilters(filtersFromUrl.split(","))
} else {
setFilters([])
}
}, [searchParams, setFilters])
// Update the URL when the filters changes
useEffect(() => {
const newSearchParams = new URLSearchParams(searchParams)
@@ -60,42 +51,46 @@ export default function HotelFilter({ className, filters }: HotelFiltersProps) {
return (
<aside className={`${styles.container} ${className}`}>
<Title as="h4">{intl.formatMessage({ id: "Filter by" })}</Title>
<div className={styles.facilities}>
<Subtitle>{intl.formatMessage({ id: "Hotel facilities" })}</Subtitle>
<ul>
{filters.facilityFilters.map((filter) => (
<li key={`li-${filter.id}`} className={styles.filter}>
<FilterCheckbox
name={filter.name}
id={filter.id.toString()}
onChange={() => toggleFilter(filter.id.toString())}
isSelected={
!!activeFilters.find((f) => f === filter.id.toString())
}
/>
</li>
))}
</ul>
</div>
<form>
<Title as="h4">{intl.formatMessage({ id: "Filter by" })}</Title>
<div className={styles.facilities}>
<Subtitle>{intl.formatMessage({ id: "Hotel facilities" })}</Subtitle>
<ul>
{filters.facilityFilters.map((filter) => (
<li key={`li-${filter.id}`} className={styles.filter}>
<FilterCheckbox
name={filter.name}
id={filter.id.toString()}
onChange={() => toggleFilter(filter.id.toString())}
isSelected={
!!activeFilters.find((f) => f === filter.id.toString())
}
/>
</li>
))}
</ul>
</div>
<div className={styles.facilities}>
<Subtitle>{intl.formatMessage({ id: "Hotel surroundings" })}</Subtitle>
<ul>
{filters.surroundingsFilters.map((filter) => (
<li key={`li-${filter.id}`} className={styles.filter}>
<FilterCheckbox
name={filter.name}
id={filter.id.toString()}
onChange={() => toggleFilter(filter.id.toString())}
isSelected={
!!activeFilters.find((f) => f === filter.id.toString())
}
/>
</li>
))}
</ul>
</div>
<div className={styles.facilities}>
<Subtitle>
{intl.formatMessage({ id: "Hotel surroundings" })}
</Subtitle>
<ul>
{filters.surroundingsFilters.map((filter) => (
<li key={`li-${filter.id}`} className={styles.filter}>
<FilterCheckbox
name={filter.name}
id={filter.id.toString()}
onChange={() => toggleFilter(filter.id.toString())}
isSelected={
!!activeFilters.find((f) => f === filter.id.toString())
}
/>
</li>
))}
</ul>
</div>
</form>
</aside>
)
}

View File

@@ -25,13 +25,20 @@ export default function MobileMapButtonContainer({
return (
<div className={styles.buttonContainer}>
<Button asChild variant="icon" intent="secondary" size="small">
<Button
asChild
theme="base"
variant="icon"
intent="secondary"
size="small"
>
<Link
href={selectHotelMap(lang)}
color="baseButtonTextOnFillNormal"
keepSearchParams
color="burgundy"
weight="bold"
>
<MapIcon color="burgundy" />
<MapIcon color="baseTextHighcontrast" />
{intl.formatMessage({ id: "See on map" })}
</Link>
</Button>

View File

@@ -23,6 +23,10 @@
height: 44px;
}
.container .listingContainer .filterContainer > button {
border: none;
}
@media (min-width: 768px) {
.container .closeButton {
display: flex;

View File

@@ -82,6 +82,11 @@
fill: var(--Base-Button-Text-On-Fill-Normal);
}
.baseTextHighcontrast,
.baseTextHighcontrast * {
fill: var(--Base-Text-High-contrast);
}
.disabled,
.disabled * {
fill: var(--Base-Text-Disabled);

View File

@@ -8,6 +8,7 @@ const config = {
baseButtonTertiaryOnFillNormal: styles.baseButtonTertiaryOnFillNormal,
baseButtonTextOnFillNormal: styles.baseButtonTextOnFillNormal,
baseIconLowContrast: styles.baseIconLowContrast,
baseTextHighcontrast: styles.baseTextHighcontrast,
black: styles.black,
blue: styles.blue,
burgundy: styles.burgundy,

View File

@@ -20,6 +20,10 @@ export default function FullView({
currentIndex,
totalImages,
}: FullViewProps) {
function handleSwipe(offset: number) {
if (offset > 30) onPrev()
if (offset < -30) onNext()
}
return (
<div className={styles.fullViewContainer}>
<Button
@@ -53,6 +57,8 @@ export default function FullView({
exit={{ opacity: 0, x: -300 }}
transition={{ duration: 0.3 }}
className={styles.fullViewImage}
drag="x"
onDragEnd={(e, info) => handleSwipe(info.offset.x)}
>
<Image
alt={image.metaData.altText}

View File

@@ -97,17 +97,40 @@ function Parking({ parking }: ParkingProps) {
const intl = useIntl()
return (
<div>
<Body>{`${intl.formatMessage({ id: parking.type })} (${parking.name})`}</Body>
<Body>
{`${intl.formatMessage({ id: parking.type })}${parking?.name ? ` (${parking.name})` : ""}`}
</Body>
<ul className={styles.list}>
<li>
{`${intl.formatMessage({
id: "Number of charging points for electric cars",
})}: ${parking.numberOfChargingSpaces}`}
</li>
<li>{`${intl.formatMessage({ id: "Parking can be reserved in advance" })}: ${parking.canMakeReservation ? intl.formatMessage({ id: "Yes" }) : intl.formatMessage({ id: "No" })}`}</li>
<li>{`${intl.formatMessage({ id: "Number of parking spots" })}: ${parking.numberOfParkingSpots}`}</li>
<li>{`${intl.formatMessage({ id: "Distance to hotel" })}: ${parking.distanceToHotel} m`}</li>
<li>{`${intl.formatMessage({ id: "Address" })}: ${parking.address}`}</li>
{parking?.numberOfChargingSpaces !== undefined && (
<li>
{intl.formatMessage(
{ id: "Number of charging points for electric cars" },
{ number: parking.numberOfChargingSpaces }
)}
</li>
)}
{parking?.canMakeReservation && (
<li>{`${intl.formatMessage({ id: "Parking can be reserved in advance" })}: ${parking.canMakeReservation ? intl.formatMessage({ id: "Yes" }) : intl.formatMessage({ id: "No" })}`}</li>
)}
{parking?.numberOfParkingSpots !== undefined && (
<li>
{intl.formatMessage(
{ id: "Number of parking spots" },
{ number: parking.numberOfParkingSpots }
)}
</li>
)}
{parking?.distanceToHotel !== undefined && (
<li>
{intl.formatMessage(
{ id: "Distance to hotel" },
{ distance: parking.distanceToHotel }
)}
</li>
)}
{parking?.address && (
<li>{`${intl.formatMessage({ id: "Address" })}: ${parking.address}`}</li>
)}
</ul>
</div>
)

View File

@@ -59,6 +59,7 @@
.dialog {
height: 100%;
outline: none;
}
.sidePeek {

View File

@@ -0,0 +1,18 @@
import { useSearchParams } from "next/navigation"
import { useEffect } from "react"
import { useHotelFilterStore } from "@/stores/hotel-filters"
export default function useInitializeFiltersFromUrl() {
const searchParams = useSearchParams()
const setFilters = useHotelFilterStore((state) => state.setFilters)
useEffect(() => {
const filtersFromUrl = searchParams.get("filters")
if (filtersFromUrl) {
setFilters(filtersFromUrl.split(","))
} else {
setFilters([])
}
}, [searchParams, setFilters])
}

View File

@@ -105,7 +105,7 @@
"Discard unsaved changes?": "Slette ændringer, der ikke er gemt?",
"Distance in km to city centre": "{number} km til centrum",
"Distance to city centre": "Afstand til centrum",
"Distance to hotel": "Afstand til hotel",
"Distance to hotel": "Afstand til hotel: {distance} m",
"Do you want to start the day with Scandics famous breakfast buffé?": "Vil du starte dagen med Scandics berømte morgenbuffet?",
"Done": "Færdig",
"Download the Scandic app": "Download Scandic-appen",
@@ -241,8 +241,8 @@
"Nordic Swan Ecolabel": "Svanemærket",
"Not found": "Ikke fundet",
"Nr night, nr adult": "{nights, number} nat, {adults, number} voksen",
"Number of charging points for electric cars": "Antal ladepunkter til elbiler",
"Number of parking spots": "Antal parkeringspladser",
"Number of charging points for electric cars": "Antal ladepunkter til elbiler: {number}",
"Number of parking spots": "Antal parkeringspladser: {number}",
"OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER",
"On your journey": "På din rejse",
"Only pay {amount} {currency}": "Betal kun {amount} {currency}",

View File

@@ -105,7 +105,7 @@
"Discard unsaved changes?": "Nicht gespeicherte Änderungen verwerfen?",
"Distance in km to city centre": "{number} km zum Stadtzentrum",
"Distance to city centre": "Entfernung zum Stadtzentrum",
"Distance to hotel": "Entfernung zum Hotel",
"Distance to hotel": "Entfernung zum Hotel: {distance} m",
"Do you want to start the day with Scandics famous breakfast buffé?": "Möchten Sie den Tag mit Scandics berühmtem Frühstücksbuffet beginnen?",
"Done": "Fertig",
"Download the Scandic app": "Laden Sie die Scandic-App herunter",
@@ -240,8 +240,8 @@
"Nordic Swan Ecolabel": "Nordic Swan Ecolabel",
"Not found": "Nicht gefunden",
"Nr night, nr adult": "{nights, number} Nacht, {adults, number} Erwachsener",
"Number of charging points for electric cars": "Anzahl der Ladestationen für Elektroautos",
"Number of parking spots": "Anzahl der Parkplätze",
"Number of charging points for electric cars": "Anzahl der Ladestationen für Elektroautos: {number}",
"Number of parking spots": "Anzahl der Parkplätze: {number}",
"OTHER PAYMENT METHODS": "ANDERE BEZAHLMETHODE",
"On your journey": "Auf deiner Reise",
"Only pay {amount} {currency}": "Nur bezahlen {amount} {currency}",

View File

@@ -113,7 +113,7 @@
"Discard unsaved changes?": "Discard unsaved changes?",
"Distance in km to city centre": "{number} km to city centre",
"Distance to city centre": "Distance to city centre",
"Distance to hotel": "Distance to hotel",
"Distance to hotel": "Distance to hotel: {distance} m",
"Do you want to start the day with Scandics famous breakfast buffé?": "Do you want to start the day with Scandics famous breakfast buffé?",
"Done": "Done",
"Download invoice": "Download invoice",
@@ -258,8 +258,8 @@
"Nordic Swan Ecolabel": "Nordic Swan Ecolabel",
"Not found": "Not found",
"Nr night, nr adult": "{nights, number} night, {adults, number} adult",
"Number of charging points for electric cars": "Number of charging points for electric cars",
"Number of parking spots": "Number of parking spots",
"Number of charging points for electric cars": "Number of charging points for electric cars: {number}",
"Number of parking spots": "Number of parking spots: {number}",
"OTHER PAYMENT METHODS": "OTHER PAYMENT METHODS",
"On your journey": "On your journey",
"Only pay {amount} {currency}": "Only pay {amount} {currency}",

View File

@@ -105,7 +105,7 @@
"Discard unsaved changes?": "Hylkäätkö tallentamattomat muutokset?",
"Distance in km to city centre": "{number} km Etäisyys kaupunkiin",
"Distance to city centre": "Etäisyys kaupungin keskustaan",
"Distance to hotel": "Etäisyys hotelliin",
"Distance to hotel": "Etäisyys hotelliin: {distance} m",
"Do you want to start the day with Scandics famous breakfast buffé?": "Haluatko aloittaa päiväsi Scandicsin kuuluisalla aamiaisbuffella?",
"Done": "Valmis",
"Download the Scandic app": "Lataa Scandic-sovellus",
@@ -241,8 +241,8 @@
"Nordic Swan Ecolabel": "Ympäristömerkki Miljömärkt",
"Not found": "Ei löydetty",
"Nr night, nr adult": "{nights, number} yö, {adults, number} aikuinen",
"Number of charging points for electric cars": "Sähköautojen latauspisteiden määrä",
"Number of parking spots": "Pysäköintipaikkojen määrä",
"Number of charging points for electric cars": "Sähköautojen latauspisteiden määrä: {number}",
"Number of parking spots": "Pysäköintipaikkojen määrä: {number}",
"OTHER PAYMENT METHODS": "MUISE KORT",
"On your journey": "Matkallasi",
"Only pay {amount} {currency}": "Vain maksaa {amount} {currency}",

View File

@@ -104,7 +104,7 @@
"Discard unsaved changes?": "Forkaste endringer som ikke er lagret?",
"Distance in km to city centre": "{number} km til sentrum",
"Distance to city centre": "Avstand til sentrum",
"Distance to hotel": "Avstand til hotell",
"Distance to hotel": "Avstand til hotell: {distance} m",
"Do you want to start the day with Scandics famous breakfast buffé?": "Vil du starte dagen med Scandics berømte frokostbuffé?",
"Done": "Ferdig",
"Download the Scandic app": "Last ned Scandic-appen",
@@ -239,8 +239,8 @@
"Nordic Swan Ecolabel": "Svanemerket",
"Not found": "Ikke funnet",
"Nr night, nr adult": "{nights, number} natt, {adults, number} voksen",
"Number of charging points for electric cars": "Antall ladepunkter for elbiler",
"Number of parking spots": "Antall parkeringsplasser",
"Number of charging points for electric cars": "Antall ladepunkter for elbiler: {number}",
"Number of parking spots": "Antall parkeringsplasser: {number}",
"OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER",
"On your journey": "På reisen din",
"Only pay {amount} {currency}": "Bare betal {amount} {currency}",

View File

@@ -104,7 +104,7 @@
"Discard unsaved changes?": "Vill du ignorera ändringar som inte har sparats?",
"Distance in km to city centre": "{number} km till centrum",
"Distance to city centre": "Avstånd till centrum",
"Distance to hotel": "Avstånd till hotell",
"Distance to hotel": "Avstånd till hotell: {distance} m",
"Do you want to start the day with Scandics famous breakfast buffé?": "Vill du starta dagen med Scandics berömda frukostbuffé?",
"Done": "Klar",
"Download the Scandic app": "Ladda ner Scandic-appen",
@@ -239,8 +239,8 @@
"Nordic Swan Ecolabel": "Svanenmärkt",
"Not found": "Hittades inte",
"Nr night, nr adult": "{nights, number} natt, {adults, number} vuxen",
"Number of charging points for electric cars": "Antal laddplatser för elbilar",
"Number of parking spots": "Antal parkeringsplatser",
"Number of charging points for electric cars": "Antal laddplatser för elbilar: {number}",
"Number of parking spots": "Antal parkeringsplatser: {number}",
"OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER",
"On your journey": "På din resa",
"Only pay {amount} {currency}": "Betala endast {amount} {currency}",

View File

@@ -21,7 +21,6 @@ export const useHotelFilterStore = create<HotelFilterState>((set) => ({
: [...state.activeFilters, filterId]
return { activeFilters: newFilters }
}),
resultCount: 0,
setResultCount: (count) => set({ resultCount: count }),
}))