In the last part we created the basic functionality with adding and removing tasks, now let's make the TODO application a bit prettier. I sketched the layout in figma using ChakraUI Figma KIT, here are two themes light and dark which we will implement.
First set the dependencies for the icons.
npm i --save @chakra-ui/icons
Write the code
Create a new Menu
component, which will have control buttons. Right now there is only a theme switch, but in the future we will add buttons for microphone, speakers, camera and possibly biometric authentication.
src/components/Menu/index.tsx
import { MoonIcon, SunIcon, } from "@chakra-ui/icons"
import { HStack, IconButton, StackProps, useColorMode } from "@chakra-ui/react"
export const Menu: React.FC<StackProps> = (props) => {
const { colorMode, toggleColorMode } = useColorMode()
return (
<HStack justify='end' {...props}>
<IconButton variant='ghost' aria-label="change theme"
onClick={toggleColorMode}
icon={colorMode === 'light' ? <SunIcon color='yellow.500' /> : <MoonIcon />} />
</HStack>
)
}
Stylize the existing TaskList
and TaskCreator
components.
src/components/TaskList/index.tsx
const TaskCard: React.FC<TaskCardProps> = ({ id, name, onDelete }) => {
const handleDelete = () => {
onDelete(id)
}
return (
<Card
direction={{ base: 'column', sm: 'row' }}
overflow='hidden'
>
<CardBody>
<Heading size='md'>{name}</Heading>
</CardBody>
<IconButton
aria-label='remove task'
colorScheme="red"
height='auto'
variant='insideCard'
borderTopLeftRadius='0'
borderBottomLeftRadius='0'
icon={<DeleteIcon color="white" />}
onClick={handleDelete} />
</Card>
)
}
export const TaskList = () => {
...
if (error) {
return (
<Center>
<Text color='red' fontSize={'xl'}>
{'Network Error'}
</Text>
</Center>
)
}
if (isLoadingDelayed) {
return (
<Center>
<Spinner size='xl' />
</Center>
)
}
return (
<Stack spacing={4} pr={'8'}>
{...tasks.map((task, idx) => (
<TaskCard key={task.id || `${task.name}_${idx}`} {...task} onDelete={handleDeleteTask} />
))}
</Stack>
)
}
src/components/TaskCreator/index.tsx
return (
<form onSubmit={handleAddTask} {...props}>
<Card
direction={{ base: 'column', sm: 'row' }}
overflow='hidden'
>
<CardBody pt='3' pb='3' pl='3' pr='8'>
<InputGroup>
<Input variant='outline' placeholder="Input a task" onChange={handleChange} />
</InputGroup>
</CardBody>
<IconButton
type="submit"
aria-label='add task'
colorScheme="green"
height='auto'
variant='insideCard'
borderTopLeftRadius='0'
borderBottomLeftRadius='0'
icon={<AddIcon color="white" />} />
</Card>
</form>
)
Basic layout.
Let's make it so that the task input field was initially a little lower than the first quarter of the screen, and when adding tasks and appearing scroll, there is an opportunity to scroll it up after which it will stick to the top edge and the tasks will go as if under it.
src/pages/index.tsx
...
<Container pt='25%' pb='5%' style={{ background: 'inherit' }}>
<Stack spacing='8' style={{ background: 'inherit' }}>
<Menu style={{
position: 'sticky', top: '0rem', zIndex: '999',
background: 'inherit',
padding: '0.4rem 0',
paddingLeft: '1rem',
marginLeft: '-1rem',
}} />
<TaskCreator style={{
position: 'sticky', top: '3.2rem', zIndex: '999',
background: colorMode === 'light' ? 'inherit' : 'transparent',
paddingLeft: '1rem',
marginLeft: '-1rem',
}}/>
<TaskList />
</Stack>
</Container>
...
There appeared a little magic paddings and margins, they are necessary for that the shadow of cards did not appear when the card behind the input field and menu. Also need to propagate background: inherit
through all components. A bit clumsy, but anyway.
src/pages/_app.tsx
...
<style jsx global>
{`
#__next {
background: inherit;
}
`}
</style>
...
Theme switcher
In the Menu
component was colorMode variable.
const { colorMode, toggleColorMode } = useColorMode()
Now add the theme information to the Chakra provider.
src/chakra.tsx
import {
ChakraProvider,
cookieStorageManagerSSR,
localStorageManager,
} from '@chakra-ui/react'
import { GetServerSideProps } from 'next'
import { PropsWithChildren } from 'react'
import theme from './theme'
interface ChakraProps {
cookies: string
}
export const Chakra: React.FC<PropsWithChildren<ChakraProps>> = ({ cookies, children }) => {
const colorModeManager =
typeof cookies === 'string'
? cookieStorageManagerSSR(cookies)
: localStorageManager
return (
<ChakraProvider colorModeManager={colorModeManager} theme={theme}>
{children}
</ChakraProvider>
)
}
export const getServerSideProps = (async ({ req }) => {
return {
props: {
cookies: req.headers.cookie ?? '',
},
}
}) satisfies GetServerSideProps<ChakraProps>
src/theme.tsx
import { extendTheme, type ThemeConfig } from '@chakra-ui/react'
const config: ThemeConfig = {
initialColorMode: 'dark',
useSystemColorMode: false,
}
const theme = extendTheme({
config,
})
export default theme
src/pages/_document.tsx
import theme from "@/theme";
import { ColorModeScript } from "@chakra-ui/react";
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<ColorModeScript initialColorMode={theme.config.initialColorMode} type="cookie"/>
<Main />
<NextScript />
</body>
</Html>
);
}
src/pages/_app.tsx
import type { AppProps } from "next/app";
import { SWRConfig } from "swr";
import { Chakra } from "@/chakra";
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<SWRConfig
value={{
refreshInterval: 3000,
}}
>
<Chakra cookies={pageProps.cookies}>
<Component {...pageProps} />
</Chakra>
</SWRConfig>
<style jsx global>
{`
#__next {
background: inherit;
}
`}
</style>
</>
)
}
src/pages/index.tsx
...
export { getServerSideProps } from '@/chakra';
In order to keep the theme from blinking on page refresh, information about theme stored in cookies, so that Next.js on the pre-render knows what color to use.
And make the color of the buttons in the task card a little bit darker.
src/theme.ts
const theme = extendTheme({
config,
components: {
Button: {
variants: {
insideCard: (props: StyleFunctionProps) => {
const c = props.theme.colors[props.colorScheme]
return {
...props.theme.components.Button.variants.solid(props),
background: c['500'],
}
}
}
}
},
})
We are done with basic styling. Thank you for doing this with me. In the next part, we'll add animations for a task list and theme switching.
If you have any questions or suggestions please leave them in the comments.
The full code is available at GitHub
Top comments (0)