diff --git a/GitRepoViewer/Shared/MainLayout.razor b/GitRepoViewer/Shared/MainLayout.razor
new file mode 100644
index 0000000..f6943e5
--- /dev/null
+++ b/GitRepoViewer/Shared/MainLayout.razor
@@ -0,0 +1,17 @@
+@inherits LayoutComponentBase
+
+<div class="page">
+ <div class="sidebar">
+ <NavMenu/>
+ </div>
+
+ <main>
+ <div class="top-row px-4">
+ <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
+ </div>
+
+ <article class="content px-4">
+ @Body
+ </article>
+ </main>
+</div>
\ No newline at end of file
diff --git a/GitRepoViewer/Shared/MainLayout.razor.css b/GitRepoViewer/Shared/MainLayout.razor.css
new file mode 100644
index 0000000..c865427
--- /dev/null
+++ b/GitRepoViewer/Shared/MainLayout.razor.css
@@ -0,0 +1,81 @@
+.page {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+}
+
+main {
+ flex: 1;
+}
+
+.sidebar {
+ background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
+}
+
+.top-row {
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #d6d5d5;
+ justify-content: flex-end;
+ height: 3.5rem;
+ display: flex;
+ align-items: center;
+}
+
+ .top-row ::deep a, .top-row ::deep .btn-link {
+ white-space: nowrap;
+ margin-left: 1.5rem;
+ text-decoration: none;
+ }
+
+ .top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
+ text-decoration: underline;
+ }
+
+ .top-row ::deep a:first-child {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+@media (max-width: 640.98px) {
+ .top-row:not(.auth) {
+ display: none;
+ }
+
+ .top-row.auth {
+ justify-content: space-between;
+ }
+
+ .top-row ::deep a, .top-row ::deep .btn-link {
+ margin-left: 0;
+ }
+}
+
+@media (min-width: 641px) {
+ .page {
+ flex-direction: row;
+ }
+
+ .sidebar {
+ width: 250px;
+ height: 100vh;
+ position: sticky;
+ top: 0;
+ }
+
+ .top-row {
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ }
+
+ .top-row.auth ::deep a:first-child {
+ flex: 1;
+ text-align: right;
+ width: 0;
+ }
+
+ .top-row, article {
+ padding-left: 2rem !important;
+ padding-right: 1.5rem !important;
+ }
+}
diff --git a/GitRepoViewer/Shared/NavMenu.razor b/GitRepoViewer/Shared/NavMenu.razor
new file mode 100644
index 0000000..3635bcb
--- /dev/null
+++ b/GitRepoViewer/Shared/NavMenu.razor
@@ -0,0 +1,61 @@
+<div class="top-row ps-3 navbar navbar-dark">
+ <div class="container-fluid">
+ <a class="navbar-brand" href="">GitRepoViewer</a>
+ <button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
+ <span class="navbar-toggler-icon"></span>
+ </button>
+ </div>
+</div>
+
+<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
+ <nav class="flex-column">
+ <div class="nav-item px-3">
+ <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
+ <span class="oi oi-home" aria-hidden="true"></span> Home
+ </NavLink>
+ </div>
+ <div class="nav-item px-3">
+ <NavLink class="nav-link" href="counter">
+ <span class="oi oi-plus" aria-hidden="true"></span> Counter
+ </NavLink>
+ </div>
+ <div class="nav-item px-3">
+ <NavLink class="nav-link" href="fetchdata">
+ <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
+ </NavLink>
+ </div>
+ @if (repoUrl != null)
+ {
+ <div class="nav-item px-3">
+ <NavLink class="nav-link" href="@("/GitLog?repo="+repoUrl)">
+ <span class="oi oi-list-rich" aria-hidden="true"></span> Commit log
+ </NavLink>
+ </div>
+ }
+ </nav>
+</div>
+
+@code {
+ private bool collapseNavMenu = true;
+
+ private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
+
+
+ private string? repoUrl { get; set; } = null;
+
+ private void ToggleNavMenu()
+ {
+ collapseNavMenu = !collapseNavMenu;
+ }
+
+
+ protected override async Task OnInitializedAsync()
+ {
+ var query = new Uri(NavigationManager.Uri).Query;
+ var queryDictionary = System.Web.HttpUtility.ParseQueryString(query);
+ repoUrl = queryDictionary["repo"];
+
+ await base.OnInitializedAsync();
+ }
+
+}
\ No newline at end of file
diff --git a/GitRepoViewer/Shared/NavMenu.razor.css b/GitRepoViewer/Shared/NavMenu.razor.css
new file mode 100644
index 0000000..604b7a1
--- /dev/null
+++ b/GitRepoViewer/Shared/NavMenu.razor.css
@@ -0,0 +1,68 @@
+.navbar-toggler {
+ background-color: rgba(255, 255, 255, 0.1);
+}
+
+.top-row {
+ height: 3.5rem;
+ background-color: rgba(0,0,0,0.4);
+}
+
+.navbar-brand {
+ font-size: 1.1rem;
+}
+
+.oi {
+ width: 2rem;
+ font-size: 1.1rem;
+ vertical-align: text-top;
+ top: -2px;
+}
+
+.nav-item {
+ font-size: 0.9rem;
+ padding-bottom: 0.5rem;
+}
+
+ .nav-item:first-of-type {
+ padding-top: 1rem;
+ }
+
+ .nav-item:last-of-type {
+ padding-bottom: 1rem;
+ }
+
+ .nav-item ::deep a {
+ color: #d7d7d7;
+ border-radius: 4px;
+ height: 3rem;
+ display: flex;
+ align-items: center;
+ line-height: 3rem;
+ }
+
+.nav-item ::deep a.active {
+ background-color: rgba(255,255,255,0.25);
+ color: white;
+}
+
+.nav-item ::deep a:hover {
+ background-color: rgba(255,255,255,0.1);
+ color: white;
+}
+
+@media (min-width: 641px) {
+ .navbar-toggler {
+ display: none;
+ }
+
+ .collapse {
+ /* Never collapse the sidebar for wide screens */
+ display: block;
+ }
+
+ .nav-scrollable {
+ /* Allow sidebar to scroll for tall menus */
+ height: calc(100vh - 3.5rem);
+ overflow-y: auto;
+ }
+}
diff --git a/GitRepoViewer/Shared/SurveyPrompt.razor b/GitRepoViewer/Shared/SurveyPrompt.razor
new file mode 100644
index 0000000..657b190
--- /dev/null
+++ b/GitRepoViewer/Shared/SurveyPrompt.razor
@@ -0,0 +1,17 @@
+<div class="alert alert-secondary mt-4">
+ <span class="oi oi-pencil me-2" aria-hidden="true"></span>
+ <strong>@Title</strong>
+
+ <span class="text-nowrap">
+ Please take our
+ <a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186157">brief survey</a>
+ </span>
+ and tell us what you think.
+</div>
+
+@code {
+ // Demonstrates how a parent component can supply parameters
+ [Parameter]
+ public string? Title { get; set; }
+
+}
\ No newline at end of file
|