Merged in feat/SW-338-filter-sort-ui (pull request #896)

Feat/SW-338 design filters and sorting on select hotel

* feat(SW-338): design hotel sort dropdown

* Translations

* feat(SW-338): style filters

* Bold

* Import type

* Translations

* Rename and add translation

* Rename translation


Approved-by: Bianca Widstam
This commit is contained in:
Niclas Edenvin
2024-11-15 07:18:30 +00:00
parent a6a0b0cf15
commit 18d40120b9
20 changed files with 159 additions and 71 deletions

View File

@@ -46,7 +46,7 @@ export default async function HotelListingItem({
</div>
<Caption color="uiTextPlaceholder">
{intl.formatMessage(
{ id: "Distance to city centre" },
{ id: "Distance in km to city centre" },
{ number: distanceToCentre }
)}
</Caption>

View File

@@ -25,7 +25,7 @@ export default async function IntroSection({
const { streetAddress, city } = address
const { distanceToCentre } = location
const formattedDistanceText = intl.formatMessage(
{ id: "Distance to city centre" },
{ id: "Distance in km to city centre" },
{ number: distanceToCentre }
)
const lang = getLang()

View File

@@ -104,7 +104,7 @@ export default function HotelCard({
</div>
<Caption color="uiTextPlaceholder">
{intl.formatMessage(
{ id: "Distance to city centre" },
{ id: "Distance in km to city centre" },
{ number: hotelData.location.distanceToCentre }
)}
</Caption>

View File

@@ -31,7 +31,7 @@ export default function HotelSelectionHeader({
</div>
<Caption color="textMediumContrast">
{intl.formatMessage(
{ id: "Distance to city centre" },
{ id: "Distance in km to city centre" },
{ number: hotel.location.distanceToCentre }
)}
</Caption>

View File

@@ -3,16 +3,30 @@
display: none;
}
.container form {
display: flex;
flex-direction: column;
gap: var(--Spacing-x3);
}
.facilities {
font-family: var(--typography-Body-Bold-fontFamily);
margin-bottom: var(--Spacing-x3);
padding-bottom: var(--Spacing-x3);
}
.facilities:first-of-type {
border-bottom: 1px solid var(--Base-Border-Subtle);
}
.facilities ul {
margin-top: var(--Spacing-x2);
}
.filter {
display: grid;
grid-template-columns: repeat(2, minmax(min-content, max-content));
gap: var(--Spacing-x-one-and-half);
margin-bottom: var(--Spacing-x-one-and-half);
margin-bottom: var(--Spacing-x1);
align-items: center;
}

View File

@@ -2,26 +2,29 @@
import { usePathname, useSearchParams } from "next/navigation"
import { useCallback, useEffect } from "react"
import { useForm } from "react-hook-form"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import Title from "@/components/TempDesignSystem/Text/Title"
import styles from "./hotelFilter.module.css"
import { HotelFiltersProps } from "@/types/components/hotelReservation/selectHotel/hotelFilters"
import type { HotelFiltersProps } from "@/types/components/hotelReservation/selectHotel/hotelFilters"
export default function HotelFilter({ filters }: HotelFiltersProps) {
const intl = useIntl()
const searchParams = useSearchParams()
const pathname = usePathname()
const { watch, handleSubmit, getValues, register } = useForm<
Record<string, boolean | undefined>
>({
const methods = useForm<Record<string, boolean | undefined>>({
defaultValues: searchParams
?.get("filters")
?.split(",")
.reduce((acc, curr) => ({ ...acc, [curr]: true }), {}),
})
const { watch, handleSubmit, getValues, register } = methods
const submitFilter = useCallback(() => {
const newSearchParams = new URLSearchParams(searchParams)
@@ -52,41 +55,36 @@ export default function HotelFilter({ filters }: HotelFiltersProps) {
return (
<aside className={styles.container}>
<div className={styles.facilities}>
<FormProvider {...methods}>
<form onSubmit={handleSubmit(submitFilter)}>
{intl.formatMessage({ id: "Hotel facilities" })}
<ul>
{filters.facilityFilters.map((filter) => (
<li key={`li-${filter.id}`} className={styles.filter}>
<input
type="checkbox"
id={`checkbox-${filter.id.toString()}`}
{...register(filter.id.toString())}
/>
<label htmlFor={`checkbox-${filter.id.toString()}`}>
{filter.name}
</label>
</li>
))}
</ul>
<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}>
<Checkbox name={filter.id.toString()}>{filter.name}</Checkbox>
</li>
))}
</ul>
</div>
{intl.formatMessage({ id: "Hotel surroundings" })}
<ul>
{filters.surroundingsFilters.map((filter) => (
<li key={`li-${filter.id}`} className={styles.filter}>
<input
type="checkbox"
id={`checkbox-${filter.id.toString()}`}
{...register(filter.id.toString())}
/>
<label htmlFor={`checkbox-${filter.id.toString()}`}>
{filter.name}
</label>
</li>
))}
</ul>
<div className={styles.facilities}>
<Subtitle>
{intl.formatMessage({ id: "Hotel surroundings" })}
</Subtitle>
<ul>
{filters.surroundingsFilters.map((filter) => (
<li key={`li-${filter.id}`} className={styles.filter}>
<Checkbox name={filter.id.toString()}>{filter.name}</Checkbox>
</li>
))}
</ul>
</div>
</form>
</div>
</FormProvider>
</aside>
)
}

View File

@@ -0,0 +1,3 @@
.container {
width: 339px;
}

View File

@@ -6,24 +6,19 @@ import { useIntl } from "react-intl"
import Select from "@/components/TempDesignSystem/Select"
import styles from "./hotelSorter.module.css"
import {
type SortItem,
SortOrder,
} from "@/types/components/hotelReservation/selectHotel/hotelSorter"
const sortItems: SortItem[] = [
{ label: "Distance", value: SortOrder.Distance },
{ label: "Name", value: SortOrder.Name },
{ label: "Price", value: SortOrder.Price },
{ label: "TripAdvisor rating", value: SortOrder.TripAdvisorRating },
]
export const DEFAULT_SORT = SortOrder.Distance
export default function HotelSorter() {
const searchParams = useSearchParams()
const pathname = usePathname()
const i18n = useIntl()
const intl = useIntl()
const onSelect = useCallback(
(value: string | number) => {
@@ -43,14 +38,30 @@ export default function HotelSorter() {
},
[pathname, searchParams]
)
const sortItems: SortItem[] = [
{
label: intl.formatMessage({ id: "Distance to city center" }),
value: SortOrder.Distance,
},
{ label: intl.formatMessage({ id: "Name" }), value: SortOrder.Name },
{ label: intl.formatMessage({ id: "Price" }), value: SortOrder.Price },
{
label: intl.formatMessage({ id: "TripAdvisor rating" }),
value: SortOrder.TripAdvisorRating,
},
]
return (
<Select
items={sortItems}
label={i18n.formatMessage({ id: "Sort by" })}
name="sort"
showRadioButton
onSelect={onSelect}
/>
<div className={styles.container}>
<Select
items={sortItems}
defaultSelectedKey={searchParams.get("sort") ?? DEFAULT_SORT}
label={intl.formatMessage({ id: "Sort by" })}
name="sort"
showRadioButton
discreet
onSelect={onSelect}
/>
</div>
)
}

View File

@@ -18,6 +18,12 @@ span.regular {
order: 1;
}
span.discreet {
color: var(--Base-Text-High-contrast);
font-weight: 500;
order: unset;
}
input:active ~ .label,
input:not(:placeholder-shown) ~ .label {
display: block;

View File

@@ -7,6 +7,7 @@ export const labelVariants = cva(styles.label, {
size: {
small: styles.small,
regular: styles.regular,
discreet: styles.discreet,
},
},
defaultVariants: {

View File

@@ -2,10 +2,12 @@ import { ChevronDownIcon } from "@/components/Icons"
import styles from "./chevron.module.css"
export default function SelectChevron() {
import type { IconProps } from "@/types/components/icon"
export default function SelectChevron(props: IconProps) {
return (
<span aria-hidden="true" className={styles.chevron}>
<ChevronDownIcon color="grey80" width={20} height={20} />
<ChevronDownIcon color="grey80" width={20} height={20} {...props} />
</span>
)
}

View File

@@ -36,6 +36,7 @@ export default function Select({
value,
maxHeight,
showRadioButton = false,
discreet = false,
}: SelectProps) {
const [rootDiv, setRootDiv] = useState<SelectPortalContainer>(undefined)
@@ -53,7 +54,7 @@ export default function Select({
<div className={styles.container} ref={setRef}>
<ReactAriaSelect
aria-label={ariaLabel}
className={styles.select}
className={`${styles.select} ${discreet && styles.discreet}`}
defaultSelectedKey={defaultSelectedKey}
name={name}
onSelectionChange={handleOnSelect}
@@ -63,12 +64,15 @@ export default function Select({
<Body asChild fontOnly>
<Button className={styles.input} data-testid={name}>
<span className={styles.inputContentWrapper} tabIndex={tabIndex}>
<Label required={required} size="small">
<Label required={required} size={discreet ? "discreet" : "small"}>
{label}
{discreet && `:`}
</Label>
<SelectValue />
</span>
<SelectChevron />
<SelectChevron
{...(discreet ? { color: "baseButtonTextOnFillNormal" } : {})}
/>
</Button>
</Body>
<Body asChild fontOnly>

View File

@@ -10,11 +10,29 @@
gap: var(--Spacing-x-half);
}
.select[data-focused="true"] {
.select[data-focused="true"],
.select[data-focused="true"].discreet {
border: 1px solid var(--Scandic-Blue-90);
outline: none;
}
.select.discreet {
border: 1px solid transparent;
}
.select.discreet .input {
background-color: unset;
color: var(--Base-Text-High-contrast);
gap: var(--Spacing-x1);
}
.select.discreet .inputContentWrapper {
align-items: center;
justify-content: flex-end;
flex-direction: row;
font-weight: 500;
}
.input {
align-items: center;
background-color: var(--UI-Opacity-White-100);
@@ -72,7 +90,7 @@
}
.listBoxItem.showRadioButton:before {
display: flex;
flex-shrink: 0;
content: "";
margin-right: var(--Spacing-x-one-and-half);
background-color: white;

View File

@@ -11,6 +11,7 @@ export interface SelectProps
value?: string | number
maxHeight?: number
showRadioButton?: boolean
discreet?: boolean
}
export type SelectPortalContainer = HTMLDivElement | undefined

View File

@@ -98,7 +98,8 @@
"Disabled booking options text": "Koder, checks og bonusnætter er endnu ikke tilgængelige på den nye hjemmeside.",
"Discard changes": "Kassér ændringer",
"Discard unsaved changes?": "Slette ændringer, der ikke er gemt?",
"Distance to city centre": "{number} km til centrum",
"Distance in km to city centre": "{number} km til centrum",
"Distance to city centre": "Afstand til centrum",
"Distance to hotel": "Afstand til hotel",
"Do you want to start the day with Scandics famous breakfast buffé?": "Vil du starte dagen med Scandics berømte morgenbuffet?",
"Done": "Færdig",
@@ -120,6 +121,7 @@
"Failed to delete credit card, please try again later.": "Kunne ikke slette kreditkort. Prøv venligst igen senere.",
"Fair": "Messe",
"Filter": "Filter",
"Filter by": "Filtrer efter",
"Find booking": "Find booking",
"Find hotels": "Find hotel",
"First name": "Fornavn",
@@ -204,6 +206,7 @@
"My pages menu": "Mine sider menu",
"My payment cards": "Mine betalingskort",
"My wishes": "Mine ønsker",
"Name": "Navn",
"Nearby": "I nærheden",
"Nearby companies": "Nærliggende virksomheder",
"New password": "Nyt kodeord",
@@ -258,6 +261,7 @@
"Practical information": "Praktisk information",
"Previous": "Forudgående",
"Previous victories": "Tidligere sejre",
"Price": "Pris",
"Price details": "Prisoplysninger",
"Proceed to login": "Fortsæt til login",
"Proceed to payment method": "Fortsæt til betalingsmetode",
@@ -342,6 +346,7 @@
"Transaction date": "Overførselsdato",
"Transactions": "Transaktioner",
"Transportations": "Transport",
"TripAdvisor rating": "TripAdvisor vurdering",
"Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)",
"Type of bed": "Sengtype",
"Type of room": "Værelsestype",

View File

@@ -98,7 +98,8 @@
"Disabled booking options text": "Codes, Schecks und Bonusnächte sind auf der neuen Website noch nicht verfügbar.",
"Discard changes": "Änderungen verwerfen",
"Discard unsaved changes?": "Nicht gespeicherte Änderungen verwerfen?",
"Distance to city centre": "{number} km zum Stadtzentrum",
"Distance in km to city centre": "{number} km zum Stadtzentrum",
"Distance to city centre": "Entfernung zum Stadtzentrum",
"Distance to hotel": "Entfernung zum Hotel",
"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",
@@ -120,6 +121,7 @@
"Failed to delete credit card, please try again later.": "Kreditkarte konnte nicht gelöscht werden. Bitte versuchen Sie es später noch einmal.",
"Fair": "Messe",
"Filter": "Filter",
"Filter by": "Filtern nach",
"Find booking": "Buchung finden",
"Find hotels": "Hotels finden",
"First name": "Vorname",
@@ -202,6 +204,7 @@
"My pages menu": "Meine Seite Menü",
"My payment cards": "Meine Zahlungskarten",
"My wishes": "Meine Wünsche",
"Name": "Name",
"Nearby": "In der Nähe",
"Nearby companies": "Nahe gelegene Unternehmen",
"New password": "Neues Kennwort",
@@ -256,6 +259,7 @@
"Practical information": "Praktische Informationen",
"Previous": "Früher",
"Previous victories": "Bisherige Siege",
"Price": "Preis",
"Price details": "Preisdetails",
"Proceed to login": "Weiter zum Login",
"Proceed to payment method": "Weiter zur Zahlungsmethode",
@@ -341,6 +345,7 @@
"Transaction date": "Transaktionsdatum",
"Transactions": "Transaktionen",
"Transportations": "Transportmittel",
"TripAdvisor rating": "TripAdvisor-Bewertung",
"Tripadvisor reviews": "{rating} ({count} Bewertungen auf Tripadvisor)",
"Type of bed": "Bettentyp",
"Type of room": "Zimmerart",

View File

@@ -106,7 +106,8 @@
"Disabled booking options text": "Codes, cheques and reward nights aren't available on the new website yet.",
"Discard changes": "Discard changes",
"Discard unsaved changes?": "Discard unsaved changes?",
"Distance to city centre": "{number} km to city centre",
"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",
"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",
@@ -129,6 +130,7 @@
"Failed to delete credit card, please try again later.": "Failed to delete credit card, please try again later.",
"Fair": "Fair",
"Filter": "Filter",
"Filter by": "Filter by",
"Find booking": "Find booking",
"Find hotels": "Find hotels",
"First name": "First name",
@@ -221,6 +223,7 @@
"My pages menu": "My pages menu",
"My payment cards": "My payment cards",
"My wishes": "My wishes",
"Name": "Name",
"Nearby": "Nearby",
"Nearby companies": "Nearby companies",
"New password": "New password",
@@ -279,6 +282,7 @@
"Practical information": "Practial information",
"Previous": "Previous",
"Previous victories": "Previous victories",
"Price": "Price",
"Price details": "Price details",
"Price excl VAT": "Price excl VAT",
"Price incl VAT": "Price incl VAT",
@@ -371,6 +375,7 @@
"Transaction date": "Transaction date",
"Transactions": "Transactions",
"Transportations": "Transportations",
"TripAdvisor rating": "TripAdvisor rating",
"Tripadvisor reviews": "{rating} ({count} reviews on Tripadvisor)",
"Type of bed": "Type of bed",
"Type of room": "Type of room",

View File

@@ -98,7 +98,8 @@
"Disabled booking options text": "Koodit, sekit ja palkintoillat eivät ole vielä saatavilla uudella verkkosivustolla.",
"Discard changes": "Hylkää muutokset",
"Discard unsaved changes?": "Hylkäätkö tallentamattomat muutokset?",
"Distance to city centre": "{number} km Etäisyys kaupunkiin",
"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",
"Do you want to start the day with Scandics famous breakfast buffé?": "Haluatko aloittaa päiväsi Scandicsin kuuluisalla aamiaisbuffella?",
"Done": "Valmis",
@@ -120,6 +121,7 @@
"Failed to delete credit card, please try again later.": "Luottokortin poistaminen epäonnistui, yritä myöhemmin uudelleen.",
"Fair": "Messukeskus",
"Filter": "Suodatin",
"Filter by": "Suodatusperuste",
"Find booking": "Etsi varaus",
"Find hotels": "Etsi hotelleja",
"First name": "Etunimi",
@@ -204,6 +206,7 @@
"My pages menu": "Omat sivut -valikko",
"My payment cards": "Minun maksukortit",
"My wishes": "Toiveeni",
"Name": "Nimi",
"Nearby": "Lähistöllä",
"Nearby companies": "Läheiset yritykset",
"New password": "Uusi salasana",
@@ -258,6 +261,7 @@
"Practical information": "Käytännön tietoa",
"Previous": "Aikaisempi",
"Previous victories": "Edelliset voitot",
"Price": "Hinta",
"Price details": "Hintatiedot",
"Proceed to login": "Jatka kirjautumiseen",
"Proceed to payment method": "Siirry maksutavalle",
@@ -343,6 +347,7 @@
"Transaction date": "Tapahtuman päivämäärä",
"Transactions": "Tapahtumat",
"Transportations": "Kuljetukset",
"TripAdvisor rating": "TripAdvisor-luokitus",
"Tripadvisor reviews": "{rating} ({count} arvostelua TripAdvisorissa)",
"Type of bed": "Vuodetyyppi",
"Type of room": "Huonetyyppi",

View File

@@ -97,7 +97,8 @@
"Disabled booking options text": "Koder, checks og belønningsnætter er enda ikke tilgjengelige på den nye nettsiden.",
"Discard changes": "Forkaste endringer",
"Discard unsaved changes?": "Forkaste endringer som ikke er lagret?",
"Distance to city centre": "{number} km til sentrum",
"Distance in km to city centre": "{number} km til sentrum",
"Distance to city centre": "Avstand til sentrum",
"Distance to hotel": "Avstand til hotell",
"Do you want to start the day with Scandics famous breakfast buffé?": "Vil du starte dagen med Scandics berømte frokostbuffé?",
"Done": "Ferdig",
@@ -119,6 +120,7 @@
"Failed to delete credit card, please try again later.": "Kunne ikke slette kredittkortet, prøv igjen senere.",
"Fair": "Messe",
"Filter": "Filter",
"Filter by": "Filtrer etter",
"Find booking": "Finn booking",
"Find hotels": "Finn hotell",
"First name": "Fornavn",
@@ -202,6 +204,7 @@
"My pages menu": "Mine sider-menyen",
"My payment cards": "Mine betalingskort",
"My wishes": "Mine ønsker",
"Name": "Navn",
"Nearby": "I nærheten",
"Nearby companies": "Nærliggende selskaper",
"New password": "Nytt passord",
@@ -256,6 +259,7 @@
"Practical information": "Praktisk informasjon",
"Previous": "Tidligere",
"Previous victories": "Tidligere seire",
"Price": "Pris",
"Price details": "Prisdetaljer",
"Proceed to login": "Fortsett til innlogging",
"Proceed to payment method": "Fortsett til betalingsmetode",
@@ -340,6 +344,7 @@
"Transaction date": "Transaksjonsdato",
"Transactions": "Transaksjoner",
"Transportations": "Transport",
"TripAdvisor rating": "TripAdvisor vurdering",
"Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)",
"Type of bed": "Sengtype",
"Type of room": "Romtype",

View File

@@ -97,7 +97,8 @@
"Disabled booking options text": "Koder, bonuscheckar och belöningsnätter är inte tillgängliga på den nya webbplatsen än.",
"Discard changes": "Ignorera ändringar",
"Discard unsaved changes?": "Vill du ignorera ändringar som inte har sparats?",
"Distance to city centre": "{number} km till centrum",
"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",
"Do you want to start the day with Scandics famous breakfast buffé?": "Vill du starta dagen med Scandics berömda frukostbuffé?",
"Done": "Klar",
@@ -119,6 +120,7 @@
"Failed to delete credit card, please try again later.": "Det gick inte att ta bort kreditkortet, försök igen senare.",
"Fair": "Mässa",
"Filter": "Filter",
"Filter by": "Filtrera på",
"Find booking": "Hitta bokning",
"Find hotels": "Hitta hotell",
"First name": "Förnamn",
@@ -202,6 +204,7 @@
"My pages menu": "Mina sidor meny",
"My payment cards": "Mina betalningskort",
"My wishes": "Mina önskningar",
"Name": "Namn",
"Nearby": "I närheten",
"Nearby companies": "Närliggande företag",
"New password": "Nytt lösenord",
@@ -256,6 +259,7 @@
"Practical information": "Praktisk information",
"Previous": "Föregående",
"Previous victories": "Tidigare segrar",
"Price": "Pris",
"Price details": "Prisdetaljer",
"Proceed to login": "Fortsätt till inloggning",
"Proceed to payment method": "Gå vidare till betalningsmetod",
@@ -340,6 +344,7 @@
"Transaction date": "Transaktionsdatum",
"Transactions": "Transaktioner",
"Transportations": "Transport",
"TripAdvisor rating": "TripAdvisor-betyg",
"Tripadvisor reviews": "{rating} ({count} recensioner på Tripadvisor)",
"Type of bed": "Sängtyp",
"Type of room": "Rumstyp",