fix(SW-1446): handle empty history and performance better
This commit is contained in:
@@ -89,7 +89,7 @@
|
|||||||
border-radius: var(--Corner-radius-Large);
|
border-radius: var(--Corner-radius-Large);
|
||||||
padding: var(--Space-x2);
|
padding: var(--Space-x2);
|
||||||
width: 360px;
|
width: 360px;
|
||||||
max-height: 430px;
|
max-height: 400px;
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
box-shadow: var(--BoxShadow-Level-4);
|
box-shadow: var(--BoxShadow-Level-4);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useIntl } from "react-intl"
|
|||||||
import { useIsMounted } from "usehooks-ts"
|
import { useIsMounted } from "usehooks-ts"
|
||||||
|
|
||||||
import { Button } from "@scandic-hotels/design-system/Button"
|
import { Button } from "@scandic-hotels/design-system/Button"
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { Results } from "../Results"
|
import { Results } from "../Results"
|
||||||
@@ -35,7 +35,8 @@ export function ClientInline({
|
|||||||
const isMounted = useIsMounted()
|
const isMounted = useIsMounted()
|
||||||
|
|
||||||
const showResults = !!results
|
const showResults = !!results
|
||||||
const showHistory = isMounted() && (!results || results.length === 0)
|
const showHistory =
|
||||||
|
latest.length > 0 && isMounted() && (!results || results.length === 0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Autocomplete>
|
<Autocomplete>
|
||||||
@@ -101,32 +102,34 @@ export function ClientInline({
|
|||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
</SearchField>
|
</SearchField>
|
||||||
<div className={styles.results}>
|
{showResults || showHistory ? (
|
||||||
<div
|
<div className={styles.results}>
|
||||||
className={cx({
|
<div
|
||||||
[styles.menuContainer]: true,
|
className={cx({
|
||||||
[styles.pending]: isPending,
|
[styles.menuContainer]: true,
|
||||||
})}
|
[styles.pending]: isPending,
|
||||||
aria-live="polite"
|
})}
|
||||||
>
|
aria-live="polite"
|
||||||
{showResults ? (
|
>
|
||||||
<ResultsMemo
|
{showResults ? (
|
||||||
aria-label={intl.formatMessage({ id: "Results" })}
|
<ResultsMemo
|
||||||
results={results}
|
aria-label={intl.formatMessage({ id: "Results" })}
|
||||||
onAction={onAction}
|
results={results}
|
||||||
/>
|
onAction={onAction}
|
||||||
) : null}
|
/>
|
||||||
{showHistory ? (
|
) : null}
|
||||||
<ResultsMemo
|
{showHistory ? (
|
||||||
aria-label={intl.formatMessage({
|
<ResultsMemo
|
||||||
id: "Latest searches",
|
aria-label={intl.formatMessage({
|
||||||
})}
|
id: "Latest searches",
|
||||||
results={latest}
|
})}
|
||||||
onAction={onAction}
|
results={latest}
|
||||||
/>
|
onAction={onAction}
|
||||||
) : null}
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</Autocomplete>
|
</Autocomplete>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from "react-aria-components"
|
} from "react-aria-components"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import { Results } from "../Results"
|
import { Results } from "../Results"
|
||||||
|
|||||||
@@ -23,51 +23,51 @@ export function ResultsSkeleton() {
|
|||||||
<div>
|
<div>
|
||||||
<div className={styles.item}>
|
<div className={styles.item}>
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
<SkeletonShimmer width="50%" />
|
<SkeletonShimmer width="50%" display="inline-block" />
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
<div className={styles.itemDescription}>
|
<div className={styles.itemDescription}>
|
||||||
<SkeletonShimmer width="38%" />
|
<SkeletonShimmer width="38%" display="inline-block" />
|
||||||
</div>
|
</div>
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.item}>
|
<div className={styles.item}>
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
<SkeletonShimmer width="40%" />
|
<SkeletonShimmer width="40%" display="inline-block" />
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
<div className={styles.itemDescription}>
|
<div className={styles.itemDescription}>
|
||||||
<SkeletonShimmer width="23%" />
|
<SkeletonShimmer width="23%" display="inline-block" />
|
||||||
</div>
|
</div>
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.item}>
|
<div className={styles.item}>
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
<SkeletonShimmer width="55%" />
|
<SkeletonShimmer width="55%" display="inline-block" />
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
<div className={styles.itemDescription}>
|
<div className={styles.itemDescription}>
|
||||||
<SkeletonShimmer width="40%" />
|
<SkeletonShimmer width="40%" display="inline-block" />
|
||||||
</div>
|
</div>
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.item}>
|
<div className={styles.item}>
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
<SkeletonShimmer width="27%" />
|
<SkeletonShimmer width="27%" display="inline-block" />
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
<div className={styles.itemDescription}>
|
<div className={styles.itemDescription}>
|
||||||
<SkeletonShimmer width="33%" />
|
<SkeletonShimmer width="33%" display="inline-block" />
|
||||||
</div>
|
</div>
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.item}>
|
<div className={styles.item}>
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
<SkeletonShimmer width="45%" />
|
<SkeletonShimmer width="45%" display="inline-block" />
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
<div className={styles.itemDescription}>
|
<div className={styles.itemDescription}>
|
||||||
<SkeletonShimmer width="37%" />
|
<SkeletonShimmer width="37%" display="inline-block" />
|
||||||
</div>
|
</div>
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,14 +3,16 @@
|
|||||||
import {
|
import {
|
||||||
Collection,
|
Collection,
|
||||||
Header,
|
Header,
|
||||||
|
ListLayout,
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
MenuSection,
|
MenuSection,
|
||||||
Text,
|
Text,
|
||||||
|
Virtualizer,
|
||||||
} from "react-aria-components"
|
} from "react-aria-components"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
import styles from "./results.module.css"
|
import styles from "./results.module.css"
|
||||||
@@ -25,20 +27,61 @@ export function Results({
|
|||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Virtualizer
|
||||||
aria-label={ariaLabel}
|
layout={ListLayout}
|
||||||
onAction={onAction}
|
layoutOptions={{
|
||||||
className={styles.menu}
|
estimatedRowHeight: 64,
|
||||||
items={results}
|
estimatedHeadingHeight: 41,
|
||||||
renderEmptyState={() => {
|
|
||||||
return null
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(section) => {
|
<Menu
|
||||||
if (section.id === "actions") {
|
aria-label={ariaLabel}
|
||||||
|
onAction={onAction}
|
||||||
|
className={styles.menu}
|
||||||
|
items={results}
|
||||||
|
renderEmptyState={() => {
|
||||||
|
return null
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(section) => {
|
||||||
|
if (section.id === "actions") {
|
||||||
|
return (
|
||||||
|
<MenuSection key={section.id} className={styles.actionsSection}>
|
||||||
|
<Header className={styles.menuDivider}>
|
||||||
|
<span className="sr-only">{section.name}</span>
|
||||||
|
</Header>
|
||||||
|
<Collection items={section.children}>
|
||||||
|
{(item) => (
|
||||||
|
<MenuItem
|
||||||
|
key={item.id}
|
||||||
|
href={item.url}
|
||||||
|
className={styles.item}
|
||||||
|
textValue={item.displayName}
|
||||||
|
>
|
||||||
|
{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>
|
||||||
|
</MenuSection>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuSection key={section.id} className={styles.actionsSection}>
|
<MenuSection key={section.id}>
|
||||||
<Header className="sr-only">{section.name}</Header>
|
<Typography variant="Title/Overline/sm">
|
||||||
|
<Header className={styles.sectionHeader}>{section.name}</Header>
|
||||||
|
</Typography>
|
||||||
<Collection items={section.children}>
|
<Collection items={section.children}>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -47,59 +90,28 @@ export function Results({
|
|||||||
className={styles.item}
|
className={styles.item}
|
||||||
textValue={item.displayName}
|
textValue={item.displayName}
|
||||||
>
|
>
|
||||||
{item.id === "clearHistory" ? (
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
<>
|
<Text slot="label" className={styles.itemLabel}>
|
||||||
<MaterialIcon icon="delete" color="CurrentColor" />
|
{item.displayName}
|
||||||
<Typography variant="Body/Supporting text (caption)/smBold">
|
</Text>
|
||||||
<Text slot="label">
|
</Typography>
|
||||||
{intl.formatMessage({
|
{item.description ? (
|
||||||
id: "Clear searches",
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
})}
|
<Text
|
||||||
</Text>
|
slot="description"
|
||||||
</Typography>
|
className={styles.itemDescription}
|
||||||
</>
|
>
|
||||||
|
{item.description}
|
||||||
|
</Text>
|
||||||
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</Collection>
|
</Collection>
|
||||||
</MenuSection>
|
</MenuSection>
|
||||||
)
|
)
|
||||||
}
|
}}
|
||||||
|
</Menu>
|
||||||
return (
|
</Virtualizer>
|
||||||
<MenuSection key={section.id}>
|
|
||||||
<Typography variant="Title/Overline/sm">
|
|
||||||
<Header className={styles.sectionHeader}>{section.name}</Header>
|
|
||||||
</Typography>
|
|
||||||
<Collection items={section.children}>
|
|
||||||
{(item) => (
|
|
||||||
<MenuItem
|
|
||||||
key={item.id}
|
|
||||||
href={item.url}
|
|
||||||
className={styles.item}
|
|
||||||
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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
.menu {
|
.menu {
|
||||||
display: flex;
|
height: 100%;
|
||||||
flex-direction: column;
|
overflow-y: auto;
|
||||||
gap: var(--Space-x2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sectionHeader {
|
.sectionHeader {
|
||||||
color: var(--UI-Text-Placeholder);
|
color: var(--UI-Text-Placeholder);
|
||||||
padding-left: var(--Space-x1);
|
padding-left: var(--Space-x1);
|
||||||
padding-bottom: var(--Space-x05);
|
padding-bottom: var(--Space-x05);
|
||||||
|
|
||||||
|
/* Due to Virtualizer we cannot use gap in .menu,
|
||||||
|
instead we use padding-top on each section header */
|
||||||
|
padding-top: var(--Space-x2);
|
||||||
|
/* Except for the first section header */
|
||||||
|
.menu > div > div:first-child & {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
@@ -45,9 +52,16 @@
|
|||||||
color: var(--Text-Tertiary);
|
color: var(--Text-Tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionsSection {
|
.menuDivider {
|
||||||
border-top: solid 1px var(--Border-Divider-Subtle);
|
padding-top: var(--Space-x2);
|
||||||
padding-top: var(--Space-x2); /* match gap of .menu */
|
padding-bottom: var(--Space-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuDivider:before {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
height: 1px;
|
||||||
|
background: var(--Border-Divider-Subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionsSection .item {
|
.actionsSection .item {
|
||||||
|
|||||||
Reference in New Issue
Block a user