@@ -36,3 +40,10 @@ export const LoggedIn: Story = {
onProfileClick: fn(),
},
};
+
+export const CustomLogoSize: Story = {
+ args: {
+ logoWidth: 80,
+ logoHeight: 16,
+ },
+};
diff --git a/src/components/sidebar/MobileHeader.tsx b/src/components/sidebar/MobileHeader.tsx
index 6c04084..1f535fe 100644
--- a/src/components/sidebar/MobileHeader.tsx
+++ b/src/components/sidebar/MobileHeader.tsx
@@ -17,6 +17,10 @@ type MobileHeaderProps = {
onMenuClick?: () => void;
/** 프로필 버튼 클릭 시 호출되는 콜백 */
onProfileClick?: () => void;
+ /** 로고 너비 (기본값: 102) */
+ logoWidth?: number;
+ /** 로고 높이 (기본값: 20) */
+ logoHeight?: number;
};
/**
@@ -29,12 +33,14 @@ export default function MobileHeader({
profileImage,
onMenuClick,
onProfileClick,
+ logoWidth = 102,
+ logoHeight = 20,
}: MobileHeaderProps) {
if (!isLoggedIn) {
return (
);
diff --git a/src/components/sidebar/Sidebar.stories.tsx b/src/components/sidebar/Sidebar.stories.tsx
index 586f5ef..7991ce4 100644
--- a/src/components/sidebar/Sidebar.stories.tsx
+++ b/src/components/sidebar/Sidebar.stories.tsx
@@ -1,5 +1,6 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite';
+import { fn } from 'storybook/test';
import Image from 'next/image';
import Sidebar from './Sidebar';
import SidebarButton from './SidebarButton';
@@ -19,6 +20,10 @@ const meta = {
layout: 'fullscreen',
},
tags: ['autodocs'],
+ argTypes: {
+ defaultCollapsed: { control: 'boolean' },
+ isLoggedIn: { control: 'boolean' },
+ },
} satisfies Meta
;
export default meta;
@@ -26,6 +31,8 @@ type Story = StoryObj;
export const LoggedIn: Story = {
args: {
+ isLoggedIn: true,
+ onProfileClick: fn(),
profileImage: (
),
@@ -54,6 +61,7 @@ export const LoggedIn: Story = {
}
label="자유게시판"
iconOnly={isCollapsed}
+ href="/boards"
/>
>
),
@@ -83,22 +91,22 @@ export const LoggedIn: Story = {
export const LoggedOut: Story = {
args: {
- footer: (isCollapsed: boolean) =>
- isCollapsed ? (
- 로그인
- ) : (
-
- ),
+ isLoggedIn: false,
+ onProfileClick: fn(),
+ },
+};
+
+export const LoggedOutCollapsed: Story = {
+ args: {
+ isLoggedIn: false,
+ defaultCollapsed: true,
+ onProfileClick: fn(),
+ },
+};
+
+export const DefaultCollapsed: Story = {
+ args: {
+ ...LoggedIn.args,
+ defaultCollapsed: true,
},
};
diff --git a/src/components/sidebar/Sidebar.tsx b/src/components/sidebar/Sidebar.tsx
index ecc6f29..baa0da2 100644
--- a/src/components/sidebar/Sidebar.tsx
+++ b/src/components/sidebar/Sidebar.tsx
@@ -11,6 +11,7 @@ import logoLarge from '@/assets/logos/logoLarge.svg';
import logoIcon from '@/assets/logos/logoIcon.svg';
import foldLeftLarge from '@/assets/icons/fold/foldLeftLarge.svg';
import foldRightLarge from '@/assets/icons/fold/foldRightLarge.svg';
+import humanBig from '@/assets/buttons/human/humanBig.svg';
type SidebarProps = {
teamSelect?: ReactNode | ((isCollapsed: boolean) => ReactNode);
@@ -20,6 +21,9 @@ type SidebarProps = {
profileImage?: ReactNode;
profileName?: string;
profileTeam?: string;
+ defaultCollapsed?: boolean;
+ isLoggedIn?: boolean;
+ onProfileClick?: () => void;
};
/**
@@ -37,14 +41,70 @@ export default function Sidebar({
profileImage,
profileName,
profileTeam,
+ defaultCollapsed,
+ isLoggedIn,
+ onProfileClick,
}: SidebarProps) {
- const [isCollapsed, setIsCollapsed] = useState(false);
+ const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed ?? false);
const renderSlot = (slot: SlotNode) => {
if (!slot) return null;
return typeof slot === 'function' ? slot(isCollapsed) : slot;
};
+ const renderFooter = () => {
+ if (footer) {
+ return (
+
+ {renderSlot(footer)}
+
+ );
+ }
+
+ if (!isLoggedIn) {
+ return (
+
+
+ {!isCollapsed && (
+
+
+
+ )}
+
+
+ 로그인
+
+
+ );
+ }
+
+ return (
+
+
{profileImage}
+
+ {!isCollapsed && (
+
+ {profileName}
+ {profileTeam}
+
+ )}
+
+
+ );
+ };
+
return (
- {footer ? (
-
@@ -71,6 +83,11 @@ export const Overview: Story = {
label="경영관리팀"
iconOnly
/>
+ }
+ label="자유게시판"
+ href="/boards"
+ />
),
parameters: {
diff --git a/src/components/sidebar/SidebarButton.tsx b/src/components/sidebar/SidebarButton.tsx
index 9793f07..ea79078 100644
--- a/src/components/sidebar/SidebarButton.tsx
+++ b/src/components/sidebar/SidebarButton.tsx
@@ -1,3 +1,4 @@
+import Link from 'next/link';
import clsx from 'clsx';
import styles from './styles/SidebarButton.module.css';
@@ -6,7 +7,7 @@ import type { SidebarButtonProps } from './types/types';
/**
* 사이드바 내 메뉴 항목 버튼.
* iconOnly가 true이면 아이콘만 표시하고 라벨은 aria-label로 전환됩니다.
- * 사이드바 접힘 상태에서 사용할 수 있습니다.
+ * href가 있으면 Link로 렌더링됩니다.
*/
export default function SidebarButton({
icon,
@@ -14,16 +15,37 @@ export default function SidebarButton({
isActive,
iconOnly,
onClick,
+ href,
}: SidebarButtonProps) {
+ const className = clsx(styles.button, isActive && styles.active, iconOnly && styles.iconOnly);
+ const content = (
+ <>
+