Skip to content

Commit

Permalink
Refactor: 모바일 네브바 추가 UI #116
Browse files Browse the repository at this point in the history
  • Loading branch information
jingoworld committed Dec 25, 2023
1 parent 7cfff14 commit b305f89
Show file tree
Hide file tree
Showing 4 changed files with 396 additions and 0 deletions.
96 changes: 96 additions & 0 deletions src/components/navbar/MobileNavBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import CssBaseline from '@mui/material/CssBaseline';
import BottomNavigation from '@mui/material/BottomNavigation';
import BottomNavigationAction from '@mui/material/BottomNavigationAction';
import HomeIcon from '@mui/icons-material/Home';
import SearchIcon from '@mui/icons-material/Search';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import PersonIcon from '@mui/icons-material/Person';
import Paper from '@mui/material/Paper';
import { useMediaQuery } from '@mui/material';
import Badge, { BadgeProps } from '@mui/material/Badge';
import { styled } from '@mui/material/styles';
import { useTheme } from '@mui/material';
import { useLocation, useNavigate } from 'react-router-dom';
import { useCart } from '../../lib/store';

export default function MobileNavBar() {
const theme = useTheme();
const { items: cartItems } = useCart();
const cartItemsCount = cartItems.length;
const matchesXS = useMediaQuery(theme.breakpoints.down('sm'));
const navigate = useNavigate();
const location = useLocation();

const routeToValueMap: { [key: string]: number } = {
'/': 0,
'/search': 1,
'/cart': 2,
'/user': 3,
};

const currentValue = routeToValueMap[location.pathname];

const handleChange = (_: React.ChangeEvent<{}>, newValue: number) => {
switch (newValue) {
case 0:
navigate('/');
break;
case 1:
navigate('/search');
break;
case 2:
navigate('/cart');
break;
case 3:
navigate('/user');
break;
}
};

return (
<Box sx={{ pb: 7 }}>
<CssBaseline />
{matchesXS && (
<Paper
sx={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
borderTop: '1px solid rgba(0, 0, 0, 0.12)',
}}
elevation={0}
>
<BottomNavigation
value={currentValue}
onChange={handleChange}
showLabels
>
<BottomNavigationAction label="홈" icon={<HomeIcon />} />
<BottomNavigationAction label="검색" icon={<SearchIcon />} />
<BottomNavigationAction
label="장바구니"
icon={
<StyledBadge badgeContent={cartItemsCount} color="error">
<ShoppingCartIcon />
</StyledBadge>
}
/>
<BottomNavigationAction label="마이" icon={<PersonIcon />} />
</BottomNavigation>
</Paper>
)}
</Box>
);
}

const StyledBadge = styled(Badge)<BadgeProps>(({ theme }) => ({
'& .MuiBadge-badge': {
right: -3,
top: 13,
border: `2px solid ${theme.palette.background.paper}`,
padding: '0 4px',
},
}));
59 changes: 59 additions & 0 deletions src/components/navbar/MobilePaymentBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import Box from '@mui/material/Box';
import CssBaseline from '@mui/material/CssBaseline';
import Button from '@mui/material/Button';
import Paper from '@mui/material/Paper';
import { useMediaQuery, useTheme } from '@mui/material';
import { useNavigate } from 'react-router-dom';

export default function MobilePaymentBar({ totalCost }: { totalCost: number }) {
const theme = useTheme();
const matchesXS = useMediaQuery(theme.breakpoints.down('sm'));
const navigate = useNavigate();

const handlePaymentClick = () => {
navigate('/checkout');
};

const formattedTotalCost = totalCost.toLocaleString('ko-KR', {
style: 'currency',
currency: 'KRW',
});

return (
<Box sx={{ pb: 7 }}>
<CssBaseline />
{matchesXS && (
<Paper
sx={{
position: 'fixed',
bottom: 0, // Height of your mobile nav bar
left: 0,
right: 0,
display: 'flex',
justifyContent: 'center',
borderRadius: 0,
borderTop: '1px solid rgba(0, 0, 0, 0.12)',
height: '70px',
}}
elevation={0}
>
<Button
variant="contained"
sx={{
margin: theme.spacing(1),
backgroundColor: 'primary', // Match the background color to your design
color: '#fff', // Match the text color to your design
width: 'calc(100% - 16px)', // Full width with padding
maxWidth: theme.breakpoints.values.sm, // Max width at small breakpoint
fontSize: '1rem',
fontWeight: 700,
}}
onClick={handlePaymentClick}
>
{formattedTotalCost} 주문하기
</Button>
</Paper>
)}
</Box>
);
}
90 changes: 90 additions & 0 deletions src/components/navbar/MobileProductActionBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { styled } from '@mui/material/styles';
import FavoriteIcon from '@mui/icons-material/Favorite';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import ShoppingCartOutlinedIcon from '@mui/icons-material/ShoppingCartOutlined';
import PaymentIcon from '@mui/icons-material/Payment';
import useBookmark from '../../hooks/useBookmark';
import useAddToCart from '../../hooks/useAddToCart';

const StyledButton = styled(Button)(({ theme }) => ({
flexGrow: 1,
margin: theme.spacing(1),
maxWidth: '100%',
}));

interface ActionBarProps {
product: any;
userId: any;
productId: any;
handlePurchase: any;
isLoggedIn: boolean;
}

export default function MobileProductActionBar({
product,
userId,
productId,
handlePurchase,
isLoggedIn,
}: ActionBarProps) {
const { isBookmarked, addBookmark, removeBookmark } = useBookmark(
userId,
productId,
);
const addProductToCart = useAddToCart();

const handleToggleBookmark = () => {
if (!isLoggedIn) {
return;
} else {
if (isBookmarked) {
removeBookmark();
alert('북마크가 삭제되었습니다.');
} else {
addBookmark();
alert('북마크가 추가되었습니다.');
}
}
};

return (
<Box
sx={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
display: 'flex',
justifyContent: 'space-around',
borderTop: '1px solid rgba(0, 0, 0, 0.12)',
backgroundColor: 'white',
zIndex: 1100,
}}
>
<StyledButton
variant="contained"
startIcon={<PaymentIcon />}
onClick={handlePurchase}
>
구매하기
</StyledButton>
<StyledButton
variant="outlined"
startIcon={<ShoppingCartOutlinedIcon />}
onClick={() => addProductToCart(product)}
>
장바구니
</StyledButton>

<StyledButton
variant="outlined"
startIcon={isBookmarked ? <FavoriteIcon /> : <FavoriteBorderIcon />}
onClick={handleToggleBookmark}
>
찜하기
</StyledButton>
</Box>
);
}
151 changes: 151 additions & 0 deletions src/components/navbar/NavigationBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import {
AppBar,
Toolbar,
Typography,
Button,
Box,
IconButton,
useMediaQuery,
FormControl,
Select,
MenuItem,
} from '@mui/material';
import GridViewRoundedIcon from '@mui/icons-material/GridViewRounded';
import TuneIcon from '@mui/icons-material/Tune';
import AppsRoundedIcon from '@mui/icons-material/AppsRounded';
import { styled, useTheme } from '@mui/material/styles';
import { useState } from 'react';
import { SORT_OPTIONS } from '../../constants';
import CustomTooltip from '../CustomTooltip';

interface INavigationBar {
totalProducts: number;
handleToggel: () => void;
handleSort: (value: string) => void;
handleDisplayChange: (value: number) => void;
isSidebarOpen: boolean;
}

export default function NavigationBar({
totalProducts,
handleToggel,
isSidebarOpen,
handleSort,
handleDisplayChange,
}: INavigationBar) {
const [selectedSortOrder, setSelectedSortOrder] = useState<string>('최신순');
const [itemsPerPage, setItemsPerPage] = useState<number>(4);
const theme = useTheme();
const isMdDown = useMediaQuery(theme.breakpoints.down('md'));

const onSortChange = (sortValue: string) => {
setSelectedSortOrder(sortValue);
handleSort(sortValue);
};

const handleGridChange = (value: number) => {
setItemsPerPage(value);
handleDisplayChange(value);
};

const isSortSelected = (sortOrder: string) => {
return selectedSortOrder === sortOrder;
};

return (
<StickyNavbar color="inherit">
<NavbarContent>
<Box sx={{ display: 'flex', flexGrow: 1, alignItems: 'center' }}>
<Button variant="text" color="inherit" onClick={handleToggel}>
{isSidebarOpen ? '필터 닫기' : '필터 열기'}
</Button>
<CustomTooltip title="필터 열기/닫기">
<IconButton onClick={handleToggel}>
<TuneIcon
style={{
color: isSidebarOpen ? 'inherit' : 'darkgray',
}}
/>
</IconButton>
</CustomTooltip>
<Typography variant="h6">{totalProducts}개의 상품</Typography>
</Box>

{isMdDown ? (
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<FormControl sx={{ m: 1, minWidth: 120 }} size="small">
<Select
labelId="filter-select-label"
id="filter-select"
value={selectedSortOrder}
sx={{ fontSize: '0.9rem', borderRadius: 0 }}
onChange={(event) => onSortChange(event.target.value as string)}
>
{SORT_OPTIONS.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
</Box>
) : (
<Box sx={{ marginLeft: 'auto' }}>
{SORT_OPTIONS.map((option) => (
<Button
key={option.value}
value={option.value}
color="inherit"
onClick={() => onSortChange(option.value)}
sx={{
fontWeight: isSortSelected(option.value) ? '700' : '300',
}}
>
{option.label}
</Button>
))}
<CustomTooltip title="아이템 크게 보기">
<IconButton
onClick={() => handleGridChange(4)}
sx={{
color: itemsPerPage === 4 ? 'inherit' : 'darkgray',
}}
>
<GridViewRoundedIcon />
</IconButton>
</CustomTooltip>
<CustomTooltip title="아이템 많이 보기">
<IconButton
onClick={() => handleGridChange(8)}
sx={{
color: itemsPerPage === 8 ? 'inherit' : 'darkgray',
}}
>
<AppsRoundedIcon />
</IconButton>
</CustomTooltip>
</Box>
)}
</NavbarContent>
</StickyNavbar>
);
}

const StickyNavbar = styled(AppBar)(({ theme }) => ({
position: 'sticky',
top: '64px',
width: '100%',
margin: 0,
padding: 0,
boxShadow: 'none',
borderBottom: '1px solid rgba(0, 0, 0, 0.12)',
zIndex: theme.zIndex.drawer + 1,
}));

const NavbarContent = styled(Toolbar)({
display: 'flex',
justifyContent: 'space-between',
padding: 0,
margin: '0 auto',
width: '100%',
});

0 comments on commit b305f89

Please sign in to comment.