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:
1 parent
8e03d12a3e
commit
90e4b9200c
127 files changed
+6962
-17
No files matched your search
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
@inherits DComponentBase
|
||||
|
||||
@* daisyUI Megamenu: https://daisyui.com/components/megamenu/ . Built on the native popover API, as daisyUI specifies:
|
||||
each DMegamenuItem renders a button and the popover it opens (per-instance ids), so opening, light dismiss and
|
||||
Escape are the browser's. The root is a popover too: with Responsive it turns, below sm, into a vertical menu that
|
||||
the toggle button opens. daisyUI supports at most 10 items. text-inherit undoes the browser's popover CanvasText
|
||||
colour, which daisyUI resets for dropdowns but not here. *@
|
||||
@if (Responsive || Direction == DDirection.Vertical)
|
||||
{
|
||||
<button type="button" class="@Cx("btn", Direction == DDirection.Vertical ? null : "sm:hidden", ToggleClass)" popovertarget="@uid">
|
||||
@ToggleText
|
||||
</button>
|
||||
}
|
||||
<div @attributes="AdditionalAttributes" id="@uid" popover class="@CssClass">
|
||||
<span class="megamenu-active"></span>
|
||||
@ChildContent
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>The DMegamenuItem children.</summary>
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
[Parameter] public DMegamenuPanelWidth PanelWidth { get; set; }
|
||||
[Parameter] public DSize Size { get; set; }
|
||||
/// <summary>
|
||||
/// Vertical (megamenu-vertical) hides the menu until the toggle button opens it, at every width. With Horizontal
|
||||
/// that happens only below sm when Responsive is set.
|
||||
/// </summary>
|
||||
[Parameter] public DDirection Direction { get; set; }
|
||||
/// <summary>Below sm, hide the menu and show the toggle button that opens it vertically (max-sm:megamenu-vertical).</summary>
|
||||
[Parameter] public bool Responsive { get; set; } = true;
|
||||
[Parameter] public string ToggleText { get; set; } = "Menu";
|
||||
[Parameter] public string ToggleClass { get; set; }
|
||||
|
||||
readonly string uid = "d" + Guid.NewGuid().ToString("N")[..8];
|
||||
|
||||
string CssClass => Cx("megamenu text-inherit", DirectionClass, PanelWidthClass, SizeClass, Class);
|
||||
|
||||
string DirectionClass => Direction == DDirection.Vertical ? "megamenu-vertical" : Responsive ? "max-sm:megamenu-vertical" : null;
|
||||
|
||||
string PanelWidthClass => PanelWidth switch
|
||||
{
|
||||
DMegamenuPanelWidth.Wide => "megamenu-wide",
|
||||
DMegamenuPanelWidth.Full => "megamenu-full",
|
||||
_ => null
|
||||
};
|
||||
|
||||
string SizeClass => Size switch
|
||||
{
|
||||
DSize.Xs => "megamenu-xs",
|
||||
DSize.Sm => "megamenu-sm",
|
||||
DSize.Lg => "megamenu-lg",
|
||||
DSize.Xl => "megamenu-xl",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace decePubClient.Components.Daisy.Navigation;
|
||||
|
||||
/// <summary>
|
||||
/// How wide each item's popover is: Default sizes it to its content next to its button, Wide spans the megamenu
|
||||
/// (megamenu-wide) and Full spans the viewport (megamenu-full).
|
||||
/// </summary>
|
||||
public enum DMegamenuPanelWidth
|
||||
{
|
||||
Default,
|
||||
Wide,
|
||||
Full
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
@inherits DComponentBase
|
||||
|
||||
@* One item of a DMegamenu: its button and the popover it opens, rendered as siblings because daisyUI pairs them by
|
||||
position (nth-of-type) among the megamenu's direct children. class and other attributes go on the popover. *@
|
||||
<button type="button" class="@Cx(HideArrow ? "after:content-none" : null, ButtonClass)" popovertarget="@uid">
|
||||
@if (Header is null)
|
||||
{
|
||||
@Text
|
||||
}
|
||||
else
|
||||
{
|
||||
@Header
|
||||
}
|
||||
</button>
|
||||
<div @attributes="AdditionalAttributes" id="@uid" popover class="@Cx("text-inherit", Class)">
|
||||
@ChildContent
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>The button's text; Header replaces it with markup.</summary>
|
||||
[Parameter] public string Text { get; set; }
|
||||
[Parameter] public RenderFragment Header { get; set; }
|
||||
/// <summary>The popover's content: a DMenu or anything else.</summary>
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
/// <summary>Drop the arrow daisyUI draws after the button's text (after:content-none).</summary>
|
||||
[Parameter] public bool HideArrow { get; set; }
|
||||
[Parameter] public string ButtonClass { get; set; }
|
||||
|
||||
readonly string uid = "d" + Guid.NewGuid().ToString("N")[..8];
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
@inherits DComponentBase
|
||||
|
||||
@* daisyUI Menu: https://daisyui.com/components/menu/ . DMenuItem, DMenuTitle and DSubmenu render their own <li>; an
|
||||
empty <li></li> is a divider. Vertical is daisyUI's default layout and adds no class. *@
|
||||
<ul @attributes="AdditionalAttributes" class="@CssClass">
|
||||
@ChildContent
|
||||
</ul>
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
[Parameter] public DDirection Direction { get; set; } = DDirection.Vertical;
|
||||
[Parameter] public DSize Size { get; set; }
|
||||
/// <summary>Show one submenu level at a time (menu-paged); an open submenu's summary becomes its back button.</summary>
|
||||
[Parameter] public bool Paged { get; set; }
|
||||
|
||||
string CssClass => Cx("menu", Direction == DDirection.Horizontal ? "menu-horizontal" : null, SizeClass,
|
||||
Paged ? "menu-paged" : null, Class);
|
||||
|
||||
string SizeClass => Size switch
|
||||
{
|
||||
DSize.Xs => "menu-xs",
|
||||
DSize.Sm => "menu-sm",
|
||||
DSize.Lg => "menu-lg",
|
||||
DSize.Xl => "menu-xl",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
@inherits DComponentBase
|
||||
|
||||
@* One item of a DMenu: an <li> holding an <a> when Href is set, otherwise a <button>. class and other attributes go
|
||||
on the <a>/<button>, which also carries menu-active; menu-disabled goes on the <li>, as daisyUI expects. *@
|
||||
<li class="@(Disabled ? "menu-disabled" : null)">
|
||||
@if (Href is null)
|
||||
{
|
||||
<button @attributes="AdditionalAttributes" type="button" class="@CssClass" disabled="@Disabled" @onclick="OnClick">
|
||||
@ChildContent
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a @attributes="AdditionalAttributes" href="@(Disabled ? null : Href)" class="@CssClass"
|
||||
aria-disabled="@(Disabled ? "true" : null)" tabindex="@(Disabled ? "-1" : null)" @onclick="OnClick">
|
||||
@ChildContent
|
||||
</a>
|
||||
}
|
||||
</li>
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
[Parameter] public string Href { get; set; }
|
||||
[Parameter] public bool Active { get; set; }
|
||||
[Parameter] public bool Disabled { get; set; }
|
||||
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
|
||||
|
||||
string CssClass => Cx(Active ? "menu-active" : null, Class);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
@inherits DComponentBase
|
||||
|
||||
@* A title row of a DMenu: <li class="menu-title">. *@
|
||||
<li @attributes="AdditionalAttributes" class="@Cx("menu-title", Class)">
|
||||
@ChildContent
|
||||
</li>
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
@inherits DComponentBase
|
||||
|
||||
@* daisyUI Navbar: https://daisyui.com/components/navbar/ . Start, Center and End each render their navbar-* part only
|
||||
when set; ChildContent goes after them, for content that doesn't fit the three slots. *@
|
||||
<div @attributes="AdditionalAttributes" class="@Cx("navbar", Class)">
|
||||
@if (Start is not null)
|
||||
{
|
||||
<div class="@Cx("navbar-start", StartClass)">@Start</div>
|
||||
}
|
||||
@if (Center is not null)
|
||||
{
|
||||
<div class="@Cx("navbar-center", CenterClass)">@Center</div>
|
||||
}
|
||||
@if (End is not null)
|
||||
{
|
||||
<div class="@Cx("navbar-end", EndClass)">@End</div>
|
||||
}
|
||||
@ChildContent
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment Start { get; set; }
|
||||
[Parameter] public RenderFragment Center { get; set; }
|
||||
[Parameter] public RenderFragment End { get; set; }
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
[Parameter] public string StartClass { get; set; }
|
||||
[Parameter] public string CenterClass { get; set; }
|
||||
[Parameter] public string EndClass { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
@inherits DComponentBase
|
||||
|
||||
@* daisyUI Pagination: https://daisyui.com/components/pagination/ . A join of btn join-items; the page is 1-based
|
||||
(@bind-Current) and the current one is btn-active with aria-current="page". Beyond Siblings pages on each side of
|
||||
the current one, runs of pages collapse into a disabled ellipsis; the first and last pages always show. *@
|
||||
<div @attributes="AdditionalAttributes" class="@Cx("join", Direction == DDirection.Vertical ? "join-vertical" : null, Class)">
|
||||
@if (ShowPreviousNext)
|
||||
{
|
||||
<button type="button" class="@ItemClass(false)" disabled="@(Current <= 1)" aria-label="@PreviousAriaLabel"
|
||||
@onclick="() => Go(Current - 1)">
|
||||
@PreviousText
|
||||
</button>
|
||||
}
|
||||
@foreach (var number in Pages())
|
||||
{
|
||||
if (number == 0)
|
||||
{
|
||||
<button type="button" class="@Cx(ItemClass(false), "btn-disabled")" disabled aria-hidden="true">@EllipsisText</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
var target = number;
|
||||
<button type="button" class="@ItemClass(number == Current)" aria-current="@(number == Current ? "page" : null)"
|
||||
@onclick="() => Go(target)">
|
||||
@number
|
||||
</button>
|
||||
}
|
||||
}
|
||||
@if (ShowPreviousNext)
|
||||
{
|
||||
<button type="button" class="@ItemClass(false)" disabled="@(Current >= Count)" aria-label="@NextAriaLabel"
|
||||
@onclick="() => Go(Current + 1)">
|
||||
@NextText
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>The number of pages.</summary>
|
||||
[Parameter] public int Count { get; set; }
|
||||
/// <summary>The current page, from 1 to Count.</summary>
|
||||
[Parameter] public int Current { get; set; } = 1;
|
||||
[Parameter] public EventCallback<int> CurrentChanged { get; set; }
|
||||
/// <summary>How many pages to show on each side of the current one before collapsing the rest into "…".</summary>
|
||||
[Parameter] public int Siblings { get; set; } = 1;
|
||||
[Parameter] public DSize Size { get; set; }
|
||||
[Parameter] public DDirection Direction { get; set; }
|
||||
[Parameter] public bool ShowPreviousNext { get; set; } = true;
|
||||
[Parameter] public string PreviousText { get; set; } = "«";
|
||||
[Parameter] public string NextText { get; set; } = "»";
|
||||
[Parameter] public string PreviousAriaLabel { get; set; } = "Previous page";
|
||||
[Parameter] public string NextAriaLabel { get; set; } = "Next page";
|
||||
[Parameter] public string EllipsisText { get; set; } = "…";
|
||||
|
||||
string ItemClass(bool active) => Cx("join-item btn", SizeClass, active ? "btn-active" : null);
|
||||
|
||||
string SizeClass => Size switch
|
||||
{
|
||||
DSize.Xs => "btn-xs",
|
||||
DSize.Sm => "btn-sm",
|
||||
DSize.Lg => "btn-lg",
|
||||
DSize.Xl => "btn-xl",
|
||||
_ => null
|
||||
};
|
||||
|
||||
// The pages to show in order, with 0 standing for an ellipsis. A gap of a single page shows that page instead.
|
||||
IEnumerable<int> Pages()
|
||||
{
|
||||
if (Count <= 0)
|
||||
yield break;
|
||||
var current = Math.Clamp(Current, 1, Count);
|
||||
var siblings = Math.Clamp(Siblings, 0, Count);
|
||||
var from = Math.Max(1, current - siblings);
|
||||
var to = Math.Min(Count, current + siblings);
|
||||
if (from > 1)
|
||||
{
|
||||
yield return 1;
|
||||
if (from > 2)
|
||||
yield return from == 3 ? 2 : 0;
|
||||
}
|
||||
for (var page = from; page <= to; page++)
|
||||
yield return page;
|
||||
if (to < Count)
|
||||
{
|
||||
if (to < Count - 1)
|
||||
yield return to == Count - 2 ? Count - 1 : 0;
|
||||
yield return Count;
|
||||
}
|
||||
}
|
||||
|
||||
async Task Go(int page)
|
||||
{
|
||||
page = Math.Clamp(page, 1, Math.Max(Count, 1));
|
||||
if (page == Current)
|
||||
return;
|
||||
Current = page;
|
||||
await CurrentChanged.InvokeAsync(page);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
@inherits DComponentBase
|
||||
|
||||
@* One step of a DSteps: <li class="step">. A Color marks it (usually the completed steps); Marker replaces the number
|
||||
in its circle (data-content) and Icon replaces the circle's content with a step-icon. *@
|
||||
<li @attributes="AdditionalAttributes" class="@Cx("step", ColorClass, Class)" data-content="@Marker">
|
||||
@if (Icon is not null)
|
||||
{
|
||||
<span class="step-icon">@Icon</span>
|
||||
}
|
||||
@ChildContent
|
||||
</li>
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
[Parameter] public DColor Color { get; set; }
|
||||
/// <summary>Text shown in the step's circle instead of its number, e.g. "✓" or "?" (data-content).</summary>
|
||||
[Parameter] public string Marker { get; set; }
|
||||
[Parameter] public RenderFragment Icon { get; set; }
|
||||
|
||||
string ColorClass => Color switch
|
||||
{
|
||||
DColor.Neutral => "step-neutral",
|
||||
DColor.Primary => "step-primary",
|
||||
DColor.Secondary => "step-secondary",
|
||||
DColor.Accent => "step-accent",
|
||||
DColor.Info => "step-info",
|
||||
DColor.Success => "step-success",
|
||||
DColor.Warning => "step-warning",
|
||||
DColor.Error => "step-error",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
@inherits DComponentBase
|
||||
|
||||
@* daisyUI Steps: https://daisyui.com/components/steps/ . Holds DStep children; horizontal is daisyUI's default and adds
|
||||
no class (for a responsive layout write class="steps-vertical lg:steps-horizontal" yourself). *@
|
||||
<ul @attributes="AdditionalAttributes" class="@Cx("steps", Direction == DDirection.Vertical ? "steps-vertical" : null, Class)">
|
||||
@ChildContent
|
||||
</ul>
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
[Parameter] public DDirection Direction { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
@inherits DComponentBase
|
||||
|
||||
@* A collapsible submenu of a DMenu: <li><details><summary>. The open state lives in C# (@bind-IsOpen): <details>
|
||||
renders open from IsOpen, and the summary's click toggles IsOpen with the native toggle prevented, so the browser
|
||||
and Blazor never fight over it. Escape closes the innermost open one (keydown stops bubbling while it is open). *@
|
||||
<li @attributes="AdditionalAttributes" class="@Cx(Disabled ? "menu-disabled" : null, Class)">
|
||||
<details open="@IsOpen" @onkeydown="OnKeyDown" @onkeydown:stopPropagation="IsOpen">
|
||||
<summary class="@HeaderClass" aria-label="@(IsOpen ? BackText : null)" @onclick="Toggle" @onclick:preventDefault="true">
|
||||
@Header
|
||||
</summary>
|
||||
<ul class="@ContentClass">
|
||||
@ChildContent
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
|
||||
@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 bool Disabled { get; set; }
|
||||
/// <summary>
|
||||
/// In a paged menu (DMenu Paged) the open summary turns into a back button reading "Back"; this replaces that text.
|
||||
/// It is set as the summary's aria-label while open, which is where daisyUI reads it from.
|
||||
/// </summary>
|
||||
[Parameter] public string BackText { get; set; }
|
||||
[Parameter] public string HeaderClass { get; set; }
|
||||
[Parameter] public string ContentClass { get; set; }
|
||||
|
||||
async Task Toggle()
|
||||
{
|
||||
if (Disabled)
|
||||
return;
|
||||
IsOpen = !IsOpen;
|
||||
await IsOpenChanged.InvokeAsync(IsOpen);
|
||||
}
|
||||
|
||||
async Task OnKeyDown(KeyboardEventArgs e)
|
||||
{
|
||||
if (e.Key != "Escape" || !IsOpen)
|
||||
return;
|
||||
IsOpen = false;
|
||||
await IsOpenChanged.InvokeAsync(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
@implements IDisposable
|
||||
|
||||
@* One tab of a DTabs: renders nothing itself; DTabs renders its Header as the tab button and its ChildContent as
|
||||
the panel while it is active. *@
|
||||
|
||||
@code {
|
||||
[CascadingParameter] DTabs Parent { get; set; }
|
||||
[Parameter] public RenderFragment Header { get; set; }
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
[Parameter] public bool Disabled { get; set; }
|
||||
[Parameter] public string Class { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (Parent is null)
|
||||
throw new InvalidOperationException($"{nameof(DTab)} must be inside a {nameof(DTabs)}.");
|
||||
Parent.Add(this);
|
||||
}
|
||||
|
||||
public void Dispose() => Parent?.Remove(this);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
@inherits DComponentBase
|
||||
|
||||
@* daisyUI Tab: https://daisyui.com/components/tab/ . Each DTab child registers itself through the cascading value;
|
||||
DTabs renders the tab buttons and the active tab's content. The active tab is an index (@bind-Active). *@
|
||||
<CascadingValue Value="this" IsFixed="true">
|
||||
@ChildContent
|
||||
</CascadingValue>
|
||||
|
||||
<div @attributes="AdditionalAttributes" role="tablist" class="@CssClass">
|
||||
@for (var i = 0; i < tabs.Count; i++)
|
||||
{
|
||||
var index = i;
|
||||
var tab = tabs[i];
|
||||
<button type="button" role="tab" aria-selected="@(index == Active ? "true" : "false")" disabled="@tab.Disabled"
|
||||
class="@Cx("tab", index == Active ? "tab-active" : null, tab.Disabled ? "tab-disabled" : null, tab.Class)"
|
||||
@onclick="() => Select(index)">
|
||||
@tab.Header
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (Active >= 0 && Active < tabs.Count)
|
||||
{
|
||||
<div role="tabpanel" class="@ContentClass">
|
||||
@tabs[Active].ChildContent
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
[Parameter] public int Active { get; set; }
|
||||
[Parameter] public EventCallback<int> ActiveChanged { get; set; }
|
||||
[Parameter] public DTabsVariant Variant { get; set; }
|
||||
[Parameter] public DSize Size { get; set; }
|
||||
[Parameter] public bool Bottom { get; set; }
|
||||
[Parameter] public string ContentClass { get; set; } = "pt-4";
|
||||
|
||||
readonly List<DTab> tabs = new();
|
||||
|
||||
string CssClass => Cx("tabs", VariantClass, SizeClass, Bottom ? "tabs-bottom" : null, Class);
|
||||
|
||||
string VariantClass => Variant switch
|
||||
{
|
||||
DTabsVariant.Box => "tabs-box",
|
||||
DTabsVariant.Border => "tabs-border",
|
||||
DTabsVariant.Lift => "tabs-lift",
|
||||
_ => null
|
||||
};
|
||||
|
||||
string SizeClass => Size switch
|
||||
{
|
||||
DSize.Xs => "tabs-xs",
|
||||
DSize.Sm => "tabs-sm",
|
||||
DSize.Lg => "tabs-lg",
|
||||
DSize.Xl => "tabs-xl",
|
||||
_ => null
|
||||
};
|
||||
|
||||
internal void Add(DTab tab)
|
||||
{
|
||||
tabs.Add(tab);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
internal void Remove(DTab tab)
|
||||
{
|
||||
tabs.Remove(tab);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
async Task Select(int index)
|
||||
{
|
||||
Active = index;
|
||||
await ActiveChanged.InvokeAsync(index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace decePubClient.Components.Daisy.Navigation;
|
||||
|
||||
public enum DTabsVariant
|
||||
{
|
||||
Default,
|
||||
Box,
|
||||
Border,
|
||||
Lift
|
||||
}
|
||||
Reference in new issue
Block a user