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

feat : 반응형 ui 적용 중 #227

Merged
merged 10 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
35 changes: 35 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.3",
"@storybook/addon-actions": "^8.3.4",
"@tanstack/react-query": "^5.59.13",
"@tanstack/react-query-devtools": "^5.59.15",
Expand Down
2 changes: 1 addition & 1 deletion src/app/(default)/learn/listening/detail/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export default function DetailListeningPage() {
}

return (
<div className="max-w-[830px] flex flex-col ">
<div className="max-w-[830px] flex flex-col gap-5">
<div>
<h1 className="text-2xl font-bold">
{listeningDetailData?.data.title}
Expand Down
127 changes: 112 additions & 15 deletions src/components/layout/MobileNav.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,126 @@
'use client';

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

import Link from 'next/link';
import { usePathname } from 'next/navigation';

import { ChevronUp, X } from 'lucide-react';

import { Button } from '@/components/ui/button';

import { navItems } from './Header';
import MobileLearningTracker from './side/MobileLearningTracker';

interface SwipeablePanelProps {
children: React.ReactNode;
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
}

export default function SwipeablePanel({
children,
isOpen,
setIsOpen,
}: SwipeablePanelProps) {
const panelRef = useRef<HTMLDivElement>(null);
const startY = useRef<number | null>(null);

// 사용자가 화면을 터치한 시점의 y 좌표 기록
const handleTouchStart = (e: React.TouchEvent) => {
startY.current = e.touches[0].clientY;
};

// 사용자가 손가락을 움직일 때 호출
// 터치 시작 지점(startY)과 현재 터치 지점(currentY) 사시의 차이(diff)를 계산
const handleTouchMove = (e: React.TouchEvent) => {
if (startY.current === null || !panelRef.current) return;
const currentY = e.touches[0].clientY;
const diff = startY.current - currentY;

if (diff > 50 && !isOpen) {
setIsOpen(true);
} else if (diff < -50 && isOpen) {
setIsOpen(false);
}
};

const handleTouchEnd = () => {
startY.current = null;
};

useEffect(() => {
if (panelRef.current) {
panelRef.current.style.transition = 'transform 0.3s ease-out';
panelRef.current.style.transform = isOpen
? 'translateY(0)'
: 'translateY(100%)';
}
}, [isOpen]);

return (
<div
ref={panelRef}
className="md:hidden fixed bottom-0 left-0 right-0 bg-white border-t rounded-t-2xl shadow-lg"
style={{
height: 'calc(100% - 4rem)',
touchAction: 'none',
transform: 'translateY(100%)',
}}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
<div className="p-4 h-full overflow-auto">
<Button
variant="ghost"
size="sm"
className="absolute top-2 right-2"
onClick={() => setIsOpen(false)}
>
<X />
</Button>
{children}
</div>
</div>
);
}

export function MobileNav() {
const pathname = usePathname();
const [isOpen, setIsOpen] = useState(false);

return (
<nav className="md:hidden fixed bottom-0 left-0 right-0 bg-white border-t">
<div className="flex justify-around">
{navItems.map((item) => (
<Link
key={item.name}
href={item.href}
className={`flex flex-col items-center py-2 ${
pathname === item.href ? 'text-blue-600' : 'text-gray-600'
}`}
<>
<nav className="md:hidden fixed bottom-0 left-0 right-0 bg-white border-t z-10">
<div className="flex justify-around items-center h-16">
{navItems.map((item) => (
<Link
key={item.name}
href={item.href}
className={`flex flex-col items-center py-1 px-3 rounded-lg transition-colors ${
pathname === item.href
? 'text-blue-600 bg-blue-50'
: 'text-gray-600 hover:bg-gray-100'
}`}
>
<item.icon className="h-6 w-6" />
<span className="text-xs mt-1">{item.name}</span>
</Link>
))}
<button
type="button"
onClick={() => setIsOpen((prev) => !prev)}
className="flex flex-col items-center py-1 px-3 rounded-lg text-gray-600 hover:bg-gray-100 transition-colors"
>
<item.icon className="h-6 w-6" />
<span className="text-xs mt-1">{item.name}</span>
</Link>
))}
</div>
</nav>
<ChevronUp className="h-6 w-6" />
<span className="text-xs mt-1">트래커</span>
</button>
</div>
</nav>
<SwipeablePanel isOpen={isOpen} setIsOpen={setIsOpen}>
<MobileLearningTracker />
</SwipeablePanel>
</>
);
}
59 changes: 3 additions & 56 deletions src/components/layout/SideArea.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,13 @@
'use client';

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

import { Book, HelpCircle, Highlighter } from 'lucide-react';

import { useUserTime } from '@/api/hooks/useUserInfo';
import { calculateDaysBetweenDates } from '@/lib/calculateDaysBetweenDates';
import React from 'react';

import LearningTracker from './side/LearningTracker';

export default function SideArea() {
const mockDailyGoals = [
{
id: 'study',
icon: <Book className="h-4 w-4" />,
label: '10분 학습',
completed: false,
},

{
id: 'highlight',
icon: <Highlighter className="h-4 w-4" />,
label: '형광펜 1개',
completed: false,
},
{
id: 'quiz',
icon: <HelpCircle className="h-4 w-4" />,
label: '퀴즈 1개',
completed: true,
},
];

const mockHistory = [
{ date: '2023.06.15', completedMissions: 3 },
{ date: '2023.06.14', completedMissions: 2 },
{ date: '2023.06.13', completedMissions: 1 },
{ date: '2023.06.12', completedMissions: 0 },
{ date: '2023.06.11', completedMissions: 1 },
];

// TODO(@godhyzzang) : 현재 가입일 데이터 수정 필요
const { data: userMembershipDurationData } = useUserTime();

const [totalLearningDays, setTotalLearningDays] = useState<number>(0);

useEffect(() => {
if (userMembershipDurationData?.data.createdAt) {
const today = new Date();
const createdAt = new Date(userMembershipDurationData.data.createdAt);
const learningDays = calculateDaysBetweenDates(today, createdAt);
setTotalLearningDays(learningDays);
}
}, [userMembershipDurationData?.data.createdAt]);

return (
<div className="h-fit w-[260px] ml-[50px] py-[60px]">
<LearningTracker
totalLearningDays={totalLearningDays}
dailyGoals={mockDailyGoals}
history={mockHistory}
/>
<div className="hidden md:block h-fit w-[260px] ml-[50px] py-[60px]">
<LearningTracker />
</div>
);
}
Loading
Loading