forked from baron/baron-sso
admin/dev 사이드바 프레임 공통화
This commit is contained in:
49
common/shell/AppSidebar.tsx
Normal file
49
common/shell/AppSidebar.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { ComponentType, ReactNode } from "react";
|
||||
import { shellLayoutClasses } from "./layout";
|
||||
|
||||
export type ShellSidebarNavItem = {
|
||||
labelKey: string;
|
||||
labelFallback: string;
|
||||
to: string;
|
||||
icon: ComponentType<{ size?: number | string }>;
|
||||
isExternal?: boolean;
|
||||
end?: boolean;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
type ShellSidebarProps = {
|
||||
brandLabel: string;
|
||||
brandTitle: string;
|
||||
brandIcon: ReactNode;
|
||||
navContent: ReactNode;
|
||||
footerContent: ReactNode;
|
||||
};
|
||||
|
||||
export function AppSidebar({
|
||||
brandLabel,
|
||||
brandTitle,
|
||||
brandIcon,
|
||||
navContent,
|
||||
footerContent,
|
||||
}: ShellSidebarProps) {
|
||||
return (
|
||||
<aside className={shellLayoutClasses.aside}>
|
||||
<div>
|
||||
<div className={shellLayoutClasses.brandSection}>
|
||||
<div className={shellLayoutClasses.brandWrap}>
|
||||
<div className={shellLayoutClasses.brandIcon}>{brandIcon}</div>
|
||||
<div>
|
||||
<p className="text-xs uppercase tracking-[0.18em] text-muted-foreground">
|
||||
{brandLabel}
|
||||
</p>
|
||||
<h1 className="text-lg font-semibold">{brandTitle}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav className={shellLayoutClasses.navWrap}>{navContent}</nav>
|
||||
</div>
|
||||
|
||||
<div>{footerContent}</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
@@ -22,55 +22,9 @@ type ShellProfileSummaryParams = {
|
||||
export const SHELL_THEME_STORAGE_KEY = "admin_theme";
|
||||
export const SHELL_SESSION_EXPIRY_STORAGE_KEY =
|
||||
"baron_session_expiry_enabled";
|
||||
|
||||
export const shellLayoutClasses = {
|
||||
root: "grid min-h-screen bg-background text-foreground md:grid-cols-[240px,1fr]",
|
||||
aside:
|
||||
"flex flex-col justify-between border-b border-border bg-card md:sticky md:top-0 md:h-screen md:border-b-0 md:border-r md:bg-card md:backdrop-blur",
|
||||
asideStatic:
|
||||
"border-b border-border bg-card md:sticky md:top-0 md:h-screen md:border-b-0 md:border-r md:bg-card md:backdrop-blur",
|
||||
brandSection:
|
||||
"flex items-center justify-between px-5 py-4 md:block md:space-y-6 md:py-6",
|
||||
brandWrap: "flex items-center gap-3 md:flex-col md:items-start",
|
||||
brandIcon:
|
||||
"grid h-11 w-11 place-items-center rounded-xl bg-primary/15 text-primary shadow-[0_12px_30px_rgba(54,211,153,0.22)]",
|
||||
scopeBadge:
|
||||
"hidden rounded-full border border-border px-3 py-2 text-xs text-muted-foreground md:inline-flex md:items-center md:gap-2",
|
||||
navWrap: "px-2 pb-4 md:px-3 md:pb-8",
|
||||
navMeta:
|
||||
"flex flex-wrap gap-2 px-3 pb-4 text-[11px] text-muted-foreground md:flex-col md:items-start",
|
||||
navList: "flex flex-col gap-1",
|
||||
navItemBase:
|
||||
"flex items-center gap-3 rounded-xl px-3 py-3 text-sm transition",
|
||||
navItemActive:
|
||||
"bg-primary/10 text-primary shadow-[0_12px_40px_rgba(54,211,153,0.18)]",
|
||||
navItemIdle: "text-muted-foreground hover:bg-muted/10 hover:text-foreground",
|
||||
sidebarFooterNotice:
|
||||
"hidden space-y-2 px-5 pb-6 pt-2 text-xs text-[var(--color-muted)] md:block",
|
||||
logoutButton:
|
||||
"flex w-full items-center gap-3 rounded-xl px-3 py-3 text-sm text-muted-foreground transition hover:bg-destructive/10 hover:text-destructive",
|
||||
header: "sticky top-0 z-20 border-b border-border bg-background/90 backdrop-blur",
|
||||
headerElevated:
|
||||
"sticky top-0 z-50 border-b border-border bg-background/90 backdrop-blur",
|
||||
headerInner: "flex items-center justify-between px-5 py-4 md:px-8",
|
||||
headerTitleWrap: "flex flex-col gap-1",
|
||||
headerActions: "flex items-center gap-2 text-sm",
|
||||
actionButton:
|
||||
"inline-flex items-center gap-2 rounded-full border border-border px-3 py-2 text-muted-foreground transition hover:bg-muted/20",
|
||||
sessionBadge:
|
||||
"hidden rounded-full border px-3 py-2 text-xs font-medium md:inline-flex",
|
||||
profileInitial:
|
||||
"grid h-8 w-8 place-items-center rounded-full bg-primary/15 text-xs font-semibold text-primary",
|
||||
profileMenu:
|
||||
"absolute right-0 z-30 mt-2 w-72 rounded-xl border border-border bg-card p-3 shadow-xl",
|
||||
profileCard:
|
||||
"mt-2 flex flex-col gap-2 rounded-lg border border-border px-3 py-3",
|
||||
settingsCard: "mt-2 rounded-lg border border-border px-3 py-3",
|
||||
content: "relative",
|
||||
contentWide: "relative min-w-0",
|
||||
main: "px-5 py-6 md:px-10 md:py-10",
|
||||
mainMinWidth: "min-w-0 px-5 py-6 md:px-10 md:py-10",
|
||||
} as const;
|
||||
export { AppSidebar } from "./AppSidebar";
|
||||
export type { ShellSidebarNavItem } from "./AppSidebar";
|
||||
export { shellLayoutClasses } from "./layout";
|
||||
|
||||
export function readShellTheme(): ShellTheme {
|
||||
return window.localStorage.getItem(SHELL_THEME_STORAGE_KEY) === "dark"
|
||||
|
||||
48
common/shell/layout.ts
Normal file
48
common/shell/layout.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
export const shellLayoutClasses = {
|
||||
root: "grid min-h-screen bg-background text-foreground md:grid-cols-[240px,1fr]",
|
||||
aside:
|
||||
"flex flex-col justify-between border-b border-border bg-card md:sticky md:top-0 md:h-screen md:border-b-0 md:border-r md:bg-card md:backdrop-blur",
|
||||
asideStatic:
|
||||
"border-b border-border bg-card md:sticky md:top-0 md:h-screen md:border-b-0 md:border-r md:bg-card md:backdrop-blur",
|
||||
brandSection:
|
||||
"flex items-center justify-between px-5 py-4 md:block md:space-y-6 md:py-6",
|
||||
brandWrap: "flex items-center gap-3 md:flex-col md:items-start",
|
||||
brandIcon:
|
||||
"grid h-11 w-11 place-items-center rounded-xl bg-primary/15 text-primary shadow-[0_12px_30px_rgba(54,211,153,0.22)]",
|
||||
scopeBadge:
|
||||
"hidden rounded-full border border-border px-3 py-2 text-xs text-muted-foreground md:inline-flex md:items-center md:gap-2",
|
||||
navWrap: "px-2 pb-4 md:px-3 md:pb-8",
|
||||
navMeta:
|
||||
"flex flex-wrap gap-2 px-3 pb-4 text-[11px] text-muted-foreground md:flex-col md:items-start",
|
||||
navList: "flex flex-col gap-1",
|
||||
navItemBase:
|
||||
"flex items-center gap-3 rounded-xl px-3 py-3 text-sm transition",
|
||||
navItemActive:
|
||||
"bg-primary/10 text-primary shadow-[0_12px_40px_rgba(54,211,153,0.18)]",
|
||||
navItemIdle: "text-muted-foreground hover:bg-muted/10 hover:text-foreground",
|
||||
sidebarFooterNotice:
|
||||
"hidden space-y-2 px-5 pb-6 pt-2 text-xs text-[var(--color-muted)] md:block",
|
||||
logoutButton:
|
||||
"flex w-full items-center gap-3 rounded-xl px-3 py-3 text-sm text-muted-foreground transition hover:bg-destructive/10 hover:text-destructive",
|
||||
header: "sticky top-0 z-20 border-b border-border bg-background/90 backdrop-blur",
|
||||
headerElevated:
|
||||
"sticky top-0 z-50 border-b border-border bg-background/90 backdrop-blur",
|
||||
headerInner: "flex items-center justify-between px-5 py-4 md:px-8",
|
||||
headerTitleWrap: "flex flex-col gap-1",
|
||||
headerActions: "flex items-center gap-2 text-sm",
|
||||
actionButton:
|
||||
"inline-flex items-center gap-2 rounded-full border border-border px-3 py-2 text-muted-foreground transition hover:bg-muted/20",
|
||||
sessionBadge:
|
||||
"hidden rounded-full border px-3 py-2 text-xs font-medium md:inline-flex",
|
||||
profileInitial:
|
||||
"grid h-8 w-8 place-items-center rounded-full bg-primary/15 text-xs font-semibold text-primary",
|
||||
profileMenu:
|
||||
"absolute right-0 z-30 mt-2 w-72 rounded-xl border border-border bg-card p-3 shadow-xl",
|
||||
profileCard:
|
||||
"mt-2 flex flex-col gap-2 rounded-lg border border-border px-3 py-3",
|
||||
settingsCard: "mt-2 rounded-lg border border-border px-3 py-3",
|
||||
content: "relative",
|
||||
contentWide: "relative min-w-0",
|
||||
main: "px-5 py-6 md:px-10 md:py-10",
|
||||
mainMinWidth: "min-w-0 px-5 py-6 md:px-10 md:py-10",
|
||||
} as const;
|
||||
Reference in New Issue
Block a user