Merged in feat/SW-1738-mobile-tablet-design-filtering (pull request #1472)

feat/SW-1738 mobile tablet design filtering

* feat(SW-1738): add top bar

* feat(SW-1738): set list type

* feat(SW-1738): mobile design for filter modal

* feat(SW-1738): change button component

* feat(SW-1738): hide divider

* feat(SW-1738): set list type

* feat(SW-1738): remove background color from checkbox wrapper in mobile

* feat(SW-1738): remove props from filter and sort component


Approved-by: Erik Tiekstra
Approved-by: Matilda Landström
This commit is contained in:
Fredrik Thorsson
2025-03-05 12:32:53 +00:00
parent 76c20df8e8
commit 4ab586660a
15 changed files with 157 additions and 77 deletions

View File

@@ -26,13 +26,10 @@ export default function CityListing() {
threshold: 300,
elementRef: scrollRef,
})
const { activeCities, filters, sortItems, isLoading } =
useDestinationDataStore((state) => ({
activeCities: state.activeCities,
filters: state.allFilters,
sortItems: state.sortItems,
isLoading: state.isLoading,
}))
const { activeCities, isLoading } = useDestinationDataStore((state) => ({
activeCities: state.activeCities,
isLoading: state.isLoading,
}))
const [allCitiesVisible, setAllCitiesVisible] = useState(
activeCities.length <= 5
)
@@ -57,11 +54,7 @@ export default function CityListing() {
{ count: activeCities.length }
)}
</Subtitle>
<DestinationFilterAndSort
filters={filters}
sortItems={sortItems}
listType="city"
/>
<DestinationFilterAndSort listType="city" />
</div>
{activeCities.length === 0 ? (
<Alert

View File

@@ -18,13 +18,10 @@
}
@media screen and (max-width: 949px) {
.hotelListWrapper {
overflow-y: scroll;
}
.hotelList {
flex-direction: row;
align-items: end;
overflow-x: scroll;
}
.header {

View File

@@ -25,13 +25,10 @@ export default function HotelList() {
const map = useMap()
const coreLib = useMapsLibrary("core")
const [visibleHotels, setVisibleHotels] = useState<HotelDataWithUrl[]>([])
const { filters, sortItems, activeHotels, isLoading } =
useDestinationDataStore((state) => ({
filters: state.allFilters,
sortItems: state.sortItems,
activeHotels: state.activeHotels,
isLoading: state.isLoading,
}))
const { activeHotels, isLoading } = useDestinationDataStore((state) => ({
activeHotels: state.activeHotels,
isLoading: state.isLoading,
}))
const debouncedUpdateVisibleHotels = useMemo(
() =>
@@ -67,11 +64,7 @@ export default function HotelList() {
{ count: visibleHotels.length }
)}
</Body>
<DestinationFilterAndSort
filters={filters}
sortItems={sortItems}
listType="hotel"
/>
<DestinationFilterAndSort listType="hotel" />
</div>
{activeHotels.length === 0 ? (
<Alert

View File

@@ -65,7 +65,7 @@
justify-self: stretch;
}
@media screen and (max-width: 950px) {
@media screen and (max-width: 949px) {
.hotelListItem {
width: 360px;
min-height: 150px;
@@ -76,6 +76,11 @@
height: 100%;
}
.tripAdvisor {
top: 12px;
left: 12px;
}
.content {
padding: var(--Spacing-x-one-and-half);
gap: var(--Spacing-x1);

View File

@@ -18,13 +18,10 @@
}
@media screen and (max-width: 949px) {
.cityListWrapper {
overflow-x: scroll;
}
.cityList {
flex-direction: row;
align-items: end;
overflow-x: scroll;
}
.header {

View File

@@ -17,13 +17,10 @@ import { AlertTypeEnum } from "@/types/enums/alert"
export default function CityList() {
const intl = useIntl()
const { filters, sortItems, activeCities, isLoading } =
useDestinationDataStore((state) => ({
filters: state.allFilters,
sortItems: state.sortItems,
activeCities: state.activeCities,
isLoading: state.isLoading,
}))
const { activeCities, isLoading } = useDestinationDataStore((state) => ({
activeCities: state.activeCities,
isLoading: state.isLoading,
}))
return isLoading ? (
<CityListSkeleton />
@@ -36,11 +33,7 @@ export default function CityList() {
{ count: activeCities.length }
)}
</Body>
<DestinationFilterAndSort
filters={filters}
sortItems={sortItems}
listType="city"
/>
<DestinationFilterAndSort listType="city" />
</div>
{activeCities.length === 0 ? (
<Alert

View File

@@ -26,13 +26,10 @@ export default function HotelListing() {
threshold: 300,
elementRef: scrollRef,
})
const { activeHotels, filters, sortItems, isLoading } =
useDestinationDataStore((state) => ({
activeHotels: state.activeHotels,
filters: state.allFilters,
sortItems: state.sortItems,
isLoading: state.isLoading,
}))
const { activeHotels, isLoading } = useDestinationDataStore((state) => ({
activeHotels: state.activeHotels,
isLoading: state.isLoading,
}))
const [allHotelsVisible, setAllHotelsVisible] = useState(
activeHotels.length <= 5
)
@@ -57,11 +54,7 @@ export default function HotelListing() {
{ count: activeHotels.length }
)}
</Subtitle>
<DestinationFilterAndSort
filters={filters}
sortItems={sortItems}
listType="hotel"
/>
<DestinationFilterAndSort listType="hotel" />
</div>
{activeHotels.length === 0 ? (
<Alert

View File

@@ -40,9 +40,7 @@
}
.closeButton {
pointer-events: initial;
box-shadow: var(--button-box-shadow);
gap: var(--Spacing-x-half);
display: none !important;
}
.zoomButton {
@@ -65,4 +63,11 @@
display: flex;
flex-direction: row-reverse;
}
.closeButton {
display: flex !important;
pointer-events: initial;
box-shadow: var(--button-box-shadow);
gap: var(--Spacing-x-half);
}
}

View File

@@ -10,7 +10,11 @@ import {
useState,
} from "react"
import { Dialog, Modal } from "react-aria-components"
import { useIntl } from "react-intl"
import DestinationFilterAndSort from "@/components/DestinationFilterAndSort"
import { ChevronLeftSmallIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import { debounce } from "@/utils/debounce"
import DynamicMap from "./DynamicMap"
@@ -49,6 +53,8 @@ export default function Map({
const [mapHeight, setMapHeight] = useState("0px")
const [scrollHeightWhenOpened, setScrollHeightWhenOpened] = useState(0)
const intl = useIntl()
const markers = getHotelMapMarkers(hotels)
const geoJson = mapMarkerDataToGeoJson(markers)
const defaultCoordinates = defaultLocation
@@ -122,6 +128,20 @@ export default function Map({
}
aria-label={"Mapview"}
>
<div className={styles.mobileNavigation}>
<Button
intent="text"
theme="base"
variant="icon"
onClick={handleClose}
>
<ChevronLeftSmallIcon color="baseTextHighcontrast" />
{intl.formatMessage({ id: "Back" })}
</Button>
<DestinationFilterAndSort
listType={pageType === "city" ? "hotel" : "city"}
/>
</div>
<aside className={styles.sidebar}>{children}</aside>
<DynamicMap
markers={markers}

View File

@@ -34,7 +34,22 @@
gap: var(--Spacing-x-half);
}
.mobileNavigation {
display: none;
}
@media screen and (max-width: 949px) {
.dialog {
flex-direction: column;
}
.mobileNavigation {
display: flex;
padding: var(--Spacing-x2);
justify-content: space-between;
background-color: var(--Surface-Primary-OnSurface-Default);
}
.sidebar {
position: absolute;
max-width: none;

View File

@@ -29,3 +29,13 @@
border-color: var(--UI-Input-Controls-Fill-Selected);
background-color: var(--UI-Input-Controls-Fill-Selected);
}
@media screen and (max-width: 767px) {
.checkboxWrapper:hover {
background-color: transparent;
}
.checkboxWrapper[data-selected] {
background-color: transparent;
}
}

View File

@@ -25,3 +25,13 @@
gap: var(--Spacing-x1) var(--Spacing-x2);
margin: var(--Spacing-x3) 0;
}
@media screen and (max-width: 767px) {
.list {
grid-template-columns: 1fr;
}
.list label {
padding-left: 0;
}
}

View File

@@ -41,7 +41,7 @@ export default function Filter({ filters }: FilterProps) {
</Subtitle>
<ul className={styles.list}>
{facilityFilters.map((filter) => (
<li key={`filter-${filter.slug}`} className={styles.item}>
<li key={`filter-${filter.slug}`}>
<Checkbox
name={filter.name}
value={filter.slug}
@@ -59,7 +59,7 @@ export default function Filter({ filters }: FilterProps) {
</Subtitle>
<ul className={styles.list}>
{surroundingsFilters.map((filter) => (
<li key={`filter-${filter.slug}`} className={styles.item}>
<li key={`filter-${filter.slug}`}>
<Checkbox
name={filter.name}
value={filter.slug}

View File

@@ -8,6 +8,7 @@
align-items: center;
z-index: var(--default-modal-overlay-z-index);
}
.dialog {
width: min(80dvw, 960px);
border-radius: var(--Corner-radius-Large);
@@ -65,3 +66,53 @@
padding: var(--Spacing-x2) var(--Spacing-x4);
border-top: 1px solid var(--Base-Border-Subtle);
}
.close {
background: none;
border: none;
cursor: pointer;
padding: 0;
}
@media screen and (max-width: 767px) {
.overlay {
height: var(--visual-viewport-height);
}
.dialog {
display: flex;
flex-direction: column;
height: 100dvh;
width: 100vw;
border-radius: 0;
}
.header {
display: flex;
justify-content: flex-end;
border-bottom: none;
padding: var(--Spacing-x3) var(--Spacing-x2);
}
.title,
.divider {
display: none;
}
.content {
height: 100%;
padding: 0 var(--Spacing-x2) var(--Spacing-x3);
overflow-y: scroll;
}
.alertWrapper:not(:empty) {
padding: var(--Spacing-x3) var(--Spacing-x2) 0;
}
.footer {
flex-direction: column-reverse;
gap: var(--Spacing-x3);
padding: var(--Spacing-x3) var(--Spacing-x2);
margin-top: auto;
}
}

View File

@@ -23,26 +23,20 @@ import Sort from "./Sort"
import styles from "./destinationFilterAndSort.module.css"
import type {
CategorizedFilters,
SortItem,
} from "@/types/components/destinationFilterAndSort"
import { AlertTypeEnum } from "@/types/enums/alert"
interface HotelFilterAndSortProps {
filters: CategorizedFilters
sortItems: SortItem[]
listType: "city" | "hotel"
}
export default function DestinationFilterAndSort({
filters,
sortItems,
listType,
}: HotelFilterAndSortProps) {
const intl = useIntl()
const router = useRouter()
const {
filters,
sortItems,
pendingFilters,
pendingSort,
defaultSort,
@@ -53,6 +47,8 @@ export default function DestinationFilterAndSort({
resetPendingValues,
setIsLoading,
} = useDestinationDataStore((state) => ({
filters: state.allFilters,
sortItems: state.sortItems,
pendingFilters: state.pendingFilters,
pendingSort: state.pendingSort,
basePath: state.basePathnameWithoutFilters,
@@ -142,13 +138,17 @@ export default function DestinationFilterAndSort({
>
<h3>{intl.formatMessage({ id: "Filter and sort" })}</h3>
</Subtitle>
<Button onClick={close} variant="icon" intent="tertiary">
<button
onClick={close}
type="button"
className={styles.close}
>
<CloseLargeIcon />
</Button>
</button>
</header>
<div className={styles.content}>
<Sort sortItems={sortItems} />
<Divider color="subtle" />
<Divider color="subtle" className={styles.divider} />
<Filter filters={filters} />
</div>
{pendingCount === 0 && (
@@ -171,7 +171,7 @@ export default function DestinationFilterAndSort({
{intl.formatMessage({ id: "Clear all filters" })}
</Button>
<Button
intent="tertiary"
intent="primary"
size="large"
theme="base"
disabled={pendingCount === 0}
@@ -179,9 +179,7 @@ export default function DestinationFilterAndSort({
>
{intl.formatMessage(
{ id: "See results ({ count })" },
{
count: pendingCount,
}
{ count: pendingCount }
)}
</Button>
</footer>