Skip to content
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
4 changes: 2 additions & 2 deletions src/components/HeaderTableInfo/HeaderTableInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export default function HeaderTableInfo(props: HeaderTitleProps) {
</div>
)}
{!isLoading && (
<div className="flex flex-col space-y-[4px]">
<h1 className="text-2xl">{name}</h1>
<div className="flex min-w-0 flex-col space-y-[4px]">
<h1 className="truncate text-lg md:text-2xl">{name}</h1>
</div>
)}
</>
Expand Down
2 changes: 1 addition & 1 deletion src/components/HeaderTitle/HeaderTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function HeaderTitle(props: HeaderTitleProps) {
</div>
)}
{!isLoading && (
<h1 className="w-full max-w-[50vw] overflow-hidden text-ellipsis whitespace-nowrap text-3xl">
<h1 className="w-full max-w-[50vw] overflow-hidden text-ellipsis whitespace-nowrap text-xl md:text-3xl">
{title}
</h1>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/TimerProgressBar/TimerProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default function TimerProgressBar({
return (
<div
className={clsx(
'h-[24px] w-full overflow-hidden rounded-full bg-default-disabled/hover',
'h-[24px] w-full flex-shrink-0 overflow-hidden rounded-full bg-default-disabled/hover',
className,
)}
role="progressbar"
Expand Down
33 changes: 24 additions & 9 deletions src/layout/components/header/StickyTriSectionHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useTranslation } from 'react-i18next';
import { PropsWithChildren } from 'react';
import clsx from 'clsx';
import { useNavigate } from 'react-router-dom';
import useLogout from '../../../hooks/mutations/useLogout';
import { isLoggedIn } from '../../../util/accessToken';
Expand All @@ -26,27 +27,41 @@ function StickyTriSectionHeader(props: PropsWithChildren) {
const { children } = props;

return (
<header className="sticky top-0 z-30 h-[80px] flex-shrink-0 border-b-[3px] border-default-disabled/hover">
<div className="relative flex h-full items-center justify-between p-[16px]">
<header className="sticky top-0 z-30 h-[56px] flex-shrink-0 border-b-[3px] border-default-disabled/hover md:h-[80px] short:h-[56px]">
<div className="relative flex h-full items-center justify-between p-[8px] md:p-[16px] short:p-[8px]">
{children}
</div>
</header>
);
}

StickyTriSectionHeader.Left = function Left(props: PropsWithChildren) {
const { children } = props;
interface SectionProps extends PropsWithChildren {
className?: string;
}

StickyTriSectionHeader.Left = function Left(props: SectionProps) {
const { children, className } = props;
return (
<div className="flex h-full flex-1 items-center justify-start text-start">
<div
className={clsx(
'flex h-full min-w-0 flex-1 items-center justify-start text-start',
className,
)}
>
{children}
</div>
);
};

StickyTriSectionHeader.Center = function Center(props: PropsWithChildren) {
const { children } = props;
StickyTriSectionHeader.Center = function Center(props: SectionProps) {
const { children, className } = props;
return (
<div className="flex h-full flex-1 items-center justify-center text-center">
<div
className={clsx(
'flex h-full min-w-0 flex-1 items-center justify-center text-center',
className,
)}
>
{children}
</div>
);
Expand All @@ -72,7 +87,7 @@ StickyTriSectionHeader.Right = function Right(props: PropsWithChildren) {

return (
<>
<div className="flex h-full flex-1 items-center justify-end gap-[12px] text-right">
<div className="flex h-full flex-1 items-center justify-end gap-[8px] text-right md:gap-[12px]">
{isGuestFlow() && (
<div className="animate-pulse whitespace-nowrap rounded-full bg-neutral-300 px-4 py-2 font-semibold">
{t('비회원 모드')}
Expand Down
2 changes: 1 addition & 1 deletion src/layout/components/main/ContentContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function ContentContainer({
return (
<main
className={`relative flex flex-grow flex-col items-center gap-2 overflow-auto ${
noPadding ? '' : 'px-8 py-4'
noPadding ? '' : 'px-4 py-4 md:px-8'
}`}
>
{children}
Expand Down
6 changes: 5 additions & 1 deletion src/layout/defaultLayout/DefaultLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import ContentContainer from '../components/main/ContentContainer';
function DefaultLayout(props: PropsWithChildren) {
const { children } = props;

return <div className="flex h-screen flex-col">{children}</div>;
return (
<div className="flex h-screen flex-col supports-[height:100dvh]:h-dvh">
{children}
</div>
);
}

DefaultLayout.Header = StickyTriSectionHeader;
Expand Down
24 changes: 22 additions & 2 deletions src/page/AudienceSharePage/AudienceSharePage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,9 @@ describe('AudienceSharePage', () => {
await screen.findByText('토론 시작을 대기 중입니다.'),
).toBeInTheDocument();
expect(screen.getByText('공유 토론 테이블')).toBeInTheDocument();
expect(screen.getByText('공유 토론 주제')).toBeInTheDocument();
expect(
screen.getByRole('heading', { name: '공유 토론 주제' }),
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: '언어 선택' }),
).toBeInTheDocument();
Expand Down Expand Up @@ -259,7 +261,25 @@ describe('AudienceSharePage', () => {
renderPage('/live/123');

expect(await screen.findByText('테이블 이름 없음')).toBeInTheDocument();
expect(screen.getByText('주제 없음')).toBeInTheDocument();
expect(
screen.getByRole('heading', { name: '주제 없음' }),
).toBeInTheDocument();
});

it('모바일에서는 헤더 대신 본문 상단에 주제를 표시한다', async () => {
mockUseAudienceShareState.mockReturnValue({
status: 'waiting',
error: null,
});
renderPage('/live/123');

const mobileAgenda = await screen.findByTestId('mobile-agenda');

expect(mobileAgenda).toHaveTextContent('공유 토론 주제');
expect(mobileAgenda).toHaveClass('md:hidden');
expect(
screen.getByRole('heading', { name: '공유 토론 주제' }).parentElement,
).toHaveClass('hidden', 'md:flex');
});

it('displaying 상태 (NORMAL)에서는 AudienceNormalTimer에 올바른 props가 전달된다', async () => {
Expand Down
52 changes: 29 additions & 23 deletions src/page/AudienceSharePage/AudienceSharePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function ErrorContent({ message, onReload }: ErrorContentProps) {
data-testid="audience-share-error-icon"
aria-hidden="true"
/>
<p className="text-xl font-semibold text-gray-800 xl:text-2xl">
<p className="break-keep px-4 text-xl font-semibold text-gray-800 xl:text-2xl">
{message}
</p>
<button
Expand Down Expand Up @@ -136,15 +136,15 @@ export default function AudienceSharePage() {
case 'WAITING':
return (
<div className="flex h-full w-full flex-col items-center justify-center space-y-[20px]">
<h1 className="text-center text-2xl font-bold text-gray-800 xl:text-4xl">
<h1 className="break-keep px-4 text-center text-2xl font-bold text-gray-800 xl:text-4xl">
{viewState.message}
</h1>
</div>
);

case 'NORMAL_TIMER':
return (
<div className="flex h-full w-full items-center justify-center">
<div className="flex h-full w-full items-center justify-center [align-items:safe_center]">
<AudienceNormalTimer
remainingTime={
normalCountdown.currentSeconds ??
Expand Down Expand Up @@ -179,7 +179,7 @@ export default function AudienceSharePage() {
viewState.timeBox.timePerSpeaking);

return (
<div className="flex h-full w-full items-center justify-center px-4 xl:px-12">
<div className="flex h-full w-full items-center justify-center [align-items:safe_center] md:px-4 xl:px-12">
<AudienceTimeBasedTimer
prosTeamName={viewState.prosTeamName}
consTeamName={viewState.consTeamName}
Expand Down Expand Up @@ -220,36 +220,42 @@ export default function AudienceSharePage() {
viewState.type === 'NORMAL_TIMER' ||
viewState.type === 'TIME_BASED_TIMER';

const tableInfo = debateTableQuery.data?.info;
const tableNameLabel =
!tableInfo?.name || tableInfo.name.trim() === ''
? t('테이블 이름 없음')
: t(tableInfo.name);
const agendaLabel =
!tableInfo?.agenda || tableInfo.agenda.trim() === ''
? t('주제 없음')
: t(tableInfo.agenda);
const shouldShowHeader = isHeaderVisible && !!tableInfo;

return (
<DefaultLayout>
{isHeaderVisible && debateTableQuery.data ? (
{shouldShowHeader ? (
<DefaultLayout.Header>
<DefaultLayout.Header.Left>
<HeaderTableInfo
name={
!debateTableQuery.data.info.name ||
debateTableQuery.data.info.name.trim() === ''
? t('테이블 이름 없음')
: t(debateTableQuery.data.info.name)
}
/>
<HeaderTableInfo name={tableNameLabel} />
</DefaultLayout.Header.Left>
<DefaultLayout.Header.Center>
<HeaderTitle
title={
!debateTableQuery.data.info.agenda ||
debateTableQuery.data.info.agenda.trim() === ''
? t('주제 없음')
: t(debateTableQuery.data.info.agenda)
}
/>
{/* 세로 모드 모바일에서는 헤더 공간이 좁아 주제를 본문 상단에 표시 */}
<DefaultLayout.Header.Center className="hidden md:flex short:flex">
<HeaderTitle title={agendaLabel} />
</DefaultLayout.Header.Center>
<DefaultLayout.Header.Right />
</DefaultLayout.Header>
) : null}
<DefaultLayout.ContentContainer>
<div className="relative flex h-full w-full flex-col">
{renderContent()}
{shouldShowHeader ? (
<p
data-testid="mobile-agenda"
className="line-clamp-2 flex-shrink-0 break-keep pb-2 text-center text-lg font-semibold text-default-black md:hidden short:hidden"
>
{agendaLabel}
</p>
) : null}
<div className="min-h-0 w-full flex-1">{renderContent()}</div>
</div>
</DefaultLayout.ContentContainer>
</DefaultLayout>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ describe('AudienceNormalTimer', () => {
const timerValue = screen.getByTestId('timer-value');

expect(debateIcon).toHaveAttribute('aria-hidden', 'true');
expect(debateIcon).toHaveClass('h-[34px]');
expect(participantRow).toHaveClass('mt-[24px]', 'text-[34px]');
expect(debateIcon).toHaveClass('h-[20px]', 'md:h-[34px]');
expect(participantRow).toHaveClass('md:mt-[24px]', 'md:text-[34px]');
expect(screen.getByText('찬성 팀')).toBeInTheDocument();
expect(screen.getByText('김토론 토론자')).toBeInTheDocument();
expect(timerValue).toHaveClass(
'mt-[64px]',
'md:mt-[64px]',
'grid-cols-[2ch_1ch_2ch]',
'gap-x-[0.33ch]',
);
Expand All @@ -40,7 +40,7 @@ describe('AudienceNormalTimer', () => {
'aria-valuenow',
'50',
);
expect(screen.getByRole('progressbar')).toHaveClass('mt-[108px]');
expect(screen.getByRole('progressbar')).toHaveClass('md:mt-[108px]');
expect(screen.queryByRole('button')).not.toBeInTheDocument();
});

Expand Down
12 changes: 6 additions & 6 deletions src/page/AudienceSharePage/components/AudienceNormalTimer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,18 @@ export default function AudienceNormalTimer({
const isOvertime = remainingTime < 0;

return (
<div className="flex h-full w-full flex-col items-center justify-center px-4">
<h1 className="text-center text-[52px] font-bold xl:text-[68px]">
<div className="flex h-full w-full flex-col items-center justify-center [justify-content:safe_center] md:px-4">
<h1 className="break-keep text-center text-[36px] font-bold md:text-[52px] xl:text-[68px] short:text-[28px]">
{speechTypeLabel}
</h1>

{stance !== 'NEUTRAL' && (
<div
className="mt-[24px] flex max-w-full items-center justify-center gap-4 text-[34px]"
className="mt-[12px] flex max-w-full items-center justify-center gap-2 text-[20px] md:mt-[24px] md:gap-4 md:text-[34px] short:mt-[4px] short:text-[18px]"
data-testid="participant-row"
>
<DTDebate
className="h-[34px] flex-shrink-0"
className="h-[20px] flex-shrink-0 md:h-[34px] short:h-[18px]"
data-testid="debate-icon"
aria-hidden="true"
/>
Expand All @@ -72,7 +72,7 @@ export default function AudienceNormalTimer({
)}

<span
className="relative mt-[64px] grid w-[5ch] grid-cols-[2ch_1ch_2ch] items-center justify-center gap-x-[0.33ch] text-[70px] font-bold tabular-nums text-default-black xl:text-[110px]"
className="relative mt-[32px] grid w-[5ch] grid-cols-[2ch_1ch_2ch] items-center justify-center gap-x-[0.33ch] text-[length:min(20vw,96px)] font-bold tabular-nums leading-none text-default-black md:mt-[64px] md:text-[70px] md:leading-normal xl:text-[110px] short:mt-[8px] short:text-[length:min(22vh,96px)] short:leading-none"
data-testid="timer-value"
aria-label={`${isOvertime ? '- ' : ''}${minutes} : ${seconds}`}
>
Expand All @@ -96,7 +96,7 @@ export default function AudienceNormalTimer({
progress={progress}
team={getProgressBarTeam(stance)}
isRunning={isRunning}
className="mt-[108px] max-w-[1280px]"
className="mt-[48px] max-w-[1280px] md:mt-[108px] short:mt-[16px] short:h-[12px] short:max-w-[480px]"
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function AudienceTimeBasedTimer({
}: AudienceTimeBasedTimerProps) {
return (
<div
className="flex h-full w-full flex-row gap-8"
className="flex h-full w-full flex-row gap-2 md:gap-8"
data-testid="time-based-timer-row"
>
<AudienceTimeBasedTimerDisplay
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,25 @@ describe('AudienceTimeBasedTimerDisplay', () => {
);

expect(screen.getByText('전체 시간')).toHaveClass(
'h-[48px]',
'w-[144px]',
'md:h-[48px]',
'md:w-[144px]',
'bg-default-black',
'text-[24px]',
'md:text-[24px]',
);
expect(screen.getByText('현재 시간')).toHaveClass(
'h-[64px]',
'w-[200px]',
'md:h-[64px]',
'md:w-[200px]',
'bg-camp-red',
'text-[32px]',
);
'md:text-[32px]',
);
// 모바일(768px 미만, 가로 모드)에서는 배지를 화면에서만 숨기고 스크린 리더에는 남긴다
['전체 시간', '현재 시간'].forEach((label) => {
expect(screen.getByText(label)).toHaveClass(
'sr-only',
'md:not-sr-only',
'short:sr-only',
);
});
expect(screen.getByTestId('cons-total-timer')).toHaveAttribute(
'aria-label',
'04 : 00',
Expand Down
Loading
Loading