1- ' use client' ;
1+ " use client" ;
22
33/**
4- * Packs detail aside shell: overview, measurements, then section units
5- * (states, accounting, governance, proof roots) for the selected row.
4+ * Packs detail shell for drill-in master-detail.
5+ * - layout="main": overview, measurements, states, accounting (primary column)
6+ * - layout="aside": governance + proof roots (route-state twin)
7+ * - layout="full": legacy single-column stack (tests / compact embeds)
68 */
79
810import React from "react" ;
@@ -21,12 +23,98 @@ import { PacksActivityDetailProofRoots } from "@/components/packs/PacksActivityD
2123
2224export type PacksActivityDetailProps = {
2325 detail : PackActivityDetailProjection | null ;
26+ /** Column role in the deposit/read-style detail grid. */
27+ layout ?: "main" | "aside" | "full" ;
2428} ;
2529
26- export function PacksActivityDetail ( { detail } : PacksActivityDetailProps ) {
30+ function DetailHeader ( { detail } : { detail : PackActivityDetailProjection } ) {
31+ return (
32+ < div >
33+ < p className = "flex items-center gap-2 text-[0.68rem] uppercase tracking-[0.22em] text-emerald-200/80" >
34+ < ShieldCheck className = "h-4 w-4" aria-hidden = "true" />
35+ Source-safe detail
36+ </ p >
37+ < h2 className = "mt-3 text-2xl font-semibold tracking-tight text-white" >
38+ { detail . title }
39+ </ h2 >
40+ < p className = "mt-2 text-sm leading-6 text-neutral-300" >
41+ { detail . description }
42+ </ p >
43+ </ div >
44+ ) ;
45+ }
46+
47+ function OverviewAndMeasurements ( {
48+ detail,
49+ } : {
50+ detail : PackActivityDetailProjection ;
51+ } ) {
52+ return (
53+ < >
54+ < PacksDetailSection title = "Overview" >
55+ < dl className = "grid gap-3 text-sm tablet:grid-cols-2" >
56+ < div >
57+ < dt className = "text-neutral-500" > Type</ dt >
58+ < dd className = "mt-1 text-neutral-100" > { formatType ( detail . type ) } </ dd >
59+ </ div >
60+ < div >
61+ < dt className = "text-neutral-500" > State</ dt >
62+ < dd className = "mt-1 text-neutral-100" >
63+ { detail . overview . state || "not recorded" }
64+ </ dd >
65+ </ div >
66+ < div >
67+ < dt className = "text-neutral-500" > Repository</ dt >
68+ < dd className = "mt-1 text-neutral-100" >
69+ { detail . overview . repository || "not recorded" }
70+ </ dd >
71+ </ div >
72+ < div >
73+ < dt className = "text-neutral-500" > Time</ dt >
74+ < dd className = "mt-1 text-neutral-100" >
75+ { formatTimestamp ( detail . timestamp ) }
76+ </ dd >
77+ </ div >
78+ </ dl >
79+ </ PacksDetailSection >
80+
81+ < PacksDetailSection title = "Measurements" >
82+ < div className = "grid gap-2" >
83+ { detail . measurements . length ? (
84+ detail . measurements . map ( ( measurement ) => (
85+ < div
86+ key = { `${ measurement . id } :${ measurement . value } ` }
87+ className = "flex items-center justify-between gap-3 border border-white/10 bg-black/18 px-3 py-2 text-sm"
88+ >
89+ < span className = "text-neutral-400" > { measurement . label } </ span >
90+ < span className = "font-mono text-neutral-100" >
91+ { measurement . value } { measurement . unit || "" }
92+ </ span >
93+ </ div >
94+ ) )
95+ ) : (
96+ < p className = "text-sm text-neutral-500" >
97+ No source-safe measurements recorded.
98+ </ p >
99+ ) }
100+ </ div >
101+ </ PacksDetailSection >
102+ </ >
103+ ) ;
104+ }
105+
106+ export function PacksActivityDetail ( {
107+ detail,
108+ layout = "full" ,
109+ } : PacksActivityDetailProps ) {
27110 if ( ! detail ) {
28111 return (
29- < aside className = "min-w-0 border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.055),rgba(255,255,255,0.025))] p-5" >
112+ < aside
113+ className = "min-w-0 border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.055),rgba(255,255,255,0.025))] p-5"
114+ data-testid = {
115+ layout === "aside" ? "packs-detail-aside" : "packs-detail-main"
116+ }
117+ >
30118 < div className = "py-12" >
31119 < ProductRouteStatePanel
32120 variant = "empty"
@@ -38,83 +126,58 @@ export function PacksActivityDetail({ detail }: PacksActivityDetailProps) {
38126 ) ;
39127 }
40128
41- return (
42- < aside className = "min-w-0 border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.055),rgba(255,255,255,0.025))] p-5" >
43- < div className = "grid gap-5" >
44- < div >
45- < p className = "flex items-center gap-2 text-[0.68rem] uppercase tracking-[0.22em] text-emerald-200/80" >
46- < ShieldCheck className = "h-4 w-4" aria-hidden = "true" />
47- Source-safe detail
48- </ p >
49- < h2 className = "mt-3 text-2xl font-semibold tracking-tight text-white " >
50- { detail . title }
51- </ h2 >
52- < p className = "mt-2 text-sm leading-6 text-neutral-300" >
53- { detail . description }
54- </ p >
129+ if ( layout === "aside" ) {
130+ return (
131+ < aside
132+ className = "grid h-fit items-start gap-5"
133+ aria-label = "Pack route state"
134+ data-testid = "packs-detail-aside"
135+ >
136+ < div className = "min-w-0 border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.055),rgba(255,255,255,0.025))] p-5" >
137+ < div className = "grid gap-5 " >
138+ { detail . governance ? (
139+ < PacksActivityDetailGovernance governance = { detail . governance } / >
140+ ) : null }
141+ < PacksActivityDetailProofRoots detail = { detail } />
142+ </ div >
55143 </ div >
144+ </ aside >
145+ ) ;
146+ }
56147
57- < PacksDetailSection title = "Overview" >
58- < dl className = "grid gap-3 text-sm tablet:grid-cols-2" >
59- < div >
60- < dt className = "text-neutral-500" > Type</ dt >
61- < dd className = "mt-1 text-neutral-100" >
62- { formatType ( detail . type ) }
63- </ dd >
64- </ div >
65- < div >
66- < dt className = "text-neutral-500" > State</ dt >
67- < dd className = "mt-1 text-neutral-100" >
68- { detail . overview . state || "not recorded" }
69- </ dd >
70- </ div >
71- < div >
72- < dt className = "text-neutral-500" > Repository</ dt >
73- < dd className = "mt-1 text-neutral-100" >
74- { detail . overview . repository || "not recorded" }
75- </ dd >
76- </ div >
77- < div >
78- < dt className = "text-neutral-500" > Time</ dt >
79- < dd className = "mt-1 text-neutral-100" >
80- { formatTimestamp ( detail . timestamp ) }
81- </ dd >
82- </ div >
83- </ dl >
84- </ PacksDetailSection >
85-
86- < PacksDetailSection title = "Measurements" >
87- < div className = "grid gap-2" >
88- { detail . measurements . length ? (
89- detail . measurements . map ( ( measurement ) => (
90- < div
91- key = { `${ measurement . id } :${ measurement . value } ` }
92- className = "flex items-center justify-between gap-3 border border-white/10 bg-black/18 px-3 py-2 text-sm"
93- >
94- < span className = "text-neutral-400" > { measurement . label } </ span >
95- < span className = "font-mono text-neutral-100" >
96- { measurement . value } { measurement . unit || "" }
97- </ span >
98- </ div >
99- ) )
100- ) : (
101- < p className = "text-sm text-neutral-500" >
102- No source-safe measurements recorded.
103- </ p >
104- ) }
105- </ div >
106- </ PacksDetailSection >
148+ if ( layout === "main" ) {
149+ return (
150+ < div
151+ className = "min-w-0 border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.055),rgba(255,255,255,0.025))] p-5"
152+ data-testid = "packs-detail-main"
153+ >
154+ < div className = "grid gap-5" >
155+ < DetailHeader detail = { detail } />
156+ < OverviewAndMeasurements detail = { detail } />
157+ < PacksActivityDetailStates detail = { detail } />
158+ { detail . accounting ? (
159+ < PacksActivityDetailAccounting accounting = { detail . accounting } />
160+ ) : null }
161+ </ div >
162+ </ div >
163+ ) ;
164+ }
107165
166+ return (
167+ < aside
168+ className = "min-w-0 border border-white/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.055),rgba(255,255,255,0.025))] p-5"
169+ data-testid = "packs-detail-full"
170+ >
171+ < div className = "grid gap-5" >
172+ < DetailHeader detail = { detail } />
173+ < OverviewAndMeasurements detail = { detail } />
108174 < PacksActivityDetailStates detail = { detail } />
109-
110175 { detail . accounting ? (
111176 < PacksActivityDetailAccounting accounting = { detail . accounting } />
112177 ) : null }
113-
114178 { detail . governance ? (
115179 < PacksActivityDetailGovernance governance = { detail . governance } />
116180 ) : null }
117-
118181 < PacksActivityDetailProofRoots detail = { detail } />
119182 </ div >
120183 </ aside >
0 commit comments