Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions NHSUKFrontendRazor/ViewComponents/HeaderViewComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace NHSUKFrontendRazor.ViewComponents
{
using Microsoft.AspNetCore.Mvc;
using NHSUKFrontendRazor.ViewModels;

/// <summary>
/// A ViewComponent that renders the NHS header, which provides consistent branding, navigation, search, and account management links.
/// The header is a key part of the NHS.UK frontend design system.
/// </summary>
public class HeaderViewComponent : ViewComponent
{
public IViewComponentResult Invoke(
Organisation organisationDetails,
bool? isService = true,
AccountLinks? accountLinks = null,
List<LinkViewModel>? navigationLinks = null,
Dictionary<string, string>? theme = null,
LinkViewModel? searchLink = null,
string? searchFolder = null,
string? searchNavView = null,
string? searchControllerName = null)
{
theme ??= HeaderTheme.BLUE;

accountLinks ??= new AccountLinks
(
new LinkViewModel("myaccount", null, "My account", null),
new LinkViewModel("Home", "Logout", "Log out", null),
null
);


var model = new HeaderViewModel(organisationDetails, isService, accountLinks, navigationLinks, theme, searchLink, searchFolder, searchNavView, searchControllerName);

return View(model);
}
}
}
173 changes: 173 additions & 0 deletions NHSUKFrontendRazor/ViewModels/HeaderViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
namespace NHSUKFrontendRazor.ViewModels
{
public class HeaderViewModel
{
/// <summary>
/// Initializes a new instance of the <see cref="HeaderViewModel"/> class.
/// </summary>
/// <param name="organisationDetails">The details of the organisation for branding.</param>
/// <param name="isService">Indicates if the header is for a service or an organisation.</param>
/// <param name="accountLinks">Links related to the user's account.</param>
/// <param name="navigationLinks">The primary navigation links for the header.</param>
/// <param name="theme">The color theme for the header and navigation.</param>
/// <param name="searchLink">A simple link for the header search form.</param>
/// <param name="searchFolder">The folder for a component-based search view.</param>
/// <param name="searchNavView">The view name for a component-based search.</param>
/// <param name="searchControllerName">The controller name for a component-based search.</param>
public HeaderViewModel(
Organisation organisationDetails,
bool? isService,
AccountLinks? accountLinks,
List<LinkViewModel>? navigationLinks,
Dictionary<string, string> theme,
LinkViewModel searchLink,
string? searchFolder,
string? searchNavView,
string? searchControllerName
)
{
OrganisationDetails = organisationDetails;
IsService = isService;
AccountLinks = accountLinks;
NavigationLinks = navigationLinks;
Theme = theme;
SearchLink = searchLink;
SearchFolder = searchFolder;
SearchNavView = searchNavView;
SearchControllerName = searchControllerName;
}

/// <summary>
/// Gets or sets the organisation details for header branding.
/// </summary>
public Organisation OrganisationDetails { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the header is for a service.
/// </summary>
public bool? IsService { get; set; }

/// <summary>
/// Gets or sets the links for the account section of the header.
/// </summary>
public AccountLinks? AccountLinks { get; set; }

/// <summary>
/// Gets or sets the list of primary navigation links.
/// </summary>
public List<LinkViewModel>? NavigationLinks { get; set; }

/// <summary>
/// Gets or sets the theme for the header, which controls CSS classes.
/// </summary>
public Dictionary<string, string> Theme { get; set; }

/// <summary>
/// Gets or sets the view model for a simple search link in the header.
/// </summary>
public LinkViewModel? SearchLink { get; set; }

/// <summary>
/// Gets or sets the folder name for invoking a search ViewComponent.
/// </summary>
public string? SearchFolder { get; set; }

/// <summary>
/// Gets or sets the view name for invoking a search ViewComponent.
/// </summary>
public string? SearchNavView { get; set; }

/// <summary>
/// Gets or sets the controller name for invoking a search ViewComponent.
/// </summary>
public string? SearchControllerName { get; set; }

}

/// <summary>
/// Provides predefined themes for the NHS header component.
/// </summary>
public static class HeaderTheme
{
public static Dictionary<string, string> BLUE = new Dictionary<string, string> { { "header", "nhsuk-header" }, { "navigation", "nhsuk-header__navigation" } };
public static Dictionary<string, string> WHITE = new Dictionary<string, string> { { "header", "nhsuk-header nhsuk-header--white" }, { "navigation", "nhsuk-header__navigation nhsuk-header__navigation--white" } };
public static Dictionary<string, string> MIXED = new Dictionary<string, string> { { "header", "nhsuk-header nhsuk-header--white" }, { "navigation", "nhsuk-header__navigation" } };
}

/// <summary>
/// Represents the details of an NHS organisation for display in the header.
/// </summary>
public class Organisation
{
/// <summary>
/// Initializes a new instance of the <see cref="Organisation"/> class.
/// </summary>
/// <param name="name">The main name of the organisation.</param>
/// <param name="split">The secondary part of the organisation's name, often a location or type.</param>
/// <param name="descriptor">A descriptor for the organisation, such as a region or trust type.</param>
public Organisation(
string name,
string split,
string descriptor)
{
Name = name;
Split = split;
Descriptor = descriptor;
}

/// <summary>
/// Gets or sets the primary name of the organisation (e.g., "Anytown").
/// </summary>
public string? Name { get; set; }

/// <summary>
/// Gets or sets the split part of the organisation name (e.g., "Clinical Commissioning Group").
/// </summary>
public string? Split { get; set; }

/// <summary>
/// Gets or sets the descriptor for the organisation (e.g., "NHS Trust").
/// </summary>
public string? Descriptor { get; set; }
}


/// <summary>
/// Represents the set of links related to user account management in the header.
/// </summary>
public class AccountLinks
{

/// <summary>
/// Initializes a new instance of the <see cref="AccountLinks"/> class.
/// </summary>
/// <param name="account">The primary link to the user's account page.</param>
/// <param name="logout">The link to log the user out.</param>
/// <param name="additionalLinks">A list of any additional links related to the user's account.</param>
public AccountLinks(
LinkViewModel account,
LinkViewModel logout,
List<LinkViewModel>? additionalLinks
)
{
Account = account;
Logout = logout;
AdditionalLinks = additionalLinks;
}

/// <summary>
/// Gets or sets the primary link to the user's account page.
/// </summary>
public LinkViewModel Account { get; set; }

/// <summary>
/// Gets or sets the link used for logging out.
/// </summary>
public LinkViewModel Logout { get; set; }

/// <summary>
/// Gets or sets a list of additional account-related links.
/// </summary>
public List<LinkViewModel>? AdditionalLinks { get; set; }
}
}
6 changes: 3 additions & 3 deletions NHSUKFrontendRazor/ViewModels/LinkViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ namespace NHSUKFrontendRazor.ViewModels

public class LinkViewModel
{
public readonly string AspAction;
public readonly string? AspAction;

public readonly string AspController;

public readonly string LinkText;

public readonly Dictionary<string, string> AspAllRouteData;
public readonly Dictionary<string, string>? AspAllRouteData;

public LinkViewModel(string aspController, string aspAction, string linkText, Dictionary<string, string> aspAllRouteData)
public LinkViewModel(string aspController, string? aspAction, string linkText, Dictionary<string, string>? aspAllRouteData)
{
AspAction = aspAction;
AspController = aspController;
Expand Down
124 changes: 124 additions & 0 deletions NHSUKFrontendRazor/Views/Shared/Components/Header/Default.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
@using NHSUKFrontendRazor.ViewModels
@model HeaderViewModel

@{
var org = Model.OrganisationDetails;
}

<header class="@Model.Theme["header"] @(Model.IsService == true ? "" : "nhsuk-header--organisation")" role="banner" data-module="nhsuk-header">
<div class="nhsuk-header__container nhsuk-width-container">
<div class="nhsuk-header__service">
<a class="nhsuk-header__service-logo" href="/" aria-label="NHS @org.Name @org.Split homepage">
<svg class="nhsuk-header__logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 80" height="40" width="100" focusable="false" role="img" aria-label="NHS">
<title>NHS</title>
<path fill="currentcolor" d="M200 0v80H0V0h200Zm-27.5 5.5c-14.5 0-29 5-29 22 0 10.2 7.7 13.5 14.7 16.3l.7.3c5.4 2 10.1 3.9 10.1 8.4 0 6.5-8.5 7.5-14 7.5s-12.5-1.5-16-3.5L135 70c5.5 2 13.5 3.5 20 3.5 15.5 0 32-4.5 32-22.5 0-19.5-25.5-16.5-25.5-25.5 0-5.5 5.5-6.5 12.5-6.5a35 35 0 0 1 14.5 3l4-13.5c-4.5-2-12-3-20-3Zm-131 2h-22l-14 65H22l9-45h.5l13.5 45h21.5l14-65H64l-9 45h-.5l-13-45Zm63 0h-18l-13 65h17l6-28H117l-5.5 28H129l13.5-65H125L119.5 32h-20l5-24.5Z" />
</svg>
@if (Model.IsService == true)
{
<span class="nhsuk-header__service-name">@org.Name @org.Split</span>
}
else
{
<span class="nhsuk-header__organisation-name">@org.Name <span class="nhsuk-header__organisation-name-split">@org.Split</span></span>

<span class="nhsuk-header__organisation-name-descriptor">@org.Descriptor</span>
}

</a>
</div>


@* ------------- SEARCH ------------- *@
@if (User.Identity.IsAuthenticated)
{
@if (Model.SearchFolder != null && Model.SearchNavView != null)
{
@await Component.InvokeAsync(Model.SearchFolder, new { navView = Model.SearchNavView, controllerName = Model.SearchControllerName })
}
else if (Model.SearchLink != null)
{
<search class="nhsuk-header__search">
<form class="nhsuk-header__search-form" id="search-form" asp-controller="@Model.SearchLink.AspController" asp-action="@Model.SearchLink.AspAction" asp-all-route-data="@Model.SearchLink.AspAllRouteData" method="get">
<label class="nhsuk-u-visually-hidden" for="search-field">Search the NHS @org.Name @org.Split</label>
<input class="nhsuk-header__search-input nhsuk-input" id="search-field" name="search-field" type="search" placeholder="Search" autocomplete="off">
<button class="nhsuk-header__search-submit" type="submit">
<svg class="nhsuk-icon nhsuk-icon--search" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" focusable="false" role="img" aria-label="Search">
<title>Search</title>
<path d="m20.7 19.3-4.1-4.1a7 7 0 1 0-1.4 1.4l4 4.1a1 1 0 0 0 1.5 0c.4-.4.4-1 0-1.4ZM6 11a5 5 0 1 1 10 0 5 5 0 0 1-10 0Z" />
</svg>

</button>
</form>
</search>
}
}


@* ------------- ACCOUNT ------------- *@
<nav class="nhsuk-header__account" aria-label="Account">
<ul class="nhsuk-header__account-list">
@if (User.Identity.IsAuthenticated && Model.AccountLinks != null) //ACCESS-ACCOUNT OR LOGOUT
{
@if (Model.AccountLinks.Account != null)
{
var account = Model.AccountLinks.Account;

<li class="nhsuk-header__account-item">
<svg class="nhsuk-icon nhsuk-icon--user" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" focusable="false" aria-hidden="true">
<path d="M12 1a11 11 0 1 1 0 22 11 11 0 0 1 0-22Zm0 2a9 9 0 0 0-5 16.5V18a4 4 0 0 1 4-4h2a4 4 0 0 1 4 4v1.5A9 9 0 0 0 12 3Zm0 3a3.5 3.5 0 1 1-3.5 3.5A3.4 3.4 0 0 1 12 6Z" />
</svg>
<a class="nhsuk-header__account-link" asp-controller="@account.AspController" asp-action="@account.AspAction">
@account.LinkText
</a>
</li>
}
@if (Model.AccountLinks.AdditionalLinks != null)
{
@foreach (var link in Model.AccountLinks.AdditionalLinks)
{
<li class="nhsuk-header__account-item">
<a class="nhsuk-header__account-link" asp-controller="@link.AspController" asp-action="@link.AspAction">
@link.LinkText
</a>
</li>
}
}
@if (Model.AccountLinks.Logout != null)
{
var logout = Model.AccountLinks.Logout;

<li class="nhsuk-header__account-item">
<a class="nhsuk-header__account-link" asp-controller="@logout.AspController" asp-action="@logout.AspAction">
@logout.LinkText
</a>
</li>
}
}
</ul>
</nav>
</div>


@* ------------- NAVIGATION ------------- *@
@if (User.Identity.IsAuthenticated)
{
<nav class="@Model.Theme["navigation"]" aria-label="Menu">
<div class="nhsuk-header__navigation-container nhsuk-width-container">
<ul class="nhsuk-header__navigation-list">
<li class="nhsuk-header__menu" hidden>
<button class="nhsuk-header__menu-toggle nhsuk-header__navigation-link" id="toggle-menu" aria-expanded="false">
<span class="nhsuk-u-visually-hidden">Browse </span>More
</button>
</li>

@foreach (var link in Model.NavigationLinks)
{
<li class="nhsuk-header__navigation-item">
<a class="nhsuk-header__navigation-link" asp-controller="@link.AspController" asp-action="@link.AspAction" asp-all-route-data="@link.AspAllRouteData">@link.LinkText</a>
</li>
}
</ul>
</div>
</nav>
}
</header>