diff --git a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor
index 1eabf57..07b7832 100644
--- a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor
+++ b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor
@@ -37,22 +37,56 @@
-
\ No newline at end of file
+
+
+@code {
+ RenderFragment RenderNavLink(PageLink page)
+ {
+ string linkClass = $"nav-list-link{(page.Pro ? " pro" : "")}";
+ return @
+ @if (page.IconClass is not null)
+ {
+ @(page.Title)
+ }
+ else
+ {
+
@(page.Title)
+ }
+ ;
+ }
+}
\ No newline at end of file
diff --git a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.cs b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.cs
index 9611e22..82bd92e 100644
--- a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.cs
+++ b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.cs
@@ -6,6 +6,22 @@ namespace dymaptic.GeoBlazor.Core.Sample.Shared.Shared;
public partial class NavMenu
{
+ // Category name constants
+ public static class Categories
+ {
+ public const string MapsAndScenes = "Maps & Scenes";
+ public const string Layers = "Layers";
+ public const string Visualization = "Visualization";
+ public const string Widgets = "Widgets";
+ public const string Queries = "Queries";
+ public const string Interaction = "Interaction";
+ public const string Location = "Location";
+ }
+
+ protected static readonly string[] GroupOrder =
+ [Categories.MapsAndScenes, Categories.Layers, Categories.Visualization,
+ Categories.Widgets, Categories.Queries, Categories.Interaction, Categories.Location];
+
[Inject]
public required IJSRuntime JsRuntime { get; set; }
[Inject]
@@ -20,6 +36,31 @@ public partial class NavMenu
? Pages
: Pages.Where(p => p.Title.Contains(_searchText, StringComparison.OrdinalIgnoreCase));
+ protected IEnumerable UngroupedPages => FilteredPages.Where(p => p.Category is null);
+
+ protected IEnumerable<(string GroupName, IEnumerable Pages)> GroupedFilteredPages =>
+ GroupOrder
+ .Select(g => (GroupName: g, Pages: FilteredPages.Where(p => p.Category == g)))
+ .Where(g => g.Pages.Any());
+
+ protected HashSet ExpandedGroups { get; set; } = new();
+
+ protected void ToggleGroup(string groupName)
+ {
+ if (!ExpandedGroups.Add(groupName))
+ {
+ ExpandedGroups.Remove(groupName);
+ }
+ }
+
+ protected bool IsGroupExpanded(string groupName)
+ {
+ if (!string.IsNullOrWhiteSpace(_searchText))
+ return true;
+
+ return ExpandedGroups.Contains(groupName);
+ }
+
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
@@ -37,9 +78,16 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
if (currentPage != string.Empty)
{
+ string? group = Pages.FirstOrDefault(p => p.Href == currentPage)?.Category;
+
+ if (group is not null)
+ {
+ ExpandedGroups.Add(group);
+ }
+
await JsRuntime.InvokeVoidAsync("scrollToNav", currentPage);
}
-
+
StateHasChanged();
}
}
@@ -96,58 +144,67 @@ await InvokeAsync(async () =>
public virtual PageLink[] Pages =>
[
new("", "Home", "oi-home"),
- new("navigation", "Navigation", "oi-compass"),
- new("drawing", "Drawing", "oi-pencil"),
- new("click-to-add", "Click to Add Point", "oi-map-marker"),
- new("many-graphics", "Many Graphics", "oi-calculator"),
- new("scene", "Scene & Attributes", "oi-globe"),
- new("widgets", "Widgets", "oi-location"),
- new("basemaps", "Basemaps", "oi-map"),
- new("feature-layers", "Feature Layers", "oi-layers"),
- new("map-image-layers", "Map Image Layers", "oi-image"),
- new("labels", "Labels", "oi-text"),
- new("popups", "Popups", "oi-chat"),
- new("popup-actions", "Popup Actions", "oi-bullhorn"),
- new("bookmarks", "Bookmarks", "oi-bookmark"),
- new("vector-layer", "Vector Layer", "oi-arrow-right"),
- new("layer-lists", "Layer Lists", "oi-list"),
- new("basemap-layer-lists", "Basemap Layer Lists", "oi-spreadsheet"),
- new("csv-layer", "CSV Layers", "oi-grid-four-up"),
- new("kmllayers", "KML Layers", "oi-excerpt"),
- new("geojson-layers", "GeoJSON Layers", null, "geojson.svg"),
- new("georss-layer", "GeoRSS Layer", "oi-rss"),
- new("osm-layer", "OpenStreetMaps Layer", null, "osm.webp"),
- new("wcslayers", "WCS Layers", "oi-project"),
- new("wfslayers", "WFS Layers", null, "wfs.svg"),
- new("wmslayers", "WMS Layers", null, "wms.svg"),
- new("wmtslayers", "WMTS Layers", null, "wmts.svg"),
- new("imagerylayer", "Imagery Layers", "oi-image"),
- new("imagery-tile-layer", "Imagery Tile Layers", null, "tile.webp"),
- new("web-map", "Web Map", "oi-browser"),
- new("web-scene", "Web Scene", "oi-box"),
- new("events", "Events", "oi-flash"),
- new("reactive-utils", "Reactive Utils", "oi-wrench"),
- new("hit-tests", "Hit Tests", "oi-star"),
- new("sql-query", "SQL Query", "oi-data-transfer-download"),
- new("sql-filter-query", "SQL Filter", "oi-arrow-thick-bottom"),
- new("server-side-queries", "Server-Side Queries", "oi-question-mark"),
- new("query-related-features", "Query Related Features", "oi-people"),
- new("query-top-features", "Query Top Features", "oi-arrow-thick-top"),
- new("place-selector", "Place Selector", "oi-arrow-bottom"),
- new("service-areas", "Service Areas", "oi-comment-square"),
- new("measurement-widgets", "Measurement Widgets", null, "ruler.svg"),
- new("calculate-geometries", "Calculate Geometries", "oi-clipboard"),
- new("projection", "Display Projection", "oi-sun"),
- new("projection-tool", "Projection Tool", "oi-cog"),
- new("basemap-projections", "Basemap Projections", "oi-bullhorn"),
- new("legends", "Legend", null, "legend.svg"),
- new("unique-value", "Unique Renderers", "oi-eyedropper"),
- new("marker-rotation", "Marker Rotation", "oi-loop-circular"),
- new("graphic-tracking", "Graphic Tracking", "oi-move"),
- new("geometry-methods", "Geometry Methods", "oi-task"),
- new("locator-methods", "Locator Methods", "oi-task"),
- new("search-multi-source", "Search Multiple Sources", "oi-magnifying-glass"),
- new("reverse-geolocator", "GeoLocator", "oi-arrow-circle-bottom")
+ new("navigation", "Navigation", "oi-compass", Category: Categories.MapsAndScenes),
+ new("scene", "Scene & Attributes", "oi-globe", Category: Categories.MapsAndScenes),
+ new("basemaps", "Basemaps", "oi-map", Category: Categories.MapsAndScenes),
+ new("web-map", "Web Map", "oi-browser", Category: Categories.MapsAndScenes),
+ new("web-scene", "Web Scene", "oi-box", Category: Categories.MapsAndScenes),
+
+ new("feature-layers", "Feature Layers", "oi-layers", Category: Categories.Layers),
+ new("map-image-layers", "Map Image Layers", "oi-image", Category: Categories.Layers),
+ new("vector-layer", "Vector Layer", "oi-arrow-right", Category: Categories.Layers),
+ new("csv-layer", "CSV Layers", "oi-grid-four-up", Category: Categories.Layers),
+ new("kmllayers", "KML Layers", "oi-excerpt", Category: Categories.Layers),
+ new("geojson-layers", "GeoJSON Layers", null, "geojson.svg", Category: Categories.Layers),
+ new("georss-layer", "GeoRSS Layer", "oi-rss", Category: Categories.Layers),
+ new("osm-layer", "OpenStreetMaps Layer", null, "osm.webp", Category: Categories.Layers),
+ new("wcslayers", "WCS Layers", "oi-project", Category: Categories.Layers),
+ new("wfslayers", "WFS Layers", null, "wfs.svg", Category: Categories.Layers),
+ new("wmslayers", "WMS Layers", null, "wms.svg", Category: Categories.Layers),
+ new("wmtslayers", "WMTS Layers", null, "wmts.svg", Category: Categories.Layers),
+ new("imagerylayer", "Imagery Layers", "oi-image", Category: Categories.Layers),
+ new("imagery-tile-layer", "Imagery Tile Layers", null, "tile.webp", Category: Categories.Layers),
+
+ new("labels", "Labels", "oi-text", Category: Categories.Visualization),
+ new("unique-value", "Unique Renderers", "oi-eyedropper", Category: Categories.Visualization),
+ new("marker-rotation", "Marker Rotation", "oi-loop-circular", Category: Categories.Visualization),
+
+ new("widgets", "Widgets", "oi-location", Category: Categories.Widgets),
+ new("popups", "Popups", "oi-chat", Category: Categories.Widgets),
+ new("popup-actions", "Popup Actions", "oi-bullhorn", Category: Categories.Widgets),
+ new("bookmarks", "Bookmarks", "oi-bookmark", Category: Categories.Widgets),
+ new("layer-lists", "Layer Lists", "oi-list", Category: Categories.Widgets),
+ new("legends", "Legend", null, "legend.svg", Category: Categories.Widgets),
+ new("basemap-layer-lists", "Basemap Layer Lists", "oi-spreadsheet", Category: Categories.Widgets),
+ new("measurement-widgets", "Measurement Widgets", null, "ruler.svg", Category: Categories.Widgets),
+ new("search-multi-source", "Search Multiple Sources", "oi-magnifying-glass", Category: Categories.Widgets),
+
+ new("sql-query", "SQL Query", "oi-data-transfer-download", Category: Categories.Queries),
+ new("sql-filter-query", "SQL Filter", "oi-arrow-thick-bottom", Category: Categories.Queries),
+ new("server-side-queries", "Server-Side Queries", "oi-question-mark", Category: Categories.Queries),
+ new("query-related-features", "Query Related Features", "oi-people", Category: Categories.Queries),
+ new("query-top-features", "Query Top Features", "oi-arrow-thick-top", Category: Categories.Queries),
+
+ new("drawing", "Drawing", "oi-pencil", Category: Categories.Interaction),
+ new("click-to-add", "Click to Add Point", "oi-map-marker", Category: Categories.Interaction),
+ new("many-graphics", "Many Graphics", "oi-calculator", Category: Categories.Interaction),
+ new("events", "Events", "oi-flash", Category: Categories.Interaction),
+ new("reactive-utils", "Reactive Utils", "oi-wrench", Category: Categories.Interaction),
+ new("hit-tests", "Hit Tests", "oi-star", Category: Categories.Interaction),
+ new("graphic-tracking", "Graphic Tracking", "oi-move", Category: Categories.Interaction),
+
+ new("place-selector", "Place Selector", "oi-arrow-bottom", Category: Categories.Location),
+ new("service-areas", "Service Areas", "oi-comment-square", Category: Categories.Location),
+ new("calculate-geometries", "Calculate Geometries", "oi-clipboard", Category: Categories.Location),
+ new("projection", "Display Projection", "oi-sun", Category: Categories.Location),
+ new("projection-tool", "Projection Tool", "oi-cog", Category: Categories.Location),
+ new("basemap-projections", "Basemap Projections", "oi-bullhorn", Category: Categories.Location),
+ new("geometry-methods", "Geometry Methods", "oi-task", Category: Categories.Location),
+ new("locator-methods", "Locator Methods", "oi-task", Category: Categories.Location),
+ new("reverse-geolocator", "GeoLocator", "oi-arrow-circle-bottom", Category: Categories.Location),
];
- public record PageLink(string Href, string Title, string? IconClass = null, string? ImageFile = null, bool Pro = false);
+
+ public record PageLink(
+ string Href, string Title, string? IconClass = null,
+ string? ImageFile = null, bool Pro = false, string? Category = null);
}
\ No newline at end of file
diff --git a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.css b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.css
index 721b2be..bcc1ef4 100644
--- a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.css
+++ b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.css
@@ -11,12 +11,12 @@
#lower-nav-container {
position: absolute;
- top: 9rem;
+ top: 10rem;
left: 0;
background-color: var(--background-grey-2);
width: 20rem;
overflow-y: auto;
- height: calc(100vh - 9rem);
+ height: calc(100vh - 10rem);
margin: 0;
padding: 1rem 0;
}
@@ -42,7 +42,7 @@
width: 2rem;
font-size: 1.1rem;
vertical-align: text-top;
- top: 2px;
+ top: -2px;
}
.oi img {
diff --git a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/wwwroot/css/site.css b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/wwwroot/css/site.css
index fa39093..52ad2aa 100644
--- a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/wwwroot/css/site.css
+++ b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/wwwroot/css/site.css
@@ -81,10 +81,14 @@ article .oi {
}
.nav-list-link .oi {
- top: 2px;
+ top: -2px;
height: 18px;
}
+.nav-list-link .oi img {
+ vertical-align: top;
+}
+
.container {
background-color: unset;
}
@@ -1019,12 +1023,6 @@ a:not([href]):not([class]), a:not([href]):not([class]):hover {
background-color: var(--background-grey-2);
}
-#nav-container {
- overflow-y: auto;
- height: calc(100vh - 6rem);
- padding: 0.5rem 0;
-}
-
.nav-list {
flex: 1 1 auto;
display: flex;
@@ -1077,7 +1075,7 @@ a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: var(--geoblazor-accent-emphasis);
}
-.nav-list .nav-list-item:has(.nav-list-link.active) {
+.nav-list .nav-list-item:has(> .nav-list-link.active) {
background-color: var(--background-grey-3);
}
@@ -1093,26 +1091,87 @@ a:not([href]):not([class]), a:not([href]):not([class]):hover {
background-image: unset;
}
+/* Pinned top-level items (Home) */
+.nav-list .nav-list-item.nav-pinned {
+ padding: 0.5rem 0.75rem;
+ margin-bottom: 0.25rem;
+}
+
+/* Group headers */
+.nav-list .nav-list-item.nav-group {
+ padding: 0;
+ margin-bottom: 0.25rem;
+ margin-top: 0.25rem;
+ background-color: transparent;
+ border-radius: 0;
+ border-top: 1px solid var(--background-grey-3);
+}
+
+.nav-list .nav-pinned + .nav-group {
+ margin-top: 0.5rem;
+}
+
+.nav-list .nav-list-item.nav-group:hover {
+ background-color: transparent;
+}
+
.nav-list .nav-list-item .nav-list-expander {
- color: var(--geoblazor-secondary);
+ color: var(--text-emphasis);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 0.75rem;
+ user-select: none;
+ border-radius: var(--box-radius);
+ background: none;
+ border: none;
+ width: 100%;
+ font: inherit;
+ text-align: left;
}
.nav-list .nav-list-item .nav-list-expander:hover {
color: var(--geoblazor-secondary-hover);
+ background-color: var(--background-grey-1);
background-image: unset;
}
-.nav-list .nav-list-item > .nav-list .nav-list-item .nav-list-link {
- color: var(--text-emphasis);
+.nav-group-title {
+ font-family: 'Roboto', sans-serif;
font-size: 1rem;
+ font-weight: 300;
}
.nav-list .nav-list-item .nav-list-expander svg {
+ width: 12px;
+ height: 12px;
transform: rotate(0);
+ transition: transform 0.2s ease;
+ fill: var(--geoblazor-secondary);
+ flex-shrink: 0;
}
.nav-list .nav-list-item.active .nav-list-expander svg {
transform: rotate(90deg);
+ fill: var(--geoblazor-accent);
+}
+
+/* Nested sublist */
+.nav-list .nav-sublist {
+ padding-left: 1rem;
+ padding-right: 0;
+ margin-top: 0;
+ padding-bottom: 0.25rem;
+}
+
+.nav-list .nav-sublist .nav-list-item {
+ padding: 0.4rem 0.75rem;
+ margin-bottom: 0.125rem;
+}
+
+.nav-list .nav-list-item > .nav-list .nav-list-item .nav-list-link {
+ font-size: 0.9rem;
}
.side-bar a {
@@ -1255,10 +1314,6 @@ a:not([href]):not([class]), a:not([href]):not([class]):hover {
height: 8rem;
}
- #nav-container {
- height: calc(100vh - 8rem);
- }
-
.site-nav {
padding: 0.5rem;
}
@@ -1327,7 +1382,11 @@ a:not([href]):not([class]), a:not([href]):not([class]):hover {
background-color: var(--background-grey-3);
}
- .nav-list .nav-list-item:has(.nav-list-link.active) {
+ .nav-list .nav-list-item .nav-list-expander:hover {
+ background-color: var(--background-grey-3);
+ }
+
+ .nav-list .nav-list-item:has(> .nav-list-link.active) {
background-color: var(--background-grey-4);
}
diff --git a/samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Shared/ProNavMenu.cs b/samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Shared/ProNavMenu.cs
index fdad07c..d3de2f7 100644
--- a/samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Shared/ProNavMenu.cs
+++ b/samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Shared/ProNavMenu.cs
@@ -17,34 +17,34 @@ public class ProNavMenu : NavMenu
href = $"pro-{href}";
}
- return new PageLink(href, p.Title, p.IconClass, p.ImageFile);
+ return p with { Href = href };
}),
- new("imagery-group-blend", "PRO: Imagery Blend", null, "blend.svg", true),
- new("sketch-query", "PRO: Sketch Query", "oi-location", null, true),
- new("edit-feature-data", "PRO: Edit Data", "oi-map-marker", null, true),
- new("popup-edit", "PRO: Popup Edit Data", "oi-pencil", null, true),
- new("update-feature-attributes", "PRO: Update Attributes", "oi-brush", null, true),
- new("apply-edits", "PRO: Apply Edits", "oi-check", null, true),
- new("spatial-relationships", "PRO Relationships", "oi-link-intact", null, true),
- new("demographic-data", "PRO: Demographics", "oi-people", null, true),
- new("length-and-area", "PRO: Length & Area", "oi-graph", null, true),
- new("swipe", "PRO: Swipe Widget", "oi-arrow-thick-left", null, true),
- new("time-slider", "PRO: Time Slider", "oi-vertical-align-center", null, true),
- new("search-custom-source", "PRO: Custom Search", "oi-magnifying-glass", null, true),
- new("clustering", "PRO: Clustering", "oi-fullscreen-exit", null, true),
- new("clustering-popups", "PRO: Clustering Popups", "oi-fullscreen-enter", null, true),
- new("cluster-pie-charts", "PRO: Pie Chart Clusters", "oi-pie-chart", null, true),
- new("binning", "PRO: Binning", "oi-grid-three-up", null, true),
- new("routes", "PRO: Routes", "oi-transfer", null, true),
- new("graphics-legend", "PRO: Graphics Legend", "oi-list-rich", null, true),
- new("group-layers", "PRO: Group Layers", null, "groupLayer.svg", true),
- new("ogc-feature-layers", "PRO: OGC Feature Layers", "oi-layers", null, true),
- new("wfsutils", "PRO: WFS Utils", "oi-wrench", null, true),
- new("print-widget", "PRO: Print Widgets", "oi-print", null, true),
- new("custom-popup-content", "PRO: Custom Popup Content", null, "customPopup.svg", true),
- new("geojson-styles", "PRO: GeoJSON Styles", "oi-brush", null, true),
- new("web-style-symbols", "PRO: Web Style Symbols", "oi-brush", null, true),
+ new("imagery-group-blend", "PRO: Imagery Blend", null, "blend.svg", true, Categories.Layers),
+ new("sketch-query", "PRO: Sketch Query", "oi-location", null, true, Categories.Queries),
+ new("edit-feature-data", "PRO: Edit Data", "oi-map-marker", null, true, Categories.Interaction),
+ new("popup-edit", "PRO: Popup Edit Data", "oi-pencil", null, true, Categories.Widgets),
+ new("update-feature-attributes", "PRO: Update Attributes", "oi-brush", null, true, Categories.Interaction),
+ new("apply-edits", "PRO: Apply Edits", "oi-check", null, true, Categories.Interaction),
+ new("spatial-relationships", "PRO Relationships", "oi-link-intact", null, true, Categories.Queries),
+ new("demographic-data", "PRO: Demographics", "oi-people", null, true, Categories.Location),
+ new("length-and-area", "PRO: Length & Area", "oi-graph", null, true, Categories.Location),
+ new("swipe", "PRO: Swipe Widget", "oi-arrow-thick-left", null, true, Categories.Widgets),
+ new("time-slider", "PRO: Time Slider", "oi-vertical-align-center", null, true, Categories.Widgets),
+ new("search-custom-source", "PRO: Custom Search", "oi-magnifying-glass", null, true, Categories.Widgets),
+ new("clustering", "PRO: Clustering", "oi-fullscreen-exit", null, true, Categories.Visualization),
+ new("clustering-popups", "PRO: Clustering Popups", "oi-fullscreen-enter", null, true, Categories.Visualization),
+ new("cluster-pie-charts", "PRO: Pie Chart Clusters", "oi-pie-chart", null, true, Categories.Visualization),
+ new("binning", "PRO: Binning", "oi-grid-three-up", null, true, Categories.Visualization),
+ new("routes", "PRO: Routes", "oi-transfer", null, true, Categories.Location),
+ new("graphics-legend", "PRO: Graphics Legend", "oi-list-rich", null, true, Categories.Visualization),
+ new("group-layers", "PRO: Group Layers", null, "groupLayer.svg", true, Categories.Layers),
+ new("ogc-feature-layers", "PRO: OGC Feature Layers", "oi-layers", null, true, Categories.Layers),
+ new("wfsutils", "PRO: WFS Utils", "oi-wrench", null, true, Categories.Layers),
+ new("print-widget", "PRO: Print Widgets", "oi-print", null, true, Categories.Widgets),
+ new("custom-popup-content", "PRO: Custom Popup Content", null, "customPopup.svg", true, Categories.Widgets),
+ new("geojson-styles", "PRO: GeoJSON Styles", "oi-brush", null, true, Categories.Visualization),
+ new("web-style-symbols", "PRO: Web Style Symbols", "oi-brush", null, true, Categories.Visualization),
// not ready, bug in Column.Width JsonConverter
- // new("highlight-features-by-geometry", "PRO: Highlight by Geometry", "oi-target", null, true)
+ // new("highlight-features-by-geometry", "PRO: Highlight by Geometry", "oi-target", null, true, Categories.Interaction)
];
}
\ No newline at end of file