Basics Done

This commit is contained in:
2026-04-13 15:08:49 +05:00
parent 9bef5813ae
commit 06ec22704b
75 changed files with 5036 additions and 2733 deletions
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<script>
// Synchronous dark-mode bootstrap — runs before first paint to prevent FOUC.
// Must stay in sync with the 'theme' localStorage key used by darkmode.js.
(function () {
try {
if (localStorage.getItem('theme') === 'dark')
document.documentElement.classList.add('dark');
} catch (e) { }
})();
</script>
<link rel="stylesheet" href="_content/Enciphered.Blazor.UIComponents/css/app.css" />
<link rel="stylesheet" href="@Assets["css/app.css"]" />
<link rel="stylesheet" href="@Assets["Enciphered.Blazor.UIComponents.Demo.styles.css"]" />
<ImportMap />
<link rel="icon" type="image/svg+xml" href="enci_white.svg" />
<HeadOutlet />
</head>
<body class="min-h-svh antialiased bg-background text-foreground">
<Routes />
<script src="_framework/blazor.web.js"></script>
<script type="module">
import { init as initDarkMode } from '/_content/Enciphered.Blazor.UIComponents/js/darkmode.js';
import { init as initSidebar } from '/_content/Enciphered.Blazor.UIComponents/js/sidebar.js';
initDarkMode();
initSidebar();
</script>
</body>
</html>
@@ -0,0 +1,87 @@
@inherits LayoutComponentBase
@using Enciphered.Blazor.UIComponents
<SidebarProvider DefaultOpen="true">
<Sidebar>
<SidebarHeader>
<div class="flex items-center gap-2 px-1 py-1.5 overflow-hidden">
<div class="flex shrink-0 aspect-square size-8 items-center justify-center">
<img src="enci_white.svg" alt="Enciphered" class="size-5 hidden dark:block" />
<img src="enci.svg" alt="Enciphered" class="size-5 block dark:hidden" />
</div>
<span class="truncate font-semibold text-sm group-data-[state=collapsed]:group-data-[mobile=false]:hidden">Enciphered UI</span>
</div>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel Label="Navigation" />
<SidebarGroupContent>
<SidebarMenuItem Href="/" Tooltip="Home">
<Icon>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</svg>
</Icon>
<ChildContent>Home</ChildContent>
</SidebarMenuItem>
<SidebarMenuItem Href="/counter" Tooltip="Counter">
<Icon>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
<line x1="12" x2="12" y1="8" y2="16" />
<line x1="8" x2="16" y1="12" y2="12" />
</svg>
</Icon>
<ChildContent>Counter</ChildContent>
</SidebarMenuItem>
<SidebarMenuItem Href="/weather" Tooltip="Weather">
<Icon>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" />
</svg>
</Icon>
<ChildContent>Weather</ChildContent>
</SidebarMenuItem>
<SidebarMenuItem Href="/forms" Tooltip="Forms">
<Icon>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
<path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z" />
</svg>
</Icon>
<ChildContent>Forms</ChildContent>
</SidebarMenuItem>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<div class="group-data-[state=collapsed]:group-data-[mobile=false]:hidden">
<SidebarSeparator />
<div class="px-3 py-2 text-xs text-sidebar-foreground/50 truncate">
&copy; 2026 Enciphered
</div>
</div>
</SidebarFooter>
</Sidebar>
<SidebarInset>
<header class="flex h-14 items-center gap-2 border-b border-border px-4">
<SidebarTrigger Class="md:hidden" />
<h1 class="text-sm font-medium">Demo App</h1>
<div class="ml-auto">
<ThemeToggle />
</div>
</header>
<div class="flex-1 p-4 md:p-6">
@Body
</div>
</SidebarInset>
</SidebarProvider>
@@ -0,0 +1,19 @@
@page "/counter"
@rendermode InteractiveServer
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
@@ -0,0 +1,36 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}
@@ -0,0 +1,126 @@
@page "/forms"
@rendermode InteractiveServer
@using System.ComponentModel.DataAnnotations
<PageTitle>Forms</PageTitle>
<div class="space-y-6 max-w-lg">
<div>
<h1 class="text-3xl font-bold tracking-tight">Forms Demo</h1>
<p class="text-muted-foreground">All input components with DataAnnotations validation.</p>
</div>
<EditForm EditContext="_editContext" OnSubmit="HandleSubmit" FormName="demo-form">
<DataAnnotationsValidator />
<div class="space-y-4">
<FormField Label="Full Name" For="name" Error="@GetError(nameof(Model.Name))">
<TextInput Id="name" @bind-Value="Model.Name" Placeholder="Jane Doe" data-testid="input-name" />
</FormField>
<FormField Label="Email" For="email" Error="@GetError(nameof(Model.Email))">
<TextInput Id="email" Type="email" @bind-Value="Model.Email" Placeholder="jane@example.com" data-testid="input-email" />
</FormField>
<FormField Label="Password" For="password" Error="@GetError(nameof(Model.Password))">
<TextInput Id="password" Type="password" @bind-Value="Model.Password" Placeholder="••••••••" data-testid="input-password" />
</FormField>
<FormField Label="Age" For="age" Error="@GetError(nameof(Model.Age))">
<NumberInput Id="age" @bind-Value="Model.Age" Placeholder="25" Min="0" Max="150" data-testid="input-age" />
</FormField>
<FormField Label="Birth Date" For="birthdate" Error="@GetError(nameof(Model.BirthDate))">
<DateInput Id="birthdate" @bind-Value="Model.BirthDate" data-testid="input-birthdate" />
</FormField>
<FormField Label="Preferred Time" For="preferredtime" Error="@GetError(nameof(Model.PreferredTime))">
<TimeInput Id="preferredtime" @bind-Value="Model.PreferredTime" data-testid="input-time" />
</FormField>
<FormField Label="Appointment" For="appointment" Error="@GetError(nameof(Model.Appointment))">
<DateTimeInput Id="appointment" @bind-Value="Model.Appointment" data-testid="input-appointment" />
</FormField>
<div class="flex gap-2 pt-2">
<Button Type="submit" data-testid="btn-submit">Submit</Button>
<Button Variant="@ButtonVariant.Outline" OnClick="HandleReset" data-testid="btn-reset">Reset</Button>
<Button Variant="@ButtonVariant.Destructive" Disabled="true" data-testid="btn-disabled">Disabled</Button>
</div>
</div>
</EditForm>
@if (_submitted)
{
<div data-testid="success-message"
class="rounded-md border border-input bg-card p-4 text-sm text-card-foreground">
<p class="font-medium">✓ Form submitted successfully</p>
<p class="text-muted-foreground mt-1">Name: @_submittedName</p>
</div>
}
</div>
@code {
private FormModel Model { get; set; } = new();
private EditContext _editContext = null!;
private bool _submitted;
private string _submittedName = "";
protected override void OnInitialized()
{
_editContext = new EditContext(Model);
}
private string? GetError(string fieldName)
{
var field = _editContext.Field(fieldName);
var messages = _editContext.GetValidationMessages(field);
return messages.FirstOrDefault();
}
private void HandleSubmit()
{
_submitted = false;
if (!_editContext.Validate())
return;
_submittedName = Model.Name!;
_submitted = true;
}
private void HandleReset()
{
Model = new();
_submitted = false;
_editContext = new EditContext(Model);
}
public class FormModel
{
[Required(ErrorMessage = "Name is required.")]
[StringLength(100, MinimumLength = 2, ErrorMessage = "Name must be 2100 characters.")]
public string? Name { get; set; }
[Required(ErrorMessage = "Email is required.")]
[EmailAddress(ErrorMessage = "Invalid email address.")]
public string? Email { get; set; }
[Required(ErrorMessage = "Password is required.")]
[StringLength(64, MinimumLength = 8, ErrorMessage = "Password must be 864 characters.")]
public string? Password { get; set; }
[Required(ErrorMessage = "Age is required.")]
[Range(1, 150, ErrorMessage = "Age must be between 1 and 150.")]
public double? Age { get; set; }
[Required(ErrorMessage = "Birth date is required.")]
public DateOnly? BirthDate { get; set; }
[Required(ErrorMessage = "Preferred time is required.")]
public TimeOnly? PreferredTime { get; set; }
[Required(ErrorMessage = "Appointment is required.")]
public DateTime? Appointment { get; set; }
}
}
@@ -0,0 +1,8 @@
@page "/"
<PageTitle>Home</PageTitle>
<div class="space-y-4">
<h1 class="text-3xl font-bold tracking-tight">Welcome</h1>
<p class="text-muted-foreground">This is the Enciphered Blazor UI Components demo app.</p>
</div>
@@ -0,0 +1,64 @@
@page "/weather"
@attribute [StreamRendering]
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th aria-label="Temperature in Celsius">Temp. (C)</th>
<th aria-label="Temperature in Farenheit">Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}
@@ -0,0 +1,5 @@
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Enciphered.Blazor.UIComponents.SidebarProvider).Assembly }">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
</Found>
</Router>
@@ -0,0 +1,11 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using Enciphered.Blazor.UIComponents.Demo
@using Enciphered.Blazor.UIComponents.Demo.Components
@using Enciphered.Blazor.UIComponents