mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-07-08 18:16:08 +00:00
@@ -1,5 +1,6 @@
|
||||
using Bunit;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Radzen.Blazor.Tests
|
||||
@@ -173,6 +174,224 @@ namespace Radzen.Blazor.Tests
|
||||
|
||||
Assert.DoesNotContain("rz-carousel-pager-overlay", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Carousel_ItemsPerPage_DefaultIsOne()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
var component = ctx.RenderComponent<RadzenCarousel>();
|
||||
|
||||
Assert.Equal(1, component.Instance.ItemsPerPage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Carousel_ItemsPerPage_SetsFlexBasisOnItems()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ItemsPerPage, 3);
|
||||
parameters.Add(p => p.Items, builder =>
|
||||
{
|
||||
builder.OpenComponent<RadzenCarouselItem>(0);
|
||||
builder.AddAttribute(1, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Slide 1")));
|
||||
builder.CloseComponent();
|
||||
|
||||
builder.OpenComponent<RadzenCarouselItem>(2);
|
||||
builder.AddAttribute(3, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Slide 2")));
|
||||
builder.CloseComponent();
|
||||
|
||||
builder.OpenComponent<RadzenCarouselItem>(4);
|
||||
builder.AddAttribute(5, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Slide 3")));
|
||||
builder.CloseComponent();
|
||||
});
|
||||
});
|
||||
|
||||
Assert.Contains("calc(100% / 3)", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Carousel_ItemsPerPage_PagerShowsPageCount()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ItemsPerPage, 2);
|
||||
parameters.Add(p => p.AllowPaging, true);
|
||||
parameters.Add(p => p.Items, builder =>
|
||||
{
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var seq = i * 2;
|
||||
builder.OpenComponent<RadzenCarouselItem>(seq);
|
||||
builder.AddAttribute(seq + 1, "ChildContent", (RenderFragment)(b => b.AddContent(0, $"Slide {i + 1}")));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 5 items / 2 per page = 3 pages (ceil), so 3 pager buttons
|
||||
var pagerButtons = component.FindAll(".rz-carousel-pager-button");
|
||||
Assert.Equal(3, pagerButtons.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Carousel_ItemsPerPage_One_NoInlineStyle()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ItemsPerPage, 1);
|
||||
parameters.Add(p => p.Items, builder =>
|
||||
{
|
||||
builder.OpenComponent<RadzenCarouselItem>(0);
|
||||
builder.AddAttribute(1, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Slide 1")));
|
||||
builder.CloseComponent();
|
||||
});
|
||||
});
|
||||
|
||||
// With ItemsPerPage=1 (default), no flex override style should be applied
|
||||
Assert.DoesNotContain("calc(100%", component.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Carousel_ItemsPerPage_PagerShowsCorrectActiveButton()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ItemsPerPage, 3);
|
||||
parameters.Add(p => p.AllowPaging, true);
|
||||
parameters.Add(p => p.Items, builder =>
|
||||
{
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
var seq = i * 2;
|
||||
builder.OpenComponent<RadzenCarouselItem>(seq);
|
||||
builder.AddAttribute(seq + 1, "ChildContent", (RenderFragment)(b => b.AddContent(0, $"Slide {i + 1}")));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 9 items / 3 per page = 3 pages
|
||||
var pagerButtons = component.FindAll(".rz-carousel-pager-button");
|
||||
Assert.Equal(3, pagerButtons.Count);
|
||||
|
||||
// First page button should be active by default (selectedIndex=0)
|
||||
Assert.Contains("rz-state-active", pagerButtons[0].GetAttribute("class"));
|
||||
Assert.DoesNotContain("rz-state-active", pagerButtons[1].GetAttribute("class"));
|
||||
Assert.DoesNotContain("rz-state-active", pagerButtons[2].GetAttribute("class"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Carousel_ItemsPerPage_PagerWithUnevenItems()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ItemsPerPage, 3);
|
||||
parameters.Add(p => p.AllowPaging, true);
|
||||
parameters.Add(p => p.Items, builder =>
|
||||
{
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
var seq = i * 2;
|
||||
builder.OpenComponent<RadzenCarouselItem>(seq);
|
||||
builder.AddAttribute(seq + 1, "ChildContent", (RenderFragment)(b => b.AddContent(0, $"Slide {i + 1}")));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 7 items / 3 per page = 3 pages (ceil)
|
||||
var pagerButtons = component.FindAll(".rz-carousel-pager-button");
|
||||
Assert.Equal(3, pagerButtons.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Carousel_ItemsPerPage_AllItemsSetFlexBasis()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ItemsPerPage, 4);
|
||||
parameters.Add(p => p.Items, builder =>
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
var seq = i * 2;
|
||||
builder.OpenComponent<RadzenCarouselItem>(seq);
|
||||
builder.AddAttribute(seq + 1, "ChildContent", (RenderFragment)(b => b.AddContent(0, $"Slide {i + 1}")));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// All 6 items should have the flex style
|
||||
var items = component.FindAll(".rz-carousel-item");
|
||||
Assert.Equal(6, items.Count);
|
||||
foreach (var item in items)
|
||||
{
|
||||
Assert.Contains("calc(100% / 4)", item.GetAttribute("style"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Carousel_ItemsPerPage_SingleItem_OnePage()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ItemsPerPage, 3);
|
||||
parameters.Add(p => p.AllowPaging, true);
|
||||
parameters.Add(p => p.Items, builder =>
|
||||
{
|
||||
builder.OpenComponent<RadzenCarouselItem>(0);
|
||||
builder.AddAttribute(1, "ChildContent", (RenderFragment)(b => b.AddContent(0, "Only slide")));
|
||||
builder.CloseComponent();
|
||||
});
|
||||
});
|
||||
|
||||
// 1 item / 3 per page = 1 page
|
||||
var pagerButtons = component.FindAll(".rz-carousel-pager-button");
|
||||
Assert.Single(pagerButtons);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Carousel_ItemsPerPage_PagerButtonCount_WithTopAndBottom()
|
||||
{
|
||||
using var ctx = new TestContext();
|
||||
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
var component = ctx.RenderComponent<RadzenCarousel>(parameters =>
|
||||
{
|
||||
parameters.Add(p => p.ItemsPerPage, 2);
|
||||
parameters.Add(p => p.AllowPaging, true);
|
||||
parameters.Add(p => p.PagerPosition, PagerPosition.TopAndBottom);
|
||||
parameters.Add(p => p.Items, builder =>
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
var seq = i * 2;
|
||||
builder.OpenComponent<RadzenCarouselItem>(seq);
|
||||
builder.AddAttribute(seq + 1, "ChildContent", (RenderFragment)(b => b.AddContent(0, $"Slide {i + 1}")));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 6 items / 2 per page = 3 pages, rendered in both top and bottom = 6 buttons total
|
||||
var pagerButtons = component.FindAll(".rz-carousel-pager-button");
|
||||
Assert.Equal(6, pagerButtons.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
@if(AllowPaging && (PagerPosition == PagerPosition.Top || PagerPosition == PagerPosition.TopAndBottom))
|
||||
{
|
||||
<RadzenStack class="rz-carousel-pager rz-carousel-pager-top" Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyContent="JustifyContent.Center" Wrap="FlexWrap.Wrap" role="list">
|
||||
@foreach (var item in items)
|
||||
@for (var p = 0; p < PageCount; p++)
|
||||
{
|
||||
var index = items.IndexOf(item);
|
||||
<button type="button" role="listitem" @onclick="@(args => Navigate(index))" class="rz-carousel-pager-button @(index == selectedIndex ? "rz-state-active" : "")" aria-label="@string.Format(PagerButtonAriaLabelFormat, index + 1)"></button>
|
||||
var pageIndex = p;
|
||||
var itemIndex = pageIndex * ItemsPerPage;
|
||||
<button type="button" role="listitem" @onclick="@(args => Navigate(itemIndex))" class="rz-carousel-pager-button @(pageIndex == ActivePage ? "rz-state-active" : "")" aria-label="@string.Format(PagerButtonAriaLabelFormat, pageIndex + 1)"></button>
|
||||
}
|
||||
</RadzenStack>
|
||||
}
|
||||
@@ -31,10 +32,11 @@
|
||||
@if(AllowPaging && (PagerPosition == PagerPosition.Bottom || PagerPosition == PagerPosition.TopAndBottom))
|
||||
{
|
||||
<RadzenStack class="rz-carousel-pager rz-carousel-pager-bottom" Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyContent="JustifyContent.Center" Wrap="FlexWrap.Wrap" role="list">
|
||||
@foreach (var item in items)
|
||||
@for (var p = 0; p < PageCount; p++)
|
||||
{
|
||||
var index = items.IndexOf(item);
|
||||
<button type="button" role="listitem" @onclick="@(args => Navigate(index))" class="rz-carousel-pager-button @(index == selectedIndex ? "rz-state-active" : "")" aria-label="@string.Format(PagerButtonAriaLabelFormat, index + 1)"></button>
|
||||
var pageIndex = p;
|
||||
var itemIndex = pageIndex * ItemsPerPage;
|
||||
<button type="button" role="listitem" @onclick="@(args => Navigate(itemIndex))" class="rz-carousel-pager-button @(pageIndex == ActivePage ? "rz-state-active" : "")" aria-label="@string.Format(PagerButtonAriaLabelFormat, pageIndex + 1)"></button>
|
||||
}
|
||||
</RadzenStack>
|
||||
}
|
||||
|
||||
@@ -104,14 +104,34 @@ namespace Radzen.Blazor
|
||||
await GoTo(index);
|
||||
}
|
||||
|
||||
int PageCount => ItemsPerPage > 1 ? (int)Math.Ceiling((double)items.Count / ItemsPerPage) : items.Count;
|
||||
|
||||
int ActivePage => ItemsPerPage > 1 ? selectedIndex / ItemsPerPage : selectedIndex;
|
||||
|
||||
async Task Prev()
|
||||
{
|
||||
await Navigate(selectedIndex == 0 ? items.Count - 1 : selectedIndex - 1);
|
||||
if (ItemsPerPage > 1)
|
||||
{
|
||||
var prevIndex = selectedIndex - ItemsPerPage;
|
||||
await Navigate(prevIndex < 0 ? (PageCount - 1) * ItemsPerPage : prevIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Navigate(selectedIndex == 0 ? items.Count - 1 : selectedIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
async Task Next()
|
||||
{
|
||||
await Navigate(selectedIndex == items.Count - 1 ? 0 : selectedIndex + 1);
|
||||
if (ItemsPerPage > 1)
|
||||
{
|
||||
var nextIndex = selectedIndex + ItemsPerPage;
|
||||
await Navigate(nextIndex >= items.Count ? 0 : nextIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Navigate(selectedIndex == items.Count - 1 ? 0 : selectedIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
async Task GoTo(int index)
|
||||
@@ -270,6 +290,13 @@ namespace Radzen.Blazor
|
||||
[Parameter]
|
||||
public bool AllowNavigation { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of items visible at the same time. Set to <c>1</c> by default.
|
||||
/// </summary>
|
||||
/// <value>The number of items per page.</value>
|
||||
[Parameter]
|
||||
public int ItemsPerPage { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the user can scroll or swipe through carousel items. Set to <c>true</c> by default.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@implements IDisposable
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<li @ref="element" @attributes=@Attributes class=@Class tabindex="0">
|
||||
<li @ref="element" @attributes=@Attributes class=@Class style=@ItemStyle tabindex="0">
|
||||
@ChildContent
|
||||
<div class="rz-carousel-snapper"></div>
|
||||
</li>
|
||||
@@ -59,6 +59,10 @@ namespace Radzen.Blazor
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
string? ItemStyle => Carousel != null && Carousel.ItemsPerPage > 1
|
||||
? $"flex: 0 0 calc(100% / {Carousel.ItemsPerPage}); width: calc(100% / {Carousel.ItemsPerPage})"
|
||||
: null;
|
||||
|
||||
int itemIndex;
|
||||
internal ElementReference element;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
Animation duration
|
||||
<RadzenNumeric @bind-Value=@animationDuration TValue="double?" aria-label="animation duration" />
|
||||
</RadzenStack>
|
||||
<RadzenStack Orientation="Orientation.Vertical" Gap="4px">
|
||||
Items per page
|
||||
<RadzenNumeric @bind-Value=@itemsPerPage Min="1" Max="6" aria-label="items per page" />
|
||||
</RadzenStack>
|
||||
<RadzenStack Orientation="Orientation.Vertical" Gap="8px">
|
||||
Auto-cycle
|
||||
<RadzenSwitch @bind-Value="@auto" InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", "auto-cycle" }})" />
|
||||
@@ -34,7 +38,7 @@
|
||||
</RadzenStack>
|
||||
</RadzenCard>
|
||||
<RadzenCard class="rz-p-4" Variant="Variant.Outlined">
|
||||
<RadzenCarousel @ref=carousel @bind-SelectedIndex="@selectedIndex" Auto="@auto" Interval="@interval" AnimationDuration="@animationDuration" PagerOverlay="false" ButtonShade="Shade.Default" Style="height:500px"
|
||||
<RadzenCarousel @ref=carousel @bind-SelectedIndex="@selectedIndex" Auto="@auto" Interval="@interval" AnimationDuration="@animationDuration" ItemsPerPage="@itemsPerPage" PagerOverlay="false" ButtonShade="Shade.Default" Style="height:500px"
|
||||
Change="@(args => console.Log($"SelectedIndex changed to {args}"))">
|
||||
<Items>
|
||||
@foreach (var orderID in orderIDs)
|
||||
@@ -58,6 +62,7 @@
|
||||
bool auto = true;
|
||||
double interval = 4000;
|
||||
double? animationDuration;
|
||||
int itemsPerPage = 1;
|
||||
string toggleText = "Stop";
|
||||
|
||||
bool started = true;
|
||||
|
||||
49
RadzenBlazorDemos/Pages/CarouselMultipleItems.razor
Normal file
49
RadzenBlazorDemos/Pages/CarouselMultipleItems.razor
Normal file
@@ -0,0 +1,49 @@
|
||||
<RadzenStack class="rz-p-0 rz-p-md-12">
|
||||
<RadzenCard class="rz-p-4" Variant="Variant.Outlined">
|
||||
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Start" Wrap="FlexWrap.Wrap">
|
||||
<RadzenStack Orientation="Orientation.Vertical" Gap="4px">
|
||||
Items per page
|
||||
<RadzenNumeric @bind-Value=@itemsPerPage Min="1" Max="6" aria-label="items per page" />
|
||||
</RadzenStack>
|
||||
<RadzenStack Orientation="Orientation.Vertical" Gap="4px">
|
||||
Auto-cycle
|
||||
<RadzenSwitch @bind-Value="@auto" InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", "auto-cycle" }})" />
|
||||
</RadzenStack>
|
||||
</RadzenStack>
|
||||
</RadzenCard>
|
||||
<RadzenCarousel @bind-SelectedIndex="@selectedIndex" Auto="@auto" Interval="4000"
|
||||
ItemsPerPage="@itemsPerPage" PagerOverlay="false" Style="height: 400px;">
|
||||
<Items>
|
||||
<RadzenCarouselItem>
|
||||
<RadzenImage Path="images/gallery/1.jpg" class="rz-h-100" />
|
||||
</RadzenCarouselItem>
|
||||
<RadzenCarouselItem>
|
||||
<RadzenImage Path="images/gallery/2.jpg" class="rz-h-100" />
|
||||
</RadzenCarouselItem>
|
||||
<RadzenCarouselItem>
|
||||
<RadzenImage Path="images/gallery/3.jpg" class="rz-h-100" />
|
||||
</RadzenCarouselItem>
|
||||
<RadzenCarouselItem>
|
||||
<RadzenImage Path="images/gallery/4.jpg" class="rz-h-100" />
|
||||
</RadzenCarouselItem>
|
||||
<RadzenCarouselItem>
|
||||
<RadzenImage Path="images/gallery/5.jpg" class="rz-h-100" />
|
||||
</RadzenCarouselItem>
|
||||
<RadzenCarouselItem>
|
||||
<RadzenImage Path="images/gallery/6.jpg" class="rz-h-100" />
|
||||
</RadzenCarouselItem>
|
||||
<RadzenCarouselItem>
|
||||
<RadzenImage Path="images/gallery/7.jpg" class="rz-h-100" />
|
||||
</RadzenCarouselItem>
|
||||
<RadzenCarouselItem>
|
||||
<RadzenImage Path="images/gallery/8.jpg" class="rz-h-100" />
|
||||
</RadzenCarouselItem>
|
||||
</Items>
|
||||
</RadzenCarousel>
|
||||
</RadzenStack>
|
||||
|
||||
@code {
|
||||
int itemsPerPage = 3;
|
||||
bool auto = false;
|
||||
int selectedIndex;
|
||||
}
|
||||
@@ -11,6 +11,16 @@
|
||||
<CarouselConfig />
|
||||
</RadzenExample>
|
||||
|
||||
<RadzenText Anchor="carousel#multiple-items" TextStyle="TextStyle.H5" TagName="TagName.H2" class="rz-pt-12">
|
||||
Multiple items per page
|
||||
</RadzenText>
|
||||
<RadzenText TextStyle="TextStyle.Body1" class="rz-mb-8">
|
||||
Use <code>ItemsPerPage</code> to display multiple items at the same time.
|
||||
</RadzenText>
|
||||
<RadzenExample ComponentName="Carousel" Example="CarouselMultipleItems">
|
||||
<CarouselMultipleItems />
|
||||
</RadzenExample>
|
||||
|
||||
<RadzenText Anchor="carousel#navigation-style" TextStyle="TextStyle.H5" TagName="TagName.H2" class="rz-pt-12">
|
||||
Navigation button styles
|
||||
</RadzenText>
|
||||
|
||||
Reference in New Issue
Block a user