@ -1,5 +1,4 @@
using System.Globalization ;
using Connected.Annotations ;
using Connected.Extensions ;
using Connected.Interop ;
using Connected.Services ;
@ -11,6 +10,19 @@ namespace Connected.Components;
public partial class Tabs : UIComponent , IAsyncDisposable
{
#region Event callbacks
/// <summary>
/// Fired when ActivePanelIndex changes.
/// </summary>
[Parameter]
public EventCallback < int > ActivePanelIndexChanged { get ; set ; }
# endregion
#region Content placeholders
private bool _isDisposed ;
private int _activePanelIndex = 0 ;
@ -30,149 +42,51 @@ public partial class Tabs : UIComponent, IAsyncDisposable
private IResizeObserver _resizeObserver ;
[CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get ; set ; }
[Inject] private IResizeObserverFactory _resizeObserverFactory { get ; set ; }
/// <summary>
/// If true, render all tabs and hide (display:none) every non-active.
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Behavior)]
public bool KeepPanelsAlive { get ; set ; } = false ;
/// <summary>
/// If true, sets the border-radius to theme default.
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public bool Rounded { get ; set ; }
/// <summary>
/// If true, sets a border between the content and the toolbar depending on the position.
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public bool Border { get ; set ; }
/// <summary>
/// If true, toolbar will be outlined.
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public bool Outlined { get ; set ; }
/// <summary>
/// If true, centers the tabitems.
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public bool Centered { get ; set ; }
/// <summary>
/// Hides the active tab slider.
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public bool HideSlider { get ; set ; }
/// <summary>
/// Glyph to use for left pagination.
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public string PrevIcon { get ; set ; } = Icons . Filled . ChevronLeft ;
/// <summary>
/// Glyph to use for right pagination.
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public string NextIcon { get ; set ; } = Icons . Filled . ChevronRight ;
/// <summary>
/// If true, always display the scroll buttons even if the tabs are smaller than the required with, buttons will be disabled if there is nothing to scroll.
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public bool AlwaysShowScrollButtons { get ; set ; }
/// <summary>
/// Sets the maxheight the component can have.
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public int? MaxHeight { get ; set ; } = null ;
/// <summary>
/// Sets the position of the tabs itself.
/// Child content of component.
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Behavior)]
public Position Position { get ; set ; } = Position . Top ;
public RenderFragment ChildContent { get ; set ; }
/// <summary>
/// The color of the component. It supports the theme colors.
/// A readonly list of the current panels. Panels should be added or removed through the RenderTree use this collection to get informations about the current panels
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public ThemeColor Color { get ; set ; } = ThemeColor . Default ;
public IReadOnlyList < TabPanel > Panels { get ; private set ; }
/// <summary>
/// The color of the tab slider. It supports the theme colors.
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public ThemeColor SliderColor { get ; set ; } = ThemeColor . Inherit ;
private List < TabPanel > _panels ;
/// <summary>
/// The color of the icon. It supports the theme colors.
/// A render fragment that is added before or after (based on the value of HeaderPosition) the tabs inside the header panel of the tab control
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public ThemeColor IconColor { get ; set ; } = ThemeColor . Inherit ;
public RenderFragment < Tabs > Header { get ; set ; }
/// <summary>
/// The color of the next/prev icons. It supports the theme colors.
/// A render fragment that is added before or after (based on the value of HeaderPosition) inside each tab panel
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public ThemeColor ScrollIconColor { get ; set ; } = ThemeColor . Inherit ;
public RenderFragment < TabPanel > TabPanelHeader { get ; set ; }
/// <summary>
/// The higher the number, the heavier the drop-shadow, applies around the whole component.
/// Additional content specified by Header is placed either before the tabs, after or not at all
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public int Elevation { set ; get ; } = 0 ;
public TabHeaderPosition HeaderPosition { get ; set ; } = TabHeaderPosition . After ;
/// <summary>
/// If true, will apply elevation, rounded, outlined effects to the whole tab component instead of just toolbar.
/// Additional content specified by Header is placed either before the tabs, after or not at all
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public bool ApplyEffectsToContainer { get ; set ; }
public TabHeaderPosition TabPanelHeaderPosition { get ; set ; } = TabHeaderPosition . After ;
/// <summary>
/// If true, disables ripple effect.
/// Can be used in derived class to add a class to the main container. If not overwritten return an empty string
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public bool DisableRipple { get ; set ; }
protected virtual string InternalClassName { get ; } = string . Empty ;
/// <summary>
/// If true, disables slider animation
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public bool DisableSliderAnimation { get = > _disableSliderAnimation ; set = > _disableSliderAnimation = value ; }
private string _prevIcon ;
/// <summary>
/// Child content of component.
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Behavior)]
public RenderFragment ChildContent { get ; set ; }
private string _nextIcon ;
/// <summary>
/// This fragment is placed between toolbar and panels.
@ -180,22 +94,9 @@ public partial class Tabs : UIComponent, IAsyncDisposable
/// The active tab will be the content of this RenderFragement
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Behavior)]
public RenderFragment < TabPanel > PrePanelContent { get ; set ; }
/// <summary>
/// Custom class/classes for TabPanel
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public string TabPanelClass { get ; set ; }
/// <summary>
/// Custom class/classes for Selected Content Panel
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Appearance)]
public string PanelClass { get ; set ; }
public TabPanel ActivePanel { get ; private set ; }
@ -203,7 +104,6 @@ public partial class Tabs : UIComponent, IAsyncDisposable
/// The current active panel index. Also with Bidirectional Binding
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Behavior)]
public int ActivePanelIndex
{
get = > _activePanelIndex ;
@ -219,113 +119,9 @@ public partial class Tabs : UIComponent, IAsyncDisposable
}
}
/// <summary>
/// Fired when ActivePanelIndex changes.
/// </summary>
[Parameter]
public EventCallback < int > ActivePanelIndexChanged { get ; set ; }
/// <summary>
/// A readonly list of the current panels. Panels should be added or removed through the RenderTree use this collection to get informations about the current panels
/// </summary>
public IReadOnlyList < TabPanel > Panels { get ; private set ; }
private List < TabPanel > _panels ;
/// <summary>
/// A render fragment that is added before or after (based on the value of HeaderPosition) the tabs inside the header panel of the tab control
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Behavior)]
public RenderFragment < Tabs > Header { get ; set ; }
/// <summary>
/// Additional content specified by Header is placed either before the tabs, after or not at all
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Behavior)]
public TabHeaderPosition HeaderPosition { get ; set ; } = TabHeaderPosition . After ;
/// <summary>
/// A render fragment that is added before or after (based on the value of HeaderPosition) inside each tab panel
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Behavior)]
public RenderFragment < TabPanel > TabPanelHeader { get ; set ; }
/// <summary>
/// Additional content specified by Header is placed either before the tabs, after or not at all
/// </summary>
[Parameter]
[Category(CategoryTypes.Tabs.Behavior)]
public TabHeaderPosition TabPanelHeaderPosition { get ; set ; } = TabHeaderPosition . After ;
/// <summary>
/// Can be used in derived class to add a class to the main container. If not overwritten return an empty string
/// </summary>
protected virtual string InternalClassName { get ; } = string . Empty ;
private string _prevIcon ;
private string _nextIcon ;
#region Life cycle management
public Tabs ( )
{
_panels = new List < TabPanel > ( ) ;
Panels = _panels . AsReadOnly ( ) ;
}
protected override void OnInitialized ( )
{
_resizeObserver = _resizeObserverFactory . Create ( ) ;
base . OnInitialized ( ) ;
}
protected override void OnParametersSet ( )
{
if ( _resizeObserver = = null )
{
_resizeObserver = _resizeObserverFactory . Create ( ) ;
}
Rerender ( ) ;
}
protected override async Task OnAfterRenderAsync ( bool firstRender )
{
if ( firstRender )
{
var items = _panels . Select ( x = > x . PanelRef ) . ToList ( ) ;
items . Add ( _tabsContentSize ) ;
if ( _panels . Count > 0 )
ActivePanel = _panels [ _activePanelIndex ] ;
await _resizeObserver . Observe ( items ) ;
_resizeObserver . OnResized + = OnResized ;
Rerender ( ) ;
StateHasChanged ( ) ;
_isRendered = true ;
}
}
public async ValueTask DisposeAsync ( )
{
if ( _isDisposed = = true )
return ;
_isDisposed = true ;
_resizeObserver . OnResized - = OnResized ;
await _resizeObserver . DisposeAsync ( ) ;
}
# endregion
[CascadingParameter(Name = "RightToLeft")] public bool RightToLeft { get ; set ; }
#region Children
[Inject] private IResizeObserverFactory _resizeObserverFactory { get ; set ; }
internal void AddPanel ( TabPanel tabPanel )
{
@ -414,37 +210,25 @@ public partial class Tabs : UIComponent, IAsyncDisposable
# endregion
#region Style and classes
#region Styling properties
protected string TabsClassnames = >
new CssBuilder ( "tabs" )
. AddClass ( $"tabs-rounded" , ApplyEffectsToContainer & & Rounded )
. AddClass ( $"paper-outlined" , ApplyEffectsToContainer & & Outlined )
. AddClass ( $"elevation-{Elevation}" , ApplyEffectsToContainer & & Elevation ! = 0 )
. AddClass ( $"tabs-reverse" , Position = = Position . Bottom )
. AddClass ( $"tabs-vertical" , IsVerticalTabs ( ) )
. AddClass ( $"tabs-vertical-reverse" , Position = = Position . Right & & ! RightToLeft | | ( Position = = Position . Left ) & & RightToLeft )
. AddClass ( InternalClassName )
. Build ( ) ;
protected string ToolbarClassnames = >
new CssBuilder ( "tabs-toolbar" )
. AddClass ( $"tabs-rounded" , ! ApplyEffectsToContainer & & Rounded )
. AddClass ( $"tabs-vertical" , IsVerticalTabs ( ) )
. AddClass ( $"tabs-toolbar-{Color.ToDescription()}" , Color ! = ThemeColor . Default )
. AddClass ( $"tabs-border-{ConvertPosition(Position).ToDescription()}" , Border )
. AddClass ( $"paper-outlined" , ! ApplyEffectsToContainer & & Outlined )
. AddClass ( $"elevation-{Elevation}" , ! ApplyEffectsToContainer & & Elevation ! = 0 )
. Build ( ) ;
protected string WrapperClassnames = >
new CssBuilder ( "tabs-toolbar-wrapper" )
. AddClass ( $"tabs-centered" , Centered )
. AddClass ( $"tabs-vertical" , IsVerticalTabs ( ) )
. Build ( ) ;
protected string WrapperScrollStyle = >
new StyleBuilder ( )
. AddStyle ( "transform" , $"translateX({(-1 * _scrollPosition).ToString(CultureInfo.InvariantCulture)}px)" , Position is Position . Top or Position . Bottom )
. AddStyle ( "transform" , $"translateY({(-1 * _scrollPosition).ToString(CultureInfo.InvariantCulture)}px)" , IsVerticalTabs ( ) )
. Build ( ) ;
@ -456,30 +240,19 @@ public partial class Tabs : UIComponent, IAsyncDisposable
protected string SliderClass = >
new CssBuilder ( "tab-slider" )
. AddClass ( $"{SliderColor.ToDescription()}" , SliderColor ! = ThemeColor . Inherit )
. AddClass ( $"tab-slider-horizontal" , Position is Position . Top or Position . Bottom )
. AddClass ( $"tab-slider-vertical" , IsVerticalTabs ( ) )
. AddClass ( $"tab-slider-horizontal-reverse" , Position = = Position . Bottom )
. AddClass ( $"tab-slider-vertical-reverse" , Position = = Position . Right | | Position = = Position . Start & & RightToLeft | | Position = = Position . End & & ! RightToLeft )
. Build ( ) ;
protected string MaxHeightStyles = >
new StyleBuilder ( )
. AddStyle ( "max-height" , MaxHeight . ToPx ( ) , MaxHeight ! = null )
. Build ( ) ;
protected string SliderStyle = > RightToLeft ?
new StyleBuilder ( )
. AddStyle ( "width" , _size . ToPx ( ) , Position is Position . Top or Position . Bottom )
. AddStyle ( "right" , _position . ToPx ( ) , Position is Position . Top or Position . Bottom )
. AddStyle ( "transition" , _disableSliderAnimation ? "none" : "right .3s cubic-bezier(.64,.09,.08,1);" , Position is Position . Top or Position . Bottom )
. AddStyle ( "transition" , _disableSliderAnimation ? "none" : "top .3s cubic-bezier(.64,.09,.08,1);" , IsVerticalTabs ( ) )
. AddStyle ( "height" , _size . ToPx ( ) , IsVerticalTabs ( ) )
. AddStyle ( "top" , _position . ToPx ( ) , IsVerticalTabs ( ) )
. Build ( ) : new StyleBuilder ( )
. AddStyle ( "width" , _size . ToPx ( ) , Position is Position . Top or Position . Bottom )
. AddStyle ( "left" , _position . ToPx ( ) , Position is Position . Top or Position . Bottom )
. AddStyle ( "transition" , _disableSliderAnimation ? "none" : "left .3s cubic-bezier(.64,.09,.08,1);" , Position is Position . Top or Position . Bottom )
. AddStyle ( "transition" , _disableSliderAnimation ? "none" : "top .3s cubic-bezier(.64,.09,.08,1);" , IsVerticalTabs ( ) )
. AddStyle ( "height" , _size . ToPx ( ) , IsVerticalTabs ( ) )
. AddStyle ( "top" , _position . ToPx ( ) , IsVerticalTabs ( ) )
@ -532,9 +305,151 @@ public partial class Tabs : UIComponent, IAsyncDisposable
return tabStyle ;
}
/// <summary>
/// Hides the active tab slider.
/// </summary>
[Parameter]
public bool HideSlider { get ; set ; }
/// <summary>
/// The color of the component. It supports the theme colors.
/// </summary>
[Parameter]
public ThemeColor Color { get ; set ; } = ThemeColor . Default ;
/// <summary>
/// The color of the tab slider. It supports the theme colors.
/// </summary>
[Parameter]
public ThemeColor SliderColor { get ; set ; } = ThemeColor . Inherit ;
/// <summary>
/// The color of the icon. It supports the theme colors.
/// </summary>
[Parameter]
public ThemeColor IconColor { get ; set ; } = ThemeColor . Inherit ;
/// <summary>
/// The color of the next/prev icons. It supports the theme colors.
/// </summary>
[Parameter]
public ThemeColor ScrollIconColor { get ; set ; } = ThemeColor . Inherit ;
/// <summary>
/// If true, always display the scroll buttons even if the tabs are smaller than the required with, buttons will be disabled if there is nothing to scroll.
/// </summary>
[Parameter]
public bool AlwaysShowScrollButtons { get ; set ; }
/// <summary>
/// Glyph to use for left pagination.
/// </summary>
[Parameter]
public string PrevIcon { get ; set ; } = Icons . Filled . ChevronLeft ;
/// <summary>
/// Glyph to use for right pagination.
/// </summary>
[Parameter]
public string NextIcon { get ; set ; } = Icons . Filled . ChevronRight ;
/// <summary>
/// Sets the position of the tabs itself.
/// </summary>
[Parameter]
public Position Position { get ; set ; } = Position . Top ;
/// <summary>
/// If true, will apply elevation, rounded, outlined effects to the whole tab component instead of just toolbar.
/// </summary>
[Parameter]
public bool ApplyEffectsToContainer { get ; set ; }
/// <summary>
/// If true, disables ripple effect.
/// </summary>
[Parameter]
public bool DisableRipple { get ; set ; }
/// <summary>
/// If true, disables slider animation
/// </summary>
[Parameter]
public bool DisableSliderAnimation { get = > _disableSliderAnimation ; set = > _disableSliderAnimation = value ; }
/// <summary>
/// Custom class/classes for TabPanel
/// </summary>
[Parameter]
public string TabPanelClass { get ; set ; }
/// <summary>
/// Custom class/classes for Selected Content Panel
/// </summary>
[Parameter]
public string PanelClass { get ; set ; }
/// <summary>
/// If true, render all tabs and hide (display:none) every non-active.
/// </summary>
[Parameter]
public bool KeepPanelsAlive { get ; set ; } = false ;
# endregion
#region Rendering and placement
#region Lifecycle
public Tabs ( )
{
_panels = new List < TabPanel > ( ) ;
Panels = _panels . AsReadOnly ( ) ;
}
protected override void OnInitialized ( )
{
_resizeObserver = _resizeObserverFactory . Create ( ) ;
base . OnInitialized ( ) ;
}
protected override void OnParametersSet ( )
{
if ( _resizeObserver = = null )
{
_resizeObserver = _resizeObserverFactory . Create ( ) ;
}
Rerender ( ) ;
}
protected override async Task OnAfterRenderAsync ( bool firstRender )
{
if ( firstRender )
{
var items = _panels . Select ( x = > x . PanelRef ) . ToList ( ) ;
items . Add ( _tabsContentSize ) ;
if ( _panels . Count > 0 )
ActivePanel = _panels [ _activePanelIndex ] ;
await _resizeObserver . Observe ( items ) ;
_resizeObserver . OnResized + = OnResized ;
Rerender ( ) ;
StateHasChanged ( ) ;
_isRendered = true ;
}
}
public async ValueTask DisposeAsync ( )
{
if ( _isDisposed = = true )
return ;
_isDisposed = true ;
_resizeObserver . OnResized - = OnResized ;
await _resizeObserver . DisposeAsync ( ) ;
}
private void Rerender ( )
{
@ -605,10 +520,6 @@ public partial class Tabs : UIComponent, IAsyncDisposable
private double GetPanelLength ( TabPanel panel ) = > panel = = null ? 0.0 : GetRelevantSize ( panel . PanelRef ) ;
# endregion
#region scrolling
private void SetScrollButtonVisibility ( )
{
_showScrollButtons = AlwaysShowScrollButtons | | _allTabsSize > _toolbarContentSize ;
@ -740,4 +651,5 @@ public partial class Tabs : UIComponent, IAsyncDisposable
}
# endregion
}