From d71040f26275209c80316025d33dda6d261cdcd0 Mon Sep 17 00:00:00 2001 From: thepra Date: Sat, 3 Oct 2026 12:43:07 +0200 Subject: [PATCH] Add carousel navigation and the mixed checkbox state, without script These were left out because daisyUI does them with scripts or with links that breaks. DCarousel: - ShowNavigation adds previous/next links on each slide, and ShowIndicators adds a row of numbered links under the strip. - The links point at the slides' ids under the current path, so Blazor treats them as same-page fragments and scrolls the strip. DCheckbox: - Indeterminate renders aria-checked="mixed", which daisyUI draws like :indeterminate. - A click clears it through IndeterminateChanged. - The neo inset well leaves the mixed state alone. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw --- Components/Daisy/DataDisplay/DCarousel.razor | 66 +++++++++++++++++-- .../Daisy/DataDisplay/DCarouselItem.razor | 22 ++++++- Components/Daisy/DataInput/DCheckbox.razor | 21 ++++-- Components/Styleguide/DataInputSection.razor | 3 + Styles/neo.css | 2 +- 5 files changed, 103 insertions(+), 11 deletions(-) diff --git a/Components/Daisy/DataDisplay/DCarousel.razor b/Components/Daisy/DataDisplay/DCarousel.razor index fa38295..e6ba869 100644 --- a/Components/Daisy/DataDisplay/DCarousel.razor +++ b/Components/Daisy/DataDisplay/DCarousel.razor @@ -1,15 +1,45 @@ @inherits DComponentBase +@inject NavigationManager NavigationManager @* 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. *@ -
- @ChildContent -
+ user scrolls or swipes it. Give each item w-full for one slide at a time. ShowNavigation (previous/next on every + slide) and ShowIndicators (numbered buttons under the strip) are links to the slides' ids, as in daisyUI's examples: + they keep the current path, so Blazor treats them as same-page fragments and scrolls the slide into view. *@ + + @if (ShowIndicators) + { +
+ @Strip +
+ @for (var i = 0; i < items.Count; i++) + { + @(i + 1) + } +
+
+ } + else + { + @Strip + } +
@code { [Parameter] public RenderFragment ChildContent { get; set; } [Parameter] public DCarouselSnap Snap { get; set; } [Parameter] public DDirection Direction { get; set; } + /// Previous/next buttons on every slide. + [Parameter] public bool ShowNavigation { get; set; } + /// A row of numbered buttons under the strip, one per slide. + [Parameter] public bool ShowIndicators { get; set; } + [Parameter] public string PreviousText { get; set; } = "Previous slide"; + [Parameter] public string NextText { get; set; } = "Next slide"; + [Parameter] public string SlideText { get; set; } = "Slide"; + + readonly List items = new(); + readonly string uid = "d" + Guid.NewGuid().ToString("N")[..8]; + + RenderFragment Strip => @
@ChildContent
; string CssClass => Cx("carousel", SnapClass, Direction == DDirection.Vertical ? "carousel-vertical" : null, Class); @@ -19,4 +49,32 @@ DCarouselSnap.End => "carousel-end", _ => null }; + + internal int Count => items.Count; + + internal int IndexOf(DCarouselItem item) => items.IndexOf(item); + + internal string SlideId(int index) => $"{uid}-{index + 1}"; + + /// The current page plus the slide's fragment; the index wraps around. + internal string Link(int index) + { + var path = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); + var hash = path.IndexOf('#'); + if (hash >= 0) + path = path[..hash]; + return $"{path}#{SlideId((index % items.Count + items.Count) % items.Count)}"; + } + + internal void Add(DCarouselItem item) + { + items.Add(item); + StateHasChanged(); + } + + internal void Remove(DCarouselItem item) + { + items.Remove(item); + StateHasChanged(); + } } diff --git a/Components/Daisy/DataDisplay/DCarouselItem.razor b/Components/Daisy/DataDisplay/DCarouselItem.razor index 7a8c9bc..159d9f7 100644 --- a/Components/Daisy/DataDisplay/DCarouselItem.razor +++ b/Components/Daisy/DataDisplay/DCarouselItem.razor @@ -1,10 +1,28 @@ @inherits DComponentBase +@implements IDisposable -@* One slide of a DCarousel. *@ -
+@* One slide of a DCarousel. It registers with the carousel for its id and, with ShowNavigation, draws the + previous/next links over itself. *@ +
@ChildContent + @if (HasNavigation) + { +
+ ❮ + ❯ +
+ }
@code { + [CascadingParameter] DCarousel Parent { get; set; } [Parameter] public RenderFragment ChildContent { get; set; } + + int Index => Parent?.IndexOf(this) ?? -1; + string Id => Parent is null ? null : Parent.SlideId(Index); + bool HasNavigation => Parent is { ShowNavigation: true, Count: > 1 }; + + protected override void OnInitialized() => Parent?.Add(this); + + public void Dispose() => Parent?.Remove(this); } diff --git a/Components/Daisy/DataInput/DCheckbox.razor b/Components/Daisy/DataInput/DCheckbox.razor index 01f8799..43fcbfa 100644 --- a/Components/Daisy/DataInput/DCheckbox.razor +++ b/Components/Daisy/DataInput/DCheckbox.razor @@ -1,7 +1,8 @@ @inherits DInputBase @* daisyUI Checkbox: https://daisyui.com/components/checkbox/ . With LabelText the checkbox sits in a
+ @@ -333,6 +334,8 @@ @code { + bool mixedCheck; + bool mixed = true; class DemoModel { [Required] public string Name { get; set; } diff --git a/Styles/neo.css b/Styles/neo.css index 56d1f3c..7e06195 100644 --- a/Styles/neo.css +++ b/Styles/neo.css @@ -191,7 +191,7 @@ /* Selectors: an inset well while unchecked; once checked they keep daisyUI's filled look. */ @utility checkbox { - &:not(:where(:checked, :indeterminate, [aria-checked="true"], :disabled)) { + &:not(:where(:checked, :indeterminate, [aria-checked="true"], [aria-checked="mixed"], :disabled)) { border-color: transparent; background-image: linear-gradient(145deg, var(--neo-grad-dark), var(--neo-grad-light)); box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);