Add the design foundation: self-hosted Nunito, type scale, new tokens and an Icon component
This commit is contained in:
1 parent
3530167cc3
commit
33fa1e4e93
9 files changed
+401
-10
No files matched your search
@@ -0,0 +1,13 @@
|
||||
@*
|
||||
Renders one icon from IconSet as inline SVG. Purely decorative: aria-hidden hides
|
||||
it from screen readers (the text next to it carries the meaning) and
|
||||
focusable="false" keeps old Edge/IE from putting it in the tab order. The stroke
|
||||
is currentColor, so the icon takes the colour of the surrounding text. An unknown
|
||||
name renders nothing at all.
|
||||
*@
|
||||
@if (_markup is not null)
|
||||
{
|
||||
<svg class="icon" width="@Size" height="@Size"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">@_markup</svg>
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Elternbeirat.Web.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Renders a decorative line icon from <see cref="IconSet"/> as inline SVG.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Inline SVG, not an <c><img></c> or icon font: no extra request, nothing
|
||||
/// from a third-party host, and the icon follows the text colour (including dark
|
||||
/// mode) through <c>currentColor</c>. The icon is hidden from assistive technology,
|
||||
/// so it must never be the only carrier of meaning.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// <Icon Name="map-pin" /> Aula
|
||||
/// <Icon Name="calendar" Size="16" />
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <seealso cref="IconSet"/>
|
||||
public partial class Icon
|
||||
{
|
||||
/// <summary>
|
||||
/// The inner SVG markup of the current icon, or <see langword="null"/> when the
|
||||
/// name is unknown and nothing should render.
|
||||
/// </summary>
|
||||
private MarkupString? _markup;
|
||||
|
||||
/// <summary>
|
||||
/// The icon name, e.g. <c>calendar</c>; see <see cref="IconSet"/> for the set.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Width and height in CSS pixels. Defaults to <c>20</c>, which sits well next
|
||||
/// to body text.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int Size { get; set; } = 20;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnParametersSet() =>
|
||||
_markup = IconSet.Find(Name) is { } markup ? new MarkupString(markup) : null;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace Elternbeirat.Web.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// The fixed set of line icons the site can show, keyed by name.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The icons are inlined as SVG markup instead of being loaded from an icon font or
|
||||
/// a CDN: the site loads nothing from third-party hosts (see <c>docs/recht.md</c>),
|
||||
/// and inline SVG needs no extra request and inherits the text colour.
|
||||
/// <para>
|
||||
/// Source: Lucide (<c>lucide.dev</c>), ISC license; the icons derived from
|
||||
/// Feather (<c>calendar</c>, <c>clock</c>, <c>download</c>,
|
||||
/// <c>external-link</c>, <c>help-circle</c>, <c>info</c>, <c>arrow-right</c>)
|
||||
/// are MIT, Copyright (c) 2013-present Cole Bemis. Both licenses only ask that
|
||||
/// the notice travels with the copy, which this comment does. Only the inner
|
||||
/// elements are stored; <see cref="Icon"/> supplies the shared
|
||||
/// <c><svg></c> wrapper (24x24 view box, 2px round stroke).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Lucide renamed <c>help-circle</c> to <c>circle-question-mark</c>; the old
|
||||
/// name is kept here because it is the one the design work refers to.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Icon"/>
|
||||
public static class IconSet
|
||||
{
|
||||
/// <summary>
|
||||
/// Inner SVG markup per icon name. Ordinal keys: names are code identifiers,
|
||||
/// not visitor text, so <c>Calendar</c> is deliberately not <c>calendar</c>.
|
||||
/// </summary>
|
||||
private static readonly FrozenDictionary<string, string> Icons =
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["calendar"] =
|
||||
"""<path d="M8 2v3"/><path d="M16 2v3"/><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/>""",
|
||||
["calendar-plus"] =
|
||||
"""<path d="M16 18h6"/><path d="M16 2v3"/><path d="M19 15v6"/><path d="M21 11.5V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h8.3"/><path d="M3 9h18"/><path d="M8 2v3"/>""",
|
||||
["clock"] =
|
||||
"""<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>""",
|
||||
["map-pin"] =
|
||||
"""<path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/>""",
|
||||
["file-text"] =
|
||||
"""<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>""",
|
||||
["download"] =
|
||||
"""<path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/>""",
|
||||
["mail"] =
|
||||
"""<path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7"/><rect x="2" y="4" width="20" height="16" rx="2"/>""",
|
||||
["users"] =
|
||||
"""<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><path d="M16 3.128a4 4 0 0 1 0 7.744"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><circle cx="9" cy="7" r="4"/>""",
|
||||
["heart-handshake"] =
|
||||
"""<path d="M19.414 14.414C21 12.828 22 11.5 22 9.5a5.5 5.5 0 0 0-9.591-3.676.6.6 0 0 1-.818.001A5.5 5.5 0 0 0 2 9.5c0 2.3 1.5 4 3 5.5l5.535 5.362a2 2 0 0 0 2.879.052 2.12 2.12 0 0 0-.004-3 2.124 2.124 0 1 0 3-3 2.124 2.124 0 0 0 3.004 0 2 2 0 0 0 0-2.828l-1.881-1.882a2.41 2.41 0 0 0-3.409 0l-1.71 1.71a2 2 0 0 1-2.828 0 2 2 0 0 1 0-2.828l2.823-2.762"/>""",
|
||||
["help-circle"] =
|
||||
"""<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/>""",
|
||||
["newspaper"] =
|
||||
"""<path d="M15 18h-5"/><path d="M18 14h-8"/><path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-4 0v-9a2 2 0 0 1 2-2h2"/><rect width="8" height="4" x="10" y="6" rx="1"/>""",
|
||||
["arrow-right"] =
|
||||
"""<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>""",
|
||||
["external-link"] =
|
||||
"""<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>""",
|
||||
["info"] =
|
||||
"""<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>""",
|
||||
}.ToFrozenDictionary(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Looks up the inner SVG markup of an icon.
|
||||
/// </summary>
|
||||
/// <param name="name">
|
||||
/// The icon name, e.g. <c>calendar</c> or <c>map-pin</c>. Case-sensitive.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The SVG child elements (paths, circles, rects) to place inside an
|
||||
/// <c><svg></c>, or <see langword="null"/> if <paramref name="name"/> is
|
||||
/// <see langword="null"/> or not part of the set.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// An unknown name yields <see langword="null"/> rather than an exception, so a
|
||||
/// typo costs a missing decoration, never a broken page.
|
||||
/// </remarks>
|
||||
public static string? Find(string? name) =>
|
||||
name is null ? null : Icons.GetValueOrDefault(name);
|
||||
}
|
||||
Reference in new issue
Block a user