Skip to content
This repository was archived by the owner on Jan 23, 2024. It is now read-only.

Commit be7fd4a

Browse files
authored
Merge pull request #58 from bryg217/refactor/resources-page-refactor
Refactor The Resources Page
2 parents a77a2ec + 56baf19 commit be7fd4a

File tree

6 files changed

+195
-68
lines changed

6 files changed

+195
-68
lines changed

i18n/ui.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@
7979
"title": "Community Resources",
8080
"description": "A rich compilation of technical descriptions and detailed information of how Chakra UI works.",
8181
"message": "A rich compilation of technical descriptions and detailed information of how Chakra UI works.",
82+
"searchFilter": {
83+
"helperText": "Filter by resource title.",
84+
"label": "Search",
85+
"placeholder": "react"
86+
},
8287
"talks": {
8388
"title": "Talks"
8489
},

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"react-live": "^2.3.0",
5757
"react-lorem-component": "^0.13.0",
5858
"react-player": "^2.9.0",
59+
"react-responsive-masonry": "^2.1.4",
5960
"react-spinners": "^0.11.0",
6061
"react-table": "^7.7.0",
6162
"remark": "^14.0.1",

pages/resources.tsx

Lines changed: 119 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
1-
import { Box, Heading, SimpleGrid, Stack, Text } from '@chakra-ui/react'
1+
import {
2+
Box,
3+
Text,
4+
Tab,
5+
Tabs,
6+
TabList,
7+
TabPanel,
8+
TabPanels,
9+
Input,
10+
FormControl,
11+
FormLabel,
12+
FormHelperText,
13+
} from '@chakra-ui/react'
214
import PageContainer from 'components/page-container'
315
import ResourceCard, { Resource } from 'components/resource-card'
416
import Sidebar from 'components/sidebar/sidebar'
517
import resources from 'configs/resources.json'
618
import { getRoutes } from 'layouts/mdx'
719
import groupBy from 'lodash/groupBy'
820
import * as React from 'react'
9-
import { t } from 'utils/i18n'
1021
import { FaMicrophone, FaPenSquare, FaVideo } from 'react-icons/fa'
22+
import { useFormik } from 'formik'
23+
import Masonry, { ResponsiveMasonry } from 'react-responsive-masonry'
24+
import { t } from 'utils/i18n'
25+
import { filterResources } from 'utils/filter-resources'
1126

1227
function Resources() {
1328
/**
@@ -18,6 +33,10 @@ function Resources() {
1833
const data = resources.data as Resource[]
1934
const groups = groupBy(data, 'type')
2035

36+
const BLOGS = t('resources.blogs.title')
37+
const TALKS = t('resources.talks.title')
38+
const VIDEOS = t('resources.videos.title')
39+
2140
return (
2241
<PageContainer
2342
sidebar={<Sidebar routes={routes} />}
@@ -27,24 +46,48 @@ function Resources() {
2746
}}
2847
>
2948
<Text mt='2'>{t('resources.message')}</Text>
30-
31-
<Stack spacing='12'>
32-
<ResourceSection
33-
title={t('resources.talks.title')}
34-
resources={groups.talk}
35-
icon={FaMicrophone}
36-
/>
37-
<ResourceSection
38-
title={t('resources.videos.title')}
39-
resources={groups.video}
40-
icon={FaVideo}
41-
/>
42-
<ResourceSection
43-
title={t('resources.blogs.title')}
44-
resources={groups.blog}
45-
icon={FaPenSquare}
46-
/>
47-
</Stack>
49+
<Tabs colorScheme='teal' variant='enclosed' mt='6'>
50+
<TabList>
51+
<Tab>
52+
<ResourcesTabContent
53+
icon={FaMicrophone}
54+
text={TALKS}
55+
/>
56+
</Tab>
57+
<Tab>
58+
<ResourcesTabContent
59+
icon={FaVideo}
60+
text={VIDEOS}
61+
/>
62+
</Tab>
63+
<Tab>
64+
<ResourcesTabContent
65+
icon={FaPenSquare}
66+
text={BLOGS}
67+
/>
68+
</Tab>
69+
</TabList>
70+
<TabPanels>
71+
<TabPanel>
72+
<ResourceSection
73+
title={TALKS}
74+
resources={groups.talk}
75+
/>
76+
</TabPanel>
77+
<TabPanel>
78+
<ResourceSection
79+
title={VIDEOS}
80+
resources={groups.video}
81+
/>
82+
</TabPanel>
83+
<TabPanel>
84+
<ResourceSection
85+
title={BLOGS}
86+
resources={groups.blog}
87+
/>
88+
</TabPanel>
89+
</TabPanels>
90+
</Tabs>
4891
</PageContainer>
4992
)
5093
}
@@ -53,29 +96,68 @@ export default Resources
5396

5497
interface ResourceSectionProps {
5598
title: string
56-
icon: React.ElementType
5799
resources: Resource[]
58100
}
59101

60102
function ResourceSection(props: ResourceSectionProps) {
61-
const { icon, title, resources } = props
103+
const { title, resources } = props
104+
const filterInputId = `resources-filter-${title.toLowerCase()}`
105+
const formik = useFormik({
106+
initialValues: { [filterInputId]: '' },
107+
onSubmit: undefined,
108+
})
109+
const filteredResources = filterResources(formik.values[filterInputId], resources)
110+
/**
111+
* Notice, that the breakpoints don't follow conventional numbers (e.g. 768, 991).
112+
* The reason for that is that the number (e.g. 767) actually represents target
113+
* number - 1 (e.g. 768 - 1), where at target number (e.g. 768) is the point at
114+
* which the grid should switch. This might be a bug with the library.
115+
*/
116+
const masonryGridBreakpoints = { 350: 1, 580: 2, 767: 1, 990: 2 }
117+
62118
return (
63-
<Box as='section' mt='12'>
64-
<Heading as='h2' size='md'>
65-
<Box
66-
as={icon}
67-
display='inline-block'
68-
verticalAlign='middle'
69-
color='teal.500'
70-
mr='3'
119+
<Box as='section' mt='8'>
120+
<FormControl id={filterInputId} mt='8' mb='8'>
121+
<FormLabel>{t('resources.searchFilter.label')}</FormLabel>
122+
<Input
123+
onChange={formik.handleChange}
124+
placeholder={t('resources.searchFilter.placeholder')}
125+
value={formik.values[filterInputId]}
71126
/>
72-
<span>{title}</span>
73-
</Heading>
74-
<SimpleGrid mt={8} columns={[1, 2]} spacing={8}>
75-
{resources.map((item, index) => (
76-
<ResourceCard key={index} data={item} />
77-
))}
78-
</SimpleGrid>
127+
<FormHelperText>{t('resources.searchFilter.helperText')}</FormHelperText>
128+
</FormControl>
129+
<ResponsiveMasonry columnsCountBreakPoints={masonryGridBreakpoints}>
130+
<Masonry gutter='16px'>
131+
{filteredResources.map(
132+
(item, index) => (
133+
<ResourceCard key={index} data={item} />
134+
),
135+
)}
136+
</Masonry>
137+
</ResponsiveMasonry>
79138
</Box>
80139
)
81140
}
141+
142+
interface ResourcesTabContentProps {
143+
icon: React.ElementType
144+
text: string
145+
}
146+
147+
function ResourcesTabContent({
148+
icon,
149+
text
150+
}: ResourcesTabContentProps) {
151+
return (
152+
<>
153+
<Box
154+
as={icon}
155+
display='inline-block'
156+
verticalAlign='middle'
157+
color='teal.500'
158+
mr='2'
159+
/>
160+
<span>{text}</span>
161+
</>
162+
)
163+
}

src/components/resource-card.tsx

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import {
22
Badge,
3-
Box,
43
BoxProps,
54
Heading,
6-
Link,
5+
LinkBox,
6+
LinkOverlay,
77
Text,
88
useColorModeValue,
99
Wrap,
1010
WrapItem,
11+
VStack,
1112
} from '@chakra-ui/react'
1213
import * as React from 'react'
1314

@@ -30,36 +31,53 @@ function ResourceCard(props: ResourceCardProps) {
3031
const color = useColorModeValue('teal.600', 'teal.400')
3132

3233
return (
33-
<Box {...rest} maxW='360px'>
34-
<Wrap className='algolia-exclude' spacing='3' mb='2' align='center'>
35-
{tags?.map((tag, index) => (
36-
<WrapItem key={index}>
37-
<Badge
38-
as='a'
39-
rel='tag'
40-
color={color}
41-
textTransform='uppercase'
42-
fontSize='xs'
43-
fontWeight='bold'
44-
>
45-
{tag}
46-
</Badge>
47-
</WrapItem>
48-
))}
49-
</Wrap>
34+
<LinkBox
35+
{...rest}
36+
p={4}
37+
rounded='lg'
38+
transitionProperty='all'
39+
transitionDuration='slower'
40+
transitionTimingFunction='ease-out'
41+
_hover={{
42+
transform: 'scale(1.025)',
43+
boxShadow: 'var(--chakra-shadows-md)',
44+
}}
45+
bg={useColorModeValue('gray.50', 'gray.700')}
46+
>
47+
<VStack spacing={2} align='stretch'>
48+
<Wrap className='algolia-exclude' spacing='3' mb='2' align='center'>
49+
{tags?.map((tag, index) => (
50+
<WrapItem key={index} overflow='hidden'>
51+
<Badge
52+
as='a'
53+
rel='tag'
54+
color={color}
55+
textTransform='uppercase'
56+
fontSize='xs'
57+
fontWeight='bold'
58+
whiteSpace='break-spaces'
59+
>
60+
{tag}
61+
</Badge>
62+
</WrapItem>
63+
))}
64+
</Wrap>
5065

51-
<Heading as='h3' size='sm'>
52-
<Link isExternal href={url}>
53-
<span className='content'>{heading}</span>
54-
</Link>
55-
</Heading>
56-
<Text fontSize='sm' color='gray.500' mt='2'>
57-
by {author}
58-
</Text>
59-
<Text lineHeight='tall' py={2} opacity={0.8}>
60-
{description}
61-
</Text>
62-
</Box>
66+
<LinkOverlay isExternal href={url}>
67+
<VStack spacing={2} align='stretch'>
68+
<Heading as='h3' size='sm'>
69+
<span className='content'>{heading}</span>
70+
</Heading>
71+
<Text fontSize='sm' color='gray.500'>
72+
by {author}
73+
</Text>
74+
<Text lineHeight='tall' opacity={0.8}>
75+
{description}
76+
</Text>
77+
</VStack>
78+
</LinkOverlay>
79+
</VStack>
80+
</LinkBox>
6381
)
6482
}
6583

src/utils/filter-resources.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Resource } from 'components/resource-card'
2+
3+
/**
4+
* Wrapper for filtering resources, in the Resources page, by heading, as per initial
5+
* requirements; it serves as a wrapper to keep the component lean, rather than defined
6+
* inline within the component.
7+
*/
8+
export function filterResources(query: string, resources: Resource[]) {
9+
const normalizedQuery = query.trim().toLowerCase()
10+
11+
return normalizedQuery !== ''
12+
? resources.filter((resource: Resource) =>
13+
resource.heading.toLowerCase().includes(normalizedQuery),
14+
)
15+
: resources
16+
}

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9104,6 +9104,11 @@ [email protected]:
91049104
use-callback-ref "^1.2.3"
91059105
use-sidecar "^1.0.1"
91069106

9107+
react-responsive-masonry@^2.1.4:
9108+
version "2.1.4"
9109+
resolved "https://registry.yarnpkg.com/react-responsive-masonry/-/react-responsive-masonry-2.1.4.tgz#28d6058167c5b047c9df2ae41216c4fdc6ca976b"
9110+
integrity sha512-Neqkc6gM7F5d+nBvDSlKQmQOjdWNabLT2EXpVvpJb6oD8lD3hRujd8IDd1UViQR++SSTkhLcsC3Ru7hc6XjT5A==
9111+
91079112
react-simple-code-editor@^0.11.0:
91089113
version "0.11.0"
91099114
resolved "https://registry.yarnpkg.com/react-simple-code-editor/-/react-simple-code-editor-0.11.0.tgz#bb57c7c29b570f2ab229872599eac184f5bc673c"

0 commit comments

Comments
 (0)