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