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
36import { isEmpty , reversedIndexOf } from 'utils/array' ;
47import { useSearch } from 'utils/search' ;
@@ -7,6 +10,7 @@ import Chip from 'components/shared/chip';
710import DropdownMenu from 'components/shared/dropdown-menu' ;
811import EmptyList from 'components/shared/empty-list' ;
912import OrderedListItem from 'components/shared/ordered-list-item' ;
13+ import SearchBar , { SearchBarProps } from 'components/shared/seach-bar' ;
1014import SectionContainer from 'components/shared/section-container' ;
1115import SectionHeading from 'components/shared/section-heading' ;
1216
@@ -27,7 +31,6 @@ type Schema = {
2731
2832export 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
113105export 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+ }
0 commit comments