fix(SW-1446): make clear history action work, reduces complexity and improves separation of concerns

This commit is contained in:
Michael Zetterberg
2025-04-04 17:01:11 +02:00
parent 4aeb5b071d
commit b2ff5124ec
11 changed files with 78 additions and 203 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
)
}

View File

@@ -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 (

View File

@@ -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>

View File

@@ -51,11 +51,6 @@
}
.actionsSection .item {
padding-top: 0;
padding-bottom: 0;
}
.clearHistoryButton {
display: flex;
flex-direction: row;
gap: var(--Space-x05);

View File

@@ -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])

View File

@@ -39,7 +39,6 @@ export function JumpToResolver({ dataPromise }: JumpToResolverProps) {
}
}}
onClearHistory={() => {
debugger
clearHistory()
}}
/>

View File

@@ -5,5 +5,4 @@ export type ClientProps = {
latest: NonNullable<LocationMatchResultsState>
setFilterString: (filter: string | null) => void
onAction: JumpToProps<JumpToData>["onAction"]
onClearHistory: () => void
}

View File

@@ -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[]
}

View File

@@ -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
}