Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add index to docs samples #291

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace GovUk.Frontend.AspNetCore.DocSamples.Pages.ErrorMessage;
namespace GovUk.Frontend.AspNetCore.DocSamples.Pages.GovUkFrontendComponent.ErrorMessage;

public class ErrorMessageWithModelStateErrorModel : PageModel
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace GovUk.Frontend.AspNetCore.DocSamples.Pages.ErrorSummary;
namespace GovUk.Frontend.AspNetCore.DocSamples.Pages.GovUkFrontendComponent.ErrorSummary;

public class ErrorSummaryWithModelStateErrorModel : PageModel
{
Expand Down
131 changes: 131 additions & 0 deletions src/GovUk.Frontend.AspNetCore.DocSamples/Pages/Index.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
@page
@using System.Text.RegularExpressions
@model IndexModel

@{
ViewData["Title"] = "Gov.UK Frontend ASP.NET Core Samples";
}

@{
static string PascalCaseToSpacedLowercase(string? input) =>
string.IsNullOrWhiteSpace(input)
? string.Empty
: Regex.Replace(input, "(\\B[A-Z])", " $1")
.ToLower()
.Trim()
;

static string CapitaliseFirstLetter(string? input) =>
string.IsNullOrWhiteSpace(input)
? string.Empty
: input.Substring(0, 1).ToUpper() + string.Join("", input.Skip(1))
;
}

<h1 id="top" class="govuk-heading-xl">
GOV.UK Frontend
</h1>

<div class="govuk-grid-row">
<div class="govuk-grid-column">
<h2 class="govuk-heading-l">Components</h2>
<h3 class="govuk-heading-m govuk-!-margin-bottom-2">All components</h3>

<div class="govuk-grid-row govuk-!-margin-bottom-8">
<div class="govuk-grid-column-full">
<ul class="govuk-list govuk-list--number govuk-list--spaced govuk-!-margin-bottom-0">
@foreach (var componentName in Model.ComponentNames())
{
<li>
@* // Component title *@
<a href="#@componentName" class="govuk-link">
@CapitaliseFirstLetter(PascalCaseToSpacedLowercase(componentName))
</a>
@* // Print all samples for this component name *@
<ul class="govuk-list govuk-list--bullet govuk-!-margin-bottom-0">
@foreach (var sample in Model.SampleDetails.Where(x => x.ComponentName == componentName))
{
<li>
<a href="#@sample.SampleName">
@CapitaliseFirstLetter(PascalCaseToSpacedLowercase(sample.SampleName))
</a>
&ndash;
<a href="@Url.Page(sample.RoutePattern)" class="govuk-link govuk-body-s" target="_blank">
Open this example in new tab
</a>
</li>
}
</ul>
</li>
}
</ul>
</div>
</div>
</div>
</div>

<div class="govuk-grid-row">
<div class="govuk-grid-column">
<h2 class="govuk-heading-l">Samples</h2>

@foreach (var componentName in Model.ComponentNames())
{
// Component title
<h3 id="@componentName"
class="govuk-heading-m govuk-!-margin-bottom-2">@CapitaliseFirstLetter(PascalCaseToSpacedLowercase(componentName))
</h3>
<p>
<a class="govuk-link govuk-link--no-visited-state app-back-to-top__link" href="#top">
<svg role="presentation" focusable="false" class="app-back-to-top__icon" xmlns="http://www.w3.org/2000/svg" width="13" height="17" viewBox="0 0 13 17">
<path fill="currentColor" d="M6.5 0L0 6.5 1.4 8l4-4v12.7h2V4l4.3 4L13 6.4z"></path>
</svg>
Back to top
</a>
</p>

@* // Print all samples for this component name *@
@foreach (var sample in Model.SampleDetails.Where(x => x.ComponentName == componentName))
{
<div class="govuk-!-margin-bottom-3">
<div class="govuk-!-margin-left-5 govuk-!-margin-bottom-3 govuk-!-padding-4"
style="border: 1px solid gray; background-color: #FAFAFE;">
<h4 id="@sample.SampleName"
class="govuk-heading-s">@CapitaliseFirstLetter(PascalCaseToSpacedLowercase(sample.SampleName))</h4>
<div>
<a class="govuk-link govuk-link--no-visited-state app-back-to-top__link" href="#top">
<svg role="presentation" focusable="false" class="app-back-to-top__icon" xmlns="http://www.w3.org/2000/svg" width="13" height="17" viewBox="0 0 13 17">
<path fill="currentColor" d="M6.5 0L0 6.5 1.4 8l4-4v12.7h2V4l4.3 4L13 6.4z"></path>
</svg>
Back to top
</a>
</div>
<p>
<a href="@Url.Page(sample.RoutePattern)" class="govuk-link" target="_blank">
Open this example in new tab
</a>
</p>
<iframe src="@Url.Page(sample.RoutePattern)"
class="govuk-iframe"
style="width: 100%; height: 400px; background-color: white;"></iframe>


<h4 class="govuk-heading-s">Code:</h4>
@if (!string.IsNullOrWhiteSpace(sample.CodeSnippet))
{
<pre style="overflow: auto; background-color: white; padding: 1em; border: 1px solid lightgray;"><code>@sample.CodeSnippet</code></pre>
}
else
{
<div class="govuk-warning-text">
<span class="govuk-warning-text__icon" aria-hidden="true">!</span>
<strong class="govuk-warning-text__text">
There was an error loading the code for this sample.
</strong>
</div>
}
</div>
</div>
}
}
</div>
</div>
130 changes: 130 additions & 0 deletions src/GovUk.Frontend.AspNetCore.DocSamples/Pages/Index.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;

namespace GovUk.Frontend.AspNetCore.DocSamples.Pages;

public class IndexModel : PageModel
{
private readonly IEnumerable<EndpointDataSource> _endpointDataSources;

public IEnumerable<SampleDetail> SampleDetails { get; private set; }

public List<string> ComponentNames() => SampleDetails
.Select(x => x.ComponentName)
.Distinct()
.OrderBy(x => x)
.ToList();

public IndexModel(IEnumerable<EndpointDataSource> endpointDataSources) =>
_endpointDataSources = endpointDataSources;

public void OnGet()
{
// Assign and filter RouteEndpoints.
var endpointSources = _endpointDataSources
.SelectMany(source => source.Endpoints)
.OfType<RouteEndpoint>()
.ToList();

Console.Out.WriteLine($"Found {endpointSources.Count} RouteEndpoints.");

// Assign and filter SampleDetails.
SampleDetails = endpointSources
.Select(SampleDetail.FromRouteEndpoint)
.Where(detail => detail is not null)
.Cast<SampleDetail>()
.OrderBy(detail => detail.ComponentName)
.ThenBy(detail => detail.SampleName)
.ToList();
}

public static string MakeReadable(string? input) =>
string.IsNullOrWhiteSpace(input)
? string.Empty
: Regex.Replace(input, "(\\B[A-Z])", " $1")
.ToLower()
.Trim();

public record SampleDetail(
string ComponentName,
string SampleName,
string RoutePattern,
string DisplayName,
string? CodeSnippet = null)
{
public static SampleDetail? FromRouteEndpoint(RouteEndpoint endpoint)
{
if (!IsRouteForComponent(endpoint))
{
return null;
}

var routeParts = GetRoutePartsFromEndpoint(endpoint);
if (routeParts.Count != 3)
{
return null;
}

var componentName = routeParts[1];
var sampleName = routeParts[2];

var filePath = $"Pages/GovUkFrontendComponent/{componentName}/{sampleName}.cshtml";

return new SampleDetail(
ComponentName: componentName,
SampleName: sampleName,
RoutePattern: endpoint.RoutePattern.RawText,
DisplayName: endpoint.DisplayName,
CodeSnippet: System.IO.File.ReadAllText(filePath)
);
}


private static bool IsRouteForComponent(RouteEndpoint endpoint)
{
var routePattern = endpoint.DisplayName;
if (routePattern is null)
{
Console.Error.WriteLine("Route is null.");
return false;
}

var routePatternParts = GetRoutePartsFromEndpoint(endpoint);
if (routePatternParts.Count != 3)
{
Console.Error.WriteLine($"Route '{routePattern}' does not have 4 parts.");
return false;
}

if (routePatternParts[0] != "GovUkFrontendComponent")
{
Console.Error.WriteLine($"Route '{routePattern}' does not start with '/GovUkFrontendComponent'.");
return false;
}

return true;
}

private static List<string> GetRoutePartsFromEndpoint(RouteEndpoint endpoint)
{
var segments = endpoint.RoutePattern.PathSegments;
var routePatternParts = segments
.Select(segment => segment.Parts)
.SelectMany(parts => parts)
.Select(part => part switch
{
RoutePatternLiteralPart literalPart => literalPart.Content,
RoutePatternParameterPart parameterPart => parameterPart.Name,
_ => string.Empty
})
.ToList();

return routePatternParts;
}
}
}
11 changes: 11 additions & 0 deletions src/GovUk.Frontend.AspNetCore.DocSamples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Overview

This project contains a collection of sample usages of each govuk-frontend component.
Each sample is shown within an otherwise empty HTML/Razor page.

The project `GovUk.Frontend.AspNetCore.DocSamplesScreenshotter` then uses these samples to generate screenshots used within the documentation (`docs/components/*.md`).

If you run this project locally, you can view the samples in your browser.
The homepage lists all samples next to the corresponding source code used to generate it, and provides links to view each one in isolation.

http://localhost:5000/
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace GovUk.Frontend.AspNetCore.DocSamples.StubControllers;

public class HomeController : Controller
{
[HttpGet("")]
public IActionResult Index() => Ok();
// [HttpGet("")]
// public IActionResult Index() => RedirectToPage("/Index");
}
Loading