From 1596342d031d1b1f9d85b1f7ab9d613a62d8868c Mon Sep 17 00:00:00 2001 From: kyy Date: Thu, 4 Jun 2026 15:56:33 +0900 Subject: [PATCH] =?UTF-8?q?=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94=20?= =?UTF-8?q?=EC=A0=91=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/layout/AppLayout.test.tsx | 16 ++++ .../src/components/layout/AppLayout.tsx | 52 ++++++++++-- adminfront/src/locales/en.toml | 4 + adminfront/src/locales/ko.toml | 4 + adminfront/src/locales/template.toml | 4 + common/shell/AppSidebar.tsx | 70 ++++++++++++++-- common/shell/index.ts | 21 +++++ common/shell/layout.ts | 17 ++++ .../src/components/layout/AppLayout.test.tsx | 18 +++++ devfront/src/components/layout/AppLayout.tsx | 80 ++++++++++++++----- devfront/src/locales/en.toml | 4 + devfront/src/locales/ko.toml | 4 + devfront/src/locales/template.toml | 4 + locales/en.toml | 4 + locales/ko.toml | 4 + locales/template.toml | 4 + 16 files changed, 277 insertions(+), 33 deletions(-) diff --git a/adminfront/src/components/layout/AppLayout.test.tsx b/adminfront/src/components/layout/AppLayout.test.tsx index 366e3f10..6634a80a 100644 --- a/adminfront/src/components/layout/AppLayout.test.tsx +++ b/adminfront/src/components/layout/AppLayout.test.tsx @@ -127,6 +127,22 @@ describe("admin AppLayout", () => { expect(worksmobileIcon.querySelector('path[fill="white"]')).toBeNull(); }); + it("toggles the sidebar and persists the collapsed state", async () => { + renderLayout(); + + const collapseButton = await screen.findByRole("button", { + name: "사이드바 접기", + }); + fireEvent.click(collapseButton); + + expect(window.localStorage.getItem("baron_shell_sidebar_collapsed")).toBe( + "true", + ); + expect( + screen.getByRole("button", { name: "사이드바 펼치기" }), + ).toBeInTheDocument(); + }); + it("opens profile menu, navigates, toggles theme/session, and logs out", async () => { renderLayout(); diff --git a/adminfront/src/components/layout/AppLayout.tsx b/adminfront/src/components/layout/AppLayout.tsx index 23d8a065..6ba74cb6 100644 --- a/adminfront/src/components/layout/AppLayout.tsx +++ b/adminfront/src/components/layout/AppLayout.tsx @@ -26,11 +26,13 @@ import { buildShellProfileSummary, buildShellSessionStatus, readShellSessionExpiryEnabled, + readShellSidebarCollapsed, readShellTheme, type ShellSidebarNavItem, type ShellTranslator, shellLayoutClasses, writeShellSessionExpiryEnabled, + writeShellSidebarCollapsed, } from "../../../../common/shell"; import { canAccessWorksmobile } from "../../features/tenants/routes/worksmobileAccess"; import { buildAuthenticatedOrgChartUrl } from "../../features/users/orgChartPicker"; @@ -165,6 +167,9 @@ function AppLayout() { const isDevelopmentRuntime = import.meta.env.MODE === "development"; const [theme, setTheme] = useState<"light" | "dark">(readShellTheme); const [isProfileOpen, setIsProfileOpen] = useState(false); + const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(() => + readShellSidebarCollapsed(false), + ); const [isSessionExpiryEnabled, setIsSessionExpiryEnabled] = useState(() => readShellSessionExpiryEnabled(!isDevelopmentRuntime), ); @@ -508,10 +513,18 @@ function AppLayout() { return next; }); }; + const handleSidebarToggle = () => { + setIsSidebarCollapsed((prev) => { + const next = !prev; + writeShellSidebarCollapsed(next); + return next; + }); + }; const sidebarNavContent = (
{navItems.map((item) => { const { labelKey, labelFallback, to, icon: Icon, isExternal } = item; + const label = t(labelKey, labelFallback); if (isExternal) { return ( @@ -522,11 +535,18 @@ function AppLayout() { rel="noopener noreferrer" className={[ shellLayoutClasses.navItemBase, + isSidebarCollapsed + ? shellLayoutClasses.navItemBaseCollapsed + : "", shellLayoutClasses.navItemIdle, ].join(" ")} + title={label} + aria-label={label} > - {t(labelKey, labelFallback)} + + {label} + ); } @@ -539,6 +559,9 @@ function AppLayout() { className={({ isActive }) => [ shellLayoutClasses.navItemBase, + isSidebarCollapsed + ? shellLayoutClasses.navItemBaseCollapsed + : "", item.isActive !== undefined ? item.isActive ? shellLayoutClasses.navItemActive @@ -548,9 +571,11 @@ function AppLayout() { : shellLayoutClasses.navItemIdle, ].join(" ") } + title={label} + aria-label={label} > - {t(labelKey, labelFallback)} + {label} ); })} @@ -561,10 +586,17 @@ function AppLayout() {
); @@ -578,13 +610,23 @@ function AppLayout() { } return ( -
+
} navContent={sidebarNavContent} footerContent={sidebarFooterContent} + collapsed={isSidebarCollapsed} + onToggleCollapsed={handleSidebarToggle} + collapseLabel={t("ui.shell.sidebar.collapse", "사이드바 접기")} + expandLabel={t("ui.shell.sidebar.expand", "사이드바 펼치기")} />
diff --git a/adminfront/src/locales/en.toml b/adminfront/src/locales/en.toml index abe23e4f..bdac84f5 100644 --- a/adminfront/src/locales/en.toml +++ b/adminfront/src/locales/en.toml @@ -1541,6 +1541,10 @@ unknown_name = "Unknown User" logout = "Logout" profile = "My Profile" +[ui.shell.sidebar] +collapse = "Collapse sidebar" +expand = "Expand sidebar" + [ui.shell.role] rp_admin = "Service Administrator (RP Admin)" super_admin = "System Administrator (Super Admin)" diff --git a/adminfront/src/locales/ko.toml b/adminfront/src/locales/ko.toml index 8f9d3694..81cd50bf 100644 --- a/adminfront/src/locales/ko.toml +++ b/adminfront/src/locales/ko.toml @@ -1544,6 +1544,10 @@ unknown_name = "Unknown User" logout = "Logout" profile = "내 정보" +[ui.shell.sidebar] +collapse = "사이드바 접기" +expand = "사이드바 펼치기" + [ui.shell.role] rp_admin = "서비스 관리자 (RP Admin)" super_admin = "시스템 관리자 (Super Admin)" diff --git a/adminfront/src/locales/template.toml b/adminfront/src/locales/template.toml index 83582b7d..444bff20 100644 --- a/adminfront/src/locales/template.toml +++ b/adminfront/src/locales/template.toml @@ -1513,6 +1513,10 @@ unknown_name = "" logout = "" profile = "" +[ui.shell.sidebar] +collapse = "" +expand = "" + [ui.shell.role] rp_admin = "" super_admin = "" diff --git a/common/shell/AppSidebar.tsx b/common/shell/AppSidebar.tsx index d49f56cc..30ef40f5 100644 --- a/common/shell/AppSidebar.tsx +++ b/common/shell/AppSidebar.tsx @@ -1,3 +1,4 @@ +import { Menu, SquareMenu } from "lucide-react"; import type { ComponentType, ReactNode } from "react"; import { shellLayoutClasses } from "./layout"; @@ -14,9 +15,13 @@ export type ShellSidebarNavItem = { type ShellSidebarProps = { brandLabel: string; brandTitle: string; - brandIcon: ReactNode; + brandIcon?: ReactNode; navContent: ReactNode; footerContent: ReactNode; + collapsed?: boolean; + onToggleCollapsed?: () => void; + collapseLabel?: string; + expandLabel?: string; }; export function AppSidebar({ @@ -25,14 +30,57 @@ export function AppSidebar({ brandIcon, navContent, footerContent, + collapsed = false, + onToggleCollapsed, + collapseLabel = "Collapse sidebar", + expandLabel = "Expand sidebar", }: ShellSidebarProps) { return ( -