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:
@@ -26,13 +26,10 @@ export default function CityListing() {
|
|||||||
threshold: 300,
|
threshold: 300,
|
||||||
elementRef: scrollRef,
|
elementRef: scrollRef,
|
||||||
})
|
})
|
||||||
const { activeCities, filters, sortItems, isLoading } =
|
const { activeCities, isLoading } = useDestinationDataStore((state) => ({
|
||||||
useDestinationDataStore((state) => ({
|
activeCities: state.activeCities,
|
||||||
activeCities: state.activeCities,
|
isLoading: state.isLoading,
|
||||||
filters: state.allFilters,
|
}))
|
||||||
sortItems: state.sortItems,
|
|
||||||
isLoading: state.isLoading,
|
|
||||||
}))
|
|
||||||
const [allCitiesVisible, setAllCitiesVisible] = useState(
|
const [allCitiesVisible, setAllCitiesVisible] = useState(
|
||||||
activeCities.length <= 5
|
activeCities.length <= 5
|
||||||
)
|
)
|
||||||
@@ -57,11 +54,7 @@ export default function CityListing() {
|
|||||||
{ count: activeCities.length }
|
{ count: activeCities.length }
|
||||||
)}
|
)}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<DestinationFilterAndSort
|
<DestinationFilterAndSort listType="city" />
|
||||||
filters={filters}
|
|
||||||
sortItems={sortItems}
|
|
||||||
listType="city"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{activeCities.length === 0 ? (
|
{activeCities.length === 0 ? (
|
||||||
<Alert
|
<Alert
|
||||||
|
|||||||
@@ -18,13 +18,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 949px) {
|
@media screen and (max-width: 949px) {
|
||||||
.hotelListWrapper {
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hotelList {
|
.hotelList {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
|
overflow-x: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
|||||||
@@ -25,13 +25,10 @@ export default function HotelList() {
|
|||||||
const map = useMap()
|
const map = useMap()
|
||||||
const coreLib = useMapsLibrary("core")
|
const coreLib = useMapsLibrary("core")
|
||||||
const [visibleHotels, setVisibleHotels] = useState<HotelDataWithUrl[]>([])
|
const [visibleHotels, setVisibleHotels] = useState<HotelDataWithUrl[]>([])
|
||||||
const { filters, sortItems, activeHotels, isLoading } =
|
const { activeHotels, isLoading } = useDestinationDataStore((state) => ({
|
||||||
useDestinationDataStore((state) => ({
|
activeHotels: state.activeHotels,
|
||||||
filters: state.allFilters,
|
isLoading: state.isLoading,
|
||||||
sortItems: state.sortItems,
|
}))
|
||||||
activeHotels: state.activeHotels,
|
|
||||||
isLoading: state.isLoading,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const debouncedUpdateVisibleHotels = useMemo(
|
const debouncedUpdateVisibleHotels = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -67,11 +64,7 @@ export default function HotelList() {
|
|||||||
{ count: visibleHotels.length }
|
{ count: visibleHotels.length }
|
||||||
)}
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
<DestinationFilterAndSort
|
<DestinationFilterAndSort listType="hotel" />
|
||||||
filters={filters}
|
|
||||||
sortItems={sortItems}
|
|
||||||
listType="hotel"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{activeHotels.length === 0 ? (
|
{activeHotels.length === 0 ? (
|
||||||
<Alert
|
<Alert
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
justify-self: stretch;
|
justify-self: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 950px) {
|
@media screen and (max-width: 949px) {
|
||||||
.hotelListItem {
|
.hotelListItem {
|
||||||
width: 360px;
|
width: 360px;
|
||||||
min-height: 150px;
|
min-height: 150px;
|
||||||
@@ -76,6 +76,11 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tripAdvisor {
|
||||||
|
top: 12px;
|
||||||
|
left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: var(--Spacing-x-one-and-half);
|
padding: var(--Spacing-x-one-and-half);
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Spacing-x1);
|
||||||
|
|||||||
@@ -18,13 +18,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 949px) {
|
@media screen and (max-width: 949px) {
|
||||||
.cityListWrapper {
|
|
||||||
overflow-x: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cityList {
|
.cityList {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
|
overflow-x: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
|||||||
@@ -17,13 +17,10 @@ import { AlertTypeEnum } from "@/types/enums/alert"
|
|||||||
|
|
||||||
export default function CityList() {
|
export default function CityList() {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const { filters, sortItems, activeCities, isLoading } =
|
const { activeCities, isLoading } = useDestinationDataStore((state) => ({
|
||||||
useDestinationDataStore((state) => ({
|
activeCities: state.activeCities,
|
||||||
filters: state.allFilters,
|
isLoading: state.isLoading,
|
||||||
sortItems: state.sortItems,
|
}))
|
||||||
activeCities: state.activeCities,
|
|
||||||
isLoading: state.isLoading,
|
|
||||||
}))
|
|
||||||
|
|
||||||
return isLoading ? (
|
return isLoading ? (
|
||||||
<CityListSkeleton />
|
<CityListSkeleton />
|
||||||
@@ -36,11 +33,7 @@ export default function CityList() {
|
|||||||
{ count: activeCities.length }
|
{ count: activeCities.length }
|
||||||
)}
|
)}
|
||||||
</Body>
|
</Body>
|
||||||
<DestinationFilterAndSort
|
<DestinationFilterAndSort listType="city" />
|
||||||
filters={filters}
|
|
||||||
sortItems={sortItems}
|
|
||||||
listType="city"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{activeCities.length === 0 ? (
|
{activeCities.length === 0 ? (
|
||||||
<Alert
|
<Alert
|
||||||
|
|||||||
@@ -26,13 +26,10 @@ export default function HotelListing() {
|
|||||||
threshold: 300,
|
threshold: 300,
|
||||||
elementRef: scrollRef,
|
elementRef: scrollRef,
|
||||||
})
|
})
|
||||||
const { activeHotels, filters, sortItems, isLoading } =
|
const { activeHotels, isLoading } = useDestinationDataStore((state) => ({
|
||||||
useDestinationDataStore((state) => ({
|
activeHotels: state.activeHotels,
|
||||||
activeHotels: state.activeHotels,
|
isLoading: state.isLoading,
|
||||||
filters: state.allFilters,
|
}))
|
||||||
sortItems: state.sortItems,
|
|
||||||
isLoading: state.isLoading,
|
|
||||||
}))
|
|
||||||
const [allHotelsVisible, setAllHotelsVisible] = useState(
|
const [allHotelsVisible, setAllHotelsVisible] = useState(
|
||||||
activeHotels.length <= 5
|
activeHotels.length <= 5
|
||||||
)
|
)
|
||||||
@@ -57,11 +54,7 @@ export default function HotelListing() {
|
|||||||
{ count: activeHotels.length }
|
{ count: activeHotels.length }
|
||||||
)}
|
)}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<DestinationFilterAndSort
|
<DestinationFilterAndSort listType="hotel" />
|
||||||
filters={filters}
|
|
||||||
sortItems={sortItems}
|
|
||||||
listType="hotel"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{activeHotels.length === 0 ? (
|
{activeHotels.length === 0 ? (
|
||||||
<Alert
|
<Alert
|
||||||
|
|||||||
@@ -40,9 +40,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.closeButton {
|
.closeButton {
|
||||||
pointer-events: initial;
|
display: none !important;
|
||||||
box-shadow: var(--button-box-shadow);
|
|
||||||
gap: var(--Spacing-x-half);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomButton {
|
.zoomButton {
|
||||||
@@ -65,4 +63,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
display: flex !important;
|
||||||
|
pointer-events: initial;
|
||||||
|
box-shadow: var(--button-box-shadow);
|
||||||
|
gap: var(--Spacing-x-half);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from "react"
|
} from "react"
|
||||||
import { Dialog, Modal } from "react-aria-components"
|
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 { debounce } from "@/utils/debounce"
|
||||||
|
|
||||||
import DynamicMap from "./DynamicMap"
|
import DynamicMap from "./DynamicMap"
|
||||||
@@ -49,6 +53,8 @@ export default function Map({
|
|||||||
const [mapHeight, setMapHeight] = useState("0px")
|
const [mapHeight, setMapHeight] = useState("0px")
|
||||||
const [scrollHeightWhenOpened, setScrollHeightWhenOpened] = useState(0)
|
const [scrollHeightWhenOpened, setScrollHeightWhenOpened] = useState(0)
|
||||||
|
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
const markers = getHotelMapMarkers(hotels)
|
const markers = getHotelMapMarkers(hotels)
|
||||||
const geoJson = mapMarkerDataToGeoJson(markers)
|
const geoJson = mapMarkerDataToGeoJson(markers)
|
||||||
const defaultCoordinates = defaultLocation
|
const defaultCoordinates = defaultLocation
|
||||||
@@ -122,6 +128,20 @@ export default function Map({
|
|||||||
}
|
}
|
||||||
aria-label={"Mapview"}
|
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>
|
<aside className={styles.sidebar}>{children}</aside>
|
||||||
<DynamicMap
|
<DynamicMap
|
||||||
markers={markers}
|
markers={markers}
|
||||||
|
|||||||
@@ -34,7 +34,22 @@
|
|||||||
gap: var(--Spacing-x-half);
|
gap: var(--Spacing-x-half);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mobileNavigation {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 949px) {
|
@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 {
|
.sidebar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
|
|||||||
@@ -29,3 +29,13 @@
|
|||||||
border-color: var(--UI-Input-Controls-Fill-Selected);
|
border-color: var(--UI-Input-Controls-Fill-Selected);
|
||||||
background-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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,3 +25,13 @@
|
|||||||
gap: var(--Spacing-x1) var(--Spacing-x2);
|
gap: var(--Spacing-x1) var(--Spacing-x2);
|
||||||
margin: var(--Spacing-x3) 0;
|
margin: var(--Spacing-x3) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
.list {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list label {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export default function Filter({ filters }: FilterProps) {
|
|||||||
</Subtitle>
|
</Subtitle>
|
||||||
<ul className={styles.list}>
|
<ul className={styles.list}>
|
||||||
{facilityFilters.map((filter) => (
|
{facilityFilters.map((filter) => (
|
||||||
<li key={`filter-${filter.slug}`} className={styles.item}>
|
<li key={`filter-${filter.slug}`}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
name={filter.name}
|
name={filter.name}
|
||||||
value={filter.slug}
|
value={filter.slug}
|
||||||
@@ -59,7 +59,7 @@ export default function Filter({ filters }: FilterProps) {
|
|||||||
</Subtitle>
|
</Subtitle>
|
||||||
<ul className={styles.list}>
|
<ul className={styles.list}>
|
||||||
{surroundingsFilters.map((filter) => (
|
{surroundingsFilters.map((filter) => (
|
||||||
<li key={`filter-${filter.slug}`} className={styles.item}>
|
<li key={`filter-${filter.slug}`}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
name={filter.name}
|
name={filter.name}
|
||||||
value={filter.slug}
|
value={filter.slug}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: var(--default-modal-overlay-z-index);
|
z-index: var(--default-modal-overlay-z-index);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog {
|
.dialog {
|
||||||
width: min(80dvw, 960px);
|
width: min(80dvw, 960px);
|
||||||
border-radius: var(--Corner-radius-Large);
|
border-radius: var(--Corner-radius-Large);
|
||||||
@@ -65,3 +66,53 @@
|
|||||||
padding: var(--Spacing-x2) var(--Spacing-x4);
|
padding: var(--Spacing-x2) var(--Spacing-x4);
|
||||||
border-top: 1px solid var(--Base-Border-Subtle);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,26 +23,20 @@ import Sort from "./Sort"
|
|||||||
|
|
||||||
import styles from "./destinationFilterAndSort.module.css"
|
import styles from "./destinationFilterAndSort.module.css"
|
||||||
|
|
||||||
import type {
|
|
||||||
CategorizedFilters,
|
|
||||||
SortItem,
|
|
||||||
} from "@/types/components/destinationFilterAndSort"
|
|
||||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||||
|
|
||||||
interface HotelFilterAndSortProps {
|
interface HotelFilterAndSortProps {
|
||||||
filters: CategorizedFilters
|
|
||||||
sortItems: SortItem[]
|
|
||||||
listType: "city" | "hotel"
|
listType: "city" | "hotel"
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DestinationFilterAndSort({
|
export default function DestinationFilterAndSort({
|
||||||
filters,
|
|
||||||
sortItems,
|
|
||||||
listType,
|
listType,
|
||||||
}: HotelFilterAndSortProps) {
|
}: HotelFilterAndSortProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const {
|
const {
|
||||||
|
filters,
|
||||||
|
sortItems,
|
||||||
pendingFilters,
|
pendingFilters,
|
||||||
pendingSort,
|
pendingSort,
|
||||||
defaultSort,
|
defaultSort,
|
||||||
@@ -53,6 +47,8 @@ export default function DestinationFilterAndSort({
|
|||||||
resetPendingValues,
|
resetPendingValues,
|
||||||
setIsLoading,
|
setIsLoading,
|
||||||
} = useDestinationDataStore((state) => ({
|
} = useDestinationDataStore((state) => ({
|
||||||
|
filters: state.allFilters,
|
||||||
|
sortItems: state.sortItems,
|
||||||
pendingFilters: state.pendingFilters,
|
pendingFilters: state.pendingFilters,
|
||||||
pendingSort: state.pendingSort,
|
pendingSort: state.pendingSort,
|
||||||
basePath: state.basePathnameWithoutFilters,
|
basePath: state.basePathnameWithoutFilters,
|
||||||
@@ -142,13 +138,17 @@ export default function DestinationFilterAndSort({
|
|||||||
>
|
>
|
||||||
<h3>{intl.formatMessage({ id: "Filter and sort" })}</h3>
|
<h3>{intl.formatMessage({ id: "Filter and sort" })}</h3>
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<Button onClick={close} variant="icon" intent="tertiary">
|
<button
|
||||||
|
onClick={close}
|
||||||
|
type="button"
|
||||||
|
className={styles.close}
|
||||||
|
>
|
||||||
<CloseLargeIcon />
|
<CloseLargeIcon />
|
||||||
</Button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<Sort sortItems={sortItems} />
|
<Sort sortItems={sortItems} />
|
||||||
<Divider color="subtle" />
|
<Divider color="subtle" className={styles.divider} />
|
||||||
<Filter filters={filters} />
|
<Filter filters={filters} />
|
||||||
</div>
|
</div>
|
||||||
{pendingCount === 0 && (
|
{pendingCount === 0 && (
|
||||||
@@ -171,7 +171,7 @@ export default function DestinationFilterAndSort({
|
|||||||
{intl.formatMessage({ id: "Clear all filters" })}
|
{intl.formatMessage({ id: "Clear all filters" })}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
intent="tertiary"
|
intent="primary"
|
||||||
size="large"
|
size="large"
|
||||||
theme="base"
|
theme="base"
|
||||||
disabled={pendingCount === 0}
|
disabled={pendingCount === 0}
|
||||||
@@ -179,9 +179,7 @@ export default function DestinationFilterAndSort({
|
|||||||
>
|
>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
{ id: "See results ({ count })" },
|
{ id: "See results ({ count })" },
|
||||||
{
|
{ count: pendingCount }
|
||||||
count: pendingCount,
|
|
||||||
}
|
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
Reference in New Issue
Block a user