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

This commit is contained in:
Mohamadzadeh 2026-08-19 19:00:54 +03:30
parent 70d99473e1
commit 9bd798188d
3 changed files with 254 additions and 199 deletions

View File

@ -839,3 +839,9 @@ function UsersTable({ data, columns }) {
```
`Sidebar.Exit` از `logoutKeycloak` داخلی UI Kit استفاده می‌کند و callback خروج از سامانه مقصد نیاز ندارد.
### Sidebar active route notes
`Sidebar` compares `currentPath` with each item's `path` and `aliases`, ignoring query strings, hashes, and trailing slashes. Use the actual application route as `path`; use `aliases` only for alternate routes that should keep the same item active.
Active sidebar items are rendered as non-links to avoid redundant same-route navigations. This is especially useful on pages that synchronize URL query params.

358
dist/layout.js vendored

File diff suppressed because one or more lines are too long

View File

@ -25,9 +25,19 @@ import { IoExit } from "react-icons/io5/index.js";
import { logoutKeycloak } from "../core/keycloak.jsx";
import { usePermission } from "../core/permission.jsx";
const normalizeSidebarPath = (value = "") => {
const path = String(value).split(/[?#]/)[0] || "";
if (!path || path === "/") return path || "/";
return path.replace(/\/+$/, "");
};
export const isSidebarPathActive = (currentPath, item) => {
const normalizedPath = currentPath?.split("?")[0] || "";
const candidatePaths = [item?.path, ...(item?.aliases || [])].filter(Boolean);
const normalizedPath = normalizeSidebarPath(currentPath);
const candidatePaths = [item?.path, ...(item?.aliases || [])]
.filter(Boolean)
.map(normalizeSidebarPath);
return candidatePaths.some(
(path) => normalizedPath === path || normalizedPath.startsWith(`${path}/`),
@ -58,19 +68,39 @@ const DesktopNavItem = ({
linkComponent,
onNavigate,
}) => {
const content = (
<IconButton
variant="ghost"
icon={<Icon as={item.icon} />}
fontSize="xl"
size={{ base: "sm", lg: "lg" }}
isDisabled={isDisabled}
isActive={isActive}
aria-label={item.title}
aria-current={isActive ? "page" : undefined}
onClick={isDisabled ? undefined : onNavigate}
/>
);
const commonProps = {
variant: isActive ? "solid" : "ghost",
colorScheme: isActive ? "blue" : undefined,
icon: <Icon as={item.icon} />,
fontSize: "xl",
size: { base: "sm", lg: "lg" },
"aria-label": item.title,
"aria-current": isActive ? "page" : undefined,
};
let content;
if (isDisabled || isActive) {
content = (
<IconButton
{...commonProps}
isDisabled={isDisabled}
cursor={isActive && !isDisabled ? "default" : undefined}
/>
);
} else {
const linkProps = linkComponent
? { as: linkComponent, href: item.path }
: { as: "a", href: item.path };
content = (
<IconButton
{...commonProps}
{...linkProps}
onClick={onNavigate}
/>
);
}
return (
<Tooltip
@ -83,17 +113,7 @@ const DesktopNavItem = ({
fontWeight="bold"
closeDelay={150}
>
{isDisabled ? (
<Box as="span">{content}</Box>
) : (
<SidebarLink
linkComponent={linkComponent}
href={item.path}
_hover={{ textDecoration: "none" }}
>
{content}
</SidebarLink>
)}
<Box as="span">{content}</Box>
</Tooltip>
);
};
@ -134,14 +154,15 @@ const MobileNavigationButton = ({
</VStack>
);
if (isDisabled) {
if (isDisabled || isActive) {
return (
<Box
flex="1"
minW={0}
display="flex"
alignItems="center"
aria-disabled="true"
aria-disabled={isDisabled ? "true" : undefined}
aria-current={isActive ? "page" : undefined}
>
{content}
</Box>
@ -398,8 +419,16 @@ const SidebarRoot = ({
</VStack>
);
if (isDisabled) {
return <Box key={item.path}>{content}</Box>;
if (isDisabled || isActive) {
return (
<Box
key={item.path}
aria-disabled={isDisabled ? "true" : undefined}
aria-current={isActive ? "page" : undefined}
>
{content}
</Box>
);
}
return (