Ported from the owner's b2b.next and adapted from MudBlazor to the daisyUI
components:
- LocalizableComponentBase provides CascadingState, Navigation, Toast,
IsLoading/IsDefaultDisabled, WhenOnline/StartOnlinePolling, and after-render
jobs that actually run.
- CascadingState starts with initialised state and polls the online status
with a PeriodicTimer. It reports errors through ILoggingService plus a toast,
and disposes what it holds.
- New pieces:
- ToastService + ToastHost, the snackbar, with AddResponseError/Success/
Warning/Exception in SUtility;
- ErrorViewer, which shows the validation messages plus AdditionalError(s);
- LoggingService, which writes to the console and the IndexedDB client log.
- HttpService is rebuilt and registered:
- auth levels Get/Post/Delete, *Anon and *TotallyAnon;
- retry with backoff;
- classified failures (FailureCodes) instead of exceptions;
- HTTP 410 on cancel;
- the "default" client.
ReadWebResult and DefaultReadFromJsonAsync read its responses.
- ClientJsonContext is source-generated, and SUtility.DefaultSerializer and
Blazored.LocalStorage use it.
- WebResult gains a C# 14 IsInvalid extension property.
The code follows the imported rules:
- Every service and async helper takes a CancellationToken last.
- Primary constructors replace constructor-plus-fields.
- MessagesService and IStorage return Task<WebResult> / Task<List<T>>.
MessagesService is still mock, building its results in memory; the seed
feed and thread moved to Faker.
- Index and ExpandMessage follow the page pattern: an offline guard with
WhenOnline, LoadData, and early returns with toasts on the results they used
to ignore. ExpandMessage reloads on route change and shows "not found"
instead of a null message.
- Model and state collections are [], and the fake identity defaults moved
out of the models.
- Components:
- no ?. chains over initialised state;
- [Inject] instead of @inject;
- IsDefaultDisabled on the actions;
- autocomplete on every text input;
- Localizer[key, args] instead of string.Format;
- the localizer no longer leaks its inner _pLocalizer.
- Settings → General gains a language picker (English/Italiano), and
SetDefaultCulture limits the culture to those two.
Bugs fixed along the way:
- A compose with untouched (null) content passed the empty check.
- File errors were joined with <br/> and rendered as markup.
- ClientLogs.ErrorMessage() threw on warnings.
- CoalescingStringLocalizer[key, args] showed a raw "{0}" for missing keys.
- AppStatusService exposed a call to a JS function that doesn't exist.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
223 lines
9.3 KiB
Plaintext
223 lines
9.3 KiB
Plaintext
@* Styleguide: the Layout components (Divider, Drawer, Footer, Hero, Indicator, Join, Mask, Stack). *@
|
|
<div class="grid gap-8 lg:grid-cols-2">
|
|
<section class="flex flex-col gap-3">
|
|
<h3 class="text-lg font-bold">DDivider</h3>
|
|
<div class="flex flex-col">
|
|
<div class="rounded-box bg-base-200 p-3">Above</div>
|
|
<DDivider>OR</DDivider>
|
|
<div class="rounded-box bg-base-200 p-3">Below</div>
|
|
<DDivider />
|
|
@foreach (var color in Enum.GetValues<DColor>().Skip(1))
|
|
{
|
|
<DDivider Color="color">@color</DDivider>
|
|
}
|
|
<DDivider Placement="DDividerPlacement.Start">Start</DDivider>
|
|
<DDivider Placement="DDividerPlacement.End" Direction="DDividerDirection.Vertical">End (vertical)</DDivider>
|
|
</div>
|
|
<div class="flex h-24">
|
|
<div class="grid grow place-items-center rounded-box bg-base-200">Left</div>
|
|
<DDivider Direction="DDividerDirection.Horizontal" Color="DColor.Primary">OR</DDivider>
|
|
<div class="grid grow place-items-center rounded-box bg-base-200">Right</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="flex flex-col gap-3">
|
|
<h3 class="text-lg font-bold">DDrawer</h3>
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<DButton OnClick="() => drawerOpen = true">Open (@(drawerOpen ? "open" : "closed"))</DButton>
|
|
<label class="flex items-center gap-2 text-sm"><input type="checkbox" class="toggle toggle-sm" @bind="drawerEnd" /> End</label>
|
|
</div>
|
|
<p class="text-sm opacity-60">The box contains the fixed side (transform-gpu); the overlay, Escape and the side's buttons close it.</p>
|
|
<div class="h-72 transform-gpu overflow-hidden rounded-box bg-base-200">
|
|
<DDrawer @bind-IsOpen="drawerOpen" End="drawerEnd" ToggleId="styleguide-drawer" class="h-full"
|
|
ContentClass="flex flex-col items-center justify-center gap-2">
|
|
<ChildContent>
|
|
<p>Page content</p>
|
|
<label for="styleguide-drawer" class="btn btn-primary drawer-button">Open with a label</label>
|
|
</ChildContent>
|
|
<Side>
|
|
<ul class="menu min-h-full w-56 bg-base-100 p-4">
|
|
<li><button type="button" @onclick="() => drawerOpen = false"><i class="ion-md-home"></i> Home</button></li>
|
|
<li><button type="button" @onclick="() => drawerOpen = false"><i class="ion-md-settings"></i> Settings</button></li>
|
|
</ul>
|
|
</Side>
|
|
</DDrawer>
|
|
</div>
|
|
<p class="text-sm opacity-60">OpenFrom="Lg": always open beside the content from lg up.</p>
|
|
<div class="h-48 transform-gpu overflow-hidden rounded-box bg-base-200">
|
|
<DDrawer @bind-IsOpen="drawerLgOpen" OpenFrom="DDrawerOpenFrom.Lg" class="h-full" ContentClass="p-4">
|
|
<ChildContent>
|
|
<DButton Size="DSize.Sm" class="lg:hidden" OnClick="() => drawerLgOpen = true">Open</DButton>
|
|
<p>Content</p>
|
|
</ChildContent>
|
|
<Side>
|
|
<ul class="menu min-h-full w-40 bg-base-100 p-2">
|
|
<li><a>Item 1</a></li>
|
|
<li><a>Item 2</a></li>
|
|
</ul>
|
|
</Side>
|
|
</DDrawer>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="flex flex-col gap-3 lg:col-span-2">
|
|
<h3 class="text-lg font-bold">DFooter</h3>
|
|
<DFooter Direction="DFooterDirection.HorizontalFromSm" class="rounded-box bg-base-200 p-10">
|
|
<aside>
|
|
<i class="ion-md-code-working text-4xl"></i>
|
|
<p>decePub<br />Responsive: a row from sm up</p>
|
|
</aside>
|
|
<DFooterNav>
|
|
<Heading>Services</Heading>
|
|
<ChildContent>
|
|
<a class="link link-hover">Branding</a>
|
|
<a class="link link-hover">Design</a>
|
|
</ChildContent>
|
|
</DFooterNav>
|
|
<DFooterNav>
|
|
<Heading>Company</Heading>
|
|
<ChildContent>
|
|
<a class="link link-hover">About us</a>
|
|
<a class="link link-hover">Contact</a>
|
|
</ChildContent>
|
|
</DFooterNav>
|
|
</DFooter>
|
|
<DFooter Center="true" Direction="DFooterDirection.Horizontal" class="rounded-box bg-base-200 p-6">
|
|
<DFooterNav class="grid-flow-col gap-4">
|
|
<a class="link link-hover">About</a>
|
|
<a class="link link-hover">Contact</a>
|
|
<a class="link link-hover">Jobs</a>
|
|
</DFooterNav>
|
|
<aside><p>Centered, horizontal</p></aside>
|
|
</DFooter>
|
|
</section>
|
|
|
|
<section class="flex flex-col gap-3">
|
|
<h3 class="text-lg font-bold">DHero</h3>
|
|
<DHero class="rounded-box bg-base-200" ContentClass="text-center">
|
|
<div class="max-w-md">
|
|
<h1 class="text-3xl font-bold">Hello there</h1>
|
|
<p class="py-4">A centered hero.</p>
|
|
<DButton Color="DColor.Primary">Get started</DButton>
|
|
</div>
|
|
</DHero>
|
|
<DHero BackgroundImage="imgs/styleguide/scene-5.svg" Overlay="true" class="min-h-64 rounded-box overflow-hidden"
|
|
ContentClass="text-neutral-content text-center">
|
|
<div class="max-w-md">
|
|
<h1 class="text-3xl font-bold">With an overlay</h1>
|
|
<p class="py-4">Background image and hero-overlay.</p>
|
|
</div>
|
|
</DHero>
|
|
<DHero class="rounded-box bg-base-200" ContentClass="flex-col lg:flex-row">
|
|
<img src="imgs/styleguide/scene-1.svg" class="max-w-32 rounded-lg shadow-2xl" alt="" />
|
|
<div>
|
|
<h1 class="text-2xl font-bold">With a figure</h1>
|
|
<p class="py-2">Column on small screens, row from lg up.</p>
|
|
</div>
|
|
</DHero>
|
|
</section>
|
|
|
|
<section class="flex flex-col gap-3">
|
|
<h3 class="text-lg font-bold">DIndicator</h3>
|
|
<div class="flex flex-wrap items-center gap-8 p-4">
|
|
<DIndicator>
|
|
<DIndicatorItem class="badge badge-secondary">new</DIndicatorItem>
|
|
<div class="grid h-24 w-24 place-items-center rounded-box bg-base-300">content</div>
|
|
</DIndicator>
|
|
<DIndicator>
|
|
<DIndicatorItem class="status status-success"></DIndicatorItem>
|
|
<DButton>Inbox</DButton>
|
|
</DIndicator>
|
|
<DIndicator>
|
|
<DIndicatorItem Vertical="DIndicatorVertical.Bottom" class="badge badge-primary">99+</DIndicatorItem>
|
|
<DButton Shape="DButtonShape.Circle"><i class="ion-md-notifications"></i></DButton>
|
|
</DIndicator>
|
|
</div>
|
|
<DIndicator class="m-4">
|
|
<DIndicatorItem Horizontal="DIndicatorHorizontal.Start" class="badge">↖︎</DIndicatorItem>
|
|
<DIndicatorItem Horizontal="DIndicatorHorizontal.Center" class="badge">↑</DIndicatorItem>
|
|
<DIndicatorItem class="badge">↗︎</DIndicatorItem>
|
|
<DIndicatorItem Horizontal="DIndicatorHorizontal.Start" Vertical="DIndicatorVertical.Middle" class="badge">←</DIndicatorItem>
|
|
<DIndicatorItem Horizontal="DIndicatorHorizontal.Center" Vertical="DIndicatorVertical.Middle" class="badge">●</DIndicatorItem>
|
|
<DIndicatorItem Vertical="DIndicatorVertical.Middle" class="badge">→</DIndicatorItem>
|
|
<DIndicatorItem Horizontal="DIndicatorHorizontal.Start" Vertical="DIndicatorVertical.Bottom" class="badge">↙︎</DIndicatorItem>
|
|
<DIndicatorItem Horizontal="DIndicatorHorizontal.Center" Vertical="DIndicatorVertical.Bottom" class="badge">↓</DIndicatorItem>
|
|
<DIndicatorItem Vertical="DIndicatorVertical.Bottom" class="badge">↘︎</DIndicatorItem>
|
|
<div class="grid h-32 w-60 place-items-center rounded-box bg-base-300">all placements</div>
|
|
</DIndicator>
|
|
</section>
|
|
|
|
<section class="flex flex-col gap-3">
|
|
<h3 class="text-lg font-bold">DJoin</h3>
|
|
<DJoin>
|
|
<DButton class="join-item">One</DButton>
|
|
<DButton class="join-item" Active="true">Two</DButton>
|
|
<DButton class="join-item">Three</DButton>
|
|
</DJoin>
|
|
<DJoin Direction="DDirection.Vertical" class="w-fit">
|
|
<DButton class="join-item">Top</DButton>
|
|
<DButton class="join-item">Middle</DButton>
|
|
<DButton class="join-item">Bottom</DButton>
|
|
</DJoin>
|
|
<DJoin Direction="DDirection.Vertical" class="lg:join-horizontal w-fit">
|
|
<DButton class="join-item">Vertical</DButton>
|
|
<DButton class="join-item">then horizontal</DButton>
|
|
<DButton class="join-item">from lg</DButton>
|
|
</DJoin>
|
|
<DJoin>
|
|
<input autocomplete="off" class="input join-item" placeholder="Email" />
|
|
<select class="select join-item w-28">
|
|
<option>Filter</option>
|
|
<option>Sci-fi</option>
|
|
</select>
|
|
<DButton class="join-item" Color="DColor.Primary"><i class="ion-md-search"></i></DButton>
|
|
</DJoin>
|
|
</section>
|
|
|
|
<section class="flex flex-col gap-3">
|
|
<h3 class="text-lg font-bold">DMask</h3>
|
|
<div class="flex flex-wrap gap-3">
|
|
@foreach (var shape in Enum.GetValues<DMaskShape>().Skip(1))
|
|
{
|
|
<div class="flex flex-col items-center gap-1 text-xs">
|
|
<DMask Shape="shape" Src="imgs/styleguide/scene-3.svg" alt="@shape" class="h-16 w-16" />
|
|
@shape
|
|
</div>
|
|
}
|
|
</div>
|
|
<div class="flex items-center gap-1">
|
|
<DMask Shape="DMaskShape.Star2" Half="DMaskHalf.First" class="h-10 w-5 bg-warning" />
|
|
<DMask Shape="DMaskShape.Star2" Half="DMaskHalf.Second" class="h-10 w-5 bg-warning" />
|
|
<DMask Shape="DMaskShape.Heart" class="ml-4 grid h-16 w-16 place-items-center bg-error text-error-content">div</DMask>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="flex flex-col gap-3">
|
|
<h3 class="text-lg font-bold">DStack</h3>
|
|
<div class="flex flex-wrap gap-10 p-4">
|
|
@foreach (var placement in Enum.GetValues<DStackPlacement>())
|
|
{
|
|
<div class="flex flex-col items-center gap-3 text-sm">
|
|
<DStack Placement="placement" class="h-20 w-28">
|
|
<div class="grid place-content-center rounded-box bg-primary text-primary-content">1</div>
|
|
<div class="grid place-content-center rounded-box bg-accent text-accent-content">2</div>
|
|
<div class="grid place-content-center rounded-box bg-secondary text-secondary-content">3</div>
|
|
</DStack>
|
|
@placement
|
|
</div>
|
|
}
|
|
<DStack class="w-32">
|
|
<img src="imgs/styleguide/scene-2.svg" class="rounded-box" alt="" />
|
|
<img src="imgs/styleguide/scene-3.svg" class="rounded-box" alt="" />
|
|
<img src="imgs/styleguide/scene-4.svg" class="rounded-box" alt="" />
|
|
</DStack>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
@code {
|
|
bool drawerOpen;
|
|
bool drawerEnd;
|
|
bool drawerLgOpen;
|
|
}
|