@@ -85,7 +85,7 @@ function UserMenuButton({ name, userRole, expanded = false }: UserMenuButtonProp
8585 ) : (
8686 < button type = "button" className = "cursor-pointer bg-transparent border-none p-0" >
8787 < Avatar size = "sm" className = "bg-primary cursor-pointer hover:opacity-85 transition-opacity" >
88- < AvatarFallback className = "bg-primary text-primary-foreground text-xs font-semibold" >
88+ < AvatarFallback className = "bg-primary text-primary-foreground group-data-[size=sm]/avatar: text-[10px] font-semibold" >
8989 { initials }
9090 </ AvatarFallback >
9191 </ Avatar >
@@ -132,17 +132,8 @@ interface WorkflowStep {
132132 onClick ?: ( ) => void ;
133133}
134134
135- const MILLION = 1_000_000 ;
136- const THOUSAND = 1_000 ;
137-
138- function formatCount ( n : number ) : string {
139- if ( n >= MILLION ) return `${ ( n / MILLION ) . toFixed ( n % MILLION === 0 ? 0 : 1 ) } M` ;
140- if ( n >= THOUSAND ) return `${ ( n / THOUSAND ) . toFixed ( n % THOUSAND === 0 ? 0 : 1 ) } K` ;
141- return n . toLocaleString ( ) ;
142- }
143-
144135const stepItemVariants = cva (
145- "flex items-center gap-2 py-3.5 px-2.5 text-xs font-normal transition-colors duration-150 whitespace-nowrap leading-tight cursor-pointer border-l-[5px] w-full bg-transparent border-r-0 border-t-0 border-b-0" ,
136+ "flex items-center gap-2 py-2 px-2.5 font-normal transition-colors duration-150 whitespace-nowrap leading-tight cursor-pointer border-l-[5px] w-full bg-transparent border-r-0 border-t-0 border-b-0" ,
146137 {
147138 variants : {
148139 active : {
@@ -198,7 +189,7 @@ function WorkflowPanel({
198189 disabled = { step . disabled }
199190 >
200191 { Icon ? (
201- < Icon className = { cn ( "w-5 h-5 " , step . isActive ? "text-primary" : "text-muted-foreground" ) } />
192+ < Icon className = { cn ( "w-4 h-4 " , step . isActive ? "text-primary" : "text-muted-foreground" ) } />
202193 ) : (
203194 < div
204195 className = { cn (
@@ -261,18 +252,11 @@ function WorkflowPanel({
261252 step . isActive ? "text-primary" : "text-muted-foreground" ,
262253 ) }
263254 >
264- < Icon className = "w-5 h-5 " />
255+ < Icon className = "w-4 h-4 " />
265256 </ span >
266257 ) }
267- < span className = "flex flex-col items-start gap-0.5 min-w-0" >
268- < span className = "truncate" > { step . label } </ span >
269- { ( step . inputCount != null || step . outputCount != null ) && (
270- < span className = "text-[10px] text-muted-foreground font-normal tabular-nums" >
271- { step . inputCount != null && < span > { formatCount ( step . inputCount ) } </ span > }
272- { step . inputCount != null && step . outputCount != null && < span className = "mx-0.5" > { "\u2192" } </ span > }
273- { step . outputCount != null && < span > { formatCount ( step . outputCount ) } </ span > }
274- </ span >
275- ) }
258+ < span className = { cn ( "text-title-sm truncate min-w-0" , ! step . isActive && "font-light" ) } >
259+ { step . label }
276260 </ span >
277261 </ button >
278262 ) ;
@@ -282,62 +266,6 @@ function WorkflowPanel({
282266 ) ;
283267}
284268
285- // ── Data count pills ─────────────────────────────────────────────────────────
286-
287- interface DataCount {
288- label : string ;
289- count : number ;
290- variant ?: "default" | "outline" | "primary" ;
291- onClick ?: ( ) => void ;
292- }
293-
294- const countPillVariants = cva (
295- "inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium tabular-nums transition-colors" ,
296- {
297- variants : {
298- variant : {
299- default : "bg-muted text-muted-foreground" ,
300- outline : "bg-transparent border border-border text-foreground" ,
301- primary : "bg-primary/10 border border-primary/30 text-primary" ,
302- } ,
303- clickable : {
304- true : "cursor-pointer hover:bg-primary/15" ,
305- false : "cursor-default" ,
306- } ,
307- } ,
308- defaultVariants : { variant : "default" , clickable : false } ,
309- } ,
310- ) ;
311-
312- function DataCountPills ( { dataCounts } : { dataCounts : DataCount [ ] } ) {
313- if ( dataCounts . length === 0 ) return null ;
314- return (
315- < div className = "flex items-center gap-1.5" >
316- { dataCounts . map ( ( dc , i ) => {
317- const pillClass = cn ( countPillVariants ( { variant : dc . variant ?? "outline" , clickable : ! ! dc . onClick } ) ) ;
318- const pillContent = (
319- < >
320- < span className = "text-muted-foreground text-[10px] uppercase tracking-wide font-medium" > { dc . label } </ span >
321- < span className = "font-semibold text-sm" > { dc . count . toLocaleString ( ) } </ span >
322- </ >
323- ) ;
324- return (
325- < React . Fragment key = { `${ dc . label } -${ i } ` } >
326- { i > 0 && < span className = "text-muted-foreground/50 text-xs" > { "\u2192" } </ span > }
327- { dc . onClick ? (
328- < button type = "button" className = { pillClass } onClick = { dc . onClick } >
329- { pillContent }
330- </ button >
331- ) : (
332- < div className = { pillClass } > { pillContent } </ div >
333- ) }
334- </ React . Fragment >
335- ) ;
336- } ) }
337- </ div >
338- ) ;
339- }
340-
341269// =============================================================================
342270// Meta
343271// =============================================================================
@@ -368,16 +296,14 @@ const htsNavGroups: NavGroup[] = [
368296const htsWorkflowSteps : WorkflowStep [ ] = [
369297 {
370298 id : "data-overview" ,
371- label : "Data Overview " ,
299+ label : "Step 1 Name " ,
372300 icon : LayoutGrid ,
373301 isActive : true ,
374- inputCount : 649568 ,
375- outputCount : 645396 ,
376302 } ,
377- { id : "global-filtering" , label : "Global Filtering " , icon : Filter , inputCount : 645396 , outputCount : 4803 } ,
378- { id : "explore-clusters" , label : "Explore Clusters " , icon : Library , inputCount : 3917 , outputCount : 20 } ,
379- { id : "review-compound" , label : "Review Selection " , icon : Search , inputCount : 20 , outputCount : 15 } ,
380- { id : "export-list" , label : "Export Primary List " , icon : Download , inputCount : 15 } ,
303+ { id : "global-filtering" , label : "Step 2 Name " , icon : Filter } ,
304+ { id : "explore-clusters" , label : "Step 3 Name " , icon : Library } ,
305+ { id : "review-compound" , label : "Step 4 Name " , icon : Search } ,
306+ { id : "export-list" , label : "Step 5 Name " , icon : Download } ,
381307] ;
382308
383309const htsBreadcrumbs = [
@@ -400,17 +326,9 @@ const DefaultShell = ({ initialCollapsed = false }: { initialCollapsed?: boolean
400326 onClick : ( ) => setActiveStepId ( s . id ) ,
401327 } ) ) ;
402328
403- const activeStep = steps . find ( ( s ) => s . isActive ) ;
404329 const activeStepIndex = steps . findIndex ( ( s ) => s . isActive ) ;
405330 const isLastStep = activeStepIndex === steps . length - 1 ;
406-
407- const dataCounts : DataCount [ ] =
408- activeStep ?. inputCount == null || activeStep ?. outputCount == null
409- ? [ ]
410- : [
411- { label : "INPUT" , count : activeStep . inputCount , variant : "outline" } ,
412- { label : "Output" , count : activeStep . outputCount , variant : "primary" } ,
413- ] ;
331+ const isFirstStep = activeStepIndex <= 0 ;
414332
415333 return (
416334 < DataAppShell
@@ -424,7 +342,16 @@ const DefaultShell = ({ initialCollapsed = false }: { initialCollapsed?: boolean
424342 breadcrumbs = { htsBreadcrumbs }
425343 headerActions = {
426344 < >
427- < DataCountPills dataCounts = { dataCounts } />
345+ { ! isFirstStep && (
346+ < Button
347+ variant = "outline"
348+ size = "sm"
349+ onClick = { ( ) => setActiveStepId ( htsWorkflowSteps [ activeStepIndex - 1 ] . id ) }
350+ className = "gap-1"
351+ >
352+ Back
353+ </ Button >
354+ ) }
428355 < Button
429356 size = "sm"
430357 disabled = { isLastStep }
@@ -453,12 +380,11 @@ export const Default: Story = {
453380
454381 await step ( "Shell renders all regions" , async ( ) => {
455382 expect ( canvas . getByText ( "HTS" ) ) . toBeInTheDocument ( ) ;
456- expect ( canvas . getByText ( " Project") ) . toBeInTheDocument ( ) ;
457- expect ( canvas . getByText ( " Explorer") ) . toBeInTheDocument ( ) ;
383+ expect ( canvas . getByRole ( "button" , { name : " Project" } ) ) . toBeInTheDocument ( ) ;
384+ expect ( canvas . getByRole ( "button" , { name : " Explorer" } ) ) . toBeInTheDocument ( ) ;
458385 expect ( canvas . getByText ( "Workflow" ) ) . toBeInTheDocument ( ) ;
459- expect ( canvas . getAllByText ( "Data Overview " ) . length ) . toBeGreaterThan ( 0 ) ;
386+ expect ( canvas . getAllByText ( "Step 1 Name " ) . length ) . toBeGreaterThan ( 0 ) ;
460387 expect ( canvas . getAllByText ( "All Projects" ) . length ) . toBeGreaterThan ( 0 ) ;
461- expect ( canvas . getByText ( "649,568" ) ) . toBeInTheDocument ( ) ;
462388 expect ( canvas . getByText ( "Next" ) ) . toBeInTheDocument ( ) ;
463389 expect ( canvas . getByText ( "Main content area" ) ) . toBeInTheDocument ( ) ;
464390 } ) ;
@@ -486,7 +412,7 @@ export const CollapsedWorkflow: Story = {
486412 const canvas = within ( canvasElement ) ;
487413
488414 await step ( "Collapsed workflow — step labels hidden" , async ( ) => {
489- expect ( canvas . queryByText ( "Global Filtering " ) ) . not . toBeInTheDocument ( ) ;
415+ expect ( canvas . queryByText ( "Step 2 Name " ) ) . not . toBeInTheDocument ( ) ;
490416 expect ( canvas . queryByText ( "Workflow" ) ) . not . toBeInTheDocument ( ) ;
491417 } ) ;
492418
@@ -571,16 +497,6 @@ const InteractiveShell = () => {
571497 const activeStep = steps . find ( ( s ) => s . isActive ) ;
572498 const isProjectPage = activePageId === "project" ;
573499
574- const dataCounts : DataCount [ ] =
575- activeStep ?. inputCount == null
576- ? [ ]
577- : [
578- { label : "INPUT" , count : activeStep . inputCount , variant : "outline" } ,
579- ...( activeStep . outputCount == null
580- ? [ ]
581- : [ { label : "Output" , count : activeStep . outputCount , variant : "primary" as const } ] ) ,
582- ] ;
583-
584500 return (
585501 < DataAppShell
586502 appName = "HTS"
@@ -597,7 +513,6 @@ const InteractiveShell = () => {
597513 headerActions = {
598514 isProjectPage && (
599515 < >
600- < DataCountPills dataCounts = { dataCounts } />
601516 < Button size = "sm" > Next</ Button >
602517 </ >
603518 )
@@ -627,7 +542,7 @@ export const Interactive: Story = {
627542
628543 await step ( "Interactive shell renders" , async ( ) => {
629544 expect ( canvas . getByText ( "HTS" ) ) . toBeInTheDocument ( ) ;
630- expect ( canvas . getAllByText ( "Data Overview " ) . length ) . toBeGreaterThan ( 0 ) ;
545+ expect ( canvas . getAllByText ( "Step 1 Name " ) . length ) . toBeGreaterThan ( 0 ) ;
631546 } ) ;
632547 } ,
633548 parameters : {
@@ -836,11 +751,10 @@ export const WorkflowPanelInteractions: Story = {
836751 play : async ( { canvasElement, step } ) => {
837752 const canvas = within ( canvasElement ) ;
838753
839- await step ( "Expanded panel shows step labels and counts " , async ( ) => {
754+ await step ( "Expanded panel shows step labels" , async ( ) => {
840755 expect ( canvas . getByText ( "Step Alpha" ) ) . toBeInTheDocument ( ) ;
841756 expect ( canvas . getByText ( "Step Beta" ) ) . toBeInTheDocument ( ) ;
842757 expect ( canvas . getByText ( "Disabled" ) ) . toBeInTheDocument ( ) ;
843- expect ( canvas . getByText ( "1K" ) ) . toBeInTheDocument ( ) ;
844758 } ) ;
845759
846760 await step ( "Clicking Step Beta makes it the active step" , async ( ) => {
@@ -916,8 +830,8 @@ export const MultipleNavGroups: Story = {
916830 const canvas = within ( canvasElement ) ;
917831
918832 await step ( "All pages from both groups are visible" , async ( ) => {
919- expect ( canvas . getByText ( " Project") ) . toBeInTheDocument ( ) ;
920- expect ( canvas . getByText ( " Explorer") ) . toBeInTheDocument ( ) ;
833+ expect ( canvas . getByRole ( "button" , { name : " Project" } ) ) . toBeInTheDocument ( ) ;
834+ expect ( canvas . getByRole ( "button" , { name : " Explorer" } ) ) . toBeInTheDocument ( ) ;
921835 expect ( canvas . getAllByText ( "Filters" ) . length ) . toBeGreaterThan ( 0 ) ;
922836 } ) ;
923837
@@ -931,7 +845,7 @@ export const MultipleNavGroups: Story = {
931845 await step ( "Active page icon has primary highlight" , async ( ) => {
932846 // Filters page is isActive — its icon container has bg-primary/10
933847 const rail = canvasElement . querySelector ( "[data-slot='data-app-sidebar-rail']" ) ;
934- const activePage = within ( rail ! ) . getByText ( "Filters" ) . closest ( "button" ) ;
848+ const activePage = within ( rail ! ) . getByRole ( "button" , { name : "Filters" } ) ;
935849 const iconContainer = activePage ?. querySelector ( ".bg-primary\\/10" ) ;
936850 expect ( iconContainer ) . toBeInTheDocument ( ) ;
937851 } ) ;
@@ -1228,16 +1142,16 @@ export const CompactProperty: Story = {
12281142 await step ( "Compact icon rail renders on desktop (hidden on mobile)" , async ( ) => {
12291143 const rail = canvasElement . querySelector ( "[data-slot='data-app-sidebar-rail']" ) ;
12301144 expect ( rail ) . toBeInTheDocument ( ) ;
1231- // Icon rail should have width of 60px
1232- expect ( rail ) . toHaveClass ( "w-[60px] " ) ;
1145+ // Icon rail should have width of 48px
1146+ expect ( rail ) . toHaveClass ( "w-12 " ) ;
12331147 } ) ;
12341148
1235- await step ( "Icon rail displays icons and labels stacked vertically " , async ( ) => {
1149+ await step ( "Icon rail displays icon-only nav buttons labelled via aria-label " , async ( ) => {
12361150 const rail = canvasElement . querySelector ( "[data-slot='data-app-sidebar-rail']" ) ;
1237- // Labels should be present
1238- expect ( within ( rail ! ) . getByText ( " Project") ) . toBeInTheDocument ( ) ;
1239- expect ( within ( rail ! ) . getByText ( " Explorer") ) . toBeInTheDocument ( ) ;
1240- expect ( within ( rail ! ) . getByText ( " Filters") ) . toBeInTheDocument ( ) ;
1151+ // Labels are exposed as accessible names (aria-label), not visible text
1152+ expect ( within ( rail ! ) . getByRole ( "button" , { name : " Project" } ) ) . toBeInTheDocument ( ) ;
1153+ expect ( within ( rail ! ) . getByRole ( "button" , { name : " Explorer" } ) ) . toBeInTheDocument ( ) ;
1154+ expect ( within ( rail ! ) . getByRole ( "button" , { name : " Filters" } ) ) . toBeInTheDocument ( ) ;
12411155 } ) ;
12421156
12431157 await step ( "Group labels are hidden in compact icon rail" , async ( ) => {
@@ -1249,7 +1163,7 @@ export const CompactProperty: Story = {
12491163
12501164 await step ( "Active page has primary background highlight in compact mode" , async ( ) => {
12511165 const rail = canvasElement . querySelector ( "[data-slot='data-app-sidebar-rail']" ) ;
1252- const projectBtn = within ( rail ! ) . getByText ( "Project" ) . closest ( "button" ) ;
1166+ const projectBtn = within ( rail ! ) . getByRole ( "button" , { name : "Project" } ) ;
12531167 const iconDiv = projectBtn ?. querySelector ( ".bg-primary\\/10" ) ;
12541168 expect ( iconDiv ) . toBeInTheDocument ( ) ;
12551169 } ) ;
@@ -1268,7 +1182,7 @@ export const CompactProperty: Story = {
12681182 // Icon rail has md:flex which means it's hidden on mobile
12691183 expect ( rail ) . toHaveClass ( "hidden" , "md:flex" ) ;
12701184 const railStyles = window . getComputedStyle ( rail ! ) ;
1271- expect ( railStyles . width ) . toBe ( "60px " ) ;
1185+ expect ( railStyles . width ) . toBe ( "48px " ) ;
12721186 } ) ;
12731187
12741188 await step ( "User menu is visible at bottom of icon rail" , async ( ) => {
0 commit comments