Skip to content

Commit 2c732cb

Browse files
authored
Spinner component with documentation and demo examples (#90)
* Add Spinner component with documentation and demo examples * Add Spinner component navigation links to demo pages and update documentation rules
1 parent af9e768 commit 2c732cb

23 files changed

Lines changed: 662 additions & 5 deletions

AGENTS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ This file is the entry point for development agents working in this repository.
2222
Rule: if a library change affects public parameters, events, rendered output, install/setup guidance, or user-facing behavior, update the matching demo/docs content in the same change set.
2323
Example: a change under `BlazorExpress.Bulma/Components/Button/` should trigger review of `BlazorExpress.Bulma.Demo.RCL/Pages/Docs/Button/` and `Pages/Demos/Button/`.
2424

25+
- Align demo navigation sources for every new component implementation.
26+
Rule: when you add a new component or first-time demo/docs area, wire it into both demo navigation sources in the same change set: `DemoPageLinkUtil` for the home/demo catalog and `DemosMainLayout` for the demos sidebar menu.
27+
Example: adding `Spinner` requires both the `DemoPageLinkUtil.GetDemosLinks()` entry and the `DemosMainLayout` `ELEMENTS` sidebar link.
28+
2529
- Keep ownership boundaries intact.
2630
Rule: reusable component code and reusable static assets belong in `BlazorExpress.Bulma/`; docs, demos, screenshots, demo CSS/JS, and content assets belong in `BlazorExpress.Bulma.Demo.RCL/`; host startup wiring belongs in the server or WebAssembly host projects.
2731

BlazorExpress.Bulma.Demo.RCL/Constants/DemoRouteConstants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public static class DemoRouteConstants
3434
public const string Demos_Image_Documentation = Demos_Prefix + "/image";
3535
public const string Demos_Notification_Documentation = Demos_Prefix + "/notification";
3636
public const string Demos_ProgressBar_Documentation = Demos_Prefix + "/progress-bar";
37+
public const string Demos_Spinner_Documentation = Demos_Prefix + "/spinner";
3738
public const string Demos_Table_Documentation = Demos_Prefix + "/table";
3839
public const string Demos_Tags_Documentation = Demos_Prefix + "/tags";
3940
public const string Demos_Title_Documentation = Demos_Prefix + "/title";
@@ -121,6 +122,7 @@ public static class DemoRouteConstants
121122
public const string Docs_Image_Documentation = Docs_Prefix + "/image";
122123
public const string Docs_Notification_Documentation = Docs_Prefix + "/notification";
123124
public const string Docs_ProgressBar_Documentation = Docs_Prefix + "/progress-bar";
125+
public const string Docs_Spinner_Documentation = Docs_Prefix + "/spinner";
124126
public const string Docs_Table_Documentation = Docs_Prefix + "/table";
125127
public const string Docs_Tags_Documentation = Docs_Prefix + "/tags";
126128
public const string Docs_Title_Documentation = Docs_Prefix + "/title";

BlazorExpress.Bulma.Demo.RCL/Layout/DemosMainLayout.razor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ private HashSet<LinkGroup> GetLinkGroups()
8383
new Link { Href = DemoRouteConstants.Demos_Image_Documentation, Text = "Image" },
8484
new Link { Href = DemoRouteConstants.Demos_Notification_Documentation, Text = "Notification" },
8585
new Link { Href = DemoRouteConstants.Demos_ProgressBar_Documentation, Text = "Progress Bar" },
86+
new Link { Href = DemoRouteConstants.Demos_Spinner_Documentation, Text = "Spinner" },
8687
new Link { Href = DemoRouteConstants.Demos_Tags_Documentation, Text = "Tags" },
8788
]
8889
});

BlazorExpress.Bulma.Demo.RCL/Layout/DocsMainLayout.razor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ private HashSet<LinkGroup> GetLinkGroups()
9797
new Link { Href = DemoRouteConstants.Docs_Image_Documentation, Text = "Image" },
9898
new Link { Href = DemoRouteConstants.Docs_Notification_Documentation, Text = "Notification" },
9999
new Link { Href = DemoRouteConstants.Docs_ProgressBar_Documentation, Text = "Progress Bar" },
100+
new Link { Href = DemoRouteConstants.Docs_Spinner_Documentation, Text = "Spinner" },
100101
new Link { Href = DemoRouteConstants.Docs_Tags_Documentation, Text = "Tags" },
101102
]
102103
});
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
@attribute [Route(pageUrl)]
2+
@layout DemosMainLayout
3+
4+
<PageMetaTags PageUrl="@pageUrl" Title="@metaTitle" Description="@metaDescription" ImageUrl="@imageUrl" />
5+
6+
<PageHero Title="@pageTitle">
7+
<SubTitleTemplate>
8+
@((MarkupString)pageDescription)
9+
</SubTitleTemplate>
10+
</PageHero>
11+
12+
<DocsLink Href="@DemoRouteConstants.Docs_Spinner_Documentation" />
13+
14+
<Section Class="p-0" Size="HeadingSize.H3" Name="How it works" PageUrl="@pageUrl" Link="how-it-works">
15+
<Block>
16+
The <strong>Spinner</strong> component shows a compact loading indicator for asynchronous work such as fetching data, saving changes, or waiting for background tasks.
17+
<br /><br />
18+
<strong>How to use:</strong>
19+
<div class="content mb-2">
20+
<ol>
21+
<li>Add the <code>Spinner</code> component to your page.</li>
22+
<li>Optionally set the <code>Color</code> property to a Bulma semantic color that matches the surrounding UI.</li>
23+
<li>Keep the default <code>Label</code> or provide your own accessible loading text for screen readers.</li>
24+
</ol>
25+
</div>
26+
This demo shows the default spinner in a typical inline loading scenario.
27+
</Block>
28+
<Demo Type="typeof(Spinner_Demo_01_How_it_works)" Tabs="true" />
29+
</Section>
30+
31+
<Section Class="p-0" Size="HeadingSize.H3" Name="Types" PageUrl="@pageUrl" Link="types">
32+
<Block>
33+
The <strong>Spinner</strong> component supports <code>SpinnerType.Border</code>, <code>SpinnerType.Grow</code>, and <code>SpinnerType.Dots</code>, letting you choose the visual style that best fits the page.
34+
<br /><br />
35+
<strong>How to use:</strong>
36+
<div class="content mb-2">
37+
<ol>
38+
<li>Use the default <code>SpinnerType.Border</code> for the standard spinner style.</li>
39+
<li>Set <code>Type="SpinnerType.Grow"</code> to switch to the grow variant.</li>
40+
<li>Set <code>Type="SpinnerType.Dots"</code> to render a dotted loading indicator.</li>
41+
</ol>
42+
</div>
43+
This demo compares the supported spinner types side by side.
44+
</Block>
45+
<Demo Type="typeof(Spinner_Demo_02_Types)" Tabs="true" />
46+
</Section>
47+
48+
<Section Class="p-0" Size="HeadingSize.H3" Name="Colors" PageUrl="@pageUrl" Link="colors">
49+
<Block>
50+
The <strong>Spinner</strong> component supports Bulma semantic colors so the loading indicator can align with your status messaging or theme.
51+
<br /><br />
52+
<strong>How to use:</strong>
53+
<div class="content mb-2">
54+
<ol>
55+
<li>Set the <code>Color</code> property to a Bulma semantic value such as <code>Primary</code>, <code>Info</code>, <code>Success</code>, or <code>Danger</code>.</li>
56+
<li>Choose a color that stays visible against the spinner's background.</li>
57+
</ol>
58+
</div>
59+
This demo illustrates Bulma semantic color options for the border, grow, and dots spinner types.
60+
</Block>
61+
<Demo Type="typeof(Spinner_Demo_03_Colors)" Tabs="true" />
62+
</Section>
63+
64+
<Section Class="p-0" Size="HeadingSize.H3" Name="Size" PageUrl="@pageUrl" Link="size">
65+
<Block>
66+
The <strong>Spinner</strong> component can be displayed in multiple Bulma size classes to suit inline text, cards, panels, or larger loading states.
67+
<br /><br />
68+
<strong>How to use:</strong>
69+
<div class="content mb-2">
70+
<ol>
71+
<li>Set the <code>Size</code> property to <code>SpinnerSize.Small</code>, <code>SpinnerSize.Normal</code>, <code>SpinnerSize.Medium</code>, or <code>SpinnerSize.Large</code> to follow the Bulma size scale.</li>
72+
<li>Leave <code>Size</code> unset to use the default spinner size.</li>
73+
</ol>
74+
</div>
75+
This demo shows the supported Bulma size options for the border, grow, and dots spinner types.
76+
</Block>
77+
<Demo Type="typeof(Spinner_Demo_04_Sizes)" Tabs="true" />
78+
</Section>
79+
80+
<Section Class="p-0" Size="HeadingSize.H3" Name="IsVisible" PageUrl="@pageUrl" Link="isvisible">
81+
<Block>
82+
The <strong>IsVisible</strong> property lets you control whether the spinner is rendered, which is useful for toggling loading states from your page logic.
83+
<br /><br />
84+
<strong>How to use:</strong>
85+
<div class="content mb-2">
86+
<ol>
87+
<li>Bind the <code>IsVisible</code> property to a boolean field.</li>
88+
<li>Update that field when your loading operation starts or ends.</li>
89+
</ol>
90+
</div>
91+
This demo shows how to show, hide, and toggle a spinner through the <code>IsVisible</code> parameter.
92+
</Block>
93+
<Demo Type="typeof(Spinner_Demo_05_IsVisible)" Tabs="true" />
94+
</Section>
95+
96+
<Section Class="p-0" Size="HeadingSize.H3" Name="Methods" PageUrl="@pageUrl" Link="methods">
97+
<Block>
98+
The <strong>Spinner</strong> component exposes <code>Show()</code> and <code>Hide()</code> methods for instance-based control when you are working with a component reference.
99+
<br /><br />
100+
<strong>How to use:</strong>
101+
<div class="content mb-2">
102+
<ol>
103+
<li>Capture the component with <code>@@ref</code>.</li>
104+
<li>Call <code>Show()</code> or <code>Hide()</code> on that reference when you want to update visibility imperatively.</li>
105+
</ol>
106+
</div>
107+
This demo shows direct control of a spinner instance through its public methods.
108+
</Block>
109+
<Demo Type="typeof(Spinner_Demo_06_Methods)" Tabs="true" />
110+
</Section>
111+
112+
@code {
113+
private const string componentName = nameof(Spinner);
114+
private const string pageUrl = DemoRouteConstants.Demos_Spinner_Documentation;
115+
private const string pageTitle = componentName;
116+
private const string pageDescription = $"Use the <code>{componentName}</code> component to indicate loading states with Bulma-aligned colors, Bulma size classes, and built-in visibility controls.";
117+
private const string metaTitle = $"Blazor {componentName} Component";
118+
private const string metaDescription = $"Use the {componentName} component to indicate loading states with Bulma-aligned colors, Bulma size classes, and built-in visibility controls.";
119+
private const string imageUrl = "";
120+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<div class="is-flex is-align-items-center" style="gap: 1rem;">
2+
<Spinner Color="SpinnerColor.Primary" />
3+
<span>Loading dashboard metrics...</span>
4+
</div>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<div class="is-flex is-align-items-center is-flex-wrap-wrap" style="gap: 1.5rem;">
2+
<div class="has-text-centered">
3+
<Spinner Color="SpinnerColor.Primary" />
4+
<div class="mt-2">Border</div>
5+
</div>
6+
<div class="has-text-centered">
7+
<Spinner Color="SpinnerColor.Info" Type="SpinnerType.Grow" />
8+
<div class="mt-2">Grow</div>
9+
</div>
10+
<div class="has-text-centered">
11+
<Spinner Color="SpinnerColor.Success" Type="SpinnerType.Dots" />
12+
<div class="mt-2">Dots</div>
13+
</div>
14+
</div>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<div class="content">
2+
<p><strong>Border</strong></p>
3+
<div class="is-flex is-align-items-center is-flex-wrap-wrap mb-4" style="gap: 1rem;">
4+
<Spinner Color="SpinnerColor.Primary" />
5+
<Spinner Color="SpinnerColor.Link" />
6+
<Spinner Color="SpinnerColor.Info" />
7+
<Spinner Color="SpinnerColor.Success" />
8+
<Spinner Color="SpinnerColor.Warning" />
9+
<Spinner Color="SpinnerColor.Danger" />
10+
<Spinner Color="SpinnerColor.Dark" />
11+
</div>
12+
13+
<p><strong>Grow</strong></p>
14+
<div class="is-flex is-align-items-center is-flex-wrap-wrap mb-4" style="gap: 1rem;">
15+
<Spinner Color="SpinnerColor.Primary" Type="SpinnerType.Grow" />
16+
<Spinner Color="SpinnerColor.Link" Type="SpinnerType.Grow" />
17+
<Spinner Color="SpinnerColor.Info" Type="SpinnerType.Grow" />
18+
<Spinner Color="SpinnerColor.Success" Type="SpinnerType.Grow" />
19+
<Spinner Color="SpinnerColor.Warning" Type="SpinnerType.Grow" />
20+
<Spinner Color="SpinnerColor.Danger" Type="SpinnerType.Grow" />
21+
<Spinner Color="SpinnerColor.Dark" Type="SpinnerType.Grow" />
22+
</div>
23+
24+
<p><strong>Dots</strong></p>
25+
<div class="is-flex is-align-items-center is-flex-wrap-wrap" style="gap: 1rem;">
26+
<Spinner Color="SpinnerColor.Primary" Type="SpinnerType.Dots" />
27+
<Spinner Color="SpinnerColor.Link" Type="SpinnerType.Dots" />
28+
<Spinner Color="SpinnerColor.Info" Type="SpinnerType.Dots" />
29+
<Spinner Color="SpinnerColor.Success" Type="SpinnerType.Dots" />
30+
<Spinner Color="SpinnerColor.Warning" Type="SpinnerType.Dots" />
31+
<Spinner Color="SpinnerColor.Danger" Type="SpinnerType.Dots" />
32+
<Spinner Color="SpinnerColor.Dark" Type="SpinnerType.Dots" />
33+
</div>
34+
</div>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<div class="content">
2+
<p><strong>Border</strong></p>
3+
<div class="is-flex is-align-items-center is-flex-wrap-wrap mb-4" style="gap: 1rem;">
4+
<Spinner Color="SpinnerColor.Primary" Size="SpinnerSize.Small" />
5+
<Spinner Color="SpinnerColor.Primary" Size="SpinnerSize.Normal" />
6+
<Spinner Color="SpinnerColor.Primary" Size="SpinnerSize.Medium" />
7+
<Spinner Color="SpinnerColor.Primary" Size="SpinnerSize.Large" />
8+
</div>
9+
10+
<p><strong>Grow</strong></p>
11+
<div class="is-flex is-align-items-center is-flex-wrap-wrap mb-4" style="gap: 1rem;">
12+
<Spinner Color="SpinnerColor.Primary" Type="SpinnerType.Grow" Size="SpinnerSize.Small" />
13+
<Spinner Color="SpinnerColor.Primary" Type="SpinnerType.Grow" Size="SpinnerSize.Normal" />
14+
<Spinner Color="SpinnerColor.Primary" Type="SpinnerType.Grow" Size="SpinnerSize.Medium" />
15+
<Spinner Color="SpinnerColor.Primary" Type="SpinnerType.Grow" Size="SpinnerSize.Large" />
16+
</div>
17+
18+
<p><strong>Dots</strong></p>
19+
<div class="is-flex is-align-items-center is-flex-wrap-wrap" style="gap: 1rem;">
20+
<Spinner Color="SpinnerColor.Primary" Type="SpinnerType.Dots" Size="SpinnerSize.Small" />
21+
<Spinner Color="SpinnerColor.Primary" Type="SpinnerType.Dots" Size="SpinnerSize.Normal" />
22+
<Spinner Color="SpinnerColor.Primary" Type="SpinnerType.Dots" Size="SpinnerSize.Medium" />
23+
<Spinner Color="SpinnerColor.Primary" Type="SpinnerType.Dots" Size="SpinnerSize.Large" />
24+
</div>
25+
</div>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<div class="is-flex is-align-items-center is-flex-wrap-wrap mb-4" style="gap: 0.75rem;">
2+
<Button Color="ButtonColor.Primary" Size="ButtonSize.Small" @onclick="ShowSpinner">Show spinner</Button>
3+
<Button Color="ButtonColor.Warning" Size="ButtonSize.Small" @onclick="HideSpinner">Hide spinner</Button>
4+
<Button Size="ButtonSize.Small" @onclick="ToggleSpinner">Toggle</Button>
5+
</div>
6+
7+
<div class="is-flex is-align-items-center" style="gap: 1rem;">
8+
<Spinner Color="SpinnerColor.Success" IsVisible="@isVisible" />
9+
<span>Current <code>IsVisible</code>: <strong>@isVisible</strong></span>
10+
</div>
11+
12+
@code {
13+
private bool isVisible = true;
14+
15+
private void HideSpinner() => isVisible = false;
16+
17+
private void ShowSpinner() => isVisible = true;
18+
19+
private void ToggleSpinner() => isVisible = !isVisible;
20+
}

0 commit comments

Comments
 (0)