diff --git a/src/App.tsx b/src/App.tsx index 1c347d0..851d008 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,7 @@ import { ProtectedRoute, PrevLocation, LoadingBar, + ViewportChecker, } from './components'; import { notifier } from './utils/renoti'; import { IRootState } from './reducers'; @@ -35,36 +36,38 @@ class App extends Component { } return ( -
- - - }> - - - - - - - - - - - - - - - + +
+ + + }> + + + + + + + + + + + + + + + + diff --git a/src/components/LikeButton.tsx b/src/components/LikeButton.tsx index 83982ec..d40effa 100644 --- a/src/components/LikeButton.tsx +++ b/src/components/LikeButton.tsx @@ -23,7 +23,7 @@ const Button = styled.button` `; const LikeOrDislike = styled(Button) <{ likeType: LikeType; active?: boolean }>` - font-size: 1em; + font-size: 1.2rem; ${props => props.active && `color: ${COLOR[props.likeType].active};`} &:hover { color: ${props => COLOR[props.likeType].hover}; @@ -46,9 +46,9 @@ const LikeButton: React.SFC = ({ return ( {likeType === 'like' ? ( - + ) : ( - + )} {typeof count !== 'undefined' &&
{count}
}
diff --git a/src/components/ProfileMenu.tsx b/src/components/ProfileMenu.tsx index 5913ec5..233d033 100644 --- a/src/components/ProfileMenu.tsx +++ b/src/components/ProfileMenu.tsx @@ -1,18 +1,27 @@ import React, { useState, useCallback } from 'react'; -import Button from '@material-ui/core/Button'; -import MenuList from '@material-ui/core/MenuList'; -import MenuItem from '@material-ui/core/MenuItem'; +import styled from 'styled-components'; import Popper from '@material-ui/core/Popper'; import ClickAwayListener from '@material-ui/core/ClickAwayListener'; import Paper from '@material-ui/core/Paper'; -import { ProfilePhoto } from '.'; +import { Divider, ProfilePhoto } from '.'; import { IUser } from '../models/user'; import { history } from '../utils/history'; +import { MenuList, MenuItem } from '../styles/common'; export interface IProfileMenuProps extends IUser { logout: () => void; } -const ProfileMenu: React.SFC = ({ id, image, logout }) => { + +const UserInfo = styled.div` + display: flex; + align-items: center; + + div { + margin-left: 10px; + } +` + +const ProfileMenu: React.SFC = ({ id, nickname, image, logout }) => { const [anchorEl, setAnchorEl] = useState(null); const handleClick = useCallback((e: React.MouseEvent) => { setAnchorEl(e.currentTarget); @@ -22,26 +31,34 @@ const ProfileMenu: React.SFC = ({ id, image, logout }) => { }, []); // Menu 액션 - const handleProfile = useCallback(() => { + const withClose = (fn: T) => { + return () => { + handleClose(); + fn(); + } + }; + const handlePost = withClose(() => { + history.push(`/ask`); + }); + const handleProfile = withClose(() => { history.push(`/profile/${id}`); - }, [id]); - const handleLogout = useCallback(() => { - handleClose(); - logout(); - }, [handleClose, logout]); + }); + const handleLogout = withClose(logout); return ( <> - + + + + +
{nickname}
+
+
+ 코드 올리기 + 프로필 보기 로그아웃
diff --git a/src/components/ProfilePhoto.tsx b/src/components/ProfilePhoto.tsx index 26478e1..8884905 100644 --- a/src/components/ProfilePhoto.tsx +++ b/src/components/ProfilePhoto.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { memo } from 'react'; import styled from 'styled-components'; import profilephoto from '../assets/ic_profilephoto.png'; import { Link } from 'react-router-dom'; @@ -12,7 +12,7 @@ interface ProfilePhotoProps extends React.ImgHTMLAttributes { } const ProfilePhoto: React.SFC = ({ - width = 40, + width = 32, src = profilephoto, userId, ...props @@ -44,4 +44,4 @@ const ProfilePhoto: React.SFC = ({ ); }; -export default ProfilePhoto; +export default memo(ProfilePhoto); diff --git a/src/components/Search.tsx b/src/components/Search/Desktop.tsx similarity index 51% rename from src/components/Search.tsx rename to src/components/Search/Desktop.tsx index 37e060d..265483c 100644 --- a/src/components/Search.tsx +++ b/src/components/Search/Desktop.tsx @@ -1,22 +1,22 @@ -import React, { useState, useCallback, useEffect } from 'react'; +import React, { useState } from 'react'; import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; -import Paper, { PaperProps } from '@material-ui/core/Paper'; +import Paper from '@material-ui/core/Paper'; import InputBase, { InputBaseProps } from '@material-ui/core/InputBase'; import { MdSearch } from 'react-icons/md'; -import { history, questionQueryHelper } from '../utils/history'; -import { RouteComponentProps, withRouter } from 'react-router'; const useStyles = makeStyles((theme: Theme) => createStyles({ root: { - padding: '2px 4px', + width: '100%', + fontSize: '1rem', alignItems: 'center', boxSizing: 'border-box', boxShadow: '0 0 0 0', }, input: { + fontSize: 'inherit', borderRadius: 5, - padding: '5px 10px', + padding: '0.25rem 0.75rem', paddingLeft: 35, width: '100%', transition: 'background-color 0.1s ease-in-out', @@ -38,66 +38,42 @@ const useStyles = makeStyles((theme: Theme) => }, icon: { position: 'absolute', - top: 8, + top: '50%', left: 5, + transform: 'translateY(-55%)', color: theme.palette.grey[500], zIndex: 2, }, }), ); - -interface IInputProps extends RouteComponentProps { - paperProps?: PaperProps; - inputProps?: InputBaseProps; +type IDesktopSearchProps = InputBaseProps & { + handleSubmit: (e: React.FormEvent) => void; } -const Search: React.SFC = ({ - location, - paperProps = {}, - inputProps = {}, +const DesktopSearch: React.SFC = ({ + handleSubmit, + ...props }) => { const classes = useStyles(); const [focused, setFocused] = useState(false); - const [search, setSearch] = useState( - questionQueryHelper.searchQuery.subject || '', - ); - - const handleChange = useCallback( - (e: React.ChangeEvent) => setSearch(e.target.value), - [], - ); - const handleSearch = (e: React.FormEvent) => { - e.preventDefault(); - history.push( - `/search?${questionQueryHelper.makeQuery({ subject: search })}`, - ); - }; - - useEffect(() => { - setSearch(questionQueryHelper.searchQuery.subject || ''); - }, [location.search]); - return ( -
- + + setFocused(true)} onBlur={() => setFocused(false)} className={`${classes.input} ${focused && classes.focusedinput}`} placeholder="질문을 검색해주세요." - {...inputProps} + {...props} />
); }; -export default withRouter(Search); +export default DesktopSearch; diff --git a/src/components/Search/Mobile.tsx b/src/components/Search/Mobile.tsx new file mode 100644 index 0000000..863a088 --- /dev/null +++ b/src/components/Search/Mobile.tsx @@ -0,0 +1,81 @@ +import React, { useState, useCallback } from 'react'; +import styled from 'styled-components'; +import TextField, { StandardTextFieldProps } from '@material-ui/core/TextField'; +import Button from '@material-ui/core/Button'; +import { MdSearch } from 'react-icons/md'; + +const FixedContainer = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.9); + z-index: 999; +` +const InnerContainer = styled.div` + position: relative; + box-sizing: border-box; + top: 40%; + width: 100%; + max-width: 800px; + margin: auto; + padding: 0 10px; +` + +const Form = styled.form` + width: 100%; + margin-bottom: 5px; + input { + font-size: 125%; + } +`; + +const FloatedButton = styled(Button)` + float: right; +` +const Icon = styled(MdSearch)` + color: ${props => props.theme.palette.darkGray}; + cursor: pointer; + &:hover { + color: black; + transition: color 0.1s; + } +` + +interface IMobileSearchProps extends StandardTextFieldProps { + handleSubmit: (e: React.FormEvent) => void +} + +const MobileSearch: React.SFC = ({ handleSubmit, ...props }) => { + const [open, setOpen] = useState(false); + const close = useCallback(() => { + setOpen(false); + }, []); + const onSubmit = (e: React.FormEvent) => { + e.preventDefault(); + e.persist(); + setOpen(false); + handleSubmit(e); + } + return ( + <> + setOpen(true)} /> + { + open && ( + + +
+ + + 닫기 +
+
+ ) + } + + + ) +} + +export default MobileSearch; \ No newline at end of file diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx new file mode 100644 index 0000000..d621e7a --- /dev/null +++ b/src/components/Search/index.tsx @@ -0,0 +1,40 @@ +import React, { useState, useCallback, useEffect, useMemo } from 'react'; +import { RouteComponentProps, withRouter } from 'react-router'; +import DesktopSearch from './Desktop'; +import MobileSearch from './Mobile'; +import { history, questionQueryHelper } from '../../utils/history'; + +interface ISearchProps extends RouteComponentProps { + isMobile: boolean; +} + +const Search: React.SFC = ({ + location, + isMobile, +}) => { + const [search, setSearch] = useState( + questionQueryHelper.searchQuery.subject || '', + ); + + const handleChange = useCallback( + (e: React.ChangeEvent) => setSearch(e.target.value), + [], + ); + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + history.push( + `/search?${questionQueryHelper.makeQuery({ subject: search })}`, + ); + }; + + useEffect(() => { + setSearch(questionQueryHelper.searchQuery.subject || ''); + }, [location.search]); + + const ResponsiveSearch = useMemo(() => { return isMobile ? MobileSearch : DesktopSearch }, [isMobile]); + return ( + + ) +}; + +export default withRouter(Search); diff --git a/src/components/ViewportChecker.tsx b/src/components/ViewportChecker.tsx new file mode 100644 index 0000000..8561225 --- /dev/null +++ b/src/components/ViewportChecker.tsx @@ -0,0 +1,26 @@ +import React, { useState, useEffect } from 'react'; +import { checkSize, DeviceType } from '../utils/device'; +import Viewport from '../context/ViewportChecker'; + + +const ViewportChecker: React.SFC = ({ children }) => { + const [device, setDevice] = useState(checkSize(window.innerWidth)); + useEffect(() => { + const handler = () => { + const newDivice = checkSize(window.innerWidth); + if (device !== newDivice) { + setDevice(newDivice); + } + } + window.addEventListener('resize', handler); + return () => window.removeEventListener('resize', handler); + }, [device]); + + return ( + + {children} + + ) +} + +export default ViewportChecker; \ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts index c8d1df9..8479319 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -25,6 +25,7 @@ import ScrollChecker from './ScrollChecker'; import Search from './Search'; import Tag from './Tag'; import TagList from './TagList'; +import ViewportChecker from './ViewportChecker'; export { AntSwitch, @@ -54,4 +55,5 @@ export { Search, Tag, TagList, + ViewportChecker }; diff --git a/src/containers/AnswerForm/Tip.tsx b/src/containers/AnswerForm/Tip.tsx index f9abdfc..d01e0dd 100644 --- a/src/containers/AnswerForm/Tip.tsx +++ b/src/containers/AnswerForm/Tip.tsx @@ -9,7 +9,7 @@ import TIPS from './Tips'; const useStyles = makeStyles((theme: Theme) => createStyles({ paper: { - width: 380, + maxWidth: '100%', padding: theme.spacing(2), }, }), diff --git a/src/containers/AnswerList/Answer.tsx b/src/containers/AnswerList/Answer.tsx index 681bf36..0b03ef2 100644 --- a/src/containers/AnswerList/Answer.tsx +++ b/src/containers/AnswerList/Answer.tsx @@ -29,6 +29,7 @@ const useStyles = makeStyles((theme: Theme) => marginTop: theme.spacing(2), padding: theme.spacing(3, 0.5), boxShadow: '0 0 0 0', + boxSizing: 'border-box', }, }), ); @@ -51,7 +52,7 @@ const Answer: React.SFC = ({ dislikedUsers = [], createdAt, isMyQuestion = false, - handleResolve = () => {}, + handleResolve = () => { }, }) => { // 답변의 코드라인 설정 const [code, setCode] = useState(codestring || ''); @@ -126,20 +127,18 @@ const Answer: React.SFC = ({ isResolved ? '채택된 답변입니다.' : isMyQuestion - ? '답변을 채택합니다.' - : '아직 채택되지 않은 답변입니다.' + ? '답변을 채택합니다.' + : '아직 채택되지 않은 답변입니다.' } aria-label="Resolve" > -
- - - -
+ + + diff --git a/src/containers/AnswerList/style.ts b/src/containers/AnswerList/style.ts index bcc398d..5974500 100644 --- a/src/containers/AnswerList/style.ts +++ b/src/containers/AnswerList/style.ts @@ -1,4 +1,5 @@ import styled from 'styled-components'; +import { BodySide, BodyMain } from '../QuestionView/style'; /* Answer와 User의 간격 맞춰야함 */ @@ -6,18 +7,16 @@ export const UserInfo = styled.div` display: flex; align-items: center; `; -export const AnswerLeft = styled.div` - flex: 0 0 80px; - text-align: center; -`; -export const AnswerRight = styled.div` - flex: 1 1; - padding: 0 10px; - max-width: calc(100% - 100px); +export const AnswerLeft = styled(BodySide)``; +export const AnswerRight = styled(BodyMain)` + flex: 1 1 calc(100% - 160px); + min-width: calc(100% - 160px); + box-sizing: border-box; `; export const ResolveCheck = styled.button<{ resolve?: boolean }>` - flex: 0 0 80px; - text-align: center; + width: 80px; + padding: 0; + text-align: right; color: ${props => props.resolve ? props.theme.palette.secondary.main : props.theme.palette.gray}; &:hover { diff --git a/src/containers/Header/index.tsx b/src/containers/Header/index.tsx index e714ad7..1bc9214 100644 --- a/src/containers/Header/index.tsx +++ b/src/containers/Header/index.tsx @@ -1,44 +1,46 @@ -import React, { useCallback } from 'react'; -import { Dispatch } from 'redux'; -import { connect } from 'react-redux'; +import React, { memo, useCallback, useContext, useMemo } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; +import isEqual from 'lodash/isEqual'; +import ViewportContext from '../../context/ViewportChecker'; import { CustomLink, Search, Logo, ProfileMenu } from '../../components'; import { openModal } from '../../actions/modal'; import { Container, LogoAdjust, Menu } from './style'; import { IRootState } from '../../reducers'; -import { IUser } from '../../models/user'; import { logout } from '../../actions/auth'; -export interface IHeaderProps { - dispatch: Dispatch; - user: IUser; - isLoggedIn: boolean; -} -const Header: React.SFC = ({ dispatch, user, isLoggedIn }) => { +const Header: React.SFC = () => { + const viewport = useContext(ViewportContext); + + const dispatch = useDispatch(); + const { isLoggedIn, user } = useSelector((state: IRootState) => ({ + isLoggedIn: state.auth.me.isLoggedIn, + user: state.auth.me.user, + }), isEqual); + const handleLogout = useCallback(() => { dispatch(logout()); }, [dispatch]); + + const isMobile = useMemo(() => viewport !== 'laptop', [viewport]); return ( - - - - - - - - + + + + + + - + {isLoggedIn ? ( @@ -56,21 +58,21 @@ const Header: React.SFC = ({ dispatch, user, isLoggedIn }) => { )} - - - - - + { + isLoggedIn && !isMobile && ( + + + + + + ) + } ); }; -const mapStateToProps = (state: IRootState) => ({ - isLoggedIn: state.auth.me.isLoggedIn, - user: state.auth.me.user, -}); -export default connect(mapStateToProps)(Header); +export default memo(Header); diff --git a/src/containers/Header/style.ts b/src/containers/Header/style.ts index 1f9f0f8..2428a78 100644 --- a/src/containers/Header/style.ts +++ b/src/containers/Header/style.ts @@ -1,4 +1,6 @@ import styled from 'styled-components'; +import { minDevice } from '../../utils/device'; + export const Container = styled.div` display: flex; align-items: center; @@ -7,17 +9,26 @@ export const Container = styled.div` height: 60px; box-sizing: border-box; - padding: 0 5%; background-color: white; - border-bottom: 5px solid ${props => props.theme.palette.primary.main}; + border-bottom: 3px solid ${props => props.theme.palette.primary.main}; + + padding: 0; + @media ${minDevice.laptop} { + padding: 0 5%; + border-bottom: 5px solid ${props => props.theme.palette.primary.main}; + } `; export const LogoAdjust = styled.div` - position: absolute; +position: absolute; top: 18px; - left: 50px; + left: 5px; + @media ${minDevice.laptop} { + left: 50px; + } `; export const Menu = styled.div` margin-left: 10px; + margin-right: 10px; `; diff --git a/src/containers/Modal/index.tsx b/src/containers/Modal/index.tsx index 294f437..f1adb13 100644 --- a/src/containers/Modal/index.tsx +++ b/src/containers/Modal/index.tsx @@ -8,6 +8,7 @@ import { openModal, closeModal } from '../../actions/modal'; import { ModalType } from '../../models/modal'; import { IRootState } from '../../reducers'; import { Inner } from './style'; +import { Theme, makeStyles, createStyles } from '@material-ui/core/styles'; const MODAL_TYPES = { signin: Signin, @@ -19,7 +20,16 @@ interface IModalProps { modalType: ModalType | null; } +const useStyles = makeStyles(() => + createStyles({ + paper: { + margin: 0, + }, + }), +); + const Modal: React.SFC = ({ dispatch, modalType }) => { + const classes = useStyles(); const CurrentModal = modalType ? MODAL_TYPES[modalType] : null; const onOpen = useCallback( (type: ModalType) => { @@ -34,7 +44,7 @@ const Modal: React.SFC = ({ dispatch, modalType }) => { return null; } return ( - + diff --git a/src/containers/Modal/style.ts b/src/containers/Modal/style.ts index 03a033b..c3f368b 100644 --- a/src/containers/Modal/style.ts +++ b/src/containers/Modal/style.ts @@ -1,4 +1,5 @@ import styled from 'styled-components'; +import { maxDevice } from '../../utils/device'; export const Inner = styled.div` padding: 20px 30px; @@ -7,8 +8,12 @@ export const Form = styled.form` display: flex; flex-wrap: wrap; - max-width: 500px; + width: 500px; margin: auto; + + @media ${maxDevice.laptop} { + width: 95%; + } `; // input 2개를 centering export const InputWrapper = styled.div<{ isLeft?: boolean }>` diff --git a/src/containers/QuestionView/style.ts b/src/containers/QuestionView/style.ts index 392de6f..e18e57a 100644 --- a/src/containers/QuestionView/style.ts +++ b/src/containers/QuestionView/style.ts @@ -1,4 +1,5 @@ import styled from 'styled-components'; +import { maxDevice } from '../../utils/device'; export const Container = styled.div` font-size: 1rem; line-height: 1.5; @@ -16,10 +17,13 @@ export const BodySide = styled.div` text-align: center; flex: 0 0 80px; + @media ${maxDevice.laptop} { + flex: 0 0 50px; + } `; export const BodyMain = styled.div` flex: 1; - max-width: calc(100% - 80px); + min-width: calc(100% - 80px); `; export const Content = styled.div` p { diff --git a/src/containers/SearchSidebar/style.ts b/src/containers/SearchSidebar/style.ts index ead4d2c..98e51c8 100644 --- a/src/containers/SearchSidebar/style.ts +++ b/src/containers/SearchSidebar/style.ts @@ -1,5 +1,6 @@ import styled from 'styled-components'; export const WatchedWrapper = styled.div<{ active?: boolean }>` + max-width: 300px; opacity: ${props => (props.active ? 1 : 0.4)}; `; diff --git a/src/containers/UserProfile/Contents/Content.tsx b/src/containers/UserProfile/Contents/Content.tsx index 743cd51..973a03b 100644 --- a/src/containers/UserProfile/Contents/Content.tsx +++ b/src/containers/UserProfile/Contents/Content.tsx @@ -2,7 +2,6 @@ import React, { useCallback } from 'react'; import isEqual from 'lodash/isEqual'; import uniqBy from 'lodash/uniqBy'; import ContentList from './ContentList'; -import { List, Tab } from './style'; import { useSelector, useDispatch } from 'react-redux'; import { IRootState } from '../../../reducers'; import { loadUserQuestions, loadUserComments } from '../../../actions/user'; @@ -51,12 +50,12 @@ const Content: React.SFC = ({ currentTab }) => { comments_count: item.comments ? item.comments.length : 0, })); return ( - +
- +

{currentTab}: {count} - +

= ({ currentTab }) => { hasNext={hasNext} />
- +
); }; diff --git a/src/containers/UserProfile/Contents/index.tsx b/src/containers/UserProfile/Contents/index.tsx index f94251a..4d37d1a 100644 --- a/src/containers/UserProfile/Contents/index.tsx +++ b/src/containers/UserProfile/Contents/index.tsx @@ -25,7 +25,7 @@ const Contents: React.SFC = ({ currentTab }) => { }, []); return ( - + {TABS.map(tab => ( = ({ currentTab }) => { ))} - - + +
- +
); }; diff --git a/src/containers/UserProfile/Contents/style.ts b/src/containers/UserProfile/Contents/style.ts index 888064f..745520a 100644 --- a/src/containers/UserProfile/Contents/style.ts +++ b/src/containers/UserProfile/Contents/style.ts @@ -2,32 +2,32 @@ import styled from 'styled-components'; export const Container = styled.div` width: 100%; - display: flex; `; -export const Side = styled.div` - flex: 0 1 150px; +export const Nav = styled.nav` +border-bottom: 1px solid ${props => props.theme.palette.gray}; `; export const Tabs = styled.ul` padding: 0; + margin-bottom: -1px; list-style-type: none; `; export const Tab = styled.li<{ selected: boolean }>` + display: inline-block; list-style-type: none; cursor: pointer; - font-size: 20px; - line-height: 1.5; - margin-top: 1rem; + margin-right: 10px; + padding-bottom: 8px; border-bottom: 1px solid ${props => - props.selected - ? props.theme.palette.primary.main - : props.theme.palette.gray}; -`; -export const Content = styled.div` - flex: 1 0; - margin-left: 20px; + props.selected + ? props.theme.palette.primary.main + : props.theme.palette.gray}; + + &:hover { + border-bottom: 1px solid ${props => props.theme.palette.primary.main}; + transition: border-bottom 0.1s; + } `; -export const List = styled.div``; export const Center = styled.div` display: flex; justify-content: center; diff --git a/src/containers/UserProfile/Info/index.tsx b/src/containers/UserProfile/Info/index.tsx index 3626e6a..c6215b1 100644 --- a/src/containers/UserProfile/Info/index.tsx +++ b/src/containers/UserProfile/Info/index.tsx @@ -3,7 +3,7 @@ import { useSelector } from 'react-redux'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; -import { InfoContainer } from './style'; +import { InfoContainer, MarginList, MarginLeft, Name, InfoLeft } from './style'; import GithubImg from '../../../assets/icon/github.png'; import { IRootState } from '../../../reducers'; import { ProfilePhoto, CustomLink, TagList } from '../../../components'; @@ -28,44 +28,44 @@ const Info: React.SFC = ({ currentPath }) => { const { nickname, tags, githubUrl, image = '' } = user; return ( - - - - - - - - {nickname} - - -
질문 수 : {questionCount}
-
답변 수 : {commentCount}
-
-
- - {isMe && ( - - - - )} - -
- - + +
+ +
+ +
+
+
+ + + + {nickname} + + + {isMe && ( + + + + )} + + +
질문 수 : {questionCount} 답변 수 : {commentCount}
+
+ {githubUrl && ( + + profile + + )} +
+
+
+
+
- - - - {githubUrl && ( - - profile - - )} - - - +
+
); }; diff --git a/src/containers/UserProfile/Info/style.ts b/src/containers/UserProfile/Info/style.ts index 16e73df..5af9b97 100644 --- a/src/containers/UserProfile/Info/style.ts +++ b/src/containers/UserProfile/Info/style.ts @@ -1,8 +1,8 @@ import styled from 'styled-components'; +import { maxDevice } from '../../../utils/device'; export const InfoContainer = styled.div` width: 100%; - min-height: 220px; `; export const Container = styled.div` @@ -32,3 +32,33 @@ export const TagWrapper = styled.span` vertical-align: middle; } `; + +export const Name = styled.h1` + font-size: 24px; + font-weight: 400; + margin: 0; + margin-right: 20px; +` + +export const InfoLeft = styled.div` + flex: 1; + & > div { + width: 150px; + height: 150px; + @media ${maxDevice.tablet} { + width: 64px; + height: 64px; + } + } +` +export const MarginRight = styled.div` + margin-right: 20px; +` +export const MarginLeft = styled.div` + margin-left: 20px; +`; +export const MarginList = styled.div` + & > * { + margin-bottom: 20px; + } +` \ No newline at end of file diff --git a/src/containers/UserProfile/index.tsx b/src/containers/UserProfile/index.tsx index 0308219..d9c13f2 100644 --- a/src/containers/UserProfile/index.tsx +++ b/src/containers/UserProfile/index.tsx @@ -1,7 +1,6 @@ import React from 'react'; import Info from './Info'; import Contents from './Contents'; -import { Divider } from '../../components'; interface IProfileProps { currentPath: string; @@ -11,7 +10,6 @@ const Profile: React.SFC = ({ currentTab, currentPath }) => { return (
-
); diff --git a/src/containers/WathcedTags/style.ts b/src/containers/WathcedTags/style.ts index 48b9e51..a277b1c 100644 --- a/src/containers/WathcedTags/style.ts +++ b/src/containers/WathcedTags/style.ts @@ -1,7 +1,7 @@ import styled from 'styled-components'; export const Container = styled.div` - max-width: 300px; + width: 100%; `; export const ContainerTitle = styled.div` width: 100%; diff --git a/src/context/ViewportChecker.tsx b/src/context/ViewportChecker.tsx new file mode 100644 index 0000000..54ed94b --- /dev/null +++ b/src/context/ViewportChecker.tsx @@ -0,0 +1,6 @@ +import { createContext } from 'react'; +import { checkSize } from '../utils/device'; + +const viewPortChecker = createContext(checkSize(window.innerWidth)); + +export default viewPortChecker; \ No newline at end of file diff --git a/src/index.css b/src/index.css index 209739f..5fc466b 100644 --- a/src/index.css +++ b/src/index.css @@ -11,4 +11,12 @@ pre, code, kbd, samp { p { line-height: 1.7rem; +} + +a:hover { + cursor: pointer; +} + +button:hover { + cursor: pointer; } \ No newline at end of file diff --git a/src/pages/Question.tsx b/src/pages/Question.tsx index 1e5c768..9354c90 100644 --- a/src/pages/Question.tsx +++ b/src/pages/Question.tsx @@ -16,13 +16,13 @@ const useStyles = makeStyles((theme: Theme) => marginTop: theme.spacing(2), padding: theme.spacing(3), boxShadow: '1px 1px 3px 1px rgba(0, 0, 0, .1)', - + }, box: { width: '100%', boxSizing: 'border-box', backgroundColor: '#fff', - padding: '30px', + padding: theme.spacing(1.5), }, }), ); diff --git a/src/pages/Search.tsx b/src/pages/Search.tsx index 8957c5c..0bbc17d 100644 --- a/src/pages/Search.tsx +++ b/src/pages/Search.tsx @@ -1,9 +1,26 @@ -import React from 'react'; -import { SearchSidebar, SearchResult } from '../containers'; +import React, { useContext, useMemo } from 'react'; +import ViewportContext from '../context/ViewportChecker'; +import { Divider } from '../components'; +import { SearchSidebar, SearchResult, WatchedTags } from '../containers'; import { MainLayout, Sidebar, LayoutWithSidebar } from '../styles/common'; import { RouteComponentProps } from 'react-router'; const Search: React.SFC = props => { + const viewport = useContext(ViewportContext); + const isMobile = useMemo(() => viewport !== 'laptop', [viewport]); + if (isMobile) { + return ( + +
+ +
+ +
+ +
+
+ ) + } return ( diff --git a/src/styles/common.ts b/src/styles/common.ts index 2b4add9..572f5e6 100644 --- a/src/styles/common.ts +++ b/src/styles/common.ts @@ -1,4 +1,5 @@ import styled from 'styled-components'; +import { maxDevice, size } from '../utils/device'; export const WithBackground = styled.div` width: 100%; @@ -6,9 +7,12 @@ export const WithBackground = styled.div` export const PageLayout = styled.div` width: 100%; - padding: 1rem 10px; + padding: 1rem 5px; box-sizing: border-box; + @media ${maxDevice.tablet} { + padding: 0; + } `; export const LayoutWithSidebar = styled.div` width: 1400px; @@ -19,13 +23,15 @@ export const LayoutWithSidebar = styled.div` justify-content: space-between; `; export const MainLayout = styled.div` - flex: 1 1 800px; - width: 800px; - min-width: 800px; + flex: 1 1 ${size.tablet}px; + width: ${size.tablet}px; + min-width: ${size.tablet}px; margin: auto; - @media screen and (max-width: 800px) { - width: 95%; + box-sizing: border-box; + @media ${maxDevice.tablet} { + width: 100%; min-width: 300px; + padding: 5px; } `; export const Sidebar = styled.div<{ left?: boolean }>` @@ -60,3 +66,23 @@ export const Title = styled.h2` padding-left: 20px; `; + +export const MenuList = styled.ul` + min-width: 200px; + font-size: 16px; + list-style: none; + padding: 1rem 0px; +` + +export const MenuItem = styled.li<{ isDivider?: boolean }>` + box-sizing: border-box; + width: 100%; + padding: 7px 25px; + ${props => !props.isDivider && ( + `&:hover { + cursor: pointer; + color: ${props.theme.palette.primary.main}; + transition: color 0.1s; + }` + )} +` \ No newline at end of file diff --git a/src/utils/device.ts b/src/utils/device.ts new file mode 100644 index 0000000..2eb3229 --- /dev/null +++ b/src/utils/device.ts @@ -0,0 +1,23 @@ +export type DeviceType = 'mobile' | 'tablet' | 'laptop'; + +export const size = { + mobile: 425, + tablet: 768, + laptop: 1024, +}; +export const checkSize = (width: number): DeviceType => { + return window.innerWidth >= size.laptop ? 'laptop' : window.innerWidth ? 'tablet' : 'mobile'; +}; + +export type MinMaxDevice = Record; + +const [minDevice, maxDevice] = Object.entries(size).reduce((acc, [key, value]) => { + acc[0][key as DeviceType] = `(min-width: ${value}px)`; + acc[1][key as DeviceType] = `(max-width: ${value}px)`; + return acc; +}, [{}, {}] as [MinMaxDevice, MinMaxDevice]); + +export { + minDevice, + maxDevice +}; \ No newline at end of file