Skip to content

Commit ba33c44

Browse files
authored
Merge pull request #15 from shellui-dev/fix/consumer-brand-logo
fix: consumer-configurable brand logo across sidebar / header / footer
2 parents 18330cf + 64c0bc4 commit ba33c44

10 files changed

Lines changed: 136 additions & 25 deletions
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
@namespace ShellDocs.Components.Chrome
2+
@inject ShellDocsOptions Options
3+
4+
@if (!string.IsNullOrWhiteSpace(Options.LogoSvg))
5+
{
6+
<span class="brand-logo-svg" style="height: @Height" aria-label="@Alt" role="img">
7+
@((MarkupString)Options.LogoSvg)
8+
</span>
9+
}
10+
else if (HasUrlLogo)
11+
{
12+
var light = Options.LogoLight ?? Options.LogoDark!;
13+
var dark = Options.LogoDark ?? Options.LogoLight!;
14+
15+
<img class="brand-logo brand-logo-light" src="@light" alt="@Alt" style="height: @Height" />
16+
@if (!string.Equals(light, dark, StringComparison.Ordinal))
17+
{
18+
<img class="brand-logo brand-logo-dark" src="@dark" alt="@Alt" style="height: @Height" />
19+
}
20+
}
21+
else
22+
{
23+
<span class="brand-logo-dot"></span>
24+
}
25+
26+
@code {
27+
private bool HasUrlLogo => !string.IsNullOrEmpty(Options.LogoLight) || !string.IsNullOrEmpty(Options.LogoDark);
28+
private string Alt => Options.LogoAlt ?? Options.SiteName ?? "";
29+
private string Height => Options.LogoHeight.ToString(System.Globalization.CultureInfo.InvariantCulture) + "rem";
30+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.brand-logo {
2+
display: inline-block;
3+
width: auto;
4+
flex-shrink: 0;
5+
object-fit: contain;
6+
}
7+
8+
.brand-logo-dark { display: none; }
9+
:root.dark .brand-logo-light { display: none; }
10+
:root.dark .brand-logo-dark { display: inline-block; }
11+
12+
.brand-logo-svg {
13+
display: inline-flex;
14+
align-items: center;
15+
flex-shrink: 0;
16+
color: var(--foreground);
17+
}
18+
::deep .brand-logo-svg > svg {
19+
height: 100%;
20+
width: auto;
21+
display: block;
22+
}
23+
24+
.brand-logo-dot {
25+
width: 1.125rem;
26+
height: 1.125rem;
27+
border-radius: 4px;
28+
background: var(--foreground);
29+
flex-shrink: 0;
30+
}

src/ShellDocs.Components/Chrome/DocsFooter.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<div class="docs-footer-inner">
55
<div class="docs-footer-brand">
66
<div class="docs-footer-mark">
7-
<span class="docs-footer-logo-dot"></span>
7+
<BrandLogo />
88
<span class="docs-footer-brand-name">@(!string.IsNullOrEmpty(Options.SiteName) ? Options.SiteName : "ShellDocs")</span>
99
</div>
1010
@if (!string.IsNullOrEmpty(Options.SiteTagline))

src/ShellDocs.Components/Chrome/DocsFooter.razor.css

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,6 @@
1616

1717
::deep .docs-footer-brand { display: flex; flex-direction: column; gap: 0.85rem; }
1818
::deep .docs-footer-mark { display: inline-flex; align-items: center; gap: 0.5rem; }
19-
::deep .docs-footer-logo-dot {
20-
width: 1.125rem;
21-
height: 1.125rem;
22-
border-radius: 4px;
23-
background: var(--foreground);
24-
}
2519
::deep .docs-footer-brand-name { font-weight: 600; font-size: 1rem; letter-spacing: -0.01em; }
2620
::deep .docs-footer-tagline {
2721
margin: 0;

src/ShellDocs.Components/Chrome/DocsHeader.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
</svg>
2121
</button>
2222
<a class="docs-header-brand" href="/">
23-
<span class="docs-header-logo-dot"></span>
23+
<BrandLogo />
2424
<span class="docs-header-brand-name">@(!string.IsNullOrEmpty(Options.SiteName) ? Options.SiteName : "Docs")</span>
2525
</a>
2626

src/ShellDocs.Components/Chrome/DocsHeader.razor.css

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,6 @@
5555
}
5656
.docs-header-brand:hover { opacity: 0.85; }
5757

58-
.docs-header-logo-dot {
59-
width: 1.125rem;
60-
height: 1.125rem;
61-
border-radius: 4px;
62-
background: var(--foreground);
63-
flex-shrink: 0;
64-
}
65-
6658
.docs-header-brand-name { font-weight: 600; letter-spacing: -0.01em; }
6759

6860
.docs-header-nav {

src/ShellDocs.Components/Chrome/DocsSidebarHeader.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<div class="docs-sidebar-header">
66
<div class="docs-sidebar-brand-row">
77
<a class="docs-sidebar-brand" href="/">
8-
<span class="docs-sidebar-logo-dot"></span>
8+
<BrandLogo />
99
<span class="docs-sidebar-brand-name">@(!string.IsNullOrEmpty(Options.SiteName) ? Options.SiteName : "Docs")</span>
1010
</a>
1111
<button type="button" class="docs-sidebar-collapse" @onclick="Collapse.Toggle" aria-label="Collapse sidebar" title="Collapse sidebar">

src/ShellDocs.Components/Chrome/DocsSidebarHeader.razor.css

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,6 @@
4747
}
4848
.docs-sidebar-collapse:hover { color: var(--foreground); background: var(--muted); }
4949
.docs-sidebar-collapse svg { width: 1rem; height: 1rem; }
50-
.docs-sidebar-logo-dot {
51-
width: 1.125rem;
52-
height: 1.125rem;
53-
border-radius: 4px;
54-
background: var(--foreground);
55-
flex-shrink: 0;
56-
}
57-
5850
.docs-sidebar-search {
5951
display: inline-flex;
6052
align-items: center;

src/ShellDocs.Components/ShellDocsOptions.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ public class ShellDocsOptions
1010
public string SiteName { get; set; } = "";
1111
public string? SiteTagline { get; set; }
1212
public string? GitHubRepo { get; set; }
13+
14+
public string? LogoLight { get; set; }
15+
public string? LogoDark { get; set; }
16+
public string? LogoAlt { get; set; }
17+
public double LogoHeight { get; set; } = 1.375;
18+
// Rendered as MarkupString — must be trusted content the consumer authored, not user input.
19+
public string? LogoSvg { get; set; }
1320
public ShellDocsTheme Theme { get; set; } = ShellDocsTheme.Shadcn;
1421
public DocsLayoutVariant LayoutVariant { get; set; } = DocsLayoutVariant.TopNav;
1522

@@ -117,6 +124,22 @@ public ShellDocsOptions AddPackage(string id, string title, string description,
117124
return this;
118125
}
119126

127+
public ShellDocsOptions SetLogo(string url, string? alt = null)
128+
{
129+
LogoLight = url;
130+
LogoDark = url;
131+
if (alt is not null) LogoAlt = alt;
132+
return this;
133+
}
134+
135+
public ShellDocsOptions SetLogo(string lightUrl, string darkUrl, string? alt = null)
136+
{
137+
LogoLight = lightUrl;
138+
LogoDark = darkUrl;
139+
if (alt is not null) LogoAlt = alt;
140+
return this;
141+
}
142+
120143
internal TypeRegistry BuildTypeRegistry()
121144
{
122145
var registry = new TypeRegistry();
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using ShellDocs.Components;
2+
using Xunit;
3+
4+
namespace ShellDocs.Tests;
5+
6+
public class BrandLogoOptionsTests
7+
{
8+
[Fact]
9+
public void SetLogo_SingleUrl_AppliesToBothThemes()
10+
{
11+
var options = new ShellDocsOptions().SetLogo("/img/logo.svg");
12+
13+
Assert.Equal("/img/logo.svg", options.LogoLight);
14+
Assert.Equal("/img/logo.svg", options.LogoDark);
15+
}
16+
17+
[Fact]
18+
public void SetLogo_LightAndDark_KeepsBothDistinct()
19+
{
20+
var options = new ShellDocsOptions().SetLogo("/img/light.svg", "/img/dark.svg", "Brand");
21+
22+
Assert.Equal("/img/light.svg", options.LogoLight);
23+
Assert.Equal("/img/dark.svg", options.LogoDark);
24+
Assert.Equal("Brand", options.LogoAlt);
25+
}
26+
27+
[Fact]
28+
public void LogoHeight_DefaultsToOnePointThreeSevenFiveRem()
29+
{
30+
Assert.Equal(1.375, new ShellDocsOptions().LogoHeight);
31+
}
32+
33+
[Fact]
34+
public void Logos_DefaultToNull_SoDotFallbackRenders()
35+
{
36+
var options = new ShellDocsOptions();
37+
Assert.Null(options.LogoLight);
38+
Assert.Null(options.LogoDark);
39+
Assert.Null(options.LogoSvg);
40+
}
41+
42+
[Fact]
43+
public void LogoSvg_HoldsRawMarkupUntouched()
44+
{
45+
const string svg = "<svg viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\" fill=\"currentColor\"/></svg>";
46+
var options = new ShellDocsOptions { LogoSvg = svg };
47+
48+
Assert.Equal(svg, options.LogoSvg);
49+
}
50+
}

0 commit comments

Comments
 (0)