refactor(comments): move panel title and close button into the tabs row

Merge the comments side-panel header into the Open/Resolved tab row to
save vertical space: title on the left, tabs centered, close button on
the right.

- comment-list-with-tabs: add optional `title`/`onClose` props; render
  the title and close button as absolutely-positioned overlays around a
  full-width centered Tabs.List. Keeping them outside Tabs.List preserves
  the tablist ARIA contract (only role="tab" children) while the tab
  list's full-width bottom border line is retained.
- aside: pass `title`/`onClose` to CommentListWithTabs for the comments
  tab and drop the shared header for that tab; the toc/details tabs keep
  their existing shared header and scroll area unchanged.
This commit is contained in:
claude_code
2026-06-22 00:37:53 +03:00
parent 4f035b8e19
commit c83343d3a3
2 changed files with 98 additions and 47 deletions

View File

@@ -27,7 +27,9 @@ export default function Aside() {
switch (tab) {
case "comments":
component = <CommentListWithTabs />;
component = (
<CommentListWithTabs title={t("Comments")} onClose={closeAside} />
);
title = "Comments";
break;
case "toc":
@@ -45,25 +47,26 @@ export default function Aside() {
return (
<Box p={0} style={{ height: "100%", display: "flex", flexDirection: "column" }}>
{component && (
<>
<Group justify="space-between" wrap="nowrap" mb="sm">
<Title order={2} size="h6" fw={500}>{t(title)}</Title>
<Tooltip label={t("Close")} withArrow>
<ActionIcon
variant="subtle"
color="gray"
onClick={closeAside}
aria-label={t("Close")}
>
<IconX size={18} />
</ActionIcon>
</Tooltip>
</Group>
{tab === "comments" ? (
component
) : (
{component &&
(tab === "comments" ? (
component
) : (
<>
<Group justify="space-between" wrap="nowrap" mb="sm">
<Title order={2} size="h6" fw={500}>
{t(title)}
</Title>
<Tooltip label={t("Close")} withArrow>
<ActionIcon
variant="subtle"
color="gray"
onClick={closeAside}
aria-label={t("Close")}
>
<IconX size={18} />
</ActionIcon>
</Tooltip>
</Group>
<ScrollArea
style={{ height: "85vh" }}
scrollbarSize={5}
@@ -71,9 +74,8 @@ export default function Aside() {
>
<div style={{ paddingBottom: "200px" }}>{component}</div>
</ScrollArea>
)}
</>
)}
</>
))}
</Box>
);
}

View File

@@ -11,6 +11,8 @@ import {
Badge,
Text,
ScrollArea,
Title,
Tooltip,
} from "@mantine/core";
import CommentListItem from "@/features/comment/components/comment-list-item";
import {
@@ -26,12 +28,17 @@ import { IPagination } from "@/lib/types.ts";
import { extractPageSlugId } from "@/lib";
import { useTranslation } from "react-i18next";
import { useGetSpaceBySlugQuery } from "@/features/space/queries/space-query.ts";
import { IconArrowUp, IconMessageOff } from "@tabler/icons-react";
import { IconArrowUp, IconMessageOff, IconX } from "@tabler/icons-react";
import { useAtom } from "jotai";
import { currentUserAtom } from "@/features/user/atoms/current-user-atom";
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
function CommentListWithTabs() {
interface CommentListWithTabsProps {
title?: string;
onClose?: () => void;
}
function CommentListWithTabs({ title, onClose }: CommentListWithTabsProps) {
const { t } = useTranslation();
const { pageSlug } = useParams();
const { data: page } = usePageQuery({ pageId: extractPageSlugId(pageSlug) });
@@ -194,28 +201,70 @@ function CommentListWithTabs() {
overflow: "hidden",
}}
>
<Tabs.List justify="center">
<Tabs.Tab
value="open"
leftSection={
<Badge size="sm" variant="light" color="blue">
{activeComments.length}
</Badge>
}
>
{t("Open")}
</Tabs.Tab>
<Tabs.Tab
value="resolved"
leftSection={
<Badge size="sm" variant="light" color="green">
{resolvedComments.length}
</Badge>
}
>
{t("Resolved")}
</Tabs.Tab>
</Tabs.List>
{/* Single header row: a full-width centered tab list with the panel
title overlaid on the left and the close button on the right.
Title/close are kept OUTSIDE Tabs.List (which is role="tablist")
so the tablist only owns role="tab" children — the tab list still
spans full width, so its bottom border line stays full-width. */}
<div style={{ position: "relative" }}>
{title && (
<Title
order={2}
size="h6"
fw={500}
style={{
position: "absolute",
left: 0,
top: "50%",
transform: "translateY(-50%)",
// Non-interactive heading must not intercept clicks meant for the row.
pointerEvents: "none",
}}
>
{title}
</Title>
)}
<Tabs.List justify="center">
<Tabs.Tab
value="open"
leftSection={
<Badge size="sm" variant="light" color="blue">
{activeComments.length}
</Badge>
}
>
{t("Open")}
</Tabs.Tab>
<Tabs.Tab
value="resolved"
leftSection={
<Badge size="sm" variant="light" color="green">
{resolvedComments.length}
</Badge>
}
>
{t("Resolved")}
</Tabs.Tab>
</Tabs.List>
{onClose && (
<Tooltip label={t("Close")} withArrow>
<ActionIcon
variant="subtle"
color="gray"
onClick={onClose}
aria-label={t("Close")}
style={{
position: "absolute",
right: 0,
top: "50%",
transform: "translateY(-50%)",
}}
>
<IconX size={18} />
</ActionIcon>
</Tooltip>
)}
</div>
<ScrollArea
style={{ flex: "1 1 auto" }}