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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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" }}
|
||||
|
||||
Reference in New Issue
Block a user