Skip to content

Commit 0f8e3fb

Browse files
authored
Merge pull request #11 from shellui-dev/feat/content-primitives
feat: content primitives : Callout, Card, Steps, FileTree, CodeGroup + stable razor:preview
2 parents 5f70726 + 53792f2 commit 0f8e3fb

33 files changed

Lines changed: 1018 additions & 191 deletions

examples/ShellDocs.Preview/Components/Demos/Callout.razor

Lines changed: 0 additions & 68 deletions
This file was deleted.

examples/ShellDocs.Preview/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
o.AddNavLink("Showcase", "/showcase");
3434
o.AddNavLink("Blog", "/blog");
3535

36-
// Demo components available for <razor:preview> blocks in markdown.
37-
o.RegisterComponent<ShellDocs.Preview.Components.Demos.Callout>();
36+
// Callout, Card, Steps, FileTree, CodeGroup, LinkCard etc. are
37+
// auto-registered by AddShellDocs — no extra work needed here.
3838
});
3939

4040
var app = builder.Build();
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
title: Card
3+
description: Bordered cards with title, description, optional icon, and optional link.
4+
category: Components
5+
order: 12
6+
---
7+
8+
# Card
9+
10+
`<Card>` renders a bordered rounded panel with a title, description, and optional icon. Pass `Href` to make the whole card a link.
11+
12+
## Basic
13+
14+
```razor:preview
15+
<Card Title="Read the docs" Description="Get started with a walkthrough of what's inside." />
16+
```
17+
18+
## As a link
19+
20+
Add `Href` and the card becomes a link with a hover accent.
21+
22+
```razor:preview
23+
<Card Title="Configuration" Description="Every option on ShellDocsOptions, one table." Href="/docs/introduction" />
24+
```
25+
26+
## In a grid
27+
28+
Wrap Cards in `<CardGrid Columns="2">` for a responsive 2-col (or 3-col) layout.
29+
30+
```razor:preview
31+
<CardGrid Columns="2">
32+
<Card Title="Fast" Description="Instant page loads, client-rendered islands only where needed." />
33+
<Card Title="Themeable" Description="One CSS var for every colour; override in your own stylesheet." />
34+
<Card Title="Composable" Description="Every layout, header, and TOC is a Blazor component you can swap." />
35+
<Card Title="Static-ready" Description="Deploy to GH Pages, Cloudflare, or S3 as pre-rendered HTML." />
36+
</CardGrid>
37+
```
38+
39+
## LinkCard
40+
41+
For "further reading" panels, `<LinkCard>` is a compact one-line variant with a hover arrow.
42+
43+
```razor:preview
44+
<LinkCard Title="Frontmatter reference" Description="Every property you can set at the top of a .md file." Href="/docs/markdown-syntax" />
45+
```
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
title: CodeGroup
3+
description: Tabbed code samples that sync across the page.
4+
category: Components
5+
order: 25
6+
---
7+
8+
# CodeGroup
9+
10+
`<CodeGroup>` groups multiple code samples into a tabbed panel — the reader picks one, sees that source, ignores the others. The most common use is a per-package-manager install snippet.
11+
12+
## Basic
13+
14+
```razor:preview
15+
<CodeGroup>
16+
<CodeTab Label="npm">npm install shelldocs</CodeTab>
17+
<CodeTab Label="pnpm">pnpm add shelldocs</CodeTab>
18+
<CodeTab Label="yarn">yarn add shelldocs</CodeTab>
19+
</CodeGroup>
20+
```
21+
22+
## Sync groups
23+
24+
Pass `SyncKey` and every `<CodeGroup>` on the page with the same key switches together. Pick "pnpm" here — every other snippet with `SyncKey="pkg"` on the page will also read pnpm.
25+
26+
```razor:preview
27+
<CodeGroup SyncKey="pkg">
28+
<CodeTab Label="npm">npm run build</CodeTab>
29+
<CodeTab Label="pnpm">pnpm build</CodeTab>
30+
<CodeTab Label="yarn">yarn build</CodeTab>
31+
</CodeGroup>
32+
```
33+
34+
## Notes
35+
36+
- The first `<CodeTab>` in source order is the default selection on first render (unless overridden by an active sync group).
37+
- Choose stable sync keys — the sync state uses the key as its dictionary bucket, so renaming a key resets everyone's pick.
38+
- Sync is per-circuit today. Cross-session persistence via `localStorage` lands in a follow-up.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
title: FileTree
3+
description: Static folder / file visualisation for project layout diagrams.
4+
category: Components
5+
order: 50
6+
---
7+
8+
# FileTree
9+
10+
`<FileTree>` renders a static folder-and-file tree — the kind of "here's what your project structure looks like" diagram you see in every framework's getting-started guide.
11+
12+
## Basic
13+
14+
```razor:preview
15+
<FileTree>
16+
<FileTreeItem Name="my-docs" IsFolder="true">
17+
<FileTreeItem Name="content" IsFolder="true">
18+
<FileTreeItem Name="docs" IsFolder="true">
19+
<FileTreeItem Name="introduction.md" />
20+
<FileTreeItem Name="installation.md" />
21+
<FileTreeItem Name="meta.json" Comment="sidebar order" />
22+
</FileTreeItem>
23+
</FileTreeItem>
24+
<FileTreeItem Name="Components" IsFolder="true">
25+
<FileTreeItem Name="Pages" IsFolder="true">
26+
<FileTreeItem Name="DocsPage.razor" Highlight="true" Comment="routes /docs/*" />
27+
<FileTreeItem Name="Home.razor" />
28+
</FileTreeItem>
29+
<FileTreeItem Name="App.razor" />
30+
</FileTreeItem>
31+
<FileTreeItem Name="Program.cs" />
32+
<FileTreeItem Name="my-docs.csproj" />
33+
</FileTreeItem>
34+
</FileTree>
35+
```
36+
37+
## Props
38+
39+
- `Name` — the file or folder name shown next to the glyph
40+
- `IsFolder` — draws the folder glyph and enables nested children
41+
- `Comment` — muted italic comment shown to the right (e.g. `// sidebar order`)
42+
- `Highlight` — soft warning-coloured background on the label to draw attention to a specific line
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"title": "Components",
3-
"pages": ["callout", "code-block", "tabs"]
3+
"pages": ["callout", "card", "code-block", "code-group", "steps", "tabs", "filetree"]
44
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
title: Steps
3+
description: Vertically-numbered onboarding sequence with a connecting rail.
4+
category: Components
5+
order: 40
6+
---
7+
8+
# Steps
9+
10+
Use `<Steps>` to lay out an onboarding sequence — install, configure, run. Each `<Step>` is auto-numbered by its position; you don't manage the counter.
11+
12+
## Basic
13+
14+
```razor:preview
15+
<Steps>
16+
<Step Title="Install the CLI">
17+
Install the ShellDocs global tool from NuGet.
18+
</Step>
19+
<Step Title="Scaffold a project">
20+
Run <code>shelldocs init</code> in your repo root.
21+
</Step>
22+
<Step Title="Author your first page">
23+
Edit <code>content/docs/introduction.md</code>. It hot-reloads.
24+
</Step>
25+
<Step Title="Ship it">
26+
<code>shelldocs build</code> emits a static site. Deploy anywhere.
27+
</Step>
28+
</Steps>
29+
```
30+
31+
## Notes
32+
33+
- Each Step's `Title` is optional — omit for a rendered numbered paragraph.
34+
- Nest richer content (paragraphs, code, callouts) via `ChildContent`.
35+
- The rail is drawn from the outer `<Steps>` `border-left`; step number chips overlay it.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
@namespace ShellDocs.Components.Content
2+
3+
<div class="callout callout-@Variant.ToLowerInvariant()">
4+
<span class="callout-icon" aria-hidden="true">
5+
@switch (Variant.ToLowerInvariant())
6+
{
7+
case "warning":
8+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
9+
break;
10+
case "danger":
11+
case "error":
12+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
13+
break;
14+
case "tip":
15+
case "success":
16+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6M10 22h4M12 2a7 7 0 0 0-4 12.7c.5.5 1 1.3 1 2.3v1h6v-1c0-1 .5-1.8 1-2.3A7 7 0 0 0 12 2z"/></svg>
17+
break;
18+
default: /* info */
19+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
20+
break;
21+
}
22+
</span>
23+
<div class="callout-body">
24+
@if (!string.IsNullOrEmpty(Title))
25+
{
26+
<div class="callout-title">@Title</div>
27+
}
28+
<div class="callout-content">
29+
@if (ChildContent is not null) { @ChildContent }
30+
else if (!string.IsNullOrEmpty(Text)) { @Text }
31+
</div>
32+
</div>
33+
</div>
34+
35+
@code {
36+
[Parameter] public string Variant { get; set; } = "info";
37+
[Parameter] public string? Title { get; set; }
38+
[Parameter] public string? Text { get; set; }
39+
[Parameter] public RenderFragment? ChildContent { get; set; }
40+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
.callout {
2+
display: flex;
3+
gap: 0.85rem;
4+
padding: 0.9rem 1.1rem;
5+
border: 1px solid var(--border);
6+
border-radius: calc(var(--radius) + 2px);
7+
background: color-mix(in oklch, var(--card) 70%, var(--background));
8+
line-height: 1.6;
9+
font-size: 0.9rem;
10+
margin: 1.25rem 0;
11+
}
12+
.callout-icon { flex-shrink: 0; display: inline-flex; align-items: flex-start; padding-top: 0.05rem; }
13+
.callout-icon svg { width: 1.05rem; height: 1.05rem; }
14+
.callout-body { flex: 1; min-width: 0; }
15+
.callout-title { font-weight: 600; margin-bottom: 0.2rem; letter-spacing: -0.005em; color: var(--foreground); }
16+
.callout-content { color: var(--muted-foreground); }
17+
.callout-content > *:first-child { margin-top: 0; }
18+
.callout-content > *:last-child { margin-bottom: 0; }
19+
20+
.callout-info { border-color: color-mix(in oklch, var(--info) 30%, var(--border)); }
21+
.callout-info .callout-icon { color: var(--info); }
22+
.callout-warning { border-color: color-mix(in oklch, var(--warning) 35%, var(--border)); }
23+
.callout-warning .callout-icon { color: var(--warning); }
24+
.callout-danger, .callout-error { border-color: color-mix(in oklch, var(--error) 35%, var(--border)); }
25+
.callout-danger .callout-icon, .callout-error .callout-icon { color: var(--error); }
26+
.callout-tip, .callout-success { border-color: color-mix(in oklch, var(--success) 30%, var(--border)); }
27+
.callout-tip .callout-icon, .callout-success .callout-icon { color: var(--success); }
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
@namespace ShellDocs.Components.Content
2+
3+
@{
4+
var isLink = !string.IsNullOrEmpty(Href);
5+
var tag = isLink ? "a" : "div";
6+
}
7+
8+
@if (isLink)
9+
{
10+
<a class="doc-card" href="@Href" target="@(External ? "_blank" : null)" rel="@(External ? "noopener" : null)">
11+
@Body
12+
</a>
13+
}
14+
else
15+
{
16+
<div class="doc-card">
17+
@Body
18+
</div>
19+
}
20+
21+
@code {
22+
[Parameter] public string? Title { get; set; }
23+
[Parameter] public string? Description { get; set; }
24+
[Parameter] public string? Href { get; set; }
25+
[Parameter] public string? IconSvg { get; set; }
26+
[Parameter] public bool External { get; set; }
27+
[Parameter] public RenderFragment? ChildContent { get; set; }
28+
29+
private RenderFragment Body => __b =>
30+
{
31+
if (!string.IsNullOrEmpty(IconSvg))
32+
{
33+
__b.OpenElement(0, "span");
34+
__b.AddAttribute(1, "class", "doc-card-icon");
35+
__b.AddMarkupContent(2, IconSvg);
36+
__b.CloseElement();
37+
}
38+
__b.OpenElement(3, "div");
39+
__b.AddAttribute(4, "class", "doc-card-body");
40+
if (!string.IsNullOrEmpty(Title))
41+
{
42+
__b.OpenElement(5, "div");
43+
__b.AddAttribute(6, "class", "doc-card-title");
44+
__b.AddContent(7, Title);
45+
__b.CloseElement();
46+
}
47+
if (!string.IsNullOrEmpty(Description))
48+
{
49+
__b.OpenElement(8, "div");
50+
__b.AddAttribute(9, "class", "doc-card-description");
51+
__b.AddContent(10, Description);
52+
__b.CloseElement();
53+
}
54+
if (ChildContent is not null)
55+
{
56+
__b.OpenElement(11, "div");
57+
__b.AddAttribute(12, "class", "doc-card-content");
58+
__b.AddContent(13, ChildContent);
59+
__b.CloseElement();
60+
}
61+
__b.CloseElement();
62+
};
63+
}

0 commit comments

Comments
 (0)