Front/React

React를 이용한 layout 컴포넌트, React Router(리액트 라우터) 페이지 이동 구현하기

oodada 2023. 10. 9. 22:24

React를 이용한 layout 컴포넌트, React Router(리액트 라우터) 페이지 이동 구현하기

Chakra-ui를 사용해서 DashBoard의 Layout을 만들어보려고 합니다.
아래 url의 디자인을 참고해 만들었습니다.

github url

Horizon Free Dashboard Figma

1. Chakra UI 란?

Chakra UI는 React용 디자인 시스템을 제공하는 라이브러리입니다. Chakra UI는 컴포넌트 기반의 스타일링을 제공하며, 테마 설정을 통해 쉽게 스타일을 적용할 수 있습니다.

Chakra UI의 주요 특징은 다음과 같습니다:

  • 컴포넌트 기반 스타일링: Chakra UI는 다양한 컴포넌트를 제공하며, 이를 사용해 스타일을 적용할 수 있습니다.
  • 테마 설정: Chakra UI는 테마 설정을 통해 쉽게 스타일을 적용할 수 있습니다.
  • 반응형 디자인: Chakra UI는 반응형 디자인을 지원하며, 다양한 디바이스에 대응할 수 있습니다.
  • 접근성: Chakra UI는 접근성을 고려한 컴포넌트를 제공하며, 웹 접근성을 향상시킬 수 있습니다.

1. Chakra-ui 설치

  • Chakra-ui는 React용 디자인 시스템을 제공하는 라이브러리이다.
npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion

2. Chakra-ui 테마 설정

  • Chakra-ui의 테마를 설정할 수 있다.
    https://v2.chakra-ui.com/docs/styled-system/customize-theme
  • foundations (디자인 시스템의 기반 요소들을 담당)
    • 색상(Color): 프로젝트 전체에서 사용되는 색상 팔레트를 정의합니다.
    • 타이포그래피(Typography): 글꼴, 글자 크기, 줄 높이 등의 기본 텍스트 스타일을 설정합니다.
    • 공간(Spacing): 마진, 패딩 등의 공간을 일관되게 관리할 수 있는 공간 체계를 정의합니다.
    • 경계선 스타일(Border Styles): 예를 들어 borders.js에서는 테두리의 두께, 스타일, 색상 등을 정의할 수 있습니다.
  • components (특정 UI 컴포넌트의 스타일을 정의하는데 사용)
    • Button: button.js 파일에서는 버튼 컴포넌트의 모양, 크기, 색상 변형 등을 정의하고 조정할 수 있습니다.
    • Input, Card, Modal 등 다른 컴포넌트들도 각각의 파일에서 커스텀 스타일을 지정할 수 있습니다.

- theme 디렉토리 생성

  • src 디렉토리에 theme 디렉토리를 생성한다.
|-- theme
    |-- theme.js
    |-- chakraStyles.js
    |-- foundations
        |-- borders.js
    |-- components
        |-- button.js

- theme.js

// theme/theme.js
import { extendTheme } from '@chakra-ui/react'

// Global style overrides
import styles from './styles'

// Foundational style overrides
import borders from './foundations/borders'

// Component style overrides
import Button from './components/button'

const overrides = {
    styles,
    borders,
    // Other foundational style overrides go here
    components: {
        Button,
        // Other components go here
    },
}

export default extendTheme(overrides)

- chakraStyles.js

// theme/chakraStyles.js
import { mode } from '@chakra-ui/theme-tools'
import { lighten } from 'polished'

export const variablesC = {
    $maxW: '1280px',
}

export const globalStyles = {
    colors: {
        primary: '#422afb',
        pirmaryLight: lighten(0.1, '#422AFB'),
        secondary: '#83D9FB',
        secondaryLight: lighten(0.1, '#83D9FB'),
        tertiary: '#22C55F',
        tertiaryLight: lighten(0.1, '#22C55F'),

        bgDefault: '#F5F7FF',

        lineDefault: '#E5E7EB',

        white: '#FFF',
        black: '#000',

        gray: {
            50: '#F9FAFB',
            100: '#F3F4F6',
            200: '#E5E7EB',
            300: '#D1D5DB',
            400: '#9CA3AF',
            500: '#6B7280',
            600: '#4B5563',
            700: '#374151',
            800: '#1F2937',
            900: '#111827',
        },
    },
    radii: {
        none: '0',
        sm: '8px',
        md: '12px',
        lg: '16px',
        xl: '20px',
        full: '9999px',
    },
    styles: {
        global: (props) => ({
            body: {
                minW: '330px',
                // overflowX: 'hidden',
                fontFamily: '"DM Sans", "sans-serif"',
                letterSpacing: '-0.5px',
                fontSize: '16px',
                fontWeight: '400',
                lineHeight: '1.5',
                color: mode('gray.900', 'white')(props),
                bg: mode('bgDefault', 'navy.900')(props),
                // bg: 'url("/bg-/m.png") no-repeat 50% 0 / 420px auto',
            },
            '#root': {
                // opacity: 0.7,
            },
            input: {
                color: 'gray.700',
            },
            html: {
                fontFamily: '"DM Sans", "sans-serif"',
            },
            'ul > li': {
                listStyle: 'none',
            },
            '.chakra-container': {
                maxWidth: 'calc(1280px + var(--chakra-space-4) * 2) !important',
            },
            '.blind': {
                position: 'absolute',
                width: 0,
                height: 0,
                margin: -1,
                padding: 0,
                overflow: 'hidden',
                clip: 'rect(0, 0, 0, 0)',
                border: 0,
                lineHeight: 0,
            },
        }),
    },
}

ButtonStyles.js

// theme/components/button.js
import { mode } from '@chakra-ui/theme-tools'
export const buttonStyles = {
    components: {
        Button: {
            baseStyle: {
                borderRadius: '0',
                boxShadow: '45px 76px 113px 7px rgba(112, 144, 176, 0.08)',
                transition: '.25s all ease',
                boxSizing: 'border-box',
                _focus: {
                    boxShadow: 'none',
                },
                _active: {
                    boxShadow: 'none',
                },
            },
            variants: {
                btnMarket: () => ({
                    bg: 'primary',
                    color: 'white',
                    fontWeight: '500',
                    borderRadius: '70px',
                    px: '24px',
                    py: '5px',
                    fontSize: 'sm',
                    _focus: {
                        bg: 'secondary',
                    },
                    _active: {
                        bg: 'secondary',
                    },
                    _hover: {
                        bg: 'secondary',
                    },
                }),
                icon: () => ({
                    w: '45px',
                    h: '45px',
                    minW: 'none',
                    bg: 'transparent',
                    color: 'gray.500',
                    borderRadius: '50%',
                    _focus: {
                        bg: 'gray.100',
                    },
                    _active: {
                        bg: 'gray.100',
                    },
                    _hover: {
                        bg: 'gray.100',
                    },
                }),
                link12: () => ({
                    height: '32px',
                    fontSize: '12px',
                    color: 'white',
                    'padding-inline-start': '0',
                    'padding-inline-end': '0',
                }),
                outline: () => ({
                    borderRadius: '0',
                    bg: ['red', 'brand.200', 'brand.900', 'white'],
                    // color: ['white', null, null, 'brand.500'],
                    color: { base: 'white', lg: 'brand.500' },
                }),
                brand: (props) => ({
                    bg: mode('brand.500', 'brand.400')(props),
                    color: 'yellow.500',
                    _focus: {
                        bg: mode('brand.500', 'brand.400')(props),
                    },
                    _active: {
                        bg: mode('brand.500', 'brand.400')(props),
                    },
                    _hover: {
                        bg: mode('brand.600', 'brand.400')(props),
                    },
                }),
            },
        },
    },
}

3. Chakra-ui App 적용

  • src 디렉토리에 App.jsx 파일을 수정한다.
import React from 'react'
import { ChakraProvider } from '@chakra-ui/react'
import { BrowserRouter as Router } from 'react-router-dom'
import Routers from './Routers'
import theme from './theme/theme'

const App = () => {
    return (
        <ChakraProvider theme={theme}>
            <Router>
                <Routers />
            </Router>
        </ChakraProvider>
    )
}

export default App

1. Layout 컴포넌트 만들기

  • Layout 컴포넌트는 Layout, Header, Footer, Content 컴포넌트를 사용하여 만들 수 있다.
  • Layout, Header, Footer, Content 컴포넌트는 각각 다음과 같이 만들 수 있다.

폴더 이름 가이드

  • 컴포넌트를 만들 때, 컴포넌트 이름과 파일 이름을 같게 만들어 주는 것이 좋다.
  • 보통 디렉토리 파일명은 소문자로 한다.
  • 컴포넌트의 파일 이름은 PascalCase(파스칼 표기법, 대문자로 시작)로 작명한다.
  • Section 같은 불분명한 의미는 짓지 않는다. 명확한 이름으로 표시를 한다. 어쩔수 없이 길어져도 괜찮다.

아래 경로로 폴더를 만들어준다.

  • src/components/layout/Header.jsx
  • src/components/layout/Footer.jsx
  • src/components/layout/Layout.jsx

Header 컴포넌트

  • src/components/layout/Header.jsx
import React from 'react'
import { Link } from 'react-router-dom'
import styled from 'styled-components'
import Gnb from './Gnb'
import { Heading } from '@chakra-ui/react'
import DividerCus from '../../common/DividerCus'

const Header = () => {
    return (
        <HeaderWapper>
            <HeaderStyle as="h1" id="logo">
                <Link to="/">
                    <span>horizon</span> free
                </Link>
            </HeaderStyle>
            <DividerCus />
            <Gnb />
        </HeaderWapper>
    )
}

const HeaderStyle = styled(Heading)`
    a {
        display: block;
        transition: color 0.3s ease-in-out;
        text-transform: uppercase;
        text-align: center;
        font-size: 26px;
        /* font-family: 'Poppins', sans-serif; */
        color: var(--primary-dark);
        span {
            font-weight: 700;
        }
        &:hover {
            color: var(--primary);
        }
    }
`

const HeaderWapper = styled.header`
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 290px;
    padding: 50px 32px;
    background-color: var(--white);
`

export default Header
  • src/components/layout/Gnb.jsx
import React from 'react'
import { Link, useLocation } from 'react-router-dom'
import styled from 'styled-components'
import { AiFillHome, AiOutlineShoppingCart } from 'react-icons/ai'
import { BsBarChartFill } from 'react-icons/bs'

const Gnb = () => {
    const location = useLocation()

    return (
        <GnbWapper>
            <ul>
                <li className={location.pathname === '/' ? 'active' : ''}>
                    <Link to="/">
                        <AiFillHome /> Dashboard
                    </Link>
                </li>
                <li className={location.pathname === '/marketplace' ? 'active' : ''}>
                    <Link to="/marketplace">
                        <AiOutlineShoppingCart />
                        NFT Marketplace
                    </Link>
                </li>
                <li className={location.pathname === '/tables' ? 'active' : ''}>
                    <Link to="/tables">
                        <BsBarChartFill />
                        Tables
                    </Link>
                </li>
                <li className={location.pathname === '/kanban' ? 'active' : ''}>
                    <Link to="/kanban">
                        <BsBarChartFill /> Kanban
                    </Link>
                </li>
                <li className={location.pathname === '/profile' ? 'active' : ''}>
                    <Link to="/profile">
                        <BsBarChartFill /> Profile
                    </Link>
                </li>
                <li className={location.pathname === '/signin' ? 'active' : ''}>
                    <Link to="/signin">
                        <BsBarChartFill /> Sign in
                    </Link>
                </li>
            </ul>

            {/* <ul>
                <li className={location.pathname === '/' ? 'active' : ''}>
                    <Link to="/">홈</Link>
                </li>
                <li className={location.pathname === '/about' ? 'active' : ''}>
                    <Link to="/about">소개</Link>
                </li>
            </ul> */}
        </GnbWapper>
    )
}

const GnbWapper = styled.nav`
    ul {
        li {
            position: relative;
            margin-left: 3px;
            &::after {
                opacity: 0;
                transition: opacity 1s;
            }
            &.active {
                &::after {
                    content: '';
                    position: absolute;
                    top: 50%;
                    right: -32px;
                    transform: translateY(-50%);
                    width: 4px;
                    height: 36px;
                    border-radius: 25px;
                    background: var(--primary);
                    opacity: 1;
                }
                a {
                    color: var(--primary-dark);
                    font-weight: 700;
                    svg {
                        color: var(--primary);
                    }
                }
            }
            a {
                display: flex;
                align-items: center;
                gap: 10px;
                padding: 16px 0;
                color: var(--secondary-grey-600);
                font-weight: 500;
                &:hover {
                    color: var(--primary-dark);
                }
            }
            svg {
                font-size: 20px;
            }
        }
    }
`

export default Gnb

Title, History 컴포넌트

  • src/components/common/Title.jsx
import React from 'react';
import { VStack, Heading } from '@chakra-ui/react';

const Title = ({ title }) => {
return (
<VStack alignItems={'flex-start'} spacing={4}>
<Heading as={'h2'} fontSize={'34px'} fontWeight={700}>
{title}
</Heading>
{/_ <Text variant={'txt164'}>{props.desc}</Text> _/}
</VStack>
);
};

export default Title;
  • src/components/Common/History.jsx
import { Link } from 'react-router-dom'
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink } from '@chakra-ui/react'

const History = ({ pagename }) => {
    // const { pagename } = props;
    return (
        <Breadcrumb color="secondary_grey_700" fontSize="14px" fontWeight="500">
            <BreadcrumbItem>
                <BreadcrumbLink as={Link} to="/">
                    Home
                </BreadcrumbLink>
            </BreadcrumbItem>
            <BreadcrumbItem isCurrentPage>
                <BreadcrumbLink as={Link} to={`/${pagename.toLowerCase()}`}>
                    {pagename}
                </BreadcrumbLink>
            </BreadcrumbItem>
        </Breadcrumb>
    )
}

export default History

Footer 컴포넌트

  • src/components/layout/Footer.jsx
import React from 'react'
import styled from 'styled-components'

const Footer = () => {
    return (
        <FooterWrapper>
            <p>footer</p>
        </FooterWrapper>
    )
}

const FooterWrapper = styled.footer`
    display: flex;
    justify-content: center;
    align-items: center;
    height: 70px;
    background-color: #eee;
`

export default Footer

Layout 컴포넌트

  • src/components/layout/layout.jsx
import React from 'react'
import Header from './header/Header'
import styled from 'styled-components'
import Title from '../common/Tilte'
import History from '../common/History'
import { Box } from '@chakra-ui/react'

const Layout = ({ title, pagename, children }) => {
    // const { pagename, children } = props;
    return (
        <Wrap>
            <Header />
            <main id="main">
                <Box p="0 0 35px 10px">
                    <History pagename={pagename} />
                    <Title title={title} />
                </Box>
                {children}
            </main>
        </Wrap>
    )
}

const Wrap = styled.div`
    display: flex;
    flex-direction: column;
    padding-left: 290px;
    background: var(--secondary-grey-300, #f4f7fe);
    #main {
        min-height: 100vh;
        padding: 50px 20px;
    }
`

export default Layout

2. Page 만들기

  • src/pages/index.jsx
import React from 'react'
import Layout from '../components/layout/Layout'

const Home = () => {
    return (
        <Layout title="DashBoard" pagename="DashBoard">
            Dashboard
        </Layout>
    )
}

export default Home

3. App.js 에서 라우팅하기

  • src/App.js
import React from 'react'
import { Route, Routes } from 'react-router-dom'
import Home from './pages'
import { ChakraProvider, CSSReset } from '@chakra-ui/react'
import GlobalStyles from './assets/styles/GlobalStyles.styles'
import theme from './theme/'
import MarketPlace from './pages/MarketPlace'
import Tables from './pages/Tables'
import Kanban from './pages/Kanban'
import Profile from './pages/Profile'
import SignIn from './pages/SignIn'

function App() {
    return (
        <ChakraProvider theme={theme}>
            <CSSReset />
            <GlobalStyles />
            <BrowserRouter>
                <Routes>
                    <Route path="/" element={<Home />} />
                    <Route path="/marketplace" element={<MarketPlace />} />
                    <Route path="/tables" element={<Tables />} />
                    <Route path="/kanban" element={<Kanban />} />
                    <Route path="/profile" element={<Profile />} />
                    <Route path="/signin" element={<SignIn />} />
                </Routes>
            </BrowserRouter>
        </ChakraProvider>
    )
}

export default App

'Front > React' 카테고리의 다른 글

React Table Data  (0) 2023.10.19
React Router를 이용한 Layout 구성하기 - React 배우기  (0) 2023.10.10
컴포넌트(Components) - React 배우기  (0) 2023.10.08
엘리먼트(Element) - React 배우기  (0) 2023.10.07
JSX - React 배우기  (0) 2023.10.07
티스토리 친구하기