fix(comments) : align admin reply menus to the right
This commit is contained in:
@@ -118,8 +118,8 @@ function RevisionHistory({ id }: { id: string }) {
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CommentCard({ item, viewer, onChange, revision = 0, inbox = false, moderationDetails = false, replies = true, defaultExpanded = false, focusReplyId, highlighted = false, overview }: {
|
export function CommentCard({ item, viewer, onChange, revision = 0, inbox = false, inboxReply = false, moderationDetails = false, replies = true, defaultExpanded = false, focusReplyId, highlighted = false, overview }: {
|
||||||
item: CommentItem; viewer: CommentViewer | null; onChange: () => void | Promise<unknown>; revision?: number; inbox?: boolean; moderationDetails?: boolean; replies?: boolean; defaultExpanded?: boolean; focusReplyId?: string; highlighted?: boolean; overview?: ReactNode;
|
item: CommentItem; viewer: CommentViewer | null; onChange: () => void | Promise<unknown>; revision?: number; inbox?: boolean; inboxReply?: boolean; moderationDetails?: boolean; replies?: boolean; defaultExpanded?: boolean; focusReplyId?: string; highlighted?: boolean; overview?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const fullWidthMobile = inbox || moderationDetails;
|
const fullWidthMobile = inbox || moderationDetails;
|
||||||
@@ -219,7 +219,7 @@ export function CommentCard({ item, viewer, onChange, revision = 0, inbox = fals
|
|||||||
</DropdownMenu>;
|
</DropdownMenu>;
|
||||||
const Container = inbox ? TableRow : "article";
|
const Container = inbox ? TableRow : "article";
|
||||||
const Content = inbox ? TableCell : "div";
|
const Content = inbox ? TableCell : "div";
|
||||||
return <Container id={`comment-${item.id}`} tabIndex={highlighted ? -1 : undefined} className={cn("scroll-mt-36", inbox ? "max-md:block" : "flex flex-col gap-2 py-2", highlighted && "rounded-lg bg-accent ring-2 ring-ring", expanded && !item.rootId && !inbox && "relative before:absolute before:bottom-8 before:left-5 before:top-12 before:border-l before:border-border", fullWidthMobile && "max-sm:before:hidden")}>
|
return <Container id={`comment-${item.id}`} tabIndex={highlighted ? -1 : undefined} className={cn("scroll-mt-36", inbox ? "max-md:block" : "flex flex-col gap-2 py-2", inboxReply && "relative", highlighted && "rounded-lg bg-accent ring-2 ring-ring", expanded && !item.rootId && !inbox && "relative before:absolute before:bottom-8 before:left-5 before:top-12 before:border-l before:border-border", fullWidthMobile && "max-sm:before:hidden")}>
|
||||||
|
|
||||||
<Content className={cn("min-w-0", inbox && "px-0 py-5 align-top whitespace-normal max-md:block max-md:pb-3 md:pr-6", inbox && expanded && !item.rootId && "relative before:absolute before:bottom-8 before:left-5 before:top-12 before:border-l before:border-border max-md:before:hidden")}>
|
<Content className={cn("min-w-0", inbox && "px-0 py-5 align-top whitespace-normal max-md:block max-md:pb-3 md:pr-6", inbox && expanded && !item.rootId && "relative before:absolute before:bottom-8 before:left-5 before:top-12 before:border-l before:border-border max-md:before:hidden")}>
|
||||||
<div className={cn("flex min-w-0 items-start gap-3", fullWidthMobile && "grid grid-cols-[auto_minmax(0,1fr)] sm:flex")}>
|
<div className={cn("flex min-w-0 items-start gap-3", fullWidthMobile && "grid grid-cols-[auto_minmax(0,1fr)] sm:flex")}>
|
||||||
@@ -232,7 +232,8 @@ export function CommentCard({ item, viewer, onChange, revision = 0, inbox = fals
|
|||||||
{(inbox || moderationDetails) && item.deleted && <Badge variant="secondary">ลบแล้ว</Badge>}
|
{(inbox || moderationDetails) && item.deleted && <Badge variant="secondary">ลบแล้ว</Badge>}
|
||||||
{item.version > 1 && canReadHistory && <Button variant="link" size="xs" onClick={() => setHistory(true)}>แก้ไขแล้ว</Button>}
|
{item.version > 1 && canReadHistory && <Button variant="link" size="xs" onClick={() => setHistory(true)}>แก้ไขแล้ว</Button>}
|
||||||
</div>
|
</div>
|
||||||
{!inbox && optionsMenu}
|
{/* Inbox replies reach across the 280px content column and 24px cell padding. */}
|
||||||
|
{!inbox && optionsMenu && <div className={cn("shrink-0", inboxReply && "md:absolute md:top-2 md:right-[-304px]")}>{optionsMenu}</div>}
|
||||||
</div>
|
</div>
|
||||||
<div className={cn("contents", fullWidthMobile && "max-sm:col-span-2 max-sm:flex max-sm:min-w-0 max-sm:flex-col max-sm:gap-2")}>
|
<div className={cn("contents", fullWidthMobile && "max-sm:col-span-2 max-sm:flex max-sm:min-w-0 max-sm:flex-col max-sm:gap-2")}>
|
||||||
{item.replyToName && <p className="text-xs text-muted-foreground">ตอบกลับ {item.replyToName}</p>}
|
{item.replyToName && <p className="text-xs text-muted-foreground">ตอบกลับ {item.replyToName}</p>}
|
||||||
@@ -254,7 +255,7 @@ export function CommentCard({ item, viewer, onChange, revision = 0, inbox = fals
|
|||||||
{replies && !item.rootId && (item.replyCount > 0 || expanded) && <Accordion value={expanded ? ["replies"] : []} onValueChange={(value) => expandReplies(value.includes("replies"))}>
|
{replies && !item.rootId && (item.replyCount > 0 || expanded) && <Accordion value={expanded ? ["replies"] : []} onValueChange={(value) => expandReplies(value.includes("replies"))}>
|
||||||
<AccordionItem value="replies"><AccordionTrigger ref={replyToggleRef} className="flex-none gap-2 rounded-full px-3" headerClassName={expanded ? "hidden" : "justify-start"}>{`การตอบกลับ ${item.replyCount.toLocaleString("th-TH")} รายการ`}</AccordionTrigger>
|
<AccordionItem value="replies"><AccordionTrigger ref={replyToggleRef} className="flex-none gap-2 rounded-full px-3" headerClassName={expanded ? "hidden" : "justify-start"}>{`การตอบกลับ ${item.replyCount.toLocaleString("th-TH")} รายการ`}</AccordionTrigger>
|
||||||
<AccordionContent keepMounted panelClassName="data-open:overflow-visible" className="[&_p]:mb-0">{hasOpenedReplies && <div className="flex flex-col gap-3">
|
<AccordionContent keepMounted panelClassName="data-open:overflow-visible" className="[&_p]:mb-0">{hasOpenedReplies && <div className="flex flex-col gap-3">
|
||||||
<CommentReplies rootId={item.id} focusReplyId={focusReplyId} viewer={viewer} onChange={onChange} revision={revision} moderationDetails={inbox || moderationDetails} />
|
<CommentReplies rootId={item.id} focusReplyId={focusReplyId} viewer={viewer} onChange={onChange} revision={revision} moderationDetails={inbox || moderationDetails} inbox={inbox} />
|
||||||
<div className={cn("relative self-start before:absolute before:-left-8 before:top-0 before:h-5 before:w-8 before:rounded-bl-2xl before:border-b before:border-l before:border-border", fullWidthMobile && "max-md:before:hidden")}>
|
<div className={cn("relative self-start before:absolute before:-left-8 before:top-0 before:h-5 before:w-8 before:rounded-bl-2xl before:border-b before:border-l before:border-border", fullWidthMobile && "max-md:before:hidden")}>
|
||||||
<Button variant="ghost" size="sm" className="rounded-full" onClick={() => { setExpanded(false); requestAnimationFrame(() => replyToggleRef.current?.focus()); }}><ChevronUpIcon data-icon="inline-start" />Show less</Button>
|
<Button variant="ghost" size="sm" className="rounded-full" onClick={() => { setExpanded(false); requestAnimationFrame(() => replyToggleRef.current?.focus()); }}><ChevronUpIcon data-icon="inline-start" />Show less</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -281,7 +282,7 @@ export function CommentCard({ item, viewer, onChange, revision = 0, inbox = fals
|
|||||||
</Container>;
|
</Container>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommentReplies({ rootId, revision, onChange, focusReplyId, moderationDetails = false }: { focusReplyId?: string; rootId: string; viewer: CommentViewer | null; revision: number; onChange: () => void | Promise<unknown>; moderationDetails?: boolean }) {
|
function CommentReplies({ rootId, revision, onChange, focusReplyId, moderationDetails = false, inbox = false }: { focusReplyId?: string; rootId: string; viewer: CommentViewer | null; revision: number; onChange: () => void | Promise<unknown>; moderationDetails?: boolean; inbox?: boolean }) {
|
||||||
const feed = useCommentFeed(`/api/comments/${rootId}/replies?view=thread`);
|
const feed = useCommentFeed(`/api/comments/${rootId}/replies?view=thread`);
|
||||||
const { refresh, busy, error, page, more } = feed;
|
const { refresh, busy, error, page, more } = feed;
|
||||||
const previousRevision = useRef(revision);
|
const previousRevision = useRef(revision);
|
||||||
@@ -302,14 +303,14 @@ function CommentReplies({ rootId, revision, onChange, focusReplyId, moderationDe
|
|||||||
}, [focusReplyId, busy, error, page, more]);
|
}, [focusReplyId, busy, error, page, more]);
|
||||||
return <div id={`replies-${rootId}`} className="flex flex-col gap-3 pt-2">
|
return <div id={`replies-${rootId}`} className="flex flex-col gap-3 pt-2">
|
||||||
{feed.error && <Alert variant="destructive"><AlertDescription>{feed.error}</AlertDescription></Alert>}
|
{feed.error && <Alert variant="destructive"><AlertDescription>{feed.error}</AlertDescription></Alert>}
|
||||||
<CommentReplyTree focusReplyId={focusReplyId} items={feed.page.items} viewer={feed.page.viewer} moderationDetails={moderationDetails} onChange={async () => { await refresh({ silent: true }); await onChange(); }} />
|
<CommentReplyTree focusReplyId={focusReplyId} items={feed.page.items} viewer={feed.page.viewer} moderationDetails={moderationDetails} inbox={inbox} onChange={async () => { await refresh({ silent: true }); await onChange(); }} />
|
||||||
{feed.busy && <CommentSkeleton count={2} />}
|
{feed.busy && <CommentSkeleton count={2} />}
|
||||||
{feed.page.nextCursor && <Button variant="outline" disabled={feed.busy} onClick={() => void feed.more()}>ดูคำตอบเพิ่มเติม</Button>}
|
{feed.page.nextCursor && <Button variant="outline" disabled={feed.busy} onClick={() => void feed.more()}>ดูคำตอบเพิ่มเติม</Button>}
|
||||||
{feed.error && <Button variant="outline" disabled={feed.busy} onClick={() => void refresh()}>ลองใหม่</Button>}
|
{feed.error && <Button variant="outline" disabled={feed.busy} onClick={() => void refresh()}>ลองใหม่</Button>}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommentReplyTree({ items, viewer, onChange, focusReplyId, moderationDetails = false }: { focusReplyId?: string; items: CommentItem[]; viewer: CommentViewer | null; onChange: () => void | Promise<unknown>; moderationDetails?: boolean }) {
|
function CommentReplyTree({ items, viewer, onChange, focusReplyId, moderationDetails = false, inbox = false }: { focusReplyId?: string; items: CommentItem[]; viewer: CommentViewer | null; onChange: () => void | Promise<unknown>; moderationDetails?: boolean; inbox?: boolean }) {
|
||||||
const loaded = new Set(items.map((item) => item.id));
|
const loaded = new Set(items.map((item) => item.id));
|
||||||
const groups = new Map<string | null, CommentItem[]>();
|
const groups = new Map<string | null, CommentItem[]>();
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
@@ -326,7 +327,7 @@ function CommentReplyTree({ items, viewer, onChange, focusReplyId, moderationDet
|
|||||||
moderationDetails && parent !== null && "before:hidden after:hidden",
|
moderationDetails && parent !== null && "before:hidden after:hidden",
|
||||||
)}>
|
)}>
|
||||||
<div className={cn(!moderationDetails && groups.has(reply.id) && "relative after:absolute after:-bottom-3 after:left-4 after:top-10 after:border-l after:border-border")}>
|
<div className={cn(!moderationDetails && groups.has(reply.id) && "relative after:absolute after:-bottom-3 after:left-4 after:top-10 after:border-l after:border-border")}>
|
||||||
<CommentCard item={reply} highlighted={reply.id === focusReplyId} viewer={viewer} moderationDetails={moderationDetails} replies={false} onChange={onChange} />
|
<CommentCard item={reply} highlighted={reply.id === focusReplyId} viewer={viewer} moderationDetails={moderationDetails} inboxReply={inbox} replies={false} onChange={onChange} />
|
||||||
</div>
|
</div>
|
||||||
{groups.has(reply.id) && <div className={cn("ml-4 flex min-w-0 flex-col gap-3 pl-8", moderationDetails && "ml-0 pl-0")}>
|
{groups.has(reply.id) && <div className={cn("ml-4 flex min-w-0 flex-col gap-3 pl-8", moderationDetails && "ml-0 pl-0")}>
|
||||||
{branch(reply.id)}
|
{branch(reply.id)}
|
||||||
|
|||||||
Reference in New Issue
Block a user