style(ui): review toggle + stats layout + exam spacing + sidebar fix — 7I.3

This commit is contained in:
reloop
2026-04-12 13:44:48 +09:00
parent 4c61b87052
commit 59f66528ca
5 changed files with 315 additions and 182 deletions

View File

@@ -5,6 +5,7 @@ import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { AxiosError } from 'axios';
import styled, { css } from 'styled-components';
import AppShell from '@/components/layout/AppShell';
import { Icon } from '@/components/ui/Icon';
import { useToast } from '@/components/ui/Toast';
import { Button, KbdHint } from '@/components/ui/primitives';
@@ -44,6 +45,7 @@ const KEYBOARD_HINTS = [
{ keys: ['S'], label: '스킵' },
{ keys: ['←'], label: '이전' },
] as const;
const FOCUS_MODE_STORAGE_KEY = 'reloop-review-focus-mode';
export default function ReviewPage() {
const router = useRouter();
@@ -61,6 +63,20 @@ export default function ReviewPage() {
const [dashboardSummary, setDashboardSummary] = useState<DashboardSummary | null>(null);
const [activityLogs, setActivityLogs] = useState<StudyLog[]>([]);
const [detailCache, setDetailCache] = useState<Record<number, StudyLogDetail>>({});
const [focusMode, setFocusMode] = useState(true);
useEffect(() => {
if (typeof window === 'undefined') return;
const storedValue = window.localStorage.getItem(FOCUS_MODE_STORAGE_KEY);
if (storedValue !== null) {
setFocusMode(storedValue === 'true');
}
}, []);
useEffect(() => {
if (typeof window === 'undefined') return;
window.localStorage.setItem(FOCUS_MODE_STORAGE_KEY, String(focusMode));
}, [focusMode]);
const load = useCallback(async () => {
if (!hasToken()) {
@@ -331,17 +347,22 @@ export default function ReviewPage() {
router.push('/dashboard');
}, [router]);
const wrapLayout = useCallback(
(content: React.ReactNode) => (focusMode ? content : <AppShell>{content}</AppShell>),
[focusMode],
);
if (loading) {
return (
<FullscreenState>
return wrapLayout(
<FullscreenState $embedded={!focusMode}>
<StateCopy> ...</StateCopy>
</FullscreenState>
</FullscreenState>,
);
}
if (error || queue === null) {
return (
<FullscreenState>
return wrapLayout(
<FullscreenState $embedded={!focusMode}>
<StateCard>
<StateTitle> .</StateTitle>
<StateSub>
@@ -358,14 +379,14 @@ export default function ReviewPage() {
</Link>
</StateActions>
</StateCard>
</FullscreenState>
</FullscreenState>,
);
}
if (queue.length === 0) {
return (
<Page>
<AmbientGlow />
return wrapLayout(
<Page $focusMode={focusMode}>
<AmbientGlow $visible={focusMode} />
<FocusHeader>
<HeaderButton type="button" onClick={goBack} aria-label="이전으로">
<Icon name="caret-left" size={18} weight="bold" />
@@ -376,7 +397,14 @@ export default function ReviewPage() {
</LogoBadge>
<LogoText>ReLoop</LogoText>
</BrandMark>
<FocusBadge> </FocusBadge>
<FocusToggleButton
type="button"
$active={focusMode}
onClick={() => setFocusMode((prev) => !prev)}
>
<Icon name="target" size={16} weight={focusMode ? 'fill' : 'regular'} />
: {focusMode ? 'ON' : 'OFF'}
</FocusToggleButton>
</FocusHeader>
<Content>
@@ -418,13 +446,13 @@ export default function ReviewPage() {
</CompletionActions>
</CompletionCard>
</Content>
</Page>
</Page>,
);
}
return (
<Page>
<AmbientGlow />
return wrapLayout(
<Page $focusMode={focusMode}>
<AmbientGlow $visible={focusMode} />
<FocusHeader>
<HeaderButton type="button" onClick={goBack} aria-label="이전으로">
@@ -438,7 +466,14 @@ export default function ReviewPage() {
<LogoText>ReLoop</LogoText>
</BrandMark>
<FocusBadge> </FocusBadge>
<FocusToggleButton
type="button"
$active={focusMode}
onClick={() => setFocusMode((prev) => !prev)}
>
<Icon name="target" size={16} weight={focusMode ? 'fill' : 'regular'} />
: {focusMode ? 'ON' : 'OFF'}
</FocusToggleButton>
</FocusHeader>
<Content>
@@ -660,7 +695,7 @@ export default function ReviewPage() {
</ActionGrid>
)}
</Content>
</Page>
</Page>,
);
}
@@ -707,18 +742,21 @@ function normalizeOptionalText(value: string | null | undefined): string | null
return trimmed.length > 0 ? trimmed : null;
}
const Page = styled.div`
min-height: 100vh;
const Page = styled.div<{ $focusMode: boolean }>`
min-height: ${({ $focusMode }) => ($focusMode ? '100vh' : 'calc(100vh - 32px)')};
background: ${theme.color.bgDeep};
position: relative;
overflow: hidden;
border-radius: ${({ $focusMode }) => ($focusMode ? '0' : theme.radius.lg)};
`;
const AmbientGlow = styled.div`
const AmbientGlow = styled.div<{ $visible: boolean }>`
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
opacity: ${({ $visible }) => ($visible ? 1 : 0)};
transition: opacity 0.2s ease;
&::before,
&::after {
@@ -842,20 +880,25 @@ const LogoText = styled.span`
}
`;
const FocusBadge = styled.span`
const FocusToggleButton = styled.button<{ $active: boolean }>`
justify-self: end;
display: inline-flex;
align-items: center;
padding: 7px 14px;
gap: 8px;
min-height: 40px;
padding: 0 14px;
border-radius: ${theme.radius.pill};
border: 1px solid rgba(79, 70, 229, 0.28);
background: rgba(79, 70, 229, 0.14);
color: #a5b4fc;
border: 1px solid
${({ $active }) =>
$active ? 'rgba(129, 140, 248, 0.44)' : theme.color.borderSoftAlpha};
background: ${({ $active }) =>
$active ? 'rgba(79, 70, 229, 0.18)' : 'rgba(255, 255, 255, 0.03)'};
color: ${({ $active }) => ($active ? '#a5b4fc' : theme.color.textSub)};
font-size: 13px;
font-weight: 600;
font-weight: 700;
@media (max-width: ${theme.breakpoint.mobile}) {
padding: 6px 10px;
padding: 0 10px;
font-size: 12px;
}
`;
@@ -1320,8 +1363,8 @@ const ActionShortcut = styled.span`
font-weight: 700;
`;
const FullscreenState = styled.div`
min-height: 100vh;
const FullscreenState = styled.div<{ $embedded: boolean }>`
min-height: ${({ $embedded }) => ($embedded ? '60vh' : '100vh')};
display: flex;
align-items: center;
justify-content: center;

View File

@@ -988,7 +988,7 @@ function resultLabel(result: StudyLog['result']) {
const PageWrap = styled.div`
display: flex;
flex-direction: column;
gap: ${theme.space.lg};
gap: 24px;
`;
const Header = styled.header`
@@ -1064,15 +1064,15 @@ const RangeButton = styled.button<{ $active: boolean }>`
const Panels = styled.div`
display: grid;
gap: ${theme.space.lg};
gap: 24px;
`;
const BottomGrid = styled.div`
display: grid;
grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
gap: ${theme.space.lg};
grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
gap: 24px;
@media (max-width: ${theme.breakpoint.desktop}) {
@media (max-width: ${theme.breakpoint.tablet}) {
grid-template-columns: 1fr;
}
`;
@@ -1082,21 +1082,16 @@ const PanelCard = styled.section`
overflow: hidden;
border: 1px solid ${theme.color.borderSoftAlpha};
border-radius: ${theme.radius.lg};
background:
radial-gradient(circle at top right, rgba(79, 70, 229, 0.12), transparent 34%),
${theme.color.surfaceDeep};
box-shadow: ${theme.shadow.cardElevated};
padding: ${theme.space.lg};
background: ${theme.color.surfaceDeep};
padding: 20px;
@media (max-width: ${theme.breakpoint.tablet}) {
padding: ${theme.space.md};
padding: 16px;
}
`;
const HeatmapCard = styled(PanelCard)`
width: min(100%, 720px);
justify-self: center;
margin-inline: auto;
width: 100%;
`;
const PanelHeader = styled.div`

View File

@@ -55,6 +55,7 @@ export default function ExamPage() {
const [isMobile, setIsMobile] = useState(false);
const [panelWidth, setPanelWidth] = useState(DEFAULT_PANEL_WIDTH);
const [isDraggingPanel, setIsDraggingPanel] = useState(false);
const [passageCollapsed, setPassageCollapsed] = useState(false);
const examStartedAtRef = useRef<number>(0);
const problemEnteredAtRef = useRef<number>(0);
@@ -123,6 +124,7 @@ export default function ExamPage() {
setMenuOpen(false);
setJumpOpen(false);
setProblemModalOpen(false);
setPassageCollapsed(false);
} catch {
setError('문제집을 불러오지 못했어. 네트워크 상태를 확인해줘.');
} finally {
@@ -427,7 +429,7 @@ export default function ExamPage() {
const answeredCurrent = answers[currentProblem.id] !== undefined && answers[currentProblem.id] !== null;
const examContent = (
<Page ref={pageRef}>
<Page ref={pageRef} $focusMode={focusMode}>
<TopBar>
<BackButton type="button" onClick={() => setShowLeaveConfirm(true)}>
<Icon name="arrow-left" size={18} />
@@ -506,17 +508,31 @@ export default function ExamPage() {
{passage && (
<PassageCard>
<PassageHeader>
<span>
[{passage.startNumber}~{passage.endNumber}]
</span>
{isMobile && (
<PassageHint>
<Icon name="arrows-clockwise" size={14} />
</PassageHint>
)}
<PassageHeaderRow>
<span>
[{passage.startNumber}~{passage.endNumber}]
</span>
{isMobile && (
<PassageHint>
<Icon name="arrows-clockwise" size={14} />
</PassageHint>
)}
</PassageHeaderRow>
<PassageToggle
type="button"
onClick={(event) => {
event.stopPropagation();
setPassageCollapsed((prev) => !prev);
}}
>
<PassageToggleChevron $collapsed={passageCollapsed}>
<Icon name="caret-down" size={14} weight="bold" />
</PassageToggleChevron>
{passageCollapsed ? '펼치기' : '접기'}
</PassageToggle>
</PassageHeader>
<PassageBody>{passage.bodyText}</PassageBody>
{!passageCollapsed && <PassageBody>{passage.bodyText}</PassageBody>}
</PassageCard>
)}
@@ -531,36 +547,38 @@ export default function ExamPage() {
)}
</ProblemMetaRow>
<ProblemTitle>{currentProblem.title}</ProblemTitle>
{currentProblemImageUrl ? (
<>
<ProblemImageButton
type="button"
onClick={(event) => {
event.stopPropagation();
openProblemModal();
}}
aria-label={`${problemSet.subjectName} ${currentProblem.number}번 이미지 크게 보기`}
>
<ProblemImage
src={currentProblemImageUrl}
alt={`${problemSet.subjectName} ${currentProblem.number}`}
/>
{isMobile && (
<ProblemImageHint>
<Icon name="export" size={14} />
</ProblemImageHint>
<ProblemContentScroll>
{currentProblemImageUrl ? (
<>
<ProblemImageButton
type="button"
onClick={(event) => {
event.stopPropagation();
openProblemModal();
}}
aria-label={`${problemSet.subjectName} ${currentProblem.number}번 이미지 크게 보기`}
>
<ProblemImage
src={currentProblemImageUrl}
alt={`${problemSet.subjectName} ${currentProblem.number}`}
/>
{isMobile && (
<ProblemImageHint>
<Icon name="export" size={14} />
</ProblemImageHint>
)}
</ProblemImageButton>
{currentProblemBodyText && (
<ProblemBody $muted>{currentProblemBodyText}</ProblemBody>
)}
</ProblemImageButton>
{currentProblemBodyText && (
<ProblemBody $muted>{currentProblemBodyText}</ProblemBody>
)}
</>
) : (
<ProblemBody>
{currentProblemBodyText ?? '본문 텍스트가 비어 있어.'}
</ProblemBody>
)}
</>
) : (
<ProblemBody>
{currentProblemBodyText ?? '본문 텍스트가 비어 있어.'}
</ProblemBody>
)}
</ProblemContentScroll>
{currentProblem.topic && <TopicChip>{currentProblem.topic}</TopicChip>}
</ProblemTextCard>
</ProblemPanel>
@@ -737,10 +755,21 @@ export default function ExamPage() {
</ReadModalHeader>
{passage && (
<ReadSection>
<ReadSectionTitle>
[{passage.startNumber}~{passage.endNumber}]
</ReadSectionTitle>
<ReadText>{passage.bodyText}</ReadText>
<ReadSectionHeader>
<ReadSectionTitle>
[{passage.startNumber}~{passage.endNumber}]
</ReadSectionTitle>
<PassageToggle
type="button"
onClick={() => setPassageCollapsed((prev) => !prev)}
>
<PassageToggleChevron $collapsed={passageCollapsed}>
<Icon name="caret-down" size={14} weight="bold" />
</PassageToggleChevron>
{passageCollapsed ? '펼치기' : '접기'}
</PassageToggle>
</ReadSectionHeader>
{!passageCollapsed && <ReadText>{passage.bodyText}</ReadText>}
</ReadSection>
)}
<ReadSection>
@@ -800,13 +829,14 @@ function clampPanelWidth(width: number): number {
return Math.min(PANEL_WIDTH_MAX, Math.max(PANEL_WIDTH_MIN, Math.round(width)));
}
const Page = styled.div`
const Page = styled.div<{ $focusMode: boolean }>`
min-height: 100vh;
background:
radial-gradient(circle at top left, rgba(79, 70, 229, 0.2), transparent 24%),
linear-gradient(180deg, #0d1018 0%, #131724 100%);
background: ${({ $focusMode }) =>
$focusMode
? 'radial-gradient(circle at top left, rgba(79, 70, 229, 0.12), transparent 24%), linear-gradient(180deg, #0d1018 0%, #131724 100%)'
: theme.color.bgDeep};
color: ${theme.color.textBright};
border-radius: 24px;
border-radius: ${({ $focusMode }) => ($focusMode ? '24px' : theme.radius.lg)};
overflow: hidden;
`;
@@ -981,21 +1011,20 @@ const Content = styled.div`
const Workspace = styled.main`
display: grid;
grid-template-columns: minmax(320px, auto) 4px minmax(0, 1fr);
gap: 0;
gap: 24px;
align-items: stretch;
@media (max-width: ${theme.breakpoint.tablet}) {
display: flex;
flex-direction: column;
gap: 16px;
gap: 24px;
}
`;
const Surface = css`
background: ${theme.color.surfaceDeep};
border: 1px solid ${theme.color.borderSoftAlpha};
border-radius: 24px;
box-shadow: ${theme.shadow.cardElevated};
border-radius: ${theme.radius.lg};
`;
const ProblemPanel = styled.section<{ $width?: number; $clickable: boolean }>`
@@ -1010,14 +1039,15 @@ const ProblemPanel = styled.section<{ $width?: number; $clickable: boolean }>`
@media (max-width: ${theme.breakpoint.tablet}) {
width: 100%;
padding: 18px 16px;
padding: 16px;
}
`;
const ResizeHandle = styled.button<{ $dragging: boolean }>`
align-self: stretch;
width: 4px;
margin: 18px 12px;
margin: 0;
justify-self: center;
border-radius: ${theme.radius.pill};
background: ${({ $dragging }) =>
$dragging ? 'rgba(99, 102, 241, 0.55)' : 'rgba(99, 102, 241, 0.18)'};
@@ -1030,21 +1060,32 @@ const ResizeHandle = styled.button<{ $dragging: boolean }>`
`;
const PassageCard = styled.div`
padding-bottom: 18px;
border-bottom: 1px solid ${theme.color.borderSoftAlpha};
display: flex;
flex-direction: column;
gap: 14px;
padding: 16px;
border-radius: ${theme.radius.lg};
border: 1px solid ${theme.color.borderSoftAlpha};
background: rgba(255, 255, 255, 0.02);
`;
const PassageHeader = styled.div`
display: flex;
align-items: center;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 14px;
color: #c7d2fe;
font-size: 13px;
font-weight: 700;
`;
const PassageHeaderRow = styled.div`
display: inline-flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
`;
const PassageHint = styled.span`
display: inline-flex;
align-items: center;
@@ -1053,11 +1094,33 @@ const PassageHint = styled.span`
font-size: 12px;
`;
const PassageToggle = styled.button`
display: inline-flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
color: ${theme.color.textSub};
font-size: 12px;
font-weight: 700;
`;
const PassageToggleChevron = styled.span<{ $collapsed: boolean }>`
display: inline-flex;
transition: transform 0.16s ease;
transform: rotate(${({ $collapsed }) => ($collapsed ? '-90deg' : '0deg')});
`;
const PassageBody = styled.div`
white-space: pre-wrap;
line-height: 2;
font-size: 17px;
color: ${theme.color.textMain};
max-height: 400px;
overflow-y: auto;
@media (max-width: ${theme.breakpoint.tablet}) {
max-height: 60vh;
}
`;
const ProblemTextCard = styled.div`
@@ -1067,6 +1130,19 @@ const ProblemTextCard = styled.div`
min-height: 0;
`;
const ProblemContentScroll = styled.div`
display: flex;
flex-direction: column;
gap: 24px;
max-height: 400px;
overflow-y: auto;
padding-right: 4px;
@media (max-width: ${theme.breakpoint.tablet}) {
max-height: 60vh;
}
`;
const ProblemMetaRow = styled.div`
display: flex;
align-items: center;
@@ -1118,9 +1194,9 @@ const ProblemImage = styled.img`
display: block;
width: 100%;
max-width: 100%;
height: auto;
border-radius: 20px;
border: 1px solid rgba(148, 163, 184, 0.26);
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.26);
background: rgba(15, 23, 42, 0.72);
`;
@@ -1160,7 +1236,7 @@ const AnswerPanel = styled.section`
padding: 24px;
@media (max-width: ${theme.breakpoint.tablet}) {
padding: 18px 16px;
padding: 16px;
}
`;
@@ -1223,7 +1299,7 @@ const AnswerStatus = styled.span<{ $answered: boolean }>`
const ChoiceList = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
gap: 16px;
`;
const ChoiceButton = styled.button<{ $selected: boolean }>`
@@ -1234,7 +1310,7 @@ const ChoiceButton = styled.button<{ $selected: boolean }>`
width: 100%;
min-height: 56px;
padding: 16px;
border-radius: 20px;
border-radius: ${theme.radius.lg};
border: 1px solid
${({ $selected }) =>
$selected ? 'rgba(129, 140, 248, 0.44)' : theme.color.borderSoftAlpha};
@@ -1481,12 +1557,20 @@ const ReadModalClose = styled.button`
const ReadSection = styled.section`
${Surface};
padding: 18px;
padding: 16px;
overflow: auto;
`;
const ReadSectionTitle = styled.h4`
const ReadSectionHeader = styled.div`
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
`;
const ReadSectionTitle = styled.h4`
margin: 0;
font-size: 18px;
line-height: 1.6;
`;
@@ -1501,12 +1585,11 @@ const ReadText = styled.div<{ $muted?: boolean }>`
const ReadImage = styled.img`
display: block;
width: 100%;
max-height: calc(100vh - 260px);
object-fit: contain;
max-width: 100%;
height: auto;
border-radius: 18px;
border: 1px solid rgba(148, 163, 184, 0.26);
background: rgba(15, 23, 42, 0.72);
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.3);
`;
const StateScreen = styled.div<{ $embedded: boolean }>`

View File

@@ -65,6 +65,7 @@ const Layout = styled.div`
display: flex;
min-height: 100vh;
overflow-x: clip;
background: ${theme.color.bgDeep};
`;
const Main = styled.main<{ $withNav: boolean }>`
@@ -75,6 +76,7 @@ const Main = styled.main<{ $withNav: boolean }>`
margin: 0 auto;
width: 100%;
min-width: 0;
background: transparent;
@media (max-width: ${theme.breakpoint.tablet}) {
padding: 16px;

View File

@@ -100,77 +100,79 @@ export default function SideNav({ user, reviewCount }: SideNavProps) {
return (
<Aside>
<TopSection>
<Brand href="/dashboard">
<LogoMark>
<Icon name="infinity" weight="bold" size={18} color="#ffffff" />
</LogoMark>
<BrandText>ReLoop</BrandText>
</Brand>
<SidebarBody>
<TopSection>
<Brand href="/dashboard">
<LogoMark>
<Icon name="infinity" weight="bold" size={18} color="#ffffff" />
</LogoMark>
<BrandText>ReLoop</BrandText>
</Brand>
<NavList>
{NAV_ITEMS.map((item) => {
const isActive = item.match(pathname);
const shouldShowBadge =
item.showBadge && typeof reviewCount === 'number' && reviewCount > 0;
<NavList>
{NAV_ITEMS.map((item) => {
const isActive = item.match(pathname);
const shouldShowBadge =
item.showBadge && typeof reviewCount === 'number' && reviewCount > 0;
return (
<NavItem key={item.href} href={item.href} $active={isActive}>
<NavLabel>
<NavIconWrap $active={isActive}>
<Icon
name={item.icon}
weight="regular"
size={18}
color={isActive ? theme.color.brandIndigo : 'currentColor'}
/>
</NavIconWrap>
<span>{item.label}</span>
</NavLabel>
{shouldShowBadge ? <ReviewBadge>{reviewCount}</ReviewBadge> : null}
</NavItem>
);
})}
</NavList>
</TopSection>
return (
<NavItem key={item.href} href={item.href} $active={isActive}>
<NavLabel>
<NavIconWrap $active={isActive}>
<Icon
name={item.icon}
weight="regular"
size={18}
color={isActive ? theme.color.brandIndigo : 'currentColor'}
/>
</NavIconWrap>
<span>{item.label}</span>
</NavLabel>
{shouldShowBadge ? <ReviewBadge>{reviewCount}</ReviewBadge> : null}
</NavItem>
);
})}
</NavList>
</TopSection>
<UserSection ref={menuRef}>
<UserButton type="button" onClick={() => setMenuOpen((prev) => !prev)}>
<Avatar>
{avatarSrc ? (
<AvatarImage src={avatarSrc} alt={`${user.nickname} avatar`} />
) : (
initial
)}
</Avatar>
<UserInfo>
<UserName>{user.nickname}</UserName>
<PlanLabel>{SUBSCRIPTION_LABELS[user.subscriptionTier]}</PlanLabel>
</UserInfo>
<CaretWrap $open={menuOpen}>
<Icon name="dots-three-vertical" size={14} weight="fill" />
</CaretWrap>
</UserButton>
<UserSection ref={menuRef}>
<UserButton type="button" onClick={() => setMenuOpen((prev) => !prev)}>
<Avatar>
{avatarSrc ? (
<AvatarImage src={avatarSrc} alt={`${user.nickname} avatar`} />
) : (
initial
)}
</Avatar>
<UserInfo>
<UserName>{user.nickname}</UserName>
<PlanLabel>{SUBSCRIPTION_LABELS[user.subscriptionTier]}</PlanLabel>
</UserInfo>
<CaretWrap $open={menuOpen}>
<Icon name="dots-three-vertical" size={14} weight="fill" />
</CaretWrap>
</UserButton>
{menuOpen ? (
<Dropdown>
{MENU_ITEMS.map((item) => (
<DropdownLink
key={item.href}
href={item.href}
onClick={() => setMenuOpen(false)}
>
<Icon name={item.icon} size={14} color={theme.color.textSub} />
{item.label}
</DropdownLink>
))}
<LogoutButton type="button" onClick={handleLogout}>
<Icon name="sign-out" size={14} color="currentColor" />
</LogoutButton>
</Dropdown>
) : null}
</UserSection>
{menuOpen ? (
<Dropdown>
{MENU_ITEMS.map((item) => (
<DropdownLink
key={item.href}
href={item.href}
onClick={() => setMenuOpen(false)}
>
<Icon name={item.icon} size={14} color={theme.color.textSub} />
{item.label}
</DropdownLink>
))}
<LogoutButton type="button" onClick={handleLogout}>
<Icon name="sign-out" size={14} color="currentColor" />
</LogoutButton>
</Dropdown>
) : null}
</UserSection>
</SidebarBody>
</Aside>
);
}
@@ -181,10 +183,8 @@ const Aside = styled.aside`
z-index: 10;
display: flex;
width: 256px;
min-height: 100vh;
height: 100vh;
flex-shrink: 0;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
border-right: 1px solid ${theme.color.borderSoftAlpha};
background: rgba(15, 15, 20, 0.5);
@@ -212,9 +212,19 @@ const Aside = styled.aside`
}
`;
const TopSection = styled.div`
const SidebarBody = styled.div`
position: relative;
z-index: 1;
display: flex;
flex: 1;
min-height: 0;
flex-direction: column;
`;
const TopSection = styled.div`
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 24px;
`;
@@ -318,7 +328,7 @@ const ReviewBadge = styled.span`
const UserSection = styled.div`
position: relative;
z-index: 1;
flex-shrink: 0;
border-top: 1px solid ${theme.color.borderSoftAlpha};
padding: 24px;
`;