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,
|
||||
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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user