99 * by the Apache License, Version 2.0
1010 */
1111
12- import { Box , Button , ColorModeSwitch , CopyButton , Flex } from '@redpanda-data/ui' ;
12+ import { Button , ColorModeSwitch , CopyButton } from '@redpanda-data/ui' ;
1313import { Link , useLocation , useMatchRoute } from '@tanstack/react-router' ;
1414import { Heading } from 'components/redpanda-ui/components/typography' ;
1515import { cn } from 'components/redpanda-ui/lib/utils' ;
16+ import { ChevronLeft } from 'lucide-react' ;
1617import { Fragment , useMemo } from 'react' ;
1718
1819import { isEmbedded , isFeatureFlagEnabled } from '../../config' ;
@@ -28,6 +29,7 @@ import {
2829 BreadcrumbList ,
2930 BreadcrumbSeparator ,
3031} from '../redpanda-ui/components/breadcrumb' ;
32+ import { Button as RegistryButton } from '../redpanda-ui/components/button' ;
3133import { Separator } from '../redpanda-ui/components/separator' ;
3234import { SidebarTrigger } from '../redpanda-ui/components/sidebar' ;
3335
@@ -38,8 +40,8 @@ type BreadcrumbHeaderRowProps = {
3840
3941function BreadcrumbHeaderRow ( { useNewSidebar, breadcrumbItems } : BreadcrumbHeaderRowProps ) {
4042 return (
41- < Flex alignItems = "center" justifyContent = "space-between ">
42- < Flex alignItems = " center" gap = { 2 } >
43+ < div className = "w-full border-b ">
44+ < div className = "flex items- center gap-2 px-6 py-4" >
4345 { useNewSidebar ? (
4446 < >
4547 < SidebarTrigger />
@@ -50,7 +52,7 @@ function BreadcrumbHeaderRow({ useNewSidebar, breadcrumbItems }: BreadcrumbHeade
5052 < Breadcrumb >
5153 < BreadcrumbList >
5254 { breadcrumbItems . map ( ( item , index ) => (
53- < Fragment key = { item . linkTo } >
55+ < Fragment key = { ` ${ index } - ${ item . linkTo } ` } >
5456 { index > 0 && < BreadcrumbSeparator /> }
5557 < BreadcrumbItem >
5658 < BreadcrumbLink asChild >
@@ -62,8 +64,8 @@ function BreadcrumbHeaderRow({ useNewSidebar, breadcrumbItems }: BreadcrumbHeade
6264 </ BreadcrumbList >
6365 </ Breadcrumb >
6466 ) }
65- </ Flex >
66- </ Flex >
67+ </ div >
68+ </ div >
6769 ) ;
6870}
6971
@@ -74,9 +76,10 @@ function AppPageHeader() {
7476 const useNewSidebar = ! isEmbedded ( ) ;
7577
7678 const pageBreadcrumbs = useUIStateStore ( ( s ) => s . pageBreadcrumbs ) ;
79+ const pageTitle = useUIStateStore ( ( s ) => s . _pageTitle ) ;
80+ const backLink = useUIStateStore ( ( s ) => s . backLink ) ;
7781 const selectedClusterName = useUIStateStore ( ( s ) => s . selectedClusterName ) ;
7882 const shouldHidePageHeader = useUIStateStore ( ( s ) => s . shouldHidePageHeader ) ;
79-
8083 const breadcrumbItems = useMemo ( ( ) => {
8184 const items : BreadcrumbEntry [ ] = [ ...pageBreadcrumbs ] ;
8285
@@ -92,38 +95,41 @@ function AppPageHeader() {
9295 } , [ pageBreadcrumbs , selectedClusterName ] ) ;
9396
9497 const lastBreadcrumb = breadcrumbItems . at ( - 1 ) ;
95- const breadcrumbsExceptLast = breadcrumbItems . slice ( 0 , - 1 ) ;
9698
9799 if ( shouldHideHeader || shouldHidePageHeader ) {
98100 return null ;
99101 }
100102
101103 return (
102- < Box >
103- { /* we need to refactor out #mainLayout > div rule, for now I've added this box as a workaround */ }
104- < BreadcrumbHeaderRow breadcrumbItems = { breadcrumbsExceptLast } useNewSidebar = { useNewSidebar } />
105-
106- < Flex alignItems = "center" justifyContent = "space-between" pb = { 2 } >
107- < Flex alignItems = "center" >
108- { lastBreadcrumb ? (
109- < Heading
110- // as="span"
111- className = { cn ( 'mr-2' , lastBreadcrumb . options ?. canBeTruncated ? 'break-spaces break-all' : 'nowrap' ) }
112- level = { 1 }
113- >
114- { lastBreadcrumb . titleNode ?? lastBreadcrumb . title }
115- </ Heading >
116- ) : null }
117- { lastBreadcrumb ? (
118- < Box >
119- { lastBreadcrumb . options ?. canBeCopied ? (
120- < CopyButton content = { lastBreadcrumb . title } variant = "ghost" />
121- ) : null }
122- </ Box >
123- ) : null }
124- { Boolean ( showRefresh ) && < DataRefreshButton /> }
125- </ Flex >
126- < Flex alignItems = "center" gap = { 2 } >
104+ < div >
105+ < BreadcrumbHeaderRow breadcrumbItems = { breadcrumbItems } useNewSidebar = { useNewSidebar } />
106+
107+ < div className = "flex items-center justify-between px-12 pt-6" >
108+ < div className = "flex flex-col gap-1" >
109+ { backLink && (
110+ < RegistryButton asChild className = "-ml-2 w-fit text-muted-foreground" size = "sm" variant = "ghost" >
111+ < Link to = { backLink . linkTo } >
112+ < ChevronLeft className = "h-4 w-4" />
113+ { backLink . title }
114+ </ Link >
115+ </ RegistryButton >
116+ ) }
117+ < div className = "flex items-center" >
118+ { pageTitle ? (
119+ < Heading
120+ className = { cn ( 'mr-2' , lastBreadcrumb ?. options ?. canBeTruncated ? 'break-spaces break-all' : 'nowrap' ) }
121+ level = { 1 }
122+ >
123+ { pageTitle }
124+ </ Heading >
125+ ) : null }
126+ { lastBreadcrumb ?. options ?. canBeCopied ? (
127+ < CopyButton content = { lastBreadcrumb . title } variant = "ghost" />
128+ ) : null }
129+ { Boolean ( showRefresh ) && < DataRefreshButton /> }
130+ </ div >
131+ </ div >
132+ < div className = "flex items-center gap-2" >
127133 { ! isEmbedded ( ) && api . isRedpanda && (
128134 < Link to = "/debug-bundle" >
129135 < Button
@@ -139,9 +145,9 @@ function AppPageHeader() {
139145 ) }
140146 < UserPreferencesButton />
141147 { IsDev && ! isEmbedded ( ) && < ColorModeSwitch m = { 0 } p = { 0 } variant = "ghost" /> }
142- </ Flex >
143- </ Flex >
144- </ Box >
148+ </ div >
149+ </ div >
150+ </ div >
145151 ) ;
146152}
147153
@@ -165,17 +171,18 @@ function useShouldShowRefresh() {
165171 const getStartedApiMatch = matchRoute ( { to : '/get-started/api' } ) ;
166172
167173 // matches acls
168- const aclCreateMatch = matchRoute ( { to : '/security/acls/create' } ) ;
169- const aclUpdateMatch = matchRoute ( { to : '/security/acls/$aclName/update' } ) ;
170174 const aclDetailMatch = matchRoute ( { to : '/security/acls/$aclName/details' } ) ;
171- const isACLRelated = aclCreateMatch || aclUpdateMatch || aclDetailMatch ;
175+ const isACLRelated = aclDetailMatch ;
172176
173177 // matches roles
174178 const roleCreateMatch = matchRoute ( { to : '/security/roles/create' } ) ;
175179 const roleUpdateMatch = matchRoute ( { to : '/security/roles/$roleName/update' } ) ;
176180 const roleDetailMatch = matchRoute ( { to : '/security/roles/$roleName/details' } ) ;
177181 const isRoleRelated = roleCreateMatch || roleUpdateMatch || roleDetailMatch ;
178182
183+ // matches user detail
184+ const userDetailMatch = matchRoute ( { to : '/security/users/$userName/details' } ) ;
185+
179186 if ( connectClusterMatch && connectClusterMatch . connector === 'create-connector' ) {
180187 return false ;
181188 }
@@ -194,6 +201,9 @@ function useShouldShowRefresh() {
194201 if ( isRoleRelated ) {
195202 return false ;
196203 }
204+ if ( userDetailMatch ) {
205+ return false ;
206+ }
197207 if ( connectWizardPagesMatch ) {
198208 return false ;
199209 }
0 commit comments