Skip to content

Commit a1d8b7a

Browse files
feat(pages/talks/all-talks-section): better searching UX
1 parent c38cd61 commit a1d8b7a

File tree

5 files changed

+83
-108
lines changed

5 files changed

+83
-108
lines changed

src/components/pages/talks/all-talks-section-item-skeleton.tsx

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/components/pages/talks/all-talks-section-skeleton.tsx

Lines changed: 0 additions & 35 deletions
This file was deleted.

src/components/pages/talks/all-talks-section.tsx

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { FunctionComponent, PropsWithChildren, useState } from 'react';
1+
import { useTheme } from 'next-themes';
2+
import { FunctionComponent, PropsWithChildren, Suspense, useMemo, useState } from 'react';
3+
import ContentLoader from 'react-content-loader';
4+
import colors from 'tailwindcss/colors';
25

36
import { isEmpty, reversedIndexOf } from 'utils/array';
47
import { useSearch } from 'utils/search';
@@ -7,6 +10,7 @@ import Chip from 'components/shared/chip';
710
import DropdownMenu from 'components/shared/dropdown-menu';
811
import EmptyList from 'components/shared/empty-list';
912
import OrderedListItem from 'components/shared/ordered-list-item';
13+
import SearchBar, { SearchBarProps } from 'components/shared/seach-bar';
1014
import SectionContainer from 'components/shared/section-container';
1115
import SectionHeading from 'components/shared/section-heading';
1216

@@ -27,7 +31,6 @@ type Schema = {
2731

2832
export type AllTalksSectionProps = {
2933
items: Array<Schema>;
30-
searchTerm: string;
3134
};
3235

3336
// ---------------------------------------------------------------------------
@@ -64,17 +67,16 @@ function renderPrefix(talkCategory: string) {
6467
}
6568

6669
// ---------------------------------------------------------------------------
67-
// UI
70+
// UI: CORE
6871
// ---------------------------------------------------------------------------
6972

70-
const AllTalksSection: FunctionComponent<PropsWithChildren<AllTalksSectionProps>> = ({
71-
items: baseItems,
72-
searchTerm,
73-
}) => {
73+
const AllTalksSection: FunctionComponent<PropsWithChildren<AllTalksSectionProps>> = ({ items: baseItems }) => {
74+
const [searchTerm, setSearchTerm] = useState('');
7475
const [selectedCategories, setSelectedCategories] = useState<string[]>(['talk', 'workshop', 'panel']);
75-
const searchedItems = useSearch<typeof searchSchema, Schema>(searchSchema, baseItems, searchTerm);
76-
77-
const items = searchedItems.filter((item) => selectedCategories.includes(item.talkCategory));
76+
77+
const onChange: SearchBarProps['onChange'] = (evt) => {
78+
setSearchTerm(evt.target.value);
79+
};
7880

7981
return (
8082
<SectionContainer>
@@ -89,25 +91,77 @@ const AllTalksSection: FunctionComponent<PropsWithChildren<AllTalksSectionProps>
8991
/>
9092
</div>
9193
<div className="mb-6">
92-
{isEmpty(items) && (
93-
<EmptyList heading="No items found 😢" subHeading="I don't have any sessions on this topic." />
94-
)}
95-
{items.map((item, index) => {
96-
const { talkTitle, talkSlug, talkCategory } = item;
97-
98-
return (
99-
<OrderedListItem
100-
key={talkSlug}
101-
label={talkTitle}
102-
index={reversedIndexOf(items.length, index)}
103-
href={talkSlug}
104-
prefix={renderPrefix(talkCategory)}
105-
/>
106-
);
107-
})}
94+
<SearchBar label="Search topics, events and places" onChange={onChange} />
95+
</div>
96+
<div className="mb-6">
97+
<Suspense fallback={<AllTalksListSkeleton items={3} />}>
98+
<AllTalksList items={baseItems} searchTerm={searchTerm} selectedCategories={selectedCategories} />
99+
</Suspense>
108100
</div>
109101
</SectionContainer>
110102
);
111103
};
112104

113105
export default AllTalksSection;
106+
107+
// ---------------------------------------------------------------------------
108+
// UI: AllTalksList
109+
// ---------------------------------------------------------------------------
110+
111+
type AllTalksListProps = {
112+
items: Array<Schema>;
113+
searchTerm: string;
114+
selectedCategories: string[];
115+
};
116+
117+
function AllTalksList({ items: baseItems, searchTerm, selectedCategories }: AllTalksListProps) {
118+
const searchedItems = useSearch<typeof searchSchema, Schema>(searchSchema, baseItems, searchTerm);
119+
const items = searchedItems.filter((item) => selectedCategories.includes(item.talkCategory));
120+
121+
if (isEmpty(items)) {
122+
return <EmptyList heading="No items found 😢" subHeading="I don't have any sessions on this topic." />;
123+
}
124+
125+
return items.map(({ talkTitle, talkSlug, talkCategory }, index) => (
126+
<OrderedListItem
127+
key={talkSlug}
128+
label={talkTitle}
129+
index={reversedIndexOf(items.length, index)}
130+
href={talkSlug}
131+
prefix={renderPrefix(talkCategory)}
132+
/>
133+
));
134+
}
135+
136+
// ---------------------------------------------------------------------------
137+
// UI: AllTalksListSkeleton
138+
// ---------------------------------------------------------------------------
139+
140+
type AllTalksListSkeletonProps = {
141+
items: number;
142+
};
143+
144+
function AllTalksListSkeleton({ items }: AllTalksListSkeletonProps) {
145+
const itemSkeletons = useMemo(() => Array.from(Array(items).keys()), [items]);
146+
const { theme } = useTheme();
147+
148+
return (
149+
<div className="mb-6">
150+
{itemSkeletons.map((skeletonId) => {
151+
return (
152+
<div key={skeletonId} className="w-full border-b border-gray-200 py-3 dark:border-gray-700">
153+
<ContentLoader
154+
speed={1}
155+
viewBox="0 0 400 25"
156+
backgroundColor={theme === 'dark' ? colors.gray[800] : colors.gray[300]}
157+
foregroundColor={theme === 'dark' ? colors.gray[600] : colors.gray[100]}
158+
>
159+
<rect x="8" y="6" rx="4" ry="4" width="15" height="15" />
160+
<rect x="50" y="6" rx="4" ry="4" width="350" height="15" />
161+
</ContentLoader>
162+
</div>
163+
);
164+
})}
165+
</div>
166+
);
167+
}

src/components/shared/dropdown-menu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const DropdownMenu: FunctionComponent<DropdownMenuProps> = (props) => {
5555

5656
{items.map((item) => {
5757
const isChecked = multiSelect ? selectedItems.includes(item.id) : currentItem === item.id;
58-
58+
5959
return (
6060
<DropdownMenuPrimitive.CheckboxItem
6161
key={item.id}

src/pages/talks/index.tsx

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { InferGetStaticPropsType, NextPage } from 'next';
22
import { NextSeo as Metadata } from 'next-seo';
3-
import { Suspense, useState } from 'react';
43

54
import { Routes, siteMetadata } from 'config/constants';
65

@@ -9,15 +8,13 @@ import TalksContentService from 'services/content/talks';
98
import { MetadataConfig, generateOpenGraphImage } from 'utils/open-graph';
109

1110
import Link from 'components/shared/link';
12-
import SearchBar, { SearchBarProps } from 'components/shared/seach-bar';
1311
import SectionContainer from 'components/shared/section-container';
1412
import Typography from 'components/shared/typography';
1513

1614
import Layout from 'components/layouts/page';
1715

1816
import ActiveTalksSection from 'components/pages/talks/active-talks-section';
1917
import AllTalksSection from 'components/pages/talks/all-talks-section';
20-
import AllTalksSectionSkeleton from 'components/pages/talks/all-talks-section-skeleton';
2118
import PhotoHighlightsSection from 'components/pages/talks/photo-highlights-section';
2219
import TopicHighlightsSection from 'components/pages/talks/topic-highlights-section';
2320
import UpcomingTalksSection from 'components/pages/talks/upcoming-talks-section';
@@ -89,12 +86,6 @@ const Page: NextPage<Props> = (props) => {
8986
} = props;
9087
const { citiesTotal, countriesTotal, talksTotal, eventsTotal } = talksStats;
9188

92-
const [searchTerm, setSearchTerm] = useState('');
93-
94-
const onChange: SearchBarProps['onChange'] = (evt) => {
95-
setSearchTerm(evt.target.value);
96-
};
97-
9889
return (
9990
<>
10091
<Metadata
@@ -107,11 +98,7 @@ const Page: NextPage<Props> = (props) => {
10798
images: [{ url: openGraphImage }],
10899
}}
109100
/>
110-
<Layout
111-
heading={metadata.description}
112-
headingGradient="borealis"
113-
subHeading={<SearchBar label={`Search topics, events and places`} onChange={onChange} />}
114-
>
101+
<Layout heading={metadata.description} headingGradient="borealis">
115102
<SectionContainer className="prose dark:prose-invert">
116103
<Typography.p>
117104
{`I've`} been speaking and learning in public since 2015, mostly about web performance,
@@ -133,12 +120,9 @@ const Page: NextPage<Props> = (props) => {
133120
</Typography.p>
134121
</SectionContainer>
135122

136-
<Suspense fallback={<AllTalksSectionSkeleton items={5} />}>
137-
<AllTalksSection items={allTalks} searchTerm={searchTerm} />
138-
</Suspense>
139-
140123
<UpcomingTalksSection items={upcomingSessions} />
141124
<ActiveTalksSection items={activeTalks} />
125+
<AllTalksSection items={allTalks} />
142126
<TopicHighlightsSection title="React Highlights" items={reactTalks} />
143127
<YoutubeHighlightsSection items={youtubeHighlights} />
144128
<PhotoHighlightsSection items={featuredTalks} />

0 commit comments

Comments
 (0)