Skip to content

Commit d40f314

Browse files
authored
[#685] [fix] iOS PWA 환경에서의 status bar 스타일 추가 (#687)
* fix: apple web app status bar style 태그 추가 * fix: statusBarStyle을 default로 수정 및 capable를 true로 명시 * fix: manifest와 metadata에서 theme, background color 제거 * fix: capable, statusBarStyle 속성 추가 및 layout 상단 패딩에 safeArea 추가 * fix: statusBarStyle을 black-translucent로 수정 * fix: RootLayout 패딩 값 수정 * feat: TopHeader safeArea값 추가 및 className props 추가 * feat: TopNavigation safeArea값 추가 * fix: TopHeader 수정 이후 애니메이션이 정상작동 하도록 수정 * feat: manifest에 themeColor, backgroundColor 추가 * fix: TopHeader에 누락된 safeArea 추가 * feat: useIsScrollAtTop 커스텀 훅 작성 * feat: TopHeader에 blur 스타일 및 props 추가 * refactor: 각 페이지에 변경된 TopHeader 적용 * chore: metadata에 themeColor 다시 추가 * fix: PWA환경에서의 도서검색 페이지 input position 수정 * fix: 도서 검색 페이지 safeArea 수정 * fix: 도서 검색 페이지 translate값 수정 * refactor: TopHeader 컴포넌트의 text props의존성을 제거 * fix: 각 페이지의 리팩토링 된 TopHeader 적용 * fix: PWA환경에서 상단 노치부분에 컨탠츠가 노출되던 현상 수정 * fix: body 스크롤 시 컨탠츠가 좁은 틈새로 보이는 버그 수정 * fix: PWA환경에서 Login 페이지의 로그인 섹션이 비정상적인 위치에 있던 버그 수정 * fix: TopHeader 스토리북에 appLayoutMeta 적용 * feat: 도서 검색 페이지에 누락된 TopHeader blur 추가
1 parent 8dc1df0 commit d40f314

File tree

11 files changed

+118
-43
lines changed

11 files changed

+118
-43
lines changed

src/app/book/search/page.tsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import bookAPI from '@/apis/book';
1414
import SSRSafeSuspense from '@/components/common/SSRSafeSuspense';
1515
import useDebounceValue from '@/hooks/useDebounce';
1616
import useQueryParams from '@/hooks/useQueryParams';
17+
import useIsScrollAtTop from '@/hooks/useIsScrollAtTop';
1718
import { checkAuthentication } from '@/utils/helpers';
1819

1920
import Loading from '@/components/common/Loading';
@@ -46,6 +47,8 @@ const BookSearchPage = () => {
4647
const watchedKeyword = watch('searchValue');
4748
const debouncedKeyword = useDebounceValue(watchedKeyword, 1000);
4849

50+
const { isScrollAtTop } = useIsScrollAtTop();
51+
4952
/* debounce된 keyword값에 따라 queryParameter를 수정하는 useEffect */
5053
useEffect(() => {
5154
const queryValue = getQueryParam(KEYWORD);
@@ -57,24 +60,29 @@ const BookSearchPage = () => {
5760
}
5861
}, [debouncedKeyword, getQueryParam, setQueryParams, removeQueryParam]);
5962

60-
/* TopHeader가 사라졌을 때 input의 위치 top: 5.8rem */
61-
const inputPositionClasses = watchedKeyword && 'sticky top-[5.8rem]';
63+
/* TopHeader가 사라졌을 때 input의 위치 top: topSafeArea + 6.15rem */
64+
const inputPositionClasses =
65+
watchedKeyword && 'sticky top-[calc(env(safe-area-inset-top)+6.15rem)]';
66+
67+
/* 검색어가 입력되었을 때 각 컨테이너의 애니메이션 class */
68+
const discoverPageAnimationClasses = `transition duration-500 ${
69+
watchedKeyword ? '-translate-y-[6.05rem]' : 'translate-y-0'
70+
}`;
71+
const headingOpacityClasses = `${
72+
watchedKeyword ? 'opacity-0' : 'opacity-100'
73+
}`;
6274

6375
return (
6476
<>
65-
<div
66-
className={`transition duration-500 ${
67-
watchedKeyword
68-
? '-translate-y-[5.8rem] opacity-0'
69-
: 'translate-y-0 opacity-100'
70-
}`}
71-
>
72-
<TopHeader text={'Discover'} />
73-
</div>
77+
<TopHeader blur={!isScrollAtTop} className={discoverPageAnimationClasses}>
78+
<h1
79+
className={`text-main-900 font-heading-bold ${headingOpacityClasses}`}
80+
>
81+
Discover
82+
</h1>
83+
</TopHeader>
7484
<article
75-
className={`flex w-full flex-col gap-[3rem] transition duration-500 ${
76-
watchedKeyword ? '-translate-y-[5.8rem]' : 'translate-y-0'
77-
}`}
85+
className={`flex w-full flex-col gap-[3rem] ${discoverPageAnimationClasses}`}
7886
>
7987
<Input
8088
type="search"

src/app/bookarchive/page.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@ import BookArchiveForAuth from '@/components/bookArchive/BookArchiveForAuth';
99
import BookArchiveForUnAuth from '@/components/bookArchive/BookArchiveForUnAuth';
1010
import TopHeader from '@/components/common/TopHeader';
1111

12+
import useIsScrollAtTop from '@/hooks/useIsScrollAtTop';
13+
1214
export default function BookArchivePage() {
15+
const { isScrollAtTop } = useIsScrollAtTop();
16+
1317
return (
1418
<div className="flex w-full flex-col gap-[1rem] pb-[2rem]">
15-
<TopHeader text="BookArchive" />
19+
<TopHeader blur={!isScrollAtTop}>
20+
<h1 className="text-main-900 font-heading-bold">BookArchive</h1>
21+
</TopHeader>
1622
{/* TODO: 스켈레톤 컴포넌트로 교체 */}
1723
<SSRSafeSuspense fallback={null}>
1824
<Contents />

src/app/group/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useMyProfileId } from '@/queries/user/useMyProfileQuery';
1212
import useMounted from '@/hooks/useMounted';
1313
import { checkAuthentication } from '@/utils/helpers';
1414
import useToast from '@/components/common/Toast/useToast';
15+
import useIsScrollAtTop from '@/hooks/useIsScrollAtTop';
1516

1617
import FloatingButton from '@/components/common/FloatingButton';
1718
import Loading from '@/components/common/Loading';
@@ -28,6 +29,7 @@ import CreateGroupBanner from '@/components/bookGroup/banner/CreateGroupBanner';
2829
const GroupPage = () => {
2930
const router = useRouter();
3031
const { show: showToast } = useToast();
32+
const { isScrollAtTop } = useIsScrollAtTop();
3133

3234
const isAuthenticated = checkAuthentication();
3335

@@ -50,7 +52,9 @@ const GroupPage = () => {
5052

5153
return (
5254
<>
53-
<TopHeader text="Group" />
55+
<TopHeader blur={!isScrollAtTop}>
56+
<h1 className="text-main-900 font-heading-bold">Group</h1>
57+
</TopHeader>
5458
<div className="flex w-full flex-col gap-[2rem]">
5559
<SearchGroupInput onClick={handleSearchInputClick} />
5660
<SSRSafeSuspense fallback={<PageSkeleton />}>

src/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ export const metadata: Metadata = {
3434
{ rel: 'icon', url: '/favicon.ico' },
3535
],
3636
appleWebApp: {
37+
capable: true,
3738
title: '다독다독',
39+
statusBarStyle: 'black-translucent',
3840
startupImage: appleSplashScreens,
3941
},
4042
};

src/app/login/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const LoginPage = () => {
2727
</p>
2828
</article>
2929

30-
<section className="absolute inset-x-[2rem] bottom-[calc(env(safe-area-inset-bottom)+2rem)] mx-auto flex max-w-[41rem] flex-col justify-center gap-[1rem]">
30+
<section className="absolute inset-x-[2rem] bottom-[2rem] mx-auto flex max-w-[41rem] flex-col justify-center gap-[1rem]">
3131
<LoginLink>
3232
<Button size="full" colorScheme="kakao">
3333
<div className="flex w-full items-center justify-center">

src/app/profile/me/page.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import userKeys from '@/queries/user/key';
1010
import { deleteAuthSession } from '@/server/session';
1111
import { deleteCookie } from '@/utils/cookie';
1212
import { checkAuthentication } from '@/utils/helpers';
13+
import useIsScrollAtTop from '@/hooks/useIsScrollAtTop';
14+
1315
import { SESSION_COOKIES_KEYS } from '@/constants';
1416
import { IconArrowRight } from '@public/icons';
1517

@@ -30,6 +32,7 @@ const USER_ID = 'me';
3032

3133
const MyProfilePage = () => {
3234
const isAuthenticated = checkAuthentication();
35+
3336
return (
3437
<SSRSafeSuspense fallback={<Loading fullpage />}>
3538
{isAuthenticated ? <MyProfileForAuth /> : <MyProfileForUnAuth />}
@@ -38,9 +41,13 @@ const MyProfilePage = () => {
3841
};
3942

4043
const MyProfileForUnAuth = () => {
44+
const { isScrollAtTop } = useIsScrollAtTop();
45+
4146
return (
4247
<>
43-
<TopHeader text="Profile" />
48+
<TopHeader blur={!isScrollAtTop}>
49+
<h1 className="text-main-900 font-heading-bold">Profile</h1>
50+
</TopHeader>
4451
<div className="flex flex-col gap-[3rem]">
4552
<div className="mb-[2rem] flex items-center gap-[1rem]">
4653
<Avatar size="large" />
@@ -81,6 +88,7 @@ const MyProfileForUnAuth = () => {
8188
const MyProfileForAuth = () => {
8289
const queryClient = useQueryClient();
8390
const router = useRouter();
91+
const { isScrollAtTop } = useIsScrollAtTop();
8492

8593
const handleLogoutButtonClick = async () => {
8694
try {
@@ -95,7 +103,11 @@ const MyProfileForAuth = () => {
95103

96104
return (
97105
<>
98-
<TopHeader text="Profile">
106+
<TopHeader
107+
className="flex items-center justify-between"
108+
blur={!isScrollAtTop}
109+
>
110+
<h1 className="text-main-900 font-heading-bold">Profile</h1>
99111
<Menu>
100112
<Menu.Toggle />
101113
<Menu.BottomSheetList>

src/components/common/TopHeader.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import { PropsWithChildren } from 'react';
22

33
type TopHeaderProps = PropsWithChildren<{
4-
text: string;
4+
blur?: boolean;
5+
className?: string;
56
}>;
67

7-
const TopHeader = ({ text, children }: TopHeaderProps) => {
8+
const DEFAULT_HEADER_CLASSES =
9+
'fixed left-0 right-0 top-0 z-30 mx-auto w-full max-w-[43rem] border-0 px-[2rem] pb-[1rem] pt-[calc(env(safe-area-inset-top)+2rem)] transition duration-1000';
10+
11+
const BLUR_HEADER_CLASSES =
12+
'border-b-black-100 border-b-[0.01rem] bg-[#FFFFFFBF] backdrop-blur-[1.6rem]';
13+
14+
const TopHeader = ({
15+
blur = false,
16+
className = '',
17+
children,
18+
}: TopHeaderProps) => {
19+
const blurClasses = blur ? BLUR_HEADER_CLASSES : 'bg-white';
20+
821
return (
9-
<header className="flex w-full items-center justify-between pb-[2rem]">
10-
<h1 className="text-main-900 font-heading-bold">{text}</h1>
22+
<header className={`${DEFAULT_HEADER_CLASSES} ${blurClasses} ${className}`}>
1123
{children}
1224
</header>
1325
);

src/components/common/TopNavigation.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ type ItemProps = TopNavigationProps;
88

99
const TopNavigation = ({ children }: TopNavigationProps) => {
1010
return (
11-
<div className="fixed left-0 right-0 top-0 z-50 mx-auto flex h-[2.4rem] w-full max-w-[43rem] items-center justify-center bg-white px-[4rem] py-[2.7rem] font-body1-regular">
11+
<header className="fixed left-0 right-0 top-0 z-50 mx-auto flex h-[2.4rem] w-full max-w-[43rem] items-center justify-center bg-white px-[4rem] pb-[2.7rem] pt-[calc(env(safe-area-inset-top)+2.7rem)] font-body1-regular">
1212
{children}
13-
</div>
13+
</header>
1414
);
1515
};
1616

1717
const LeftItem = ({ children }: ItemProps) => {
1818
return (
19-
<div className="absolute left-[0rem] flex pl-[2rem] [&_svg]:h-[2rem] [&_svg]:w-[2rem] [&_svg]:cursor-pointer">
19+
<nav className="absolute left-[0rem] flex pl-[2rem] [&_svg]:h-[2rem] [&_svg]:w-[2rem] [&_svg]:cursor-pointer">
2020
{children}
21-
</div>
21+
</nav>
2222
);
2323
};
2424

src/components/layout/Layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ const Layout = ({ children }: LayoutProps) => {
1515
const isRootPath = pathname && rootPaths.includes(pathname);
1616

1717
const dynamicClass = isRootPath
18-
? 'pb-[calc(env(safe-area-inset-bottom)+7rem)] pt-[2rem]'
19-
: 'pb-[calc(env(safe-area-inset-bottom)+2rem)] pt-[5.4rem]';
18+
? 'pb-[calc(env(safe-area-inset-bottom)+7rem)] pt-[calc(env(safe-area-inset-top)+7.15rem)]'
19+
: 'pb-[calc(env(safe-area-inset-bottom)+2rem)] pt-[calc(env(safe-area-inset-top)+5.4rem)]';
2020

2121
return (
2222
<>

src/hooks/useIsScrollAtTop.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { useEffect, useState } from 'react';
2+
3+
const useIsScrollAtTop = () => {
4+
const [isScrollAtTop, setIsScrollAtTop] = useState(true);
5+
6+
const listener = () => {
7+
setIsScrollAtTop(window.scrollY === 0);
8+
};
9+
10+
useEffect(() => {
11+
window.addEventListener('scroll', listener);
12+
return () => {
13+
window.removeEventListener('scroll', listener);
14+
};
15+
}, []);
16+
17+
return { isScrollAtTop };
18+
};
19+
20+
export default useIsScrollAtTop;

0 commit comments

Comments
 (0)