From 0bca79ba7f0a45b0a9d7ad2ee0a1ec38277cc956 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Tue, 3 Oct 2023 00:35:07 +0200 Subject: [PATCH 01/13] wip --- .../Microsoft.Fast.Components.FluentUI.xml | 2120 +++-------------- .../Demo/Shared/Pages/Lab/IssueTester.razor | 32 +- .../Demo/Shared/Shared/DemoNavMenuTree.razor | 2 +- .../FluentCollapsibleRegion.razor | 11 + .../FluentCollapsibleRegion.razor.cs | 118 + .../FluentCollapsibleRegion.razor.css | 31 + src/Core/Components/Icons/CoreIcons.cs | 12 + .../NavMenu/FluentNavMenu.razor.css | 1 - .../Components/NavMenu2/FluentNavGroup.razor | 61 + .../NavMenu2/FluentNavGroup.razor.cs | 139 ++ .../NavMenu2/FluentNavGroup.razor.css | 77 + .../Components/NavMenu2/FluentNavLink.razor | 47 + .../NavMenu2/FluentNavLink.razor.cs | 99 + .../NavMenu2/FluentNavLink.razor.css | 38 + .../Components/NavMenu2/FluentNavMenu2.razor | 29 + .../NavMenu2/FluentNavMenu2.razor.cs | 112 + .../NavMenu2/FluentNavMenu2.razor.css | 158 ++ 17 files changed, 1324 insertions(+), 1763 deletions(-) create mode 100644 src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor create mode 100644 src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs create mode 100644 src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.css create mode 100644 src/Core/Components/NavMenu2/FluentNavGroup.razor create mode 100644 src/Core/Components/NavMenu2/FluentNavGroup.razor.cs create mode 100644 src/Core/Components/NavMenu2/FluentNavGroup.razor.css create mode 100644 src/Core/Components/NavMenu2/FluentNavLink.razor create mode 100644 src/Core/Components/NavMenu2/FluentNavLink.razor.cs create mode 100644 src/Core/Components/NavMenu2/FluentNavLink.razor.css create mode 100644 src/Core/Components/NavMenu2/FluentNavMenu2.razor create mode 100644 src/Core/Components/NavMenu2/FluentNavMenu2.razor.cs create mode 100644 src/Core/Components/NavMenu2/FluentNavMenu2.razor.css diff --git a/examples/Demo/Shared/Microsoft.Fast.Components.FluentUI.xml b/examples/Demo/Shared/Microsoft.Fast.Components.FluentUI.xml index f4c2fffcbc..b255119c53 100644 --- a/examples/Demo/Shared/Microsoft.Fast.Components.FluentUI.xml +++ b/examples/Demo/Shared/Microsoft.Fast.Components.FluentUI.xml @@ -209,16 +209,6 @@ Defaults to - - - displayed at the start of anchor content. - - - - - displayed at the end of anchor content. - - Gets or sets the content to be rendered inside the component. @@ -258,37 +248,6 @@ Gets or sets the content to be rendered inside the component. - - - Gets or sets the width of the component. - - - - - Gets or sets the height of the component. - - - - - Gets or sets the tooltip to display when hovering over the icon. - - - - - Gets or sets the icon to be displayed when the badge is cancellable. - By default, a small cross icon is displayed. - - - - - Event callback for when the badge is clicked. - - - - - Event callback for when the badge icon is clicked. - - The associated web component. @@ -639,30 +598,14 @@ Defaults to - - - Background color of this button (overrides the property). - Set the value "rgba(0, 0, 0, 0)" to display a transparent button. - - - - - Color of the font (overrides the property). - - - - - Display a progress ring and disable the button. - - - displayed at the start of button content. + displayed to the left of button content. - displayed at the end of button content. + displayed to the right of button content. @@ -680,11 +623,6 @@ Command executed when the user clicks on the button. - - - Constructs an instance of . - - @@ -704,7 +642,7 @@ - + @@ -848,22 +786,11 @@ for this column. - - - An optional CSS style specification. If specified, this is included in the style attribute of header and grid cells - for this column. - - If specified, controls the justification of header and grid cells for this column. - - - If true, generate a title attribute for the cell contents - - An optional template for this column's header cell. If not specified, the default header template @@ -920,12 +847,6 @@ The current . The data for the row being rendered. - - - Overridden by derived components to provide the raw content for the column's cells. - - The data for the row being rendered. - Gets or sets a that will be rendered for this column's header cell. @@ -1172,16 +1093,6 @@ Optionally defines a class to be applied to a rendered row. - - - Optionally defines a style to be applied to a rendered row. - - - - - If specified, grids render this fragment when there is no content. - - Constructs an instance of . @@ -1215,11 +1126,6 @@ - - - Gets or sets the reference to the item that holds this cell's values - - Gets or sets the cell type. See @@ -1242,20 +1148,14 @@ 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 - When FluentDataGrid is virtualized, this value is not used - String that gets applied to the css gridTemplateColumns attribute for the row + String that gets applied to the the css gridTemplateColumns attribute for the row @@ -1605,7 +1505,7 @@ - Type style for the day (numeric or 2-digits). + Format style for the day (numeric or 2-digits). @@ -1799,20 +1699,6 @@ - - - - - - - - - - - - Prevents scrolling outside of the dialog while it is shown. - - Indicates the element is modal. When modal, user mouse interaction will be limited to the contents of the element by a modal @@ -1824,11 +1710,6 @@ Gets or sets if the dialog is hidden - - - The event callback invoked when change. - - Indicates that the dialog should trap focus. @@ -1856,7 +1737,7 @@ - Used when not calling the to show a dialog. + Used when not calling the to show a dialog @@ -1864,159 +1745,12 @@ The event callback invoked to return the dialog result. - - - - - - - - - Shows the dialog - - - - - Hides the dialog - - - - - Toggle the primary action button - - - - - - Toggle the secondary action button - - - - - - Closes the dialog with a cancel result. - - - - - - Closes the dialog with a cancel result. - - - - - - - Closes the dialog with a OK result. - - - - - - Closes the dialog with a OK result. - - - - Closes the dialog - - - - - - - - - - - - - - - - - - - - - - - - - - - Gets or sets the content to be rendered inside the component. - - - - - - - - - - - - - - When true, the footer is visible. - Default is True. - - - - - Gets or sets the content to be rendered inside the component. - - - - - - - - - - - - - - - - - - - - - - - - - - When true, the header is visible. - Default is True. - - - - - When true, shows the dismiss button in the header. - If defined, this value will replace the one defined in the . - - - - - Gets or sets the content to be rendered inside the component. - - - - - - - - - + Constructs an instance of . @@ -2036,42 +1770,22 @@ Determines if the dialog is modal. Defaults to true. - Obscures the area around the dialog. - - - - - Determines if a modal dialog is dismissible by clicking - outside the dialog. Defaults to false. + When true, clicking outside the dialog will dismiss the dialog. - - - Prevents scrolling outside of the dialog while it is shown. - to use - - Indicates if dialog should trap focus. - Defaults to true. + Indicates that the dialog should trap focus. - Show the title in the header. - Defaults to true. + When true, shows the title in the header. - Show the dismiss button in the header. - Defaults to true. - - - - - Title of the dismiss button, display in a tooltip. - Defaults to "Close". + When true, shows the dismiss button in the header. @@ -2079,21 +1793,11 @@ Text to display for the primary action. - - - When true, primary action's button is enabled. - - Text to display for the secondary action. - - - When true, secondary action's button is enabled. - - Width of the dialog. Must be a valid CSS width value like "600px" or "3em" @@ -2125,11 +1829,6 @@ The value that labels an interactive element. - - - The type of dialog. - - Gets whether the primary button is displayed or not. Depends on PrimaryAction having a value. @@ -2234,50 +1933,35 @@ - - - - - - - - - - - /> - - - - - - - - - - - - - - - - - - - - - - - - + + + A event that will be invoked when showing a dialog with a custom component + - - + + + Shows the standard with the given parameters."/> + + The componente that receives the callback + Name of the callback function + that holds the content to display - - + + + Shows a custom splash screen dialog with the given parameters."/> + + The componente that receives the callback + Name of the callback function + that holds the content to display - - + + + Shows a splash screen of the given type with the given parameters."/> + + The type of the component to show + The componente that receives the callback + Name of the callback function + that holds the content to display @@ -2326,204 +2010,46 @@ Parameters to pass to component being displayed. - + - Shows a success message box. Does not have a callback function. + Shows a panel with the dialog component type as the body, + passing the specified - The message to display. - The title to display on the dialog. + Parameters to pass to component being displayed. - + - Shows a warning message box. Does not have a callback function. + Shows a panel with the dialog component type as the body, + passing the specified - The message to display. - The title to display on the dialog. + Type of component to display. + Parameters to pass to component being displayed. - + - Shows an error message box. Does not have a callback function. + Shows a dialog with the component type as the body, + passing the specified - The message to display. - The title to display on the dialog. + Parameters to pass to component being displayed. - + - Shows an information message box. Does not have a callback function. + Shows a dialog with the component type as the body, + passing the specified - The message to display. - The title to display on the dialog. + Type of component to display. + Content to pass to component being displayed. + Parameters to configure the dialog component. - + - Shows a confirmation message box. Has a callback function which returns boolean - (true=PrimaryAction clicked, false=SecondaryAction clicked). - - The component that receives the callback function. - The callback function. - The message to display. - The text to display on the primary button. - The text to display on the secondary button. - The title to display on the dialog. - - - - Shows a confirmation message box. Has no callback function - (true=PrimaryAction clicked, false=SecondaryAction clicked). - - The message to display. - The text to display on the primary button. - The text to display on the secondary button. - The title to display on the dialog. - - - - Shows a custom message box. Has a callback function which returns boolean - (true=PrimaryAction clicked, false=SecondaryAction clicked). - - Parameters to pass to component being displayed. - - - - Shows the standard with the given parameters. - - The component that receives the callback - Name of the callback function - that holds the content to display - - - - Shows a custom splash screen dialog with the given parameters. - - The component that receives the callback - Name of the callback function - that holds the content to display - - - - Shows a splash screen of the given type with the given parameters. - - The type of the component to show - The component that receives the callback - Name of the callback function - that holds the content to display - - - - Shows the standard with the given parameters. - - The component that receives the callback - Name of the callback function - that holds the content to display - - - - Shows the standard with the given parameters. - - that holds the content to display - - - - Shows a custom splash screen dialog with the given parameters."/> - - The component that receives the callback - Name of the callback function - that holds the content to display - - - - Shows a custom splash screen dialog with the given parameters. - - that holds the content to display - - - - Shows a splash screen of the given type with the given parameters. - - The type of the component to show - The component that receives the callback - Name of the callback function - that holds the content to display - - - - Shows a splash screen of the given type with the given parameters. - - The type of the component to show - that holds the content to display - - - - Convenience method to create a for a dialog result. - You can also call EventCallback.Factory.Create directly. + Convenience method to create a for a dialog result. + You can also call EventCallback.Factory.Create directly. - - - A event that will be invoked when showing a dialog with a custom component - - - - - Shows a dialog with the component type as the body, - passing the specified - - Type of content to pass to component being displayed. - Type of component to display. - Content to pass to component being displayed. - Parameters to configure the dialog component. - - - - Shows a dialog with the component type as the body, - passing the specified - - Type of component to display. - Content to pass to component being displayed. - Parameters to configure the dialog component. - - - - Shows a dialog with the component type as the body. - - Type of component to display. - Parameters to configure the dialog component. - - - - Updates a dialog. - - Type of content to pass to component being displayed. - Id of the dialog to update. - Parameters to configure the dialog component. - - - - Shows a panel with the dialog component type as the body - - Type of content to pass to component being displayed. - Type of component to display. - Content to pass to component being displayed. - Parameters to configure the dialog component. - - - - Shows a panel with the dialog component type as the body - - Type of component to display. - Content to pass to component being displayed. - Parameters to configure the dialog component. - - - - Shows a panel with the dialog component type as the body - - Type of component to display. - Parameters to configure the dialog component. - A event that will be invoked when showing a dialog with a custom component @@ -2687,14 +2213,8 @@ - FluentUI Emoji content. - - - - - Please use the constructor including parameters. + FluentUI Icon content. - @@ -2913,11 +2433,6 @@ List of delimiters chars. Example: " ,;". - - - If true, highlights the text until the next regex boundary - - Description: Scroll speed in pixels per second @@ -3015,12 +2530,6 @@ FluentUI Icon content. - - - Please use the constructor including parameters. - - - Initializes a new instance of the class. @@ -3350,128 +2859,6 @@ Gets or sets the content to be rendered inside the component. - - - Initializes a new instance of the class. - - - - - - - - - - - Sets the placeholder value of the element, generally used to provide a hint to the user. - - - - - For , this property must be True. - Set the property to 1 to select just one item. - - - - - Gets or sets the visual appearance. See - - - - - Filter the list of options (items), using the text encoded by the user. - - - - - Gets or sets the style applied to all of the component. - - - - - Gets or sets the css class applied to all of the component. - - - - - Gets or sets the number of maximum options (items) returned by . - Default value is 9. - - - - - Gets or sets the maximum number of options (items) selected. - Exceeding this value, the user must delete some elements in order to select new ones. - See the . - - - - - Gets or sets the message displayed when the is reached. - - - - - Template for the items. - - - - - Template for the items. - - - - - Footer content, placed at the top of the popup panel. - - - - - Footer content, placed at the bottom of the popup panel. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Gets or sets if the element is auto completes. See @@ -3498,14 +2885,21 @@ Gets or sets the visual appearance. See + + + Width style + + + + + Height style + + The maximum number of options that should be visible in the listbox scroll area. - - - Gets or sets if the element is disabled @@ -3539,75 +2933,6 @@ - - - People picker option component. - - - - - - - - - - - Gets or sets the initials to display if no image is provided. - Byt default, the first letters of the is used. - - - - - Gets or sets the name to display. - - - - - Gets or sets the content to display under the . - - - - - Gets or sets the image to display, in replacement of the initials. - - - - - Gets or sets the size of the image. - - - - - / The status to show. See for options. - - - - - The title to show on hover. If not provided, the status will be used. - - - - - Gets or sets the size to use. - Default is ExtraSmall. - - - - - Gets or sets the event raised when the user clicks on the dismiss button. - - - - - Gets or sets the title of the dismiss button. - - - - - - - - The open attribute. @@ -3651,16 +2976,6 @@ - - - Width of the component. - - - - - Height of the component or of the popup panel. - - Text used on aria-label attribute. @@ -3684,8 +2999,7 @@ - Function used to determine which value to return for the selected item. - Only for and components. + Function used to determine which text to return for the selected item. @@ -3763,7 +3077,7 @@ - + @@ -3775,600 +3089,169 @@ - - - - - - - - - uses this event to return the list of items to display. - - - - - - Text to search. - - - - - List of items to display. - - - - - Gets or sets the header content. - - - - - Gets or sets the subheader content. - - - - - Gets or sets the height of the header (in pixels). - - - - - Gets or set the tite of the navigation menu - - - - - Gets or sets the content of the navigation menu - - - - - Gets or sets the content of the body - - - - - Gets or sets the height of the header (in pixels). - - - - - Gets or sets the content to be rendered inside the component. - - - - - - - - - - - - - - Identifier of the source component clickable by the end user. - - - - - Gets or sets the automatic trigger. See - Possible values are None (default), Left, Middle, Right, Back, Forward - - - - - Gets or sets the Menu status. - - - - - Gets or sets the content to be rendered inside the component. - - - - - Menu position (left or right). - - - - - Width of this menu. - - - - - Raised when the property changed. - - - - - Draw the menu below the component clicked (true) or - using the mouse coordinates (false). - - - - - - - - Close the menu. - - - - - - Method called from JavaScript to get the current mouse ccordinates. - - - - - - - - Dispose this menu. - - - - - Gets or sets the owning FluentMenu. - - - - - Gets or sets the menu item label. - - - - - Gets or sets if the element is disabled. - - - - - The expanded state of the element. - - - - - The role of the element. - - - - - Gets or sets if the element is checked. - - - - - Gets or sets the content to be rendered inside the component. - - - - - List of sub-menu items. - - - - - Event raised when the user click on this item. - - - - - - - - - - - - - - - - - The type of message bar. Default is MessageType.MessageBar. See for more details. - - - - - The actual message instance shown in the message bar. - - - - - The message to be shown whennot using the MessageService methods - - - - - Intent of the message bar. Default is MessageIntent.Info. See for more details. - - - - - Icon to show in the message bar based on the intent of the message. See for more details. - - - - - Visibility of the message bar. Default is true. - - - - - Most important info to be shown in the message bar. - - - - - Time on which the message was created. Default is DateTime.Now. Onlu used when MessageType is Notification. - - - - - The color of the icon. Only applied when intent is MessageBarIntent.Custom - Default is Color.Accent - - - - - A link can be shown after the message. - - - - - Button to show as primary action. - - - - - Button to show as secondary action. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Display only messages for this section. - - - - - Displays messages as a single line (with the message only) - or as a card (with the detailed message). - - - - - Maximum number of messages displayed. Rest is stored in memory to be displayed when an shown message is closed. - Default value is 5 - Set a value equal to or less than zero, to display all messages for this (or all categories if not set). - - - - - Display the newest messages on top (true) or on bottom (false). - - - - - Clear all (shown and stored) messages when the user navigates to a new page. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Intent of the message bar. Default is MessageIntent.Info. - See for more details. - - - - - Indication of in which message bar the message needs to be shown. Default is null. - - - - - Most important info to be shown in the message bar. - - - - - Message to be shown in the message bar. - - - - - Link to be shown in the message bar (after the body). - - - - - Close the message bar. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - Identification of the the message belongs to. + Gets or sets the header content. - + - The timestamp of the message. + Gets or sets the subheader content. - + - Icon to show in the message bar based on the intent of the message. See for more details. + Gets or sets the height of the header (in pixels). - + - Most important info to be shown in the message bar. + Gets or set the tite of the navigation menu - + - Message to be shown in the message bar after the title. + Gets or sets the content of the navigation menu - + - Link to be shown in the message bar after the title/message. + Gets or sets the content of the body - + - Action to be executed when the message bar is closed. + Gets or sets the height of the header (in pixels). - + - Action to be executed for the primary button. + Gets or sets the content to be rendered inside the component. - + + + + + + + + + + - Action to be executed for the secondary button. + Identifier of the source component clickable by the end user. - + - Intent of the message bar. Default is MessageIntent.Info. + Gets or sets the automatic trigger. See + Possible values are None (default), Left, Middle, Right, Back, Forward - + - Remove the message bar after navigation. + Gets or sets the Menu status. - + - Timeout in seconds after which the message bar is removed. Default is null. + Gets or sets the content to be rendered inside the component. - - - - - - - - - - - - - - - - - - - - - - + - Gets all messages. + Menu position (left or right). - + - Retrieve messages to show in the message bar. + Width of this menu. - Number of messages to get (defaults to 5) - Optional section to retrieve messages for - - + - Show a message based on the provided parameters in a message bar. + Raised when the property changed. - Main info - - + - Show a message based on the provided parameters in a message bar. + Draw the menu below the component clicked (true) or + using the mouse coordinates (false). - Main info - Intent of the message - - + + + + - Show a message based on the provided parameters in a message bar. + Close the menu. - Main info - Intent of the message - Section to show the messagebar in - + - Show a message based on the provided options in a message bar. + Method called from JavaScript to get the current mouse ccordinates. - Message options + + - + - Show a message based on the provided parameters in a message bar. + Dispose this menu. - Main info - - + - Show a message based on the provided parameters in a message bar. + Gets or sets the owning FluentMenu. - Main info - Intent of the message - - + - Show a message based on the provided parameters in a message bar. + Gets or sets the menu item label. - Main info - Intent of the message - Section to show the messagebar in - - + - Show a message based on the provided message options in a message bar. + Gets or sets if the element is disabled. - Message options - - + - Clear all messages (per section, if provided) from the message bar. + The expanded state of the element. - Optional section - + - Remove a message from the message bar. + The role of the element. - Message to remove - - + + + Gets or sets if the element is checked. + - - + + + Gets or sets the content to be rendered inside the component. + - + - Remove all messages (per section, if provided) from the message bar. + List of sub-menu items. - Optional section - + - Count the number of messages (per section, if provided) in the message bar . + Event raised when the user click on this item. - Optional section - int + + + @@ -4746,9 +3629,6 @@ - - - @@ -4888,12 +3768,6 @@ Gets or sets the id of the component the popover is positioned relative to - - - The default horizontal position of the region relative to the anchor element - Default is unset. See - - Gets or sets popover opened state @@ -4935,20 +3809,22 @@ Child content of component, the content that the badge will be applied to. - + - The title to show on hover the component. - If not provided, the will be used. + The status to show. See for options. + Default is Available - + - The status to show. See for options. + Left position of the badge (percentage as number). + Default value is 50. - + - The title to show on hover the status. If not provided, the status will be used. + Bottom position of the badge (percentage as number). + Default value is -10. @@ -4959,7 +3835,7 @@ - Gets or sets the size to use. + Gets or sets the to use. Default is Small. @@ -5096,12 +3972,6 @@ - - - - - - Allows associating a datalist to the element by id. @@ -5162,21 +4032,6 @@ Gets or sets if the skeleton is shimmered - - - Gets or sets the width of the skeleton - - - - - Gets or sets the height of the skeleton - - - - - Gets or sets whether the skeleton is visible - - Gets or sets the content to be rendered inside the component. @@ -5393,11 +4248,6 @@ True to let the user edit the property. - - - Render the tab content only when the tab is selected. - - Customized content of this tab panel. @@ -5415,7 +4265,7 @@ - If this tab is outside of vistible tab panel area. + Gets if this component is out of panel. @@ -5581,12 +4431,6 @@ Gets or sets the content to be rendered inside the component. - - - - - - Gets or sets the text filed type. See @@ -5632,12 +4476,6 @@ Gets or sets the content to be rendered inside the component. - - - Specifies whether a form or an input field should have autocomplete "on" or "off" or another value. - An Id value must be set to use this property. - - Closes the toast @@ -6002,263 +4840,55 @@ - Gets or sets the content to be rendered inside the component. - - - - - Gets or sets a reference to the list of registered services. - - - https://github.com/dotnet/aspnetcore/issues/24193 - - - - - Gets a reference to the tooltip service (if registered). - - - - - Gets the default tooltip options. - - - - - Use ITooltipService to create the tooltip, if this service was injected. - If the is dynamic, set this to false. - Default, true. - - - - - Gets or sets if the tooltip is visible - - - - - Required. Gets or sets the control identifier associated with the tooltip. - - - - - Gets or sets the delay (in milliseconds). Default is 300. - - - - - Gets or sets the tooltip's position. See . - Don't set this if you want the tooltip to use the best position. - - - - - Gets or sets the maximum width of tooltip panel. - - - - - Controls when the tooltip updates its position, default is anchor which only updates when - the anchor is resized. auto will update on scroll/resize events. - Corresponds to anchored-region auto-update-mode. - - - - - Gets or sets whether the horizontal viewport is locked - - - - - Gets or sets whether the vertical viewport is locked - - - - - Gets or sets the content to be rendered inside the component. - - - - - Callback for when the tooltip is dismissed - - - - - - - - - - - - - - - - - - - - Service for managing tooltips. - - - - - Action that is invoked when the tooltip list is updated. - - - - - Gets the list of tooltips currently registered with the service. - - - - - Gets the global options for tooltips. - - - - - Adds a tooltip to the service. - - - - - - clears all tooltips from the service. - - - - - removes a tooltip from the service. - - Identifier of the tooltip - - - - Global options for tooltips. - - - - - Default delay (in milliseconds). - - - - - Gets or sets the delay (in milliseconds). Default is 300. - - - - - Gets or sets the default tooltip's position. - See - - - - - Gets or sets the default maximum width of tooltip panel. - Default is 500px. - - - - - Gets or sets whether the horizontal viewport is locked - - - - - Gets or sets whether the vertical viewport is locked - - - - - Options for a tooltip. - - - - - Gets or sets the unique identifier of the tooltip. - - - - - Gets or sets the anchor identifier of the tooltip. + Gets or sets the content to be rendered inside the component. - + - Gets or sets the tooltip content. + Gets or sets if the tooltip is visible - + - Gets or sets the tooltip panel width. + Gets or sets the anchor - + - Gets or sets the delay (in milliseconds). Default is 300. + Gets or sets the delay (in miliseconds) - + Gets or sets the tooltip's position. See - - - Callback for when the tooltip is dismissed - - - + - Gets or sets if the tooltip is visible + Controls when the tooltip updates its position, default is anchor which only updates when + the anchor is resized. auto will update on scroll/resize events. + Corresponds to anchored-region auto-update-mode. - - - - + - Initializes a new instance of the class. + Gets or sets wether the horizontal viewport is locked - + - Initializes a new instance of the class. + Gets or sets wether the vertical viewport is locked - Default global options - - - - - - - - - - - - - - - - - - - - - - + - Clears all tooltips from the service. + Gets or sets the content to be rendered inside the component. - - + + + Callback for when the tooltip is dismissed + @@ -9385,31 +8015,6 @@ A sticky header row. - - - The type of . - - - - - A normal dialog. - - - - - A dialog shown as a message box. - - - - - A dialog shown as a panel. - - - - - A dialog shown as a splash screen. - - Defines the role of the divider. @@ -9697,32 +8302,27 @@ - No positions set. + The anchored region position is unset. - Position at the start of the anchor. + The anchored region is positioned at the start of the anchor. - Position at the end of the anchor. + The anchored region is positioned at the end of the anchor. - Position on the left of the anchor. + The anchored region is positioned in the left of the anchor. - Position on the right of the anchor. - - - - - Position at the center of the anchor. + The anchored region is positioned in the right of the anchor. @@ -9929,37 +8529,6 @@ Informs about a custom event or operation. - - - - - - - - - - - - - - - - - - - - - - - - To be displayed in a at the top of screen, dialog or card. - - - - - To be displayed in a notification center. - - Defines the possible options for mouse buttons. @@ -10008,18 +8577,12 @@ - - - - - - @@ -10029,24 +8592,15 @@ - - - - - - - - - @@ -10056,9 +8610,6 @@ - - - @@ -10068,9 +8619,6 @@ - - - @@ -10495,22 +9043,17 @@ - No position set. + No positioning set - Position at the top. + Position at the top - Position at the bottom. - - - - - Position at the center. + Position at the bottom @@ -10526,6 +9069,30 @@ true if both sets render the same attributes; otherwise, false. + + + Used to convert a CssBuilder into a null when it is empty. + Usage: class=null causes the attribute to be excluded when rendered. + + + string + + + + Used to convert a StyleBuilder into a null when it is empty. + Usage: style=null causes the attribute to be excluded when rendered. + + + string + + + + Used to convert a string.IsNullOrEmpty into a null when it is empty. + Usage: attribute=null causes the attribute to be excluded when rendered. + + + string + Extension methods for . @@ -10615,36 +9182,6 @@ - - - The text to show for the button - - - - - The function to call when the link is clicked - - - - - The text to show for the link - - - - - The address to navigate to when the link is clicked - - - - - The target window or frame to open the link in - - - - - The function to call when the link is clicked - - Represents an event that you may subscribe to. This differs from normal C# events in that the handlers @@ -10681,12 +9218,6 @@ Defines the global Fluent UI Blazor component library services configuration - - - Gets or sets a value indicating whether the library should use the TooltipServiceProvider. - If set to true, add the FluentTooltipProvider component at end of the MainLayout.razor page. - - A strongly-typed resource class, for looking up localized strings, etc. @@ -10799,16 +9330,32 @@ Looks up a localized string similar to {0} years ago. - + - Initializes a new instance of the class. + Creates a CssBuilder used to define conditional CSS classes used in a component. + Call Build() to return the completed CSS Classes as a string. + + + + + + Creates an Empty CssBuilder used to define conditional CSS classes used in a component. + Call Build() to return the completed CSS Classes as a string. - Initializes a new instance of the class. + Creates a CssBuilder used to define conditional CSS classes used in a component. + Call Build() to return the completed CSS Classes as a string. + + + + + + Adds a raw string to the builder that will be concatenated with the next class or value added to the builder. - The user classes to include at the end. + + CssBuilder @@ -10825,88 +9372,68 @@ Condition in which the CSS Class is added. CssBuilder - + Adds a conditional CSS Class to the builder with space separator. CSS Class to conditionally add. - Condition in which the CSS Class is added. + Nullable condition in which the CSS Class is added. CssBuilder - - - Finalize the completed CSS Classes as a string. - - string - - - - ToString should only and always call Build to finalize the rendered string. - - - - + - Adds a raw string to the builder that will be concatenated with the next classes or value added to the builder. + Adds a conditional CSS Class to the builder with space separator. - - StyleBuilder + CSS Class to conditionally add. + Condition in which the CSS Class is added. + CssBuilder - + - Initializes a new instance of the class. + Adds a conditional CSS Class to the builder with space separator. + Function that returns a CSS Class to conditionally add. + Condition in which the CSS Class is added. + CssBuilder - + - Adds a conditional in-line style to the builder with space separator and closing semicolon.. + Adds a conditional CSS Class to the builder with space separator. - - - Style to add - StyleBuilder + Function that returns a CSS Class to conditionally add. + Condition in which the CSS Class is added. + CssBuilder - + - Adds a conditional in-line style to the builder with space separator and closing semicolon.. + Adds a conditional nested CssBuilder to the builder with space separator. - - - Style to conditionally add. - Condition in which the style is added. - StyleBuilder + CSS Class to conditionally add. + Condition in which the CSS Class is added. + CssBuilder - + - Adds a conditional in-line style to the builder with space separator and closing semicolon.. + Adds a conditional CSS Class to the builder with space separator. - - - Style to conditionally add. - Condition in which the style is added. - StyleBuilder + CSS Class to conditionally add. + Condition in which the CSS Class is added. + CssBuilder - + - Finalize the completed Style as a string. + Adds a conditional CSS Class when it exists in a dictionary to the builder with space separator. + Null safe operation. - string + Additional Attribute splat parameters + CssBuilder - + - Finalize the completed Style as a string. + Finalize the completed CSS Classes as a string. string - - - Adds a raw string to the builder that will be concatenated with the next style or value added to the builder. - - - - - StyleBuilder - Flags for a member that is JSON (de)serialized. @@ -10933,7 +9460,7 @@ Inspired from https://github.com/MudBlazor - + Splits the text into fragments, according to the text to be highlighted @@ -10942,19 +9469,32 @@ The texts to be highlighted Regex expression that was used to split fragments. Whether it's case sensitive or not - If true, splits until the next regex boundary - + + + Creates a StyleBuilder used to define conditional in-line style used in a component. Call Build() to return the completed style as a string. + + + + + + + Creates a StyleBuilder used to define conditional in-line style used in a component. Call Build() to return the completed style as a string. + + + + - Initializes a new instance of the class. + Creates a StyleBuilder used to define conditional in-line style used in a component. Call Build() to return the completed style as a string. - + - Initializes a new instance of the class. + Creates a StyleBuilder used to define conditional in-line style used in a component. Call Build() to return the completed style as a string. - The user styles to include at the end. + + @@ -10962,6 +9502,13 @@ + + + Adds a raw string to the builder that will be concatenated with the next style or value added to the builder. + + + StyleBuilder + Adds a conditional in-line style to the builder with space separator and closing semicolon.. @@ -10979,6 +9526,15 @@ Condition in which the style is added. StyleBuilder + + + Adds a conditional in-line style to the builder with space separator and closing semicolon.. + + + Style to conditionally add. + Condition in which the style is added. + + Adds a conditional in-line style to the builder with space separator and closing semicolon.. @@ -10988,25 +9544,69 @@ Condition in which the style is added. StyleBuilder - + - Finalize the completed Style as a string. + Adds a conditional in-line style to the builder with space separator and closing semicolon.. - string + + Style to conditionally add. + Condition in which the style is added. + StyleBuilder - + - ToString should only and always call Build to finalize the rendered string. + Adds a conditional nested StyleBuilder to the builder with separator and closing semicolon. - + Style Builder to conditionally add. + StyleBuilder - + - Adds a raw string to the builder that will be concatenated with the next style or value added to the builder. + Adds a conditional nested StyleBuilder to the builder with separator and closing semicolon. - + Style Builder to conditionally add. + Condition in which the style is added. + StyleBuilder + + + + Adds a conditional in-line style to the builder with space separator and closing semicolon.. + + Style Builder to conditionally add. + Condition in which the styles are added. + StyleBuilder + + + + Adds a conditional in-line style to the builder with space separator and closing semicolon.. + A ValueBuilder action defines a complex set of values for the property. + + + + + + + + Adds a conditional in-line style when it exists in a dictionary to the builder with separator. + Null safe operation. + + Additional Attribute splat parameters StyleBuilder + + + Finalize the completed Style as a string. + + string + + + + Adds a space separated conditional value to a property. + + + + + Custom -derived type for the CheckRGBString method. diff --git a/examples/Demo/Shared/Pages/Lab/IssueTester.razor b/examples/Demo/Shared/Pages/Lab/IssueTester.razor index e8853c9642..358c06d209 100644 --- a/examples/Demo/Shared/Pages/Lab/IssueTester.razor +++ b/examples/Demo/Shared/Pages/Lab/IssueTester.razor @@ -1 +1,31 @@ -@page "/IssueTester" \ No newline at end of file +@page "/IssueTester" + +
+ + Item 1 + Item 2 + + Item 3.1 + Item 3.2 + + Item 4 + Item 5 + + Item 6.1 + Item 6.2 + + Item 6.3.1 + Item 6.3.2 + + Item 6.3.3.1 + Item 6.3.3.2 + + + + +
+ +@code +{ + bool Expanded = true; +} \ No newline at end of file diff --git a/examples/Demo/Shared/Shared/DemoNavMenuTree.razor b/examples/Demo/Shared/Shared/DemoNavMenuTree.razor index e4842e482d..67df067879 100644 --- a/examples/Demo/Shared/Shared/DemoNavMenuTree.razor +++ b/examples/Demo/Shared/Shared/DemoNavMenuTree.razor @@ -1,5 +1,5 @@
- +

Home

diff --git a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor new file mode 100644 index 0000000000..bd8b6b45f8 --- /dev/null +++ b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor @@ -0,0 +1,11 @@ +@namespace Microsoft.Fast.Components.FluentUI +@inherits FluentComponentBase +@using System.Globalization + +
+
+
+ @ChildContent +
+
+
\ No newline at end of file diff --git a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs new file mode 100644 index 0000000000..2ad407ee4c --- /dev/null +++ b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs @@ -0,0 +1,118 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.Fast.Components.FluentUI.Utilities; +using Microsoft.JSInterop; + + +namespace Microsoft.Fast.Components.FluentUI; +public partial class FluentCollapsibleRegion : FluentComponentBase +{ + + internal enum CollapseState + { + Entering, Entered, Exiting, Exited + } + + //internal double _height; + private bool _expanded, _isRendered, _updateHeight; + private ElementReference _wrapper; + internal CollapseState _state = CollapseState.Exited; + + protected string? StyleValue => + new StyleBuilder() + .AddStyle("max-height", MaxHeight, MaxHeight is not null) + .AddStyle("height", "auto", Expanded) + .AddStyle("height", "0", !Expanded) + .AddStyle(Style) + .Build(); + + protected string? ClassValue => + new CssBuilder("fluent-collapsible-region-container") + .AddClass($"fluent-collapsible-region-entering", _state == CollapseState.Entering) + .AddClass($"fluent-collapsible-region-entered", _state == CollapseState.Entered) + .AddClass($"fluent-collapsible-region-exiting", _state == CollapseState.Exiting) + .AddClass(Class) + .Build(); + + /// + /// If true, the region is expaned, otherwise it is collapsed. + /// + [Parameter] + public bool Expanded + { + get => _expanded; + set + { + if (_expanded == value) + return; + _expanded = value; + + if (_isRendered) + { + _state = _expanded ? CollapseState.Entering : CollapseState.Exiting; + //_ = UpdateHeight(); + _updateHeight = true; + } + else if (_expanded) + { + _state = CollapseState.Entered; + } + + _ = ExpandedChanged.InvokeAsync(_expanded); + } + } + + /// + /// Explicitly sets the height for the Collapse element to override the css default. + /// + [Parameter] + public string? MaxHeight { get; set; } + + /// + /// Child content of component. + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [Parameter] + public EventCallback OnAnimationEnd { get; set; } + + [Parameter] + public EventCallback ExpandedChanged { get; set; } + + + //internal async Task UpdateHeight() + //{ + // try + // { + // _height = (await _wrapper.MudGetBoundingClientRectAsync())?.Height ?? 0; + // } + // catch (Exception ex) when (ex is JSDisconnectedException or TaskCanceledException) + // { + // _height = 0; + // } + + // if (_height > MaxHeight) + // { + // _height = MaxHeight.Value; + // } + //} + + //protected override async Task OnAfterRenderAsync(bool firstRender) + protected override void OnAfterRender(bool firstRender) + { + if (firstRender) + { + _isRendered = true; + //await UpdateHeight(); + } + else if (_updateHeight && _state is CollapseState.Entering or CollapseState.Exiting) + { + _updateHeight = false; + //await UpdateHeight(); + StateHasChanged(); + } + } +} diff --git a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.css b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.css new file mode 100644 index 0000000000..3b3f6944ac --- /dev/null +++ b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.css @@ -0,0 +1,31 @@ +.fluent-collapsible-region-container { + height: 0; + overflow: hidden; +} + + + + +.fluent-collapsible-region-entered { + overflow: initial; +} + + +.fluent-collapsible-region-exiting { +} + + .fluent-collapsible-region-exiting.fluent-navgroup-collapsible-region { + animation-duration: 300ms; + } + +.fluent-collapsible-region-hidden { + visibility: hidden; +} + +.fluent-collapsible-region-wrapper { + display: flex; +} + +.fluent-collapsible-region-wrapper-inner { + width: 100%; +} diff --git a/src/Core/Components/Icons/CoreIcons.cs b/src/Core/Components/Icons/CoreIcons.cs index 7f8a26be92..149a6d2b9c 100644 --- a/src/Core/Components/Icons/CoreIcons.cs +++ b/src/Core/Components/Icons/CoreIcons.cs @@ -53,6 +53,18 @@ public class Warning : Icon { public Warning() : base("Warning", IconVariant.Fil } } + internal static partial class Regular + { + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal static partial class Size12 + { + public class ChevronDown : Icon { public ChevronDown() : base("ChevronDown", IconVariant.Regular, IconSize.Size12, "") { } } + public class ChevronLeft : Icon { public ChevronLeft() : base("ChevronLeft", IconVariant.Regular, IconSize.Size12, "") { } } + public class ChevronRight : Icon { public ChevronRight() : base("ChevronRight", IconVariant.Regular, IconSize.Size12, "") { } } + public class ChevronUp : Icon { public ChevronUp() : base("ChevronUp", IconVariant.Regular, IconSize.Size12, "") { } } + + } + } internal static partial class Regular { [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] diff --git a/src/Core/Components/NavMenu/FluentNavMenu.razor.css b/src/Core/Components/NavMenu/FluentNavMenu.razor.css index ca6e73f0da..23b0d4d066 100644 --- a/src/Core/Components/NavMenu/FluentNavMenu.razor.css +++ b/src/Core/Components/NavMenu/FluentNavMenu.razor.css @@ -5,7 +5,6 @@ background-color: var(--neutral-layer-1); } - /* NavMenu expander */ diff --git a/src/Core/Components/NavMenu2/FluentNavGroup.razor b/src/Core/Components/NavMenu2/FluentNavGroup.razor new file mode 100644 index 0000000000..92a030756e --- /dev/null +++ b/src/Core/Components/NavMenu2/FluentNavGroup.razor @@ -0,0 +1,61 @@ +@namespace Microsoft.Fast.Components.FluentUI +@inherits FluentComponentBase +@using Microsoft.AspNetCore.Components.Rendering +@using Microsoft.AspNetCore.Components.Routing + +
+
+
+ @if (!string.IsNullOrEmpty(Href)) + { + + @if (Icon is not null) + { + + } + + + + @_renderButton + } + else + { + + } +
+
+ + + @ChildContent + + +
+ +@code { + private void RenderButton(RenderTreeBuilder __builder) + { + @if (!HideExpandIcon) + { + + + } + } +} \ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavGroup.razor.cs b/src/Core/Components/NavMenu2/FluentNavGroup.razor.cs new file mode 100644 index 0000000000..5e4269b6d4 --- /dev/null +++ b/src/Core/Components/NavMenu2/FluentNavGroup.razor.cs @@ -0,0 +1,139 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.Fast.Components.FluentUI.Utilities; + +namespace Microsoft.Fast.Components.FluentUI; + +#nullable enable +public partial class FluentNavGroup : FluentComponentBase +{ + private bool _expanded; + private readonly RenderFragment _renderButton; + + protected string? Classname => + new CssBuilder("fluent-nav-group") + .AddClass("fluent-nav-item") + .AddClass(Class) + .AddClass("expanded", Expanded) + .AddClass($"disabled", Disabled) + .Build(); + + protected string? ButtonClassname => + new CssBuilder("expand-collapse-button") + .AddClass("rotate", Expanded) + .Build(); + + protected string? IconClassname => + new CssBuilder("fluent-nav-icon") + .AddClass($"fluent-nav-icon-default", IconColor == Color.Accent) + .Build(); + + protected string? ExpandIconClassname => + new CssBuilder("fluent-nav-expand-icon") + .AddClass($"fluent-transform", Expanded && !Disabled) + .AddClass($"fluent-transform-disabled", Expanded && Disabled) + .Build(); + + internal Dictionary Attributes + { + get => Disabled ? new Dictionary() : new Dictionary + { + { "href", Href }, + { "target", Target }, + { "rel", !string.IsNullOrWhiteSpace(Target) ? "noopener noreferrer" : string.Empty } + }; + } + + [Parameter] + public string? Title { get; set; } + + /// + /// URL for the link. + /// + [Parameter] + public string? Href { get; set; } + + /// + /// The target attribute specifies where to open the link, if Href is specified. + /// Possible values: _blank | _self | _parent | _top. + /// + [Parameter] + public string? Target { get; set; } + + /// + /// Class names to use to indicate the item is active, separated by space. + /// + [Parameter] + public string ActiveClass { get; set; } = "active"; + + /// + /// Icon to use if set. + /// + [Parameter] + public Icon? Icon { get; set; } + + /// + /// The color of the icon. Default value uses the accent color. + /// + [Parameter] + public Color IconColor { get; set; } = Color.Accent; + + /// + /// If true, the button will be disabled. + /// + [Parameter] + public bool Disabled { get; set; } + + /// + /// If true, expands the nav group, otherwise collapse it. + /// Two-way bindable + /// + [Parameter] + public bool Expanded + { + get => _expanded; + set + { + if (_expanded == value) + return; + + _expanded = value; + ExpandedChanged.InvokeAsync(_expanded); + } + } + + /// + /// If true, hides expand-icon at the end of the NavGroup. + /// + [Parameter] + public bool HideExpandIcon { get; set; } + + /// + /// Explicitly sets the height for the Collapse element to override the css default. + /// + [Parameter] + public string? MaxHeight { get; set; } + + /// + /// If set, overrides the default expand icon. + /// + [Parameter] + public Icon ExpandIcon { get; set; } = new CoreIcons.Regular.Size12.ChevronDown(); + + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [Parameter] + public EventCallback ExpandedChanged { get; set; } + + public FluentNavGroup() + { + _renderButton = RenderButton; + } + + protected Task ExpandedToggleAsync() + { + _expanded = !Expanded; + + return ExpandedChanged.InvokeAsync(_expanded); + } +} \ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavGroup.razor.css b/src/Core/Components/NavMenu2/FluentNavGroup.razor.css new file mode 100644 index 0000000000..c1a7bfbb4f --- /dev/null +++ b/src/Core/Components/NavMenu2/FluentNavGroup.razor.css @@ -0,0 +1,77 @@ +::deep .fluent-nav-link { + width: 100%; + font-weight: 500; + color: inherit; + line-height: 1.75; + display: inline-flex; + align-items: center; + justify-content: flex-start; + background-color: transparent; + /*margin-inline-start: calc(var(--design-unit) * 2px);*/ + /*margin-inline-end: calc(var(--design-unit) * 2px);*/ + text-decoration: none; +} + +::deep .fluent-nav-icon { + margin-inline-end: calc(var(--design-unit) * 2px + 2px) +} + +::deep .fluent-nav-group.disabled { + color: var(--neutral-fill-secondary-rest) !important; + cursor: not-allowed; + pointer-events: none; +} + + + ::deep .fluent-nav-group.disabled .fluent-nav-icon { + fill: var(--neutral-stroke-rest) !important; + } + + ::deep .fluent-nav-group .fluent-nav-text { + font-weight: 500; + } + +/* + Group expander +*/ +::deep .expand-collapse-button { + position: absolute; + right: calc(var(--design-unit) * 2px); + left: unset; + + + background: none; + border: none; + border-radius: calc(var(--control-corner-radius) * 1px); + width: calc((((var(--base-height-multiplier) / 2) * var(--design-unit)) + ((var(--design-unit) * var(--density)) / 2) + (var(--design-unit) * 2)) * 1px); + height: calc((((var(--base-height-multiplier) / 2) * var(--design-unit)) + ((var(--design-unit) * var(--density)) / 2) + (var(--design-unit) * 2)) * 1px); + padding: 0px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; +} + +[dir="rtl"] * .expand-collapse-button { + position: absolute; + left: calc(var(--design-unit) * 2px); + right: unset; + margin-inline-start: calc(var(--expand-collapse-button-size) - (var(--design-unit) * 2px)); +} + +::deep .expand-collapse-button:hover { + background: var(--tree-item-expand-collapse-hover) +} + +::deep svg { + transition: transform 0.1s linear 0s; + pointer-events: none; +} + +[dir="rtl"] ::deep svg { + transform: rotate(180deg); +} + +::deep .rotate svg { + transform: rotate(90deg); +} diff --git a/src/Core/Components/NavMenu2/FluentNavLink.razor b/src/Core/Components/NavMenu2/FluentNavLink.razor new file mode 100644 index 0000000000..0603b0fba5 --- /dev/null +++ b/src/Core/Components/NavMenu2/FluentNavLink.razor @@ -0,0 +1,47 @@ +@namespace Microsoft.Fast.Components.FluentUI +@inherits FluentComponentBase +@using Microsoft.AspNetCore.Components.Routing + +
+
+
+ @if (!OnClick.HasDelegate) + { + + @if (Icon is not null) + { + + } + else + { + + } +
+ @ChildContent +
+
+ } + else + { +
+ @if (Icon is not null) + { + + } +else + { + + } +
+ @ChildContent +
+
+ } +
+
+
\ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavLink.razor.cs b/src/Core/Components/NavMenu2/FluentNavLink.razor.cs new file mode 100644 index 0000000000..025ad7fd83 --- /dev/null +++ b/src/Core/Components/NavMenu2/FluentNavLink.razor.cs @@ -0,0 +1,99 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.Fast.Components.FluentUI.Utilities; + + +namespace Microsoft.Fast.Components.FluentUI; + +#nullable enable +public partial class FluentNavLink +{ + internal string? ClassValue => new CssBuilder("fluent-nav-item") + .AddClass(Class) + .Build(); + + internal string? LinkClassname => new CssBuilder("fluent-nav-link") + .AddClass($"disabled", Disabled) + .Build(); + + + internal Dictionary Attributes + { + get => Disabled ? new Dictionary() : new Dictionary + { + { "href", Href }, + { "target", Target }, + { "rel", !string.IsNullOrWhiteSpace(Target) ? "noopener noreferrer" : string.Empty } + }; + } + + [Inject] + private NavigationManager NavigationManager { get; set; } = default!; + + /// + /// URL for the link. + /// + [Parameter] + public string? Href { get; set; } + + /// + /// Icon to use if set. + /// + [Parameter] + public Icon? Icon { get; set; } + + /// + /// The color of the icon. It supports the theme colors, default value uses the themes drawer icon color. + /// + [Parameter] + public Color IconColor { get; set; } = Color.Accent; + + [Parameter] + public NavLinkMatch Match { get; set; } = NavLinkMatch.Prefix; + + /// + /// The target attribute specifies where to open the link, if Href is specified. + /// Possible values: _blank | _self | _parent | _top. + /// + [Parameter] + public string? Target { get; set; } + + /// + /// Class names to use to indicate the item is active, separated by space. + /// + [Parameter] + public string ActiveClass { get; set; } = "active"; + + [Parameter] + public bool Disabled { get; set; } + + /// + /// If true, force browser to redirect outside component router-space. + /// + [Parameter] + public bool ForceLoad { get; set; } + + /// + /// Gets or sets the content to display + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [Parameter] + public EventCallback OnClick { get; set; } + + protected async Task OnClickHandler(MouseEventArgs ev) + { + if (Disabled) + return; + if (Href != null) + { + NavigationManager.NavigateTo(Href, ForceLoad); + } + else + { + await OnClick.InvokeAsync(ev); + } + } +} \ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavLink.razor.css b/src/Core/Components/NavMenu2/FluentNavLink.razor.css new file mode 100644 index 0000000000..c18c18e915 --- /dev/null +++ b/src/Core/Components/NavMenu2/FluentNavLink.razor.css @@ -0,0 +1,38 @@ +::deep .fluent-nav-link { + width: 100%; + font-weight: 400; + color: inherit; + line-height: 1.75; + display: inline-flex; + align-items: center; + justify-content: flex-start; + background-color: transparent; + /* margin-inline-start: calc(var(--design-unit) * 2px);*/ + /*margin-inline-end: calc(var(--design-unit) * 2px);*/ + text-decoration: none; +} + +::deep .fluent-nav-icon { + margin-inline-end: calc(var(--design-unit) * 2px + 2px); + min-width: 20px; +} + +::deep .fluent-nav-link.disabled { + color: var(--neutral-fill-secondary-rest) !important; + cursor: not-allowed ; + pointer-events: none; +} + + +::deep .fluent-nav-link.disabled .fluent-nav-icon { + fill: var(--neutral-stroke-rest) !important; +} + + +::deep .fluent-nav-link .fluent-nav-text { + width: 100%; + text-align: start; + + margin-inline-end: unset; + letter-spacing: 0; +} diff --git a/src/Core/Components/NavMenu2/FluentNavMenu2.razor b/src/Core/Components/NavMenu2/FluentNavMenu2.razor new file mode 100644 index 0000000000..2a1c21fa76 --- /dev/null +++ b/src/Core/Components/NavMenu2/FluentNavMenu2.razor @@ -0,0 +1,29 @@ +@namespace Microsoft.Fast.Components.FluentUI +@inherits FluentComponentBase + +
+ @if (Collapsible) + { +
+
+
+ @if (CollapserContent is not null) + { + + @CollapserContent + + } + else + { + + } +
+
+
+ } + @ChildContent +
\ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavMenu2.razor.cs b/src/Core/Components/NavMenu2/FluentNavMenu2.razor.cs new file mode 100644 index 0000000000..d254bafd77 --- /dev/null +++ b/src/Core/Components/NavMenu2/FluentNavMenu2.razor.cs @@ -0,0 +1,112 @@ +using System.ComponentModel; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.Fast.Components.FluentUI.Utilities; + +namespace Microsoft.Fast.Components.FluentUI; + +public partial class FluentNavMenu2 : FluentComponentBase +{ + private const string WIDTH_COLLAPSED_MENU = "40px"; + + internal string? ClassValue => new CssBuilder("fluent-nav-menu") + .AddClass(Class) + .Build(); + + internal string? StyleValue => new StyleBuilder(Style) + .AddStyle("margin", Margin, !string.IsNullOrEmpty(Margin)) + .AddStyle("width", $"{Width}px", () => !Collapsed && Width.HasValue) + .AddStyle("width", WIDTH_COLLAPSED_MENU, () => Collapsed) + .AddStyle("min-width", WIDTH_COLLAPSED_MENU, () => Collapsed) + .Build(); + + /// + /// Gets or sets the content to be rendered for the collapse icon + /// when the menu is collapsible. The default icon will be used if + /// this is not specified. + /// + [Parameter] + public RenderFragment? CollapserContent { get; set; } + + /// + /// Gets or sets the title of the navigation menu + /// Default to "Navigation menu" + /// + [Parameter] + public string? Title { get; set; } = "Navigation menu"; + + /// + /// Gets or sets the width of the menu (in pixels). + /// + [Parameter] + public int? Width { get; set; } + + /// + /// Gets or sets whether or not the menu can be collapsed. + /// + [Parameter] + public bool Collapsible { get; set; } + + /// + [Parameter] + public bool Collapsed { get; set; } = false; + + /// + /// Event callback for when the property changes. + /// + [Parameter] + public EventCallback CollapsedChanged { get; set; } + + /// + /// Adjust the vertical spacing between navlinks. + /// + [Parameter] + public string? Margin { get; set; } + + + [Parameter] + public RenderFragment? ChildContent { get; set; } + + + /// + /// Navigation manager + /// + [Inject] + protected NavigationManager NavigationManager { get; private set; } = null!; + + public FluentNavMenu2() + { + Id = Identifier.NewId(); + } + + private Task ToggleCollapsedAsync() => SetCollapsedAsync(!Collapsed); + + private async Task HandleExpandCollapseKeyDownAsync(KeyboardEventArgs args) + { + Task handler = args.Code switch + { + "Enter" => SetCollapsedAsync(true), + "ArrowRight" => SetCollapsedAsync(true), + "ArrowLeft" => SetCollapsedAsync(false), + _ => Task.CompletedTask + }; + await handler; + } + + private async Task SetCollapsedAsync(bool value) + { + if (value == Collapsed) + { + return; + } + + Collapsed = value; + if (CollapsedChanged.HasDelegate) + { + await CollapsedChanged.InvokeAsync(value); + } + + StateHasChanged(); + + } +} \ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavMenu2.razor.css b/src/Core/Components/NavMenu2/FluentNavMenu2.razor.css new file mode 100644 index 0000000000..a222a1fb51 --- /dev/null +++ b/src/Core/Components/NavMenu2/FluentNavMenu2.razor.css @@ -0,0 +1,158 @@ +/* + NavMenu +*/ +.fluent-nav-menu { + flex-direction: column; + align-items: stretch; + min-width: fit-content; + font-size: 0px; +} + +::deep .fluent-nav-item { + margin: calc(var(--design-unit) * 1px + 1px) 0; + -webkit-user-select: none; + user-select: none; +} + +::deep .fluent-nav-menu .fluent-nav-group { + margin: calc(var(--design-unit) * 1px + 1px) 0; +} + + + + /*::deep .fluent-nav-item a { + color: var(--neutral-foreground-rest); + }*/ + + + ::deep .fluent-nav-item:has(a:hover):not(.fluent-nav-link-disabled) { + cursor: pointer; + background: var(--neutral-fill-secondary-rest); + } + + + ::deep .fluent-nav-item:has(a.active) { + background: var(--neutral-fill-secondary-rest); + } + + + ::deep .fluent-nav-item:has(a.active)::before { + content: ""; + display: block; + position: absolute; + width: 3px; + height: calc(((var(--base-height-multiplier) + var(--density)) * var(--design-unit) / 2) * 1px); + background: var(--accent-fill-rest); + border-radius: calc(var(--control-corner-radius) * 1px); + margin: calc(var(--design-unit) * 2px) 2px; + z-index: 5; + } + +/* + NavMenu expander +*/ +::deep .navmenu-expander { + height: 32px; + background-color: var(--neutral-fill-stealth-rest); + margin: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 2px) 0 calc(var(--design-unit) * 2px); + border-radius: calc(var(--control-corner-radius) * 1px); + user-select: none; +} + + ::deep .navmenu-expander:hover { + background-color: var(--neutral-fill-secondary-rest); + } + + ::deep .navmenu-expander.selected::after { + display: none; + } + + + +/* + Child Elements (Groups and Items) +*/ + + + + +/* + Groups +*/ +::deep .fluent-nav-group .content-region { + -webkit-user-select: none; + user-select: none; + /*margin-inline-start: calc(var(--design-unit) * 2px);*/ + /*margin-inline-end: calc(var(--design-unit) * 2px);*/ +} + +::deep .content-region { + display: inline-flex; + align-items: center; + white-space: nowrap; + width: 100%; + height: calc((var(--base-height-multiplier) + var(--density)) * var(--design-unit) * 1px); + margin-inline-start: calc(var(--design-unit) * 2px); + font-family: var(--body-font); + font-size: var(--type-ramp-base-font-size); + line-height: var(--type-ramp-base-line-height); + font-weight: initial; + font-variation-settings: var(--type-ramp-base-font-variations); +} + +/* + Group items +*/ +::deep .navmenu-group .navmenu-child-element::part(content-region) { + padding-inline-start: calc(var(--design-unit) * 2px); +} + +/* Hide any items inside groups of a collapsed nav menu*/ +::deep .navmenu.collapsed > .navmenu-parent-element::part(items) { + display: none; +} + + +/* + Nav links +*/ +::deep .fluent-nav-menu .fluent-nav-menu-item .content-region { + margin-inline-start: calc(var(--design-unit) * 2px); +} + +/* + Nav items +*/ + +::deep .fluent-nav-item .positioning-region { + display: flex; + position: relative; + box-sizing: border-box; + background: var(--neutral-fill-stealth-rest); + border: calc(var(--stroke-width) * 1px) solid transparent; + border-radius: calc(var(--control-corner-radius) * 1px); + height: calc(((var(--base-height-multiplier) + var(--density)) * var(--design-unit) + 1) * 1px); +} + +::deep .fluent-nav-group { + margin-top: calc((var(--design-unit) * 2px) + 2px); +} + + +::deep .fluent-nav-group > .fluent-nav-item > .positioning-region { + padding-inline-start: 12px; +} + +::deep .fluent-nav-group * .fluent-nav-menu > .fluent-nav-item > .positioning-region { + padding-inline-start: 24px; +} + +::deep .fluent-nav-group * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item > .positioning-region { + padding-inline-start: 36px; +} + + +::deep .fluent-nav-group * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item > .positioning-region { + padding-inline-start: 48px; +} + From 716e0e0a82274313cbd50a1afec6a8cd2237d3e2 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 4 Oct 2023 00:19:47 +0200 Subject: [PATCH 02/13] NavMenu wip (almost done) --- .../Demo/Shared/Pages/Lab/IssueTester.razor | 29 -- .../NavMenu/Examples/NavMenuCollapsible.razor | 18 +- .../NavMenu/Examples/NavMenuDataBound.razor | 41 +- .../NavMenu/Examples/NavMenuDefault.razor | 95 ++--- .../Shared/Pages/NavMenu/NavMenuPage.razor | 44 ++- .../Examples/NavMenuTreeCollapsible.razor | 33 ++ .../Examples/NavMenuTreeCustomActions.razor} | 5 +- .../Examples/NavMenuTreeDataBound.razor | 56 +++ .../Examples/NavMenuTreeDefault.razor | 68 ++++ .../Pages/NavMenuTree/NavMenuTreePage.razor | 73 ++++ .../Demo/Shared/Shared/DemoMainLayout.razor | 2 +- examples/Demo/Shared/Shared/DemoNavMenu.razor | 106 +++++ .../Demo/Shared/Shared/DemoNavMenu.razor.css | 3 + .../Demo/Shared/Shared/DemoNavMenuTree.razor | 4 +- .../Demo/Shared/wwwroot/docs/UpgradeGuide.md | 27 +- examples/Demo/Shared/wwwroot/docs/WhatsNew.md | 5 +- .../FluentCollapsibleRegion.razor | 6 +- .../FluentCollapsibleRegion.razor.cs | 27 +- .../FluentCollapsibleRegion.razor.css | 30 +- .../FluentNavBase.cs} | 79 ++-- .../Components/NavMenu/FluentNavGroup.razor | 69 ++++ .../NavMenu/FluentNavGroup.razor.cs | 100 +++++ .../FluentNavGroup.razor.css | 31 +- .../Components/NavMenu/FluentNavLink.razor | 45 +++ .../Components/NavMenu/FluentNavLink.razor.cs | 33 ++ .../FluentNavLink.razor.css | 17 +- .../Components/NavMenu/FluentNavMenu.razor | 57 ++- .../Components/NavMenu/FluentNavMenu.razor.cs | 293 +------------- .../NavMenu/FluentNavMenu.razor.css | 144 ++++--- .../Components/NavMenu2/FluentNavGroup.razor | 61 --- .../NavMenu2/FluentNavGroup.razor.cs | 139 ------- .../Components/NavMenu2/FluentNavLink.razor | 47 --- .../Components/NavMenu2/FluentNavMenu2.razor | 29 -- .../NavMenu2/FluentNavMenu2.razor.cs | 112 ------ .../NavMenu2/FluentNavMenu2.razor.css | 158 -------- .../FluentNavMenuGroup.razor | 0 .../FluentNavMenuGroup.razor.cs | 0 .../FluentNavMenuItemBase.cs | 2 +- .../FluentNavMenuLink.razor | 0 .../FluentNavMenuLink.razor.cs | 0 .../NavMenuTree/FluentNavMenuTree.razor | 39 ++ .../NavMenuTree/FluentNavMenuTree.razor.cs | 372 ++++++++++++++++++ .../NavMenuTree/FluentNavMenuTree.razor.css | 94 +++++ .../INavMenuItemsOwner.cs | 0 44 files changed, 1396 insertions(+), 1197 deletions(-) create mode 100644 examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeCollapsible.razor rename examples/Demo/Shared/Pages/{NavMenu/Examples/NavMenuCustomActions.razor => NavMenuTree/Examples/NavMenuTreeCustomActions.razor} (96%) create mode 100644 examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDataBound.razor create mode 100644 examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDefault.razor create mode 100644 examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor create mode 100644 examples/Demo/Shared/Shared/DemoNavMenu.razor create mode 100644 examples/Demo/Shared/Shared/DemoNavMenu.razor.css rename src/Core/Components/{NavMenu2/FluentNavLink.razor.cs => NavMenu/FluentNavBase.cs} (68%) create mode 100644 src/Core/Components/NavMenu/FluentNavGroup.razor create mode 100644 src/Core/Components/NavMenu/FluentNavGroup.razor.cs rename src/Core/Components/{NavMenu2 => NavMenu}/FluentNavGroup.razor.css (67%) create mode 100644 src/Core/Components/NavMenu/FluentNavLink.razor create mode 100644 src/Core/Components/NavMenu/FluentNavLink.razor.cs rename src/Core/Components/{NavMenu2 => NavMenu}/FluentNavLink.razor.css (54%) delete mode 100644 src/Core/Components/NavMenu2/FluentNavGroup.razor delete mode 100644 src/Core/Components/NavMenu2/FluentNavGroup.razor.cs delete mode 100644 src/Core/Components/NavMenu2/FluentNavLink.razor delete mode 100644 src/Core/Components/NavMenu2/FluentNavMenu2.razor delete mode 100644 src/Core/Components/NavMenu2/FluentNavMenu2.razor.cs delete mode 100644 src/Core/Components/NavMenu2/FluentNavMenu2.razor.css rename src/Core/Components/{NavMenu => NavMenuTree}/FluentNavMenuGroup.razor (100%) rename src/Core/Components/{NavMenu => NavMenuTree}/FluentNavMenuGroup.razor.cs (100%) rename src/Core/Components/{NavMenu => NavMenuTree}/FluentNavMenuItemBase.cs (98%) rename src/Core/Components/{NavMenu => NavMenuTree}/FluentNavMenuLink.razor (100%) rename src/Core/Components/{NavMenu => NavMenuTree}/FluentNavMenuLink.razor.cs (100%) create mode 100644 src/Core/Components/NavMenuTree/FluentNavMenuTree.razor create mode 100644 src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.cs create mode 100644 src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.css rename src/Core/Components/{NavMenu => NavMenuTree}/INavMenuItemsOwner.cs (100%) diff --git a/examples/Demo/Shared/Pages/Lab/IssueTester.razor b/examples/Demo/Shared/Pages/Lab/IssueTester.razor index 358c06d209..ff45576dd1 100644 --- a/examples/Demo/Shared/Pages/Lab/IssueTester.razor +++ b/examples/Demo/Shared/Pages/Lab/IssueTester.razor @@ -1,31 +1,2 @@ @page "/IssueTester" -
- - Item 1 - Item 2 - - Item 3.1 - Item 3.2 - - Item 4 - Item 5 - - Item 6.1 - Item 6.2 - - Item 6.3.1 - Item 6.3.2 - - Item 6.3.3.1 - Item 6.3.3.2 - - - - -
- -@code -{ - bool Expanded = true; -} \ No newline at end of file diff --git a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuCollapsible.razor b/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuCollapsible.razor index 306e13565b..533d9e5514 100644 --- a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuCollapsible.razor +++ b/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuCollapsible.razor @@ -1,17 +1,15 @@ -@namespace FluentUI.Demo.Shared - -

Collapsible Navigation Example

+

Collapsible Navigation Example

- - - - - - - + Item 1 + Item 2 + + Item 3.1 + Item 3.2 + + Item 4
diff --git a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDataBound.razor b/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDataBound.razor index 66dca1cd85..076c6a1666 100644 --- a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDataBound.razor +++ b/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDataBound.razor @@ -1,20 +1,16 @@ -@namespace FluentUI.Demo.Shared - -@inject ILogger logger; - -

Navigation Examples

+

Navigation Examples

- - - - - - - - + + Item 1.1 + Item 1.2 + + + Item 2.1 + Item 2.2 + @@ -29,19 +25,7 @@ Item 2 -

Selected elements

- - Item 1.1 - - - Item 1.2 - - - Item 2.1 - - - Item 2.2 - +
@@ -50,11 +34,6 @@ bool MenuExpanded = true; bool Item1Expanded = true; bool Item2Expanded = true; - - bool Item1Point1Selected = false; - bool Item1Point2Selected = false; - bool Item2Point1Selected = false; - bool Item2Point2Selected = false; } diff --git a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor b/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor index ab90b10a44..ea7b67ad13 100644 --- a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor +++ b/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor @@ -1,71 +1,62 @@ -@namespace FluentUI.Demo.Shared - -@inject ILogger logger; - -

Navigation Examples

+

Navigation Examples

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ + Home + Item 2 + + Item 3.1 + Item 3.2 + + Item 4 + Item 5 + + Item 6.1 + Item 6.2 + + Item 6.3.1 + Item 6.3.2 + + Item 6.3.3.1 + Item 6.3.3.2 + + Item 6.3.3.3.1 + Item 6.3.3.3.2 + + + + + +
+ + @code + { + bool expanded = true; + } - - - - + Item 1 + Item 2 + Item 3 + Item 4 - - - - + Item 1 + Item 2 + Item 3 + Item 4
@code { - void OnClick(MouseEventArgs e) { - logger.LogInformation("Item Clicked"); - } - - protected override void OnAfterRender(bool firstRender) { - if (firstRender) - { - - } - base.OnAfterRender(firstRender); + DemoLogger.WriteLine("NavMenu item clicked"); } } diff --git a/examples/Demo/Shared/Pages/NavMenu/NavMenuPage.razor b/examples/Demo/Shared/Pages/NavMenu/NavMenuPage.razor index 91ccf6a257..369be16ca3 100644 --- a/examples/Demo/Shared/Pages/NavMenu/NavMenuPage.razor +++ b/examples/Demo/Shared/Pages/NavMenu/NavMenuPage.razor @@ -1,10 +1,35 @@ @page "/NavMenu" +@using FluentUI.Demo.Shared.Pages.NavMenu.Examples + +

NavMenu, NavGroup and NavLink

+ +
+ IMPORTANT +

+ With version 3.2 a new, much improved, set of components to build side-bar menus has been added. +

+

+ + If you DO NOT want to upgrade to these new menu components, you can continue to use the pre-version 3.2 components. The only thing + you need to do is to change the name of the FluentNavMenu component in your application to FluentNavMenuTree. + +

+

+ Everything works exactely as before by changing the name of this FluentNavMenu component in your application. (This probably needs to be done + in one place only). The FluentNavMenuGroup and FluentNavMenuLink components have not been changed and do not need to be altered. +

+

+ We consider the FluentNavMenuTree, FluentNavMenuGroup and FluentNavMenuLink components as deprecated and will remove them in a future version. +

+

+ If you wish to upgrade to the new menu components, please refer to the What's new section for more information. +

+
-

NavMenu, NavMenuGroup and NavMenuLink

- The FluentNavMenuGroup, FluentNavMenuLink and FluentNavMenu components can be used to build - hierarchical, collapsible and expandable menus. They can range from simple 1-level deep lists to complex multi-level menu - structures. + The FluentNavMenu, FluentNavGroup and FluentNavLink components can be used to build + hierarchical, collapsible and expandable side-bar menus. They can range from simple 1-level deep lists to complex multi-level menu + structures (with a max of 5 levels).

None of these components are particulary useful when used stand-alone. @@ -12,15 +37,15 @@ - + - +

Examples

- This demo shows 3 different versions of a NavMenu (with FluentNavMenuGoups and FluentNavMenuLinks). + This demo shows 3 different versions of a NavMenu (with FluentNavGroups and FluentNavLink)s. From left to right:
  • Menu with several sub-menus, icons, etc
  • @@ -42,8 +67,3 @@ - - - An example of intercepting menu actions to provide custom behavior. - - diff --git a/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeCollapsible.razor b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeCollapsible.razor new file mode 100644 index 0000000000..660d5f57ee --- /dev/null +++ b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeCollapsible.razor @@ -0,0 +1,33 @@ +

    Collapsible Navigation Example

    + + +
    + + + + + + + + + +
    +
    + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce quis lorem lacus. + Ut id leo non enim feugiat ultrices. Proin vulputate volutpat urna nec iaculis. + Integer dui lacus, fermentum sit amet aliquet in, scelerisque vitae dui. + Nulla fringilla sagittis orci eu consectetur. Fusce eget dolor non lectus placerat + tincidunt. Pellentesque aliquam non odio ac porttitor. Nam finibus mattis sagittis. + Ut hendrerit porttitor massa in aliquam. Duis laoreet fringilla feugiat. + Sed maximus, urna in fringilla posuere, enim urna bibendum justo, vel molestie nibh orci nec lectus. + Etiam a varius justo. Aenean nisl ante, interdum eget vulputate eget, iaculis ut massa. + Suspendisse maximus sed purus id molestie. Lorem ipsum dolor sit amet, consectetur adipiscing elit. + +
    +
    + +@code +{ + bool Expanded = true; +} \ No newline at end of file diff --git a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuCustomActions.razor b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeCustomActions.razor similarity index 96% rename from examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuCustomActions.razor rename to examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeCustomActions.razor index 1afe53dca6..ba11d8a38c 100644 --- a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuCustomActions.razor +++ b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeCustomActions.razor @@ -1,11 +1,10 @@ @using System.Text; -@namespace FluentUI.Demo.Shared

    Custom Actions Example

    - + @* This item is handled by the MenuLevelHandler, which intercepts all items but will only handle ones with Id ending with _HandleAtMenuLevel @@ -48,7 +47,7 @@ - +

    Log

    diff --git a/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDataBound.razor b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDataBound.razor new file mode 100644 index 0000000000..891f81ac0a --- /dev/null +++ b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDataBound.razor @@ -0,0 +1,56 @@ +

    Navigation Examples

    + + + + + + + + + + + + + + + +

    Expanded elements

    + + Menu + + + Item 1 + + + Item 2 + + +

    Selected elements

    + + Item 1.1 + + + Item 1.2 + + + Item 2.1 + + + Item 2.2 + + +
    +
    + +@code { + bool MenuExpanded = true; + bool Item1Expanded = true; + bool Item2Expanded = true; + + bool Item1Point1Selected = false; + bool Item1Point2Selected = false; + bool Item2Point1Selected = false; + bool Item2Point2Selected = false; +} + + diff --git a/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDefault.razor b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDefault.razor new file mode 100644 index 0000000000..9e54688dfe --- /dev/null +++ b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDefault.razor @@ -0,0 +1,68 @@ +

    Navigation Examples

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@code +{ + + void OnClick(MouseEventArgs e) + { + DemoLogger.WriteLine("NavMenuTree item clicked"); + } + + protected override void OnAfterRender(bool firstRender) { + if (firstRender) + { + + } + base.OnAfterRender(firstRender); + } +} + + diff --git a/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor b/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor new file mode 100644 index 0000000000..6441c3c0e2 --- /dev/null +++ b/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor @@ -0,0 +1,73 @@ +@page "/NavMenuTree" +@using FluentUI.Demo.Shared.Pages.NavMenuTree.Examples + +

    NavMenuTree, NavMenuGroup and NavMenuLink

    + +
    + IMPORTANT +

    + With version 3.2 a new, much improved, set of components to build side-bar menus has been added. +

    +

    + + If you DO NOT want to upgrade to these new menu components, you can continue to use the pre-version 3.2 components. The only thing + you need to do is to change the name of the FluentNavMenu component in your application to FluentNavMenuTree. + +

    +

    + Everything works exactely as before by changing the name of this FluentNavMenu component in your application. (This probably needs to be done + in one place only). The FluentNavMenuGroup and FluentNavMenuLink components have not been changed and do not need to be altered. +

    +

    + We consider the FluentNavMenuTree, FluentNavMenuGroup and FluentNavMenuLink components as deprecated and will remove them in a future version. +

    +

    + If you wish to upgrade to the new menu components, please refer to the What's new section for more information. +

    +
    +

    + The FluentNavMenuGroup, FluentNavMenuLink and FluentNavMenu components can be used to build + hierarchical, collapsible and expandable menus. They can range from simple 1-level deep lists to complex multi-level menu + structures. +

    +

    + None of these components are particulary useful when used stand-alone. +

    + + + + + + + +

    Examples

    + + + + This demo shows 3 different versions of a NavMenu (with FluentNavMenuGoups and FluentNavMenuLinks). + From left to right: +
      +
    • Menu with several sub-menus, icons, etc
    • +
    • Menu without sub-menus but with icons
    • +
    • Menu with just text links
    • +
    +
    +
    + + + + More complete example which show how menu works with together with a text section when it collapses. + + + + + + An example data binding the Expanded parameter. + + + + + + An example of intercepting menu actions to provide custom behavior. + + diff --git a/examples/Demo/Shared/Shared/DemoMainLayout.razor b/examples/Demo/Shared/Shared/DemoMainLayout.razor index 221bcb0a05..fbd73a253f 100644 --- a/examples/Demo/Shared/Shared/DemoMainLayout.razor +++ b/examples/Demo/Shared/Shared/DemoMainLayout.razor @@ -53,7 +53,7 @@
    diff --git a/examples/Demo/Shared/Shared/DemoNavMenu.razor b/examples/Demo/Shared/Shared/DemoNavMenu.razor new file mode 100644 index 0000000000..2a73f4e265 --- /dev/null +++ b/examples/Demo/Shared/Shared/DemoNavMenu.razor @@ -0,0 +1,106 @@ +
    + + +

    Home

    +
    + + + What's new + Upgrade guide + Project setup + Code setup + Design tokens + Reboot + Icons and Emoji + DialogService + MessageService + ToastService + Project templates + Fluent UI Form + Blazor Form + + + + FluentComponentBase + FluentInputBase + Clear cache + + + + Header + Footer + BodyContent + Grid + Layout + MainLayout + Spacer + Splitter + Stack + + + + Accordion + Anchor + Anchored Region + + Badge + CounterBadge + PresenceBadge + + Autocomplete + Breadcrumb + Button + Card + Checkbox + CodeEditor + Combobox + Data grid + Date & Time + Dialog + Divider + Drag and Drop + Emoji + Flipper + Highlighter + Horizontal Scroll + Icon + InputFile + Label + Listbox + Menu + MenuButton + MessageBar + MessageBox + NavMenu + NavMenuTree + Number Field + Option + Overflow + Overlay + Panel + Popover + Progress + Progress Ring + Radio + Radio Group + Search + Select + Skeleton + Slider + SplashScreen + Switch + Tabs + TextArea + Text Field + Toast + Toolbar + Tooltip + Tree View + + + + MarkdownSection + TableOfContents + +
    +
    \ No newline at end of file diff --git a/examples/Demo/Shared/Shared/DemoNavMenu.razor.css b/examples/Demo/Shared/Shared/DemoNavMenu.razor.css new file mode 100644 index 0000000000..7348dbe7be --- /dev/null +++ b/examples/Demo/Shared/Shared/DemoNavMenu.razor.css @@ -0,0 +1,3 @@ +::deep fluent-tree-item { + margin: 5px 0; +} diff --git a/examples/Demo/Shared/Shared/DemoNavMenuTree.razor b/examples/Demo/Shared/Shared/DemoNavMenuTree.razor index 67df067879..835d0a4e5b 100644 --- a/examples/Demo/Shared/Shared/DemoNavMenuTree.razor +++ b/examples/Demo/Shared/Shared/DemoNavMenuTree.razor @@ -1,5 +1,5 @@
    - +

    Home

    @@ -110,5 +110,5 @@ -
    +
    \ No newline at end of file diff --git a/examples/Demo/Shared/wwwroot/docs/UpgradeGuide.md b/examples/Demo/Shared/wwwroot/docs/UpgradeGuide.md index 740734e98d..a5fd404af8 100644 --- a/examples/Demo/Shared/wwwroot/docs/UpgradeGuide.md +++ b/examples/Demo/Shared/wwwroot/docs/UpgradeGuide.md @@ -1,10 +1,23 @@ -## Breaking changes - The `FluentDataGrid` component is, as you may know, a `QuickGrid` in disguise. We - aligned the underlying code even more to the productized version that will ship with - .NET 8. Where we previously aligned parameter names to the `fluent-datagrid` Web - Component, we will now align to the `QuickGrid` naming. This should make - integrating/copying `QuickGrid` component examples in your own environment easier and - will make it easier for us to keep the code up-to-date. Changes that need to be made in parameter names from v2 are: +## Breaking changes v3.2.0 + +The pre-v3.2 `FluentNavMenu` has been renamed to `FluentNavMenuTree`. If you want to upgrade your previous menu code, the following changes need to be made: + +* Change all occurrences of `` to `` +* Change `FluentNavMenuLink` from a self-closing tag to a tag with a closing tag +* Move the `FluentNavMenuLink` `Text` parameters contents to in between the opening and closing tag +* Change any `@onclick` occurences to `OnClick` + +* Change all occurences of `FluentNavMenuGroup` to `FluentNavGroup' +* Replace the `Text` parameter with `Title` + + +## Breaking changes v3.0.0 +The `FluentDataGrid` component is, as you may know, a `QuickGrid` in disguise. We +aligned the underlying code even more to the productized version that will ship with +.NET 8. Where we previously aligned parameter names to the `fluent-datagrid` Web +Component, we will now align to the `QuickGrid` naming. This should make +integrating/copying `QuickGrid` component examples in your own environment easier and +will make it easier for us to keep the code up-to-date. Changes that need to be made in parameter names from v2 are: * RowsData -> Items * RowsDataProvider -> ItemsProvider * RowsDataSize -> ItemSize diff --git a/examples/Demo/Shared/wwwroot/docs/WhatsNew.md b/examples/Demo/Shared/wwwroot/docs/WhatsNew.md index 6b84563f29..608416c326 100644 --- a/examples/Demo/Shared/wwwroot/docs/WhatsNew.md +++ b/examples/Demo/Shared/wwwroot/docs/WhatsNew.md @@ -1,4 +1,7 @@ -## V3.1.1 +## V3.2.0 +- New NavMenu, NavGroup and NavLink components. See [NavMenu](https://www.fluentui-blazor.net/NavMenu) page for examples. + +## V3.1.1 - Fix [#776](https://github.com/microsoft/fluentui-blazor/issues/776): Icon throws exception when deployed to Azure - Fix [#755](https://github.com/microsoft/fluentui-blazor/issues/755): Icon throws exception when deployed to Azure - Fix [#789](https://github.com/microsoft/fluentui-blazor/issues/789): Navigation to "/" crashes with FluentNavMenu diff --git a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor index bd8b6b45f8..66ab387d57 100644 --- a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor +++ b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor @@ -3,9 +3,5 @@ @using System.Globalization
    -
    -
    - @ChildContent -
    -
    + @ChildContent
    \ No newline at end of file diff --git a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs index 2ad407ee4c..faaa4ad07b 100644 --- a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs +++ b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs @@ -16,9 +16,9 @@ internal enum CollapseState } //internal double _height; - private bool _expanded, _isRendered, _updateHeight; - private ElementReference _wrapper; - internal CollapseState _state = CollapseState.Exited; + private bool _expanded, _isRendered; //, _updateHeight; + //private ElementReference _wrapper; + //internal CollapseState _state = CollapseState.Exited; protected string? StyleValue => new StyleBuilder() @@ -30,9 +30,9 @@ internal enum CollapseState protected string? ClassValue => new CssBuilder("fluent-collapsible-region-container") - .AddClass($"fluent-collapsible-region-entering", _state == CollapseState.Entering) - .AddClass($"fluent-collapsible-region-entered", _state == CollapseState.Entered) - .AddClass($"fluent-collapsible-region-exiting", _state == CollapseState.Exiting) + //.AddClass($"fluent-collapsible-region-entering", _state == CollapseState.Entering) + //.AddClass($"fluent-collapsible-region-entered", _state == CollapseState.Entered) + //.AddClass($"fluent-collapsible-region-exiting", _state == CollapseState.Exiting) .AddClass(Class) .Build(); @@ -51,13 +51,13 @@ public bool Expanded if (_isRendered) { - _state = _expanded ? CollapseState.Entering : CollapseState.Exiting; + //_state = _expanded ? CollapseState.Entering : CollapseState.Exiting; //_ = UpdateHeight(); - _updateHeight = true; + //_updateHeight = true; } else if (_expanded) { - _state = CollapseState.Entered; + //_state = CollapseState.Entered; } _ = ExpandedChanged.InvokeAsync(_expanded); @@ -100,19 +100,12 @@ public bool Expanded // } //} - //protected override async Task OnAfterRenderAsync(bool firstRender) + protected override void OnAfterRender(bool firstRender) { if (firstRender) { _isRendered = true; - //await UpdateHeight(); - } - else if (_updateHeight && _state is CollapseState.Entering or CollapseState.Exiting) - { - _updateHeight = false; - //await UpdateHeight(); - StateHasChanged(); } } } diff --git a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.css b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.css index 3b3f6944ac..2f5758665a 100644 --- a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.css +++ b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.css @@ -1,31 +1,3 @@ .fluent-collapsible-region-container { - height: 0; overflow: hidden; -} - - - - -.fluent-collapsible-region-entered { - overflow: initial; -} - - -.fluent-collapsible-region-exiting { -} - - .fluent-collapsible-region-exiting.fluent-navgroup-collapsible-region { - animation-duration: 300ms; - } - -.fluent-collapsible-region-hidden { - visibility: hidden; -} - -.fluent-collapsible-region-wrapper { - display: flex; -} - -.fluent-collapsible-region-wrapper-inner { - width: 100%; -} +} \ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavLink.razor.cs b/src/Core/Components/NavMenu/FluentNavBase.cs similarity index 68% rename from src/Core/Components/NavMenu2/FluentNavLink.razor.cs rename to src/Core/Components/NavMenu/FluentNavBase.cs index 025ad7fd83..625a31eacb 100644 --- a/src/Core/Components/NavMenu2/FluentNavLink.razor.cs +++ b/src/Core/Components/NavMenu/FluentNavBase.cs @@ -1,42 +1,35 @@ -using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Routing; using Microsoft.AspNetCore.Components.Web; +using Microsoft.Fast.Components.FluentUI; using Microsoft.Fast.Components.FluentUI.Utilities; - namespace Microsoft.Fast.Components.FluentUI; -#nullable enable -public partial class FluentNavLink +/// +/// Base class for and . +/// +public abstract class FluentNavBase : FluentComponentBase { - internal string? ClassValue => new CssBuilder("fluent-nav-item") - .AddClass(Class) - .Build(); - - internal string? LinkClassname => new CssBuilder("fluent-nav-link") - .AddClass($"disabled", Disabled) - .Build(); - - - internal Dictionary Attributes - { - get => Disabled ? new Dictionary() : new Dictionary - { - { "href", Href }, - { "target", Target }, - { "rel", !string.IsNullOrWhiteSpace(Target) ? "noopener noreferrer" : string.Empty } - }; - } - - [Inject] - private NavigationManager NavigationManager { get; set; } = default!; + /// + /// The text to display for the group. + /// + [Parameter] + public string? Title { get; set; } /// - /// URL for the link. + /// URL for the group. /// [Parameter] public string? Href { get; set; } + /// + /// The target attribute specifies where to open the group, if Href is specified. + /// Possible values: _blank | _self | _parent | _top. + /// + [Parameter] + public string? Target { get; set; } + /// /// Icon to use if set. /// @@ -49,15 +42,15 @@ public partial class FluentNavLink [Parameter] public Color IconColor { get; set; } = Color.Accent; - [Parameter] - public NavLinkMatch Match { get; set; } = NavLinkMatch.Prefix; - /// - /// The target attribute specifies where to open the link, if Href is specified. - /// Possible values: _blank | _self | _parent | _top. + /// If true, the button will be disabled. /// [Parameter] - public string? Target { get; set; } + public bool Disabled { get; set; } + + [Parameter] + public RenderFragment? ChildContent { get; set; } + /// /// Class names to use to indicate the item is active, separated by space. @@ -66,22 +59,27 @@ public partial class FluentNavLink public string ActiveClass { get; set; } = "active"; [Parameter] - public bool Disabled { get; set; } + public NavLinkMatch Match { get; set; } = NavLinkMatch.Prefix; + + [CascadingParameter(Name = "NavMenuExpanded")] + protected bool NavMenuExpanded { get; private set; } /// - /// If true, force browser to redirect outside component router-space. + /// Returns if the item has an set. /// + public bool HasIcon => Icon is not null; + [Parameter] - public bool ForceLoad { get; set; } + public EventCallback OnClick { get; set; } /// - /// Gets or sets the content to display + /// If true, force browser to redirect outside component router-space. /// [Parameter] - public RenderFragment? ChildContent { get; set; } + public bool ForceLoad { get; set; } - [Parameter] - public EventCallback OnClick { get; set; } + [Inject] + private NavigationManager NavigationManager { get; set; } = default!; protected async Task OnClickHandler(MouseEventArgs ev) { @@ -96,4 +94,5 @@ protected async Task OnClickHandler(MouseEventArgs ev) await OnClick.InvokeAsync(ev); } } -} \ No newline at end of file +} + diff --git a/src/Core/Components/NavMenu/FluentNavGroup.razor b/src/Core/Components/NavMenu/FluentNavGroup.razor new file mode 100644 index 0000000000..f579a24eb3 --- /dev/null +++ b/src/Core/Components/NavMenu/FluentNavGroup.razor @@ -0,0 +1,69 @@ +@namespace Microsoft.Fast.Components.FluentUI +@inherits FluentNavBase +@using Microsoft.AspNetCore.Components.Rendering +@using Microsoft.AspNetCore.Components.Routing + + +@if (NavMenuExpanded || HasIcon) +{ +
    +
    +
    + @if (!string.IsNullOrEmpty(Href)) + { + + @_renderContent + + @_renderButton + } + else + { + + } +
    +
    + + + @ChildContent + + +
    +} +@code { + private void RenderContent(RenderTreeBuilder __builder) + { + @if (Icon is not null) + { + + } + else + { + + } +
    + @Title +
    + } + + private void RenderButton(RenderTreeBuilder __builder) + { + @if (!HideExpander) + { + + + } + } +} \ No newline at end of file diff --git a/src/Core/Components/NavMenu/FluentNavGroup.razor.cs b/src/Core/Components/NavMenu/FluentNavGroup.razor.cs new file mode 100644 index 0000000000..2b1dc1199d --- /dev/null +++ b/src/Core/Components/NavMenu/FluentNavGroup.razor.cs @@ -0,0 +1,100 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.Fast.Components.FluentUI.Utilities; + +namespace Microsoft.Fast.Components.FluentUI; + +public partial class FluentNavGroup : FluentNavBase +{ + private readonly RenderFragment _renderContent; + private readonly RenderFragment _renderButton; + + protected string? ClassValue => + new CssBuilder("fluent-nav-group") + .AddClass("fluent-nav-item") + .AddClass("expanded", Expanded) + .AddClass("disabled", Disabled) + .AddClass(Class) + .Build(); + + protected string? ButtonClassValue => + new CssBuilder("expand-collapse-button") + .AddClass("rotate", Expanded) + .Build(); + + protected string? ExpandIconClassValue => + new CssBuilder("fluent-nav-expand-icon") + .Build(); + + internal Dictionary Attributes + { + get => Disabled ? new Dictionary() : new Dictionary + { + { "href", Href }, + { "target", Target }, + { "rel", !string.IsNullOrWhiteSpace(Target) ? "noopener noreferrer" : string.Empty } + }; + } + + /// + /// If true, expands the nav group, otherwise collapse it. + /// Two-way bindable + /// + [Parameter] + public bool Expanded { get; set; } + + /// + /// If true, hides expand button at the end of the NavGroup. + /// + [Parameter] + public bool HideExpander { get; set; } + + /// + /// Explicitly sets the height for the Collapse element to override the css default. + /// + [Parameter] + public string? MaxHeight { get; set; } + + /// + /// If set, overrides the default expand icon. + /// + [Parameter] + public Icon ExpandIcon { get; set; } = new CoreIcons.Regular.Size12.ChevronRight(); + + [Parameter] + public EventCallback ExpandedChanged { get; set; } + + public FluentNavGroup() + { + _renderContent = RenderContent; + _renderButton = RenderButton; + } + + private Task ToggleExpandedAsync() => SetExpandedAsync(!Expanded); + + private async Task HandleExpanderKeyDownAsync(KeyboardEventArgs args) + { + Task handler = args.Code switch + { + "Enter" => SetExpandedAsync(!Expanded), + "ArrowRight" => SetExpandedAsync(true), + "ArrowLeft" => SetExpandedAsync(false), + _ => Task.CompletedTask + }; + await handler; + } + + private async Task SetExpandedAsync(bool value) + { + if (value == Expanded) + { + return; + } + + Expanded = value; + if (ExpandedChanged.HasDelegate) + { + await ExpandedChanged.InvokeAsync(value); + } + } +} \ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavGroup.razor.css b/src/Core/Components/NavMenu/FluentNavGroup.razor.css similarity index 67% rename from src/Core/Components/NavMenu2/FluentNavGroup.razor.css rename to src/Core/Components/NavMenu/FluentNavGroup.razor.css index c1a7bfbb4f..0996372231 100644 --- a/src/Core/Components/NavMenu2/FluentNavGroup.razor.css +++ b/src/Core/Components/NavMenu/FluentNavGroup.razor.css @@ -2,50 +2,34 @@ width: 100%; font-weight: 500; color: inherit; - line-height: 1.75; - display: inline-flex; + display: flex; align-items: center; - justify-content: flex-start; - background-color: transparent; - /*margin-inline-start: calc(var(--design-unit) * 2px);*/ - /*margin-inline-end: calc(var(--design-unit) * 2px);*/ text-decoration: none; } ::deep .fluent-nav-icon { - margin-inline-end: calc(var(--design-unit) * 2px + 2px) + margin-inline-end: calc(var(--design-unit) * 2px + 2px); + min-width: 20px; } ::deep .fluent-nav-group.disabled { color: var(--neutral-fill-secondary-rest) !important; - cursor: not-allowed; pointer-events: none; } - ::deep .fluent-nav-group.disabled .fluent-nav-icon { fill: var(--neutral-stroke-rest) !important; } - ::deep .fluent-nav-group .fluent-nav-text { - font-weight: 500; - } -/* - Group expander -*/ +/* Group expand/collapse */ ::deep .expand-collapse-button { position: absolute; right: calc(var(--design-unit) * 2px); left: unset; - - - background: none; - border: none; border-radius: calc(var(--control-corner-radius) * 1px); width: calc((((var(--base-height-multiplier) / 2) * var(--design-unit)) + ((var(--design-unit) * var(--density)) / 2) + (var(--design-unit) * 2)) * 1px); height: calc((((var(--base-height-multiplier) / 2) * var(--design-unit)) + ((var(--design-unit) * var(--density)) / 2) + (var(--design-unit) * 2)) * 1px); - padding: 0px; display: flex; justify-content: center; align-items: center; @@ -56,22 +40,21 @@ position: absolute; left: calc(var(--design-unit) * 2px); right: unset; - margin-inline-start: calc(var(--expand-collapse-button-size) - (var(--design-unit) * 2px)); } ::deep .expand-collapse-button:hover { background: var(--tree-item-expand-collapse-hover) } -::deep svg { +::deep .expand-collapse-button svg { transition: transform 0.1s linear 0s; pointer-events: none; } -[dir="rtl"] ::deep svg { +[dir="rtl"] * ::deep .expand-collapse-button svg { transform: rotate(180deg); } -::deep .rotate svg { +::deep .rotate.expand-collapse-button svg { transform: rotate(90deg); } diff --git a/src/Core/Components/NavMenu/FluentNavLink.razor b/src/Core/Components/NavMenu/FluentNavLink.razor new file mode 100644 index 0000000000..49381059ba --- /dev/null +++ b/src/Core/Components/NavMenu/FluentNavLink.razor @@ -0,0 +1,45 @@ +@namespace Microsoft.Fast.Components.FluentUI +@inherits FluentNavBase +@using Microsoft.AspNetCore.Components.Rendering +@using Microsoft.AspNetCore.Components.Routing + +
    +
    +
    + @if (!OnClick.HasDelegate && !string.IsNullOrEmpty(Href)) + { + + @_renderContent + + } + else + { +
    + @_renderContent +
    + } +
    +
    +
    + +@code { + private void RenderContent(RenderTreeBuilder __builder) + { + @if (Icon is not null) + { + + } + else + { + + } +
    + @ChildContent +
    + + + } +} \ No newline at end of file diff --git a/src/Core/Components/NavMenu/FluentNavLink.razor.cs b/src/Core/Components/NavMenu/FluentNavLink.razor.cs new file mode 100644 index 0000000000..d75677678a --- /dev/null +++ b/src/Core/Components/NavMenu/FluentNavLink.razor.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.Fast.Components.FluentUI.Utilities; + +namespace Microsoft.Fast.Components.FluentUI; + +public partial class FluentNavLink : FluentNavBase +{ + private readonly RenderFragment _renderContent; + + internal string? ClassValue => new CssBuilder("fluent-nav-item") + .AddClass(Class) + .Build(); + + internal string? LinkClassValue => new CssBuilder("fluent-nav-link") + .AddClass($"disabled", Disabled) + .Build(); + + internal Dictionary Attributes + { + get => Disabled ? new Dictionary() : new Dictionary + { + { "href", Href }, + { "target", Target }, + { "rel", !string.IsNullOrWhiteSpace(Target) ? "noopener noreferrer" : string.Empty } + }; + } + + public FluentNavLink() + { + _renderContent = RenderContent; + } +} \ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavLink.razor.css b/src/Core/Components/NavMenu/FluentNavLink.razor.css similarity index 54% rename from src/Core/Components/NavMenu2/FluentNavLink.razor.css rename to src/Core/Components/NavMenu/FluentNavLink.razor.css index c18c18e915..973cfd6c97 100644 --- a/src/Core/Components/NavMenu2/FluentNavLink.razor.css +++ b/src/Core/Components/NavMenu/FluentNavLink.razor.css @@ -1,14 +1,8 @@ ::deep .fluent-nav-link { - width: 100%; font-weight: 400; color: inherit; - line-height: 1.75; - display: inline-flex; + display: flex; align-items: center; - justify-content: flex-start; - background-color: transparent; - /* margin-inline-start: calc(var(--design-unit) * 2px);*/ - /*margin-inline-end: calc(var(--design-unit) * 2px);*/ text-decoration: none; } @@ -27,12 +21,3 @@ ::deep .fluent-nav-link.disabled .fluent-nav-icon { fill: var(--neutral-stroke-rest) !important; } - - -::deep .fluent-nav-link .fluent-nav-text { - width: 100%; - text-align: start; - - margin-inline-end: unset; - letter-spacing: 0; -} diff --git a/src/Core/Components/NavMenu/FluentNavMenu.razor b/src/Core/Components/NavMenu/FluentNavMenu.razor index 2055c23304..e6fcba62ea 100644 --- a/src/Core/Components/NavMenu/FluentNavMenu.razor +++ b/src/Core/Components/NavMenu/FluentNavMenu.razor @@ -1,39 +1,32 @@ @namespace Microsoft.Fast.Components.FluentUI @inherits FluentComponentBase -
    - - - - - - @if (Collapsible) +
    + + @if (Collapsible) + { +
    +
    +
    + @if (ExpanderContent is not null) { - - @if (ExpanderContent is not null) - { -
    - @ExpanderContent -
    - } - else - { - - } -
    + + @ExpanderContent + + } + else + { + } - @ChildContent - - - - +
    +
    +
    + } + @ChildContent
    \ No newline at end of file diff --git a/src/Core/Components/NavMenu/FluentNavMenu.razor.cs b/src/Core/Components/NavMenu/FluentNavMenu.razor.cs index 40f39e5786..52f91537c9 100644 --- a/src/Core/Components/NavMenu/FluentNavMenu.razor.cs +++ b/src/Core/Components/NavMenu/FluentNavMenu.razor.cs @@ -1,42 +1,29 @@ using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Routing; using Microsoft.AspNetCore.Components.Web; using Microsoft.Fast.Components.FluentUI.Utilities; namespace Microsoft.Fast.Components.FluentUI; -public partial class FluentNavMenu : FluentComponentBase, INavMenuItemsOwner, IDisposable +public partial class FluentNavMenu : FluentComponentBase { private const string WIDTH_COLLAPSED_MENU = "40px"; - private bool _disposed; - private bool _hasChildIcons => ((INavMenuItemsOwner)this).HasChildIcons; - private readonly Dictionary _allItems = new(); - private readonly List _childItems = new(); - private readonly string _expandCollapseTreeItemId = Identifier.NewId(); - private FluentTreeItem? _currentlySelectedTreeItem; - private FluentTreeItem? _previousSuccessfullySelectedTreeItem; - protected string? ClassValue => new CssBuilder(Class) - .AddClass("navmenu") - .AddClass("collapsed", Collapsed) - .AddClass("navmenu-parent-element") + internal string? ClassValue => new CssBuilder("fluent-nav-menu") + .AddClass(Class) + .AddClass("collapsed", () => !Expanded) .Build(); - protected string? StyleValue => new StyleBuilder(Style) + internal string? StyleValue => new StyleBuilder(Style) + .AddStyle("margin", Margin, !string.IsNullOrEmpty(Margin)) .AddStyle("width", $"{Width}px", () => Expanded && Width.HasValue) + .AddStyle("width", "100%", () => Expanded && !Width.HasValue) .AddStyle("width", WIDTH_COLLAPSED_MENU, () => !Expanded) .AddStyle("min-width", WIDTH_COLLAPSED_MENU, () => !Expanded) .Build(); - - /// - /// Gets or sets the content to be rendered inside the component. - /// - [Parameter] - public RenderFragment? ChildContent { get; set; } - + /// - /// Gets or sets the content to be rendered for the expander icon - /// when the menu is collapsible. The default icon will be used if + /// Gets or sets the content to be rendered for the collapse icon + /// when the menu is collapsible. The default icon will be used if /// this is not specified. /// [Parameter] @@ -72,27 +59,16 @@ public partial class FluentNavMenu : FluentComponentBase, INavMenuItemsOwner, ID public EventCallback ExpandedChanged { get; set; } /// - /// Called when the user attempts to execute the default action of a menu item. + /// Adjust the vertical spacing between navlinks. /// [Parameter] - public EventCallback OnAction { get; set; } + public string? Margin { get; set; } - /// - /// If set to then the tree will - /// expand when it is created. - /// - [Parameter] - public bool InitiallyExpanded { get; set; } - /// - /// If true, the menu will re-navigate to the current page when the user clicks on the currently selected menu item. - /// [Parameter] - public bool ReNavigate { get; set; } = false; - - /// - public bool Collapsed => !Expanded; + public RenderFragment? ChildContent { get; set; } + /// /// Navigation manager /// @@ -104,139 +80,13 @@ public FluentNavMenu() Id = Identifier.NewId(); } - /// - IEnumerable INavMenuItemsOwner.GetChildItems() => _childItems; - - /// - void INavMenuItemsOwner.Register(FluentNavMenuItemBase child) - { - _childItems.Add(child); - StateHasChanged(); - } - - /// - void INavMenuItemsOwner.Unregister(FluentNavMenuItemBase child) - { - _childItems.Remove(child); - StateHasChanged(); - } - - void IDisposable.Dispose() - { - // Do not change this code. Put clean-up code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - internal void Register(FluentNavMenuItemBase item) - { - _allItems[item.Id!] = item; - StateHasChanged(); - } - - internal async Task MenuItemExpandedChangedAsync(INavMenuItemsOwner menuItem) - { - if (menuItem.Id == _expandCollapseTreeItemId) - { - return; - } - - if (menuItem.Expanded && !Expanded) - { - await SetExpandedAsync(value: true); - } - } - - internal void Unregister(FluentNavMenuItemBase item) - { - _allItems.Remove(item.Id!); - StateHasChanged(); - } - - protected override async Task OnInitializedAsync() - { - NavigationManager.LocationChanged += HandleNavigationManagerLocationChanged; - - if (InitiallyExpanded) - { - await SetExpandedAsync(true); - } - } - - protected override void OnAfterRender(bool firstRender) - { - if (firstRender) - { - SelectMenuItemForCurrentUrl(); - } - } - - private void SelectMenuItemForCurrentUrl() - { - HandleNavigationManagerLocationChanged(null, new LocationChangedEventArgs(NavigationManager.Uri, isNavigationIntercepted: false)); - } - - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - NavigationManager.LocationChanged -= HandleNavigationManagerLocationChanged; - _allItems.Clear(); - _childItems.Clear(); - } - - _disposed = true; - } - - private Task ToggleCollapsedAsync() => SetExpandedAsync(!Expanded); - - private async void HandleNavigationManagerLocationChanged(object? sender, LocationChangedEventArgs e) - { - FluentNavMenuItemBase? menuItem = null; - - string localPath = new Uri(NavigationManager.Uri).LocalPath; - if (string.IsNullOrEmpty(localPath)) - localPath = "/"; - - if (localPath == "/") - { - if (_allItems.Count > 0) menuItem = _allItems.Values.ElementAt(0); - } - else - { - // This will match the first item that has a Href that matches the current URL exactly - menuItem = _allItems.Values - .Where(x => !string.IsNullOrEmpty(x.Href)) - .FirstOrDefault(x => x.Href != "/" && localPath.Equals((x.Href!), StringComparison.InvariantCultureIgnoreCase)); - - // If not found, try to match the first item that has a Href (ending in a "/") that starts with the current URL - // URL: https://.../Panel/Panel2 starts with Href: https://.../Panel + "/" - // Extra "/" is needed to avoid matching https://.../Panels with https://.../Panel - if (menuItem is null) - { - menuItem = _allItems.Values - .Where(x => !string.IsNullOrEmpty(x.Href)) - .FirstOrDefault(x => x.Href != "/" && localPath.StartsWith((x.Href! + "/"), StringComparison.InvariantCultureIgnoreCase)); - } - } - if (menuItem is not null) - { - _currentlySelectedTreeItem = menuItem.TreeItem; - _previousSuccessfullySelectedTreeItem = menuItem.TreeItem; - await _currentlySelectedTreeItem.SetSelectedAsync(true); - } - } + private Task ToggleExpandedAsync() => SetExpandedAsync(!Expanded); private async Task HandleExpandCollapseKeyDownAsync(KeyboardEventArgs args) { Task handler = args.Code switch { - "Enter" => SetExpandedAsync(true), + "Enter" => SetExpandedAsync(value: !Expanded), "ArrowRight" => SetExpandedAsync(true), "ArrowLeft" => SetExpandedAsync(false), _ => Task.CompletedTask @@ -258,115 +108,6 @@ private async Task SetExpandedAsync(bool value) } StateHasChanged(); - } - - private async Task HandleCurrentSelectedChangedAsync(FluentTreeItem? treeItem) - { - // If an already activated menu item is clicked again, then it will - // match the previously selected one but have Selected == false. - // In this case, the user has indicated they wish to re-trigger - // its action. For the case of a simple navigation, this will be the same - // page and therefore do nothing. - // But for a nav menu with custom actions like showing a dialog etc, it will - // re-trigger and repeat that action. - - // itemWasClickedWhilstAlreadySelected will never be true as treeItem is null when the treeItem is clicked again - // left the code in for now but this should probably be removed - //bool itemWasClickedWhilstAlreadySelected = treeItem?.Selected == false && treeItem == _previousSuccessfullySelectedTreeItem; - //if (itemWasClickedWhilstAlreadySelected) - //{ - // await TryActivateMenuItemAsync(treeItem); - // return; - //} - - if (treeItem is null && _previousSuccessfullySelectedTreeItem is not null && ReNavigate) - { - await TryActivateMenuItemAsync(_previousSuccessfullySelectedTreeItem, true); - return; - } - // If the user has selected a different item, then it will not match the previously - // selected item, and it will have Selected == true. - // So try to activate the new one instead of the old one. - // If it succeeds then keep it selected, if it fails then revert to the last successfully selected - // tree item. This prevents the user from selecting an item with no Href or custom action. - if (treeItem?.Selected == true && _allItems.TryGetValue(treeItem.Id!, out FluentNavMenuItemBase? menuItem)) - { - bool activated = await TryActivateMenuItemAsync(treeItem); - if (activated) - { - _currentlySelectedTreeItem = treeItem; - _previousSuccessfullySelectedTreeItem = treeItem; - } - else - { - _currentlySelectedTreeItem = _previousSuccessfullySelectedTreeItem; - } - } - // At this point we have either - // 1) Succeeded, - // 2) Failed and reverted to a previously successful item without re-executing its action, - // 3) Failed and have no previously successful item to revert to. - // If we have no currently selected item then we fall back to selecting whichever matches the current - // URI. - if (_currentlySelectedTreeItem is null) - { - SelectMenuItemForCurrentUrl(); - } - - // Now we need to ensure the currently selected item has Selected=true, and - // the previous has Selected=false - if (_currentlySelectedTreeItem?.Selected == false) - { - await _currentlySelectedTreeItem.SetSelectedAsync(true); - } - - if (_currentlySelectedTreeItem?.Selected == true) - { - if (_previousSuccessfullySelectedTreeItem?.Selected == true && _previousSuccessfullySelectedTreeItem != _currentlySelectedTreeItem) - { - await _previousSuccessfullySelectedTreeItem.SetSelectedAsync(false); - } - _previousSuccessfullySelectedTreeItem = _currentlySelectedTreeItem; - } - - // If we still don't have a currently selected item, then make sure the invalid one - // the user tried to select is not selected. - if (treeItem?.Selected == true && treeItem != _currentlySelectedTreeItem) - { - await treeItem.SetSelectedAsync(false); - } - } - - private async ValueTask TryActivateMenuItemAsync(FluentTreeItem? treeItem, bool renavigate = false) - { - if (treeItem is null) - { - return false; - } - - if (!_allItems.TryGetValue(treeItem.Id!, out FluentNavMenuItemBase? menuItem)) - { - return false; - } - - NavMenuActionArgs? actionArgs = new NavMenuActionArgs(target: menuItem, renavigate: renavigate); - if (OnAction.HasDelegate) - { - await OnAction.InvokeAsync(actionArgs); - } - - if (!actionArgs.Handled) - { - await menuItem.ExecuteAsync(actionArgs); - } - - if (actionArgs.Handled) - { - await menuItem.SetSelectedAsync(true); - } - - return actionArgs.Handled; } - -} +} \ No newline at end of file diff --git a/src/Core/Components/NavMenu/FluentNavMenu.razor.css b/src/Core/Components/NavMenu/FluentNavMenu.razor.css index 23b0d4d066..44ff9dc1e0 100644 --- a/src/Core/Components/NavMenu/FluentNavMenu.razor.css +++ b/src/Core/Components/NavMenu/FluentNavMenu.razor.css @@ -1,94 +1,112 @@ -/* - NavMenu -*/ -.navmenu { - background-color: var(--neutral-layer-1); +/* NavMenu */ +.fluent-nav-menu { + flex-direction: column; + align-items: stretch; + min-width: fit-content; } -/* - NavMenu expander -*/ -::deep .navmenu-expander::part(positioning-region) { - background-color: var(--neutral-fill-stealth-rest); -} - -::deep .navmenu-expander::part(content-region) { - margin-inline-start: calc(var(--design-unit) * 2px); +::deep .fluent-nav-item { + margin: calc(var(--design-unit) * 1px + 1px) 0; -webkit-user-select: none; user-select: none; } -::deep .navmenu-expander::part(positioning-region):hover { - background-color: var(--neutral-fill-secondary-rest); -} - -::deep .navmenu-expander.selected::after { - display: none; + /* Hover and active highlighting */ + ::deep .fluent-nav-item .positioning-region:hover:not(:has(a.disabled)) { + cursor: pointer; + background: var(--neutral-fill-secondary-rest); + } + + ::deep .fluent-nav-item .positioning-region:has(a.active) { + background: var(--neutral-fill-secondary-rest); + } + + /* Active item indicator */ + ::deep .fluent-nav-item .positioning-region:has(a.active)::before { + content: ""; + display: block; + position: absolute; + left: 0px; + right: unset; + width: 3px; + height: calc(((var(--base-height-multiplier) + var(--density)) * var(--design-unit) / 2) * 1px); + background: var(--accent-fill-rest); + border-radius: calc(var(--control-corner-radius) * 1px); + margin: calc(var(--design-unit) * 2px) 2px; + z-index: 5; + } + +[dir='rtl'] * ::deep .fluent-nav-item .positioning-region:has(a.active)::before { + left: unset; + right: 0px; } - - -/* - Child Elements (Groups and Items) -*/ -::deep .navmenu-child-element::part(content-region) { - -webkit-user-select: none; - user-select: none; +::deep .content-region { + display: flex; + align-items: center; + white-space: nowrap; + width: 100%; margin-inline-start: calc(var(--design-unit) * 2px); - margin-inline-end: calc(var(--design-unit) * 2px); } -::deep .navmenu .treeitem-text, ::deep .navmenu fluent-tree-item.navmenu-child-element::part(start) { - pointer-events: none; +/* Nav items */ +::deep .fluent-nav-item .positioning-region { + display: flex; + position: relative; + box-sizing: border-box; + background: var(--neutral-fill-stealth-rest); + border: calc(var(--stroke-width) * 1px) solid transparent; + border-radius: calc(var(--control-corner-radius) * 1px); + height: calc(((var(--base-height-multiplier) + var(--density)) * var(--design-unit) + 1) * 1px); } -::deep .navmenu.collapsed .treeitem-text, ::deep .navmenu.collapsed fluent-tree-item.navmenu-child-element::part(expand-collapse-button) { - display: none; +/* NavGroup margins */ +::deep .fluent-nav-group { + margin: calc((var(--design-unit) * 2px) + 2px) 0; } +::deep .fluent-nav-menu .fluent-nav-group { + margin: calc((var(--design-unit) * 2px) + 2px) 0; +} - -/* - Groups -*/ -::deep .navmenu-group::part(content-region) { - margin-inline-end: var(--expand-collapse-button-size); +::deep .fluent-nav-group .fluent-nav-item:last-of-type, .fluent-nav-menu .fluent-nav-item:last-of-type { + margin-bottom: 0; } +/* level indenting */ +::deep .fluent-nav-group * .fluent-nav-menu > .fluent-nav-item > .positioning-region { + padding-inline-start: 24px; +} +::deep .fluent-nav-group * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item > .positioning-region { + padding-inline-start: 48px; +} -/* - Group expander -*/ -::deep .navmenu .navmenu-group::part(expand-collapse-button) { - right: calc(var(--design-unit) * 2px); - left: unset; - margin-inline-end: calc(var(--expand-collapse-button-size) * -1); +::deep .fluent-nav-group * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item > .positioning-region { + padding-inline-start: 72px; } -[dir="rtl"] * ::deep .navmenu-group::part(expand-collapse-button) { - left: calc(var(--design-unit) * 2px); - right: unset; - margin-inline-start: calc(var(--expand-collapse-button-size) - (var(--design-unit) * 2px)); +::deep .fluent-nav-group * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item > .positioning-region { + padding-inline-start: 96px; } +/* collapsed */ +::deep.collapsed .fluent-nav-text { + display: none; +} -/* - Group items -*/ -::deep .navmenu-group .navmenu-child-element::part(content-region) { - padding-inline-start: calc(var(--design-unit) * 2px); +::deep.collapsed .expand-collapse-button { + display: none; } -/* Hide any items inside groups of a collapsed nav menu*/ -::deep .navmenu.collapsed > .navmenu-parent-element::part(items) { +::deep.collapsed .fluent-nav-group * .fluent-nav-menu > .fluent-nav-item { display: none; } + ::deep.collapsed .fluent-nav-group * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item { + display: none; + } -/* - Nav links -*/ -::deep .navmenu .navmenu-link::part(content-region) { - margin-inline-start: calc(var(--design-unit) * 2px); -} \ No newline at end of file + ::deep.collapsed .fluent-nav-group * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item { + display: none; + } diff --git a/src/Core/Components/NavMenu2/FluentNavGroup.razor b/src/Core/Components/NavMenu2/FluentNavGroup.razor deleted file mode 100644 index 92a030756e..0000000000 --- a/src/Core/Components/NavMenu2/FluentNavGroup.razor +++ /dev/null @@ -1,61 +0,0 @@ -@namespace Microsoft.Fast.Components.FluentUI -@inherits FluentComponentBase -@using Microsoft.AspNetCore.Components.Rendering -@using Microsoft.AspNetCore.Components.Routing - -
    -
    -
    - @if (!string.IsNullOrEmpty(Href)) - { - - @if (Icon is not null) - { - - } - - - - @_renderButton - } - else - { - - } -
    -
    - - - @ChildContent - - -
    - -@code { - private void RenderButton(RenderTreeBuilder __builder) - { - @if (!HideExpandIcon) - { - - - } - } -} \ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavGroup.razor.cs b/src/Core/Components/NavMenu2/FluentNavGroup.razor.cs deleted file mode 100644 index 5e4269b6d4..0000000000 --- a/src/Core/Components/NavMenu2/FluentNavGroup.razor.cs +++ /dev/null @@ -1,139 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.Fast.Components.FluentUI.Utilities; - -namespace Microsoft.Fast.Components.FluentUI; - -#nullable enable -public partial class FluentNavGroup : FluentComponentBase -{ - private bool _expanded; - private readonly RenderFragment _renderButton; - - protected string? Classname => - new CssBuilder("fluent-nav-group") - .AddClass("fluent-nav-item") - .AddClass(Class) - .AddClass("expanded", Expanded) - .AddClass($"disabled", Disabled) - .Build(); - - protected string? ButtonClassname => - new CssBuilder("expand-collapse-button") - .AddClass("rotate", Expanded) - .Build(); - - protected string? IconClassname => - new CssBuilder("fluent-nav-icon") - .AddClass($"fluent-nav-icon-default", IconColor == Color.Accent) - .Build(); - - protected string? ExpandIconClassname => - new CssBuilder("fluent-nav-expand-icon") - .AddClass($"fluent-transform", Expanded && !Disabled) - .AddClass($"fluent-transform-disabled", Expanded && Disabled) - .Build(); - - internal Dictionary Attributes - { - get => Disabled ? new Dictionary() : new Dictionary - { - { "href", Href }, - { "target", Target }, - { "rel", !string.IsNullOrWhiteSpace(Target) ? "noopener noreferrer" : string.Empty } - }; - } - - [Parameter] - public string? Title { get; set; } - - /// - /// URL for the link. - /// - [Parameter] - public string? Href { get; set; } - - /// - /// The target attribute specifies where to open the link, if Href is specified. - /// Possible values: _blank | _self | _parent | _top. - /// - [Parameter] - public string? Target { get; set; } - - /// - /// Class names to use to indicate the item is active, separated by space. - /// - [Parameter] - public string ActiveClass { get; set; } = "active"; - - /// - /// Icon to use if set. - /// - [Parameter] - public Icon? Icon { get; set; } - - /// - /// The color of the icon. Default value uses the accent color. - /// - [Parameter] - public Color IconColor { get; set; } = Color.Accent; - - /// - /// If true, the button will be disabled. - /// - [Parameter] - public bool Disabled { get; set; } - - /// - /// If true, expands the nav group, otherwise collapse it. - /// Two-way bindable - /// - [Parameter] - public bool Expanded - { - get => _expanded; - set - { - if (_expanded == value) - return; - - _expanded = value; - ExpandedChanged.InvokeAsync(_expanded); - } - } - - /// - /// If true, hides expand-icon at the end of the NavGroup. - /// - [Parameter] - public bool HideExpandIcon { get; set; } - - /// - /// Explicitly sets the height for the Collapse element to override the css default. - /// - [Parameter] - public string? MaxHeight { get; set; } - - /// - /// If set, overrides the default expand icon. - /// - [Parameter] - public Icon ExpandIcon { get; set; } = new CoreIcons.Regular.Size12.ChevronDown(); - - [Parameter] - public RenderFragment? ChildContent { get; set; } - - [Parameter] - public EventCallback ExpandedChanged { get; set; } - - public FluentNavGroup() - { - _renderButton = RenderButton; - } - - protected Task ExpandedToggleAsync() - { - _expanded = !Expanded; - - return ExpandedChanged.InvokeAsync(_expanded); - } -} \ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavLink.razor b/src/Core/Components/NavMenu2/FluentNavLink.razor deleted file mode 100644 index 0603b0fba5..0000000000 --- a/src/Core/Components/NavMenu2/FluentNavLink.razor +++ /dev/null @@ -1,47 +0,0 @@ -@namespace Microsoft.Fast.Components.FluentUI -@inherits FluentComponentBase -@using Microsoft.AspNetCore.Components.Routing - -
    -
    -
    - @if (!OnClick.HasDelegate) - { - - @if (Icon is not null) - { - - } - else - { - - } -
    - @ChildContent -
    -
    - } - else - { -
    - @if (Icon is not null) - { - - } -else - { - - } -
    - @ChildContent -
    -
    - } -
    -
    -
    \ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavMenu2.razor b/src/Core/Components/NavMenu2/FluentNavMenu2.razor deleted file mode 100644 index 2a1c21fa76..0000000000 --- a/src/Core/Components/NavMenu2/FluentNavMenu2.razor +++ /dev/null @@ -1,29 +0,0 @@ -@namespace Microsoft.Fast.Components.FluentUI -@inherits FluentComponentBase - -
    - @if (Collapsible) - { -
    -
    -
    - @if (CollapserContent is not null) - { - - @CollapserContent - - } - else - { - - } -
    -
    -
    - } - @ChildContent -
    \ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavMenu2.razor.cs b/src/Core/Components/NavMenu2/FluentNavMenu2.razor.cs deleted file mode 100644 index d254bafd77..0000000000 --- a/src/Core/Components/NavMenu2/FluentNavMenu2.razor.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System.ComponentModel; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.Fast.Components.FluentUI.Utilities; - -namespace Microsoft.Fast.Components.FluentUI; - -public partial class FluentNavMenu2 : FluentComponentBase -{ - private const string WIDTH_COLLAPSED_MENU = "40px"; - - internal string? ClassValue => new CssBuilder("fluent-nav-menu") - .AddClass(Class) - .Build(); - - internal string? StyleValue => new StyleBuilder(Style) - .AddStyle("margin", Margin, !string.IsNullOrEmpty(Margin)) - .AddStyle("width", $"{Width}px", () => !Collapsed && Width.HasValue) - .AddStyle("width", WIDTH_COLLAPSED_MENU, () => Collapsed) - .AddStyle("min-width", WIDTH_COLLAPSED_MENU, () => Collapsed) - .Build(); - - /// - /// Gets or sets the content to be rendered for the collapse icon - /// when the menu is collapsible. The default icon will be used if - /// this is not specified. - /// - [Parameter] - public RenderFragment? CollapserContent { get; set; } - - /// - /// Gets or sets the title of the navigation menu - /// Default to "Navigation menu" - /// - [Parameter] - public string? Title { get; set; } = "Navigation menu"; - - /// - /// Gets or sets the width of the menu (in pixels). - /// - [Parameter] - public int? Width { get; set; } - - /// - /// Gets or sets whether or not the menu can be collapsed. - /// - [Parameter] - public bool Collapsible { get; set; } - - /// - [Parameter] - public bool Collapsed { get; set; } = false; - - /// - /// Event callback for when the property changes. - /// - [Parameter] - public EventCallback CollapsedChanged { get; set; } - - /// - /// Adjust the vertical spacing between navlinks. - /// - [Parameter] - public string? Margin { get; set; } - - - [Parameter] - public RenderFragment? ChildContent { get; set; } - - - /// - /// Navigation manager - /// - [Inject] - protected NavigationManager NavigationManager { get; private set; } = null!; - - public FluentNavMenu2() - { - Id = Identifier.NewId(); - } - - private Task ToggleCollapsedAsync() => SetCollapsedAsync(!Collapsed); - - private async Task HandleExpandCollapseKeyDownAsync(KeyboardEventArgs args) - { - Task handler = args.Code switch - { - "Enter" => SetCollapsedAsync(true), - "ArrowRight" => SetCollapsedAsync(true), - "ArrowLeft" => SetCollapsedAsync(false), - _ => Task.CompletedTask - }; - await handler; - } - - private async Task SetCollapsedAsync(bool value) - { - if (value == Collapsed) - { - return; - } - - Collapsed = value; - if (CollapsedChanged.HasDelegate) - { - await CollapsedChanged.InvokeAsync(value); - } - - StateHasChanged(); - - } -} \ No newline at end of file diff --git a/src/Core/Components/NavMenu2/FluentNavMenu2.razor.css b/src/Core/Components/NavMenu2/FluentNavMenu2.razor.css deleted file mode 100644 index a222a1fb51..0000000000 --- a/src/Core/Components/NavMenu2/FluentNavMenu2.razor.css +++ /dev/null @@ -1,158 +0,0 @@ -/* - NavMenu -*/ -.fluent-nav-menu { - flex-direction: column; - align-items: stretch; - min-width: fit-content; - font-size: 0px; -} - -::deep .fluent-nav-item { - margin: calc(var(--design-unit) * 1px + 1px) 0; - -webkit-user-select: none; - user-select: none; -} - -::deep .fluent-nav-menu .fluent-nav-group { - margin: calc(var(--design-unit) * 1px + 1px) 0; -} - - - - /*::deep .fluent-nav-item a { - color: var(--neutral-foreground-rest); - }*/ - - - ::deep .fluent-nav-item:has(a:hover):not(.fluent-nav-link-disabled) { - cursor: pointer; - background: var(--neutral-fill-secondary-rest); - } - - - ::deep .fluent-nav-item:has(a.active) { - background: var(--neutral-fill-secondary-rest); - } - - - ::deep .fluent-nav-item:has(a.active)::before { - content: ""; - display: block; - position: absolute; - width: 3px; - height: calc(((var(--base-height-multiplier) + var(--density)) * var(--design-unit) / 2) * 1px); - background: var(--accent-fill-rest); - border-radius: calc(var(--control-corner-radius) * 1px); - margin: calc(var(--design-unit) * 2px) 2px; - z-index: 5; - } - -/* - NavMenu expander -*/ -::deep .navmenu-expander { - height: 32px; - background-color: var(--neutral-fill-stealth-rest); - margin: calc(var(--design-unit) * 2px) calc(var(--design-unit) * 2px) 0 calc(var(--design-unit) * 2px); - border-radius: calc(var(--control-corner-radius) * 1px); - user-select: none; -} - - ::deep .navmenu-expander:hover { - background-color: var(--neutral-fill-secondary-rest); - } - - ::deep .navmenu-expander.selected::after { - display: none; - } - - - -/* - Child Elements (Groups and Items) -*/ - - - - -/* - Groups -*/ -::deep .fluent-nav-group .content-region { - -webkit-user-select: none; - user-select: none; - /*margin-inline-start: calc(var(--design-unit) * 2px);*/ - /*margin-inline-end: calc(var(--design-unit) * 2px);*/ -} - -::deep .content-region { - display: inline-flex; - align-items: center; - white-space: nowrap; - width: 100%; - height: calc((var(--base-height-multiplier) + var(--density)) * var(--design-unit) * 1px); - margin-inline-start: calc(var(--design-unit) * 2px); - font-family: var(--body-font); - font-size: var(--type-ramp-base-font-size); - line-height: var(--type-ramp-base-line-height); - font-weight: initial; - font-variation-settings: var(--type-ramp-base-font-variations); -} - -/* - Group items -*/ -::deep .navmenu-group .navmenu-child-element::part(content-region) { - padding-inline-start: calc(var(--design-unit) * 2px); -} - -/* Hide any items inside groups of a collapsed nav menu*/ -::deep .navmenu.collapsed > .navmenu-parent-element::part(items) { - display: none; -} - - -/* - Nav links -*/ -::deep .fluent-nav-menu .fluent-nav-menu-item .content-region { - margin-inline-start: calc(var(--design-unit) * 2px); -} - -/* - Nav items -*/ - -::deep .fluent-nav-item .positioning-region { - display: flex; - position: relative; - box-sizing: border-box; - background: var(--neutral-fill-stealth-rest); - border: calc(var(--stroke-width) * 1px) solid transparent; - border-radius: calc(var(--control-corner-radius) * 1px); - height: calc(((var(--base-height-multiplier) + var(--density)) * var(--design-unit) + 1) * 1px); -} - -::deep .fluent-nav-group { - margin-top: calc((var(--design-unit) * 2px) + 2px); -} - - -::deep .fluent-nav-group > .fluent-nav-item > .positioning-region { - padding-inline-start: 12px; -} - -::deep .fluent-nav-group * .fluent-nav-menu > .fluent-nav-item > .positioning-region { - padding-inline-start: 24px; -} - -::deep .fluent-nav-group * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item > .positioning-region { - padding-inline-start: 36px; -} - - -::deep .fluent-nav-group * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item * .fluent-nav-menu > .fluent-nav-item > .positioning-region { - padding-inline-start: 48px; -} - diff --git a/src/Core/Components/NavMenu/FluentNavMenuGroup.razor b/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor similarity index 100% rename from src/Core/Components/NavMenu/FluentNavMenuGroup.razor rename to src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor diff --git a/src/Core/Components/NavMenu/FluentNavMenuGroup.razor.cs b/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor.cs similarity index 100% rename from src/Core/Components/NavMenu/FluentNavMenuGroup.razor.cs rename to src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor.cs diff --git a/src/Core/Components/NavMenu/FluentNavMenuItemBase.cs b/src/Core/Components/NavMenuTree/FluentNavMenuItemBase.cs similarity index 98% rename from src/Core/Components/NavMenu/FluentNavMenuItemBase.cs rename to src/Core/Components/NavMenuTree/FluentNavMenuItemBase.cs index 480fc803fa..60b361cb43 100644 --- a/src/Core/Components/NavMenu/FluentNavMenuItemBase.cs +++ b/src/Core/Components/NavMenuTree/FluentNavMenuItemBase.cs @@ -67,7 +67,7 @@ public abstract class FluentNavMenuItemBase : FluentComponentBase, IDisposable public int? Width { get; set; } [CascadingParameter] - protected FluentNavMenu NavMenu { get; private set; } = default!; + protected FluentNavMenuTree NavMenu { get; private set; } = default!; [CascadingParameter(Name = "NavMenuExpanded")] protected bool NavMenuExpanded { get; private set; } diff --git a/src/Core/Components/NavMenu/FluentNavMenuLink.razor b/src/Core/Components/NavMenuTree/FluentNavMenuLink.razor similarity index 100% rename from src/Core/Components/NavMenu/FluentNavMenuLink.razor rename to src/Core/Components/NavMenuTree/FluentNavMenuLink.razor diff --git a/src/Core/Components/NavMenu/FluentNavMenuLink.razor.cs b/src/Core/Components/NavMenuTree/FluentNavMenuLink.razor.cs similarity index 100% rename from src/Core/Components/NavMenu/FluentNavMenuLink.razor.cs rename to src/Core/Components/NavMenuTree/FluentNavMenuLink.razor.cs diff --git a/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor b/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor new file mode 100644 index 0000000000..2055c23304 --- /dev/null +++ b/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor @@ -0,0 +1,39 @@ +@namespace Microsoft.Fast.Components.FluentUI +@inherits FluentComponentBase + +
    + + + + + + @if (Collapsible) + { + + @if (ExpanderContent is not null) + { +
    + @ExpanderContent +
    + } + else + { + + } +
    + } + @ChildContent +
    +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.cs b/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.cs new file mode 100644 index 0000000000..6399133fee --- /dev/null +++ b/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.cs @@ -0,0 +1,372 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.Fast.Components.FluentUI.Utilities; + +namespace Microsoft.Fast.Components.FluentUI; + +public partial class FluentNavMenuTree : FluentComponentBase, INavMenuItemsOwner, IDisposable +{ + private const string WIDTH_COLLAPSED_MENU = "40px"; + private bool _disposed; + private bool _hasChildIcons => ((INavMenuItemsOwner)this).HasChildIcons; + private readonly Dictionary _allItems = new(); + private readonly List _childItems = new(); + private readonly string _expandCollapseTreeItemId = Identifier.NewId(); + private FluentTreeItem? _currentlySelectedTreeItem; + private FluentTreeItem? _previousSuccessfullySelectedTreeItem; + + protected string? ClassValue => new CssBuilder(Class) + .AddClass("navmenu") + .AddClass("collapsed", Collapsed) + .AddClass("navmenu-parent-element") + .Build(); + + protected string? StyleValue => new StyleBuilder(Style) + .AddStyle("width", $"{Width}px", () => Expanded && Width.HasValue) + .AddStyle("width", WIDTH_COLLAPSED_MENU, () => !Expanded) + .AddStyle("min-width", WIDTH_COLLAPSED_MENU, () => !Expanded) + .Build(); + + /// + /// Gets or sets the content to be rendered inside the component. + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + /// + /// Gets or sets the content to be rendered for the expander icon + /// when the menu is collapsible. The default icon will be used if + /// this is not specified. + /// + [Parameter] + public RenderFragment? ExpanderContent { get; set; } + + /// + /// Gets or sets the title of the navigation menu + /// Default to "Navigation menu" + /// + [Parameter] + public string? Title { get; set; } = "Navigation menu"; + + /// + /// Gets or sets the width of the menu (in pixels). + /// + [Parameter] + public int? Width { get; set; } + + /// + /// Gets or sets whether or not the menu can be collapsed. + /// + [Parameter] + public bool Collapsible { get; set; } + + /// + [Parameter] + public bool Expanded { get; set; } = true; + + /// + /// Event callback for when the property changes. + /// + [Parameter] + public EventCallback ExpandedChanged { get; set; } + + /// + /// Called when the user attempts to execute the default action of a menu item. + /// + [Parameter] + public EventCallback OnAction { get; set; } + + /// + /// If set to then the tree will + /// expand when it is created. + /// + [Parameter] + public bool InitiallyExpanded { get; set; } + + /// + /// If true, the menu will re-navigate to the current page when the user clicks on the currently selected menu item. + /// + [Parameter] + public bool ReNavigate { get; set; } = false; + + /// + public bool Collapsed => !Expanded; + + /// + /// Navigation manager + /// + [Inject] + protected NavigationManager NavigationManager { get; private set; } = null!; + + public FluentNavMenuTree() + { + Id = Identifier.NewId(); + } + + /// + IEnumerable INavMenuItemsOwner.GetChildItems() => _childItems; + + /// + void INavMenuItemsOwner.Register(FluentNavMenuItemBase child) + { + _childItems.Add(child); + StateHasChanged(); + } + + /// + void INavMenuItemsOwner.Unregister(FluentNavMenuItemBase child) + { + _childItems.Remove(child); + StateHasChanged(); + } + + void IDisposable.Dispose() + { + // Do not change this code. Put clean-up code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + internal void Register(FluentNavMenuItemBase item) + { + _allItems[item.Id!] = item; + StateHasChanged(); + } + + internal async Task MenuItemExpandedChangedAsync(INavMenuItemsOwner menuItem) + { + if (menuItem.Id == _expandCollapseTreeItemId) + { + return; + } + + if (menuItem.Expanded && !Expanded) + { + await SetExpandedAsync(value: true); + } + } + + internal void Unregister(FluentNavMenuItemBase item) + { + _allItems.Remove(item.Id!); + StateHasChanged(); + } + + protected override async Task OnInitializedAsync() + { + NavigationManager.LocationChanged += HandleNavigationManagerLocationChanged; + + if (InitiallyExpanded) + { + await SetExpandedAsync(true); + } + } + + protected override void OnAfterRender(bool firstRender) + { + if (firstRender) + { + SelectMenuItemForCurrentUrl(); + } + } + + private void SelectMenuItemForCurrentUrl() + { + HandleNavigationManagerLocationChanged(null, new LocationChangedEventArgs(NavigationManager.Uri, isNavigationIntercepted: false)); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + NavigationManager.LocationChanged -= HandleNavigationManagerLocationChanged; + _allItems.Clear(); + _childItems.Clear(); + } + + _disposed = true; + } + + private Task ToggleCollapsedAsync() => SetExpandedAsync(!Expanded); + + private async void HandleNavigationManagerLocationChanged(object? sender, LocationChangedEventArgs e) + { + FluentNavMenuItemBase? menuItem = null; + + string localPath = new Uri(NavigationManager.Uri).LocalPath; + if (string.IsNullOrEmpty(localPath)) + localPath = "/"; + + if (localPath == "/") + { + if (_allItems.Count > 0) menuItem = _allItems.Values.ElementAt(0); + } + else + { + // This will match the first item that has a Href that matches the current URL exactly + menuItem = _allItems.Values + .Where(x => !string.IsNullOrEmpty(x.Href)) + .FirstOrDefault(x => x.Href != "/" && localPath.Equals((x.Href!), StringComparison.InvariantCultureIgnoreCase)); + + // If not found, try to match the first item that has a Href (ending in a "/") that starts with the current URL + // URL: https://.../Panel/Panel2 starts with Href: https://.../Panel + "/" + // Extra "/" is needed to avoid matching https://.../Panels with https://.../Panel + if (menuItem is null) + { + menuItem = _allItems.Values + .Where(x => !string.IsNullOrEmpty(x.Href)) + .FirstOrDefault(x => x.Href != "/" && localPath.StartsWith((x.Href! + "/"), StringComparison.InvariantCultureIgnoreCase)); + } + } + if (menuItem is not null) + { + _currentlySelectedTreeItem = menuItem.TreeItem; + _previousSuccessfullySelectedTreeItem = menuItem.TreeItem; + await _currentlySelectedTreeItem.SetSelectedAsync(true); + } + } + + private async Task HandleExpandCollapseKeyDownAsync(KeyboardEventArgs args) + { + Task handler = args.Code switch + { + "Enter" => SetExpandedAsync(true), + "ArrowRight" => SetExpandedAsync(true), + "ArrowLeft" => SetExpandedAsync(false), + _ => Task.CompletedTask + }; + await handler; + } + + private async Task SetExpandedAsync(bool value) + { + if (value == Expanded) + { + return; + } + + Expanded = value; + if (ExpandedChanged.HasDelegate) + { + await ExpandedChanged.InvokeAsync(value); + } + + StateHasChanged(); + } + + private async Task HandleCurrentSelectedChangedAsync(FluentTreeItem? treeItem) + { + // If an already activated menu item is clicked again, then it will + // match the previously selected one but have Selected == false. + // In this case, the user has indicated they wish to re-trigger + // its action. For the case of a simple navigation, this will be the same + // page and therefore do nothing. + // But for a nav menu with custom actions like showing a dialog etc, it will + // re-trigger and repeat that action. + + // itemWasClickedWhilstAlreadySelected will never be true as treeItem is null when the treeItem is clicked again + // left the code in for now but this should probably be removed + //bool itemWasClickedWhilstAlreadySelected = treeItem?.Selected == false && treeItem == _previousSuccessfullySelectedTreeItem; + //if (itemWasClickedWhilstAlreadySelected) + //{ + // await TryActivateMenuItemAsync(treeItem); + // return; + //} + + if (treeItem is null && _previousSuccessfullySelectedTreeItem is not null && ReNavigate) + { + await TryActivateMenuItemAsync(_previousSuccessfullySelectedTreeItem, true); + return; + } + // If the user has selected a different item, then it will not match the previously + // selected item, and it will have Selected == true. + // So try to activate the new one instead of the old one. + // If it succeeds then keep it selected, if it fails then revert to the last successfully selected + // tree item. This prevents the user from selecting an item with no Href or custom action. + if (treeItem?.Selected == true && _allItems.TryGetValue(treeItem.Id!, out FluentNavMenuItemBase? menuItem)) + { + bool activated = await TryActivateMenuItemAsync(treeItem); + if (activated) + { + _currentlySelectedTreeItem = treeItem; + _previousSuccessfullySelectedTreeItem = treeItem; + } + else + { + _currentlySelectedTreeItem = _previousSuccessfullySelectedTreeItem; + } + } + + // At this point we have either + // 1) Succeeded, + // 2) Failed and reverted to a previously successful item without re-executing its action, + // 3) Failed and have no previously successful item to revert to. + // If we have no currently selected item then we fall back to selecting whichever matches the current + // URI. + if (_currentlySelectedTreeItem is null) + { + SelectMenuItemForCurrentUrl(); + } + + // Now we need to ensure the currently selected item has Selected=true, and + // the previous has Selected=false + if (_currentlySelectedTreeItem?.Selected == false) + { + await _currentlySelectedTreeItem.SetSelectedAsync(true); + } + + if (_currentlySelectedTreeItem?.Selected == true) + { + if (_previousSuccessfullySelectedTreeItem?.Selected == true && _previousSuccessfullySelectedTreeItem != _currentlySelectedTreeItem) + { + await _previousSuccessfullySelectedTreeItem.SetSelectedAsync(false); + } + _previousSuccessfullySelectedTreeItem = _currentlySelectedTreeItem; + } + + // If we still don't have a currently selected item, then make sure the invalid one + // the user tried to select is not selected. + if (treeItem?.Selected == true && treeItem != _currentlySelectedTreeItem) + { + await treeItem.SetSelectedAsync(false); + } + } + + private async ValueTask TryActivateMenuItemAsync(FluentTreeItem? treeItem, bool renavigate = false) + { + if (treeItem is null) + { + return false; + } + + if (!_allItems.TryGetValue(treeItem.Id!, out FluentNavMenuItemBase? menuItem)) + { + return false; + } + + NavMenuActionArgs? actionArgs = new NavMenuActionArgs(target: menuItem, renavigate: renavigate); + if (OnAction.HasDelegate) + { + await OnAction.InvokeAsync(actionArgs); + } + + if (!actionArgs.Handled) + { + await menuItem.ExecuteAsync(actionArgs); + } + + if (actionArgs.Handled) + { + await menuItem.SetSelectedAsync(true); + } + + return actionArgs.Handled; + } + +} diff --git a/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.css b/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.css new file mode 100644 index 0000000000..23b0d4d066 --- /dev/null +++ b/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.css @@ -0,0 +1,94 @@ +/* + NavMenu +*/ +.navmenu { + background-color: var(--neutral-layer-1); +} + +/* + NavMenu expander +*/ +::deep .navmenu-expander::part(positioning-region) { + background-color: var(--neutral-fill-stealth-rest); +} + +::deep .navmenu-expander::part(content-region) { + margin-inline-start: calc(var(--design-unit) * 2px); + -webkit-user-select: none; + user-select: none; +} + +::deep .navmenu-expander::part(positioning-region):hover { + background-color: var(--neutral-fill-secondary-rest); +} + +::deep .navmenu-expander.selected::after { + display: none; +} + + + +/* + Child Elements (Groups and Items) +*/ +::deep .navmenu-child-element::part(content-region) { + -webkit-user-select: none; + user-select: none; + margin-inline-start: calc(var(--design-unit) * 2px); + margin-inline-end: calc(var(--design-unit) * 2px); +} + +::deep .navmenu .treeitem-text, ::deep .navmenu fluent-tree-item.navmenu-child-element::part(start) { + pointer-events: none; +} + +::deep .navmenu.collapsed .treeitem-text, ::deep .navmenu.collapsed fluent-tree-item.navmenu-child-element::part(expand-collapse-button) { + display: none; +} + + + +/* + Groups +*/ +::deep .navmenu-group::part(content-region) { + margin-inline-end: var(--expand-collapse-button-size); +} + + + +/* + Group expander +*/ +::deep .navmenu .navmenu-group::part(expand-collapse-button) { + right: calc(var(--design-unit) * 2px); + left: unset; + margin-inline-end: calc(var(--expand-collapse-button-size) * -1); +} + +[dir="rtl"] * ::deep .navmenu-group::part(expand-collapse-button) { + left: calc(var(--design-unit) * 2px); + right: unset; + margin-inline-start: calc(var(--expand-collapse-button-size) - (var(--design-unit) * 2px)); +} + + +/* + Group items +*/ +::deep .navmenu-group .navmenu-child-element::part(content-region) { + padding-inline-start: calc(var(--design-unit) * 2px); +} + +/* Hide any items inside groups of a collapsed nav menu*/ +::deep .navmenu.collapsed > .navmenu-parent-element::part(items) { + display: none; +} + + +/* + Nav links +*/ +::deep .navmenu .navmenu-link::part(content-region) { + margin-inline-start: calc(var(--design-unit) * 2px); +} \ No newline at end of file diff --git a/src/Core/Components/NavMenu/INavMenuItemsOwner.cs b/src/Core/Components/NavMenuTree/INavMenuItemsOwner.cs similarity index 100% rename from src/Core/Components/NavMenu/INavMenuItemsOwner.cs rename to src/Core/Components/NavMenuTree/INavMenuItemsOwner.cs From 31054b4c9821a40a3a5ee2f46054b81ff4b4406f Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 4 Oct 2023 09:19:22 +0200 Subject: [PATCH 03/13] Update documentation --- Directory.Build.props | 4 ++-- README.md | 7 +++---- WHATSNEW.md | 5 ++++- .../Demo/Shared/Pages/Lab/IssueTester.razor | 3 +-- .../Examples/NavMenuTreeDefault.razor | 2 +- examples/Demo/Shared/Shared/DemoNavMenu.razor | 2 +- .../Demo/Shared/Shared/DemoNavMenuList.razor | 4 ++-- .../Demo/Shared/Shared/DemoNavMenuTree.razor | 2 +- .../Demo/Shared/wwwroot/docs/IconsAndEmoji.md | 20 ++++++++++++++----- .../Demo/Shared/wwwroot/docs/UpgradeGuide.md | 14 +++++++++---- examples/Demo/Shared/wwwroot/docs/WhatsNew.md | 2 +- 11 files changed, 41 insertions(+), 24 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 7a784a92c0..37c032747d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,8 +12,8 @@ $(MSBuildThisFileDirectory) - 3.1.2 - 3.1.2 + 3.2.0 + 3.2.0 $(VersionFile) diff --git a/README.md b/README.md index f88bb44720..920e6c73a4 100644 --- a/README.md +++ b/README.md @@ -73,14 +73,13 @@ to your `index.html` or `_Layout.cshtml` file in the `` section like this: The file contains a number of CSS variables that are required to be defined for the components to work correctly. -### Project file -if you want to use icons and/or emoji, starting with version 2.1 you need add a `` to your project file. Within this group you can specify which icons and emoji are made available for usage and publication. Please refer to the [project setup](https://www.fluentui-blazor.net/ProjectSetup) document for more information. - - ### Code Please refer to the [code setup](https://www.fluentui-blazor.net/CodeSetup) document to learn what needs to be included in your `Program.cs` file so that all necessary services are available and setup in the correct way. +## Working with Icons and Emoji +We have additional packages available that include the complete Fluent UI System icons and Fluent UI Emoji collections. +Please refer to the [Icons and Emoji](https://www.fluentui-blazor.net/IconsAndEmoji) page for more information. ## Getting started by using project templates To make it easier to start a project that uses the Fluent UI Blazor components out of the box, we have created the diff --git a/WHATSNEW.md b/WHATSNEW.md index 5db09c21ea..ec395c2771 100644 --- a/WHATSNEW.md +++ b/WHATSNEW.md @@ -1,4 +1,7 @@ -## V3.1.1 +## V3.2.0 +- New NavMenu, NavGroup and NavLink components. **Breaking change** - See the [Upgrade guide](https://www.fluentui-blazor.net/UpgradeGuide) for details. See [NavMenu](https://www.fluentui-blazor.net/NavMenu) page for examples. + +## V3.1.1 - Fix [#776](https://github.com/microsoft/fluentui-blazor/issues/776): Icon throws exception when deployed to Azure - Fix [#755](https://github.com/microsoft/fluentui-blazor/issues/755): Icon throws exception when deployed to Azure - Fix [#789](https://github.com/microsoft/fluentui-blazor/issues/789): Navigation to "/" crashes with FluentNavMenu diff --git a/examples/Demo/Shared/Pages/Lab/IssueTester.razor b/examples/Demo/Shared/Pages/Lab/IssueTester.razor index ff45576dd1..e8853c9642 100644 --- a/examples/Demo/Shared/Pages/Lab/IssueTester.razor +++ b/examples/Demo/Shared/Pages/Lab/IssueTester.razor @@ -1,2 +1 @@ -@page "/IssueTester" - +@page "/IssueTester" \ No newline at end of file diff --git a/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDefault.razor b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDefault.razor index 9e54688dfe..c36cccbce2 100644 --- a/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDefault.razor +++ b/examples/Demo/Shared/Pages/NavMenuTree/Examples/NavMenuTreeDefault.razor @@ -2,7 +2,7 @@ - + diff --git a/examples/Demo/Shared/Shared/DemoNavMenu.razor b/examples/Demo/Shared/Shared/DemoNavMenu.razor index 2a73f4e265..47740aa17b 100644 --- a/examples/Demo/Shared/Shared/DemoNavMenu.razor +++ b/examples/Demo/Shared/Shared/DemoNavMenu.razor @@ -7,7 +7,7 @@ What's new Upgrade guide - Project setup + @* Project setup *@ Code setup Design tokens Reboot diff --git a/examples/Demo/Shared/Shared/DemoNavMenuList.razor b/examples/Demo/Shared/Shared/DemoNavMenuList.razor index eef71058e5..3bc9f8b523 100644 --- a/examples/Demo/Shared/Shared/DemoNavMenuList.razor +++ b/examples/Demo/Shared/Shared/DemoNavMenuList.razor @@ -8,9 +8,9 @@
  • What's new?
  • -
  • + @*
  • Project setup -
  • + *@
  • Code setup
  • diff --git a/examples/Demo/Shared/Shared/DemoNavMenuTree.razor b/examples/Demo/Shared/Shared/DemoNavMenuTree.razor index 835d0a4e5b..90b9a7da01 100644 --- a/examples/Demo/Shared/Shared/DemoNavMenuTree.razor +++ b/examples/Demo/Shared/Shared/DemoNavMenuTree.razor @@ -8,7 +8,7 @@

    More information

    - + @* *@ diff --git a/examples/Demo/Shared/wwwroot/docs/IconsAndEmoji.md b/examples/Demo/Shared/wwwroot/docs/IconsAndEmoji.md index 30a35b068c..7a4251fe5e 100644 --- a/examples/Demo/Shared/wwwroot/docs/IconsAndEmoji.md +++ b/examples/Demo/Shared/wwwroot/docs/IconsAndEmoji.md @@ -1,3 +1,5 @@ +>**If you are upgrading from a previous version of the library and you were alreasy using icons and/or emoji, please see the [Upgrade Guide](https://www.fluentui-blazor.net/UpgradeGuide) for more information.** + Starting with v3, the assets for the icons and emoji are removed from the library package and are provided through additional (separate) packages for both the icon and emoji resources. The components, and icons that are used by the library itself, are still part of the package. Adding the [Microsoft.Fast.Components.FluentUI.Icons package](https://www.nuget.org/packages/Microsoft.Fast.Components.FluentUI.Icons) and/or [Microsoft.Fast.Components.FluentUI.Emojis package](https://www.nuget.org/packages/Microsoft.Fast.Components.FluentUI.Emojis) @@ -5,16 +7,24 @@ is enough to make the resources available to your code. We use the [.NET trimming capabilities](https://learn.microsoft.com/aspnet/core/blazor/host-and-deploy/configure-trimmer) to publish only those assests that are actually being used in your program. Usually this results in some very small DLL's that only contain the resources that are actually being used in your application. -We still have support for both the **complete** [Fluent UI System Icons](https://github.com/microsoft/fluentui-system-icons) and the [Fluent Emoji](https://github.com/microsoft/fluentui-emoji) libraries. +We support the **complete** [Fluent UI System Icons](https://github.com/microsoft/fluentui-system-icons) and [Fluent Emoji](https://github.com/microsoft/fluentui-emoji) collections. ## Getting Started -To use the **Fluent UI System Icons** in your application, you will need to install the [Microsoft.Fast.Components.FluentUI.Icons](https://www.nuget.org/packages/Microsoft.Fast.Components.FluentUI.Icons/) NuGet package in the project are using the main library. +To use the **Fluent UI System Icons** in your application, you will need to install the [Microsoft.Fast.Components.FluentUI.Icons](https://www.nuget.org/packages/Microsoft.Fast.Components.FluentUI.Icons/) NuGet package in the project which is using the main library. + +```shell +dotnet add package Microsoft.Fast.Components.FluentUI.Icons +``` +To use the **Fluent UI Emoji** in your application, you need to install the [Microsoft.Fast.Components.FluentUI.Emojis](https://www.nuget.org/packages/Microsoft.Fast.Components.FluentUI.Emojis/) NuGet package in the project which is using the main library. + +```shell +dotnet add package Microsoft.Fast.Components.FluentUI.Emojis +``` -To use the **Fluent UI Emoji** in your application, you need to install the [Microsoft.Fast.Components.FluentUI.Emojis](https://www.nuget.org/packages/Microsoft.Fast.Components.FluentUI.Emojis/) NuGet package in the project are using the main library. -#### `FluentIcon` component +#### Using the `FluentIcon` component To use the icons, you add a `FluentIcon` component in your code like this: @@ -58,7 +68,7 @@ After adding the class, you can start using this custom icon like a "normal" Flu ``` -#### `FluentEmoji` component +#### Using the `FluentEmoji` component To use the emoji, you add a `FleuntEmoji` component in your code like this: diff --git a/examples/Demo/Shared/wwwroot/docs/UpgradeGuide.md b/examples/Demo/Shared/wwwroot/docs/UpgradeGuide.md index a5fd404af8..45225821c1 100644 --- a/examples/Demo/Shared/wwwroot/docs/UpgradeGuide.md +++ b/examples/Demo/Shared/wwwroot/docs/UpgradeGuide.md @@ -1,14 +1,20 @@ ## Breaking changes v3.2.0 -The pre-v3.2 `FluentNavMenu` has been renamed to `FluentNavMenuTree`. If you want to upgrade your previous menu code, the following changes need to be made: +### The pre-v3.2 `FluentNavMenu` has been renamed to `FluentNavMenuTree` +A new `FluentNavMenu` component has been added. + +If you want to **upgrade** your previous menu code, the following changes need to be made: * Change all occurrences of `` to `` * Change `FluentNavMenuLink` from a self-closing tag to a tag with a closing tag -* Move the `FluentNavMenuLink` `Text` parameters contents to in between the opening and closing tag -* Change any `@onclick` occurences to `OnClick` +* Move the `FluentNavMenuLink` `Text` parameter content to in between the opening and closing tag +* Change any `@onclick` occurrences to `OnClick` -* Change all occurences of `FluentNavMenuGroup` to `FluentNavGroup' +* Change all occurrences of `FluentNavMenuGroup` to `FluentNavGroup' * Replace the `Text` parameter with `Title` + +If you want to **keep** your previous menu code, the following change needs to be made: +* Rename `FluentNavMenu` to `FluentNavMenuTree` ## Breaking changes v3.0.0 diff --git a/examples/Demo/Shared/wwwroot/docs/WhatsNew.md b/examples/Demo/Shared/wwwroot/docs/WhatsNew.md index 608416c326..386ee3d954 100644 --- a/examples/Demo/Shared/wwwroot/docs/WhatsNew.md +++ b/examples/Demo/Shared/wwwroot/docs/WhatsNew.md @@ -1,5 +1,5 @@ ## V3.2.0 -- New NavMenu, NavGroup and NavLink components. See [NavMenu](https://www.fluentui-blazor.net/NavMenu) page for examples. +- New NavMenu, NavGroup and NavLink components. **Breaking change** - See the [Upgrade guide](https://www.fluentui-blazor.net/UpgradeGuide) for details. See [NavMenu](https://www.fluentui-blazor.net/NavMenu) page for examples. ## V3.1.1 - Fix [#776](https://github.com/microsoft/fluentui-blazor/issues/776): Icon throws exception when deployed to Azure From f2381e0de641623ade3599a72085ccc9e5540dbd Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 4 Oct 2023 11:11:04 +0200 Subject: [PATCH 04/13] FluentNavGroup: add Gap parameter Refer to upgrade guide on docs --- examples/Demo/Shared/Pages/NavMenu/NavMenuPage.razor | 2 +- .../Shared/Pages/NavMenuTree/NavMenuTreePage.razor | 2 +- examples/Demo/Shared/Shared/DemoNavMenu.razor | 2 +- src/Core/Components/NavMenu/FluentNavGroup.razor | 2 +- src/Core/Components/NavMenu/FluentNavGroup.razor.cs | 12 ++++++++++++ src/Core/Components/NavMenu/FluentNavGroup.razor.css | 1 - src/Core/Components/NavMenu/FluentNavMenu.razor.css | 6 +++--- 7 files changed, 19 insertions(+), 8 deletions(-) diff --git a/examples/Demo/Shared/Pages/NavMenu/NavMenuPage.razor b/examples/Demo/Shared/Pages/NavMenu/NavMenuPage.razor index 369be16ca3..d8592475fd 100644 --- a/examples/Demo/Shared/Pages/NavMenu/NavMenuPage.razor +++ b/examples/Demo/Shared/Pages/NavMenu/NavMenuPage.razor @@ -22,7 +22,7 @@ We consider the FluentNavMenuTree, FluentNavMenuGroup and FluentNavMenuLink components as deprecated and will remove them in a future version.

    - If you wish to upgrade to the new menu components, please refer to the What's new section for more information. + If you wish to upgrade to the new menu components, please refer to the Upgrade guide for more information.

    diff --git a/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor b/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor index 6441c3c0e2..ca80642f95 100644 --- a/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor +++ b/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor @@ -22,7 +22,7 @@ We consider the FluentNavMenuTree, FluentNavMenuGroup and FluentNavMenuLink components as deprecated and will remove them in a future version.

    - If you wish to upgrade to the new menu components, please refer to the What's new section for more information. + If you wish to upgrade to the new menu components, please refer to the Upgrade guide for more information.

    diff --git a/examples/Demo/Shared/Shared/DemoNavMenu.razor b/examples/Demo/Shared/Shared/DemoNavMenu.razor index 47740aa17b..62aec62a0f 100644 --- a/examples/Demo/Shared/Shared/DemoNavMenu.razor +++ b/examples/Demo/Shared/Shared/DemoNavMenu.razor @@ -4,7 +4,7 @@

    Home

    - + What's new Upgrade guide @* Project setup *@ diff --git a/src/Core/Components/NavMenu/FluentNavGroup.razor b/src/Core/Components/NavMenu/FluentNavGroup.razor index f579a24eb3..b99a59b1cd 100644 --- a/src/Core/Components/NavMenu/FluentNavGroup.razor +++ b/src/Core/Components/NavMenu/FluentNavGroup.razor @@ -6,7 +6,7 @@ @if (NavMenuExpanded || HasIcon) { -
    +
    @if (!string.IsNullOrEmpty(Href)) diff --git a/src/Core/Components/NavMenu/FluentNavGroup.razor.cs b/src/Core/Components/NavMenu/FluentNavGroup.razor.cs index 2b1dc1199d..72e5894fa9 100644 --- a/src/Core/Components/NavMenu/FluentNavGroup.razor.cs +++ b/src/Core/Components/NavMenu/FluentNavGroup.razor.cs @@ -17,6 +17,11 @@ public partial class FluentNavGroup : FluentNavBase .AddClass(Class) .Build(); + internal string? StyleValue => new StyleBuilder(Style) + .AddStyle("margin", $"{Gap} 0" , !string.IsNullOrEmpty(Gap)) + .Build(); + + protected string? ButtonClassValue => new CssBuilder("expand-collapse-button") .AddClass("rotate", Expanded) @@ -55,6 +60,13 @@ public partial class FluentNavGroup : FluentNavBase [Parameter] public string? MaxHeight { get; set; } + /// + /// Defines the vertical spacing between the NavGroup and adjecent items. + /// Needs to be a valid CSS value. Defaults to 10px. + /// + [Parameter] + public string? Gap { get; set; } = "10px"; + /// /// If set, overrides the default expand icon. /// diff --git a/src/Core/Components/NavMenu/FluentNavGroup.razor.css b/src/Core/Components/NavMenu/FluentNavGroup.razor.css index 0996372231..429f84045f 100644 --- a/src/Core/Components/NavMenu/FluentNavGroup.razor.css +++ b/src/Core/Components/NavMenu/FluentNavGroup.razor.css @@ -1,6 +1,5 @@ ::deep .fluent-nav-link { width: 100%; - font-weight: 500; color: inherit; display: flex; align-items: center; diff --git a/src/Core/Components/NavMenu/FluentNavMenu.razor.css b/src/Core/Components/NavMenu/FluentNavMenu.razor.css index 44ff9dc1e0..3dddd77fb8 100644 --- a/src/Core/Components/NavMenu/FluentNavMenu.razor.css +++ b/src/Core/Components/NavMenu/FluentNavMenu.razor.css @@ -61,13 +61,13 @@ } /* NavGroup margins */ -::deep .fluent-nav-group { +/*::deep .fluent-nav-group .standout { margin: calc((var(--design-unit) * 2px) + 2px) 0; } -::deep .fluent-nav-menu .fluent-nav-group { +::deep .fluent-nav-menu .fluent-nav-group .standout { margin: calc((var(--design-unit) * 2px) + 2px) 0; -} +}*/ ::deep .fluent-nav-group .fluent-nav-item:last-of-type, .fluent-nav-menu .fluent-nav-item:last-of-type { margin-bottom: 0; From 5dfbb13aa9f10b5e2df819a8e8225c80ecace9b0 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 4 Oct 2023 11:53:48 +0200 Subject: [PATCH 05/13] Prepare for add obsolete attribute --- .../Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor | 7 ++++++- .../Components/NavMenuTree/FluentNavMenuGroup.razor.cs | 1 + src/Core/Components/NavMenuTree/FluentNavMenuItemBase.cs | 2 ++ src/Core/Components/NavMenuTree/FluentNavMenuLink.razor.cs | 1 + src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.cs | 1 + 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor b/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor index ca80642f95..5063f436f4 100644 --- a/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor +++ b/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor @@ -1,4 +1,5 @@ @page "/NavMenuTree" +# @using FluentUI.Demo.Shared.Pages.NavMenuTree.Examples

    NavMenuTree, NavMenuGroup and NavMenuLink

    @@ -6,7 +7,8 @@
    IMPORTANT

    - With version 3.2 a new, much improved, set of components to build side-bar menus has been added. + With version 3.2 a new, much improved, set of components to build side-bar menus has been added. The demos shown here are using + components which are marked obsolete and will be removed in a future version.

    @@ -34,12 +36,15 @@ None of these components are particulary useful when used stand-alone.

    +#pragma warning disable CS0618 +#pragma warning restore CS0618 +

    Examples

    diff --git a/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor.cs b/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor.cs index 57b2bc2934..d75d08947c 100644 --- a/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor.cs +++ b/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor.cs @@ -3,6 +3,7 @@ namespace Microsoft.Fast.Components.FluentUI; +//[Obsolete("This component has been replaced with the FluentNavGroup and will be removed in a future version.")] public partial class FluentNavMenuGroup : FluentNavMenuItemBase, INavMenuItemsOwner, IDisposable { private readonly List _childItems = new(); diff --git a/src/Core/Components/NavMenuTree/FluentNavMenuItemBase.cs b/src/Core/Components/NavMenuTree/FluentNavMenuItemBase.cs index 60b361cb43..fd91201cbc 100644 --- a/src/Core/Components/NavMenuTree/FluentNavMenuItemBase.cs +++ b/src/Core/Components/NavMenuTree/FluentNavMenuItemBase.cs @@ -67,7 +67,9 @@ public abstract class FluentNavMenuItemBase : FluentComponentBase, IDisposable public int? Width { get; set; } [CascadingParameter] +#pragma warning disable CS0618 // Type or member is obsolete protected FluentNavMenuTree NavMenu { get; private set; } = default!; +#pragma warning restore CS0618 // Type or member is obsolete [CascadingParameter(Name = "NavMenuExpanded")] protected bool NavMenuExpanded { get; private set; } diff --git a/src/Core/Components/NavMenuTree/FluentNavMenuLink.razor.cs b/src/Core/Components/NavMenuTree/FluentNavMenuLink.razor.cs index 91873e6bf7..dee03a617d 100644 --- a/src/Core/Components/NavMenuTree/FluentNavMenuLink.razor.cs +++ b/src/Core/Components/NavMenuTree/FluentNavMenuLink.razor.cs @@ -2,6 +2,7 @@ namespace Microsoft.Fast.Components.FluentUI; +//[Obsolete("This component has been replaced with the FluentNavLink and will be removed in a future version.")] public partial class FluentNavMenuLink : FluentNavMenuItemBase, IDisposable { protected string? ClassValue => new CssBuilder(Class) diff --git a/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.cs b/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.cs index 6399133fee..764229bfc6 100644 --- a/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.cs +++ b/src/Core/Components/NavMenuTree/FluentNavMenuTree.razor.cs @@ -5,6 +5,7 @@ namespace Microsoft.Fast.Components.FluentUI; +//[Obsolete("This component has been replaced with the FluentNavMenu and will be removed in a future version.")] public partial class FluentNavMenuTree : FluentComponentBase, INavMenuItemsOwner, IDisposable { private const string WIDTH_COLLAPSED_MENU = "40px"; From 7ef35a293dd2fe3285e113b378abee629cd23628 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 4 Oct 2023 12:04:54 +0200 Subject: [PATCH 06/13] Update home page banner version --- examples/Demo/Shared/Pages/Index/Index.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Demo/Shared/Pages/Index/Index.razor b/examples/Demo/Shared/Pages/Index/Index.razor index ed2bc810c8..717e1dfe65 100644 --- a/examples/Demo/Shared/Pages/Index/Index.razor +++ b/examples/Demo/Shared/Pages/Index/Index.razor @@ -6,10 +6,10 @@

    Welcome to the Fluent UI Blazor components library

    -

    This is the demo and documentation site for the next version (3.1.1) of the library

    +

    This is the demo and documentation site for the next version (3.2.0) of the library


    - The previous demos and documentation site (2.4.3) is also still available. + The version 2 demos and documentation site is also still available.

    From 35cf60f115957845291094582104850677090783 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 4 Oct 2023 23:27:39 +0200 Subject: [PATCH 07/13] Process review comments --- .../FluentCollapsibleRegion.razor.cs | 57 ++----------------- src/Core/Components/NavMenu/FluentNavBase.cs | 4 +- .../NavMenu/FluentNavGroup.razor.cs | 7 +++ .../Components/NavMenu/FluentNavLink.razor.cs | 4 +- .../Components/NavMenu/FluentNavMenu.razor.cs | 3 + .../NavMenuTree/FluentNavMenuGroup.razor | 4 +- .../NavMenuTree/FluentNavMenuGroup.razor.cs | 2 + 7 files changed, 25 insertions(+), 56 deletions(-) diff --git a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs index faaa4ad07b..494f49d5c9 100644 --- a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs +++ b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs @@ -9,31 +9,18 @@ namespace Microsoft.Fast.Components.FluentUI; public partial class FluentCollapsibleRegion : FluentComponentBase { - - internal enum CollapseState - { - Entering, Entered, Exiting, Exited - } - - //internal double _height; - private bool _expanded, _isRendered; //, _updateHeight; - //private ElementReference _wrapper; - //internal CollapseState _state = CollapseState.Exited; + private bool _expanded, _isRendered; protected string? StyleValue => - new StyleBuilder() + new StyleBuilder(Style) .AddStyle("max-height", MaxHeight, MaxHeight is not null) .AddStyle("height", "auto", Expanded) .AddStyle("height", "0", !Expanded) - .AddStyle(Style) .Build(); protected string? ClassValue => - new CssBuilder("fluent-collapsible-region-container") - //.AddClass($"fluent-collapsible-region-entering", _state == CollapseState.Entering) - //.AddClass($"fluent-collapsible-region-entered", _state == CollapseState.Entered) - //.AddClass($"fluent-collapsible-region-exiting", _state == CollapseState.Exiting) - .AddClass(Class) + new CssBuilder(Class) + .AddClass("fluent-collapsible-region-container") .Build(); /// @@ -46,20 +33,10 @@ public bool Expanded set { if (_expanded == value) - return; - _expanded = value; - - if (_isRendered) - { - //_state = _expanded ? CollapseState.Entering : CollapseState.Exiting; - //_ = UpdateHeight(); - //_updateHeight = true; - } - else if (_expanded) { - //_state = CollapseState.Entered; + return; } - + _expanded = value; _ = ExpandedChanged.InvokeAsync(_expanded); } } @@ -76,30 +53,8 @@ public bool Expanded [Parameter] public RenderFragment? ChildContent { get; set; } - [Parameter] - public EventCallback OnAnimationEnd { get; set; } - [Parameter] public EventCallback ExpandedChanged { get; set; } - - - //internal async Task UpdateHeight() - //{ - // try - // { - // _height = (await _wrapper.MudGetBoundingClientRectAsync())?.Height ?? 0; - // } - // catch (Exception ex) when (ex is JSDisconnectedException or TaskCanceledException) - // { - // _height = 0; - // } - - // if (_height > MaxHeight) - // { - // _height = MaxHeight.Value; - // } - //} - protected override void OnAfterRender(bool firstRender) { diff --git a/src/Core/Components/NavMenu/FluentNavBase.cs b/src/Core/Components/NavMenu/FluentNavBase.cs index 625a31eacb..d8b9f59679 100644 --- a/src/Core/Components/NavMenu/FluentNavBase.cs +++ b/src/Core/Components/NavMenu/FluentNavBase.cs @@ -67,7 +67,7 @@ public abstract class FluentNavBase : FluentComponentBase /// /// Returns if the item has an set. /// - public bool HasIcon => Icon is not null; + internal bool HasIcon => Icon is not null; [Parameter] public EventCallback OnClick { get; set; } @@ -84,7 +84,9 @@ public abstract class FluentNavBase : FluentComponentBase protected async Task OnClickHandler(MouseEventArgs ev) { if (Disabled) + { return; + } if (Href != null) { NavigationManager.NavigateTo(Href, ForceLoad); diff --git a/src/Core/Components/NavMenu/FluentNavGroup.razor.cs b/src/Core/Components/NavMenu/FluentNavGroup.razor.cs index 72e5894fa9..ea7e00be9c 100644 --- a/src/Core/Components/NavMenu/FluentNavGroup.razor.cs +++ b/src/Core/Components/NavMenu/FluentNavGroup.razor.cs @@ -72,6 +72,10 @@ public partial class FluentNavGroup : FluentNavBase /// [Parameter] public Icon ExpandIcon { get; set; } = new CoreIcons.Regular.Size12.ChevronRight(); + + /// + /// Gets or sets a callback that is triggered whenever changes. + /// [Parameter] public EventCallback ExpandedChanged { get; set; } @@ -88,6 +92,9 @@ private async Task HandleExpanderKeyDownAsync(KeyboardEventArgs args) { Task handler = args.Code switch { + "NumpadEnter" => SetExpandedAsync(!Expanded), + "NumpadArrowRight" => SetExpandedAsync(true), + "NumpadArrowLeft" => SetExpandedAsync(false), "Enter" => SetExpandedAsync(!Expanded), "ArrowRight" => SetExpandedAsync(true), "ArrowLeft" => SetExpandedAsync(false), diff --git a/src/Core/Components/NavMenu/FluentNavLink.razor.cs b/src/Core/Components/NavMenu/FluentNavLink.razor.cs index d75677678a..812a4963d3 100644 --- a/src/Core/Components/NavMenu/FluentNavLink.razor.cs +++ b/src/Core/Components/NavMenu/FluentNavLink.razor.cs @@ -8,8 +8,8 @@ public partial class FluentNavLink : FluentNavBase { private readonly RenderFragment _renderContent; - internal string? ClassValue => new CssBuilder("fluent-nav-item") - .AddClass(Class) + internal string? ClassValue => new CssBuilder(Class) + .AddClass("fluent-nav-item") .Build(); internal string? LinkClassValue => new CssBuilder("fluent-nav-link") diff --git a/src/Core/Components/NavMenu/FluentNavMenu.razor.cs b/src/Core/Components/NavMenu/FluentNavMenu.razor.cs index 52f91537c9..28f610d2d7 100644 --- a/src/Core/Components/NavMenu/FluentNavMenu.razor.cs +++ b/src/Core/Components/NavMenu/FluentNavMenu.razor.cs @@ -86,6 +86,9 @@ private async Task HandleExpandCollapseKeyDownAsync(KeyboardEventArgs args) { Task handler = args.Code switch { + "NumpadEnter" => SetExpandedAsync(!Expanded), + "NumpadArrowRight" => SetExpandedAsync(true), + "NumpadArrowLeft" => SetExpandedAsync(false), "Enter" => SetExpandedAsync(value: !Expanded), "ArrowRight" => SetExpandedAsync(true), "ArrowLeft" => SetExpandedAsync(false), diff --git a/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor b/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor index 188d0806a2..613effa0ab 100644 --- a/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor +++ b/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor @@ -17,11 +17,11 @@ Text="@Text"> @if (HasIcon) { - + } else if (SiblingHasIcon) { - + } @if (GetShouldRenderChildContent()) { diff --git a/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor.cs b/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor.cs index d75d08947c..370ef3574a 100644 --- a/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor.cs +++ b/src/Core/Components/NavMenuTree/FluentNavMenuGroup.razor.cs @@ -6,6 +6,8 @@ namespace Microsoft.Fast.Components.FluentUI; //[Obsolete("This component has been replaced with the FluentNavGroup and will be removed in a future version.")] public partial class FluentNavMenuGroup : FluentNavMenuItemBase, INavMenuItemsOwner, IDisposable { + internal const string ICON_WIDTH = "20px"; + private readonly List _childItems = new(); private bool HasChildIcons => ((INavMenuItemsOwner)this).HasChildIcons; private bool Visible => NavMenu.Expanded || HasIcon; From 21311c609296021faae90955e49454a115cd634a Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 4 Oct 2023 23:35:10 +0200 Subject: [PATCH 08/13] Fix #816 --- .../Components/MainLayout/FluentMainLayout.razor | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Core/Components/MainLayout/FluentMainLayout.razor b/src/Core/Components/MainLayout/FluentMainLayout.razor index 1a4e7c3867..91ca93c839 100644 --- a/src/Core/Components/MainLayout/FluentMainLayout.razor +++ b/src/Core/Components/MainLayout/FluentMainLayout.razor @@ -5,15 +5,17 @@ @Header - - + + @SubHeader - + - - @NavMenuContent - - + @if (NavMenuContent != null) + { + + @NavMenuContent + + } @Body From aa732c51903cac62ab197ff46dda679da0b48c0f Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Wed, 4 Oct 2023 23:46:34 +0200 Subject: [PATCH 09/13] Fix #811 Fix compiler warning --- .../FluentCollapsibleRegion.razor.cs | 11 ++--------- src/Core/Components/Divider/FluentDivider.razor | 1 + 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs index 494f49d5c9..f89c42a52d 100644 --- a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs +++ b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs @@ -9,7 +9,7 @@ namespace Microsoft.Fast.Components.FluentUI; public partial class FluentCollapsibleRegion : FluentComponentBase { - private bool _expanded, _isRendered; + private bool _expanded; protected string? StyleValue => new StyleBuilder(Style) @@ -55,12 +55,5 @@ public bool Expanded [Parameter] public EventCallback ExpandedChanged { get; set; } - - protected override void OnAfterRender(bool firstRender) - { - if (firstRender) - { - _isRendered = true; - } - } + } diff --git a/src/Core/Components/Divider/FluentDivider.razor b/src/Core/Components/Divider/FluentDivider.razor index eace7ea01f..4b6d2c2b52 100644 --- a/src/Core/Components/Divider/FluentDivider.razor +++ b/src/Core/Components/Divider/FluentDivider.razor @@ -1,6 +1,7 @@ @namespace Microsoft.Fast.Components.FluentUI @inherits FluentComponentBase Date: Fri, 6 Oct 2023 10:47:04 +0200 Subject: [PATCH 10/13] NavGroup: add TitleTemplate parameter, update docs and examples --- .../Pages/NavMenu/Examples/NavMenuDefault.razor | 7 +++++-- .../Demo/Shared/Pages/NavMenu/NavMenuPage.razor | 16 ++++++++-------- src/Core/Components/NavMenu/FluentNavGroup.razor | 1 + .../Components/NavMenu/FluentNavGroup.razor.cs | 10 +++++++++- .../Components/NavMenu/FluentNavMenu.razor.css | 2 +- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor b/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor index ea7b67ad13..cc0c06ef71 100644 --- a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor +++ b/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor @@ -6,8 +6,11 @@ Home Item 2 - Item 3.1 - Item 3.2 +

    Item 3

    + + Item 3.1 + Item 3.2 +
    Item 4 Item 5 diff --git a/examples/Demo/Shared/Pages/NavMenu/NavMenuPage.razor b/examples/Demo/Shared/Pages/NavMenu/NavMenuPage.razor index d8592475fd..b162346cbf 100644 --- a/examples/Demo/Shared/Pages/NavMenu/NavMenuPage.razor +++ b/examples/Demo/Shared/Pages/NavMenu/NavMenuPage.razor @@ -35,12 +35,6 @@ None of these components are particulary useful when used stand-alone.

    - - - - - -

    Examples

    @@ -48,7 +42,7 @@ This demo shows 3 different versions of a NavMenu (with FluentNavGroups and FluentNavLink)s. From left to right:
      -
    • Menu with several sub-menus, icons, etc
    • +
    • Menu with several sub-menus, icons, etc. The first group (Item 3) uses both the Title as the TitleTemplate parameters
    • Menu without sub-menus but with icons
    • Menu with just text links
    @@ -63,7 +57,13 @@ - An example data binding the Expanded parameter. + An example of binding to the Expanded parameter. +

    API Documentation

    + + + + + diff --git a/src/Core/Components/NavMenu/FluentNavGroup.razor b/src/Core/Components/NavMenu/FluentNavGroup.razor index b99a59b1cd..1c8bf8b4e9 100644 --- a/src/Core/Components/NavMenu/FluentNavGroup.razor +++ b/src/Core/Components/NavMenu/FluentNavGroup.razor @@ -48,6 +48,7 @@ }
    @Title + @TitleTemplate
    } diff --git a/src/Core/Components/NavMenu/FluentNavGroup.razor.cs b/src/Core/Components/NavMenu/FluentNavGroup.razor.cs index ea7e00be9c..d478246e91 100644 --- a/src/Core/Components/NavMenu/FluentNavGroup.razor.cs +++ b/src/Core/Components/NavMenu/FluentNavGroup.razor.cs @@ -72,7 +72,15 @@ public partial class FluentNavGroup : FluentNavBase ///
[Parameter] public Icon ExpandIcon { get; set; } = new CoreIcons.Regular.Size12.ChevronRight(); - + + /// + /// Allows for specific markup and styling to be applied for the group title + /// When using this, the containded s and s need to be placed in a ChildContent tag. + /// When specifying both Title and TitleTemplate, both will be rendered. + /// + [Parameter] + public RenderFragment? TitleTemplate { get; set; } + /// /// Gets or sets a callback that is triggered whenever changes. /// diff --git a/src/Core/Components/NavMenu/FluentNavMenu.razor.css b/src/Core/Components/NavMenu/FluentNavMenu.razor.css index 3dddd77fb8..605fb82ed4 100644 --- a/src/Core/Components/NavMenu/FluentNavMenu.razor.css +++ b/src/Core/Components/NavMenu/FluentNavMenu.razor.css @@ -57,7 +57,7 @@ background: var(--neutral-fill-stealth-rest); border: calc(var(--stroke-width) * 1px) solid transparent; border-radius: calc(var(--control-corner-radius) * 1px); - height: calc(((var(--base-height-multiplier) + var(--density)) * var(--design-unit) + 1) * 1px); + min-height: calc(((var(--base-height-multiplier) + var(--density)) * var(--design-unit) + 1) * 1px); } /* NavGroup margins */ From b893487719857fe28c8101958cd0b65af34f72ed Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Fri, 6 Oct 2023 13:51:35 +0200 Subject: [PATCH 11/13] Add NavMenu components tests --- .../NavMenu/Examples/NavMenuDefault.razor | 2 +- .../Pages/NavMenuTree/NavMenuTreePage.razor | 18 +-- src/Core/Components/NavMenu/FluentNavBase.cs | 8 +- .../NavMenu/FluentNavGroup.razor.cs | 5 + .../Components/NavMenu/FluentNavMenu.razor | 15 +- .../Components/NavMenu/FluentNavMenu.razor.cs | 4 +- ...Tests.FluentNavGroup_Default.verified.html | 20 +++ ...ests.FluentNavGroup_Disabled.verified.html | 20 +++ ...upTests.FluentNavGroup_Empty.verified.html | 20 +++ ...roupTests.FluentNavGroup_Gap.verified.html | 20 +++ ....FluentNavGroup_HideExpander.verified.html | 15 ++ ...oupTests.FluentNavGroup_Href.verified.html | 20 +++ ...oupTests.FluentNavGroup_Icon.verified.html | 22 +++ ...entNavGroup_IconAndIconColor.verified.html | 22 +++ ...FluentNavGroup_TitleTemplate.verified.html | 21 +++ tests/Core/NavMenu/FluentNavGroupTests.cs | 147 ++++++++++++++++++ ...kTests.FluentNavLink_Default.verified.html | 11 ++ ...FluentNavLink_ExtendedTtitle.verified.html | 13 ++ ...ests.FluentNavLink_ForceLoad.verified.html | 11 ++ ...LinkTests.FluentNavLink_Href.verified.html | 11 ++ ...LinkTests.FluentNavLink_Icon.verified.html | 13 ++ ...uentNavLink_IconAndIconColor.verified.html | 13 ++ ...inkTests.FluentNavLink_Match.verified.html | 11 ++ ...kTests.FluentNavLink_OnClick.verified.html | 11 ++ ...nkTests.FluentNavLink_Target.verified.html | 11 ++ tests/Core/NavMenu/FluentNavLinkTests.cs | 144 +++++++++++++++++ ...ts.FluentNavMenu_Collapsible.verified.html | 11 ++ ...vMenu_CollapsibleCustomTitle.verified.html | 11 ++ ...ts.FluentNavMenu_CustomTitle.verified.html | 2 + ...uTests.FluentNavMenu_Default.verified.html | 2 + ...luentNavMenu_ExpanderContent.verified.html | 10 ++ ...nuTests.FluentNavMenu_Margin.verified.html | 2 + ...ts.FluentNavMenu_NotExpanded.verified.html | 2 + ...enuTests.FluentNavMenu_Width.verified.html | 2 + tests/Core/NavMenu/FluentNavMenuTests.cs | 119 ++++++++++++++ 35 files changed, 768 insertions(+), 21 deletions(-) create mode 100644 tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Default.verified.html create mode 100644 tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Disabled.verified.html create mode 100644 tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Empty.verified.html create mode 100644 tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Gap.verified.html create mode 100644 tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_HideExpander.verified.html create mode 100644 tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Href.verified.html create mode 100644 tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Icon.verified.html create mode 100644 tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_IconAndIconColor.verified.html create mode 100644 tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_TitleTemplate.verified.html create mode 100644 tests/Core/NavMenu/FluentNavGroupTests.cs create mode 100644 tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Default.verified.html create mode 100644 tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ExtendedTtitle.verified.html create mode 100644 tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ForceLoad.verified.html create mode 100644 tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Href.verified.html create mode 100644 tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Icon.verified.html create mode 100644 tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_IconAndIconColor.verified.html create mode 100644 tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Match.verified.html create mode 100644 tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_OnClick.verified.html create mode 100644 tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Target.verified.html create mode 100644 tests/Core/NavMenu/FluentNavLinkTests.cs create mode 100644 tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Collapsible.verified.html create mode 100644 tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CollapsibleCustomTitle.verified.html create mode 100644 tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CustomTitle.verified.html create mode 100644 tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Default.verified.html create mode 100644 tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_ExpanderContent.verified.html create mode 100644 tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Margin.verified.html create mode 100644 tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_NotExpanded.verified.html create mode 100644 tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Width.verified.html create mode 100644 tests/Core/NavMenu/FluentNavMenuTests.cs diff --git a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor b/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor index cc0c06ef71..ecf147c754 100644 --- a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor +++ b/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor @@ -2,7 +2,7 @@
- + Home Item 2 diff --git a/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor b/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor index 5063f436f4..4b4def70de 100644 --- a/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor +++ b/examples/Demo/Shared/Pages/NavMenuTree/NavMenuTreePage.razor @@ -1,5 +1,5 @@ @page "/NavMenuTree" -# + @using FluentUI.Demo.Shared.Pages.NavMenuTree.Examples

NavMenuTree, NavMenuGroup and NavMenuLink

@@ -36,15 +36,6 @@ None of these components are particulary useful when used stand-alone.

-#pragma warning disable CS0618 - - - - - - -#pragma warning restore CS0618 -

Examples

@@ -76,3 +67,10 @@ An example of intercepting menu actions to provide custom behavior. + +

API Documentation

+ + + + + \ No newline at end of file diff --git a/src/Core/Components/NavMenu/FluentNavBase.cs b/src/Core/Components/NavMenu/FluentNavBase.cs index d8b9f59679..7dfbdfdf4e 100644 --- a/src/Core/Components/NavMenu/FluentNavBase.cs +++ b/src/Core/Components/NavMenu/FluentNavBase.cs @@ -11,12 +11,6 @@ namespace Microsoft.Fast.Components.FluentUI; ///
public abstract class FluentNavBase : FluentComponentBase { - /// - /// The text to display for the group. - /// - [Parameter] - public string? Title { get; set; } - /// /// URL for the group. /// @@ -62,7 +56,7 @@ public abstract class FluentNavBase : FluentComponentBase public NavLinkMatch Match { get; set; } = NavLinkMatch.Prefix; [CascadingParameter(Name = "NavMenuExpanded")] - protected bool NavMenuExpanded { get; private set; } + public bool NavMenuExpanded { get; private set; } /// /// Returns if the item has an set. diff --git a/src/Core/Components/NavMenu/FluentNavGroup.razor.cs b/src/Core/Components/NavMenu/FluentNavGroup.razor.cs index d478246e91..032477eb48 100644 --- a/src/Core/Components/NavMenu/FluentNavGroup.razor.cs +++ b/src/Core/Components/NavMenu/FluentNavGroup.razor.cs @@ -40,6 +40,11 @@ public partial class FluentNavGroup : FluentNavBase { "rel", !string.IsNullOrWhiteSpace(Target) ? "noopener noreferrer" : string.Empty } }; } + /// + /// The text to display for the group. + /// + [Parameter] + public string? Title { get; set; } /// /// If true, expands the nav group, otherwise collapse it. diff --git a/src/Core/Components/NavMenu/FluentNavMenu.razor b/src/Core/Components/NavMenu/FluentNavMenu.razor index e6fcba62ea..c7a22ab322 100644 --- a/src/Core/Components/NavMenu/FluentNavMenu.razor +++ b/src/Core/Components/NavMenu/FluentNavMenu.razor @@ -1,7 +1,20 @@ @namespace Microsoft.Fast.Components.FluentUI @inherits FluentComponentBase -
+ +@{ + string? title; + if (Collapsible) + { + title = null; + } + else +{ + title = Title; + } +} + +
@if (Collapsible) { diff --git a/src/Core/Components/NavMenu/FluentNavMenu.razor.cs b/src/Core/Components/NavMenu/FluentNavMenu.razor.cs index 28f610d2d7..b013ee5a76 100644 --- a/src/Core/Components/NavMenu/FluentNavMenu.razor.cs +++ b/src/Core/Components/NavMenu/FluentNavMenu.razor.cs @@ -30,8 +30,8 @@ public partial class FluentNavMenu : FluentComponentBase public RenderFragment? ExpanderContent { get; set; } /// - /// Gets or sets the title of the navigation menu - /// Default to "Navigation menu" + /// Gets or sets the title of the navigation menu using the aria-label attribute. + /// Defaults to "Navigation menu" /// [Parameter] public string? Title { get; set; } = "Navigation menu"; diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Default.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Default.verified.html new file mode 100644 index 0000000000..8b93415058 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Default.verified.html @@ -0,0 +1,20 @@ + +
+
+
+ +
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Disabled.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Disabled.verified.html new file mode 100644 index 0000000000..5a811fe02d --- /dev/null +++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Disabled.verified.html @@ -0,0 +1,20 @@ + +
+
+
+ +
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Empty.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Empty.verified.html new file mode 100644 index 0000000000..d7cc43c4e8 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Empty.verified.html @@ -0,0 +1,20 @@ + +
+
+
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Gap.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Gap.verified.html new file mode 100644 index 0000000000..058c9cd134 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Gap.verified.html @@ -0,0 +1,20 @@ + +
+
+
+ +
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_HideExpander.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_HideExpander.verified.html new file mode 100644 index 0000000000..44df1c08a8 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_HideExpander.verified.html @@ -0,0 +1,15 @@ + +
+
+
+ +
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Href.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Href.verified.html new file mode 100644 index 0000000000..496f16f24b --- /dev/null +++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Href.verified.html @@ -0,0 +1,20 @@ + +
+
+
+ + +
Group title +
+
+ +
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Icon.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Icon.verified.html new file mode 100644 index 0000000000..eea5d4105d --- /dev/null +++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Icon.verified.html @@ -0,0 +1,22 @@ + +
+
+
+ +
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_IconAndIconColor.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_IconAndIconColor.verified.html new file mode 100644 index 0000000000..df27f477aa --- /dev/null +++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_IconAndIconColor.verified.html @@ -0,0 +1,22 @@ + +
+
+
+ +
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_TitleTemplate.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_TitleTemplate.verified.html new file mode 100644 index 0000000000..7c8f681410 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_TitleTemplate.verified.html @@ -0,0 +1,21 @@ + +
+
+
+ +
+
+
+
NavGroups and NavLinks here
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavGroupTests.cs b/tests/Core/NavMenu/FluentNavGroupTests.cs new file mode 100644 index 0000000000..4cf8245a4a --- /dev/null +++ b/tests/Core/NavMenu/FluentNavGroupTests.cs @@ -0,0 +1,147 @@ +using Microsoft.Fast.Components.FluentUI.Tests.Extensions; +using Xunit; + +namespace Microsoft.Fast.Components.FluentUI.Tests.NavMenu; + +public class FluentNavGroupTests : TestBase +{ + [Fact] + public void FluentNavGroup_Default() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.NavMenuExpanded, true); + parameters.Add(p => p.Title, "Group title"); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavGroup_Empty() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.NavMenuExpanded, true); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavGroup_HideExpander() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.NavMenuExpanded, true); + parameters.Add(p => p.HideExpander, true); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavGroup_Disabled() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.NavMenuExpanded, true); + parameters.Add(p => p.Disabled, true); + parameters.Add(p => p.Title, "Group title"); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavGroup_TitleTemplate() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.NavMenuExpanded, true); + parameters.Add(p => p.TitleTemplate, "

Group title

"); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavGroup_Gap() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.NavMenuExpanded, true); + parameters.Add(p => p.Title, "Group title"); + parameters.Add(p => p.Gap, "20px"); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavGroup_Href() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.NavMenuExpanded, true); + parameters.Add(p => p.Title, "Group title"); + parameters.Add(p => p.Href, "/NavMenu"); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavGroup_Icon() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.NavMenuExpanded, true); + parameters.Add(p => p.Title, "Group title"); + parameters.Add(p => p.Icon, SampleIcons.Info); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavGroup_IconAndIconColor() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.NavMenuExpanded, true); + parameters.Add(p => p.Title, "Group title"); + parameters.Add(p => p.Icon, SampleIcons.Info); + parameters.Add(p => p.IconColor, Color.Neutral); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + +} diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Default.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Default.verified.html new file mode 100644 index 0000000000..17d77eeee3 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Default.verified.html @@ -0,0 +1,11 @@ + +
+
+
+ +
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ExtendedTtitle.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ExtendedTtitle.verified.html new file mode 100644 index 0000000000..3b7ccb8f99 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ExtendedTtitle.verified.html @@ -0,0 +1,13 @@ + +
+
+
+ +
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ForceLoad.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ForceLoad.verified.html new file mode 100644 index 0000000000..08d6cf5258 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_ForceLoad.verified.html @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Href.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Href.verified.html new file mode 100644 index 0000000000..08d6cf5258 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Href.verified.html @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Icon.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Icon.verified.html new file mode 100644 index 0000000000..61c7275191 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Icon.verified.html @@ -0,0 +1,13 @@ + +
+
+
+ +
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_IconAndIconColor.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_IconAndIconColor.verified.html new file mode 100644 index 0000000000..d891dbbbb2 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_IconAndIconColor.verified.html @@ -0,0 +1,13 @@ + +
+
+
+ +
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Match.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Match.verified.html new file mode 100644 index 0000000000..08d6cf5258 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Match.verified.html @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_OnClick.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_OnClick.verified.html new file mode 100644 index 0000000000..17d77eeee3 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_OnClick.verified.html @@ -0,0 +1,11 @@ + +
+
+
+ +
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Target.verified.html b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Target.verified.html new file mode 100644 index 0000000000..2d91ef5729 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavLinkTests.FluentNavLink_Target.verified.html @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavLinkTests.cs b/tests/Core/NavMenu/FluentNavLinkTests.cs new file mode 100644 index 0000000000..b0c3822111 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavLinkTests.cs @@ -0,0 +1,144 @@ +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.Fast.Components.FluentUI.Tests.Extensions; +using Xunit; + +namespace Microsoft.Fast.Components.FluentUI.Tests.NavMenu; + +public class FluentNavLinkTests : TestBase +{ + [Fact] + public void FluentNavLink_Default() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.AddChildContent("NavLink text"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavLink_ExtendedTtitle() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.AddChildContent("

NavLink text

"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavLink_Href() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.Href, "/NavMenu"); + parameters.AddChildContent("NavLink text"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavLink_Target() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.Href, "/NavMenu"); + parameters.Add(p => p.Target, "_blank"); + parameters.AddChildContent("NavLink text"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavLink_Match() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.Href, "/NavMenu"); + parameters.Add(p => p.Match, NavLinkMatch.All); + parameters.AddChildContent("NavLink text"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavLink_ForceLoad() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.Href, "/NavMenu"); + parameters.Add(p => p.ForceLoad, true); + parameters.AddChildContent("NavLink text"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavLink_Icon() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.Icon, SampleIcons.Info); + parameters.AddChildContent("NavLink text"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavLink_IconAndIconColor() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.NavMenuExpanded, true); + parameters.Add(p => p.Icon, SampleIcons.Info); + parameters.Add(p => p.IconColor, Color.Neutral); + parameters.AddChildContent("NavLink text"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavLink_OnClick() + { + Action onClickHandler = _ => { }; + + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.OnClick, onClickHandler); + parameters.AddChildContent("NavLink text"); + }); + + // Assert + cut.Verify(); + } + + //ActiveClass + //Match + //Target +} diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Collapsible.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Collapsible.verified.html new file mode 100644 index 0000000000..c4a556086e --- /dev/null +++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Collapsible.verified.html @@ -0,0 +1,11 @@ + +
+
+
+
+ +
+
+
NavGroups and NavLinks here
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CollapsibleCustomTitle.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CollapsibleCustomTitle.verified.html new file mode 100644 index 0000000000..d6b657561e --- /dev/null +++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CollapsibleCustomTitle.verified.html @@ -0,0 +1,11 @@ + +
+
+
+
+ +
+
+
NavGroups and NavLinks here
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CustomTitle.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CustomTitle.verified.html new file mode 100644 index 0000000000..39ec9a18d1 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_CustomTitle.verified.html @@ -0,0 +1,2 @@ + +
NavGroups and NavLinks here
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Default.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Default.verified.html new file mode 100644 index 0000000000..bbf9fd18df --- /dev/null +++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Default.verified.html @@ -0,0 +1,2 @@ + +
NavGroups and NavLinks here
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_ExpanderContent.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_ExpanderContent.verified.html new file mode 100644 index 0000000000..ab9e5b4c22 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_ExpanderContent.verified.html @@ -0,0 +1,10 @@ + +
+
+
+
+
custom expander
+
+
+
+
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Margin.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Margin.verified.html new file mode 100644 index 0000000000..a3874c3e52 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Margin.verified.html @@ -0,0 +1,2 @@ + +
NavGroups and NavLinks here
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_NotExpanded.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_NotExpanded.verified.html new file mode 100644 index 0000000000..40aabf93c6 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_NotExpanded.verified.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Width.verified.html b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Width.verified.html new file mode 100644 index 0000000000..f1c083ae5b --- /dev/null +++ b/tests/Core/NavMenu/FluentNavMenuTests.FluentNavMenu_Width.verified.html @@ -0,0 +1,2 @@ + +
NavGroups and NavLinks here
\ No newline at end of file diff --git a/tests/Core/NavMenu/FluentNavMenuTests.cs b/tests/Core/NavMenu/FluentNavMenuTests.cs new file mode 100644 index 0000000000..c587c458c2 --- /dev/null +++ b/tests/Core/NavMenu/FluentNavMenuTests.cs @@ -0,0 +1,119 @@ +using Xunit; + +namespace Microsoft.Fast.Components.FluentUI.Tests.NavMenu; + +public class FluentNavMenuTests : TestBase +{ + [Fact] + public void FluentNavMenu_Default() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavMenu_Collapsible() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.Collapsible, true); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavMenu_NotExpanded() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.Expanded, false); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavMenu_CustomTitle() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.Title, "Custom title"); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + + [Fact] + public void FluentNavMenu_CollapsibleCustomTitle() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.Collapsible, true); + parameters.Add(p => p.Title, "Custom title"); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavMenu_Width() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.Width, 300); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavMenu_Margin() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.Margin, "5px 15px"); + parameters.AddChildContent("NavGroups and NavLinks here"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentNavMenu_ExpanderContent() + { + // Arrange & Act + var cut = TestContext.RenderComponent(parameters => + { + parameters.Add(p => p.Collapsible, true); + parameters.Add(p => p.ExpanderContent, "
custom expander
"); + }); + + // Assert + cut.Verify(); + } +} From c23f308416ec0151c170e6a1b215edc089b7f017 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Fri, 6 Oct 2023 20:44:41 +0200 Subject: [PATCH 12/13] Process review comments --- .../Pages/NavMenu/Examples/NavMenuDefault.razor | 6 +++--- examples/Demo/Shared/Shared/DemoNavMenu.razor | 12 ++++++------ .../FluentCollapsibleRegion.razor.cs | 5 ++++- src/Core/Components/NavMenu/FluentNavBase.cs | 12 ++++++++++-- .../Components/NavMenu/FluentNavGroup.razor | 6 +++--- .../Components/NavMenu/FluentNavGroup.razor.cs | 2 +- .../Components/NavMenu/FluentNavGroup.razor.css | 3 ++- .../Components/NavMenu/FluentNavLink.razor.css | 3 ++- src/Core/Components/NavMenu/FluentNavMenu.razor | 15 +-------------- .../Components/NavMenu/FluentNavMenu.razor.css | 17 ++++++----------- src/Core/Enums/Color.cs | 6 ++++++ 11 files changed, 44 insertions(+), 43 deletions(-) diff --git a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor b/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor index ecf147c754..8ac20860d8 100644 --- a/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor +++ b/examples/Demo/Shared/Pages/NavMenu/Examples/NavMenuDefault.razor @@ -14,13 +14,13 @@
Item 4 Item 5 - + Item 6.1 Item 6.2 - Item 6.3.1 + Item 6.3.1 Item 6.3.1 Item 6.3.1 Item 6.3.2 - + Item 6.3.3.1 Item 6.3.3.2 diff --git a/examples/Demo/Shared/Shared/DemoNavMenu.razor b/examples/Demo/Shared/Shared/DemoNavMenu.razor index 62aec62a0f..d32edddab8 100644 --- a/examples/Demo/Shared/Shared/DemoNavMenu.razor +++ b/examples/Demo/Shared/Shared/DemoNavMenu.razor @@ -4,7 +4,7 @@

Home

- + What's new Upgrade guide @* Project setup *@ @@ -20,13 +20,13 @@ Blazor Form - + FluentComponentBase FluentInputBase Clear cache - + Header Footer BodyContent @@ -38,11 +38,11 @@ Stack - + Accordion Anchor Anchored Region - + Badge CounterBadge PresenceBadge @@ -98,7 +98,7 @@ Tree View - + MarkdownSection TableOfContents diff --git a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs index f89c42a52d..96c3d9faf5 100644 --- a/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs +++ b/src/Core/Components/CollapsibleRegion/FluentCollapsibleRegion.razor.cs @@ -13,7 +13,7 @@ public partial class FluentCollapsibleRegion : FluentComponentBase protected string? StyleValue => new StyleBuilder(Style) - .AddStyle("max-height", MaxHeight, MaxHeight is not null) + .AddStyle("max-height", MaxHeight, !string.IsNullOrEmpty(MaxHeight)) .AddStyle("height", "auto", Expanded) .AddStyle("height", "0", !Expanded) .Build(); @@ -53,6 +53,9 @@ public bool Expanded [Parameter] public RenderFragment? ChildContent { get; set; } + /// + /// Callback for when the Expanded property changes. + /// [Parameter] public EventCallback ExpandedChanged { get; set; } diff --git a/src/Core/Components/NavMenu/FluentNavBase.cs b/src/Core/Components/NavMenu/FluentNavBase.cs index 7dfbdfdf4e..c1779964f0 100644 --- a/src/Core/Components/NavMenu/FluentNavBase.cs +++ b/src/Core/Components/NavMenu/FluentNavBase.cs @@ -42,16 +42,21 @@ public abstract class FluentNavBase : FluentComponentBase [Parameter] public bool Disabled { get; set; } + /// + /// Gets or sets the content to be shown. + /// [Parameter] public RenderFragment? ChildContent { get; set; } - /// /// Class names to use to indicate the item is active, separated by space. /// [Parameter] public string ActiveClass { get; set; } = "active"; + /// + /// Gets or sets how the link should be matched. Defaults to . + /// [Parameter] public NavLinkMatch Match { get; set; } = NavLinkMatch.Prefix; @@ -63,6 +68,9 @@ public abstract class FluentNavBase : FluentComponentBase ///
internal bool HasIcon => Icon is not null; + /// + /// The callback to invoke when the item is clicked. + /// [Parameter] public EventCallback OnClick { get; set; } @@ -81,7 +89,7 @@ protected async Task OnClickHandler(MouseEventArgs ev) { return; } - if (Href != null) + if (!string.IsNullOrEmpty(Href)) { NavigationManager.NavigateTo(Href, ForceLoad); } diff --git a/src/Core/Components/NavMenu/FluentNavGroup.razor b/src/Core/Components/NavMenu/FluentNavGroup.razor index 1c8bf8b4e9..baac263994 100644 --- a/src/Core/Components/NavMenu/FluentNavGroup.razor +++ b/src/Core/Components/NavMenu/FluentNavGroup.razor @@ -6,7 +6,7 @@ @if (NavMenuExpanded || HasIcon) { -
+
@if (!string.IsNullOrEmpty(Href)) @@ -46,7 +46,7 @@ { } -
+
@Title @TitleTemplate
@@ -62,7 +62,7 @@ @onclick="ToggleExpandedAsync" @onclick:stopPropagation="true" @onkeydown="@HandleExpanderKeyDownAsync"> - +
} diff --git a/src/Core/Components/NavMenu/FluentNavGroup.razor.cs b/src/Core/Components/NavMenu/FluentNavGroup.razor.cs index 032477eb48..8a5b7a3af6 100644 --- a/src/Core/Components/NavMenu/FluentNavGroup.razor.cs +++ b/src/Core/Components/NavMenu/FluentNavGroup.razor.cs @@ -70,7 +70,7 @@ public partial class FluentNavGroup : FluentNavBase /// Needs to be a valid CSS value. Defaults to 10px. ///
[Parameter] - public string? Gap { get; set; } = "10px"; + public string? Gap { get; set; } /// /// If set, overrides the default expand icon. diff --git a/src/Core/Components/NavMenu/FluentNavGroup.razor.css b/src/Core/Components/NavMenu/FluentNavGroup.razor.css index 429f84045f..a7eed56927 100644 --- a/src/Core/Components/NavMenu/FluentNavGroup.razor.css +++ b/src/Core/Components/NavMenu/FluentNavGroup.razor.css @@ -1,9 +1,10 @@ ::deep .fluent-nav-link { width: 100%; color: inherit; - display: flex; align-items: center; text-decoration: none; + display: grid; + grid-template-columns: 30px auto 40px; } ::deep .fluent-nav-icon { diff --git a/src/Core/Components/NavMenu/FluentNavLink.razor.css b/src/Core/Components/NavMenu/FluentNavLink.razor.css index 973cfd6c97..615539adc0 100644 --- a/src/Core/Components/NavMenu/FluentNavLink.razor.css +++ b/src/Core/Components/NavMenu/FluentNavLink.razor.css @@ -1,9 +1,10 @@ ::deep .fluent-nav-link { font-weight: 400; color: inherit; - display: flex; align-items: center; text-decoration: none; + display: grid; + grid-template-columns: 30px auto 40px; } ::deep .fluent-nav-icon { diff --git a/src/Core/Components/NavMenu/FluentNavMenu.razor b/src/Core/Components/NavMenu/FluentNavMenu.razor index c7a22ab322..2fd23c6f52 100644 --- a/src/Core/Components/NavMenu/FluentNavMenu.razor +++ b/src/Core/Components/NavMenu/FluentNavMenu.razor @@ -1,20 +1,7 @@ @namespace Microsoft.Fast.Components.FluentUI @inherits FluentComponentBase - -@{ - string? title; - if (Collapsible) - { - title = null; - } - else -{ - title = Title; - } -} - -
+
@if (Collapsible) { diff --git a/src/Core/Components/NavMenu/FluentNavMenu.razor.css b/src/Core/Components/NavMenu/FluentNavMenu.razor.css index 605fb82ed4..32525ff060 100644 --- a/src/Core/Components/NavMenu/FluentNavMenu.razor.css +++ b/src/Core/Components/NavMenu/FluentNavMenu.razor.css @@ -2,7 +2,6 @@ .fluent-nav-menu { flex-direction: column; align-items: stretch; - min-width: fit-content; } ::deep .fluent-nav-item { @@ -12,7 +11,7 @@ } /* Hover and active highlighting */ - ::deep .fluent-nav-item .positioning-region:hover:not(:has(a.disabled)) { + ::deep .fluent-nav-item .positioning-region:hover:not(:has(.disabled)) { cursor: pointer; background: var(--neutral-fill-secondary-rest); } @@ -60,15 +59,6 @@ min-height: calc(((var(--base-height-multiplier) + var(--density)) * var(--design-unit) + 1) * 1px); } -/* NavGroup margins */ -/*::deep .fluent-nav-group .standout { - margin: calc((var(--design-unit) * 2px) + 2px) 0; -} - -::deep .fluent-nav-menu .fluent-nav-group .standout { - margin: calc((var(--design-unit) * 2px) + 2px) 0; -}*/ - ::deep .fluent-nav-group .fluent-nav-item:last-of-type, .fluent-nav-menu .fluent-nav-item:last-of-type { margin-bottom: 0; } @@ -90,6 +80,11 @@ padding-inline-start: 96px; } +::deep .fluent-nav-text { + overflow: hidden; + text-overflow: ellipsis; +} + /* collapsed */ ::deep.collapsed .fluent-nav-text { display: none; diff --git a/src/Core/Enums/Color.cs b/src/Core/Enums/Color.cs index b347790366..31b848bcfd 100644 --- a/src/Core/Enums/Color.cs +++ b/src/Core/Enums/Color.cs @@ -71,6 +71,12 @@ public enum Color [Description("var(--neutral-layer-1)")] Lightweight, + /// + /// Use the --neutral-stroke-rest CSS variable color, adapts to light/dark mode. + /// + [Description("var(--neutral-stroke-rest)")] + Disabled, + /// /// Supply an HTML hex color string value (#rrggbb or #rgb) for the CustomColor parameter. /// From b972aa3f166fceaec7361a3276b9cd181dce7161 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Fri, 6 Oct 2023 23:59:25 +0200 Subject: [PATCH 13/13] Fix tests --- ...Tests.FluentNavGroup_Default.verified.html | 5 +-- ....FluentNavGroup_Disabled.verified (2).html | 20 ++++++++++++ ...ests.FluentNavGroup_Disabled.verified.html | 32 +++++++++---------- ...upTests.FluentNavGroup_Empty.verified.html | 32 +++++++++---------- ...roupTests.FluentNavGroup_Gap.verified.html | 6 ++-- ....FluentNavGroup_HideExpander.verified.html | 4 +-- ...oupTests.FluentNavGroup_Href.verified.html | 6 ++-- ...oupTests.FluentNavGroup_Icon.verified.html | 6 ++-- ...entNavGroup_IconAndIconColor.verified.html | 6 ++-- ...FluentNavGroup_TitleTemplate.verified.html | 4 +-- 10 files changed, 71 insertions(+), 50 deletions(-) create mode 100644 tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Disabled.verified (2).html diff --git a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Default.verified.html b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Default.verified.html index 8b93415058..91478a34cb 100644 --- a/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Default.verified.html +++ b/tests/Core/NavMenu/FluentNavGroupTests.FluentNavGroup_Default.verified.html @@ -1,10 +1,11 @@ -
+