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

[#685] [fix] iOS PWA 환경에서의 status bar 스타일 추가 #687

Merged
merged 25 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
757ccec
fix: apple web app status bar style 태그 추가
hanyugeon Aug 8, 2024
c0ac8d2
fix: statusBarStyle을 default로 수정 및 capable를 true로 명시
hanyugeon Aug 8, 2024
1eaecdf
fix: manifest와 metadata에서 theme, background color 제거
hanyugeon Aug 9, 2024
513809e
fix: capable, statusBarStyle 속성 추가 및 layout 상단 패딩에 safeArea 추가
hanyugeon Aug 9, 2024
458eefe
fix: statusBarStyle을 black-translucent로 수정
hanyugeon Aug 9, 2024
ff84ff3
fix: RootLayout 패딩 값 수정
hanyugeon Aug 10, 2024
d52682e
feat: TopHeader safeArea값 추가 및 className props 추가
hanyugeon Aug 10, 2024
5ecb565
feat: TopNavigation safeArea값 추가
hanyugeon Aug 10, 2024
cbaf145
fix: TopHeader 수정 이후 애니메이션이 정상작동 하도록 수정
hanyugeon Aug 10, 2024
650992d
feat: manifest에 themeColor, backgroundColor 추가
hanyugeon Aug 10, 2024
ed5e069
fix: TopHeader에 누락된 safeArea 추가
hanyugeon Aug 10, 2024
f2e48a6
feat: useIsScrollAtTop 커스텀 훅 작성
hanyugeon Aug 15, 2024
7c4429b
feat: TopHeader에 blur 스타일 및 props 추가
hanyugeon Aug 15, 2024
797469d
refactor: 각 페이지에 변경된 TopHeader 적용
hanyugeon Aug 15, 2024
b1eaf7a
chore: metadata에 themeColor 다시 추가
hanyugeon Aug 15, 2024
019285b
fix: PWA환경에서의 도서검색 페이지 input position 수정
hanyugeon Aug 15, 2024
e3c40a0
fix: 도서 검색 페이지 safeArea 수정
hanyugeon Aug 15, 2024
ace6a8f
fix: 도서 검색 페이지 translate값 수정
hanyugeon Aug 15, 2024
2c464cc
refactor: TopHeader 컴포넌트의 text props의존성을 제거
hanyugeon Aug 15, 2024
20c766c
fix: 각 페이지의 리팩토링 된 TopHeader 적용
hanyugeon Aug 15, 2024
a6d6bbb
fix: PWA환경에서 상단 노치부분에 컨탠츠가 노출되던 현상 수정
hanyugeon Aug 15, 2024
a4a97cb
fix: body 스크롤 시 컨탠츠가 좁은 틈새로 보이는 버그 수정
hanyugeon Aug 15, 2024
27e113b
fix: PWA환경에서 Login 페이지의 로그인 섹션이 비정상적인 위치에 있던 버그 수정
hanyugeon Aug 15, 2024
0c952bf
fix: TopHeader 스토리북에 appLayoutMeta 적용
hanyugeon Aug 18, 2024
a3fd60c
feat: 도서 검색 페이지에 누락된 TopHeader blur 추가
hanyugeon Aug 19, 2024
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
36 changes: 22 additions & 14 deletions src/app/book/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import bookAPI from '@/apis/book';
import SSRSafeSuspense from '@/components/common/SSRSafeSuspense';
import useDebounceValue from '@/hooks/useDebounce';
import useQueryParams from '@/hooks/useQueryParams';
import useIsScrollAtTop from '@/hooks/useIsScrollAtTop';
import { checkAuthentication } from '@/utils/helpers';

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

const { isScrollAtTop } = useIsScrollAtTop();

/* debounce된 keyword값에 따라 queryParameter를 수정하는 useEffect */
useEffect(() => {
const queryValue = getQueryParam(KEYWORD);
Expand All @@ -57,24 +60,29 @@ const BookSearchPage = () => {
}
}, [debouncedKeyword, getQueryParam, setQueryParams, removeQueryParam]);

/* TopHeader가 사라졌을 때 input의 위치 top: 5.8rem */
const inputPositionClasses = watchedKeyword && 'sticky top-[5.8rem]';
/* TopHeader가 사라졌을 때 input의 위치 top: topSafeArea + 6.15rem */
const inputPositionClasses =
watchedKeyword && 'sticky top-[calc(env(safe-area-inset-top)+6.15rem)]';

/* 검색어가 입력되었을 때 각 컨테이너의 애니메이션 class */
const discoverPageAnimationClasses = `transition duration-500 ${
watchedKeyword ? '-translate-y-[6.05rem]' : 'translate-y-0'
}`;
const headingOpacityClasses = `${
watchedKeyword ? 'opacity-0' : 'opacity-100'
}`;

return (
<>
<div
className={`transition duration-500 ${
watchedKeyword
? '-translate-y-[5.8rem] opacity-0'
: 'translate-y-0 opacity-100'
}`}
>
<TopHeader text={'Discover'} />
</div>
<TopHeader blur={!isScrollAtTop} className={discoverPageAnimationClasses}>
<h1
className={`text-main-900 font-heading-bold ${headingOpacityClasses}`}
>
Discover
</h1>
</TopHeader>
<article
className={`flex w-full flex-col gap-[3rem] transition duration-500 ${
watchedKeyword ? '-translate-y-[5.8rem]' : 'translate-y-0'
}`}
className={`flex w-full flex-col gap-[3rem] ${discoverPageAnimationClasses}`}
>
<Input
type="search"
Expand Down
8 changes: 7 additions & 1 deletion src/app/bookarchive/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ import BookArchiveForAuth from '@/components/bookArchive/BookArchiveForAuth';
import BookArchiveForUnAuth from '@/components/bookArchive/BookArchiveForUnAuth';
import TopHeader from '@/components/common/TopHeader';

import useIsScrollAtTop from '@/hooks/useIsScrollAtTop';

export default function BookArchivePage() {
const { isScrollAtTop } = useIsScrollAtTop();

return (
<div className="flex w-full flex-col gap-[1rem] pb-[2rem]">
<TopHeader text="BookArchive" />
<TopHeader blur={!isScrollAtTop}>
<h1 className="text-main-900 font-heading-bold">BookArchive</h1>
</TopHeader>
{/* TODO: 스켈레톤 컴포넌트로 교체 */}
<SSRSafeSuspense fallback={null}>
<Contents />
Expand Down
6 changes: 5 additions & 1 deletion src/app/group/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useMyProfileId } from '@/queries/user/useMyProfileQuery';
import useMounted from '@/hooks/useMounted';
import { checkAuthentication } from '@/utils/helpers';
import useToast from '@/components/common/Toast/useToast';
import useIsScrollAtTop from '@/hooks/useIsScrollAtTop';

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

const isAuthenticated = checkAuthentication();

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

return (
<>
<TopHeader text="Group" />
<TopHeader blur={!isScrollAtTop}>
<h1 className="text-main-900 font-heading-bold">Group</h1>
</TopHeader>
<div className="flex w-full flex-col gap-[2rem]">
<SearchGroupInput onClick={handleSearchInputClick} />
<SSRSafeSuspense fallback={<PageSkeleton />}>
Expand Down
2 changes: 2 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ export const metadata: Metadata = {
{ rel: 'icon', url: '/favicon.ico' },
],
appleWebApp: {
capable: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment;

요건 어떤 속성인가요?!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gxxrxn

iOS 모바일 환경에서 상단 노치 부분까지 확장해서 전체화면으로 보여주도록 하는 옵션이에요!
해당 옵션을 true로 하여 BottomSheet 혹은 Menu 가 동작할때
나타나는 Dim 영역이 상단 노치 부분까지 포함 되도록 했습니다!

title: '다독다독',
statusBarStyle: 'black-translucent',
startupImage: appleSplashScreens,
},
};
Expand Down
2 changes: 1 addition & 1 deletion src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const LoginPage = () => {
</p>
</article>

<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]">
<section className="absolute inset-x-[2rem] bottom-[2rem] mx-auto flex max-w-[41rem] flex-col justify-center gap-[1rem]">
<LoginLink>
<Button size="full" colorScheme="kakao">
<div className="flex w-full items-center justify-center">
Expand Down
16 changes: 14 additions & 2 deletions src/app/profile/me/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import userKeys from '@/queries/user/key';
import { deleteAuthSession } from '@/server/session';
import { deleteCookie } from '@/utils/cookie';
import { checkAuthentication } from '@/utils/helpers';
import useIsScrollAtTop from '@/hooks/useIsScrollAtTop';

import { SESSION_COOKIES_KEYS } from '@/constants';
import { IconArrowRight } from '@public/icons';

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

const MyProfilePage = () => {
const isAuthenticated = checkAuthentication();

return (
<SSRSafeSuspense fallback={<Loading fullpage />}>
{isAuthenticated ? <MyProfileForAuth /> : <MyProfileForUnAuth />}
Expand All @@ -38,9 +41,13 @@ const MyProfilePage = () => {
};

const MyProfileForUnAuth = () => {
const { isScrollAtTop } = useIsScrollAtTop();

return (
<>
<TopHeader text="Profile" />
<TopHeader blur={!isScrollAtTop}>
<h1 className="text-main-900 font-heading-bold">Profile</h1>
</TopHeader>
<div className="flex flex-col gap-[3rem]">
<div className="mb-[2rem] flex items-center gap-[1rem]">
<Avatar size="large" />
Expand Down Expand Up @@ -81,6 +88,7 @@ const MyProfileForUnAuth = () => {
const MyProfileForAuth = () => {
const queryClient = useQueryClient();
const router = useRouter();
const { isScrollAtTop } = useIsScrollAtTop();

const handleLogoutButtonClick = async () => {
try {
Expand All @@ -95,7 +103,11 @@ const MyProfileForAuth = () => {

return (
<>
<TopHeader text="Profile">
<TopHeader
className="flex items-center justify-between"
blur={!isScrollAtTop}
>
<h1 className="text-main-900 font-heading-bold">Profile</h1>
<Menu>
<Menu.Toggle />
<Menu.DropdownList>
Expand Down
20 changes: 16 additions & 4 deletions src/components/common/TopHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { PropsWithChildren } from 'react';

type TopHeaderProps = PropsWithChildren<{
text: string;
blur?: boolean;
className?: string;
}>;

const TopHeader = ({ text, children }: TopHeaderProps) => {
const DEFAULT_HEADER_CLASSES =
'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';

const BLUR_HEADER_CLASSES =
'border-b-black-100 border-b-[0.01rem] bg-[#FFFFFFBF] backdrop-blur-[1.6rem]';

const TopHeader = ({
blur = false,
className = '',
children,
}: TopHeaderProps) => {
const blurClasses = blur ? BLUR_HEADER_CLASSES : 'bg-white';

return (
<header className="flex w-full items-center justify-between pb-[2rem]">
<h1 className="text-main-900 font-heading-bold">{text}</h1>
<header className={`${DEFAULT_HEADER_CLASSES} ${blurClasses} ${className}`}>
{children}
</header>
);
Expand Down
8 changes: 4 additions & 4 deletions src/components/common/TopNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ type ItemProps = TopNavigationProps;

const TopNavigation = ({ children }: TopNavigationProps) => {
return (
<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">
<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">
{children}
</div>
</header>
);
};

const LeftItem = ({ children }: ItemProps) => {
return (
<div className="absolute left-[0rem] flex pl-[2rem] [&_svg]:h-[2rem] [&_svg]:w-[2rem] [&_svg]:cursor-pointer">
<nav className="absolute left-[0rem] flex pl-[2rem] [&_svg]:h-[2rem] [&_svg]:w-[2rem] [&_svg]:cursor-pointer">
{children}
</div>
</nav>
);
};

Expand Down
4 changes: 2 additions & 2 deletions src/components/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const Layout = ({ children }: LayoutProps) => {
const isRootPath = pathname && rootPaths.includes(pathname);

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

return (
<>
Expand Down
20 changes: 20 additions & 0 deletions src/hooks/useIsScrollAtTop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useEffect, useState } from 'react';

const useIsScrollAtTop = () => {
const [isScrollAtTop, setIsScrollAtTop] = useState(true);

const listener = () => {
setIsScrollAtTop(window.scrollY === 0);
};

useEffect(() => {
window.addEventListener('scroll', listener);
return () => {
window.removeEventListener('scroll', listener);
};
}, []);

return { isScrollAtTop };
};

export default useIsScrollAtTop;
39 changes: 25 additions & 14 deletions src/stories/common/TopHeader.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,44 @@
import { Meta, StoryObj } from '@storybook/react';

import { IconHamburger } from '@public/icons';
import TopHeader from '@/components/common/TopHeader';

import { appLayoutMeta } from '@/stories/meta';

const meta: Meta<typeof TopHeader> = {
title: 'Common/TopHeader',
component: TopHeader,
tags: ['autodocs'],
...appLayoutMeta,
};

export default meta;

type Story = StoryObj<typeof TopHeader>;

export const Default: Story = {
args: { text: 'BookArchive' },
render: args => <TopHeader {...args} />,
render: () => (
<TopHeader>
<h1 className="text-main-900 font-heading-bold">BookArchive</h1>
</TopHeader>
),
};

export const WithMenu: Story = {
args: { text: 'Profile' },
export const WithChildren: Story = {
args: {
className: 'flex items-center justify-between',
},
render: args => (
<TopHeader {...args}>
<button
onClick={() => {
alert('HAMBURGUR MENU!🍔');
}}
>
<IconHamburger width={20} height={20} alt="햄버거메뉴" />
</button>
</TopHeader>
<main className="w-full max-w-[43rem] bg-black-300">
<TopHeader {...args}>
<h1 className="text-main-900 font-heading-bold">Profile</h1>
<button
onClick={() => {
alert('HAMBURGUR MENU!🍔');
}}
>
<IconHamburger width={20} height={20} alt="햄버거메뉴" />
</button>
</TopHeader>
</main>
),
};
Loading