update sidebar
All checks were successful
Build and Deploy Next.js + Nginx Docker Image / build-and-deploy (push) Successful in 50s
Build and Deploy Next.js + Nginx Docker Image / deploy (push) Successful in 7s

This commit is contained in:
Mohamadzadeh 2026-08-19 17:59:03 +03:30
parent c3d9a696f6
commit be4a6ed120
5 changed files with 264 additions and 209 deletions

View File

@ -814,3 +814,26 @@ function UsersTable({ data, columns }) {
); );
} }
``` ```
## Sidebar.Exit
`Sidebar` به‌صورت پیش‌فرض گزینه خروج را نمایش نمی‌دهد. برای فعال‌کردن خروج Keycloak داخلی UI Kit، آن را به‌صورت compound component اضافه کنید:
```jsx
<Sidebar
items={items}
currentPath={router.asPath}
linkComponent={NextLink}
>
<Sidebar.Exit />
</Sidebar>
```
عنوان و آیکون خروج قابل تغییر هستند:
```jsx
<Sidebar.Exit title="خروج از سامانه" icon={MyExitIcon} />
```
`Sidebar.Exit` از `logoutKeycloak` داخلی UI Kit استفاده می‌کند و callback خروج از سامانه مقصد نیاز ندارد.

12
dist/layout.d.ts vendored
View File

@ -13,8 +13,7 @@ export type SidebarProps = {
currentPath?: string; currentPath?: string;
linkComponent?: any; linkComponent?: any;
onNavigate?: (item: SidebarItem) => void; onNavigate?: (item: SidebarItem) => void;
logoutTitle?: string; children?: any;
logoutIcon?: any;
primaryMobileCount?: number; primaryMobileCount?: number;
moreTitle?: string; moreTitle?: string;
moreDrawerTitle?: string; moreDrawerTitle?: string;
@ -22,7 +21,14 @@ export type SidebarProps = {
mobileProps?: Record<string, any>; mobileProps?: Record<string, any>;
}; };
export function Sidebar(props: SidebarProps): any; export type SidebarExitProps = {
title?: string;
icon?: any;
};
export const Sidebar: ((props: SidebarProps) => any) & {
Exit: (props: SidebarExitProps) => any;
};
export function isSidebarPathActive( export function isSidebarPathActive(
currentPath: string | undefined, currentPath: string | undefined,
item: SidebarItem, item: SidebarItem,

322
dist/layout.js vendored

File diff suppressed because one or more lines are too long

View File

@ -19,9 +19,9 @@ import {
VStack, VStack,
useDisclosure, useDisclosure,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { forwardRef, useEffect, useRef } from "react"; import { Children, forwardRef, isValidElement, useEffect, useRef } from "react";
import { FiMoreHorizontal } from "react-icons/fi"; import { FiMoreHorizontal } from "react-icons/fi/index.js";
import { IoExit } from "react-icons/io5"; import { IoExit } from "react-icons/io5/index.js";
import { logoutKeycloak } from "../core/keycloak.jsx"; import { logoutKeycloak } from "../core/keycloak.jsx";
import { usePermission } from "../core/permission.jsx"; import { usePermission } from "../core/permission.jsx";
@ -165,13 +165,14 @@ const MobileNavigationButton = ({
); );
}; };
export const Sidebar = ({ const SidebarExit = () => null;
const SidebarRoot = ({
items = [], items = [],
currentPath, currentPath,
linkComponent, linkComponent,
onNavigate, onNavigate,
logoutTitle = "خروج", children,
logoutIcon = IoExit,
primaryMobileCount = 4, primaryMobileCount = 4,
moreTitle = "بیشتر", moreTitle = "بیشتر",
moreDrawerTitle = "سایر بخش‌های سامانه", moreDrawerTitle = "سایر بخش‌های سامانه",
@ -182,6 +183,13 @@ export const Sidebar = ({
const moreButtonRef = useRef(); const moreButtonRef = useRef();
const { cannot, can, isLoading } = usePermission(); const { cannot, can, isLoading } = usePermission();
const exitElement = Children.toArray(children).find(
(child) => isValidElement(child) && child.type === SidebarExit,
);
const hasExit = Boolean(exitElement);
const logoutTitle = exitElement?.props?.title || "خروج";
const logoutIcon = exitElement?.props?.icon || IoExit;
useEffect(() => { useEffect(() => {
if (isLoading) return; if (isLoading) return;
@ -209,7 +217,7 @@ export const Sidebar = ({
const active = (item) => isSidebarPathActive(resolvedCurrentPath, item); const active = (item) => isSidebarPathActive(resolvedCurrentPath, item);
const isSecondaryItemActive = secondaryItems.some(active); const isSecondaryItemActive = secondaryItems.some(active);
const hasMoreContent = true; const hasMoreContent = secondaryItems.length > 0 || hasExit;
const handleNavigate = (item) => { const handleNavigate = (item) => {
onNavigate?.(item); onNavigate?.(item);
@ -252,25 +260,27 @@ export const Sidebar = ({
/> />
))} ))}
<Tooltip {hasExit && (
label={logoutTitle} <Tooltip
placement="left-start" label={logoutTitle}
hasArrow placement="left-start"
rounded="md" hasArrow
py={2} rounded="md"
px={4} py={2}
fontWeight="bold" px={4}
closeDelay={150} fontWeight="bold"
> closeDelay={150}
<IconButton >
variant="ghost" <IconButton
icon={<Icon as={logoutIcon} />} variant="ghost"
fontSize="xl" icon={<Icon as={logoutIcon} />}
size={{ base: "sm", lg: "lg" }} fontSize="xl"
aria-label={logoutTitle} size={{ base: "sm", lg: "lg" }}
onClick={handleLogout} aria-label={logoutTitle}
/> onClick={handleLogout}
</Tooltip> />
</Tooltip>
)}
</VStack> </VStack>
</Stack> </Stack>
@ -414,24 +424,26 @@ export const Sidebar = ({
})} })}
</SimpleGrid> </SimpleGrid>
<Box mt={4} pt={4} borderTopWidth="1px" borderColor="gray.200"> {hasExit && (
<HStack <Box mt={4} pt={4} borderTopWidth="1px" borderColor="gray.200">
as="button" <HStack
type="button" as="button"
w="full" type="button"
justify="center" w="full"
py={3} justify="center"
borderRadius="xl" py={3}
border={0} borderRadius="xl"
color="red.600" border={0}
bg="red.50" color="red.600"
cursor="pointer" bg="red.50"
onClick={handleLogout} cursor="pointer"
> onClick={handleLogout}
<Icon as={logoutIcon} boxSize={5} /> >
<Text fontWeight="bold">خروج از سامانه</Text> <Icon as={logoutIcon} boxSize={5} />
</HStack> <Text fontWeight="bold">{logoutTitle}</Text>
</Box> </HStack>
</Box>
)}
</DrawerBody> </DrawerBody>
</DrawerContent> </DrawerContent>
</Drawer> </Drawer>
@ -439,3 +451,7 @@ export const Sidebar = ({
</> </>
); );
}; };
export const Sidebar = Object.assign(SidebarRoot, {
Exit: SidebarExit,
});

12
src/layout/index.d.ts vendored
View File

@ -13,8 +13,7 @@ export type SidebarProps = {
currentPath?: string; currentPath?: string;
linkComponent?: any; linkComponent?: any;
onNavigate?: (item: SidebarItem) => void; onNavigate?: (item: SidebarItem) => void;
logoutTitle?: string; children?: any;
logoutIcon?: any;
primaryMobileCount?: number; primaryMobileCount?: number;
moreTitle?: string; moreTitle?: string;
moreDrawerTitle?: string; moreDrawerTitle?: string;
@ -22,7 +21,14 @@ export type SidebarProps = {
mobileProps?: Record<string, any>; mobileProps?: Record<string, any>;
}; };
export function Sidebar(props: SidebarProps): any; export type SidebarExitProps = {
title?: string;
icon?: any;
};
export const Sidebar: ((props: SidebarProps) => any) & {
Exit: (props: SidebarExitProps) => any;
};
export function isSidebarPathActive( export function isSidebarPathActive(
currentPath: string | undefined, currentPath: string | undefined,
item: SidebarItem, item: SidebarItem,