From 15d51c8ee9053e22261283cd6aa34b41536dc002 Mon Sep 17 00:00:00 2001 From: yun-zhi-ztl <15071461069@163.com> Date: Thu, 19 Mar 2026 20:26:56 +0800 Subject: [PATCH] fix: collapse review skill detail by default --- .../review-skill-detail-section.test.tsx | 8 +- .../review/review-skill-detail-section.tsx | 190 ++++++++------ .../pages/dashboard/review-detail.test.tsx | 6 +- web/src/pages/dashboard/review-detail.tsx | 232 +++++++++--------- 4 files changed, 239 insertions(+), 197 deletions(-) diff --git a/web/src/features/review/review-skill-detail-section.test.tsx b/web/src/features/review/review-skill-detail-section.test.tsx index d0832a3b..fed3f16f 100644 --- a/web/src/features/review/review-skill-detail-section.test.tsx +++ b/web/src/features/review/review-skill-detail-section.test.tsx @@ -55,14 +55,14 @@ describe('ReviewSkillDetailSection', () => { const html = renderToStaticMarkup() expect(html).toContain('1.2.0') - expect(html).toContain('/api/v1/reviews/1/download') + expect(html).toContain('Expand full overview') }) - it('renders the detail card as an independently scrollable pane on large screens', () => { + it('renders the detail content inside a collapsed disclosure card by default', () => { const html = renderToStaticMarkup() - expect(html).toContain('xl:max-h-[calc(100vh-8rem)]') - expect(html).toContain('xl:overflow-y-auto') + expect(html).toContain('aria-expanded="false"') + expect(html).not.toContain('data-review-skill-detail-panel') }) it('renders inline error state without requiring detail data', () => { diff --git a/web/src/features/review/review-skill-detail-section.tsx b/web/src/features/review/review-skill-detail-section.tsx index ccb8ed5b..55e8665d 100644 --- a/web/src/features/review/review-skill-detail-section.tsx +++ b/web/src/features/review/review-skill-detail-section.tsx @@ -1,9 +1,11 @@ +import { useState } from 'react' +import { ChevronDown, ChevronUp } from 'lucide-react' import { useTranslation } from 'react-i18next' import { buildApiUrl } from '@/api/client' import type { ReviewSkillDetail } from '@/api/types' import { FileTree } from '@/features/skill/file-tree' import { MarkdownRenderer } from '@/features/skill/markdown-renderer' -import { buttonVariants } from '@/shared/ui/button' +import { Button, buttonVariants } from '@/shared/ui/button' import { Card } from '@/shared/ui/card' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/shared/ui/tabs' import { getReviewDownloadHref, getReviewSkillDocumentation, isActiveReviewVersion } from './review-skill-detail' @@ -16,6 +18,7 @@ interface ReviewSkillDetailSectionProps { export function ReviewSkillDetailSection({ detail, isLoading, hasError }: ReviewSkillDetailSectionProps) { const { t } = useTranslation() + const [isExpanded, setIsExpanded] = useState(false) if (isLoading) { return ( @@ -42,8 +45,13 @@ export function ReviewSkillDetailSection({ detail, isLoading, hasError }: Review const documentation = getReviewSkillDocumentation(detail) return ( - -
+ + - - - {t('skillDetail.tabOverview')} - {t('skillDetail.tabFiles')} - {t('skillDetail.tabVersions')} - + {isExpanded ? ( +
+ - - {documentation ? ( -
-

{documentation.path}

-
- -
-
- ) : ( -
- {t('review.noDocumentation')} -
- )} -
+ + + {t('skillDetail.tabOverview')} + {t('skillDetail.tabFiles')} + {t('skillDetail.tabVersions')} + - - {detail.files.length > 0 ? ( - - ) : ( -
- {t('skillDetail.noFiles')} -
- )} -
- - - {detail.versions.length > 0 ? ( -
- {detail.versions.map((version) => ( -
-
-
- {version.version} - - {version.status} - - {isActiveReviewVersion(version, detail) ? ( - - {t('review.activeReviewVersion')} - - ) : null} -
- {version.changelog ? ( -

{version.changelog}

- ) : null} -
-
- {t('skillDetail.fileCount', { count: version.fileCount })} + + {documentation ? ( +
+

{documentation.path}

+
+
- ))} -
- ) : ( -
- {t('skillDetail.noVersions')} -
- )} - - + ) : ( +
+ {t('review.noDocumentation')} +
+ )} + + + + {detail.files.length > 0 ? ( + + ) : ( +
+ {t('skillDetail.noFiles')} +
+ )} +
+ + + {detail.versions.length > 0 ? ( +
+ {detail.versions.map((version) => ( +
+
+
+ {version.version} + + {version.status} + + {isActiveReviewVersion(version, detail) ? ( + + {t('review.activeReviewVersion')} + + ) : null} +
+ {version.changelog ? ( +

{version.changelog}

+ ) : null} +
+
+ {t('skillDetail.fileCount', { count: version.fileCount })} +
+
+ ))} +
+ ) : ( +
+ {t('skillDetail.noVersions')} +
+ )} +
+ + +
+ +
+
+ ) : ( +
+ +
+ )} ) } diff --git a/web/src/pages/dashboard/review-detail.test.tsx b/web/src/pages/dashboard/review-detail.test.tsx index a925db1e..b6cbece2 100644 --- a/web/src/pages/dashboard/review-detail.test.tsx +++ b/web/src/pages/dashboard/review-detail.test.tsx @@ -110,10 +110,10 @@ describe('ReviewDetailPage', () => { navigateMock.mockReset() }) - it('uses a two-column desktop layout that keeps moderation controls in a sticky sidebar', () => { + it('keeps the page in a single-column flow and leaves the skill detail behind a collapsed section', () => { const html = renderToStaticMarkup() - expect(html).toContain('xl:grid xl:grid-cols-[minmax(0,24rem)_minmax(0,1fr)]') - expect(html).toContain('xl:sticky xl:top-6') + expect(html).toContain('max-w-3xl animate-fade-up') + expect(html).toContain('aria-expanded="false"') }) }) diff --git a/web/src/pages/dashboard/review-detail.tsx b/web/src/pages/dashboard/review-detail.tsx index 5a5c1b07..4079d31d 100644 --- a/web/src/pages/dashboard/review-detail.tsx +++ b/web/src/pages/dashboard/review-detail.tsx @@ -62,8 +62,6 @@ export function ReviewDetailPage() { } const handleReject = async () => { - // Rejections require explicit operator feedback so submitters can understand - // what must change before the package is resubmitted. if (!comment.trim()) { toast.error(t('review.rejectReasonRequired')) return @@ -89,7 +87,7 @@ export function ReviewDetailPage() { } return ( -
+

{t('review.detail')}

@@ -100,129 +98,127 @@ export function ReviewDetailPage() {
-
+ +
+
+ +

{review.namespace}/{review.skillSlug}

+
+
+ +

+ + {review.version} + +

+
+
+ +

+ {review.status === 'PENDING' && ( + {t('review.statusPending')} + )} + {review.status === 'APPROVED' && ( + {t('review.statusApproved')} + )} + {review.status === 'REJECTED' && ( + {t('review.statusRejected')} + )} +

+
+
+ +

{review.submittedByName || review.submittedBy}

+
+
+ +

{formatDate(review.submittedAt)}

+
+ {review.reviewedBy && ( + <> +
+ +

{review.reviewedByName || review.reviewedBy}

+
+
+ +

+ {review.reviewedAt ? formatDate(review.reviewedAt) : '—'} +

+
+ + )} +
+ + {review.reviewComment && ( +
+ +

{review.reviewComment}

+
+ )} +
+ + {review.status === 'PENDING' && ( -
-
- -

{review.namespace}/{review.skillSlug}

-
-
- -

- - {review.version} - -

-
-
- -

- {review.status === 'PENDING' && ( - {t('review.statusPending')} - )} - {review.status === 'APPROVED' && ( - {t('review.statusApproved')} - )} - {review.status === 'REJECTED' && ( - {t('review.statusRejected')} - )} -

-
-
- -

{review.submittedByName || review.submittedBy}

-
-
- -

{formatDate(review.submittedAt)}

-
- {review.reviewedBy && ( +

{t('review.actions')}

+ +
+ +