From 6df5cbbea10ba54567d456f34c224cd87b4fd44e Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Mon, 11 Sep 2023 12:45:58 +0200 Subject: [PATCH] Add FluentDialogHeader, FluentDialogFooter and related updates --- .../Microsoft.Fast.Components.FluentUI.xml | 84 +++++++++++++++- .../Demo/Shared/Pages/Dialog/DialogPage.razor | 54 +++++++++++ .../Pages/Dialog/Examples/DialogDefault.razor | 14 ++- .../Examples/DialogServiceExample.razor | 4 +- .../Demo/Shared/Pages/DialogServicePage.razor | 2 +- src/Core/Components/Dialog/FluentDialog.razor | 58 ++++------- .../Components/Dialog/FluentDialog.razor.cs | 45 ++++++++- .../Components/Dialog/FluentDialog.razor.css | 25 +---- .../Dialog/FluentDialogFooter.razor | 27 ++++++ .../Dialog/FluentDialogFooter.razor.cs | 95 +++++++++++++++++++ .../Dialog/FluentDialogFooter.razor.css | 15 +++ .../Dialog/FluentDialogHeader.razor | 14 +++ .../Dialog/FluentDialogHeader.razor.cs | 29 ++++++ .../Dialog/FluentDialogHeader.razor.css | 6 ++ 14 files changed, 395 insertions(+), 77 deletions(-) create mode 100644 src/Core/Components/Dialog/FluentDialogFooter.razor create mode 100644 src/Core/Components/Dialog/FluentDialogFooter.razor.cs create mode 100644 src/Core/Components/Dialog/FluentDialogFooter.razor.css create mode 100644 src/Core/Components/Dialog/FluentDialogHeader.razor create mode 100644 src/Core/Components/Dialog/FluentDialogHeader.razor.cs create mode 100644 src/Core/Components/Dialog/FluentDialogHeader.razor.css diff --git a/examples/Demo/Shared/Microsoft.Fast.Components.FluentUI.xml b/examples/Demo/Shared/Microsoft.Fast.Components.FluentUI.xml index b7b5f78cbd..02f2b1576a 100644 --- a/examples/Demo/Shared/Microsoft.Fast.Components.FluentUI.xml +++ b/examples/Demo/Shared/Microsoft.Fast.Components.FluentUI.xml @@ -1157,6 +1157,11 @@ + + + Gets or sets the reference to the item that holds this cell's values + + Gets or sets the cell type. See @@ -1179,6 +1184,11 @@ Gets or sets the owning component + + + Gets or sets the reference to the item that holds this row's values + + Gets or sets the index of this row @@ -1742,6 +1752,11 @@ Gets or sets if the dialog is hidden + + + The event callback invoked when change. + + Indicates that the dialog should trap focus. @@ -1769,7 +1784,17 @@ - Used when not calling the to show a dialog + Used when not calling the to show a dialog. + + + + + Content to render in header. + + + + + Content to render in footer. @@ -1782,6 +1807,63 @@ Closes the dialog + + + Gets or sets the dialog position: + left (full height), right (full height) + or screen middle (using Width and Height properties). + + + + + Text to display for the primary action. + + + + + When true, primary action's button is enabled. + + + + + The event callback invoked when primary button is clicked + + + + + Text to display for the secondary action. + + + + + When true, secondary action's button is enabled. + + + + + The event callback invoked when secondary button is clicked + + + + + Gets whether the primary button is displayed or not. Depends on PrimaryAction having a value. + + + + + Gets whether the secondary button is displayed or not. Depends on SecondaryAction having a value. + + + + + Title of the dialog + + + + + When true, shows the dismiss button in the header. + + Constructs an instance of . diff --git a/examples/Demo/Shared/Pages/Dialog/DialogPage.razor b/examples/Demo/Shared/Pages/Dialog/DialogPage.razor index bb6899528c..429641e92f 100644 --- a/examples/Demo/Shared/Pages/Dialog/DialogPage.razor +++ b/examples/Demo/Shared/Pages/Dialog/DialogPage.razor @@ -118,3 +118,57 @@ +

Dialog header and footer

+

The dialog header and footer can be changed by using the FluentDialog's HeaderTemplate and FooterTemplate parameters.

+

+ The default implementation uses the FluentDialogHeader and FluentDialogFooter components (see documentation below). + You can use the content of these components as the base for your own implementation: +

+ +

Default dialog header

+ +<div class="fluent-dialog-header"> + <FluentStack Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center"> + <FluentLabel Typo="Typography.PaneHeader" Style="width: 100%; margin: 0px;">@@Title</FluentLabel> + @@if (ShowDismiss) + { + <FluentButton Appearance = " Appearance.Stealth " + @@onclick= "@@(() = > Dialog!.CancelAsync())" > + <FluentIcon Icon = " CoreIcons.Regular.Size24.Dismiss & quot; Width = " 16px & quot; / > + </FluentButton > + } + </FluentStack> +</div> + + +

Default dialog footer

+ +<div class="@@(Alignment == HorizontalAlignment.Center ? "fluent-dialog-footer-normal" : "fluent-dialog-footer-bottom")"> + <FluentStack Orientation="Orientation.Horizontal" + HorizontalAlignment="@@(Alignment == HorizontalAlignment.Center ? HorizontalAlignment.Right : HorizontalAlignment.Left)"> + @@if (ShowPrimaryAction) + { + <FluentButton title="@@PrimaryAction" + @@onclick="@@OnPrimaryActionButtonClickAsync" + Appearance="Appearance.Accent" + Disabled="@@(!PrimaryActionEnabled)"> + @@PrimaryAction + </FluentButton> + } + @@if (ShowSecondaryAction) + { + <FluentButton title="@@SecondaryAction" + @@onclick="@@OnSecondaryActionButtonClickAsync" + Appearance="Appearance.Neutral" + Disabled="@@(!SecondaryActionEnabled)"> + @@SecondaryAction + </FluentButton> + } + </FluentStack> +</div> + + + + + + diff --git a/examples/Demo/Shared/Pages/Dialog/Examples/DialogDefault.razor b/examples/Demo/Shared/Pages/Dialog/Examples/DialogDefault.razor index 95f9d3a6ac..a069d80256 100644 --- a/examples/Demo/Shared/Pages/Dialog/Examples/DialogDefault.razor +++ b/examples/Demo/Shared/Pages/Dialog/Examples/DialogDefault.razor @@ -8,15 +8,22 @@ When 'Trap focus' is checked, only the elements within the dialog will receive focus. When unchecked, focus will also move outside of the dialog.

+

+ Hidden is bound to dialog visibility. You can show/hide dialog by changing this property or calling Show() / Hide() + on component reference. +

Modal Trap focus + + Hidden +
-
[Parameter] - public bool Hidden { get; set; } + public bool Hidden + { + get => _hidden; + set + { + if (value == _hidden) + return; + _hidden = value; + HiddenChanged.InvokeAsync(value); + } + } + + /// + /// The event callback invoked when change. + /// + [Parameter] + public EventCallback HiddenChanged { get; set; } /// /// Indicates that the dialog should trap focus. @@ -58,11 +75,23 @@ public partial class FluentDialog : FluentComponentBase //, IDisposable /// - /// Used when not calling the to show a dialog + /// Used when not calling the to show a dialog. /// [Parameter] public RenderFragment? ChildContent { get; set; } + /// + /// Content to render in header. + /// + [Parameter] + public RenderFragment? HeaderTemplate { get; set; } + + /// + /// Content to render in footer. + /// + [Parameter] + public RenderFragment? FooterTemplate { get; set; } + /// /// The event callback invoked to return the dialog result. /// @@ -85,7 +114,6 @@ public partial class FluentDialog : FluentComponentBase //, IDisposable .Build(); [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(DialogEventArgs))] - public FluentDialog() { @@ -159,9 +187,16 @@ public void ToggleSecondaryActionButton(bool isEnabled) public async Task CloseAsync(DialogResult dialogResult) { DialogContext?.DialogContainer.DismissInstance(Id!, dialogResult); - if (Instance.Parameters.OnDialogResult.HasDelegate) + if (Instance is not null) + { + if (Instance.Parameters.OnDialogResult.HasDelegate) + { + await Instance.Parameters.OnDialogResult.InvokeAsync(dialogResult); + } + } + else { - await Instance.Parameters.OnDialogResult.InvokeAsync(dialogResult); + Hide(); } } } \ No newline at end of file diff --git a/src/Core/Components/Dialog/FluentDialog.razor.css b/src/Core/Components/Dialog/FluentDialog.razor.css index f43d2d7007..3eeb5e1c3e 100644 --- a/src/Core/Components/Dialog/FluentDialog.razor.css +++ b/src/Core/Components/Dialog/FluentDialog.razor.css @@ -33,13 +33,6 @@ fluent-dialog[class~="left"]::part(control) { overflow: hidden; } - .fluent-dialog-content .fluent-dialog-header { - font-size: 20px; - font-weight: 600; - margin: 20px; - width: calc(100% - 40px); - } - .fluent-dialog-content .fluent-dialog-body { height: calc(100% - 150px); margin: 0px 20px 0px 20px; @@ -48,23 +41,7 @@ fluent-dialog[class~="left"]::part(control) { } .fluent-dialog-content .fluent-dialog-body[nofooter] { - height: calc(100% - 95px); - } - - .fluent-dialog-content .fluent-dialog-footer-bottom { - font-size: 14px; - font-weight: normal; - margin: 20px; - width: calc(100% - 40px); - position: fixed; - bottom: 0px; - } - - .fluent-dialog-content .fluent-dialog-footer-normal { - font-size: 14px; - font-weight: normal; - margin: 20px; - width: calc(100% - 40px); + height: calc(100% - 80px); } diff --git a/src/Core/Components/Dialog/FluentDialogFooter.razor b/src/Core/Components/Dialog/FluentDialogFooter.razor new file mode 100644 index 0000000000..0314e8faaf --- /dev/null +++ b/src/Core/Components/Dialog/FluentDialogFooter.razor @@ -0,0 +1,27 @@ +@namespace Microsoft.Fast.Components.FluentUI +@inherits FluentComponentBase + +
+ + @if (ShowPrimaryAction) + { + + @PrimaryAction + + } + @if (ShowSecondaryAction) + { + + @SecondaryAction + + } + +
+ diff --git a/src/Core/Components/Dialog/FluentDialogFooter.razor.cs b/src/Core/Components/Dialog/FluentDialogFooter.razor.cs new file mode 100644 index 0000000000..0b697ef862 --- /dev/null +++ b/src/Core/Components/Dialog/FluentDialogFooter.razor.cs @@ -0,0 +1,95 @@ +using Microsoft.AspNetCore.Components; + +namespace Microsoft.Fast.Components.FluentUI; + +public partial class FluentDialogFooter : FluentComponentBase +{ + [CascadingParameter] + private FluentDialog? Dialog { get; set; } + + /// + /// Gets or sets the dialog position: + /// left (full height), right (full height) + /// or screen middle (using Width and Height properties). + /// + [Parameter] + public virtual HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Center; + + /// + /// Text to display for the primary action. + /// + [Parameter] + public string? PrimaryAction { get; set; } = "Ok"; //DialogResources.ButtonPrimary; + + /// + /// When true, primary action's button is enabled. + /// + [Parameter] + public bool PrimaryActionEnabled { get; set; } = true; + + /// + /// The event callback invoked when primary button is clicked + /// + [Parameter] + public EventCallback OnPrimaryAction { get; set; } + + /// + /// Text to display for the secondary action. + /// + [Parameter] + public string? SecondaryAction { get; set; } = "Cancel"; //DialogResources.ButtonSecondary; + + /// + /// When true, secondary action's button is enabled. + /// + [Parameter] + public bool SecondaryActionEnabled { get; set; } = true; + + /// + /// The event callback invoked when secondary button is clicked + /// + [Parameter] + public EventCallback OnSecondaryAction { get; set; } + + /// + /// Gets whether the primary button is displayed or not. Depends on PrimaryAction having a value. + /// + private bool ShowPrimaryAction => !string.IsNullOrEmpty(PrimaryAction); + + /// + /// Gets whether the secondary button is displayed or not. Depends on SecondaryAction having a value. + /// + private bool ShowSecondaryAction => !string.IsNullOrEmpty(SecondaryAction); + + protected override void OnParametersSet() + { + if (Dialog is null) + { + throw new ArgumentNullException(nameof(Dialog), "FluentDialogFooter must be used inside FluentDialog"); + } + } + + private async Task OnPrimaryActionButtonClickAsync() + { + if (OnPrimaryAction.HasDelegate) + { + await OnPrimaryAction.InvokeAsync(); + } + else + { + await Dialog!.CloseAsync(Dialog.Instance?.Content ?? true); + } + } + + private async Task OnSecondaryActionButtonClickAsync() + { + if (OnSecondaryAction.HasDelegate) + { + await OnSecondaryAction.InvokeAsync(); + } + else + { + await Dialog!.CancelAsync(Dialog.Instance?.Content ?? false); + } + } +} \ No newline at end of file diff --git a/src/Core/Components/Dialog/FluentDialogFooter.razor.css b/src/Core/Components/Dialog/FluentDialogFooter.razor.css new file mode 100644 index 0000000000..c8b44e536c --- /dev/null +++ b/src/Core/Components/Dialog/FluentDialogFooter.razor.css @@ -0,0 +1,15 @@ +.fluent-dialog-footer-bottom { + font-size: 14px; + font-weight: normal; + margin: 20px; + width: calc(100% - 40px); + position: fixed; + bottom: 0px; +} + +.fluent-dialog-footer-normal { + font-size: 14px; + font-weight: normal; + margin: 20px; + width: calc(100% - 40px); +} diff --git a/src/Core/Components/Dialog/FluentDialogHeader.razor b/src/Core/Components/Dialog/FluentDialogHeader.razor new file mode 100644 index 0000000000..8bbabeecf3 --- /dev/null +++ b/src/Core/Components/Dialog/FluentDialogHeader.razor @@ -0,0 +1,14 @@ +@namespace Microsoft.Fast.Components.FluentUI +@inherits FluentComponentBase + +
+ + @Title + @if (ShowDismiss) + { + + + + } + +
\ No newline at end of file diff --git a/src/Core/Components/Dialog/FluentDialogHeader.razor.cs b/src/Core/Components/Dialog/FluentDialogHeader.razor.cs new file mode 100644 index 0000000000..36e4791d5e --- /dev/null +++ b/src/Core/Components/Dialog/FluentDialogHeader.razor.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Components; + +namespace Microsoft.Fast.Components.FluentUI; + +public partial class FluentDialogHeader : FluentComponentBase +{ + [CascadingParameter] + private FluentDialog? Dialog { get; set; } + + /// + /// Title of the dialog + /// + [Parameter] + public string Title { get; set; } = string.Empty; + + /// + /// When true, shows the dismiss button in the header. + /// + [Parameter] + public bool ShowDismiss { get; set; } + + protected override void OnParametersSet() + { + if (Dialog is null) + { + throw new ArgumentNullException(nameof(Dialog), "FluentDialogHeader must be used inside FluentDialog"); + } + } +} \ No newline at end of file diff --git a/src/Core/Components/Dialog/FluentDialogHeader.razor.css b/src/Core/Components/Dialog/FluentDialogHeader.razor.css new file mode 100644 index 0000000000..d34846fcf1 --- /dev/null +++ b/src/Core/Components/Dialog/FluentDialogHeader.razor.css @@ -0,0 +1,6 @@ +.fluent-dialog-header { + font-size: 20px; + font-weight: 600; + margin: 20px; + width: calc(100% - 40px); +}