diff --git a/BlazorBootstrap.Demo.RCL/Components/Layout/MainLayout.razor.cs b/BlazorBootstrap.Demo.RCL/Components/Layout/MainLayout.razor.cs index 19796ed60..eb910fbca 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Layout/MainLayout.razor.cs +++ b/BlazorBootstrap.Demo.RCL/Components/Layout/MainLayout.razor.cs @@ -33,50 +33,51 @@ internal override IEnumerable GetNavItems() new (){ Id = "504", Text = "Buttons", Href = "/buttons", IconName = IconName.ToggleOn, ParentId = "5" }, new (){ Id = "505", Text = "Callout", Href = "/callout", IconName = IconName.StickyFill, ParentId = "5" }, new (){ Id = "506", Text = "Card", Href = "/card", IconName = IconName.CardHeading, ParentId = "5" }, - new (){ Id = "507", Text = "Charts", Href = "/charts", IconName = IconName.BarChartLineFill, ParentId = "5", Match = NavLinkMatch.All }, - new (){ Id = "508", Text = "Collapse", Href = "/collapse", IconName = IconName.ArrowsCollapse, ParentId = "5" }, - new (){ Id = "509", Text = "Confirm Dialog", Href = "/confirm-dialog", IconName = IconName.QuestionDiamondFill, ParentId = "5" }, - new (){ Id = "510", Text = "Dropdown", Href = "/dropdown", IconName = IconName.MenuButtonWideFill, ParentId = "5" }, - new (){ Id = "510", Text = "Google Maps", Href = "/google-maps", IconName = IconName.Map, ParentId = "5" }, + new (){ Id = "507", Text = "Carousel", Href = "/carousel", IconName = IconName.CollectionPlayFill, ParentId = "5" }, + new (){ Id = "508", Text = "Charts", Href = "/charts", IconName = IconName.BarChartLineFill, ParentId = "5", Match = NavLinkMatch.All }, + new (){ Id = "509", Text = "Collapse", Href = "/collapse", IconName = IconName.ArrowsCollapse, ParentId = "5" }, + new (){ Id = "510", Text = "Confirm Dialog", Href = "/confirm-dialog", IconName = IconName.QuestionDiamondFill, ParentId = "5" }, + new (){ Id = "511", Text = "Dropdown", Href = "/dropdown", IconName = IconName.MenuButtonWideFill, ParentId = "5" }, + new (){ Id = "512", Text = "Google Maps", Href = "/google-maps", IconName = IconName.Map, ParentId = "5" }, #region Grid - new (){ Id = "511", Text = "Grid", IconName = IconName.Grid, ParentId = "5" }, - new (){ Id = "51101", Text = "Overview", Href = "/grid/overview", IconName = IconName.Grid, ParentId = "511" }, // first item- do not change - new (){ Id = "51102", Text = "Alignment", Href = "/grid/alignment", IconName = IconName.Justify, ParentId = "511" }, - new (){ Id = "51103", Text = "Custom CSS Class", Href = "/grid/custom-css-class", IconName = IconName.FileTypeCss, ParentId = "511" }, - new (){ Id = "51104", Text = "Data Binding", Href = "/grid/data-binding", IconName = IconName.GridFill, ParentId = "511" }, - new (){ Id = "51106", Text = "Detail View", Href = "/grid/detail-view", IconName = IconName.ListNested, ParentId = "511" }, - new (){ Id = "51107", Text = "Events", Href = "/grid/events", IconName = IconName.LightningChargeFill, ParentId = "511" }, - new (){ Id = "51107", Text = "Filters", Href = "/grid/filters", IconName = IconName.FunnelFill, ParentId = "511" }, - new (){ Id = "51108", Text = "Fixed Header", Href = "/grid/fixed-header", IconName = IconName.Table, ParentId = "511" }, - new (){ Id = "51109", Text = "Freeze Columns", Href = "/grid/freeze-columns", IconName = IconName.LayoutThreeColumns, ParentId = "511" }, - new (){ Id = "51110", Text = "Grid Settings", Href = "/grid/settings", IconName = IconName.GearFill, ParentId = "511" }, - new (){ Id = "51111", Text = "Nested Grid", Href = "/grid/nested-grid", IconName = IconName.Pip, ParentId = "511" }, - new (){ Id = "51112", Text = "Paging", Href = "/grid/paging", IconName = IconName.ChevronBarRight, ParentId = "511" }, - new (){ Id = "51113", Text = "Selection", Href = "/grid/selection", IconName = IconName.CheckSquareFill, ParentId = "511" }, - new (){ Id = "51114", Text = "Sorting", Href = "/grid/sorting", IconName = IconName.ArrowDownUp, ParentId = "511" }, - new (){ Id = "51115", Text = "Translations", Href = "/grid/translations", IconName = IconName.Translate, ParentId = "511" }, - new (){ Id = "51199", Text = "Other", Href = "/grid/other", IconName = IconName.PlusSquareFill, ParentId = "511" }, // last item - do not change + new (){ Id = "513", Text = "Grid", IconName = IconName.Grid, ParentId = "5" }, + new (){ Id = "51101", Text = "Overview", Href = "/grid/overview", IconName = IconName.Grid, ParentId = "513" }, // first item - do not change + new (){ Id = "51102", Text = "Alignment", Href = "/grid/alignment", IconName = IconName.Justify, ParentId = "513" }, + new (){ Id = "51103", Text = "Custom CSS Class", Href = "/grid/custom-css-class", IconName = IconName.FileTypeCss, ParentId = "513" }, + new (){ Id = "51104", Text = "Data Binding", Href = "/grid/data-binding", IconName = IconName.GridFill, ParentId = "513" }, + new (){ Id = "51106", Text = "Detail View", Href = "/grid/detail-view", IconName = IconName.ListNested, ParentId = "513" }, + new (){ Id = "51107", Text = "Events", Href = "/grid/events", IconName = IconName.LightningChargeFill, ParentId = "513" }, + new (){ Id = "51107", Text = "Filters", Href = "/grid/filters", IconName = IconName.FunnelFill, ParentId = "513" }, + new (){ Id = "51108", Text = "Fixed Header", Href = "/grid/fixed-header", IconName = IconName.Table, ParentId = "513" }, + new (){ Id = "51109", Text = "Freeze Columns", Href = "/grid/freeze-columns", IconName = IconName.LayoutThreeColumns, ParentId = "513" }, + new (){ Id = "51110", Text = "Grid Settings", Href = "/grid/settings", IconName = IconName.GearFill, ParentId = "513" }, + new (){ Id = "51111", Text = "Nested Grid", Href = "/grid/nested-grid", IconName = IconName.Pip, ParentId = "513" }, + new (){ Id = "51112", Text = "Paging", Href = "/grid/paging", IconName = IconName.ChevronBarRight, ParentId = "513" }, + new (){ Id = "51113", Text = "Selection", Href = "/grid/selection", IconName = IconName.CheckSquareFill, ParentId = "513" }, + new (){ Id = "51114", Text = "Sorting", Href = "/grid/sorting", IconName = IconName.ArrowDownUp, ParentId = "513" }, + new (){ Id = "51115", Text = "Translations", Href = "/grid/translations", IconName = IconName.Translate, ParentId = "513" }, + new (){ Id = "51199", Text = "Other", Href = "/grid/other", IconName = IconName.PlusSquareFill, ParentId = "513" }, // last item - do not change #endregion Grid - new (){ Id = "512", Text = "Modals", Href = "/modals", IconName = IconName.WindowStack, ParentId = "5" }, - new (){ Id = "513", Text = "Offcanvas", Href = "/offcanvas", IconName = IconName.LayoutSidebarReverse, ParentId = "5" }, - new (){ Id = "514", Text = "Pagination", Href = "/pagination", IconName = IconName.ThreeDots, ParentId = "5" }, - new (){ Id = "515", Text = "PDF Viewer", Href = "/pdf-viewer", IconName = IconName.FilePdfFill, ParentId = "5" }, - new (){ Id = "516", Text = "Placeholders", Href = "/placeholders", IconName = IconName.ColumnsGap, ParentId = "5" }, - new (){ Id = "517", Text = "Preload", Href = "/preload", IconName = IconName.ArrowClockwise, ParentId = "5" }, - new (){ Id = "518", Text = "Progress", Href = "/progress", IconName = IconName.UsbC, ParentId = "5" }, - new (){ Id = "519", Text = "Ribbon", Href = "/ribbon", IconName = IconName.WindowStack, ParentId = "5" }, - new (){ Id = "520", Text = "Script Loader", Href = "/script-loader", IconName = IconName.CodeSlash, ParentId = "5" }, - new (){ Id = "521", Text = "Sidebar", Href = "/sidebar", IconName = IconName.LayoutSidebar, ParentId = "5" }, - new (){ Id = "522", Text = "Sidebar 2", Href = "/sidebar2", IconName = IconName.ListNested, ParentId = "5" }, - new (){ Id = "523", Text = "Sortable List", Href = "/sortable-list", IconName = IconName.ArrowsMove, ParentId = "5" }, - new (){ Id = "524", Text = "Spinner", Href = "/spinners", IconName = IconName.ArrowRepeat, ParentId = "5" }, - new (){ Id = "525", Text = "Tabs", Href = "/tabs", IconName = IconName.WindowPlus, ParentId = "5" }, - new (){ Id = "526", Text = "Toasts", Href = "/toasts", IconName = IconName.ExclamationTriangleFill, ParentId = "5" }, - new (){ Id = "527", Text = "Tooltips", Href = "/tooltips", IconName = IconName.ChatSquareDotsFill, ParentId = "5" }, + new (){ Id = "514", Text = "Modals", Href = "/modals", IconName = IconName.WindowStack, ParentId = "5" }, + new (){ Id = "515", Text = "Offcanvas", Href = "/offcanvas", IconName = IconName.LayoutSidebarReverse, ParentId = "5" }, + new (){ Id = "516", Text = "Pagination", Href = "/pagination", IconName = IconName.ThreeDots, ParentId = "5" }, + new (){ Id = "517", Text = "PDF Viewer", Href = "/pdf-viewer", IconName = IconName.FilePdfFill, ParentId = "5" }, + new (){ Id = "518", Text = "Placeholders", Href = "/placeholders", IconName = IconName.ColumnsGap, ParentId = "5" }, + new (){ Id = "519", Text = "Preload", Href = "/preload", IconName = IconName.ArrowClockwise, ParentId = "5" }, + new (){ Id = "520", Text = "Progress", Href = "/progress", IconName = IconName.UsbC, ParentId = "5" }, + new (){ Id = "521", Text = "Ribbon", Href = "/ribbon", IconName = IconName.WindowStack, ParentId = "5" }, + new (){ Id = "522", Text = "Script Loader", Href = "/script-loader", IconName = IconName.CodeSlash, ParentId = "5" }, + new (){ Id = "523", Text = "Sidebar", Href = "/sidebar", IconName = IconName.LayoutSidebar, ParentId = "5" }, + new (){ Id = "524", Text = "Sidebar 2", Href = "/sidebar2", IconName = IconName.ListNested, ParentId = "5" }, + new (){ Id = "525", Text = "Sortable List", Href = "/sortable-list", IconName = IconName.ArrowsMove, ParentId = "5" }, + new (){ Id = "526", Text = "Spinner", Href = "/spinners", IconName = IconName.ArrowRepeat, ParentId = "5" }, + new (){ Id = "527", Text = "Tabs", Href = "/tabs", IconName = IconName.WindowPlus, ParentId = "5" }, + new (){ Id = "528", Text = "Toasts", Href = "/toasts", IconName = IconName.ExclamationTriangleFill, ParentId = "5" }, + new (){ Id = "529", Text = "Tooltips", Href = "/tooltips", IconName = IconName.ChatSquareDotsFill, ParentId = "5" }, new (){ Id = "6", Text = "Data Visualization", IconName = IconName.BarChartFill, IconColor = IconColor.Warning }, new (){ Id = "600", Text = "Bar Chart", Href = "/charts/bar-chart", IconName = IconName.BarChartFill, ParentId = "6", Match = NavLinkMatch.All }, diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/CarouselDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/CarouselDocumentation.razor new file mode 100644 index 000000000..68900b6be --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/CarouselDocumentation.razor @@ -0,0 +1,101 @@ +@page "/carousel" + +@title + + + +

Blazor Carousel

+
Blazor Carousel component is a slideshow component that cycles through elements, images, or slides of text.
+ + + + +
Here is a basic example of a carousel with three slides.
+ + + +
+ You can add indicators to the carousel, alongside the previous/next controls. + The indicators allow users to jump directly to a particular slide. + Set ShowIndicators to true to show the indicators. +
+ + + +
+ You can add captions to your slides with the CarouselCaption component within any CarouselItem. + They can be easily hidden on smaller viewports. +
+ + + +
+ To animate slides with a fading transition instead of sliding, set Crossfade to true. +
+ + + +
+ You can make your carousels autoplay on page load by setting the Autoplay parameter to CarouselAutoPlay.StartOnPageLoad. + Autoplaying carousels automatically pause while hovered with the mouse. +
+ +
+ When the Autoplay parameter is set to CarouselAutoPlay.StartAfterUserInteraction, the carousel won’t automatically start to cycle on page load. + Instead, it will only start after the first user interaction. +
+ + + +
+ Add Interval parameter to a CarouselItem component to change the amount of time to delay between automatically cycling to the next item. +
+ + + +
+ Hide the controls by setting ShowPreviousNextControls parameter to false. +
+ + + +
+ Carousels support swiping left/right on touchscreen devices to move between slides. + This can be disabled by setting the Touch option to false. +
+ + + +
+ Blazor Bootstrap Carousel component exposes a two events for hooking into Carousel functionality. + + + + + + + + + + + + + + + + + +
Event NameDescription
OnslideFires immediately when the slide instance method is invoked.
OnslidFired when the carousel has completed its slide transition.
+
+ + + + NOTE: All of the images were generated using Microsoft Designer. + + +@code { + private string pageUrl = "/carousel"; + private string title = "Blazor Carousel Component"; + private string description = "Blazor Carousel component is a slideshow component that cycles through elements, images, or slides of text."; + private string imageUrl = "https://i.imgur.com/YoZd9Hy.png"; +} \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_01_Examples.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_01_Examples.razor new file mode 100644 index 000000000..bb2d2b0b5 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_01_Examples.razor @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_02_Indicators.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_02_Indicators.razor new file mode 100644 index 000000000..c0b533fbd --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_02_Indicators.razor @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_03_Captions.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_03_Captions.razor new file mode 100644 index 000000000..7dcb4f348 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_03_Captions.razor @@ -0,0 +1,23 @@ + + + + +

Earth Day

+

Let's unite to protect our planet and create a sustainable future for generations to come.

+
+
+ + + +

International Yoga Day

+

Embrace the ancient art of harmony for a healthier, happier you.

+
+
+ + + +

World Water Day

+

Every drop counts, let's protect our planet's most precious resource.

+
+
+
\ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_04_Crossfade.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_04_Crossfade.razor new file mode 100644 index 000000000..cd8b7ebd7 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_04_Crossfade.razor @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_05_Autoplay_A_StartOnPageLoad.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_05_Autoplay_A_StartOnPageLoad.razor new file mode 100644 index 000000000..53f868140 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_05_Autoplay_A_StartOnPageLoad.razor @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_05_Autoplay_B_StartAfterUserInteraction.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_05_Autoplay_B_StartAfterUserInteraction.razor new file mode 100644 index 000000000..354b0f3f2 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_05_Autoplay_B_StartAfterUserInteraction.razor @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_06_Individual_Carouselitem_Interval.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_06_Individual_Carouselitem_Interval.razor new file mode 100644 index 000000000..fbf1605cb --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_06_Individual_Carouselitem_Interval.razor @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_07_Autoplaying_Carousels_without_Controls.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_07_Autoplaying_Carousels_without_Controls.razor new file mode 100644 index 000000000..a152ece96 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_07_Autoplaying_Carousels_without_Controls.razor @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_08_Touch.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_08_Touch.razor new file mode 100644 index 000000000..f030da25a --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_08_Touch.razor @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_09_Events.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_09_Events.razor new file mode 100644 index 000000000..b2370ebec --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Carousel/Carousel_Demo_09_Events.razor @@ -0,0 +1,39 @@ + + + + + + + + + + + + +@code { + [Inject] + ToastService ToastService { get; set; } = default!; + private void Onslid(CarouselEventArgs e) + { + var message = new ToastMessage + { + Type = ToastType.Secondary, + Title = "Carousel Events", + HelpText = $"{DateTime.Now}", + Message = $"Onslid: from={e.From}, to={e.To}" + }; + ToastService.Notify(message); + } + + private void Onslide(CarouselEventArgs e) + { + var message = new ToastMessage + { + Type = ToastType.Secondary, + Title = "Carousel Events", + HelpText = $"{DateTime.Now}", + Message = $"Onslide: from={e.From}, to={e.To}" + }; + ToastService.Notify(message); + } +} \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor index df499566d..d14856e41 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor @@ -75,6 +75,11 @@

Card

+

Charts Updated

diff --git a/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-01.png b/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-01.png new file mode 100644 index 000000000..f25061961 Binary files /dev/null and b/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-01.png differ diff --git a/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-02.png b/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-02.png new file mode 100644 index 000000000..2cf7b3d50 Binary files /dev/null and b/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-02.png differ diff --git a/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-03.png b/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-03.png new file mode 100644 index 000000000..7a82d00e7 Binary files /dev/null and b/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-03.png differ diff --git a/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-04.png b/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-04.png new file mode 100644 index 000000000..ef2d51c77 Binary files /dev/null and b/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-04.png differ diff --git a/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-05.png b/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-05.png new file mode 100644 index 000000000..988486d93 Binary files /dev/null and b/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-05.png differ diff --git a/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-06.png b/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-06.png new file mode 100644 index 000000000..9dd342a32 Binary files /dev/null and b/BlazorBootstrap.Demo.RCL/wwwroot/images/slide-06.png differ diff --git a/blazorbootstrap/Components/Carousel/Carousel.razor b/blazorbootstrap/Components/Carousel/Carousel.razor new file mode 100644 index 000000000..58333ca89 --- /dev/null +++ b/blazorbootstrap/Components/Carousel/Carousel.razor @@ -0,0 +1,46 @@ +@namespace BlazorBootstrap +@inherits BlazorBootstrapComponentBase + + +
+ @if (ShowIndicators && HasItems) + { +
+ @{ + var i = 0; + foreach (var item in items) + { + var j = i; + bool isActiveItem = (i == activeIndex); + string? itemClass = isActiveItem ? "active" : null; + string? label = item.Label ?? $"Slide {i + 1}"; + + + i++; + } + } +
+ } + +
+ @ChildContent +
+ + @if (ShowPreviousNextControls) + { + + + } +
+
\ No newline at end of file diff --git a/blazorbootstrap/Components/Carousel/Carousel.razor.cs b/blazorbootstrap/Components/Carousel/Carousel.razor.cs new file mode 100644 index 000000000..83effeea5 --- /dev/null +++ b/blazorbootstrap/Components/Carousel/Carousel.razor.cs @@ -0,0 +1,228 @@ +namespace BlazorBootstrap; + +public partial class Carousel : BlazorBootstrapComponentBase +{ + #region Fields and Constants + + /// + /// Represents active index. + /// + private int activeIndex = 0; + + /// + /// Determines whether the default active set. + /// + private bool isDefaultActiveCarouselItemSet = false; + + private List items = new(); + + private DotNetObjectReference? objRef; + + #endregion + + #region Methods + + /// + protected override async ValueTask DisposeAsyncCore(bool disposing) + { + if (disposing) + { + try + { + if (IsRenderComplete) + await JSRuntime.InvokeVoidAsync(CarouselInterop.Dispose, Id); + } + catch (JSDisconnectedException) + { + // do nothing + } + + objRef?.Dispose(); + } + + await base.DisposeAsyncCore(disposing); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + CarouselOptions options = new() { Interval = Interval, Keyboard = Keyboard, Ride = Autoplay.ToCarouselAutoPlayString(), Touch = Touch }; + await JSRuntime.InvokeVoidAsync(CarouselInterop.Initialize, Id, options, objRef); + StateHasChanged(); // Required + } + + // Set active tab + if (firstRender && !isDefaultActiveCarouselItemSet) + await ShowItemByIndexAsync(activeIndex); + + await base.OnAfterRenderAsync(firstRender); + } + + protected override async Task OnInitializedAsync() + { + objRef ??= DotNetObjectReference.Create(this); + + await base.OnInitializedAsync(); + } + + [JSInvokable] + public async Task bslide(CarouselEventArgs args) + { + activeIndex = args.To; + await Onslide.InvokeAsync(args); + } + + [JSInvokable] + public async Task bsSlid(CarouselEventArgs args) => await Onslid.InvokeAsync(args); + + /// + /// Shows by index. + /// + /// + public ValueTask ShowItemByIndexAsync(int index) + { + if (!isDefaultActiveCarouselItemSet) + isDefaultActiveCarouselItemSet = true; + + return JSRuntime.InvokeVoidAsync(CarouselInterop.To, Id, index); + } + + internal void AddItem(CarouselItem carouselItem) + { + items.Add(carouselItem); + + if (carouselItem.Active) + activeIndex = items.Count - 1; + } + + /// + /// Shows next . + /// + public ValueTask PauseCarouselAsync() => JSRuntime.InvokeVoidAsync(CarouselInterop.Pause, Id); + + /// + /// Shows next . + /// + public ValueTask ShowNextItemAsync() + { + var nextIndex = activeIndex + 1; + activeIndex = nextIndex > items.Count - 1 ? 0 : nextIndex; + + return JSRuntime.InvokeVoidAsync(CarouselInterop.Next, Id); + } + + /// + /// Shows previous . + /// + public ValueTask ShowPreviousItemAsync() + { + var previousIndex = activeIndex - 1; + activeIndex = previousIndex < 0 ? items.Count - 1 : previousIndex; + + return JSRuntime.InvokeVoidAsync(CarouselInterop.Previous, Id); + } + + #endregion + + #region Properties, Indexers + + protected override string? ClassNames => + BuildClassNames( + Class, + (BootstrapClass.Carousel, true), + (BootstrapClass.CarouselSlide, true), + (BootstrapClass.CarouselFade, Crossfade) + ); + + /// + /// Controls the autoplay behavior of the carousel. + /// + /// + /// Default value is . + /// + [Parameter] + public CarouselAutoPlay Autoplay { get; set; } = CarouselAutoPlay.None; + + /// + /// Gets or sets the content to be rendered within the component. + /// + /// + /// Default value is null. + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + /// + /// Determines whether to use a crossfade effect when transitioning between slides. + /// + /// + /// Default value is . + /// + [Parameter] + public bool Crossfade { get; set; } + + private bool HasItems => items.Any(); + + /// + /// The amount of time to delay between automatically cycling an item. + /// + /// + /// Default value is 5000 milliseconds. + /// + [Parameter] + public int? Interval { get; set; } = 5000; + + private int ItemCount => items.Count; + + /// + /// Whether the carousel should react to keyboard events. + /// + /// + /// Default value is . + /// + [Parameter] + public bool Keyboard { get; set; } = true; + + /// + /// Fired when the carousel has completed its slide transition. + /// + [Parameter] + public EventCallback Onslid { get; set; } + + /// + /// Fires immediately when the slide instance method is invoked. + /// + [Parameter] + public EventCallback Onslide { get; set; } + + /// + /// Indicates whether to show indicators (dots) below the carousel to navigate between slides. + /// + /// + /// Default value is . + /// + [Parameter] + public bool ShowIndicators { get; set; } + + /// + /// Specifies whether to display the previous and next controls (arrows) for navigating slides. + /// + /// + /// Default value is . + /// + [Parameter] + public bool ShowPreviousNextControls { get; set; } = true; + + /// + /// Carousels support swiping left/right on touchscreen devices to move between slides. + /// This can be disabled by setting the parameter to . + /// + /// + /// Default value is . + /// + [Parameter] + public bool Touch { get; set; } = true; + + #endregion +} diff --git a/blazorbootstrap/Components/Carousel/CarouselCaption.razor b/blazorbootstrap/Components/Carousel/CarouselCaption.razor new file mode 100644 index 000000000..a4b5e65dc --- /dev/null +++ b/blazorbootstrap/Components/Carousel/CarouselCaption.razor @@ -0,0 +1,6 @@ +@namespace BlazorBootstrap +@inherits BlazorBootstrapComponentBase + + \ No newline at end of file diff --git a/blazorbootstrap/Components/Carousel/CarouselCaption.razor.cs b/blazorbootstrap/Components/Carousel/CarouselCaption.razor.cs new file mode 100644 index 000000000..fc1959f09 --- /dev/null +++ b/blazorbootstrap/Components/Carousel/CarouselCaption.razor.cs @@ -0,0 +1,25 @@ +namespace BlazorBootstrap; + +public partial class CarouselCaption : BlazorBootstrapComponentBase +{ + #region Properties, Indexers + + protected override string? ClassNames => + BuildClassNames( + Class, + (BootstrapClass.CarouselCaption, true), + (BootstrapClass.DisplayBlock, true), + (BootstrapClass.DisplayMediumBlock, true) + ); + + /// + /// Gets or sets the content to be rendered within the component. + /// + /// + /// Default value is . + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + #endregion +} diff --git a/blazorbootstrap/Components/Carousel/CarouselInterop.cs b/blazorbootstrap/Components/Carousel/CarouselInterop.cs new file mode 100644 index 000000000..a4d1f7bfa --- /dev/null +++ b/blazorbootstrap/Components/Carousel/CarouselInterop.cs @@ -0,0 +1,19 @@ +namespace BlazorBootstrap; + +public class CarouselInterop +{ + #region Fields and Constants + + private const string Prefix = "window.blazorBootstrap.carousel."; + + public const string Cycle = Prefix + "cycle"; + public const string Dispose = Prefix + "dispose"; + public const string Initialize = Prefix + "initialize"; + public const string Next = Prefix + "next"; + public const string NextWhenVisible = Prefix + "nextWhenVisible"; + public const string Pause = Prefix + "pause"; + public const string Previous = Prefix + "prev"; + public const string To = Prefix + "to"; + + #endregion +} diff --git a/blazorbootstrap/Components/Carousel/CarouselItem.razor b/blazorbootstrap/Components/Carousel/CarouselItem.razor new file mode 100644 index 000000000..ef9980828 --- /dev/null +++ b/blazorbootstrap/Components/Carousel/CarouselItem.razor @@ -0,0 +1,11 @@ +@namespace BlazorBootstrap +@inherits BlazorBootstrapComponentBase + +
+ @ChildContent +
\ No newline at end of file diff --git a/blazorbootstrap/Components/Carousel/CarouselItem.razor.cs b/blazorbootstrap/Components/Carousel/CarouselItem.razor.cs new file mode 100644 index 000000000..14a21c0b1 --- /dev/null +++ b/blazorbootstrap/Components/Carousel/CarouselItem.razor.cs @@ -0,0 +1,62 @@ +namespace BlazorBootstrap; + +public partial class CarouselItem : BlazorBootstrapComponentBase +{ + #region Methods + + protected override async Task OnInitializedAsync() + { + Id = IdUtility.GetNextId(); // Required + Parent.AddItem(this); + await base.OnInitializedAsync(); + } + + #endregion + + #region Properties, Indexers + + protected override string? ClassNames => + BuildClassNames( + Class, + (BootstrapClass.CarouselItem, true), + (BootstrapClass.Active, Active) + ); + + /// + /// Gets or sets the active state. + /// + /// + /// Default value is . + /// + [Parameter] + public bool Active { get; set; } + + /// + /// Gets or sets the content to be rendered within the component. + /// + /// + /// Default value is . + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + /// + /// The amount of time to delay between automatically cycling an item. + /// + /// + /// Default value is 5000. + /// + [Parameter] + public int? Interval { get; set; } = 5000; + + /// + /// Gets or sets the aria-label. + /// + [Parameter] + public string? Label { get; set; } + + [CascadingParameter(Name = "Carousel")] + public Carousel Parent { get; set; } = default!; + + #endregion +} diff --git a/blazorbootstrap/Constants/BootstrapAttributes.cs b/blazorbootstrap/Constants/BootstrapAttributes.cs index 315ef1e52..d321f74be 100644 --- a/blazorbootstrap/Constants/BootstrapAttributes.cs +++ b/blazorbootstrap/Constants/BootstrapAttributes.cs @@ -7,7 +7,9 @@ public static class BootstrapAttributes { #region Fields and Constants + public const string DataBootstrapRide = "data-bs-ride"; public const string DataBootstrapToggle = "data-bs-toggle"; + public const string DataBootstrapTouch = "data-bs-touch"; #endregion } diff --git a/blazorbootstrap/Constants/BootstrapClass.cs b/blazorbootstrap/Constants/BootstrapClass.cs index 36bbe5fa6..03fc8791d 100644 --- a/blazorbootstrap/Constants/BootstrapClass.cs +++ b/blazorbootstrap/Constants/BootstrapClass.cs @@ -47,12 +47,31 @@ public static class BootstrapClass public const string CardText = "card-text"; public const string CardTitle = "card-title"; + public const string Carousel = "carousel"; + public const string CarouselCaption = "carousel-caption"; + public const string CarouselControlNext = "carousel-control-next"; + public const string CarouselControlNextIcon = "carousel-control-next-icon"; + public const string CarouselControlPrevious = "carousel-control-prev"; + public const string CarouselControlPreviousIcon = "carousel-control-prev-icon"; + public const string CarouselDark = "carousel-dark"; + public const string CarouselFade = "carousel-fade"; + public const string CarouselIndicators = "carousel-indicators"; + public const string CarouselInner = "carousel-inner"; + public const string CarouselItem = "carousel-item"; + public const string CarouselSlide = "slide"; + public const string Collapse = "collapse"; public const string CollapseHorizontal = "collapse-horizontal"; public const string ConfirmationModal = "modal-confirmation"; public const string Disabled = "disabled"; + public const string DisplayNone = "d-none"; + public const string DisplayBlock = "d-block"; + public const string DisplaySmallBlock = "d-sm-block"; + public const string DisplayMediumBlock = "d-md-block"; + public const string DisplayLargeBlock = "d-lg-block"; + public const string DisplayExtraLargeBlock = "d-xl-block"; public const string Dropdown = "dropdown"; public const string DropdownDivider = "dropdown-divider"; diff --git a/blazorbootstrap/Enums/CarouselAutoPlay.cs b/blazorbootstrap/Enums/CarouselAutoPlay.cs new file mode 100644 index 000000000..71d3fb67d --- /dev/null +++ b/blazorbootstrap/Enums/CarouselAutoPlay.cs @@ -0,0 +1,8 @@ +namespace BlazorBootstrap; + +public enum CarouselAutoPlay +{ + None, + StartOnPageLoad, + StartAfterUserInteraction +} diff --git a/blazorbootstrap/EventArguments/CarouselEventArgs.cs b/blazorbootstrap/EventArguments/CarouselEventArgs.cs new file mode 100644 index 000000000..ad62d65de --- /dev/null +++ b/blazorbootstrap/EventArguments/CarouselEventArgs.cs @@ -0,0 +1,19 @@ +namespace BlazorBootstrap; + +public class CarouselEventArgs : EventArgs +{ + /// + /// The direction in which the is sliding (either "left" or "right"). + /// + public string? Direction { get; set; } + + /// + /// The index of the current item. + /// + public int From { get; set; } + + /// + /// The index of the next item. + /// + public int To { get; set; } +} diff --git a/blazorbootstrap/Extensions/EnumExtensions.cs b/blazorbootstrap/Extensions/EnumExtensions.cs index e915a7b67..8a296db35 100644 --- a/blazorbootstrap/Extensions/EnumExtensions.cs +++ b/blazorbootstrap/Extensions/EnumExtensions.cs @@ -221,6 +221,14 @@ public static string ToCardColorClass(this CardColor cardColor) => _ => "" }; + public static string? ToCarouselAutoPlayString(this CarouselAutoPlay autoplay) => + autoplay switch + { + CarouselAutoPlay.StartOnPageLoad => "carousel", + CarouselAutoPlay.StartAfterUserInteraction => "true", + _ => null + }; + public static string ToCssString(this Unit unit) => unit switch { diff --git a/blazorbootstrap/Models/CarouselOptions.cs b/blazorbootstrap/Models/CarouselOptions.cs new file mode 100644 index 000000000..1a0fc0cbd --- /dev/null +++ b/blazorbootstrap/Models/CarouselOptions.cs @@ -0,0 +1,64 @@ +namespace BlazorBootstrap; + +public class CarouselOptions +{ + #region Properties, Indexers + + /// + /// The amount of time to delay between automatically cycling an item. + /// + /// + /// Default value is 5000. + /// + public int? Interval { get; set; } = 5000; + + /// + /// Whether the carousel should react to keyboard events. + /// + /// + /// Default value is . + /// + public bool Keyboard { get; set; } = true; + + /// + /// If set to "hover", pauses the cycling of the carousel on mouseenter and resumes the cycling of the carousel on + /// mouseleave. + /// If set to , hovering over the carousel won’t pause it. + /// On touch-enabled devices, when set to "hover", cycling will pause on touchend (once the user finished interacting with + /// the carousel) for two intervals, before automatically resuming. + /// This is in addition to the mouse behavior. + /// + /// + /// Default value is "hover". + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public object? Pause { get; set; } = "hover"; + + /// + /// If set to , autoplays the carousel after the user manually cycles the first item. + /// If set to "carousel", autoplays the carousel on load. + /// + /// + /// Default value is . + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public object? Ride { get; set; } = false; + + /// + /// Whether the carousel should support left/right swipe interactions on touchscreen devices. + /// + /// + /// Default value is . + /// + public bool Touch { get; set; } = true; + + /// + /// Whether the carousel should cycle continuously or have hard stops. + /// + /// + /// Default value is . + /// + public bool Wrap { get; set; } = true; + + #endregion +} diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index 2733fc87f..1d45c7a38 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -249,6 +249,57 @@ window.blazorBootstrap = { bodyEl[0].style['overflow'] = 'auto'; } }, + carousel: { + cycle: (elementId) => { + let carouselEl = document.getElementById(elementId); + if (carouselEl != null) + bootstrap?.Carousel?.getOrCreateInstance(carouselEl)?.cycle(); + }, + dispose: (elementId) => { + let carouselEl = document.getElementById(elementId); + if (carouselEl != null) + bootstrap?.Carousel?.getOrCreateInstance(carouselEl)?.dispose(); + }, + initialize: (elementId, options, dotNetHelper) => { + let carouselEl = document.getElementById(elementId); + if (carouselEl == null) + return; + + carouselEl.addEventListener('slid.bs.carousel', function (e) { + dotNetHelper.invokeMethodAsync('bsSlid', e); + }); + carouselEl.addEventListener('slide.bs.carousel', function (e) { + dotNetHelper.invokeMethodAsync('bslide', e); + }); + + bootstrap?.Carousel?.getOrCreateInstance(carouselEl, options); + }, + next: (elementId) => { + let carouselEl = document.getElementById(elementId); + if (carouselEl != null) + bootstrap?.Carousel?.getOrCreateInstance(carouselEl)?.next(); + }, + nextWhenVisible: (elementId) => { + let carouselEl = document.getElementById(elementId); + if (carouselEl != null) + bootstrap?.Carousel?.getOrCreateInstance(carouselEl)?.nextWhenVisible(); + }, + pause: (elementId) => { + let carouselEl = document.getElementById(elementId); + if (carouselEl != null) + bootstrap?.Carousel?.getOrCreateInstance(carouselEl)?.pause(); + }, + prev: (elementId) => { + let carouselEl = document.getElementById(elementId); + if (carouselEl != null) + bootstrap?.Carousel?.getOrCreateInstance(carouselEl)?.prev(); + }, + to: (elementId, index) => { + let carouselEl = document.getElementById(elementId); + if (carouselEl != null) + bootstrap?.Carousel?.getOrCreateInstance(carouselEl)?.to(index); + }, + }, currencyInput: { initialize: (elementId, isFloat, allowNegativeNumbers, decimalSeperator) => { let currencyEl = document.getElementById(elementId); @@ -432,7 +483,7 @@ window.blazorBootstrap = { position: marker.position, title: marker.title, gmpClickable: clickable - }); + }); window.blazorBootstrap.googlemaps.markerEls[elementId].push(markerEl); diff --git a/docs/docs/05-components/carousel.mdx b/docs/docs/05-components/carousel.mdx new file mode 100644 index 000000000..a6eef0ecb --- /dev/null +++ b/docs/docs/05-components/carousel.mdx @@ -0,0 +1,310 @@ +--- +title: Blazor Carousel Component +description: Blazor Carousel component is a slideshow component that cycles through elements, images, or slides of text. +image: https://i.imgur.com/YoZd9Hy.png + +sidebar_label: Carousel +sidebar_position: 7 +--- + +import CarbonAd from '/carbon-ad.mdx' + +# Blazor Carousel + +Blazor Carousel component is a slideshow component that cycles through elements, images, or slides of text. + + + +Blazor Bootstrap: Carousel component + +## Parameters + +### Carousel Parameters + +| Name | Type | Default | Required | Description | Added Version | +|:--|:--|:--|:--|:--|:--| +| Autoplay | `CarouselAutoPlay` | `CarouselAutoPlay.None` | | Controls the autoplay behavior of the carousel. | 3.0.0 | +| ChildContent | RenderFragment? | null | ✔️ | Gets or sets the content to be rendered within the component. | 3.0.0 | +| Crossfade | bool | false | | Determines whether to use a crossfade effect when transitioning between slides. | 3.0.0 | +| Interval | int? | 5000 milliseconds | | The amount of time to delay between automatically cycling an item. | 3.0.0 | +| Keyboard | bool | true | | Whether the carousel should react to keyboard events. | 3.0.0 | +| ShowIndicators | bool | false | | Indicates whether to show indicators (dots) below the carousel to navigate between slides. | 3.0.0 | +| ShowPreviousNextControls | bool | true | | Specifies whether to display the previous and next controls (arrows) for navigating slides. | 3.0.0 | +| Touch | bool | true | | Carousels support swiping left/right on touchscreen devices to move between slides. This can be disabled by setting the `Touch` parameter to `false`. | 3.0.0 | + +### CarouselItem Parameters + +| Name | Type | Default | Required | Description | Added Version | +|:--|:--|:--|:--|:--|:--| +| Active | bool | false | | Gets or sets the active state. | 3.0.0 | +| ChildContent | RenderFragment | null | ✔️ | Gets or sets the content to be rendered within the component. | 3.0.0 | +| Interval | int? | 5000 milliseconds | | The amount of time to delay between automatically cycling an item. | 3.0.0 | +| Label | string? | null | | Gets or sets the aria-label. | 3.0.0 | + +## Methods + +| Name | Description | Added Version | +|--|--|--| +| ShowItemByIndexAsync(int index) | Shows `CarouselItem` by index. | 3.0.0 | +| PauseCarouselAsync() | Shows next `CarouselItem`. | 3.0.0 | +| ShowNextItemAsync() | Shows next `CarouselItem`. | 3.0.0 | +| ShowPreviousItemAsync() | Shows previous `CarouselItem`. | 3.0.0 | + +## Callback Events + +| Name | Description | Added Version | +|--|--|--| +| Onslide | Fires immediately when the slide instance method is invoked. | 3.0.0 | +| Onslid | Fired when the carousel has completed its slide transition. | 3.0.0 | + +## Examples + +### Carousel + +Here is a basic example of a carousel with three slides. + +Blazor Bootstrap: Carousel Component - Examples + +```cshtml {} showLineNumbers + + + + + + + + + + + +``` + +[See the demo here.](https://demos.blazorbootstrap.com/carousel#examples) + +### Indicators + +You can add indicators to the carousel, alongside the previous/next controls. +The indicators allow users to jump directly to a particular slide. +Set `ShowIndicators` to `true` to show the indicators. + +Blazor Bootstrap: Carousel Component - Indicators + +```cshtml {} showLineNumbers + + + + + + + + + + + +``` + +[See the demo here.](https://demos.blazorbootstrap.com/carousel#indicators) + +### Captions + +You can add captions to your slides with the `CarouselCaption` component within any `CarouselItem`. +They can be easily hidden on smaller viewports. + +Blazor Bootstrap: Carousel Component - Captions + +```cshtml {} showLineNumbers + + + + +

Earth Day

+

Let's unite to protect our planet and create a sustainable future for generations to come.

+
+
+ + + +

International Yoga Day

+

Embrace the ancient art of harmony for a healthier, happier you.

+
+
+ + + +

World Water Day

+

Every drop counts, let's protect our planet's most precious resource.

+
+
+
+``` + +[See the demo here.](https://demos.blazorbootstrap.com/carousel#captions) + +### Crossfade + +To animate slides with a fading transition instead of sliding, set `Crossfade` to `true`. + +Blazor Bootstrap: Carousel Component - Crossfade + +```cshtml {} showLineNumbers + + + + + + + + + + + +``` + +[See the demo here.](https://demos.blazorbootstrap.com/carousel#crossfade) + +### Autoplaying carousels + +You can make your carousels autoplay on page load by setting the `Autoplay` parameter to `CarouselAutoPlay.StartOnPageLoad`. +Autoplaying carousels automatically pause while hovered with the mouse. + +```cshtml {} showLineNumbers + + + + + + + + + + + +``` + +When the `Autoplay` parameter is set to `CarouselAutoPlay.StartAfterUserInteraction`, the carousel won't automatically start to cycle on page load. +Instead, it will only start after the first user interaction. + +```cshtml {} showLineNumbers + + + + + + + + + + + +``` + +[See the demo here.](https://demos.blazorbootstrap.com/carousel#autoplaying-carousels) + +### Individual carousel item interval + +Add `Interval` parameter to a `CarouselItem` component to change the amount of time to delay between automatically cycling to the next item. + +```cshtml {} showLineNumbers + + + + + + + + + + + +``` + +[See the demo here.](https://demos.blazorbootstrap.com/carousel#individual-carousel-item-interval) + +### Autoplaying carousels without controls + +Hide the controls by setting `ShowPreviousNextControls` parameter to `false`. + +```cshtml {} showLineNumbers + + + + + + + + + + + +``` + +[See the demo here.](https://demos.blazorbootstrap.com/carousel#autoplaying-carousels-without-controls) + +### Disable touch swiping + +Carousels support swiping left/right on touchscreen devices to move between slides. +This can be disabled by setting the `Touch` option to `false`. + +```cshtml {} showLineNumbers + + + + + + + + + + + +``` + +[See the demo here.](https://demos.blazorbootstrap.com/carousel#disable-touch-swiping) + +### Events + +Blazor Bootstrap Carousel component exposes a two events for hooking into Carousel functionality. + +```cshtml {} showLineNumbers + + + + + + + + + + + + +@code { + [Inject] + ToastService ToastService { get; set; } = default!; + private void Onslid(CarouselEventArgs e) + { + var message = new ToastMessage + { + Type = ToastType.Secondary, + Title = "Carousel Events", + HelpText = $"{DateTime.Now}", + Message = $"Onslid: from={e.From}, to={e.To}" + }; + ToastService.Notify(message); + } + + private void Onslide(CarouselEventArgs e) + { + var message = new ToastMessage + { + Type = ToastType.Secondary, + Title = "Carousel Events", + HelpText = $"{DateTime.Now}", + Message = $"Onslide: from={e.From}, to={e.To}" + }; + ToastService.Notify(message); + } +} +``` + +[See the demo here.](https://demos.blazorbootstrap.com/carousel#events) \ No newline at end of file