-
-
Notifications
You must be signed in to change notification settings - Fork 160
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: extract SideNav component from VerticalSideBarLayout
- Loading branch information
1 parent
fabd6a1
commit 0283ec2
Showing
5 changed files
with
306 additions
and
257 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,283 @@ | ||
--- | ||
import type { CollectionKey } from 'astro:content'; | ||
import { House, BookOpenText, Workflow, TableProperties } from 'lucide-react'; | ||
import { isCollectionVisibleInCatalog } from '@eventcatalog'; | ||
import { getChannels } from '@utils/channels'; | ||
import { getDomains } from '@utils/collections/domains'; | ||
import { getFlows } from '@utils/collections/flows'; | ||
import { getServices } from '@utils/collections/services'; | ||
import { getCommands } from '@utils/commands'; | ||
import { getEvents } from '@utils/events'; | ||
import { hasLandingPageForDocs } from '@utils/pages'; | ||
import { getQueries } from '@utils/queries'; | ||
import { getTeams } from '@utils/teams'; | ||
import { buildUrl } from '@utils/url-builder'; | ||
import { getUsers } from '@utils/users'; | ||
import CatalogResourcesSideBar from './SideBars/CatalogResourcesSideBar'; | ||
const [events, commands, queries, services, domains, channels, flows, teams, users] = await Promise.all([ | ||
getEvents({ getAllVersions: false }), | ||
getCommands({ getAllVersions: false }), | ||
getQueries({ getAllVersions: false }), | ||
getServices({ getAllVersions: false }), | ||
getDomains({ getAllVersions: false }), | ||
getChannels({ getAllVersions: false }), | ||
getFlows({ getAllVersions: false }), | ||
getTeams(), | ||
getUsers(), | ||
]); | ||
const messages = [...events, ...commands, ...queries]; | ||
// @ts-ignore for large catalogs https://github.com/event-catalog/eventcatalog/issues/552 | ||
const allData = [...domains, ...services, ...messages, ...channels, ...flows, ...teams, ...users]; | ||
const currentPath = Astro.url.pathname; | ||
const catalogHasDefaultLandingPageForDocs = await hasLandingPageForDocs(); | ||
const getDefaultUrl = (route: string, defaultValue: string) => { | ||
const collections = [ | ||
{ data: domains, key: 'domains' }, | ||
{ data: services, key: 'services' }, | ||
{ data: events, key: 'events' }, | ||
{ data: commands, key: 'commands' }, | ||
{ data: queries, key: 'queries' }, | ||
{ data: flows, key: 'flows' }, | ||
]; | ||
for (const { data, key } of collections) { | ||
if (data.length > 0 && isCollectionVisibleInCatalog(key)) { | ||
const item = data[0]; | ||
return buildUrl(`/${route}/${key}/${item.data.id}/${item.data.latestVersion}`); | ||
} | ||
} | ||
return buildUrl(defaultValue); | ||
}; | ||
const navigationItems = [ | ||
{ | ||
id: '/', | ||
label: 'Home', | ||
icon: House, | ||
href: buildUrl('/'), | ||
current: currentPath === '/', | ||
sidebar: false, | ||
}, | ||
{ | ||
id: '/docs', | ||
label: 'Documentation', | ||
icon: BookOpenText, | ||
href: catalogHasDefaultLandingPageForDocs ? buildUrl('/docs') : getDefaultUrl('docs', '/docs'), | ||
current: currentPath.includes('/docs'), | ||
sidebar: true, | ||
}, | ||
{ | ||
id: '/visualiser', | ||
label: 'Visualiser', | ||
icon: Workflow, | ||
href: getDefaultUrl('visualiser', '/visualiser'), | ||
current: currentPath.includes('/visualiser'), | ||
sidebar: true, | ||
}, | ||
{ | ||
id: '/discover', | ||
label: 'Explore', | ||
icon: TableProperties, | ||
href: buildUrl('/discover/events'), | ||
current: currentPath.includes('/discover/'), | ||
sidebar: false, | ||
}, | ||
]; | ||
const allDataAsSideNav = allData.reduce( | ||
(acc, item) => { | ||
const title = item.collection; | ||
const group = acc[title] || []; | ||
const currentPath = Astro.url.pathname; | ||
const route = currentPath.includes('visualiser') ? 'visualiser' : 'docs'; | ||
if ( | ||
currentPath.includes('visualiser') && | ||
(item.collection === 'teams' || item.collection === 'users' || item.collection === 'channels') | ||
) { | ||
return acc; | ||
} | ||
const navigationItem = { | ||
label: item.data.name, | ||
version: item.collection === 'teams' || item.collection === 'users' ? null : item.data.version, | ||
// items: item.collection === 'users' ? [] : item.headings, | ||
visible: isCollectionVisibleInCatalog(item.collection), | ||
// @ts-ignore | ||
href: item.data.version | ||
? // @ts-ignore | ||
buildUrl(`/${route}/${item.collection}/${item.data.id}/${item.data.version}`) | ||
: buildUrl(`/${route}/${item.collection}/${item.data.id}`), | ||
collection: item.collection, | ||
}; | ||
group.push(navigationItem); | ||
return { | ||
...acc, | ||
[title]: group, | ||
}; | ||
}, | ||
{} as Record<CollectionKey, Array<{ label: string; version: string | null; href: string; collection: string }>> | ||
); | ||
const sideNav = { | ||
...(currentPath.includes('visualiser') | ||
? { | ||
'bounded context map': [ | ||
{ label: 'Domain map', href: buildUrl('/visualiser/context-map'), collection: 'bounded-context-map' }, | ||
], | ||
} | ||
: {}), | ||
...allDataAsSideNav, | ||
}; | ||
const currentNavigationItem = navigationItems.find((item) => item.current); | ||
const showSideBarOnLoad = currentNavigationItem?.sidebar && !(currentPath.includes('asyncapi') || currentPath.includes('/spec')); | ||
--- | ||
|
||
<Fragment> | ||
<div | ||
id="eventcatalog-vertical-nav" | ||
class="sticky top-header shrink-0 flex flex-col items-center w-16 h-[calc(100vh-theme(spacing.header))] py-4 bg-white bg-gradient-to-b from-white to-gray-100 border-r border-gray-200 z-20 shadow-md justify-between" | ||
> | ||
<nav class="flex flex-col h-full justify-between"> | ||
<div class="flex flex-col items-center flex-1 space-y-8"> | ||
{ | ||
navigationItems.map((item) => { | ||
return ( | ||
<a | ||
id={item.id} | ||
data-role="nav-item" | ||
href={item.href} | ||
data-active={item.current} | ||
data-sidebar={item.sidebar} | ||
class="p-1.5 inline-block transition-colors duration-200 rounded-lg data-[active=true]:text-white data-[active=true]:bg-gradient-to-b data-[active=true]:from-purple-500 data-[active=true]:to-purple-700 hover:data-[active=false]:bg-gradient-to-r hover:data-[active=false]:from-purple-500 hover:data-[active=false]:to-purple-700 hover:data-[active=false]:text-white data-[active=false]:text-gray-700" | ||
> | ||
<div class="has-tooltip"> | ||
<span class="tooltip rounded shadow-lg p-1 text-xs bg-gradient-to-l from-purple-500 to-purple-700 text-white ml-10"> | ||
{item.label} | ||
</span> | ||
<item.icon className="h-6 w-6 " /> | ||
</div> | ||
</a> | ||
); | ||
}) | ||
} | ||
</div> | ||
</nav> | ||
</div> | ||
|
||
{ | ||
showSideBarOnLoad && ( | ||
<div | ||
id="sidebar" | ||
data-state={showSideBarOnLoad ? 'open' : 'closed'} | ||
class="sidebar-transition sticky top-header h-[calc(100vh-theme(spacing.header))] px-5 py-4 overflow-y-auto shrink-0 bg-white bg-gradient-to-b from-white to-gray-100 border-r border-gray-200 w-60 shadow-lg" | ||
> | ||
<CatalogResourcesSideBar resources={sideNav} currentPath={currentPath} client:load transition:persist /> | ||
</div> | ||
) | ||
} | ||
</Fragment> | ||
|
||
<style> | ||
.sidebar-transition { | ||
transition-property: margin-left; | ||
transition-duration: 300ms; | ||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | ||
} | ||
</style> | ||
|
||
<script> | ||
/** | ||
* On page change set the active navigation item based on the current path. | ||
*/ | ||
function setActiveNavItem() { | ||
const navItems = document.querySelectorAll('[data-role="nav-item"]'); | ||
const currentPath = window.location.pathname; | ||
|
||
navItems.forEach((item) => { | ||
const id = item.getAttribute('id')!; | ||
const isHomeNavItem = id === '/'; | ||
|
||
// prettier-ignore | ||
const isActive = isHomeNavItem | ||
? currentPath === id | ||
: currentPath.includes(id); | ||
|
||
item.setAttribute('data-active', String(isActive)); | ||
}); | ||
} | ||
|
||
function handleNavItemClick(e: Event) { | ||
const item = e.currentTarget as HTMLElement; | ||
const isActive = item.getAttribute('data-active') === 'true'; | ||
const hasSidebar = item.getAttribute('data-sidebar') === 'true'; | ||
|
||
if (isActive && hasSidebar) { | ||
e.preventDefault(); | ||
toggleSidebar(); | ||
} | ||
} | ||
|
||
function toggleSidebar() { | ||
const sidebar = document.getElementById('sidebar'); | ||
const isSidebarOpen = sidebar?.dataset.state === 'open'; | ||
|
||
if (isSidebarOpen) hideSidebar(); | ||
else showSidebar(); | ||
} | ||
|
||
function showSidebar() { | ||
const sidebar = document.getElementById('sidebar'); | ||
if (sidebar) { | ||
sidebar.setAttribute('data-state', 'open'); | ||
sidebar.style.marginLeft = '0'; | ||
} | ||
} | ||
|
||
function hideSidebar() { | ||
const sidebar = document.getElementById('sidebar'); | ||
if (sidebar) { | ||
sidebar.setAttribute('data-state', 'closed'); | ||
sidebar.style.marginLeft = sidebar.getBoundingClientRect().width * -1 + 'px'; | ||
} | ||
} | ||
|
||
/** | ||
* Set the sidebar state based on the current path and the current active navigation item. | ||
*/ | ||
function setSidebarState() { | ||
const currentPath = window.location.href; | ||
const currentNavItem = document.querySelector('[data-role="nav-item"][data-active="true"]'); | ||
const hasSidebarCurrentNavItem = currentNavItem?.getAttribute('data-sidebar') === 'true'; | ||
|
||
if (!hasSidebarCurrentNavItem || currentPath.includes('asyncapi') || currentPath.includes('/spec')) { | ||
hideSidebar(); | ||
} else { | ||
showSidebar(); | ||
} | ||
} | ||
|
||
// Listen to the CustomEvent emitted by `VerticalSideBarLayout.astro` | ||
document.addEventListener('contentLoaded', () => { | ||
setActiveNavItem(); | ||
setSidebarState(); | ||
|
||
const navItems = document.querySelectorAll('[data-role="nav-item"]'); | ||
navItems.forEach((item) => { | ||
// On the first page load the `contentLoaded` event is emitted twice. | ||
// To prevent the event listener from being added twice, we remove it first. | ||
item.removeEventListener('click', handleNavItemClick); | ||
item.addEventListener('click', handleNavItemClick); | ||
}); | ||
}); | ||
</script> |
Oops, something went wrong.