diff --git a/src/MudBlazor.Docs/Pages/Components/Stack/Examples/StackBreakpointExample.razor b/src/MudBlazor.Docs/Pages/Components/Stack/Examples/StackBreakpointExample.razor new file mode 100644 index 000000000000..be144f686927 --- /dev/null +++ b/src/MudBlazor.Docs/Pages/Components/Stack/Examples/StackBreakpointExample.razor @@ -0,0 +1,28 @@ +@namespace MudBlazor.Docs.Examples + + + + + Item 1 + Item 2 + Item 3 + + + + + + + + @foreach (var value in Enum.GetValues(typeof(Breakpoint)).Cast()){ + @value + } + + + + + +@code { + private Breakpoint _breakpoint = Breakpoint.None; + private bool _row = false; + private bool _reverse = false; +} \ No newline at end of file diff --git a/src/MudBlazor.Docs/Pages/Components/Stack/StackPage.razor b/src/MudBlazor.Docs/Pages/Components/Stack/StackPage.razor index 1abee50448c2..7dfc33fa9e26 100644 --- a/src/MudBlazor.Docs/Pages/Components/Stack/StackPage.razor +++ b/src/MudBlazor.Docs/Pages/Components/Stack/StackPage.razor @@ -24,6 +24,15 @@ + + + With the @nameof(MudStack.Breakpoint) property set, the state of the @nameof(MudStack.Row) property will be reversed based on the defined screen size breakpoint. For example, when set to a specific breakpoint such as @nameof(Breakpoint.Md), the row state is reversed at that screen size. If set to a range like @nameof(Breakpoint.MdAndUp), the row state will be reversed for all screen sizes greater than or equal to the specified breakpoint. + + + + + + The default spacing can be changed by setting any number between 0 and 16 with the @nameof(MudStack.Spacing) property. diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointReverseRowTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointReverseRowTest.razor new file mode 100644 index 000000000000..aa9ce80914ab --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointReverseRowTest.razor @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointReverseTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointReverseTest.razor new file mode 100644 index 000000000000..e4331ed246bd --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointReverseTest.razor @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointRowTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointRowTest.razor new file mode 100644 index 000000000000..73731a283b2d --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointRowTest.razor @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointTest.razor new file mode 100644 index 000000000000..d6bd541241d5 --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Stack/BreakpointTest.razor @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MudBlazor.UnitTests/Components/StackTests.cs b/src/MudBlazor.UnitTests/Components/StackTests.cs index b39a422f3a18..d8ce2594e5e8 100644 --- a/src/MudBlazor.UnitTests/Components/StackTests.cs +++ b/src/MudBlazor.UnitTests/Components/StackTests.cs @@ -82,6 +82,124 @@ public void CheckSpacingClass(int spacing) stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", "flex-column", $"gap-{spacing}" }); } + [Test] + [TestCase(Breakpoint.None)] + [TestCase(Breakpoint.Always)] + [TestCase(Breakpoint.Xs)] + [TestCase(Breakpoint.Sm)] + [TestCase(Breakpoint.Md)] + [TestCase(Breakpoint.Lg)] + [TestCase(Breakpoint.Xl)] + [TestCase(Breakpoint.Xxl)] + [TestCase(Breakpoint.SmAndDown)] + [TestCase(Breakpoint.MdAndDown)] + [TestCase(Breakpoint.LgAndDown)] + [TestCase(Breakpoint.XlAndDown)] + [TestCase(Breakpoint.SmAndUp)] + [TestCase(Breakpoint.MdAndUp)] + [TestCase(Breakpoint.LgAndUp)] + [TestCase(Breakpoint.XlAndUp)] + [TestCase(Breakpoint.None, true)] + [TestCase(Breakpoint.Always, true)] + [TestCase(Breakpoint.Xs, true)] + [TestCase(Breakpoint.Sm, true)] + [TestCase(Breakpoint.Md, true)] + [TestCase(Breakpoint.Lg, true)] + [TestCase(Breakpoint.Xl, true)] + [TestCase(Breakpoint.Xxl, true)] + [TestCase(Breakpoint.SmAndDown, true)] + [TestCase(Breakpoint.MdAndDown, true)] + [TestCase(Breakpoint.LgAndDown, true)] + [TestCase(Breakpoint.XlAndDown, true)] + [TestCase(Breakpoint.SmAndUp, true)] + [TestCase(Breakpoint.MdAndUp, true)] + [TestCase(Breakpoint.LgAndUp, true)] + [TestCase(Breakpoint.XlAndUp, true)] + [TestCase(Breakpoint.None, true, true)] + [TestCase(Breakpoint.Always, true, true)] + [TestCase(Breakpoint.Xs, true, true)] + [TestCase(Breakpoint.Sm, true, true)] + [TestCase(Breakpoint.Md, true, true)] + [TestCase(Breakpoint.Lg, true, true)] + [TestCase(Breakpoint.Xl, true, true)] + [TestCase(Breakpoint.Xxl, true, true)] + [TestCase(Breakpoint.SmAndDown, true, true)] + [TestCase(Breakpoint.MdAndDown, true, true)] + [TestCase(Breakpoint.LgAndDown, true, true)] + [TestCase(Breakpoint.XlAndDown, true, true)] + [TestCase(Breakpoint.SmAndUp, true, true)] + [TestCase(Breakpoint.MdAndUp, true, true)] + [TestCase(Breakpoint.LgAndUp, true, true)] + [TestCase(Breakpoint.XlAndUp, true, true)] + public void CheckBreakpointClass(Breakpoint breakpoint, bool row = false, bool reverse = false) + { + var stack = Context.RenderComponent(x => x.Add(c => c.Breakpoint, breakpoint).Add(c => c.Row, row).Add(c => c.Reverse, reverse)); + + // Get the Default and Reverse States + string defaultState = (row ? "row" : "column") + (reverse ? "-reverse" : string.Empty); + string reverseState = (row ? "column" : "row") + (reverse ? "-reverse" : string.Empty); + + // Get the Stack Class + var stackClass = stack.Find(".d-flex"); + + // Handle Special Cases + switch (breakpoint) + { + // If the Breakpoint is None or Always, return the default direction + case Breakpoint.None: // If breakpoint is None, return the default direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", "gap-3" }); + break; + case Breakpoint.Always: // If breakpoint is Always, return the reverse direction, honestly the user should just use the Row Property + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{reverseState}", "gap-3" }); + break; + case Breakpoint.Xs: // Xs is Reverse Direction, Sm and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{reverseState}", $"flex-sm-{defaultState}", "gap-3" }); + break; + case Breakpoint.Sm: // Xs is Default Direction, Sm is Reverse Direction, Md and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-sm-{reverseState}", $"flex-md-{defaultState}", "gap-3" }); + break; + case Breakpoint.Md: // Xs to Sm is Default Direction, Md is Reverse Direction, Lg and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-md-{reverseState}", $"flex-lg-{defaultState}", "gap-3" }); + break; + case Breakpoint.Lg: // Xs to Md is Default Direction, Lg is Reverse Direction, Xl and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-lg-{reverseState}", $"flex-xl-{defaultState}", "gap-3" }); + break; + case Breakpoint.Xl: // Xs to Lg is Default Direction, Xl is Reverse Direction, Xxl is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-xl-{reverseState}", $"flex-xxl-{defaultState}", "gap-3" }); + break; + case Breakpoint.Xxl: // Xs to Xl is Default Direction, Xxl is Reverse Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-xxl-{reverseState}", "gap-3" }); + break; + case Breakpoint.SmAndDown: // Sm and Down is Reverse Direction, Md and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{reverseState}", $"flex-md-{defaultState}", "gap-3" }); + break; + case Breakpoint.MdAndDown: // Md and Down is Reverse Direction, Lg and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{reverseState}", $"flex-lg-{defaultState}", "gap-3" }); + break; + case Breakpoint.LgAndDown: // Lg and Down is Reverse Direction, Xl and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{reverseState}", $"flex-xl-{defaultState}", "gap-3" }); + break; + case Breakpoint.XlAndDown: // Xl and Down is Reverse Direction, Xxl and Up is Default Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{reverseState}", $"flex-xxl-{defaultState}", "gap-3" }); + break; + case Breakpoint.SmAndUp: // Xs is Default Direction, Sm and Up is Reverse Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-sm-{reverseState}", "gap-3" }); + break; + case Breakpoint.MdAndUp: // Xs to Sm is Default Direction, Md and Up is Reverse Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-md-{reverseState}", "gap-3" }); + break; + case Breakpoint.LgAndUp: // Xs to Md is Default Direction, Lg and Up is Reverse Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-lg-{reverseState}", "gap-3" }); + break; + case Breakpoint.XlAndUp: // Xs to Lg is Default Direction, Xl and Up is Reverse Direction + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", $"flex-xl-{reverseState}", "gap-3" }); + break; + default: // Return the default direction if no Breakpoint is Matched + stackClass.ClassList.Should().ContainInOrder(new[] { "d-flex", $"flex-{defaultState}", "gap-3" }); + break; + } + } + [Test] [TestCase(Justify.FlexStart, "start")] [TestCase(Justify.Center, "center")] diff --git a/src/MudBlazor/Components/Stack/MudStack.razor.cs b/src/MudBlazor/Components/Stack/MudStack.razor.cs index 32157b89f4f4..20dd1c9961b4 100644 --- a/src/MudBlazor/Components/Stack/MudStack.razor.cs +++ b/src/MudBlazor/Components/Stack/MudStack.razor.cs @@ -16,7 +16,7 @@ public partial class MudStack : MudComponentBase { protected string Classname => new CssBuilder("d-flex") - .AddClass($"flex-{(Row ? "row" : "column")}{(Reverse ? "-reverse" : string.Empty)}") + .AddClass(getFlexDirection()) .AddClass($"justify-{Justify?.ToDescriptionString()}", Justify is not null) .AddClass($"align-{AlignItems?.ToDescriptionString()}", AlignItems is not null) .AddClass($"flex-{Wrap?.ToDescriptionString()}", Wrap is not null) @@ -25,6 +25,67 @@ public partial class MudStack : MudComponentBase .AddClass(Class) .Build(); + /// + /// Gets the CSS flex direction based on the current breakpoint and row settings. + /// + /// + /// This property generates a CSS flex direction string based on the following conditions: + /// - If the breakpoint is not set or is "none" or "always", it returns the default direction based on the Row and Reverse properties. + /// - If the breakpoint is one of the predefined sizes ("xs", "sm", "md", "lg", "xl", "xxl"), it generates a flex direction string that changes at the specified breakpoint. + /// - If the breakpoint ends with "anddown", it generates a flex direction string that applies up to and including the specified breakpoint. + /// - If the breakpoint ends with "andup", it generates a flex direction string that applies from the specified breakpoint and up. + /// + /// + /// A string representing the CSS flex direction based on the current breakpoint and row settings. + /// + private string getFlexDirection() + { + // Sets the inital direction based on the Row Parameter and Appends the reverse if needed + string defaultState = (Row ? "row" : "column") + (Reverse ? "-reverse" : string.Empty); + // Sets the reverse direction based on the Row Parameter and Appends the reverse if needed + string reverseState = (Row ? "column" : "row") + (Reverse ? "-reverse" : string.Empty); + + // Switch statement to determine the breakpoint and return the appropriate flex direction + switch (Breakpoint) + { + // If the Breakpoint is None or Always, return the default direction + case Breakpoint.None: // If breakpoint is None, return the default direction + return $"flex-{defaultState}"; + case Breakpoint.Always: // If breakpoint is Always, return the reverse direction, honestly the user should just use the Row Property + return $"flex-{reverseState}"; + case Breakpoint.Xs: // Xs is Reverse Direction, Sm and Up is Default Direction + return $"flex-{reverseState} flex-sm-{defaultState}"; + case Breakpoint.Sm: // Xs is Default Direction, Sm is Reverse Direction, Md and Up is Default Direction + return $"flex-{defaultState} flex-sm-{reverseState} flex-md-{defaultState}"; + case Breakpoint.Md: // Xs to Sm is Default Direction, Md is Reverse Direction, Lg and Up is Default Direction + return $"flex-{defaultState} flex-md-{reverseState} flex-lg-{defaultState}"; + case Breakpoint.Lg: // Xs to Md is Default Direction, Lg is Reverse Direction, Xl and Up is Default Direction + return $"flex-{defaultState} flex-lg-{reverseState} flex-xl-{defaultState}"; + case Breakpoint.Xl: // Xs to Lg is Default Direction, Xl is Reverse Direction, Xxl is Default Direction + return $"flex-{defaultState} flex-xl-{reverseState} flex-xxl-{defaultState}"; + case Breakpoint.Xxl: // Xs to Xl is Default Direction, Xxl is Reverse Direction + return $"flex-{defaultState} flex-xxl-{reverseState}"; + case Breakpoint.SmAndDown: // Sm and Down is Reverse Direction, Md and Up is Default Direction + return $"flex-{reverseState} flex-md-{defaultState}"; + case Breakpoint.MdAndDown: // Md and Down is Reverse Direction, Lg and Up is Default Direction + return $"flex-{reverseState} flex-lg-{defaultState}"; + case Breakpoint.LgAndDown: // Lg and Down is Reverse Direction, Xl and Up is Default Direction + return $"flex-{reverseState} flex-xl-{defaultState}"; + case Breakpoint.XlAndDown: // Xl and Down is Reverse Direction, Xxl and Up is Default Direction + return $"flex-{reverseState} flex-xxl-{defaultState}"; + case Breakpoint.SmAndUp: // Xs is Default Direction, Sm and Up is Reverse Direction + return $"flex-{defaultState} flex-sm-{reverseState}"; + case Breakpoint.MdAndUp: // Xs to Sm is Default Direction, Md and Up is Reverse Direction + return $"flex-{defaultState} flex-md-{reverseState}"; + case Breakpoint.LgAndUp: // Xs to Md is Default Direction, Lg and Up is Reverse Direction + return $"flex-{defaultState} flex-lg-{reverseState}"; + case Breakpoint.XlAndUp: // Xs to Lg is Default Direction, Xl and Up is Reverse Direction + return $"flex-{defaultState} flex-xl-{reverseState}"; + default: // Return the default direction if no Breakpoint is Matched + return $"flex-{defaultState}"; + } + } + /// /// Displays items horizontally. /// @@ -98,6 +159,13 @@ public partial class MudStack : MudComponentBase [Category(CategoryTypes.Stack.Behavior)] public Wrap? Wrap { get; set; } + /// + /// The breakpoint at which the Stack should switch to a row or column layout. + /// + [Parameter] + [Category(CategoryTypes.Stack.Behavior)] + public Breakpoint Breakpoint { get; set; } = Breakpoint.None; + /// /// The content within this component. /// diff --git a/src/MudBlazor/Styles/utilities/flexbox/_flex-direction.scss b/src/MudBlazor/Styles/utilities/flexbox/_flex-direction.scss index c106f76b9126..ad63c087ce8f 100644 --- a/src/MudBlazor/Styles/utilities/flexbox/_flex-direction.scss +++ b/src/MudBlazor/Styles/utilities/flexbox/_flex-direction.scss @@ -18,4 +18,4 @@ } } -@include flex-direction(""); +@include flex-direction(""); \ No newline at end of file