Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

네브바 UI + 유튜브 채널 UI Fix #266

Merged
merged 5 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 40 additions & 37 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
"*.ts",
"*.tsx"
],
"rules": {}
"rules": {
"max-len": ["error", { "code": 2000 }]
}
},
{
"files": [
Expand Down Expand Up @@ -117,42 +119,43 @@
"ts": "never",
"tsx": "never"
}
], // import 구문에서 파일 확장자를 생략해야합니다. 패키지를 import할 때는 이 규칙을 무시합니다.
"import/order": [ // import 순서를 지정합니다.
"error",
{
"groups": [ // 먼저 해당 groups의 순서를 갖습니다.
"type",
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
"unknown"
],
"pathGroups": [ // 기본 groups 외 특정 순서를 지정할 수 있습니다.
{
"pattern": "react*",
"group": "external",
"position": "before"
},
{
"pattern": "@/public/images/*",
"group": "unknown",
"position": "after"
}
],
"pathGroupsExcludedImportTypes": [
"type"
], // type 종류는 pathGroups를 적용하지 않습니다.
"newlines-between": "always", // import grouping 간에 한 줄을 추가합니다.
"alphabetize": { // 같은 grouping 내에선 알파벳 순서를 따릅니다.
"order": "asc",
"caseInsensitive": true
}
}
],
],
// // import 구문에서 파일 확장자를 생략해야합니다. 패키지를 import할 때는 이 규칙을 무시합니다.
// "import/order": [ // import 순서를 지정합니다.
// "error",
// {
// "groups": [ // 먼저 해당 groups의 순서를 갖습니다.
// "type",
// "builtin",
// "external",
// "internal",
// "parent",
// "sibling",
// "index",
// "unknown"
// ],
// "pathGroups": [ // 기본 groups 외 특정 순서를 지정할 수 있습니다.
// {
// "pattern": "react*",
// "group": "external",
// "position": "before"
// },
// {
// "pattern": "@/public/images/*",
// "group": "unknown",
// "position": "after"
// }
// ],
// "pathGroupsExcludedImportTypes": [
// "type"
// ], // type 종류는 pathGroups를 적용하지 않습니다.
// "newlines-between": "always", // import grouping 간에 한 줄을 추가합니다.
// "alphabetize": { // 같은 grouping 내에선 알파벳 순서를 따릅니다.
// "order": "asc",
// "caseInsensitive": true
// }
// }
// ],
"import/no-extraneous-dependencies": [ // 아래 devDependencies 내 파일들은 해당 룰에 영향을 받지 않습니다.
"error",
{
Expand Down
13 changes: 13 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 90,
"tabWidth": 2,
"jsxBracketSameLine": false,
"objectCurlySpacing": true,
"endOfLine": "auto",
"bracketSpacing": true,
"arrowParens": "avoid",
"proseWrap": "always"
}
17 changes: 10 additions & 7 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
/** @type {import('next').NextConfig} */
const path = require('path')
const path = require('path');

module.exports = {
output: 'standalone',
sassOptions: {
includePaths: [path.join(__dirname, 'styles')],
},
images: {
domains: ['i.ytimg.com', 'yt3.ggpht.com'],
},
// async rewrites() {
// return [
// {
Expand All @@ -17,17 +20,17 @@ module.exports = {
webpack: (config, context) => {
if (context?.isServer) {
if (Array.isArray(config.resolve.alias)) {
config.resolve.alias.push({ name: "msw/browser", alias: false })
config.resolve.alias.push({ name: 'msw/browser', alias: false });
} else {
config.resolve.alias["msw/browser"] = false
config.resolve.alias['msw/browser'] = false;
}
} else {
if (Array.isArray(config.resolve.alias)) {
config.resolve.alias.push({ name: "msw/node", alias: false })
config.resolve.alias.push({ name: 'msw/node', alias: false });
} else {
config.resolve.alias["msw/node"] = false
config.resolve.alias['msw/node'] = false;
}
}
return config
return config;
},
}
};
61 changes: 52 additions & 9 deletions src/app/channel/page.module.scss
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
.headerTitleWrapper {
display: flex;
box-sizing: border-box;
padding: 23px var(--default-padding) 12px;
flex-direction: row;
padding: 23px var(--default-padding) 2px;

.left {
margin: 7px 10px 0 0;
}
}

.mainContainer {
position: relative;
margin-bottom: 76px;

.headerFix {
position: sticky;
z-index: 100;
top: 0;
right: 0;
left: 0;
background-color: white;
}

.channelList {
display: flex;
box-sizing: border-box;
margin: 12px 0;
padding: 0 var(--default-padding);
padding: 0 4px;
overflow: auto;
white-space: nowrap;
gap: 5px;
Expand All @@ -19,18 +35,45 @@
display: none;
}

.channelImage {
display: block;
width: 50px;
height: 50px;
border-radius: 50%;
}

.imageNone {
display: none;
}

.channelButton {
padding: 5px 10px;
padding-bottom: 10px;
transition: 0.15s all ease-out;
border: 1px solid var(--primary200);
border-radius: 15px;
color: var(--primary200);
font-size: 12px;

// border: 1px solid var(--primary200);
// border-radius: 15px;
color: var(--gray500);
font-size: 16px;

&.selected {
border-color: var(--primary400);
background-color: var(--primary400);
color: var(--white);
position: relative;

// border-bottom: 2px solid var(--primary500);
&::after {
content: "";
position: absolute;
bottom: 0;
left: 50%; // 가운데 정렬
width: 80%; // 테두리의 길이를 조정
height: 2px;
transform: translateX(-50%); // 가운데 정렬
background-color: var(--primary500);
}

// border-color: var(--primary400);
// background-color: var(--primary400);
color: var(--primary500);
}
}
}
Expand Down
114 changes: 92 additions & 22 deletions src/app/channel/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
/* eslint-disable react/no-array-index-key */

/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */

'use client';

import { useState } from 'react';
import { useEffect, useState } from 'react';

import classNames from 'classnames/bind';

import LeftIcon from '@/components/shared/header/headerItems/LeftIcon';
import Title from '@components/shared/title/Title';
import useYoutubeList from '@remote/queries/channel/useYoutubeList';
import BottomNav from '@shared/bottom-nav/BottomNav';
import ChannelArticle from '@shared/channel-article/ChannelArticle';

import useChannel from '@/remote/queries/channel/useChannel';
import Image from 'next/image';
import Flex from '@/components/shared/flex/Flex';
import styles from './page.module.scss';

const cx = classNames.bind(styles);

const CHANNEL_LIST = {
export const CHANNEL_LIST = {
DetailWizard: 'UCJM63e_MydEL2o6dMuJ_teQ',
ShineFreak: 'UCoqiH2Ce3qc8wr_t2GvIWvw',
autogrm: 'UCKUHhKTlNTHRlwbjoFDKOfA',
Expand All @@ -27,34 +33,98 @@ function ChannelPage() {
const [selectedChannel, setSelectedChannel] = useState(Object.values(CHANNEL_LIST)[0]);

const { data: dataList, isError } = useYoutubeList(selectedChannel);
const [scroll, setScroll] = useState(true);

const handleChannel = (channelId: string) => {
setSelectedChannel(channelId);
};

const { data: channelList1 } = useChannel('UCJM63e_MydEL2o6dMuJ_teQ');
const { data: channelList2 } = useChannel('UCoqiH2Ce3qc8wr_t2GvIWvw');
const { data: channelList3 } = useChannel('UCKUHhKTlNTHRlwbjoFDKOfA');
const { data: channelList4 } = useChannel('UCHVO8oWoMCIIdQHXUB32X1w');
const { data: channelList5 } = useChannel('UCB22mXLQeRlCPn-H000OxYg');

const channelList = [
channelList1,
channelList2,
channelList3,
channelList4,
channelList5,
];

// console.log(channelList);
const [lastScroll, setLastScroll] = useState(0);

useEffect(() => {
const checkScrollTop = () => {
const currentScroll = document.documentElement.scrollTop;
if (currentScroll > 100 && lastScroll <= 100) {
setScroll(false);
setLastScroll(currentScroll);
} else if (currentScroll <= 100 && lastScroll > 100) {
setScroll(true);
setLastScroll(currentScroll);
}
};

const intervalId = setInterval(checkScrollTop, 100); // 1초에 한 번씩 checkScrollTop 함수를 호출

return () => {
clearInterval(intervalId); // 컴포넌트가 언마운트될 때 타이머를 정리
};
}, [lastScroll]);
return (
<>
<div className={cx('headerTitleWrapper')}>
<Title title="유튜브 추천 채널" titleSize="t3" />
</div>
<main className={cx('mainContainer')}>
<ul className={cx('channelList')}>
{Object.entries(CHANNEL_LIST).map(([channelName, channelId], idx) => {
return (
<li key={idx}>
<button aria-label="채널 버튼" className={cx('channelButton', selectedChannel === channelId ? 'selected' : '')} onClick={() => { return handleChannel(channelId); }}>
{channelName}
</button>
</li>
);
})}
</ul>

{isError && (<div className={cx('error')}>서버요청을 초과하였습니다.</div>)}
<div className={cx('headerFix')}>
<div className={cx('headerTitleWrapper')}>
<LeftIcon className={cx('left')} type="search" />
<Title title="추천 페이지" titleSize="t4" />
</div>
<ul className={cx('channelList')}>
{Object.entries(CHANNEL_LIST).map(([channelName, channelId], idx) => {
return (
<li
onClick={() => {
return handleChannel(channelId);
}}
key={idx}
>
<Flex direction="column" align="center" justify="center">
<Image
className={cx(scroll ? 'channelImage' : 'imageNone')}
src={
channelList[idx]?.items[0]?.snippet.thumbnails.high.url as string
}
alt="유튜브 채널 썸네일"
width={320}
height={180}
layout="cover"
objectFit="responsive"
/>
<button
aria-label="채널 버튼"
className={cx(
'channelButton',
selectedChannel === channelId ? 'selected' : '',
)}
onClick={() => {
return handleChannel(channelId);
}}
>
{channelName}
</button>
</Flex>
</li>
);
})}
</ul>
</div>
<div>{/* {channelList?.items?.thumbnails.high.url} */}</div>
{isError && <div className={cx('error')}>서버요청을 초과하였습니다.</div>}
{dataList?.items?.map((data, idx) => {
return (
<ChannelArticle key={idx} data={data} />
);
return <ChannelArticle key={idx} data={data} />;
})}
</main>
<BottomNav />
Expand Down
Loading
Loading