fix(SW-1446): make clear history action work, reduces complexity and improves separation of concerns
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { cx } from "class-variance-authority"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { memo, useMemo, useTransition } from "react"
|
||||
import { memo, useTransition } from "react"
|
||||
import {
|
||||
Autocomplete,
|
||||
Button as ButtonRAC,
|
||||
@@ -17,24 +16,20 @@ import { Button } from "@scandic-hotels/design-system/Button"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { ResultHistory } from "../Results/ResultHistory"
|
||||
import { ResultMatches } from "../Results/ResultMatches"
|
||||
import { Results } from "../Results"
|
||||
|
||||
import styles from "./clientInline.module.css"
|
||||
|
||||
import type { ClientProps } from "@/types/components/destinationOverviewPage/jumpTo/client"
|
||||
|
||||
const ResultMatchesMemo = memo(ResultMatches)
|
||||
const ResultHistoryMemo = memo(ResultHistory)
|
||||
const ResultsMemo = memo(Results)
|
||||
|
||||
export function ClientInline({
|
||||
results,
|
||||
latest,
|
||||
setFilterString,
|
||||
onAction,
|
||||
onClearHistory,
|
||||
}: ClientProps) {
|
||||
const router = useRouter()
|
||||
const intl = useIntl()
|
||||
const [isPending, startTransition] = useTransition()
|
||||
const isMounted = useIsMounted()
|
||||
@@ -42,20 +37,6 @@ export function ClientInline({
|
||||
const showResults = !!results
|
||||
const showHistory = isMounted() && (!results || results.length === 0)
|
||||
|
||||
const latestResults = useMemo(() => {
|
||||
return latest.concat({
|
||||
id: "actions", // The string "Actions" converts into a divider below
|
||||
name: "Actions",
|
||||
children: [
|
||||
{
|
||||
id: "clearHistory",
|
||||
type: "clearHistory",
|
||||
displayName: intl.formatMessage({ id: "Clear searches" }),
|
||||
},
|
||||
],
|
||||
})
|
||||
}, [intl, latest])
|
||||
|
||||
return (
|
||||
<Autocomplete>
|
||||
<div className={styles.autocomplete}>
|
||||
@@ -73,22 +54,6 @@ export function ClientInline({
|
||||
onSubmit={(evt) => {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
|
||||
startTransition(() => {
|
||||
if (results) {
|
||||
const firstItem = results[0].children[0]
|
||||
onAction(firstItem.id)
|
||||
if (firstItem.url) {
|
||||
router.push(firstItem.url)
|
||||
}
|
||||
} else if (latest) {
|
||||
const firstItem = latest[0].children[0]
|
||||
onAction(firstItem.id)
|
||||
if (firstItem.url) {
|
||||
router.push(firstItem.url)
|
||||
}
|
||||
}
|
||||
})
|
||||
}}
|
||||
>
|
||||
<div className={styles.fields}>
|
||||
@@ -145,12 +110,19 @@ export function ClientInline({
|
||||
aria-live="polite"
|
||||
>
|
||||
{showResults ? (
|
||||
<ResultMatchesMemo results={results} onAction={onAction} />
|
||||
<ResultsMemo
|
||||
aria-label={intl.formatMessage({ id: "Results" })}
|
||||
results={results}
|
||||
onAction={onAction}
|
||||
/>
|
||||
) : null}
|
||||
{showHistory ? (
|
||||
<ResultHistoryMemo
|
||||
results={latestResults}
|
||||
onClearHistory={onClearHistory}
|
||||
<ResultsMemo
|
||||
aria-label={intl.formatMessage({
|
||||
id: "Latest searches",
|
||||
})}
|
||||
results={latest}
|
||||
onAction={onAction}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { cx } from "class-variance-authority"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { memo, useMemo, useTransition } from "react"
|
||||
import { memo, useTransition } from "react"
|
||||
import {
|
||||
Autocomplete,
|
||||
Button as ButtonRAC,
|
||||
@@ -20,44 +19,26 @@ import { useIntl } from "react-intl"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { ResultHistory } from "../Results/ResultHistory"
|
||||
import { ResultMatches } from "../Results/ResultMatches"
|
||||
import { Results } from "../Results"
|
||||
|
||||
import styles from "./clientModal.module.css"
|
||||
|
||||
import type { ClientProps } from "@/types/components/destinationOverviewPage/jumpTo/client"
|
||||
|
||||
const ResultMatchesMemo = memo(ResultMatches)
|
||||
const ResultHistoryMemo = memo(ResultHistory)
|
||||
const ResultsMemo = memo(Results)
|
||||
|
||||
export function ClientModal({
|
||||
results,
|
||||
latest,
|
||||
setFilterString,
|
||||
onAction,
|
||||
onClearHistory,
|
||||
}: ClientProps) {
|
||||
const router = useRouter()
|
||||
const intl = useIntl()
|
||||
const [isPending, startTransition] = useTransition()
|
||||
|
||||
const showResults = !!results
|
||||
const showHistory = !results || results.length === 0
|
||||
|
||||
const latestResults = useMemo(() => {
|
||||
return latest.concat({
|
||||
id: "actions", // The string "Actions" converts into a divider below
|
||||
name: "Actions",
|
||||
children: [
|
||||
{
|
||||
id: "clearHistory",
|
||||
type: "clearHistory",
|
||||
displayName: intl.formatMessage({ id: "Clear searches" }),
|
||||
},
|
||||
],
|
||||
})
|
||||
}, [intl, latest])
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<ButtonRAC className={styles.trigger}>
|
||||
@@ -106,22 +87,6 @@ export function ClientModal({
|
||||
onSubmit={(evt) => {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
|
||||
startTransition(() => {
|
||||
if (results) {
|
||||
const firstItem = results[0].children[0]
|
||||
onAction(firstItem.id)
|
||||
if (firstItem.url) {
|
||||
router.push(firstItem.url)
|
||||
}
|
||||
} else if (latest) {
|
||||
const firstItem = latest[0].children[0]
|
||||
onAction(firstItem.id)
|
||||
if (firstItem.url) {
|
||||
router.push(firstItem.url)
|
||||
}
|
||||
}
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
@@ -166,15 +131,19 @@ export function ClientModal({
|
||||
aria-live="polite"
|
||||
>
|
||||
{showResults ? (
|
||||
<ResultMatchesMemo
|
||||
<ResultsMemo
|
||||
aria-label={intl.formatMessage({ id: "Results" })}
|
||||
results={results}
|
||||
onAction={onAction}
|
||||
/>
|
||||
) : null}
|
||||
{showHistory ? (
|
||||
<ResultHistoryMemo
|
||||
results={latestResults}
|
||||
onClearHistory={onClearHistory}
|
||||
<ResultsMemo
|
||||
aria-label={intl.formatMessage({
|
||||
id: "Latest searches",
|
||||
})}
|
||||
results={latest}
|
||||
onAction={onAction}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
Collection,
|
||||
Header,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuSection,
|
||||
Text,
|
||||
} from "react-aria-components"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import styles from "./results.module.css"
|
||||
|
||||
import type { ResultMatchesProps } from "@/types/components/destinationOverviewPage/jumpTo/results"
|
||||
|
||||
export function ResultMatches({ results, onAction }: ResultMatchesProps) {
|
||||
const intl = useIntl()
|
||||
|
||||
return (
|
||||
<Menu
|
||||
aria-label={intl.formatMessage({ id: "Results" })}
|
||||
className={styles.menu}
|
||||
items={results}
|
||||
onAction={(key) => onAction(key.toString())}
|
||||
renderEmptyState={() => {
|
||||
return (
|
||||
<div className={styles.noResults}>
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<Header className={styles.noResultsLabel}>
|
||||
{intl.formatMessage({ id: "No results" })}
|
||||
</Header>
|
||||
</Typography>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<span className={styles.noResultsDescription}>
|
||||
{intl.formatMessage({
|
||||
id: "We couldn't find a matching location for your search.",
|
||||
})}
|
||||
</span>
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
>
|
||||
{(section) => (
|
||||
<MenuSection key={section.id}>
|
||||
<Typography variant="Title/Overline/sm">
|
||||
<Header className={styles.sectionHeader}>{section.name}</Header>
|
||||
</Typography>
|
||||
<Collection items={section.children}>
|
||||
{(item) => (
|
||||
<MenuItem
|
||||
className={styles.item}
|
||||
href={item.url}
|
||||
key={item.id}
|
||||
textValue={item.displayName}
|
||||
>
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<Text slot="label" className={styles.itemLabel}>
|
||||
{item.displayName}
|
||||
</Text>
|
||||
</Typography>
|
||||
{item.description ? (
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<Text slot="description" className={styles.itemDescription}>
|
||||
{item.description}
|
||||
</Text>
|
||||
</Typography>
|
||||
) : null}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Collection>
|
||||
</MenuSection>
|
||||
)}
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
|
||||
import styles from "./results.module.css"
|
||||
|
||||
export function ResultSkeleton() {
|
||||
export function ResultsSkeleton() {
|
||||
const intl = useIntl()
|
||||
|
||||
return (
|
||||
@@ -10,22 +10,24 @@ import {
|
||||
} from "react-aria-components"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Button } from "@scandic-hotels/design-system/Button"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import styles from "./results.module.css"
|
||||
|
||||
import type { ResultHistoryProps } from "@/types/components/destinationOverviewPage/jumpTo/results"
|
||||
import type { ResultsProps } from "@/types/components/destinationOverviewPage/jumpTo/results"
|
||||
|
||||
export function ResultHistory({ results, onClearHistory }: ResultHistoryProps) {
|
||||
export function Results({
|
||||
"aria-label": ariaLabel,
|
||||
results,
|
||||
onAction,
|
||||
}: ResultsProps) {
|
||||
const intl = useIntl()
|
||||
|
||||
return (
|
||||
<Menu
|
||||
aria-label={intl.formatMessage({
|
||||
id: "Latest searches",
|
||||
})}
|
||||
aria-label={ariaLabel}
|
||||
onAction={onAction}
|
||||
className={styles.menu}
|
||||
items={results}
|
||||
renderEmptyState={() => {
|
||||
@@ -45,19 +47,18 @@ export function ResultHistory({ results, onClearHistory }: ResultHistoryProps) {
|
||||
className={styles.item}
|
||||
textValue={item.displayName}
|
||||
>
|
||||
<Button
|
||||
typography="Body/Supporting text (caption)/smBold"
|
||||
variant="Text"
|
||||
onPress={onClearHistory}
|
||||
className={styles.clearHistoryButton}
|
||||
>
|
||||
<MaterialIcon icon="delete" color="CurrentColor" />
|
||||
<Text slot="label">
|
||||
{intl.formatMessage({
|
||||
id: "Clear searches",
|
||||
})}
|
||||
</Text>
|
||||
</Button>
|
||||
{item.id === "clearHistory" ? (
|
||||
<>
|
||||
<MaterialIcon icon="delete" color="CurrentColor" />
|
||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
||||
<Text slot="label">
|
||||
{intl.formatMessage({
|
||||
id: "Clear searches",
|
||||
})}
|
||||
</Text>
|
||||
</Typography>
|
||||
</>
|
||||
) : null}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Collection>
|
||||
@@ -51,11 +51,6 @@
|
||||
}
|
||||
|
||||
.actionsSection .item {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.clearHistoryButton {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Space-x05);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useCallback, useMemo, useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
import { useIsMounted, useMediaQuery } from "usehooks-ts"
|
||||
|
||||
import { isDefined } from "@/server/utils"
|
||||
@@ -24,6 +25,7 @@ export function JumpToClient<T extends JumpToData>({
|
||||
onAction,
|
||||
onClearHistory,
|
||||
}: JumpToProps<T>) {
|
||||
const intl = useIntl()
|
||||
const isMounted = useIsMounted()
|
||||
const displayInModal = useMediaQuery("(max-width: 767px)")
|
||||
|
||||
@@ -143,12 +145,23 @@ export function JumpToClient<T extends JumpToData>({
|
||||
{
|
||||
id: "latestSearches",
|
||||
name: "Latest searches",
|
||||
children,
|
||||
children: children,
|
||||
},
|
||||
{
|
||||
id: "actions", // The string "Actions" converts into a divider
|
||||
name: "Actions",
|
||||
children: [
|
||||
{
|
||||
id: "clearHistory",
|
||||
type: "clearHistory",
|
||||
displayName: intl.formatMessage({ id: "Clear searches" }),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
return []
|
||||
}, [data, history])
|
||||
}, [data, history, intl])
|
||||
|
||||
const results = useMemo(() => {
|
||||
if (filterString) {
|
||||
@@ -163,8 +176,15 @@ export function JumpToClient<T extends JumpToData>({
|
||||
results,
|
||||
latest,
|
||||
setFilterString,
|
||||
onAction,
|
||||
onClearHistory,
|
||||
onAction: (key) => {
|
||||
switch (key) {
|
||||
case "clearHistory":
|
||||
onClearHistory()
|
||||
break
|
||||
default:
|
||||
onAction(key)
|
||||
}
|
||||
},
|
||||
}
|
||||
}, [results, latest, setFilterString, onAction, onClearHistory])
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ export function JumpToResolver({ dataPromise }: JumpToResolverProps) {
|
||||
}
|
||||
}}
|
||||
onClearHistory={() => {
|
||||
debugger
|
||||
clearHistory()
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -5,5 +5,4 @@ export type ClientProps = {
|
||||
latest: NonNullable<LocationMatchResultsState>
|
||||
setFilterString: (filter: string | null) => void
|
||||
onAction: JumpToProps<JumpToData>["onAction"]
|
||||
onClearHistory: () => void
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { Key } from "react-aria-components"
|
||||
|
||||
export type JumpToDataItem = {
|
||||
id: string
|
||||
id: Key
|
||||
displayName: string
|
||||
type: "hotels" | "cities"
|
||||
description: string
|
||||
@@ -15,7 +17,7 @@ export type JumpToHistory = {
|
||||
type: JumpToDataItem["type"]
|
||||
}[]
|
||||
|
||||
export type JumpToProps<T extends { id: string }[]> = {
|
||||
export type JumpToProps<T extends { id: Key }[]> = {
|
||||
data: T
|
||||
history: JumpToHistory
|
||||
onAction: (id: T[number]["id"]) => void
|
||||
@@ -23,7 +25,7 @@ export type JumpToProps<T extends { id: string }[]> = {
|
||||
}
|
||||
|
||||
export type LocationMatch = {
|
||||
id: string
|
||||
id: Key
|
||||
displayName: string
|
||||
type: string
|
||||
description?: string
|
||||
@@ -35,7 +37,7 @@ export type ScoringMatch = LocationMatch & {
|
||||
}
|
||||
|
||||
export type LocationMatchResult = {
|
||||
id: string
|
||||
id: Key
|
||||
name: string
|
||||
children: LocationMatch[]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import type { ClientProps } from "./client"
|
||||
|
||||
export type ResultHistoryProps = Pick<ClientProps, "onClearHistory"> & {
|
||||
results: ClientProps["latest"]
|
||||
}
|
||||
|
||||
export type ResultMatchesProps = Pick<ClientProps, "onAction"> & {
|
||||
export type ResultsProps = Pick<ClientProps, "onAction"> & {
|
||||
results: NonNullable<ClientProps["results"]>
|
||||
"aria-label": string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user