Add D-prefixed Blazor wrappers for every daisyUI 5 component

Components/Daisy/ wraps all 69 components in daisyUI's llms.txt, grouped by
daisyUI's categories (Actions, DataDisplay, Navigation, Feedback, DataInput,
Layout, Mockup), with part components where daisyUI has repeating parts (DTab,
DMenuItem, DTimelineItem, ...).
- Each enum maps to whole literal class names in a switch, so Tailwind's scanner
  generates them. Nothing is concatenated.
- Value-holding fields derive from DInputBase<TValue>, Blazor's InputBase, so
  @bind-Value and EditForm validation work. Invalid fields get the component's
  *-error class.
- Open and active state lives in C# (@bind-IsOpen, @bind-Active) and is rendered
  with daisyUI's *-open modifiers. DDropdown adds an outside-click layer and
  Escape. No component uses JS interop.
- The one exception is DCalendar, which wraps the Cally web component, vendored
  as wwwroot/vendor/cally.js (0.9.2, MIT).

/styleguide (not linked) renders every component in all its colours, sizes and
states, with a theme and hue picker that previews without saving.

neo.css follows what the styleguide showed:
- select keeps daisyUI's arrow;
- skeleton-text isn't sunk;
- the phone mockup and dock are raised;
- active menu items and OTP digits are inset;
- a joined accordion is raised as one block.
The primary, secondary and accent colours are deeper, so they still read as
text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-03 12:01:42 +02:00
1 parent 8e03d12a3e
commit 90e4b9200c
127 files changed
+6961 -16

No files matched your search

+74
View File
@@ -0,0 +1,74 @@
@inherits DComponentBase
@* daisyUI Button: https://daisyui.com/components/button/ . Renders an <a> when Href is set. *@
@if (Href is null)
{
<button @attributes="AdditionalAttributes" type="@Type" class="@CssClass" disabled="@Disabled" @onclick="OnClick">
@ChildContent
</button>
}
else
{
<a @attributes="AdditionalAttributes" href="@(Disabled ? null : Href)" class="@CssClass" role="button"
aria-disabled="@(Disabled ? "true" : null)" tabindex="@(Disabled ? "-1" : null)" @onclick="OnClick">
@ChildContent
</a>
}
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DColor Color { get; set; }
[Parameter] public DButtonVariant Variant { get; set; }
[Parameter] public DSize Size { get; set; }
[Parameter] public DButtonShape Shape { get; set; }
/// <summary>Pressed look (btn-active); the neo theme renders it inset.</summary>
[Parameter] public bool Active { get; set; }
[Parameter] public bool Disabled { get; set; }
[Parameter] public string Type { get; set; } = "button";
[Parameter] public string Href { get; set; }
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
string CssClass => Cx("btn", ColorClass, VariantClass, SizeClass, ShapeClass,
Active ? "btn-active" : null, Disabled && Href is not null ? "btn-disabled" : null, Class);
string ColorClass => Color switch
{
DColor.Neutral => "btn-neutral",
DColor.Primary => "btn-primary",
DColor.Secondary => "btn-secondary",
DColor.Accent => "btn-accent",
DColor.Info => "btn-info",
DColor.Success => "btn-success",
DColor.Warning => "btn-warning",
DColor.Error => "btn-error",
_ => null
};
string VariantClass => Variant switch
{
DButtonVariant.Outline => "btn-outline",
DButtonVariant.Dash => "btn-dash",
DButtonVariant.Soft => "btn-soft",
DButtonVariant.Ghost => "btn-ghost",
DButtonVariant.Link => "btn-link",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "btn-xs",
DSize.Sm => "btn-sm",
DSize.Lg => "btn-lg",
DSize.Xl => "btn-xl",
_ => null
};
string ShapeClass => Shape switch
{
DButtonShape.Wide => "btn-wide",
DButtonShape.Block => "btn-block",
DButtonShape.Square => "btn-square",
DButtonShape.Circle => "btn-circle",
_ => null
};
}
+20
View File
@@ -0,0 +1,20 @@
namespace decePubClient.Components.Daisy.Actions;
public enum DButtonVariant
{
Default,
Outline,
Dash,
Soft,
Ghost,
Link
}
public enum DButtonShape
{
Default,
Wide,
Block,
Square,
Circle
}
+66
View File
@@ -0,0 +1,66 @@
@inherits DComponentBase
@* daisyUI Dropdown: https://daisyui.com/components/dropdown/ . The open state lives in C# (@bind-IsOpen):
dropdown-open shows the content and dropdown-close keeps the focus-within rule from showing it while closed.
While open, a transparent full-screen layer under the content closes it on an outside click; Escape closes it
too. With Hover the CSS hover rule opens it instead. *@
<div @attributes="AdditionalAttributes" class="@CssClass" @onkeydown="OnKeyDown">
<div role="button" tabindex="0" class="@TriggerClass" aria-haspopup="true" aria-expanded="@(IsOpen ? "true" : "false")"
@onclick="Toggle">
@Trigger
</div>
@if (IsOpen)
{
<div class="fixed inset-0 z-[998] cursor-default" aria-hidden="true" @onclick="Close"></div>
}
<div tabindex="-1" class="@Cx("dropdown-content z-[999]", ContentClass)" @onclick="OnContentClick">
@ChildContent
</div>
</div>
@code {
[Parameter] public RenderFragment Trigger { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public bool IsOpen { get; set; }
[Parameter] public EventCallback<bool> IsOpenChanged { get; set; }
[Parameter] public DDropdownPlacement Placement { get; set; }
[Parameter] public DDropdownAlign Align { get; set; }
/// <summary>Open on hover (dropdown-hover) as well as on click.</summary>
[Parameter] public bool Hover { get; set; }
/// <summary>Close after any click inside the content, e.g. on a menu item.</summary>
[Parameter] public bool CloseOnContentClick { get; set; }
[Parameter] public string TriggerClass { get; set; }
[Parameter] public string ContentClass { get; set; }
string CssClass => Cx("dropdown", PlacementClass, AlignClass,
IsOpen ? "dropdown-open" : Hover ? "dropdown-hover" : "dropdown-close", Class);
string PlacementClass => Placement switch
{
DDropdownPlacement.Top => "dropdown-top",
DDropdownPlacement.Left => "dropdown-left",
DDropdownPlacement.Right => "dropdown-right",
_ => null
};
string AlignClass => Align switch
{
DDropdownAlign.Center => "dropdown-center",
DDropdownAlign.End => "dropdown-end",
_ => null
};
Task Toggle() => SetOpen(!IsOpen);
Task Close() => SetOpen(false);
Task OnContentClick() => CloseOnContentClick ? SetOpen(false) : Task.CompletedTask;
Task OnKeyDown(KeyboardEventArgs e) => e.Key == "Escape" && IsOpen ? SetOpen(false) : Task.CompletedTask;
async Task SetOpen(bool isOpen)
{
IsOpen = isOpen;
await IsOpenChanged.InvokeAsync(isOpen);
}
}
@@ -0,0 +1,18 @@
namespace decePubClient.Components.Daisy.Actions;
/// <summary>Which side of the trigger the content opens on; Bottom is daisyUI's default.</summary>
public enum DDropdownPlacement
{
Bottom,
Top,
Left,
Right
}
/// <summary>How the content lines up with the trigger along that side; Start is daisyUI's default.</summary>
public enum DDropdownAlign
{
Start,
Center,
End
}
+53
View File
@@ -0,0 +1,53 @@
@inherits DComponentBase
@* daisyUI FAB: https://daisyui.com/components/fab/ . Opening is daisyUI's own :focus-within rule (there is no
open-state class): the trigger is the focusable first child, and focus moving out of the FAB closes it. The order
of the children matters to daisyUI: trigger, then Close or MainAction, then the speed-dial items (ChildContent,
e.g. DButton Size="DSize.Lg" Shape="DButtonShape.Circle", optionally inside a <div> with a text label). *@
<div @attributes="AdditionalAttributes" class="@CssClass">
<div tabindex="0" role="button" aria-label="@LabelText" class="@Cx("btn btn-lg btn-circle", ColorClass, TriggerClass)">
@Trigger
</div>
@if (Close is not null)
{
<div class="fab-close">@Close</div>
}
@if (MainAction is not null)
{
<div class="fab-main-action">@MainAction</div>
}
@ChildContent
</div>
@code {
/// <summary>Content of the button shown while closed, usually an icon.</summary>
[Parameter] public RenderFragment Trigger { get; set; }
/// <summary>Accessible name of the trigger (aria-label), since it usually holds only an icon.</summary>
[Parameter] public string LabelText { get; set; }
[Parameter] public DColor Color { get; set; } = DColor.Primary;
[Parameter] public string TriggerClass { get; set; }
/// <summary>Replaces the trigger while open; clicking it closes the FAB. Not focusable, e.g. a text label and a
/// &lt;span class="btn btn-circle btn-lg"&gt;.</summary>
[Parameter] public RenderFragment Close { get; set; }
/// <summary>Replaces the trigger while open with a main action, e.g. a label and a button.</summary>
[Parameter] public RenderFragment MainAction { get; set; }
/// <summary>The speed-dial items.</summary>
[Parameter] public RenderFragment ChildContent { get; set; }
/// <summary>Arrange the items in a quarter circle (fab-flower) instead of a column.</summary>
[Parameter] public bool Flower { get; set; }
string CssClass => Cx("fab", Flower ? "fab-flower" : null, Class);
string ColorClass => Color switch
{
DColor.Neutral => "btn-neutral",
DColor.Primary => "btn-primary",
DColor.Secondary => "btn-secondary",
DColor.Accent => "btn-accent",
DColor.Info => "btn-info",
DColor.Success => "btn-success",
DColor.Warning => "btn-warning",
DColor.Error => "btn-error",
_ => null
};
}
+83
View File
@@ -0,0 +1,83 @@
@inherits DComponentBase
@* daisyUI Modal: https://daisyui.com/components/modal/ . Always rendered; the open state lives in C# (@bind-IsOpen)
and shows as modal-open. On opening, the box takes the focus (Blazor's FocusAsync) so that Escape, handled on the
root, closes it; the backdrop button stretches over the area outside the box and closes it on click. *@
<div @attributes="AdditionalAttributes" role="dialog" aria-modal="true" aria-labelledby="@(Heading is null ? null : uid)"
class="@CssClass" @onkeydown="OnKeyDown">
<div @ref="box" tabindex="-1" class="@Cx("modal-box focus:outline-none", BoxClass)">
@if (Heading is not null)
{
<h3 id="@uid" class="text-lg font-bold">@Heading</h3>
}
@ChildContent
@if (Actions is not null)
{
<div class="modal-action">
@Actions
</div>
}
</div>
@if (CloseOnBackdrop)
{
<div class="modal-backdrop">
<button type="button" @onclick="Close">@CloseText</button>
</div>
}
</div>
@code {
[Parameter] public RenderFragment Heading { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public RenderFragment Actions { get; set; }
[Parameter] public bool IsOpen { get; set; }
[Parameter] public EventCallback<bool> IsOpenChanged { get; set; }
[Parameter] public DModalPlacement Placement { get; set; }
/// <summary>Close on a click outside the box (renders the modal-backdrop).</summary>
[Parameter] public bool CloseOnBackdrop { get; set; } = true;
/// <summary>Text of the invisible backdrop button, read by screen readers.</summary>
[Parameter] public string CloseText { get; set; } = "close";
[Parameter] public string BoxClass { get; set; }
readonly string uid = "d" + Guid.NewGuid().ToString("N")[..8];
ElementReference box;
bool wasOpen;
bool focusPending;
string CssClass => Cx("modal", PlacementClass, IsOpen ? "modal-open" : null, Class);
string PlacementClass => Placement switch
{
DModalPlacement.Top => "modal-top",
DModalPlacement.Bottom => "modal-bottom",
DModalPlacement.Start => "modal-start",
DModalPlacement.End => "modal-end",
_ => null
};
protected override void OnParametersSet()
{
if (IsOpen && !wasOpen)
focusPending = true;
wasOpen = IsOpen;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!focusPending)
return;
focusPending = false;
await box.FocusAsync();
}
Task Close() => SetOpen(false);
Task OnKeyDown(KeyboardEventArgs e) => e.Key == "Escape" && IsOpen ? SetOpen(false) : Task.CompletedTask;
async Task SetOpen(bool isOpen)
{
IsOpen = isOpen;
wasOpen = isOpen;
await IsOpenChanged.InvokeAsync(isOpen);
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace decePubClient.Components.Daisy.Actions;
/// <summary>Where the box sits on the screen; Middle is daisyUI's default.</summary>
public enum DModalPlacement
{
Middle,
Top,
Bottom,
Start,
End
}
+35
View File
@@ -0,0 +1,35 @@
@inherits DComponentBase
@* daisyUI Swap: https://daisyui.com/components/swap/ . A label around daisyUI's hidden checkbox: clicking anywhere on
it toggles the checkbox, whose state is bound to Active (@bind-Active); On shows while checked, Off otherwise. *@
<label @attributes="AdditionalAttributes" class="@CssClass">
<input type="checkbox" checked="@Active" disabled="@Disabled" aria-label="@LabelText" @onchange="OnChange" />
<div class="swap-on">@On</div>
<div class="swap-off">@Off</div>
</label>
@code {
[Parameter] public RenderFragment On { get; set; }
[Parameter] public RenderFragment Off { get; set; }
[Parameter] public bool Active { get; set; }
[Parameter] public EventCallback<bool> ActiveChanged { get; set; }
[Parameter] public DSwapEffect Effect { get; set; }
[Parameter] public bool Disabled { get; set; }
/// <summary>Accessible name of the checkbox (aria-label), for swaps that show only icons.</summary>
[Parameter] public string LabelText { get; set; }
string CssClass => Cx("swap", EffectClass, Disabled ? "cursor-not-allowed opacity-50" : null, Class);
string EffectClass => Effect switch
{
DSwapEffect.Rotate => "swap-rotate",
DSwapEffect.Flip => "swap-flip",
_ => null
};
async Task OnChange(ChangeEventArgs e)
{
Active = e.Value is true;
await ActiveChanged.InvokeAsync(Active);
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace decePubClient.Components.Daisy.Actions;
public enum DSwapEffect
{
None,
Rotate,
Flip
}
@@ -0,0 +1,27 @@
@inherits DComponentBase
@* daisyUI Theme controller: https://daisyui.com/components/theme-controller/ . While checked, the page uses the theme
named in Value (daisyUI's :root:has(.theme-controller:checked) rule). The app itself sets data-theme from C#, so
this exists for completeness. Style it with class="toggle", "checkbox", "radio", "btn" and so on. For a radio
group, pass the same name attribute to each and set Checked from one shared value: a radio raises no change event
when another one unchecks it. *@
<input @attributes="AdditionalAttributes" type="@(Radio ? "radio" : "checkbox")" value="@Value" class="@CssClass"
checked="@Checked" @onchange="OnChange" />
@code {
/// <summary>A daisyUI theme name, e.g. "neo-dark".</summary>
[Parameter] public string Value { get; set; }
[Parameter] public bool Checked { get; set; }
[Parameter] public EventCallback<bool> CheckedChanged { get; set; }
/// <summary>Render a radio instead of a checkbox.</summary>
[Parameter] public bool Radio { get; set; }
string CssClass => Cx("theme-controller", Class);
async Task OnChange(ChangeEventArgs e)
{
// A checkbox reports its checked state; a radio only reports its value when it becomes checked.
Checked = Radio || e.Value is true;
await CheckedChanged.InvokeAsync(Checked);
}
}
+27
View File
@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Components;
namespace decePubClient.Components.Daisy;
/// <summary>
/// Base of the daisyUI wrappers. A consumer's <c>class</c> lands in <see cref="Class"/> and is appended after the
/// component's own classes; every other unmatched attribute is passed through to the root element.
/// </summary>
public abstract class DComponentBase : ComponentBase
{
[Parameter] public string Class { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object> AdditionalAttributes { get; set; }
protected static string Cx(params string[] classes) => DCx.Join(classes);
}
public static class DCx
{
/// <summary>
/// Joins the non-empty class names. Every daisyUI class handed to it must be written out whole somewhere in the
/// source, e.g. a switch arm <c>DColor.Primary => "btn-primary"</c>, never assembled like <c>"btn-" + color</c>:
/// Tailwind generates only the classes it finds in .razor and .cs files.
/// </summary>
public static string Join(params string[] classes) =>
string.Join(' ', classes.Where(c => !string.IsNullOrWhiteSpace(c)));
}
+39
View File
@@ -0,0 +1,39 @@
using System.Globalization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace decePubClient.Components.Daisy;
/// <summary>
/// Base of the daisyUI form fields. It is Blazor's <see cref="InputBase{TValue}"/>, so <c>@bind-Value</c> and
/// EditForm validation work as with InputText; it also works outside an EditForm, as long as the value is bound.
/// A consumer's <c>class</c> stays in AdditionalAttributes, and <see cref="InputBase{TValue}.CssClass"/> adds the
/// EditContext's valid/invalid/modified markers to it.
/// </summary>
public abstract class DInputBase<TValue> : InputBase<TValue>
{
/// <summary>Whether the bound field currently has validation messages.</summary>
protected bool IsInvalid => EditContext != null && EditContext.GetValidationMessages(FieldIdentifier).Any();
/// <summary>The attributes for the inner input element, without class (that goes on the styled element).</summary>
protected IReadOnlyDictionary<string, object> InputAttributes =>
AdditionalAttributes?.Where(a => a.Key != "class").ToDictionary(a => a.Key, a => a.Value);
protected static string Cx(params string[] classes) => DCx.Join(classes);
protected override string FormatValueAsString(TValue value) =>
BindConverter.FormatValue(value, CultureInfo.InvariantCulture)?.ToString();
protected override bool TryParseValueFromString(string value, out TValue result, out string validationErrorMessage)
{
if (BindConverter.TryConvertTo(value, CultureInfo.InvariantCulture, out result))
{
validationErrorMessage = null;
return true;
}
validationErrorMessage = $"The {DisplayName ?? FieldIdentifier.FieldName} field is not valid.";
return false;
}
}
+34
View File
@@ -0,0 +1,34 @@
namespace decePubClient.Components.Daisy;
// Modifiers shared by many components. Each component maps them to its own class names in a switch whose arms are
// whole literals (DColor.Primary => "btn-primary"), so Tailwind finds them. The first member is the default and
// maps to no class.
public enum DColor
{
None,
Neutral,
Primary,
Secondary,
Accent,
Info,
Success,
Warning,
Error
}
/// <summary>daisyUI's sizes; <see cref="Md"/> is the default and renders no class.</summary>
public enum DSize
{
Md,
Xs,
Sm,
Lg,
Xl
}
public enum DDirection
{
Horizontal,
Vertical
}
@@ -0,0 +1,46 @@
@inherits DComponentBase
@* daisyUI Accordion: https://daisyui.com/components/accordion/ . Instead of daisyUI's radio group, the open item is an
index in C# (@bind-Active, -1 = none). Each DAccordionItem registers itself through the cascading value and renders a
DCollapse that is open only while its index is Active. The cascading value isn't fixed, so every item re-renders
whenever the accordion does. *@
<div @attributes="AdditionalAttributes" class="@CssClass">
<CascadingValue Value="this">
@ChildContent
</CascadingValue>
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
/// <summary>Index of the open item, in registration order; -1 when all are closed.</summary>
[Parameter] public int Active { get; set; } = -1;
[Parameter] public EventCallback<int> ActiveChanged { get; set; }
[Parameter] public DCollapseIcon Icon { get; set; }
/// <summary>Join the items into one block (join join-vertical, join-item).</summary>
[Parameter] public bool Joined { get; set; }
readonly List<DAccordionItem> items = new();
string CssClass => Cx(Joined ? "join join-vertical rounded-box neo-raised" : "flex flex-col gap-2", Class);
internal int IndexOf(DAccordionItem item) => items.IndexOf(item);
internal void Add(DAccordionItem item)
{
items.Add(item);
StateHasChanged();
}
internal void Remove(DAccordionItem item)
{
items.Remove(item);
StateHasChanged();
}
internal async Task Select(int index)
{
Active = index;
await ActiveChanged.InvokeAsync(index);
StateHasChanged();
}
}
@@ -0,0 +1,32 @@
@inherits DComponentBase
@implements IDisposable
@* One item of a DAccordion: a DCollapse (so collapse + neo-raised, keyboard and aria-expanded come from there) whose
open state is the accordion's Active index. Opening it closes the others; clicking it while open closes it. *@
<DCollapse @attributes="AdditionalAttributes" IsOpen="@IsOpen" IsOpenChanged="OnIsOpenChanged" Icon="@Parent.Icon"
Disabled="@Disabled" HeaderClass="@HeaderClass" ContentClass="@ContentClass" Header="@Header"
Class="@Cx(Parent.Joined ? "join-item shadow-none!" : null, Class)">
@ChildContent
</DCollapse>
@code {
[CascadingParameter] DAccordion Parent { get; set; }
[Parameter] public RenderFragment Header { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public bool Disabled { get; set; }
[Parameter] public string HeaderClass { get; set; }
[Parameter] public string ContentClass { get; set; }
bool IsOpen => Parent.Active >= 0 && Parent.Active == Parent.IndexOf(this);
protected override void OnInitialized()
{
if (Parent is null)
throw new InvalidOperationException($"{nameof(DAccordionItem)} must be inside a {nameof(DAccordion)}.");
Parent.Add(this);
}
Task OnIsOpenChanged(bool isOpen) => Parent.Select(isOpen ? Parent.IndexOf(this) : -1);
public void Dispose() => Parent?.Remove(this);
}
+51
View File
@@ -0,0 +1,51 @@
@inherits DComponentBase
@* daisyUI Aura: https://daisyui.com/components/aura/ . ChildContent must be exactly one element (a btn, card, …).
The light takes the text colour (Color, or any text-* in Class); a bg-* sets its background and duration-* its
speed. daisyUI advises at most one aura per page. *@
<div @attributes="AdditionalAttributes" class="@CssClass">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DAuraVariant Variant { get; set; }
/// <summary>Colour of the light, as text-*; the child inherits it unless it sets its own text colour.</summary>
[Parameter] public DColor Color { get; set; }
[Parameter] public DSize Size { get; set; }
string CssClass => Cx("aura", VariantClass, ColorClass, SizeClass, Class);
string VariantClass => Variant switch
{
DAuraVariant.Dual => "aura-dual",
DAuraVariant.Rainbow => "aura-rainbow",
DAuraVariant.Holo => "aura-holo",
DAuraVariant.Gold => "aura-gold",
DAuraVariant.Silver => "aura-silver",
DAuraVariant.Glow => "aura-glow",
_ => null
};
string ColorClass => Color switch
{
DColor.Neutral => "text-neutral",
DColor.Primary => "text-primary",
DColor.Secondary => "text-secondary",
DColor.Accent => "text-accent",
DColor.Info => "text-info",
DColor.Success => "text-success",
DColor.Warning => "text-warning",
DColor.Error => "text-error",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "aura-xs",
DSize.Sm => "aura-sm",
DSize.Lg => "aura-lg",
DSize.Xl => "aura-xl",
_ => null
};
}
@@ -0,0 +1,12 @@
namespace decePubClient.Components.Daisy.DataDisplay;
public enum DAuraVariant
{
Default,
Dual,
Rainbow,
Holo,
Gold,
Silver,
Glow
}
@@ -0,0 +1,36 @@
@inherits DComponentBase
@* daisyUI Avatar: https://daisyui.com/components/avatar/ . Class goes on the inner frame, where daisyUI puts the size
and shape (w-16 rounded-full, mask mask-squircle) and, for a placeholder, the colours (bg-neutral
text-neutral-content); the other attributes go on the outer .avatar. Without Src the frame shows ChildContent. *@
<div @attributes="AdditionalAttributes" class="@CssClass">
<div class="@Class">
@if (Src is not null)
{
<img src="@Src" alt="@Alt" />
}
else
{
@ChildContent
}
</div>
</div>
@code {
[Parameter] public string Src { get; set; }
[Parameter] public string Alt { get; set; } = "";
/// <summary>Shown in the frame when there is no Src, e.g. initials with Placeholder.</summary>
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DAvatarStatus Status { get; set; }
/// <summary>Centre ChildContent in the frame (avatar-placeholder); give the frame a bg-* in Class.</summary>
[Parameter] public bool Placeholder { get; set; }
string CssClass => Cx("avatar", StatusClass, Placeholder ? "avatar-placeholder" : null);
string StatusClass => Status switch
{
DAvatarStatus.Online => "avatar-online",
DAvatarStatus.Offline => "avatar-offline",
_ => null
};
}
@@ -0,0 +1,8 @@
namespace decePubClient.Components.Daisy.DataDisplay;
public enum DAvatarStatus
{
None,
Online,
Offline
}
@@ -0,0 +1,11 @@
@inherits DComponentBase
@* daisyUI Avatar group: https://daisyui.com/components/avatar/ . Overlap the DAvatar children with a negative
space-x in Class, e.g. -space-x-6. *@
<div @attributes="AdditionalAttributes" class="@Cx("avatar-group", Class)">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
}
+44
View File
@@ -0,0 +1,44 @@
@inherits DComponentBase
@* daisyUI Badge: https://daisyui.com/components/badge/ . Without ChildContent it is an empty dot. *@
<span @attributes="AdditionalAttributes" class="@CssClass">@ChildContent</span>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DColor Color { get; set; }
[Parameter] public DBadgeVariant Variant { get; set; }
[Parameter] public DSize Size { get; set; }
string CssClass => Cx("badge", ColorClass, VariantClass, SizeClass, Class);
string ColorClass => Color switch
{
DColor.Neutral => "badge-neutral",
DColor.Primary => "badge-primary",
DColor.Secondary => "badge-secondary",
DColor.Accent => "badge-accent",
DColor.Info => "badge-info",
DColor.Success => "badge-success",
DColor.Warning => "badge-warning",
DColor.Error => "badge-error",
_ => null
};
string VariantClass => Variant switch
{
DBadgeVariant.Outline => "badge-outline",
DBadgeVariant.Dash => "badge-dash",
DBadgeVariant.Soft => "badge-soft",
DBadgeVariant.Ghost => "badge-ghost",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "badge-xs",
DSize.Sm => "badge-sm",
DSize.Lg => "badge-lg",
DSize.Xl => "badge-xl",
_ => null
};
}
@@ -0,0 +1,10 @@
namespace decePubClient.Components.Daisy.DataDisplay;
public enum DBadgeVariant
{
Default,
Outline,
Dash,
Soft,
Ghost
}
+66
View File
@@ -0,0 +1,66 @@
@inherits DComponentBase
@* daisyUI Card: https://daisyui.com/components/card/ . Figure is wrapped in <figure> before the body (after it with
FigureLast); the body renders only when Heading, ChildContent or Actions is set. Give it a background in Class
(bg-base-100). *@
<div @attributes="AdditionalAttributes" class="@CssClass">
@if (Figure is not null && !FigureLast)
{
<figure class="@FigureClass">@Figure</figure>
}
@if (Heading is not null || ChildContent is not null || Actions is not null)
{
<div class="@Cx("card-body", BodyClass)">
@if (Heading is not null)
{
<h2 class="card-title">@Heading</h2>
}
@ChildContent
@if (Actions is not null)
{
<div class="@Cx("card-actions", ActionsClass)">@Actions</div>
}
</div>
}
@if (Figure is not null && FigureLast)
{
<figure class="@FigureClass">@Figure</figure>
}
</div>
@code {
[Parameter] public RenderFragment Figure { get; set; }
[Parameter] public RenderFragment Heading { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public RenderFragment Actions { get; set; }
[Parameter] public DCardVariant Variant { get; set; }
[Parameter] public DSize Size { get; set; }
/// <summary>Figure beside the body instead of above it (card-side).</summary>
[Parameter] public bool Side { get; set; }
/// <summary>Figure as the background behind the body (image-full).</summary>
[Parameter] public bool ImageFull { get; set; }
/// <summary>Put the figure after the body: at the bottom, or on the right with Side.</summary>
[Parameter] public bool FigureLast { get; set; }
[Parameter] public string FigureClass { get; set; }
[Parameter] public string BodyClass { get; set; }
/// <summary>Extra classes on card-actions, e.g. justify-end.</summary>
[Parameter] public string ActionsClass { get; set; }
string CssClass => Cx("card", VariantClass, SizeClass, Side ? "card-side" : null, ImageFull ? "image-full" : null, Class);
string VariantClass => Variant switch
{
DCardVariant.Border => "card-border",
DCardVariant.Dash => "card-dash",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "card-xs",
DSize.Sm => "card-sm",
DSize.Lg => "card-lg",
DSize.Xl => "card-xl",
_ => null
};
}
@@ -0,0 +1,8 @@
namespace decePubClient.Components.Daisy.DataDisplay;
public enum DCardVariant
{
Default,
Border,
Dash
}
@@ -0,0 +1,22 @@
@inherits DComponentBase
@* daisyUI Carousel: https://daisyui.com/components/carousel/ . A scroll-snap strip of DCarouselItem children; the
user scrolls or swipes it. Give each item w-full for one slide at a time. *@
<div @attributes="AdditionalAttributes" class="@CssClass">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DCarouselSnap Snap { get; set; }
[Parameter] public DDirection Direction { get; set; }
string CssClass => Cx("carousel", SnapClass, Direction == DDirection.Vertical ? "carousel-vertical" : null, Class);
string SnapClass => Snap switch
{
DCarouselSnap.Center => "carousel-center",
DCarouselSnap.End => "carousel-end",
_ => null
};
}
@@ -0,0 +1,9 @@
namespace decePubClient.Components.Daisy.DataDisplay;
/// <summary>Where each item snaps; <see cref="Start"/> is daisyUI's default, so it renders no class.</summary>
public enum DCarouselSnap
{
Start,
Center,
End
}
@@ -0,0 +1,10 @@
@inherits DComponentBase
@* One slide of a DCarousel. *@
<div @attributes="AdditionalAttributes" class="@Cx("carousel-item", Class)">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
}
+32
View File
@@ -0,0 +1,32 @@
@inherits DComponentBase
@* daisyUI Chat: https://daisyui.com/components/chat/ . One line of a conversation: an optional avatar, header and
footer around one DChatBubble (ChildContent). Avatar renders inside <div class="chat-image avatar">, so it takes
the avatar's frame, e.g. <div class="w-10 rounded-full"><img … /></div>. *@
<div @attributes="AdditionalAttributes" class="@CssClass">
@if (Avatar is not null)
{
<div class="chat-image avatar">@Avatar</div>
}
@if (Header is not null)
{
<div class="@Cx("chat-header", HeaderClass)">@Header</div>
}
@ChildContent
@if (Footer is not null)
{
<div class="@Cx("chat-footer", FooterClass)">@Footer</div>
}
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public RenderFragment Avatar { get; set; }
[Parameter] public RenderFragment Header { get; set; }
[Parameter] public RenderFragment Footer { get; set; }
[Parameter] public DChatPlacement Placement { get; set; }
[Parameter] public string HeaderClass { get; set; }
[Parameter] public string FooterClass { get; set; } = "opacity-50";
string CssClass => Cx("chat", Placement == DChatPlacement.End ? "chat-end" : "chat-start", Class);
}
@@ -0,0 +1,27 @@
@inherits DComponentBase
@* daisyUI Chat bubble: https://daisyui.com/components/chat/ . The message of a DChat; one per DChat, since daisyUI
places every bubble in the same grid cell. *@
<div @attributes="AdditionalAttributes" class="@CssClass">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DColor Color { get; set; }
string CssClass => Cx("chat-bubble", ColorClass, Class);
string ColorClass => Color switch
{
DColor.Neutral => "chat-bubble-neutral",
DColor.Primary => "chat-bubble-primary",
DColor.Secondary => "chat-bubble-secondary",
DColor.Accent => "chat-bubble-accent",
DColor.Info => "chat-bubble-info",
DColor.Success => "chat-bubble-success",
DColor.Warning => "chat-bubble-warning",
DColor.Error => "chat-bubble-error",
_ => null
};
}
@@ -0,0 +1,8 @@
namespace decePubClient.Components.Daisy.DataDisplay;
/// <summary>daisyUI requires a placement, so unlike other enums both members render a class.</summary>
public enum DChatPlacement
{
Start,
End
}
@@ -0,0 +1,45 @@
@inherits DComponentBase
@* daisyUI Collapse: https://daisyui.com/components/collapse/ . The open state lives in C# (@bind-IsOpen) and is
rendered with collapse-open / collapse-close; clicking the header (or Enter/Space on it) toggles it. neo-raised
gives it the neumorphic panel look (see Styles/neo.css for why it isn't a @utility override). *@
<div @attributes="AdditionalAttributes" class="@CssClass">
<div class="@Cx("collapse-title", Disabled ? "cursor-not-allowed" : "cursor-pointer", HeaderClass)" role="button"
tabindex="@(Disabled ? "-1" : "0")" aria-expanded="@(IsOpen ? "true" : "false")" aria-disabled="@(Disabled ? "true" : null)"
@onclick="Toggle" @onkeydown="OnKeyDown">
@Header
</div>
<div class="@Cx("collapse-content", ContentClass)">
@ChildContent
</div>
</div>
@code {
[Parameter] public RenderFragment Header { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public bool IsOpen { get; set; }
[Parameter] public EventCallback<bool> IsOpenChanged { get; set; }
[Parameter] public DCollapseIcon Icon { get; set; }
[Parameter] public bool Disabled { get; set; }
[Parameter] public string HeaderClass { get; set; }
[Parameter] public string ContentClass { get; set; }
string CssClass => Cx("collapse neo-raised", IconClass, IsOpen ? "collapse-open" : "collapse-close", Class);
string IconClass => Icon switch
{
DCollapseIcon.Arrow => "collapse-arrow",
DCollapseIcon.Plus => "collapse-plus",
_ => null
};
async Task Toggle()
{
if (Disabled)
return;
IsOpen = !IsOpen;
await IsOpenChanged.InvokeAsync(IsOpen);
}
Task OnKeyDown(KeyboardEventArgs e) => e.Key is "Enter" or " " ? Toggle() : Task.CompletedTask;
}
@@ -0,0 +1,8 @@
namespace decePubClient.Components.Daisy.DataDisplay;
public enum DCollapseIcon
{
None,
Arrow,
Plus
}
@@ -0,0 +1,18 @@
@inherits DComponentBase
@* daisyUI Countdown: https://daisyui.com/components/countdown/ . Shows one number (0-999) that rolls to each new
Value; --value and --digits go in the inner span's style. For a clock, put several side by side with separators. *@
<span @attributes="AdditionalAttributes" class="@Cx("countdown", Class)">
<span style="@ValueStyle" aria-live="polite" aria-label="@Number">@Number</span>
</span>
@code {
/// <summary>The number to show, clamped to 0-999.</summary>
[Parameter] public int Value { get; set; }
/// <summary>Minimum digits shown (1-3); 2 pads 5 to 05.</summary>
[Parameter] public int Digits { get; set; } = 1;
int Number => Math.Clamp(Value, 0, 999);
string ValueStyle => Digits > 1 ? $"--value:{Number}; --digits:{Math.Min(Digits, 3)};" : $"--value:{Number};";
}
+20
View File
@@ -0,0 +1,20 @@
@inherits DComponentBase
@* daisyUI Diff: https://daisyui.com/components/diff/ . Item1 and Item2 overlap, and the resizer handle reveals one over
the other; each slot takes one element (an img, or a div that fills it). Keyboard focus on the figure or on item 1
slides the handle to either end. It has no height of its own: AspectClass (or a height in Class) gives it one. *@
<figure @attributes="AdditionalAttributes" class="@Cx("diff", AspectClass, Class)" tabindex="0">
<div class="diff-item-1" role="img" aria-label="@Item1Alt" tabindex="0">@Item1</div>
<div class="diff-item-2" role="img" aria-label="@Item2Alt">@Item2</div>
<div class="diff-resizer"></div>
</figure>
@code {
[Parameter] public RenderFragment Item1 { get; set; }
[Parameter] public RenderFragment Item2 { get; set; }
/// <summary>Accessible name of item 1 (its role is img, which hides the alt of an img inside).</summary>
[Parameter] public string Item1Alt { get; set; }
[Parameter] public string Item2Alt { get; set; }
/// <summary>An aspect-* class for the figure; null when Class gives it a height instead.</summary>
[Parameter] public string AspectClass { get; set; } = "aspect-16/9";
}
@@ -0,0 +1,24 @@
@inherits DComponentBase
@* daisyUI Hover 3D: https://daisyui.com/components/hover-3d/ . ChildContent must be one non-interactive element (a
<figure>, a card); the eight empty divs after it are the hover zones that tilt it. With Href the whole thing is a
link, the only way to make it clickable. *@
@if (Href is null)
{
<div @attributes="AdditionalAttributes" class="@Cx("hover-3d", Class)">
@ChildContent
<div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>
</div>
}
else
{
<a @attributes="AdditionalAttributes" href="@Href" class="@Cx("hover-3d", Class)">
@ChildContent
<div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>
</a>
}
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Href { get; set; }
}
@@ -0,0 +1,21 @@
@inherits DComponentBase
@* daisyUI Hover gallery: https://daisyui.com/components/hover-gallery/ . Shows the first image; moving the pointer
across it shows the others. Up to 10 images of the same size, from Images or as <img> children; cap its width in
Class (max-w-60), or it fills the container. *@
<figure @attributes="AdditionalAttributes" class="@Cx("hover-gallery", Class)">
@if (Images is not null)
{
foreach (var src in Images.Take(10))
{
<img src="@src" alt="@Alt" />
}
}
@ChildContent
</figure>
@code {
[Parameter] public IEnumerable<string> Images { get; set; }
[Parameter] public string Alt { get; set; } = "";
[Parameter] public RenderFragment ChildContent { get; set; }
}
+20
View File
@@ -0,0 +1,20 @@
@inherits DComponentBase
@* daisyUI Kbd: https://daisyui.com/components/kbd/ *@
<kbd @attributes="AdditionalAttributes" class="@CssClass">@ChildContent</kbd>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DSize Size { get; set; }
string CssClass => Cx("kbd", SizeClass, Class);
string SizeClass => Size switch
{
DSize.Xs => "kbd-xs",
DSize.Sm => "kbd-sm",
DSize.Lg => "kbd-lg",
DSize.Xl => "kbd-xl",
_ => null
};
}
+10
View File
@@ -0,0 +1,10 @@
@inherits DComponentBase
@* daisyUI List: https://daisyui.com/components/list/ . Rows are DListRow children; a plain <li> works as a caption. *@
<ul @attributes="AdditionalAttributes" class="@Cx("list", Class)">
@ChildContent
</ul>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
}
@@ -0,0 +1,11 @@
@inherits DComponentBase
@* One row of a DList. Its second child fills the remaining width; put list-col-grow on another child to change that,
and list-col-wrap on a child to move it to a second line. *@
<li @attributes="AdditionalAttributes" class="@Cx("list-row", Class)">
@ChildContent
</li>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
}
+37
View File
@@ -0,0 +1,37 @@
@inherits DComponentBase
@* One stat of a DStats: ChildContent is the value (stat-value); each slot renders its part only when set. *@
<div @attributes="AdditionalAttributes" class="@Cx("stat", Class)">
@if (Figure is not null)
{
<div class="@Cx("stat-figure", FigureClass)">@Figure</div>
}
@if (Heading is not null)
{
<div class="stat-title">@Heading</div>
}
@if (ChildContent is not null)
{
<div class="@Cx("stat-value", ValueClass)">@ChildContent</div>
}
@if (Desc is not null)
{
<div class="stat-desc">@Desc</div>
}
@if (Actions is not null)
{
<div class="stat-actions">@Actions</div>
}
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public RenderFragment Figure { get; set; }
[Parameter] public RenderFragment Heading { get; set; }
[Parameter] public RenderFragment Desc { get; set; }
[Parameter] public RenderFragment Actions { get; set; }
/// <summary>Extra classes on stat-figure, e.g. text-primary.</summary>
[Parameter] public string FigureClass { get; set; }
/// <summary>Extra classes on stat-value, e.g. text-primary.</summary>
[Parameter] public string ValueClass { get; set; }
}
+14
View File
@@ -0,0 +1,14 @@
@inherits DComponentBase
@* daisyUI Stat: https://daisyui.com/components/stat/ . The container of DStat items; horizontal unless Direction is
Vertical (for a responsive switch add lg:stats-horizontal in Class). *@
<div @attributes="AdditionalAttributes" class="@CssClass">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DDirection Direction { get; set; }
string CssClass => Cx("stats", Direction == DDirection.Vertical ? "stats-vertical" : null, Class);
}
@@ -0,0 +1,45 @@
@inherits DComponentBase
@* daisyUI Status: https://daisyui.com/components/status/ . Give it an aria-label (online, error, …). Ping stacks an
animate-ping copy under the dot, as in daisyUI's ping example; the attributes stay on the visible dot. *@
@if (Ping)
{
<span class="inline-grid *:[grid-area:1/1]">
<span class="@Cx(CssClass, "animate-ping")" aria-hidden="true"></span>
<span @attributes="AdditionalAttributes" class="@CssClass"></span>
</span>
}
else
{
<span @attributes="AdditionalAttributes" class="@CssClass"></span>
}
@code {
[Parameter] public DColor Color { get; set; }
[Parameter] public DSize Size { get; set; }
[Parameter] public bool Ping { get; set; }
string CssClass => Cx("status", ColorClass, SizeClass, Class);
string ColorClass => Color switch
{
DColor.Neutral => "status-neutral",
DColor.Primary => "status-primary",
DColor.Secondary => "status-secondary",
DColor.Accent => "status-accent",
DColor.Info => "status-info",
DColor.Success => "status-success",
DColor.Warning => "status-warning",
DColor.Error => "status-error",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "status-xs",
DSize.Sm => "status-sm",
DSize.Lg => "status-lg",
DSize.Xl => "status-xl",
_ => null
};
}
+40
View File
@@ -0,0 +1,40 @@
@inherits DComponentBase
@* daisyUI Table: https://daisyui.com/components/table/ . ChildContent is the <thead>/<tbody>/<tfoot> markup. Class and
the other attributes go on the <table>; with Scroll (the default) it sits in an overflow-x-auto div, whose classes
are WrapperClass (pinned rows need it to have a height, e.g. h-96). *@
@if (Scroll)
{
<div class="@Cx("overflow-x-auto", WrapperClass)">
<table @attributes="AdditionalAttributes" class="@CssClass">@ChildContent</table>
</div>
}
else
{
<table @attributes="AdditionalAttributes" class="@CssClass">@ChildContent</table>
}
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DSize Size { get; set; }
[Parameter] public bool Zebra { get; set; }
/// <summary>Sticky thead/tfoot rows while the wrapper scrolls vertically.</summary>
[Parameter] public bool PinRows { get; set; }
/// <summary>Sticky &lt;th&gt; columns while the wrapper scrolls horizontally.</summary>
[Parameter] public bool PinCols { get; set; }
/// <summary>Wrap the table in an overflow-x-auto div so it scrolls on narrow screens.</summary>
[Parameter] public bool Scroll { get; set; } = true;
[Parameter] public string WrapperClass { get; set; }
string CssClass => Cx("table", Zebra ? "table-zebra" : null, PinRows ? "table-pin-rows" : null,
PinCols ? "table-pin-cols" : null, SizeClass, Class);
string SizeClass => Size switch
{
DSize.Xs => "table-xs",
DSize.Sm => "table-sm",
DSize.Lg => "table-lg",
DSize.Xl => "table-xl",
_ => null
};
}
@@ -0,0 +1,24 @@
@inherits DComponentBase
@* daisyUI Text rotate: https://daisyui.com/components/text-rotate/ . Loops through 2-6 lines, one at a time, pausing on
hover. The lines are Items, or <span> children in ChildContent (for per-line classes). Set the loop time with a
duration-* in Class (default 10s). *@
<span @attributes="AdditionalAttributes" class="@Cx("text-rotate", Class)">
<span class="@(Centered ? "justify-items-center" : null)">
@if (Items is not null)
{
foreach (var item in Items.Take(6))
{
<span>@item</span>
}
}
@ChildContent
</span>
</span>
@code {
[Parameter] public IEnumerable<string> Items { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
/// <summary>Centre the lines horizontally.</summary>
[Parameter] public bool Centered { get; set; }
}
@@ -0,0 +1,21 @@
@inherits DComponentBase
@* daisyUI Timeline: https://daisyui.com/components/timeline/ . Horizontal unless Direction is Vertical (for a
responsive switch add lg:timeline-horizontal in Class). Every DTimelineItem draws its connecting <hr> on both
sides; the two arbitrary variants here hide the outer ends, so the line runs only between items. *@
<ul @attributes="AdditionalAttributes" class="@CssClass">
@ChildContent
</ul>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DDirection Direction { get; set; }
/// <summary>Put every item on one side of the line (timeline-compact).</summary>
[Parameter] public bool Compact { get; set; }
/// <summary>Align the icon with the start of the content instead of its middle (timeline-snap-icon).</summary>
[Parameter] public bool SnapIcon { get; set; }
string CssClass => Cx("timeline", Direction == DDirection.Vertical ? "timeline-vertical" : null,
Compact ? "timeline-compact" : null, SnapIcon ? "timeline-snap-icon" : null,
"[&>li:first-child>hr:first-child]:hidden [&>li:last-child>hr:last-child]:hidden", Class);
}
@@ -0,0 +1,36 @@
@inherits DComponentBase
@* One event of a DTimeline: Start and End on either side of the line, Middle (an icon) on it. Each part renders only
when set. The <hr> before and after are the line segments to the neighbouring items; colour them with
LineBeforeClass / LineAfterClass (bg-primary). *@
<li @attributes="AdditionalAttributes" class="@Class">
<hr class="@LineBeforeClass" />
@if (Start is not null)
{
<div class="@Cx("timeline-start", StartBox ? "timeline-box" : null, StartClass)">@Start</div>
}
@if (Middle is not null)
{
<div class="@Cx("timeline-middle", MiddleClass)">@Middle</div>
}
@if (End is not null)
{
<div class="@Cx("timeline-end", EndBox ? "timeline-box" : null, EndClass)">@End</div>
}
<hr class="@LineAfterClass" />
</li>
@code {
[Parameter] public RenderFragment Start { get; set; }
[Parameter] public RenderFragment Middle { get; set; }
[Parameter] public RenderFragment End { get; set; }
/// <summary>Draw Start as a box (timeline-box).</summary>
[Parameter] public bool StartBox { get; set; }
/// <summary>Draw End as a box (timeline-box).</summary>
[Parameter] public bool EndBox { get; set; }
[Parameter] public string StartClass { get; set; }
[Parameter] public string MiddleClass { get; set; }
[Parameter] public string EndClass { get; set; }
[Parameter] public string LineBeforeClass { get; set; }
[Parameter] public string LineAfterClass { get; set; }
}
@@ -0,0 +1,40 @@
@inherits DInputBase<DateOnly?>
@using System.Globalization
@* daisyUI Calendar (Cally): https://daisyui.com/components/calendar/ . Wraps the <calendar-date> web component from
wwwroot/vendor/cally.js; value, min and max are yyyy-MM-dd, and Blazor reads the element's value on its change
event. Other Cally attributes (locale, first-day-of-week, months) pass through. *@
<calendar-date @attributes="InputAttributes" class="@Cx("cally", CssClass)" value="@CurrentValueAsString" min="@Format(Min)"
max="@Format(Max)" @onchange="OnChange" aria-invalid="@(IsInvalid ? "true" : null)">
<svg aria-label="@PreviousText" class="fill-current size-4" slot="previous" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.75 19.5 8.25 12l7.5-7.5"></path></svg>
<svg aria-label="@NextText" class="fill-current size-4" slot="next" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m8.25 4.5 7.5 7.5-7.5 7.5"></path></svg>
<calendar-month></calendar-month>
</calendar-date>
@code {
[Parameter] public DateOnly? Min { get; set; }
[Parameter] public DateOnly? Max { get; set; }
[Parameter] public string PreviousText { get; set; } = "Previous";
[Parameter] public string NextText { get; set; } = "Next";
static string Format(DateOnly? date) => date?.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
protected override string FormatValueAsString(DateOnly? value) => Format(value);
protected override bool TryParseValueFromString(string value, out DateOnly? result, out string validationErrorMessage)
{
validationErrorMessage = null;
result = null;
if (string.IsNullOrEmpty(value))
return true;
if (DateOnly.TryParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date))
{
result = date;
return true;
}
validationErrorMessage = $"The {DisplayName ?? FieldIdentifier.FieldName} field is not a valid date.";
return false;
}
void OnChange(ChangeEventArgs e) => CurrentValueAsString = e.Value?.ToString();
}
@@ -0,0 +1,50 @@
@inherits DInputBase<bool>
@* daisyUI Checkbox: https://daisyui.com/components/checkbox/ . With LabelText the checkbox sits in a <label class="label">
followed by the text, as in daisyUI's fieldset examples. *@
@if (LabelText is null)
{
@Checkbox
}
else
{
<label class="label">
@Checkbox
@LabelText
</label>
}
@code {
[Parameter] public DColor Color { get; set; }
[Parameter] public DSize Size { get; set; }
[Parameter] public string LabelText { get; set; }
string CssClassFull => Cx("checkbox", IsInvalid ? "checkbox-error" : ColorClass, SizeClass, CssClass);
RenderFragment Checkbox => @<input @attributes="InputAttributes" type="checkbox" class="@CssClassFull" name="@NameAttributeValue"
checked="@CurrentValue" @onchange="OnChange" aria-invalid="@(IsInvalid ? "true" : null)" />;
string ColorClass => Color switch
{
DColor.Neutral => "checkbox-neutral",
DColor.Primary => "checkbox-primary",
DColor.Secondary => "checkbox-secondary",
DColor.Accent => "checkbox-accent",
DColor.Info => "checkbox-info",
DColor.Success => "checkbox-success",
DColor.Warning => "checkbox-warning",
DColor.Error => "checkbox-error",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "checkbox-xs",
DSize.Sm => "checkbox-sm",
DSize.Lg => "checkbox-lg",
DSize.Xl => "checkbox-xl",
_ => null
};
void OnChange(ChangeEventArgs e) => CurrentValue = e.Value is true;
}
@@ -0,0 +1,21 @@
@inherits DComponentBase
@* daisyUI Fieldset: https://daisyui.com/components/fieldset/ . Legend becomes the fieldset-legend, Description the
trailing p.label; disabled on the fieldset disables every field in it. *@
<fieldset @attributes="AdditionalAttributes" class="@Cx("fieldset", Class)">
@if (Legend is not null)
{
<legend class="fieldset-legend">@Legend</legend>
}
@ChildContent
@if (Description is not null)
{
<p class="label">@Description</p>
}
</fieldset>
@code {
[Parameter] public RenderFragment Legend { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public RenderFragment Description { get; set; }
}
@@ -0,0 +1,39 @@
@inherits DComponentBase
@* daisyUI File input: https://daisyui.com/components/file-input/ . Blazor's InputFile with the file-input classes;
read the files in OnChange (e.File / e.GetMultipleFiles()). *@
<InputFile @attributes="AdditionalAttributes" class="@CssClass" OnChange="OnChange" multiple="@Multiple" accept="@Accept" />
@code {
[Parameter] public EventCallback<InputFileChangeEventArgs> OnChange { get; set; }
[Parameter] public bool Multiple { get; set; }
/// <summary>The accept attribute: file types, e.g. "image/*,.pdf".</summary>
[Parameter] public string Accept { get; set; }
[Parameter] public DColor Color { get; set; }
[Parameter] public DSize Size { get; set; }
[Parameter] public bool Ghost { get; set; }
string CssClass => Cx("file-input", ColorClass, SizeClass, Ghost ? "file-input-ghost" : null, Class);
string ColorClass => Color switch
{
DColor.Neutral => "file-input-neutral",
DColor.Primary => "file-input-primary",
DColor.Secondary => "file-input-secondary",
DColor.Accent => "file-input-accent",
DColor.Info => "file-input-info",
DColor.Success => "file-input-success",
DColor.Warning => "file-input-warning",
DColor.Error => "file-input-error",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "file-input-xs",
DSize.Sm => "file-input-sm",
DSize.Lg => "file-input-lg",
DSize.Xl => "file-input-xl",
_ => null
};
}
+43
View File
@@ -0,0 +1,43 @@
@typeparam TValue
@inherits DInputBase<TValue>
@* daisyUI Filter: https://daisyui.com/components/filter/ . The <div> form of the filter: a native form reset would
not tell Blazor, and a <form> can't nest in an EditForm. Each option is a radio button checked while Value equals
it; the filter-reset radio sets Value back to default, so with no default among the options nothing is selected. *@
<div @attributes="InputAttributes" class="@Cx("filter", CssClass)">
<input type="radio" name="@GroupName" class="@Cx("btn", "filter-reset", SizeClass)" aria-label="@ResetText"
disabled="@Disabled" @onchange="() => CurrentValue = default" />
@if (Options is not null)
{
foreach (var option in Options)
{
<input type="radio" name="@GroupName" class="@Cx("btn", SizeClass)" aria-label="@(TextOf?.Invoke(option) ?? option?.ToString())"
checked="@IsSelected(option)" disabled="@Disabled"
@onchange="() => CurrentValue = option" />
}
}
</div>
@code {
[Parameter] public IEnumerable<TValue> Options { get; set; }
/// <summary>An option's button text; ToString() when null.</summary>
[Parameter] public Func<TValue, string> TextOf { get; set; }
[Parameter] public string ResetText { get; set; } = "×";
[Parameter] public DSize Size { get; set; }
[Parameter] public bool Disabled { get; set; }
readonly string uid = "d" + Guid.NewGuid().ToString("N")[..8];
string GroupName => string.IsNullOrEmpty(NameAttributeValue) ? uid : NameAttributeValue;
bool IsSelected(TValue option) => EqualityComparer<TValue>.Default.Equals(CurrentValue, option);
string SizeClass => Size switch
{
DSize.Xs => "btn-xs",
DSize.Sm => "btn-sm",
DSize.Lg => "btn-lg",
DSize.Xl => "btn-xl",
_ => null
};
}
+65
View File
@@ -0,0 +1,65 @@
@typeparam TValue
@inherits DInputBase<TValue>
@* daisyUI Input: https://daisyui.com/components/input/ . With Prefix or Suffix (icons, labels, kbd) the input class
moves to a wrapping <label>, as daisyUI requires when the field holds more than one element. *@
@if (Prefix is null && Suffix is null)
{
<input @attributes="InputAttributes" type="@Type" class="@CssClassFull" name="@NameAttributeValue"
value="@CurrentValueAsString" @oninput="OnInput" @onchange="OnChange" aria-invalid="@(IsInvalid ? "true" : null)" />
}
else
{
<label class="@CssClassFull">
@Prefix
<input @attributes="InputAttributes" type="@Type" class="grow" name="@NameAttributeValue"
value="@CurrentValueAsString" @oninput="OnInput" @onchange="OnChange" aria-invalid="@(IsInvalid ? "true" : null)" />
@Suffix
</label>
}
@code {
[Parameter] public string Type { get; set; } = "text";
[Parameter] public DColor Color { get; set; }
[Parameter] public DSize Size { get; set; }
[Parameter] public bool Ghost { get; set; }
/// <summary>Update the bound value on every keystroke instead of on change.</summary>
[Parameter] public bool Immediate { get; set; }
[Parameter] public RenderFragment Prefix { get; set; }
[Parameter] public RenderFragment Suffix { get; set; }
/// <summary>Adds daisyUI's validator class: error/success colors and a following DValidatorHint.</summary>
[Parameter] public bool Validator { get; set; }
string CssClassFull => Cx("input", IsInvalid ? "input-error" : ColorClass, SizeClass, Ghost ? "input-ghost" : null,
Validator ? "validator" : null, CssClass);
string ColorClass => Color switch
{
DColor.Neutral => "input-neutral",
DColor.Primary => "input-primary",
DColor.Secondary => "input-secondary",
DColor.Accent => "input-accent",
DColor.Info => "input-info",
DColor.Success => "input-success",
DColor.Warning => "input-warning",
DColor.Error => "input-error",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "input-xs",
DSize.Sm => "input-sm",
DSize.Lg => "input-lg",
DSize.Xl => "input-xl",
_ => null
};
void OnInput(ChangeEventArgs e)
{
if (Immediate)
CurrentValueAsString = e.Value?.ToString();
}
void OnChange(ChangeEventArgs e) => CurrentValueAsString = e.Value?.ToString();
}
+22
View File
@@ -0,0 +1,22 @@
@inherits DComponentBase
@* daisyUI Label: https://daisyui.com/components/label/ . By default a span.label holding ChildContent, for a DInput or
DSelect Prefix/Suffix or a fieldset. Floating renders label.floating-label around the field in ChildContent, with
Text as the label that floats above it once the field has focus or a value. *@
@if (Floating)
{
<label @attributes="AdditionalAttributes" class="@Cx("floating-label", Class)">
@ChildContent
<span>@Text</span>
</label>
}
else
{
<span @attributes="AdditionalAttributes" class="@Cx("label", Class)">@(Text)@ChildContent</span>
}
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Text { get; set; }
[Parameter] public bool Floating { get; set; }
}
+58
View File
@@ -0,0 +1,58 @@
@inherits DInputBase<string>
@* daisyUI OTP: https://daisyui.com/components/otp/ . The otp class is on the label; Length (4 to 6) sets the number of
digit boxes, maxlength and pattern together. required is on by default because daisyUI styles the complete code
through :valid; required="false" removes it. *@
<label class="@CssClassFull">
@for (var i = 0; i < Digits; i++)
{
<span></span>
}
<input type="text" autocomplete="one-time-code" inputmode="numeric" required @attributes="InputAttributes"
maxlength="@Digits" pattern="@($"[0-9]{{{Digits}}}")" name="@NameAttributeValue" value="@CurrentValueAsString"
@oninput="OnInput" @onchange="OnChange" aria-invalid="@(IsInvalid ? "true" : null)" />
</label>
@code {
[Parameter] public int Length { get; set; } = 6;
[Parameter] public DColor Color { get; set; }
[Parameter] public DSize Size { get; set; }
/// <summary>Joins the digit boxes into one field (otp-joined).</summary>
[Parameter] public bool Joined { get; set; }
/// <summary>Update the bound value on every keystroke instead of on change.</summary>
[Parameter] public bool Immediate { get; set; }
int Digits => Math.Clamp(Length, 4, 6);
string CssClassFull => Cx("otp", IsInvalid ? "otp-error" : ColorClass, SizeClass, Joined ? "otp-joined" : null, CssClass);
string ColorClass => Color switch
{
DColor.Neutral => "otp-neutral",
DColor.Primary => "otp-primary",
DColor.Secondary => "otp-secondary",
DColor.Accent => "otp-accent",
DColor.Info => "otp-info",
DColor.Success => "otp-success",
DColor.Warning => "otp-warning",
DColor.Error => "otp-error",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "otp-xs",
DSize.Sm => "otp-sm",
DSize.Lg => "otp-lg",
DSize.Xl => "otp-xl",
_ => null
};
void OnInput(ChangeEventArgs e)
{
if (Immediate)
CurrentValueAsString = e.Value?.ToString();
}
void OnChange(ChangeEventArgs e) => CurrentValueAsString = e.Value?.ToString();
}
+60
View File
@@ -0,0 +1,60 @@
@typeparam TValue
@inherits DComponentBase
@* One radio of a DRadioGroup: checked while the group's value equals Value; choosing it sets the group's value. With
LabelText it sits in a label.label followed by the text. *@
@if (LabelText is null)
{
@Radio
}
else
{
<label class="label">
@Radio
@LabelText
</label>
}
@code {
[CascadingParameter] DRadioGroup<TValue> Group { get; set; }
[Parameter] public TValue Value { get; set; }
[Parameter] public DColor Color { get; set; }
[Parameter] public DSize Size { get; set; }
[Parameter] public bool Disabled { get; set; }
[Parameter] public string LabelText { get; set; }
RenderFragment Radio =>
@<input @attributes="AdditionalAttributes" type="radio" class="@CssClass" name="@Group.GroupName"
checked="@Group.IsSelected(Value)" disabled="@Disabled" @onchange="() => Group.Select(Value)"
aria-invalid="@(Group.GroupInvalid ? "true" : null)" />;
string CssClass => Cx("radio", Group.GroupInvalid ? "radio-error" : ColorClass, SizeClass, Class);
string ColorClass => Color switch
{
DColor.Neutral => "radio-neutral",
DColor.Primary => "radio-primary",
DColor.Secondary => "radio-secondary",
DColor.Accent => "radio-accent",
DColor.Info => "radio-info",
DColor.Success => "radio-success",
DColor.Warning => "radio-warning",
DColor.Error => "radio-error",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "radio-xs",
DSize.Sm => "radio-sm",
DSize.Lg => "radio-lg",
DSize.Xl => "radio-xl",
_ => null
};
protected override void OnInitialized()
{
if (Group is null)
throw new InvalidOperationException("DRadio must be inside a DRadioGroup of the same TValue.");
}
}
@@ -0,0 +1,33 @@
@typeparam TValue
@inherits DInputBase<TValue>
@attribute [CascadingTypeParameter(nameof(TValue))]
@* daisyUI Radio: https://daisyui.com/components/radio/ . The group holds the bound value and cascades itself (not
fixed, so every DRadio re-renders with it); its radios share one name, the name attribute or a per-instance id.
It adds a div role="radiogroup" only when given attributes (a class, an aria-label). *@
<CascadingValue Value="this">
@if (AdditionalAttributes is null || AdditionalAttributes.Count == 0)
{
@ChildContent
}
else
{
<div @attributes="InputAttributes" role="radiogroup" class="@CssClass">
@ChildContent
</div>
}
</CascadingValue>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
readonly string uid = "d" + Guid.NewGuid().ToString("N")[..8];
internal string GroupName => string.IsNullOrEmpty(NameAttributeValue) ? uid : NameAttributeValue;
internal bool GroupInvalid => IsInvalid;
internal bool IsSelected(TValue value) => EqualityComparer<TValue>.Default.Equals(CurrentValue, value);
internal void Select(TValue value) => CurrentValue = value;
}
+54
View File
@@ -0,0 +1,54 @@
@typeparam TValue
@inherits DInputBase<TValue>
@* daisyUI Range: https://daisyui.com/components/range/ . TValue is numeric; Min and Max are always rendered, as daisyUI
requires. *@
<input @attributes="InputAttributes" type="range" class="@CssClassFull" name="@NameAttributeValue" min="@Format(Min)"
max="@Format(Max)" step="@(Step is null ? null : Format(Step.Value))" value="@CurrentValueAsString"
@oninput="OnInput" @onchange="OnChange" aria-invalid="@(IsInvalid ? "true" : null)" />
@code {
[Parameter] public double Min { get; set; }
[Parameter] public double Max { get; set; } = 100;
[Parameter] public double? Step { get; set; }
[Parameter] public DColor Color { get; set; }
[Parameter] public DSize Size { get; set; }
[Parameter] public DDirection Direction { get; set; }
/// <summary>Update the bound value while dragging instead of on release.</summary>
[Parameter] public bool Immediate { get; set; }
string CssClassFull => Cx("range", IsInvalid ? "range-error" : ColorClass, SizeClass,
Direction == DDirection.Vertical ? "range-vertical" : null, CssClass);
string ColorClass => Color switch
{
DColor.Neutral => "range-neutral",
DColor.Primary => "range-primary",
DColor.Secondary => "range-secondary",
DColor.Accent => "range-accent",
DColor.Info => "range-info",
DColor.Success => "range-success",
DColor.Warning => "range-warning",
DColor.Error => "range-error",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "range-xs",
DSize.Sm => "range-sm",
DSize.Lg => "range-lg",
DSize.Xl => "range-xl",
_ => null
};
static string Format(double value) => value.ToString(System.Globalization.CultureInfo.InvariantCulture);
void OnInput(ChangeEventArgs e)
{
if (Immediate)
CurrentValueAsString = e.Value?.ToString();
}
void OnChange(ChangeEventArgs e) => CurrentValueAsString = e.Value?.ToString();
}
+85
View File
@@ -0,0 +1,85 @@
@inherits DInputBase<int>
@* daisyUI Rating: https://daisyui.com/components/rating/ . A radio per item, sharing a per-instance name; Value is the
number of the checked item, 0 for none. With Half every item is two half radios and Value counts halves (3 = 1.5
stars). Static renders the read-only <div> version, marking the current item with aria-current. *@
<div @attributes="InputAttributes" class="@CssClassFull">
@if (Static)
{
for (var i = 1; i <= Items; i++)
{
<div class="@ItemCss(i)" aria-label="@ItemText(i)" aria-current="@(i == CurrentValue ? "true" : null)"></div>
}
}
else
{
if (Clearable)
{
<input type="radio" name="@GroupName" class="rating-hidden" aria-label="@ClearText" checked="@(CurrentValue == 0)"
disabled="@Disabled" @onchange="() => CurrentValue = 0" />
}
for (var i = 1; i <= Items; i++)
{
var item = i;
<input type="radio" name="@GroupName" class="@ItemCss(item)" aria-label="@ItemText(item)" checked="@(item == CurrentValue)"
disabled="@Disabled" @onchange="() => CurrentValue = item" aria-invalid="@(IsInvalid ? "true" : null)" />
}
}
</div>
@code {
[Parameter] public int Count { get; set; } = 5;
[Parameter] public DRatingMask Mask { get; set; }
[Parameter] public bool Half { get; set; }
[Parameter] public DSize Size { get; set; }
/// <summary>Adds the hidden first radio (rating-hidden) that clears the rating to 0.</summary>
[Parameter] public bool Clearable { get; set; }
/// <summary>Read-only rating: <div> items instead of radios.</summary>
[Parameter] public bool Static { get; set; }
[Parameter] public bool Disabled { get; set; }
/// <summary>Classes for every item, e.g. a color: bg-orange-400.</summary>
[Parameter] public string ItemClass { get; set; }
/// <summary>The unit in each item's aria-label ("2 star").</summary>
[Parameter] public string UnitText { get; set; } = "star";
[Parameter] public string ClearText { get; set; } = "clear";
readonly string uid = "d" + Guid.NewGuid().ToString("N")[..8];
string GroupName => string.IsNullOrEmpty(NameAttributeValue) ? uid : NameAttributeValue;
int Items => Half ? Count * 2 : Count;
string CssClassFull => Cx("rating", Half ? "rating-half" : null, SizeClass, CssClass);
string ItemCss(int item) => Cx("mask", MaskClass, Half ? (item % 2 == 1 ? "mask-half-1" : "mask-half-2") : null, ItemClass);
string ItemText(int item) =>
$"{(Half ? item / 2.0 : item).ToString(System.Globalization.CultureInfo.InvariantCulture)} {UnitText}";
string MaskClass => Mask switch
{
DRatingMask.Star2 => "mask-star-2",
DRatingMask.Heart => "mask-heart",
DRatingMask.Squircle => "mask-squircle",
DRatingMask.Hexagon => "mask-hexagon",
DRatingMask.Hexagon2 => "mask-hexagon-2",
DRatingMask.Decagon => "mask-decagon",
DRatingMask.Pentagon => "mask-pentagon",
DRatingMask.Diamond => "mask-diamond",
DRatingMask.Circle => "mask-circle",
DRatingMask.Triangle => "mask-triangle",
DRatingMask.Triangle2 => "mask-triangle-2",
DRatingMask.Triangle3 => "mask-triangle-3",
DRatingMask.Triangle4 => "mask-triangle-4",
_ => "mask-star"
};
string SizeClass => Size switch
{
DSize.Xs => "rating-xs",
DSize.Sm => "rating-sm",
DSize.Lg => "rating-lg",
DSize.Xl => "rating-xl",
_ => null
};
}
@@ -0,0 +1,20 @@
namespace decePubClient.Components.Daisy.DataInput;
/// <summary>The mask shape of a DRating's items. A rating always needs one, so the default Star is mask-star.</summary>
public enum DRatingMask
{
Star,
Star2,
Heart,
Squircle,
Hexagon,
Hexagon2,
Decagon,
Pentagon,
Diamond,
Circle,
Triangle,
Triangle2,
Triangle3,
Triangle4
}
+59
View File
@@ -0,0 +1,59 @@
@typeparam TValue
@inherits DInputBase<TValue>
@* daisyUI Select: https://daisyui.com/components/select/ . The options are ChildContent (<option value="...">);
enums bind by name. With Prefix (an icon or a span.label) the select class moves to a wrapping <label>. *@
@if (Prefix is null)
{
<select @attributes="InputAttributes" class="@CssClassFull" name="@NameAttributeValue" value="@CurrentValueAsString"
@onchange="OnChange" aria-invalid="@(IsInvalid ? "true" : null)">
@ChildContent
</select>
}
else
{
<label class="@CssClassFull">
@Prefix
<select @attributes="InputAttributes" name="@NameAttributeValue" value="@CurrentValueAsString" @onchange="OnChange"
aria-invalid="@(IsInvalid ? "true" : null)">
@ChildContent
</select>
</label>
}
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public RenderFragment Prefix { get; set; }
[Parameter] public DColor Color { get; set; }
[Parameter] public DSize Size { get; set; }
[Parameter] public bool Ghost { get; set; }
/// <summary>Adds daisyUI's validator class: error/success colors and a following DValidatorHint.</summary>
[Parameter] public bool Validator { get; set; }
string CssClassFull => Cx("select", IsInvalid ? "select-error" : ColorClass, SizeClass, Ghost ? "select-ghost" : null,
Validator ? "validator" : null, CssClass);
string ColorClass => Color switch
{
DColor.Neutral => "select-neutral",
DColor.Primary => "select-primary",
DColor.Secondary => "select-secondary",
DColor.Accent => "select-accent",
DColor.Info => "select-info",
DColor.Success => "select-success",
DColor.Warning => "select-warning",
DColor.Error => "select-error",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "select-xs",
DSize.Sm => "select-sm",
DSize.Lg => "select-lg",
DSize.Xl => "select-xl",
_ => null
};
void OnChange(ChangeEventArgs e) => CurrentValueAsString = e.Value?.ToString();
}
@@ -0,0 +1,50 @@
@typeparam TValue
@inherits DInputBase<TValue>
@* daisyUI Textarea: https://daisyui.com/components/textarea/ . Blazor sets the textarea's value property, so the bound
value is the content; rows, placeholder and the like pass through. *@
<textarea @attributes="InputAttributes" class="@CssClassFull" name="@NameAttributeValue" value="@CurrentValueAsString"
@oninput="OnInput" @onchange="OnChange" aria-invalid="@(IsInvalid ? "true" : null)"></textarea>
@code {
[Parameter] public DColor Color { get; set; }
[Parameter] public DSize Size { get; set; }
[Parameter] public bool Ghost { get; set; }
/// <summary>Update the bound value on every keystroke instead of on change.</summary>
[Parameter] public bool Immediate { get; set; }
/// <summary>Adds daisyUI's validator class: error/success colors and a following DValidatorHint.</summary>
[Parameter] public bool Validator { get; set; }
string CssClassFull => Cx("textarea", IsInvalid ? "textarea-error" : ColorClass, SizeClass, Ghost ? "textarea-ghost" : null,
Validator ? "validator" : null, CssClass);
string ColorClass => Color switch
{
DColor.Neutral => "textarea-neutral",
DColor.Primary => "textarea-primary",
DColor.Secondary => "textarea-secondary",
DColor.Accent => "textarea-accent",
DColor.Info => "textarea-info",
DColor.Success => "textarea-success",
DColor.Warning => "textarea-warning",
DColor.Error => "textarea-error",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "textarea-xs",
DSize.Sm => "textarea-sm",
DSize.Lg => "textarea-lg",
DSize.Xl => "textarea-xl",
_ => null
};
void OnInput(ChangeEventArgs e)
{
if (Immediate)
CurrentValueAsString = e.Value?.ToString();
}
void OnChange(ChangeEventArgs e) => CurrentValueAsString = e.Value?.ToString();
}
+101
View File
@@ -0,0 +1,101 @@
@inherits DInputBase<bool>
@* daisyUI Toggle: https://daisyui.com/components/toggle/ . With CheckedIcon/UncheckedIcon the toggle class moves to a
wrapper around the checkbox and the two icons (each must be a single element: daisyUI shows the wrapper's 2nd child
while unchecked and its 3rd while checked). daisyUI colors such a wrapper only through aria-checked, which a label
may not carry, so its color is a has-checked: variable instead. With LabelText all of it sits in a label.label. *@
@if (!HasIcons && LabelText is null)
{
@Checkbox(Cx("toggle", IsInvalid ? "toggle-error" : ColorClass, SizeClass, CssClass))
}
else if (!HasIcons)
{
<label class="label">
@Checkbox(Cx("toggle", IsInvalid ? "toggle-error" : ColorClass, SizeClass, CssClass))
@LabelText
</label>
}
else if (LabelText is null)
{
<label class="@WrapperClass">@IconParts</label>
}
else
{
<label class="label">
<span class="@WrapperClass">@IconParts</span>
@LabelText
</label>
}
@code {
[Parameter] public DColor Color { get; set; }
[Parameter] public DSize Size { get; set; }
[Parameter] public string LabelText { get; set; }
[Parameter] public RenderFragment CheckedIcon { get; set; }
[Parameter] public RenderFragment UncheckedIcon { get; set; }
bool HasIcons => CheckedIcon is not null || UncheckedIcon is not null;
string WrapperClass => Cx("toggle", IsInvalid ? "has-checked:[--input-color:var(--color-error)]" : WrapperColorClass, SizeClass, CssClass);
RenderFragment Checkbox(string cssClass) =>
@<input @attributes="InputAttributes" type="checkbox" class="@cssClass" name="@NameAttributeValue" checked="@CurrentValue"
@onchange="OnChange" aria-invalid="@(IsInvalid ? "true" : null)" />;
RenderFragment IconParts => @<text>
@Checkbox(null)
@if (UncheckedIcon is null)
{
<span></span>
}
else
{
@UncheckedIcon
}
@if (CheckedIcon is null)
{
<span></span>
}
else
{
@CheckedIcon
}
</text>;
string ColorClass => Color switch
{
DColor.Neutral => "toggle-neutral",
DColor.Primary => "toggle-primary",
DColor.Secondary => "toggle-secondary",
DColor.Accent => "toggle-accent",
DColor.Info => "toggle-info",
DColor.Success => "toggle-success",
DColor.Warning => "toggle-warning",
DColor.Error => "toggle-error",
_ => null
};
string WrapperColorClass => Color switch
{
DColor.Neutral => "has-checked:[--input-color:var(--color-neutral)]",
DColor.Primary => "has-checked:[--input-color:var(--color-primary)]",
DColor.Secondary => "has-checked:[--input-color:var(--color-secondary)]",
DColor.Accent => "has-checked:[--input-color:var(--color-accent)]",
DColor.Info => "has-checked:[--input-color:var(--color-info)]",
DColor.Success => "has-checked:[--input-color:var(--color-success)]",
DColor.Warning => "has-checked:[--input-color:var(--color-warning)]",
DColor.Error => "has-checked:[--input-color:var(--color-error)]",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "toggle-xs",
DSize.Sm => "toggle-sm",
DSize.Lg => "toggle-lg",
DSize.Xl => "toggle-xl",
_ => null
};
void OnChange(ChangeEventArgs e) => CurrentValue = e.Value is true;
}
@@ -0,0 +1,12 @@
@inherits DComponentBase
@* daisyUI Validator: https://daisyui.com/components/validator/ . Place it right after a field with Validator set (a
following sibling): it shows, in the error color, while that field is :user-invalid or aria-invalid (failed
EditForm validation). *@
<p @attributes="AdditionalAttributes" class="@Cx("validator-hint", Compact ? "hidden" : null, Class)">@ChildContent</p>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
/// <summary>Takes no space while the field is valid (daisyUI's validator-hint hidden) instead of keeping its line.</summary>
[Parameter] public bool Compact { get; set; }
}
+44
View File
@@ -0,0 +1,44 @@
@inherits DComponentBase
@* daisyUI Alert: https://daisyui.com/components/alert/ . The alert is a grid of up to three cells: Icon, ChildContent
(wrapped in one <div>, so a heading plus text stays one cell) and Actions. role="alert" comes before the splatted
attributes, so a consumer can pass role="status" for a non-urgent message. *@
<div role="alert" @attributes="AdditionalAttributes" class="@CssClass">
@Icon
<div>@ChildContent</div>
@if (Actions is not null)
{
<div class="flex gap-2">@Actions</div>
}
</div>
@code {
[Parameter] public RenderFragment Icon { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public RenderFragment Actions { get; set; }
[Parameter] public DAlertColor Color { get; set; }
[Parameter] public DAlertVariant Variant { get; set; }
[Parameter] public DDirection Direction { get; set; }
/// <summary>Vertical on small screens, horizontal from sm up (alert-vertical sm:alert-horizontal).</summary>
[Parameter] public bool Responsive { get; set; }
string CssClass => Cx("alert", ColorClass, VariantClass,
Direction == DDirection.Vertical || Responsive ? "alert-vertical" : null, Responsive ? "sm:alert-horizontal" : null, Class);
string ColorClass => Color switch
{
DAlertColor.Info => "alert-info",
DAlertColor.Success => "alert-success",
DAlertColor.Warning => "alert-warning",
DAlertColor.Error => "alert-error",
_ => null
};
string VariantClass => Variant switch
{
DAlertVariant.Outline => "alert-outline",
DAlertVariant.Dash => "alert-dash",
DAlertVariant.Soft => "alert-soft",
_ => null
};
}
+19
View File
@@ -0,0 +1,19 @@
namespace decePubClient.Components.Daisy.Feedback;
/// <summary>daisyUI's alert colours; there is no neutral/primary/secondary/accent alert, hence not DColor.</summary>
public enum DAlertColor
{
Default,
Info,
Success,
Warning,
Error
}
public enum DAlertVariant
{
Default,
Outline,
Dash,
Soft
}
+47
View File
@@ -0,0 +1,47 @@
@inherits DComponentBase
@* daisyUI Loading: https://daisyui.com/components/loading/ . It is drawn in currentColor, so Color maps to text-*
classes. role="status" and aria-label come before the splatted attributes, so a consumer can override them. *@
<span role="status" aria-label="@LabelText" @attributes="AdditionalAttributes" class="@CssClass"></span>
@code {
[Parameter] public DLoadingVariant Variant { get; set; }
[Parameter] public DSize Size { get; set; }
[Parameter] public DColor Color { get; set; }
/// <summary>What screen readers announce (aria-label).</summary>
[Parameter] public string LabelText { get; set; } = "Loading";
string CssClass => Cx("loading", VariantClass, SizeClass, ColorClass, Class);
string VariantClass => Variant switch
{
DLoadingVariant.Dots => "loading-dots",
DLoadingVariant.Ring => "loading-ring",
DLoadingVariant.Ball => "loading-ball",
DLoadingVariant.Bars => "loading-bars",
DLoadingVariant.Infinity => "loading-infinity",
_ => null
};
string SizeClass => Size switch
{
DSize.Xs => "loading-xs",
DSize.Sm => "loading-sm",
DSize.Lg => "loading-lg",
DSize.Xl => "loading-xl",
_ => null
};
string ColorClass => Color switch
{
DColor.Neutral => "text-neutral",
DColor.Primary => "text-primary",
DColor.Secondary => "text-secondary",
DColor.Accent => "text-accent",
DColor.Info => "text-info",
DColor.Success => "text-success",
DColor.Warning => "text-warning",
DColor.Error => "text-error",
_ => null
};
}
@@ -0,0 +1,12 @@
namespace decePubClient.Components.Daisy.Feedback;
/// <summary>The animation; Spinner is what a bare loading class draws.</summary>
public enum DLoadingVariant
{
Spinner,
Dots,
Ring,
Ball,
Bars,
Infinity
}
+30
View File
@@ -0,0 +1,30 @@
@using System.Globalization
@inherits DComponentBase
@* daisyUI Progress: https://daisyui.com/components/progress/ . A null Value leaves out the value attribute, which makes
the bar indeterminate (animated). Numbers are written with the invariant culture. The width comes from class
(it is 100% by default). *@
<progress @attributes="AdditionalAttributes" class="@CssClass" value="@(Value?.ToString(CultureInfo.InvariantCulture))"
max="@(Max.ToString(CultureInfo.InvariantCulture))"></progress>
@code {
/// <summary>The progress, from 0 to Max; null shows an indeterminate bar.</summary>
[Parameter] public double? Value { get; set; }
[Parameter] public double Max { get; set; } = 100;
[Parameter] public DColor Color { get; set; }
string CssClass => Cx("progress", ColorClass, Class);
string ColorClass => Color switch
{
DColor.Neutral => "progress-neutral",
DColor.Primary => "progress-primary",
DColor.Secondary => "progress-secondary",
DColor.Accent => "progress-accent",
DColor.Info => "progress-info",
DColor.Success => "progress-success",
DColor.Warning => "progress-warning",
DColor.Error => "progress-error",
_ => null
};
}
@@ -0,0 +1,63 @@
@using System.Globalization
@inherits DComponentBase
@* daisyUI Radial progress: https://daisyui.com/components/radial-progress/ . Value/Max becomes a 0-100 percentage in
the --value style variable and aria-valuenow, and the default text; Diameter and Thickness set --size and
--thickness. A consumer's style is appended to those. It is drawn in currentColor, so Color maps to text-*. *@
<div @attributes="AdditionalAttributes" class="@CssClass" style="@StyleValue" role="progressbar"
aria-valuenow="@PercentText" aria-valuemin="0" aria-valuemax="100">
@if (ChildContent is null)
{
@($"{Math.Round(Percent)}%")
}
else
{
@ChildContent
}
</div>
@code {
[Parameter] public double Value { get; set; }
[Parameter] public double Max { get; set; } = 100;
[Parameter] public DColor Color { get; set; }
/// <summary>A CSS length for --size (daisyUI's default is 5rem).</summary>
[Parameter] public string Diameter { get; set; }
/// <summary>A CSS length for --thickness (daisyUI's default is a tenth of the size).</summary>
[Parameter] public string Thickness { get; set; }
/// <summary>Replaces the default "NN%" text.</summary>
[Parameter] public RenderFragment ChildContent { get; set; }
double Percent => Max > 0 ? Math.Clamp(Value / Max * 100, 0, 100) : 0;
string PercentText => Math.Round(Percent, 2).ToString(CultureInfo.InvariantCulture);
string StyleValue
{
get
{
var style = $"--value:{PercentText};";
if (!string.IsNullOrEmpty(Diameter))
style += $" --size:{Diameter};";
if (!string.IsNullOrEmpty(Thickness))
style += $" --thickness:{Thickness};";
if (AdditionalAttributes != null && AdditionalAttributes.TryGetValue("style", out var extra) && extra is not null)
style += " " + extra;
return style;
}
}
string CssClass => Cx("radial-progress", ColorClass, Class);
string ColorClass => Color switch
{
DColor.Neutral => "text-neutral",
DColor.Primary => "text-primary",
DColor.Secondary => "text-secondary",
DColor.Accent => "text-accent",
DColor.Info => "text-info",
DColor.Success => "text-success",
DColor.Warning => "text-warning",
DColor.Error => "text-error",
_ => null
};
}
+13
View File
@@ -0,0 +1,13 @@
@inherits DComponentBase
@* daisyUI Skeleton: https://daisyui.com/components/skeleton/ . Size it with h-* / w-* classes; IsText (skeleton-text)
animates ChildContent's text instead of drawing a block. *@
<div @attributes="AdditionalAttributes" class="@Cx("skeleton", IsText ? "skeleton-text" : null, Class)">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
/// <summary>Shimmer the text in ChildContent (skeleton-text) rather than a filled block.</summary>
[Parameter] public bool IsText { get; set; }
}
+29
View File
@@ -0,0 +1,29 @@
@inherits DComponentBase
@* daisyUI Toast: https://daisyui.com/components/toast/ . A fixed-position stack (ChildContent, usually DAlerts) in a
corner or edge of the viewport; End/Bottom is daisyUI's default and adds no class. *@
<div @attributes="AdditionalAttributes" class="@CssClass">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DToastHorizontal Horizontal { get; set; }
[Parameter] public DToastVertical Vertical { get; set; }
string CssClass => Cx("toast", HorizontalClass, VerticalClass, Class);
string HorizontalClass => Horizontal switch
{
DToastHorizontal.Start => "toast-start",
DToastHorizontal.Center => "toast-center",
_ => null
};
string VerticalClass => Vertical switch
{
DToastVertical.Top => "toast-top",
DToastVertical.Middle => "toast-middle",
_ => null
};
}
+17
View File
@@ -0,0 +1,17 @@
namespace decePubClient.Components.Daisy.Feedback;
/// <summary>Where the toast sits horizontally; End is daisyUI's default.</summary>
public enum DToastHorizontal
{
End,
Start,
Center
}
/// <summary>Where the toast sits vertically; Bottom is daisyUI's default.</summary>
public enum DToastVertical
{
Bottom,
Top,
Middle
}
+51
View File
@@ -0,0 +1,51 @@
@inherits DComponentBase
@* daisyUI Tooltip: https://daisyui.com/components/tooltip/ . Shows on hover or keyboard focus of ChildContent: Tip
sets data-tip, or TipContent renders a tooltip-content part for markup. IsOpen keeps it shown (tooltip-open). *@
<div @attributes="AdditionalAttributes" class="@CssClass" data-tip="@Tip">
@if (TipContent is not null)
{
<div class="tooltip-content">@TipContent</div>
}
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Tip { get; set; }
[Parameter] public RenderFragment TipContent { get; set; }
[Parameter] public DTooltipPlacement Placement { get; set; }
[Parameter] public DTooltipAlign Align { get; set; }
/// <summary>Neutral is the default look; daisyUI has no tooltip-neutral.</summary>
[Parameter] public DColor Color { get; set; }
[Parameter] public bool IsOpen { get; set; }
string CssClass => Cx("tooltip", PlacementClass, AlignClass, ColorClass, IsOpen ? "tooltip-open" : null, Class);
string PlacementClass => Placement switch
{
DTooltipPlacement.Bottom => "tooltip-bottom",
DTooltipPlacement.Left => "tooltip-left",
DTooltipPlacement.Right => "tooltip-right",
_ => null
};
string AlignClass => Align switch
{
DTooltipAlign.Start => "tooltip-start",
DTooltipAlign.End => "tooltip-end",
_ => null
};
string ColorClass => Color switch
{
DColor.Primary => "tooltip-primary",
DColor.Secondary => "tooltip-secondary",
DColor.Accent => "tooltip-accent",
DColor.Info => "tooltip-info",
DColor.Success => "tooltip-success",
DColor.Warning => "tooltip-warning",
DColor.Error => "tooltip-error",
_ => null
};
}
@@ -0,0 +1,18 @@
namespace decePubClient.Components.Daisy.Feedback;
/// <summary>Which side of the element the tooltip shows on; Top is daisyUI's default.</summary>
public enum DTooltipPlacement
{
Top,
Bottom,
Left,
Right
}
/// <summary>How the tooltip lines up with the element along that side; Center is daisyUI's default.</summary>
public enum DTooltipAlign
{
Center,
Start,
End
}
+40
View File
@@ -0,0 +1,40 @@
@inherits DComponentBase
@* daisyUI Divider: https://daisyui.com/components/divider/ . Leave ChildContent empty for a blank line. *@
<div @attributes="AdditionalAttributes" class="@CssClass">@ChildContent</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DColor Color { get; set; }
[Parameter] public DDividerDirection Direction { get; set; }
[Parameter] public DDividerPlacement Placement { get; set; }
string CssClass => Cx("divider", DirectionClass, ColorClass, PlacementClass, Class);
string DirectionClass => Direction switch
{
DDividerDirection.Horizontal => "divider-horizontal",
DDividerDirection.Vertical => "divider-vertical",
_ => null
};
string ColorClass => Color switch
{
DColor.Neutral => "divider-neutral",
DColor.Primary => "divider-primary",
DColor.Secondary => "divider-secondary",
DColor.Accent => "divider-accent",
DColor.Info => "divider-info",
DColor.Success => "divider-success",
DColor.Warning => "divider-warning",
DColor.Error => "divider-error",
_ => null
};
string PlacementClass => Placement switch
{
DDividerPlacement.Start => "divider-start",
DDividerPlacement.End => "divider-end",
_ => null
};
}
+21
View File
@@ -0,0 +1,21 @@
namespace decePubClient.Components.Daisy.Layout;
/// <summary>
/// daisyUI's names, which describe the content around the divider: <see cref="Horizontal"/> (divider-horizontal) is a
/// vertical line between items side by side; <see cref="Vertical"/> (divider-vertical) is a horizontal line between
/// stacked items, as is the default.
/// </summary>
public enum DDividerDirection
{
Default,
Horizontal,
Vertical
}
/// <summary>Where the text sits along the line; Center is daisyUI's default.</summary>
public enum DDividerPlacement
{
Center,
Start,
End
}
+60
View File
@@ -0,0 +1,60 @@
@inherits DComponentBase
@* daisyUI Drawer: https://daisyui.com/components/drawer/ . daisyUI's hidden drawer-toggle checkbox is bound to IsOpen
(@bind-IsOpen); the overlay label toggles it natively, Escape closes it, and any
<label for="{ToggleId}" class="btn drawer-button"> in the content toggles it too. Put the navbar and footer in
ChildContent. *@
<div @attributes="AdditionalAttributes" class="@CssClass" @onkeydown="OnKeyDown">
<input id="@toggleId" type="checkbox" class="drawer-toggle" checked="@IsOpen" @onchange="OnChange" />
<div class="@Cx("drawer-content", ContentClass)">
@ChildContent
</div>
<div class="@Cx("drawer-side", SideClass)">
<label for="@toggleId" aria-label="@CloseText" class="drawer-overlay"></label>
@Side
</div>
</div>
@code {
/// <summary>The page content, beside the side.</summary>
[Parameter] public RenderFragment ChildContent { get; set; }
/// <summary>The sidebar, e.g. &lt;ul class="menu bg-base-200 min-h-full w-80 p-4"&gt;.</summary>
[Parameter] public RenderFragment Side { get; set; }
[Parameter] public bool IsOpen { get; set; }
[Parameter] public EventCallback<bool> IsOpenChanged { get; set; }
/// <summary>Put the side on the right (drawer-end).</summary>
[Parameter] public bool End { get; set; }
[Parameter] public DDrawerOpenFrom OpenFrom { get; set; }
/// <summary>The checkbox id, for drawer-button labels; defaults to a per-instance id.</summary>
[Parameter] public string ToggleId { get; set; }
/// <summary>Accessible name of the overlay, which closes the side.</summary>
[Parameter] public string CloseText { get; set; } = "close sidebar";
[Parameter] public string ContentClass { get; set; }
[Parameter] public string SideClass { get; set; }
readonly string uid = "d" + Guid.NewGuid().ToString("N")[..8];
string toggleId => ToggleId ?? uid;
string CssClass => Cx("drawer", End ? "drawer-end" : null, OpenFromClass, Class);
string OpenFromClass => OpenFrom switch
{
DDrawerOpenFrom.Sm => "sm:drawer-open",
DDrawerOpenFrom.Md => "md:drawer-open",
DDrawerOpenFrom.Lg => "lg:drawer-open",
DDrawerOpenFrom.Xl => "xl:drawer-open",
DDrawerOpenFrom.Always => "drawer-open",
_ => null
};
Task OnChange(ChangeEventArgs e) => SetOpen(e.Value is true);
Task OnKeyDown(KeyboardEventArgs e) => e.Key == "Escape" && IsOpen ? SetOpen(false) : Task.CompletedTask;
async Task SetOpen(bool isOpen)
{
IsOpen = isOpen;
await IsOpenChanged.InvokeAsync(isOpen);
}
}
+12
View File
@@ -0,0 +1,12 @@
namespace decePubClient.Components.Daisy.Layout;
/// <summary>From which breakpoint the side stays open beside the content (drawer-open), whatever IsOpen says.</summary>
public enum DDrawerOpenFrom
{
Never,
Sm,
Md,
Lg,
Xl,
Always
}
+25
View File
@@ -0,0 +1,25 @@
@inherits DComponentBase
@* daisyUI Footer: https://daisyui.com/components/footer/ . Its children are laid out on a grid; use DFooterNav (or
<nav> / <aside>) for each column. *@
<footer @attributes="AdditionalAttributes" class="@CssClass">
@ChildContent
</footer>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DFooterDirection Direction { get; set; }
/// <summary>Center the children (footer-center).</summary>
[Parameter] public bool Center { get; set; }
string CssClass => Cx("footer", DirectionClass, Center ? "footer-center" : null, Class);
string DirectionClass => Direction switch
{
DFooterDirection.Horizontal => "footer-horizontal",
DFooterDirection.HorizontalFromSm => "sm:footer-horizontal",
DFooterDirection.HorizontalFromMd => "md:footer-horizontal",
DFooterDirection.HorizontalFromLg => "lg:footer-horizontal",
_ => null
};
}
+12
View File
@@ -0,0 +1,12 @@
namespace decePubClient.Components.Daisy.Layout;
/// <summary>How the footer lays out its children: in a column (daisyUI's default), in a row, or in a row from a
/// breakpoint up (the responsive sm:footer-horizontal that daisyUI recommends).</summary>
public enum DFooterDirection
{
Vertical,
Horizontal,
HorizontalFromSm,
HorizontalFromMd,
HorizontalFromLg
}
+15
View File
@@ -0,0 +1,15 @@
@inherits DComponentBase
@* One column of a DFooter: a <nav> with an optional footer-title heading above its links. *@
<nav @attributes="AdditionalAttributes" class="@Class">
@if (Heading is not null)
{
<h6 class="footer-title">@Heading</h6>
}
@ChildContent
</nav>
@code {
[Parameter] public RenderFragment Heading { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
}
+36
View File
@@ -0,0 +1,36 @@
@inherits DComponentBase
@* daisyUI Hero: https://daisyui.com/components/hero/ . ChildContent goes in hero-content (text, and optionally a
figure); with Overlay a hero-overlay tints the background image. *@
<div @attributes="AdditionalAttributes" class="@CssClass" style="@StyleValue">
@if (Overlay)
{
<div class="@Cx("hero-overlay", OverlayClass)"></div>
}
<div class="@Cx("hero-content", ContentClass)">
@ChildContent
</div>
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
/// <summary>URL of a background image, set as an inline background-image.</summary>
[Parameter] public string BackgroundImage { get; set; }
/// <summary>Add a hero-overlay color layer above the background image.</summary>
[Parameter] public bool Overlay { get; set; }
[Parameter] public string OverlayClass { get; set; }
/// <summary>Classes for hero-content, e.g. "text-center" or "flex-col lg:flex-row".</summary>
[Parameter] public string ContentClass { get; set; }
string CssClass => Cx("hero", Class);
// The consumer's own style attribute is kept, after the background image.
string StyleValue
{
get
{
var style = AdditionalAttributes?.GetValueOrDefault("style")?.ToString();
return BackgroundImage is null ? style : Cx($"background-image: url('{BackgroundImage}');", style);
}
}
}
+13
View File
@@ -0,0 +1,13 @@
@inherits DComponentBase
@* daisyUI Indicator: https://daisyui.com/components/indicator/ . Put the DIndicatorItem children first and the main
element after them, as daisyUI requires. *@
<div @attributes="AdditionalAttributes" class="@CssClass">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
string CssClass => Cx("indicator", Class);
}
@@ -0,0 +1,17 @@
namespace decePubClient.Components.Daisy.Layout;
/// <summary>Horizontal position of an indicator item; End is daisyUI's default.</summary>
public enum DIndicatorHorizontal
{
End,
Start,
Center
}
/// <summary>Vertical position of an indicator item; Top is daisyUI's default.</summary>
public enum DIndicatorVertical
{
Top,
Middle,
Bottom
}
@@ -0,0 +1,26 @@
@inherits DComponentBase
@* An indicator-item of a DIndicator, placed on its corner or edge, e.g. with class="badge badge-primary". *@
<span @attributes="AdditionalAttributes" class="@CssClass">@ChildContent</span>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DIndicatorHorizontal Horizontal { get; set; }
[Parameter] public DIndicatorVertical Vertical { get; set; }
string CssClass => Cx("indicator-item", HorizontalClass, VerticalClass, Class);
string HorizontalClass => Horizontal switch
{
DIndicatorHorizontal.Start => "indicator-start",
DIndicatorHorizontal.Center => "indicator-center",
_ => null
};
string VerticalClass => Vertical switch
{
DIndicatorVertical.Middle => "indicator-middle",
DIndicatorVertical.Bottom => "indicator-bottom",
_ => null
};
}
+15
View File
@@ -0,0 +1,15 @@
@inherits DComponentBase
@* daisyUI Join: https://daisyui.com/components/join/ . Groups its direct children, rounding only the outer corners;
give each of them the join-item class (e.g. <DButton class="join-item">). For a responsive group, add
class="lg:join-horizontal" to a vertical one. *@
<div @attributes="AdditionalAttributes" class="@CssClass">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DDirection Direction { get; set; }
string CssClass => Cx("join", Direction == DDirection.Vertical ? "join-vertical" : null, Class);
}
+47
View File
@@ -0,0 +1,47 @@
@inherits DComponentBase
@* daisyUI Mask: https://daisyui.com/components/mask/ . Renders an <img> when Src is set, otherwise a <div> around
ChildContent. daisyUI requires a Shape or a Half; size it with w-* and h-*. *@
@if (Src is not null)
{
<img @attributes="AdditionalAttributes" src="@Src" class="@CssClass" />
}
else
{
<div @attributes="AdditionalAttributes" class="@CssClass">@ChildContent</div>
}
@code {
[Parameter] public string Src { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DMaskShape Shape { get; set; }
[Parameter] public DMaskHalf Half { get; set; }
string CssClass => Cx("mask", ShapeClass, HalfClass, Class);
string ShapeClass => Shape switch
{
DMaskShape.Squircle => "mask-squircle",
DMaskShape.Heart => "mask-heart",
DMaskShape.Hexagon => "mask-hexagon",
DMaskShape.Hexagon2 => "mask-hexagon-2",
DMaskShape.Decagon => "mask-decagon",
DMaskShape.Pentagon => "mask-pentagon",
DMaskShape.Diamond => "mask-diamond",
DMaskShape.Circle => "mask-circle",
DMaskShape.Star => "mask-star",
DMaskShape.Star2 => "mask-star-2",
DMaskShape.Triangle => "mask-triangle",
DMaskShape.Triangle2 => "mask-triangle-2",
DMaskShape.Triangle3 => "mask-triangle-3",
DMaskShape.Triangle4 => "mask-triangle-4",
_ => null
};
string HalfClass => Half switch
{
DMaskHalf.First => "mask-half-1",
DMaskHalf.Second => "mask-half-2",
_ => null
};
}
+28
View File
@@ -0,0 +1,28 @@
namespace decePubClient.Components.Daisy.Layout;
public enum DMaskShape
{
None,
Squircle,
Heart,
Hexagon,
Hexagon2,
Decagon,
Pentagon,
Diamond,
Circle,
Star,
Star2,
Triangle,
Triangle2,
Triangle3,
Triangle4
}
/// <summary>Keep only the first or second half of the shape (mask-half-1 / mask-half-2), as rating does.</summary>
public enum DMaskHalf
{
None,
First,
Second
}
+22
View File
@@ -0,0 +1,22 @@
@inherits DComponentBase
@* daisyUI Stack: https://daisyui.com/components/stack/ . The first child is on top; w-* and h-* on the stack make
all its children the same size. *@
<div @attributes="AdditionalAttributes" class="@CssClass">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DStackPlacement Placement { get; set; }
string CssClass => Cx("stack", PlacementClass, Class);
string PlacementClass => Placement switch
{
DStackPlacement.Top => "stack-top",
DStackPlacement.Start => "stack-start",
DStackPlacement.End => "stack-end",
_ => null
};
}
+10
View File
@@ -0,0 +1,10 @@
namespace decePubClient.Components.Daisy.Layout;
/// <summary>Which side the items behind the first one peek out from; Bottom is daisyUI's default.</summary>
public enum DStackPlacement
{
Bottom,
Top,
Start,
End
}
@@ -0,0 +1,30 @@
@inherits DComponentBase
@* daisyUI Browser mockup: https://daisyui.com/components/mockup-browser/ . The toolbar shows Toolbar if given,
otherwise Url in an address bar (a div with the input class). *@
<div @attributes="AdditionalAttributes" class="@CssClass">
<div class="mockup-browser-toolbar">
@if (Toolbar is not null)
{
@Toolbar
}
else if (Url is not null)
{
<div class="input">@Url</div>
}
</div>
<div class="@ContentClass">
@ChildContent
</div>
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
/// <summary>Text of the address bar.</summary>
[Parameter] public string Url { get; set; }
/// <summary>Custom toolbar content, in place of the address bar.</summary>
[Parameter] public RenderFragment Toolbar { get; set; }
[Parameter] public string ContentClass { get; set; }
string CssClass => Cx("mockup-browser", Class);
}
+12
View File
@@ -0,0 +1,12 @@
@inherits DComponentBase
@* daisyUI Code mockup: https://daisyui.com/components/mockup-code/ . Each line is a DMockupCodeLine. *@
<div @attributes="AdditionalAttributes" class="@CssClass">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
string CssClass => Cx("mockup-code", Class);
}
@@ -0,0 +1,10 @@
@inherits DComponentBase
@* One line of a DMockupCode: <pre data-prefix><code>. Highlight it with a class, e.g. "bg-warning text-warning-content". *@
<pre @attributes="AdditionalAttributes" data-prefix="@Prefix" class="@Class"><code>@ChildContent</code></pre>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
/// <summary>Shown before the line, e.g. "$", "&gt;" or a line number.</summary>
[Parameter] public string Prefix { get; set; }
}
@@ -0,0 +1,16 @@
@inherits DComponentBase
@* daisyUI Phone mockup: https://daisyui.com/components/mockup-phone/ . ChildContent fills the display. *@
<div @attributes="AdditionalAttributes" class="@CssClass">
<div class="mockup-phone-camera"></div>
<div class="@Cx("mockup-phone-display", DisplayClass)">
@ChildContent
</div>
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string DisplayClass { get; set; }
string CssClass => Cx("mockup-phone", Class);
}
@@ -0,0 +1,15 @@
@inherits DComponentBase
@* daisyUI Window mockup: https://daisyui.com/components/mockup-window/ . *@
<div @attributes="AdditionalAttributes" class="@CssClass">
<div class="@ContentClass">
@ChildContent
</div>
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string ContentClass { get; set; }
string CssClass => Cx("mockup-window", Class);
}
@@ -0,0 +1,23 @@
@inherits DComponentBase
@* One crumb of a DBreadcrumbs: <li><a> with Href, otherwise <li><span> (the current page). class and other
attributes go on the <a>/<span>. *@
<li>
@if (Href is null)
{
<span @attributes="AdditionalAttributes" class="@Cx("inline-flex items-center gap-2", Class)">
@ChildContent
</span>
}
else
{
<a @attributes="AdditionalAttributes" href="@Href" class="@Class">
@ChildContent
</a>
}
</li>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Href { get; set; }
}
@@ -0,0 +1,13 @@
@inherits DComponentBase
@* daisyUI Breadcrumbs: https://daisyui.com/components/breadcrumbs/ . Holds DBreadcrumbItem children; a max-width
(e.g. class="max-w-xs") makes the list scroll. *@
<div @attributes="AdditionalAttributes" class="@Cx("breadcrumbs", Class)">
<ul>
@ChildContent
</ul>
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
}
+21
View File
@@ -0,0 +1,21 @@
@inherits DComponentBase
@* daisyUI Dock: https://daisyui.com/components/dock/ . A bar fixed to the bottom of the viewport holding DDockItem
children; add class="relative" to place it in the flow instead. *@
<div @attributes="AdditionalAttributes" class="@Cx("dock", SizeClass, Class)">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public DSize Size { get; set; }
string SizeClass => Size switch
{
DSize.Xs => "dock-xs",
DSize.Sm => "dock-sm",
DSize.Lg => "dock-lg",
DSize.Xl => "dock-xl",
_ => null
};
}
@@ -0,0 +1,38 @@
@inherits DComponentBase
@* One button of a DDock: ChildContent is the icon and Text the dock-label under it. Renders an <a> when Href is set,
with aria-current="page" while Active. *@
@if (Href is null)
{
<button @attributes="AdditionalAttributes" type="button" class="@CssClass" disabled="@Disabled" @onclick="OnClick">
@ChildContent
@if (Text is not null)
{
<span class="dock-label">@Text</span>
}
</button>
}
else
{
<a @attributes="AdditionalAttributes" href="@(Disabled ? null : Href)" class="@CssClass"
aria-current="@(Active ? "page" : null)" aria-disabled="@(Disabled ? "true" : null)" tabindex="@(Disabled ? "-1" : null)"
@onclick="OnClick">
@ChildContent
@if (Text is not null)
{
<span class="dock-label">@Text</span>
}
</a>
}
@code {
/// <summary>The icon.</summary>
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Text { get; set; }
[Parameter] public bool Active { get; set; }
[Parameter] public bool Disabled { get; set; }
[Parameter] public string Href { get; set; }
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
string CssClass => Cx(Active ? "dock-active" : null, Class);
}
+40
View File
@@ -0,0 +1,40 @@
@inherits DComponentBase
@* daisyUI Link: https://daisyui.com/components/link/ . Without Href it renders a <button> styled as a link, so an
OnClick-only link stays keyboard-reachable. *@
@if (Href is null)
{
<button @attributes="AdditionalAttributes" type="button" class="@CssClass" @onclick="OnClick">
@ChildContent
</button>
}
else
{
<a @attributes="AdditionalAttributes" href="@Href" class="@CssClass" @onclick="OnClick">
@ChildContent
</a>
}
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Href { get; set; }
[Parameter] public DColor Color { get; set; }
/// <summary>Underline only on hover (link-hover).</summary>
[Parameter] public bool Hover { get; set; }
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
string CssClass => Cx("link", ColorClass, Hover ? "link-hover" : null, Class);
string ColorClass => Color switch
{
DColor.Neutral => "link-neutral",
DColor.Primary => "link-primary",
DColor.Secondary => "link-secondary",
DColor.Accent => "link-accent",
DColor.Info => "link-info",
DColor.Success => "link-success",
DColor.Warning => "link-warning",
DColor.Error => "link-error",
_ => null
};
}
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff. Show more