20240423 학습내용

2024. 4. 23. 11:32React

20240423 화
반응형 웹

 

import React, { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import Badge from '@mui/material/Badge';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Divider from '@mui/material/Divider';
import Drawer from '@mui/material/Drawer';
import Grid from '@mui/material/Grid';
import IconButton from '@mui/material/IconButton';
import InputBase from '@mui/material/InputBase';
import Link from '@mui/material/Link';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import MenuIcon from '@mui/icons-material/Menu';
import Paper from '@mui/material/Paper';
import SearchIcon from '@mui/icons-material/Search';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import YouTubeIcon from '@mui/icons-material/YouTube';
import VideocamIcon from '@mui/icons-material/Videocam';

import { useAuthContext } from "../context/AuthContext";
import LoginModal from "./LoginModal";
import useWatchVideo from '../hooks/useWatchVideo';

export default function SearchHeader() {
  const { keyword } = useParams();
  const navigate = useNavigate();
  const [text, setText] = useState('');
  const [isMenuOpen, setIsMenuOpen] = useState(false);
  const handleSubmit = e => {
    e.preventDefault();
    navigate(`/videos/${text}`);
  }
  useEffect(() => {
    setText(keyword || '');
  }, [keyword]);
  const { user, logout } = useAuthContext();
  const { getCount: { data: count }} = useWatchVideo(user);

  return (
    <header>
      <Stack direction={'row'} alignItems='center'>
        <Grid container>
          <Grid item xs={2} md={2} lg={3}>
            <Link href='/' style={{textDecoration: 'none', color: 'black'}}>
              <Stack direction={'row'} spacing={1} alignItems='center'>
                <YouTubeIcon color='error' fontSize="large" />
                <Typography variant="h4" 
                  sx={{fontWeight: 'bold', display: {xs: 'none', md: 'none', lg: 'flex'}}}>
                  Youtube
                </Typography>
              </Stack>
            </Link>
          </Grid>
          <Grid item xs={7} md={6} lg={4}>
            <Paper
              component="form" onSubmit={handleSubmit}
              sx={{ p:'2px 4px', display:'flex', alignItems:'center', width:'100%' }}
            >
              <InputBase
                sx={{ ml: 1, flex: 1 }}
                placeholder="검색..."
                value={text} 
                onChange={e => setText(e.target.value)}
              />
              <Divider sx={{ height: 28, m: 0.5 }} orientation="vertical" />
              <IconButton type="button" sx={{ p: 1 }} aria-label="search" onClick={handleSubmit}>
                <SearchIcon />
              </IconButton>
            </Paper>
          </Grid>
          <Grid item xs={3} md={4} lg={5}>
            <Stack direction='row' spacing={2} justifyContent='right' alignItems='center'
              sx={{display: {xs: 'none', md: 'none', lg: 'flex'}}}>
              {user && user.isAdmin && 
                <Link href='/videos/admin' underline="hover" color='primary'>
                  <Typography variant="h6">관리자 메뉴</Typography>
                </Link>}
              {user && 
                <Link href='/videos/record' underline="hover" color='primary'>
                  <Stack direction={'row'} alignItems='center' sx={{mr: 1}}>
                    <Typography variant="h6">시청기록</Typography>
                    <Badge badgeContent={count} color="primary">
                      <VideocamIcon color="action" />
                    </Badge>
                  </Stack>
                </Link>}
              {user && user.photoURL && (
                <img src={user.photoURL} alt={user.displayName} height='32' style={{borderRadius:100}} />
              )}
              {user && <Typography variant="h6">{user.displayName}</Typography>}
              {user && (
                <Button variant="outlined" onClick={logout}>
                  로그아웃
                </Button>
              )}
              {!user && <LoginModal />}
            </Stack>
            <Stack direction='row' spacing={2} justifyContent='right' alignItems='center'
              sx={{display: {xs: 'flex', md: 'flex', lg: 'none'}}}>
              {(count > 0) &&
                <Link href='/videos/record' color='primary'>
                  <Badge badgeContent={count} color="primary">
                    <VideocamIcon color="action" />
                  </Badge>
                </Link>}
              <IconButton sx={{ p: 1 }} aria-label="menu" color="inherit"
                onClick={() => setIsMenuOpen(true)}>
                <MenuIcon />
              </IconButton>
              <Drawer open={isMenuOpen} onClose={() => setIsMenuOpen(false)} anchor="right"
                sx={{ "& .MuiDrawer-paper": { height: "20%" } }}>
                <Box sx={{ width: 250 }} role="presentation" onClick={() => setIsMenuOpen(false)}>
                  <List>
                    {user && 
                      <ListItem key={'key01'} disablePadding sx={{ px: 2, py: 0.5 }}>
                        <Link href='/videos/record' color='primary'>
                          <Typography variant="h6">시청기록</Typography>
                        </Link>
                      </ListItem>}
                    {user && user.isAdmin &&
                      <ListItem key={'key02'} disablePadding sx={{ px: 2, py: 0.5 }}>
                        <Link href='/videos/admin' color='primary'>
                          <Typography variant="h6">관리자 메뉴</Typography>
                        </Link>
                      </ListItem>}
                    <ListItem key={'key03'} disablePadding sx={{ px: 2, py: 0.5 }}>
                      {user && (
                        <Button variant="outlined" onClick={logout}>
                          로그아웃
                        </Button>
                      )}
                      {!user && <LoginModal />}
                    </ListItem>
                  </List>
                </Box>
              </Drawer>
            </Stack>
          </Grid>
        </Grid>
      </Stack>
      <Divider sx={{my: 1}} />
    </header>
  )
}

 

설명 예시 :
로그인 후 youtube 구현 페이지 예시

# one source multi use - 개발 한 것을 스마트폰 태블릿 노트북 데스크탑 에서도 볼 수 있는 것을 지칭하는 말

 


1) triger point
*1.xs 스마트폰 기준 반응형 normal framework에서 우선순위가 가장 높음 -정확한 픽셀은 검색 후 이용 : 웹브라우저 형태로 볼 수 있게 구현
2.sm 화면 구성요소가 적어짐 (유투브 동영상 갯수)
3.md 로고가 축소
*4.lg
5.xl

hamburger menu > drawer 사용
관리자메뉴 : 아코디언 - xs로 줄어도 상관 없음

 


2) SearchHeader.jsx
구현 방법 : Grid 가장 밖 : container 

container 
Grid
xs={2} 7 3 : 12
md={2} 6 4 : 12
lg={3} 4 5 : 12

large 일 때 youtube 글자가 보임
xs일 때는 안보임 - 마크만 보임

sx mui tag css style tag display 속성에 <{sx={{display: {xs: 'none', md: 'none', lg: 'flex'}}}>

 


3) 줄어들었을 때 웹에 햄버거 메뉴만 보이게 하는 방법
Stack
sx
<{sx={{display: {xs: 'flex', md: 'flex', lg: 'none'}}}>
<(count > 0) &&
menu onClick isMenuOpen: true 면 open 됨 colose: false 
anchor="right" 왼쪽이면 튕겨져나감
height  전체에서 20%

관리자 페이지에 사용자별 리스트 보이게 하기
<List>

4) 단위
픽셀 기본 폰트 16px css 기준
반응형에서는 px을 사용하면 좋지 않다
한 글자의 크기 = 1em 이라고 함

fixed font 2d coding font 같음
variable font 가병폰트 제일 큰 글자 대문자 M 길이가 가장 김 프린터 용어 M이 기준이 됨

rem 
부모의 텍스트 px 
부모 사이지에서 맞는 형태의 자식 글자 사이즈
rem 이라는 단어를 고민해보기

반응형 단위
vw : 내가 표시할 수 있는 width의 100분의 1
vh : 내가 표시할 수 있는 높이의 100분의 1
이미지 일 경우 
처음 시작할 때 : 100px > 내 화면에 차지하는 상대하는 비율로 표시하기 10% 20% 등
이미지 : 200px 일 때, 깨질 경우 화질이 떨어지지 않게 maxwidth: 200px 로 잡아서 그 이상은 커지지 않게 하기