diff --git a/apps/gitness/src/AppV2.tsx b/apps/gitness/src/AppV2.tsx new file mode 100644 index 000000000..7437de380 --- /dev/null +++ b/apps/gitness/src/AppV2.tsx @@ -0,0 +1,52 @@ +import { I18nextProvider } from 'react-i18next' +import { createBrowserRouter, RouterProvider } from 'react-router-dom' + +import { QueryClientProvider } from '@tanstack/react-query' +import { NuqsAdapter } from 'nuqs/adapters/react-router' + +import { TooltipProvider } from '@harnessio/canary' +import { CodeServiceAPIClient } from '@harnessio/code-service-client' + +import { AppProvider } from './framework/context/AppContext' +import { ExitConfirmProvider } from './framework/context/ExitConfirmContext' +import { ThemeProvider } from './framework/context/ThemeContext' +import { queryClient } from './framework/queryClient' +import i18n from './i18n/i18n' +import { routes } from './routes' + +const BASE_URL_PREFIX = `${window.apiUrl || ''}/api/v1` + +export default function AppV1() { + new CodeServiceAPIClient({ + urlInterceptor: (url: string) => `${BASE_URL_PREFIX}${url}`, + responseInterceptor: (response: Response) => { + switch (response.status) { + case 401: + window.location.href = '/signin' + break + } + return response + } + }) + + // Router Configuration + const router = createBrowserRouter(routes) + + return ( + + + + + + + + + + + + + + + + ) +} diff --git a/apps/gitness/src/components-v2/app-shell.tsx b/apps/gitness/src/components-v2/app-shell.tsx new file mode 100644 index 000000000..2a49f1e7c --- /dev/null +++ b/apps/gitness/src/components-v2/app-shell.tsx @@ -0,0 +1,200 @@ +import { useCallback, useEffect, useMemo, useState } from 'react' +import { Outlet, useLocation, useNavigate } from 'react-router-dom' + +import { + ManageNavigation, + MenuGroupType, + MenuGroupTypes, + MoreSubmenu, + Navbar, + NavbarItemType, + SettingsMenu +} from '@harnessio/ui/components' +import { useLocationChange } from '@harnessio/ui/hooks' +import { SandboxLayout } from '@harnessio/ui/views' + +import { useNav } from '../components/stores/recent-pinned-nav-links.store' +import { getNavbarMenuData } from '../data/navbar-menu-data' +import { getPinnedMenuItemsData } from '../data/pinned-menu-items-data' +import { useAppContext } from '../framework/context/AppContext' +import { useThemeStore } from '../framework/context/ThemeContext' +import { useTranslationStore } from '../i18n/stores/i18n-store' +import BreadcrumbsV1 from './breadcrumbs/breadcrumbs' + +interface NavLinkStorageInterface { + state: { + recent: NavbarItemType[] + pinned: NavbarItemType[] + } +} + +const AppShell = () => { + const { currentUser } = useAppContext() + const navigate = useNavigate() + const location = useLocation() + const { pinnedMenu, recentMenu, setPinned, setRecent, setNavLinks } = useNav() + const { t } = useTranslationStore() + const [showMoreMenu, setShowMoreMenu] = useState(false) + const [showSettingMenu, setShowSettingMenu] = useState(false) + const [showCustomNav, setShowCustomNav] = useState(false) + + const pinnedMenuItemsData = useMemo(() => getPinnedMenuItemsData(t), [t]) + + useLocationChange({ t, onRouteChange: setRecent }) + + useEffect(() => { + const linksFromStorage = localStorage.getItem('nav-items') + let parsedLinksFromStorage: NavLinkStorageInterface | undefined + + if (linksFromStorage) { + parsedLinksFromStorage = JSON.parse(linksFromStorage) + } + + /** + * Logic for setting initial pinned links + * + * setting initial pinned link only if no pinned links are stored in local storage. + * Pinned links cannot be empty as we will have some links permanantly. + */ + if (parsedLinksFromStorage && !parsedLinksFromStorage?.state?.pinned?.length) { + const pinnedItems = pinnedMenu.filter( + item => !pinnedMenuItemsData.some(staticPinned => staticPinned.id === item.id) + ) + setNavLinks({ pinnedMenu: [...pinnedMenuItemsData, ...pinnedItems] }) + } + }, []) + + /** + * Map mock data menu by type to Settings and More + */ + const { moreMenu, settingsMenu } = useMemo(() => { + const navbarMenuData = getNavbarMenuData(t) + return navbarMenuData.reduce<{ + moreMenu: MenuGroupType[] + settingsMenu: MenuGroupType[] + }>( + (acc, item) => { + if (item.type === MenuGroupTypes.SETTINGS) { + acc.settingsMenu.push(item) + } else { + acc.moreMenu.push(item) + } + + return acc + }, + { + moreMenu: [], + settingsMenu: [] + } + ) + }, [t]) + + /** + * Handle logout + */ + const handleLogOut = () => navigate('/logout') + + /** + * Toggle show more menu + */ + const handleMoreMenu = useCallback(() => { + setShowSettingMenu(false) + setShowMoreMenu(prevState => !prevState) + }, []) + + /** + * Toggle system settings menu + */ + const handleSettingsMenu = useCallback(() => { + setShowMoreMenu(false) + setShowSettingMenu(prevState => !prevState) + }, []) + + /** + * Toggle custom navigation modal + */ + const handleCustomNav = useCallback(() => { + setShowCustomNav(prevState => !prevState) + }, []) + + /** + * Close all menu when location changed + */ + useEffect(() => { + setShowMoreMenu(false) + setShowSettingMenu(false) + setShowCustomNav(false) + }, [location]) + + /** + * Handle save recent and pinned items + */ + const handleSave = (nextRecentItems: NavbarItemType[], nextPinnedItems: NavbarItemType[]) => { + setNavLinks({ + pinnedMenu: nextPinnedItems, + recentMenu: nextRecentItems + }) + } + + /** + * Remove recent menu item + */ + const handleRemoveRecentMenuItem = useCallback( + (item: NavbarItemType) => { + setRecent(item, true) + }, + [setRecent] + ) + + /** + * Change pinned menu items + */ + const handleChangePinnedMenuItem = useCallback( + (item: NavbarItemType, pin: boolean) => { + setPinned(item, pin) + }, + [setPinned] + ) + + return ( + + + + +
+
+ +
+ +
+ + + +
+ ) +} + +export default AppShell diff --git a/apps/gitness/src/components-v2/breadcrumbs/breadcrumbs.tsx b/apps/gitness/src/components-v2/breadcrumbs/breadcrumbs.tsx new file mode 100644 index 000000000..7b3f7760e --- /dev/null +++ b/apps/gitness/src/components-v2/breadcrumbs/breadcrumbs.tsx @@ -0,0 +1,47 @@ +import { useMatches } from 'react-router-dom' + +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator, + Topbar +} from '@harnessio/ui/components' + +import { CustomHandle } from '../../routes' + +function Breadcrumbs() { + const matches = useMatches() + + return ( + + + + + {matches.map((match, index) => { + const { breadcrumb } = (match.handle || {}) as CustomHandle + const isFirst = index === 0 + const isLast = index === matches.length - 1 + + if (!breadcrumb) return null + + return ( + + {!isFirst ? / : null} + {isLast ? ( + breadcrumb(match.params) + ) : ( + {breadcrumb(match.params)} + )} + + ) + })} + + + + + ) +} + +export default Breadcrumbs diff --git a/apps/gitness/src/components-v2/breadcrumbs/project-dropdown.tsx b/apps/gitness/src/components-v2/breadcrumbs/project-dropdown.tsx new file mode 100644 index 000000000..7af7214b9 --- /dev/null +++ b/apps/gitness/src/components-v2/breadcrumbs/project-dropdown.tsx @@ -0,0 +1,44 @@ +import { useNavigate, useParams } from 'react-router-dom' + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + Icon, + Text +} from '@harnessio/ui/components' + +import { useAppContext } from '../../framework/context/AppContext' +import { PathParams } from '../../RouteDefinitions' + +function ProjectDropdown(): JSX.Element { + const { spaceId } = useParams() + const navigate = useNavigate() + const { spaces } = useAppContext() + return ( + + + {spaceId ?? 'Select project'} + + + + {spaces.map(({ identifier }) => ( + { + if (identifier) { + navigate(`/${identifier}/repos`) + } + }} + > + {identifier} + + ))} + + + ) +} + +export { ProjectDropdown } diff --git a/apps/gitness/src/data/navbar-menu-data.ts b/apps/gitness/src/data/navbar-menu-data.ts new file mode 100644 index 000000000..c5bc81b24 --- /dev/null +++ b/apps/gitness/src/data/navbar-menu-data.ts @@ -0,0 +1,339 @@ +import { TFunction } from 'i18next' + +import { MenuGroupType, MenuGroupTypes } from '@harnessio/ui/components' + +export const getNavbarMenuData = (t: TFunction): MenuGroupType[] => [ + { + groupId: 0, + title: t('component:navbar.devops'), + type: MenuGroupTypes.GENERAL, + items: [ + { + id: 0, + iconName: 'repositories-gradient', + title: t('component:navbar.repositories'), + description: 'Integrated & familiar git experience.', + to: '/repos' + }, + { + id: 1, + iconName: 'pipelines-gradient', + title: t('component:navbar.pipelines'), + description: 'Up to 4X faster than other solutions.', + to: '/pipelines' + }, + { + id: 2, + iconName: 'execution-gradient', + title: t('component:navbar.executions'), + description: 'Optimize feature rollout velocity.', + to: '/executions' + }, + { + id: 3, + iconName: 'database-gradient', + title: t('component:navbar.databases'), + description: 'Manage all your infrastructure.', + to: '/databases' + }, + { + id: 4, + iconName: 'artifacts-gradient', + title: t('component:navbar.artifacts'), + description: 'Validate service resilience.', + to: '/artifacts' + }, + { + id: 5, + iconName: 'infrastructure-gradient', + title: t('component:navbar.infrastructure'), + description: 'Manage all your infrastructure.', + to: '/infrastructure' + }, + { + id: 6, + iconName: 'flag-gradient', + title: t('component:navbar.feature-flags'), + description: 'Optimize feature rollout velocity.', + to: '/feature-flags' + } + ] + }, + { + groupId: 1, + title: 'Devex', + type: MenuGroupTypes.GENERAL, + items: [ + { + id: 7, + iconName: 'dev-portal-gradient', + title: t('component:navbar.developer-portal'), + description: 'Built for developers, onboard in minutes.', + to: '/developer/portal' + }, + { + id: 8, + iconName: 'dev-envs-gradient', + title: t('component:navbar.developer-environments'), + description: 'Integrated & familiar git experience.', + to: '/developer/environments' + }, + { + id: 9, + iconName: 'dev-insights-gradient', + title: t('component:navbar.developer-insights'), + description: 'Actionable insights on SDLC.', + to: '/developer/insights' + } + ] + }, + { + groupId: 2, + title: t('component:navbar.secops'), + type: MenuGroupTypes.GENERAL, + items: [ + { + id: 10, + iconName: 'security-tests-gradient', + title: t('component:navbar.security-tests'), + description: 'Shift left security testing.', + to: '/security-tests' + }, + { + id: 11, + iconName: 'supply-chain-gradient', + title: t('component:navbar.supply-chain'), + description: 'Artifact integrity and governance.', + to: '/supply-chain' + } + ] + }, + { + groupId: 3, + title: t('component:navbar.finops'), + type: MenuGroupTypes.GENERAL, + items: [ + { + id: 12, + iconName: 'cloud-costs-gradient', + title: t('component:navbar.cloud-costs'), + description: 'Intelligent cost management.', + to: '/cloud-costs' + } + ] + }, + { + groupId: 4, + title: t('component:navbar.reliability'), + type: MenuGroupTypes.GENERAL, + items: [ + { + id: 13, + iconName: 'incidents-gradient', + title: t('component:navbar.incidents'), + description: 'Shift left security testing.', + to: '/incidents' + }, + { + id: 14, + iconName: 'chaos-engineering-gradient', + title: t('component:navbar.chaos-engineering'), + description: 'Validate service resilience.', + to: '/chaos' + } + ] + }, + { + groupId: 5, + title: t('component:navbar.platform'), + type: MenuGroupTypes.GENERAL, + items: [ + { + id: 15, + iconName: 'dashboards-gradient', + title: t('component:navbar.dashboards'), + description: 'Intelligent cost management.', + to: '/dashboards' + } + ] + }, + { + groupId: 6, + title: t('component:navbar.general'), + type: MenuGroupTypes.SETTINGS, + items: [ + { + id: 16, + iconName: 'settings-2', + title: t('component:navbar.settings'), + to: '/admin/default-settings' + }, + { + id: 17, + iconName: 'notification', + title: t('component:navbar.notifications'), + to: '/admin/notifications' + } + ] + }, + { + groupId: 7, + title: t('component:navbar.resources'), + type: MenuGroupTypes.SETTINGS, + items: [ + { + id: 18, + iconName: 'wrench', + title: t('component:navbar.services'), + to: '/admin/services' + }, + { + id: 19, + iconName: 'environment', + title: t('component:navbar.environments'), + to: '/admin/environments' + }, + { + id: 20, + iconName: 'connectors', + title: t('component:navbar.connectors'), + to: '/admin/connectors' + }, + { + id: 21, + iconName: 'hierarchy', + title: t('component:navbar.delegates'), + to: '/admin/delegates' + }, + { + id: 22, + iconName: 'key', + title: t('component:navbar.secrets'), + to: '/admin/secrets' + }, + { + id: 23, + iconName: 'file-icon', + title: t('component:navbar.file-store'), + to: '/admin/filte-store' + }, + { + id: 24, + iconName: 'sidebar-icon', + title: t('component:navbar.templates'), + to: '/admin/templates' + }, + { + id: 25, + iconName: 'variable', + title: t('component:navbar.variables'), + to: '/admin/variables' + }, + { + id: 26, + iconName: 'clock-icon', + title: t('component:navbar.slo-downtime'), + to: '/admin/slo-downtime' + }, + { + id: 27, + iconName: 'search', + title: t('component:navbar.discovery'), + to: '/admin/discovery' + }, + { + id: 28, + iconName: 'eye', + title: t('component:navbar.monitored-services'), + to: '/admin/monitored-services' + }, + { + id: 29, + iconName: 'stack', + title: t('component:navbar.overrides'), + to: '/admin/overrides' + }, + { + id: 30, + iconName: 'bookmark-icon', + title: t('component:navbar.certificates'), + to: '/admin/certificates' + }, + { + id: 31, + iconName: 'webhook', + title: t('component:navbar.webhooks'), + to: '/admin/webhooks' + } + ] + }, + { + groupId: 8, + title: t('component:navbar.access-control'), + type: MenuGroupTypes.SETTINGS, + items: [ + { + id: 32, + iconName: 'user', + title: t('component:navbar.users'), + to: '/admin/users' + }, + { + id: 33, + iconName: 'users', + title: t('component:navbar.user-groups'), + to: '/admin/users-group' + }, + { + id: 34, + iconName: 'account-icon', + title: t('component:navbar.service-accounts'), + to: '/admin/service-accounts' + }, + { + id: 35, + iconName: 'folder-icon', + title: t('component:navbar.resource-groups'), + to: '/admin/resource-groups' + }, + { + id: 36, + iconName: 'briefcase', + title: t('component:navbar.roles'), + to: '/admin/roles' + } + ] + }, + { + groupId: 9, + title: t('component:navbar.security-and-governance'), + type: MenuGroupTypes.SETTINGS, + items: [ + { + id: 37, + iconName: 'shield', + title: t('component:navbar.policies'), + to: '/admin/policies' + }, + { + id: 38, + iconName: 'snow', + title: t('component:navbar.freeze-windows'), + to: '/admin/freeze-windows' + } + ] + }, + { + groupId: 10, + title: t('component:navbar.external-tickets'), + type: MenuGroupTypes.SETTINGS, + items: [ + { + id: 39, + iconName: 'ticket', + title: t('component:navbar.external-tickets'), + to: '/admin/external-tickets' + } + ] + } +] diff --git a/apps/gitness/src/data/pinned-menu-items-data.ts b/apps/gitness/src/data/pinned-menu-items-data.ts new file mode 100644 index 000000000..2c5f9def9 --- /dev/null +++ b/apps/gitness/src/data/pinned-menu-items-data.ts @@ -0,0 +1,22 @@ +import { TFunction } from 'i18next' + +import { NavbarItemType } from '@harnessio/ui/components' + +export const getPinnedMenuItemsData = (t: TFunction): NavbarItemType[] => [ + { + id: 0, + iconName: 'repositories-gradient', + title: t('component:navbar.repositories'), + description: 'Integrated & familiar git experience.', + to: '/repos', + permanentlyPinned: true + }, + { + id: 1, + iconName: 'pipelines-gradient', + title: t('component:navbar.pipelines'), + description: 'Up to 4X faster than other solutions.', + to: '/pipelines', + permanentlyPinned: true + } +] diff --git a/apps/gitness/src/main.tsx b/apps/gitness/src/main.tsx index d9ff25fa2..5bbffc8fb 100644 --- a/apps/gitness/src/main.tsx +++ b/apps/gitness/src/main.tsx @@ -1,5 +1,5 @@ import { createRoot } from 'react-dom/client' -import App from './App' +import App from './AppV2' createRoot(document.getElementById('root')!).render() diff --git a/apps/gitness/src/pages-v2/profile-settings/settings-layout.tsx b/apps/gitness/src/pages-v2/profile-settings/settings-layout.tsx index 73f940632..9f6ada525 100644 --- a/apps/gitness/src/pages-v2/profile-settings/settings-layout.tsx +++ b/apps/gitness/src/pages-v2/profile-settings/settings-layout.tsx @@ -2,7 +2,6 @@ import { Outlet, useLocation } from 'react-router-dom' import { ProfileSettingsTabNav } from '@harnessio/ui/views' -import Breadcrumbs from '../../components/breadcrumbs/breadcrumbs' import { useTranslationStore } from '../../i18n/stores/i18n-store' export const SettingsLayout = () => { @@ -11,8 +10,7 @@ export const SettingsLayout = () => { return ( <> -
- +
diff --git a/apps/gitness/src/pages-v2/project/settings-layout.tsx b/apps/gitness/src/pages-v2/project/settings-layout.tsx index d79f730d1..82aa830a0 100644 --- a/apps/gitness/src/pages-v2/project/settings-layout.tsx +++ b/apps/gitness/src/pages-v2/project/settings-layout.tsx @@ -2,13 +2,10 @@ import { Outlet } from 'react-router-dom' import { ProjectSettingsPage } from '@harnessio/ui/views' -import Breadcrumbs from '../../components/breadcrumbs/breadcrumbs' - export const SettingsLayout = () => { return ( <> -
- +
diff --git a/apps/gitness/src/pages-v2/repo/repo-layout.tsx b/apps/gitness/src/pages-v2/repo/repo-layout.tsx index 3ed5e22f6..328b1671b 100644 --- a/apps/gitness/src/pages-v2/repo/repo-layout.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-layout.tsx @@ -2,14 +2,12 @@ import { Outlet } from 'react-router-dom' import { RepoSubheader } from '@harnessio/ui/components' -import Breadcrumbs from '../../components/breadcrumbs/breadcrumbs' import { useTranslationStore } from '../../i18n/stores/i18n-store' const RepoLayout = () => { return ( <> -
- +
diff --git a/apps/gitness/src/pages-v2/repo/repo-list.tsx b/apps/gitness/src/pages-v2/repo/repo-list.tsx index fdbf61edd..b2d621501 100644 --- a/apps/gitness/src/pages-v2/repo/repo-list.tsx +++ b/apps/gitness/src/pages-v2/repo/repo-list.tsx @@ -5,7 +5,6 @@ import { parseAsInteger, useQueryState } from 'nuqs' import { ListReposOkResponse, useListReposQuery } from '@harnessio/code-service-client' import { SandboxRepoListPage } from '@harnessio/ui/views' -import Breadcrumbs from '../../components/breadcrumbs/breadcrumbs' import { useGetSpaceURLParam } from '../../framework/hooks/useGetSpaceParam' import useSpaceSSE from '../../framework/hooks/useSpaceSSE' import { useTranslationStore } from '../../i18n/stores/i18n-store' @@ -75,7 +74,6 @@ export default function ReposListPage() { return ( <> - ) => string +} + +// Create a new type by intersecting RouteObject with the custom handle +type CustomRouteObject = RouteObject & { + handle?: CustomHandle +} + +export const routes: CustomRouteObject[] = [ + { + path: '/', + element: , + handle: { + breadcrumb: () => + }, + children: [ + { + index: true, + element: + }, + { + path: ':spaceId/repos', + handle: { + breadcrumb: () => Repositories + }, + children: [ + { index: true, element: }, + { + path: 'create', + element: + }, + { + path: 'import', + element: + }, + { + path: ':repoId', + element: , + handle: { + breadcrumb: ({ repoId }: { repoId: string }) => {repoId} + }, + children: [ + { + index: true, + element: + }, + { + path: 'summary', + element: , + handle: { + breadcrumb: () => Summary + } + }, + { + path: 'commits', + element: , + handle: { + breadcrumb: () => Commits + } + }, + { + path: 'branches', + element: , + handle: { + breadcrumb: () => Branches + } + }, + { + path: 'code', + element: ( + + + + ), + handle: { + breadcrumb: () => Files + }, + children: [ + { + index: true, + element: + }, + { + path: '*', + element: + } + ] + }, + { + path: 'pulls', + handle: { + breadcrumb: () => Pull Requests + }, + children: [ + { index: true, element: }, + { + path: 'compare/:diffRefs*?', + element: + }, + { + path: ':pullRequestId', + element: , + handle: { + breadcrumb: ({ pullRequestId }: { pullRequestId: string }) => {pullRequestId} + }, + children: [ + { + index: true, + element: + }, + { + path: 'conversation', + element: ( + + + + ) + }, + { + path: 'commits', + element: , + handle: { + breadcrumb: () => Commits + } + }, + { + path: 'changes', + element: ( + + + + ), + handle: { + breadcrumb: () => Changes + } + } + ] + } + ] + }, + { + path: 'pipelines', + handle: { + breadcrumb: () => Pipelines + }, + children: [ + { index: true, element: }, + { + path: ':pipelineId', + element: , + handle: { + breadcrumb: ({ pipelineId }: { pipelineId: string }) => ( +
+ {pipelineId} + / + Executions +
+ ) + } + } + ] + }, + { + path: 'settings', + element: , + handle: { + breadcrumb: () => Settings + }, + children: [ + { + index: true, + element: + }, + { + path: 'general', + element: , + handle: { + breadcrumb: () => General + } + }, + { + path: 'rules', + element: , + handle: { + breadcrumb: () => Rules + } + }, + { + path: 'rules/create', + element: , + children: [ + { + path: ':identifier', + element: + } + ] + }, + { + path: 'webhooks', + element: , + handle: { + breadcrumb: () => Webhooks + } + }, + { + path: 'webhooks/create', + element: , + children: [ + { + path: ':webhookId', + element: + } + ] + } + ] + } + ] + } + ] + }, + { + path: ':spaceId/settings', + element: , + handle: { + breadcrumb: () => Settings + }, + children: [ + { + index: true, + element: + }, + { + path: 'general', + element: <>General, + handle: { + breadcrumb: () => General + } + }, + { + path: 'members', + element: , + handle: { + breadcrumb: () => Members + } + } + ] + } + ] + }, + { + path: 'create', + element: , + handle: { + breadcrumb: () => Create project + }, + children: [] + }, + { + path: 'repos', + element: ( + +

Repo

+
+ ) + }, + { + path: 'pipelines', + element: ( + +

pipelines

+
+ ) + }, + { + path: 'executions', + element: ( + +

executions

+
+ ) + }, + { + path: 'databases', + element: ( + +

databases

+
+ ) + }, + { + path: 'signin', + element: + }, + { + path: 'signup', + element: + }, + { + path: 'settings', + element: , + handle: { + breadcrumb: () => Settings + }, + children: [ + { + index: true, + element: + }, + { + path: 'general', + element: , + handle: { + breadcrumb: () => General + } + }, + { + path: 'keys', + element: , + handle: { + breadcrumb: () => Keys + } + } + ] + }, + { + path: 'admin/default-settings', + element: + }, + { + path: 'theme', + element: + }, + { + path: 'logout', + element: + }, + { + path: 'chaos', + element: + }, + { + path: 'artifacts', + element: + }, + { + path: 'secrets', + element: + }, + { + path: 'connectors', + element: + }, + { + path: 'continuous-delivery-gitops', + element: + }, + { + path: 'continuous-integration', + element: + }, + { + path: 'feature-flags', + element: + }, + { + path: 'infrastructure-as-code', + element: + }, + { + path: 'service-reliability', + element: + }, + { + path: 'developer/portal', + element: + }, + { + path: 'developer/environments', + element: + }, + { + path: 'developer/insights', + element: + }, + { + path: 'infrastructure', + element: + }, + { + path: 'code-repository', + element: + }, + { + path: 'supply-chain', + element: + }, + { + path: 'security-tests', + element: + }, + { + path: 'cloud-costs', + element: + }, + { + path: 'incidents', + element: + }, + { + path: 'dashboards', + element: + } +] diff --git a/apps/gitness/src/styles.css b/apps/gitness/src/styles.css index 8ed79197f..521b57531 100644 --- a/apps/gitness/src/styles.css +++ b/apps/gitness/src/styles.css @@ -3,3 +3,11 @@ @import '@harnessio/canary/src/styles.css'; */ @import 'highlight.js/styles/atom-one-dark.css'; + +:root { + --breadcrumbs-height: 55px; +} + +.top-breadcrumbs { + top: var(--breadcrumbs-height); +} diff --git a/packages/ui/src/components/breadcrumb.tsx b/packages/ui/src/components/breadcrumb.tsx index 68f88918f..22cada0b1 100644 --- a/packages/ui/src/components/breadcrumb.tsx +++ b/packages/ui/src/components/breadcrumb.tsx @@ -62,9 +62,9 @@ BreadcrumbPage.displayName = 'BreadcrumbPage' type BreadcrumbSeparatorProps = ComponentProps<'li'> const BreadcrumbSeparator = ({ children, className, ...props }: BreadcrumbSeparatorProps) => ( - + ) BreadcrumbSeparator.displayName = 'BreadcrumbSeparator'