From dd1c6e2153f67fa9fbdadc0cacdd14dc51063828 Mon Sep 17 00:00:00 2001 From: GerardasB <10091419+GerardasB@users.noreply.github.com> Date: Thu, 27 Nov 2025 15:16:45 +0200 Subject: [PATCH 01/25] Add placement property to StandardLayoutToolbarItem --- .../src/appui-react/toolbar/ToolbarItem.ts | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts b/ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts index 9d3859efa2d..12671087a5a 100644 --- a/ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts +++ b/ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts @@ -14,13 +14,13 @@ import type { BadgeType, IconSpec } from "@itwin/core-react"; import type { BadgeKind } from "@itwin/core-react/internal"; import { UiItemsProvider } from "../ui-items-provider/UiItemsProvider.js"; -/** Used to specify the usage of the toolbar which determine the toolbar position. +/** Used to specify the usage of the toolbar which determines the toolbar position. * @public */ export enum ToolbarUsage { - /** Contains tools to Create Update and Delete content - in AppUI this is in top left of content area. */ + /** Contains tools to manipulate the content. Positioned at the top-left of the content area by default. */ ContentManipulation = 0, - /** Manipulate view/camera - in AppUI this is in top right of content area. */ + /** Contains tools to navigate the view and control the camera. Positioned at the top-right of the content area by default. */ ViewNavigation = 1, } @@ -28,12 +28,22 @@ export enum ToolbarUsage { * @public */ export enum ToolbarOrientation { - /** Horizontal toolbar. */ + /** Horizontal toolbar with items are arranged left to right. */ Horizontal = 0, - /** Vertical toolbar. */ + /** Vertical toolbar with items are arranged top to bottom. */ Vertical = 1, } +/** + * Used to specify the advanced placement of the toolbar. This takes precedence over the {@link ToolbarUsage}. + * + * Known placements: + * - `"view-settings"`: contains tools to control the view settings. Positioned at the bottom-right of the content area by default. + * + * @public + */ +export type ToolbarPlacement = "view-settings" | (string & {}); + /** Describes the data needed to insert a UI items into an existing set of UI items. * @public */ @@ -180,4 +190,6 @@ export interface StandardLayoutToolbarItem { readonly usage: ToolbarUsage; /** Describes toolbar orientation. */ readonly orientation: ToolbarOrientation; + /** Describes toolbar placement. */ + readonly placement?: ToolbarPlacement; } From 40b850b1a54561239a16acf66fe3a36fc5509bb8 Mon Sep 17 00:00:00 2001 From: GerardasB <10091419+GerardasB@users.noreply.github.com> Date: Thu, 27 Nov 2025 15:31:55 +0200 Subject: [PATCH 02/25] Rename to custom usage. --- ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts b/ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts index 12671087a5a..bbe8dedc855 100644 --- a/ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts +++ b/ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts @@ -35,14 +35,14 @@ export enum ToolbarOrientation { } /** - * Used to specify the advanced placement of the toolbar. This takes precedence over the {@link ToolbarUsage}. + * Used to specify the custom usage of the toolbar which determines the toolbar position. This takes precedence over the {@link ToolbarUsage}. * - * Known placements: + * Known custom usages: * - `"view-settings"`: contains tools to control the view settings. Positioned at the bottom-right of the content area by default. * * @public */ -export type ToolbarPlacement = "view-settings" | (string & {}); +export type ToolbarCustomUsage = "view-settings" | (string & {}); /** Describes the data needed to insert a UI items into an existing set of UI items. * @public @@ -190,6 +190,6 @@ export interface StandardLayoutToolbarItem { readonly usage: ToolbarUsage; /** Describes toolbar orientation. */ readonly orientation: ToolbarOrientation; - /** Describes toolbar placement. */ - readonly placement?: ToolbarPlacement; + /** Describes a custom toolbar usage. This takes precedence over the {@link usage}. */ + readonly customUsage?: ToolbarCustomUsage; } From 16f43d841c8f7f79bf22aa26385f63abfdba946e Mon Sep 17 00:00:00 2001 From: GerardasB <10091419+GerardasB@users.noreply.github.com> Date: Thu, 27 Nov 2025 15:44:12 +0200 Subject: [PATCH 03/25] Rename to advanced usage. --- ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts b/ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts index bbe8dedc855..3ac5ea4553b 100644 --- a/ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts +++ b/ui/appui-react/src/appui-react/toolbar/ToolbarItem.ts @@ -35,14 +35,15 @@ export enum ToolbarOrientation { } /** - * Used to specify the custom usage of the toolbar which determines the toolbar position. This takes precedence over the {@link ToolbarUsage}. + * Used to specify the advanced usage of the toolbar which determines the toolbar position. This takes precedence over the {@link ToolbarUsage}. * - * Known custom usages: + * Known advanced usages: * - `"view-settings"`: contains tools to control the view settings. Positioned at the bottom-right of the content area by default. * + * @note This type is non-exhaustive to allow for future additions. * @public */ -export type ToolbarCustomUsage = "view-settings" | (string & {}); +export type ToolbarAdvancedUsage = "view-settings" | (string & {}); /** Describes the data needed to insert a UI items into an existing set of UI items. * @public @@ -190,6 +191,6 @@ export interface StandardLayoutToolbarItem { readonly usage: ToolbarUsage; /** Describes toolbar orientation. */ readonly orientation: ToolbarOrientation; - /** Describes a custom toolbar usage. This takes precedence over the {@link usage}. */ - readonly customUsage?: ToolbarCustomUsage; + /** Describes an advanced toolbar usage. This takes precedence over the {@link usage}. */ + readonly advancedUsage?: ToolbarAdvancedUsage; } From c128e3b83991801ba38e805db052ce01c5758c16 Mon Sep 17 00:00:00 2001 From: GerardasB <10091419+GerardasB@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:29:38 +0200 Subject: [PATCH 04/25] Add stories --- docs/storybook/src/Utils.tsx | 47 ++++++++ .../components/ToolbarComposer.stories.tsx | 42 +------ .../src/frontstage/Toolbar.stories.tsx | 113 ++++++++++++++++++ docs/storybook/src/frontstage/Toolbar.tsx | 39 ++++++ 4 files changed, 203 insertions(+), 38 deletions(-) create mode 100644 docs/storybook/src/frontstage/Toolbar.stories.tsx create mode 100644 docs/storybook/src/frontstage/Toolbar.tsx diff --git a/docs/storybook/src/Utils.tsx b/docs/storybook/src/Utils.tsx index 4448e2b80ff..244377af5fb 100644 --- a/docs/storybook/src/Utils.tsx +++ b/docs/storybook/src/Utils.tsx @@ -2,6 +2,7 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ +import { action } from "storybook/actions"; import type { ArgTypes } from "@storybook/react-vite"; import { ContentProps, @@ -12,6 +13,11 @@ import { StageUsage, StandardContentLayouts, StandardFrontstageProps, + ToolbarActionItem, + ToolbarGroupItem, + ToolbarItemUtilities, + ToolbarOrientation, + ToolbarUsage, Widget, } from "@itwin/appui-react"; import { SvgPlaceholder } from "@itwin/itwinui-icons-react"; @@ -110,3 +116,44 @@ export function createWidget(id: number, overrides?: Partial): Widget { ...overrides, }; } + +export function createToolbarItemFactory() { + let i = 0; + function createActionItem( + overrides?: Omit, "icon"> + ) { + const id = `item${++i}`; + const label = `Item ${i}`; + return ToolbarItemUtilities.createActionItem({ + id, + label, + icon: , + execute: () => action(label)(), + layouts: { + standard: { + usage: ToolbarUsage.ContentManipulation, + orientation: ToolbarOrientation.Horizontal, + }, + }, + ...overrides, + }); + } + + function createGroupItem( + overrides?: Omit, "icon"> + ) { + const id = `group${++i}`; + const label = `Group ${i}`; + return ToolbarItemUtilities.createGroupItem({ + id, + label, + icon: , + ...overrides, + }); + } + + return { + createActionItem, + createGroupItem, + }; +} diff --git a/docs/storybook/src/components/ToolbarComposer.stories.tsx b/docs/storybook/src/components/ToolbarComposer.stories.tsx index 5189af8d797..8de7b4a96e9 100644 --- a/docs/storybook/src/components/ToolbarComposer.stories.tsx +++ b/docs/storybook/src/components/ToolbarComposer.stories.tsx @@ -11,8 +11,6 @@ import { import { CommandItemDef, ToolItemDef, - ToolbarActionItem, - ToolbarGroupItem, ToolbarHelper, ToolbarItemUtilities, ToolbarOrientation, @@ -36,7 +34,10 @@ import placeholderIcon from "@bentley/icons-generic/icons/placeholder.svg"; import { AppUiDecorator, InitializerDecorator } from "../Decorators"; import { withResizer } from "../../.storybook/addons/Resizer"; import { createBumpEvent } from "../createBumpEvent"; -import { enumArgType } from "../Utils"; +import { + enumArgType, + createToolbarItemFactory as createItemFactory, +} from "../Utils"; import { ToolbarComposerStory } from "./ToolbarComposer"; const meta = { @@ -573,41 +574,6 @@ function createAbstractConditionalIcon() { }; } -function createItemFactory() { - let i = 0; - function createActionItem( - overrides?: Omit, "icon"> - ) { - const id = `item${++i}`; - const label = `Item ${i}`; - return ToolbarItemUtilities.createActionItem({ - id, - label, - icon: , - execute: () => action(label)(), - ...overrides, - }); - } - - function createGroupItem( - overrides?: Omit, "icon"> - ) { - const id = `group${++i}`; - const label = `Group ${i}`; - return ToolbarItemUtilities.createGroupItem({ - id, - label, - icon: , - ...overrides, - }); - } - - return { - createActionItem, - createGroupItem, - }; -} - function createItems() { const action1 = ToolbarItemUtilities.createActionItem( "item1", diff --git a/docs/storybook/src/frontstage/Toolbar.stories.tsx b/docs/storybook/src/frontstage/Toolbar.stories.tsx new file mode 100644 index 00000000000..5e5a8b820c7 --- /dev/null +++ b/docs/storybook/src/frontstage/Toolbar.stories.tsx @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { + ToolbarUsage, + ToolbarOrientation, + ToolbarItemLayouts, +} from "@itwin/appui-react"; +import { Page } from "../AppUiStory"; +import { ToolbarStory } from "./Toolbar"; +import { createToolbarItemFactory, enumArgType } from "../Utils"; + +const meta = { + title: "Frontstage/Toolbar", + component: ToolbarStory, + tags: ["autodocs"], + parameters: { + docs: { + page: () => , + }, + layout: "fullscreen", + }, + args: { + usage: ToolbarUsage.ContentManipulation, + orientation: ToolbarOrientation.Horizontal, + getItemProvider: ({ usage, orientation }) => { + return { + id: "items", + getToolbarItems: () => { + const factory = createToolbarItemFactory(); + const layouts = { + standard: { + usage, + orientation, + }, + } satisfies ToolbarItemLayouts; + return [ + factory.createActionItem({ layouts }), + factory.createActionItem({ layouts }), + factory.createActionItem({ layouts }), + ]; + }, + }; + }, + }, + argTypes: { + usage: enumArgType(ToolbarUsage), + orientation: enumArgType(ToolbarOrientation), + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const ContentManipulation: Story = {}; + +export const ContentManipulationVertical: Story = { + name: "Content Manipulation (vertical)", + args: { + ...ContentManipulation.args, + orientation: ToolbarOrientation.Vertical, + }, +}; + +export const ViewNavigation: Story = { + args: { + usage: ToolbarUsage.ViewNavigation, + }, +}; + +export const ViewNavigationVertical: Story = { + name: "View Navigation (vertical)", + args: { + ...ViewNavigation.args, + orientation: ToolbarOrientation.Vertical, + }, +}; + +export const ViewSettings: Story = { + args: { + usage: ToolbarUsage.ViewNavigation, + getItemProvider: ({ usage, orientation }) => { + return { + id: "items", + getToolbarItems: () => { + const factory = createToolbarItemFactory(); + const layouts = { + standard: { + usage, + orientation, + advancedUsage: "view-settings", + }, + } satisfies ToolbarItemLayouts; + return [ + factory.createActionItem({ layouts }), + factory.createActionItem({ layouts }), + factory.createActionItem({ layouts }), + ]; + }, + }; + }, + }, +}; + +export const ViewSettingsVertical: Story = { + name: "View Settings (vertical)", + args: { + ...ViewSettings.args, + orientation: ToolbarOrientation.Vertical, + }, +}; diff --git a/docs/storybook/src/frontstage/Toolbar.tsx b/docs/storybook/src/frontstage/Toolbar.tsx new file mode 100644 index 00000000000..b480e2b0e18 --- /dev/null +++ b/docs/storybook/src/frontstage/Toolbar.tsx @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ +import { + ToolbarOrientation, + ToolbarUsage, + UiFramework, + UiItemsProvider, +} from "@itwin/appui-react"; +import { AppUiStory } from "../AppUiStory"; +import { createFrontstage } from "../Utils"; + +type ToolbarStoryProps = { + usage: ToolbarUsage; + orientation: ToolbarOrientation; + getItemProvider: (props: ToolbarStoryProps) => UiItemsProvider; +}; + +export function ToolbarStory(props: ToolbarStoryProps) { + const frontstage = createFrontstage({ + rightPanelProps: { + sizeSpec: 250, + }, + hideStatusBar: true, + hideToolSettings: true, + }); + const provider = props.getItemProvider?.(props); + return ( + { + UiFramework.visibility.autoHideUi = false; + }} + /> + ); +} From fce4df1143f7ed5c51763c6a63f4613a1387179c Mon Sep 17 00:00:00 2001 From: GerardasB <10091419+GerardasB@users.noreply.github.com> Date: Fri, 28 Nov 2025 14:11:55 +0200 Subject: [PATCH 05/25] Add secondary toolbars to NavigationArea --- .../layout/widget/NavigationArea.scss | 18 +++++++++++++++--- .../layout/widget/NavigationArea.tsx | 18 ++++++++++++++++-- .../appui-react/toolbar/ToolbarComposer.tsx | 16 ++++++++++++++-- .../useActiveStageProvidedToolbarItems.ts | 18 ++++++++++++------ .../toolbar/useUiItemsProviderToolbarItems.tsx | 3 ++- .../widgets/NavigationWidgetComposer.tsx | 12 ++++++++++-- .../widgets/ViewToolWidgetComposer.tsx | 16 ++++++++++++++++ 7 files changed, 85 insertions(+), 16 deletions(-) diff --git a/ui/appui-react/src/appui-react/layout/widget/NavigationArea.scss b/ui/appui-react/src/appui-react/layout/widget/NavigationArea.scss index c245710f96d..ebeac05a917 100644 --- a/ui/appui-react/src/appui-react/layout/widget/NavigationArea.scss +++ b/ui/appui-react/src/appui-react/layout/widget/NavigationArea.scss @@ -14,9 +14,10 @@ grid-gap: 6px; grid-template-areas: "htools button" - ". vtools"; + ". vtools" + "secondary-htools vtools"; grid-template-columns: 1fr auto; - grid-template-rows: auto 1fr; + grid-template-rows: auto 1fr auto; justify-items: end; &.nz-hidden { @@ -39,7 +40,6 @@ > .nz-vertical-toolbar-container { grid-area: vtools; - height: calc(100% - 60px); display: inline-flex; flex-direction: column; align-items: flex-end; @@ -66,4 +66,16 @@ justify-content: flex-end; } } + + > .nz-secondary-vertical-toolbar-container { + grid-area: vtools; + + @include nz-widget-opacity; + } + + > .nz-secondary-horizontal-toolbar-container { + grid-area: secondary-htools; + + @include nz-widget-opacity; + } } diff --git a/ui/appui-react/src/appui-react/layout/widget/NavigationArea.tsx b/ui/appui-react/src/appui-react/layout/widget/NavigationArea.tsx index 1816b3b3d24..fbdc460c558 100644 --- a/ui/appui-react/src/appui-react/layout/widget/NavigationArea.tsx +++ b/ui/appui-react/src/appui-react/layout/widget/NavigationArea.tsx @@ -21,10 +21,14 @@ export interface NavigationAreaProps extends CommonProps, NoChildrenProps { * I.e. [[AppButton]] in NavigationArea zone or navigation aid control in Navigation zone. */ navigationAid?: React.ReactNode; - /** Horizontal toolbar. See [[Toolbar]] */ + /** Horizontal toolbar. Positioned at the top-right. */ horizontalToolbar?: React.ReactNode; - /** Vertical toolbar. See [[Toolbar]] */ + /** Vertical toolbar. Positioned at the top-right. */ verticalToolbar?: React.ReactNode; + /** Secondary horizontal toolbar. Positioned at the bottom-right. */ + secondaryHorizontalToolbar?: React.ReactNode; + /** Secondary vertical toolbar. Positioned at the bottom-right. */ + secondaryVerticalToolbar?: React.ReactNode; /** Handler for mouse enter */ onMouseEnter?: (event: React.MouseEvent) => void; /** Handler for mouse leave */ @@ -68,7 +72,17 @@ export function NavigationArea(props: NavigationAreaProps) { onMouseLeave={props.onMouseLeave} > {props.verticalToolbar} +
{props.secondaryVerticalToolbar}
+ {props.secondaryHorizontalToolbar && ( +
+ {props.secondaryHorizontalToolbar} +
+ )} ); } diff --git a/ui/appui-react/src/appui-react/toolbar/ToolbarComposer.tsx b/ui/appui-react/src/appui-react/toolbar/ToolbarComposer.tsx index 568b72ad53a..71ae8a562af 100644 --- a/ui/appui-react/src/appui-react/toolbar/ToolbarComposer.tsx +++ b/ui/appui-react/src/appui-react/toolbar/ToolbarComposer.tsx @@ -19,6 +19,7 @@ import { ToolbarDragInteractionContext } from "./DragInteraction.js"; import type { CommonToolbarItem, ToolbarActionItem, + ToolbarAdvancedUsage, ToolbarGroupItem, ToolbarItem, } from "./ToolbarItem.js"; @@ -196,6 +197,8 @@ export interface ExtensibleToolbarProps { usage: ToolbarUsage; /** Toolbar orientation. */ orientation: ToolbarOrientation; + /** Advanced usage string to further specify the toolbar context for UI item providers. */ + advancedUsage?: ToolbarAdvancedUsage; /** Describes the ids of active toolbar items. * By default only the toolbar item with id that matches the active `Tool` id is active. * @note Property {@link CommonToolbarItem.isActiveCondition} takes precedence when determining the active state of a toolbar item. @@ -210,10 +213,19 @@ export interface ExtensibleToolbarProps { */ // eslint-disable-next-line @typescript-eslint/no-deprecated export function ToolbarComposer(props: ExtensibleToolbarProps) { - const { usage, orientation, activeItemIds: activeItemIdsProp } = props; + const { + usage, + orientation, + advancedUsage, + activeItemIds: activeItemIdsProp, + } = props; // process items from addon UI providers - const addonItems = useActiveStageProvidedToolbarItems(usage, orientation); + const addonItems = useActiveStageProvidedToolbarItems( + usage, + orientation, + advancedUsage + ); const allItems = React.useMemo(() => { return combineItems(props.items, addonItems); diff --git a/ui/appui-react/src/appui-react/toolbar/useActiveStageProvidedToolbarItems.ts b/ui/appui-react/src/appui-react/toolbar/useActiveStageProvidedToolbarItems.ts index 86a136051cb..d7ab6e911ac 100644 --- a/ui/appui-react/src/appui-react/toolbar/useActiveStageProvidedToolbarItems.ts +++ b/ui/appui-react/src/appui-react/toolbar/useActiveStageProvidedToolbarItems.ts @@ -11,6 +11,7 @@ import { useActiveStageId } from "../hooks/useActiveStageId.js"; import { useAvailableUiItemsProviders } from "../hooks/useAvailableUiItemsProviders.js"; import { UiFramework } from "../UiFramework.js"; import type { + ToolbarAdvancedUsage, ToolbarItem, ToolbarOrientation, ToolbarUsage, @@ -20,7 +21,8 @@ import { UiItemsManager } from "../ui-items-provider/UiItemsManager.js"; function getItems( stageId: string, usage: ToolbarUsage, - orientation: ToolbarOrientation + orientation: ToolbarOrientation, + advancedUsage: ToolbarAdvancedUsage | undefined ) { const frontstageDef = UiFramework.frontstages.activeFrontstageDef; if (!frontstageDef) return []; @@ -31,7 +33,10 @@ function getItems( usage, orientation ); - return toolbarItems; + return toolbarItems.filter((item) => { + const itemUsage = item.layouts?.standard?.advancedUsage; + return itemUsage === advancedUsage; + }); } /** @@ -41,13 +46,14 @@ function getItems( */ export const useActiveStageProvidedToolbarItems = ( usage: ToolbarUsage, - orientation: ToolbarOrientation + orientation: ToolbarOrientation, + advancedUsage: ToolbarAdvancedUsage | undefined ): readonly ToolbarItem[] => { const uiItemsProviderIds = useAvailableUiItemsProviders(); const stageId = useActiveStageId(); const [items, setItems] = React.useState(() => - getItems(stageId, usage, orientation) + getItems(stageId, usage, orientation, advancedUsage) ); const providersRef = React.useRef(""); const currentStageRef = React.useRef(""); @@ -65,8 +71,8 @@ export const useActiveStageProvidedToolbarItems = ( currentStageRef.current = stageId; providersRef.current = uiProviders; - const newItems = getItems(stageId, usage, orientation); + const newItems = getItems(stageId, usage, orientation, advancedUsage); setItems(newItems); - }, [orientation, stageId, uiItemsProviderIds, usage]); + }, [orientation, stageId, uiItemsProviderIds, usage, advancedUsage]); return items; }; diff --git a/ui/appui-react/src/appui-react/toolbar/useUiItemsProviderToolbarItems.tsx b/ui/appui-react/src/appui-react/toolbar/useUiItemsProviderToolbarItems.tsx index 08121c06404..645a0fc54d4 100644 --- a/ui/appui-react/src/appui-react/toolbar/useUiItemsProviderToolbarItems.tsx +++ b/ui/appui-react/src/appui-react/toolbar/useUiItemsProviderToolbarItems.tsx @@ -27,7 +27,8 @@ export const useUiItemsProviderToolbarItems = ( ): readonly ToolbarItem[] => { const providedItems = useActiveStageProvidedToolbarItems( toolbarUsage, - toolbarOrientation + toolbarOrientation, + undefined ); const [items, setItems] = React.useState(providedItems); React.useEffect(() => { diff --git a/ui/appui-react/src/appui-react/widgets/NavigationWidgetComposer.tsx b/ui/appui-react/src/appui-react/widgets/NavigationWidgetComposer.tsx index a7cfcb18513..190d867d824 100644 --- a/ui/appui-react/src/appui-react/widgets/NavigationWidgetComposer.tsx +++ b/ui/appui-react/src/appui-react/widgets/NavigationWidgetComposer.tsx @@ -205,10 +205,14 @@ function DefaultNavigationAid() { */ // eslint-disable-next-line @typescript-eslint/no-deprecated export interface NavigationWidgetComposerProps extends CommonProps { - /** Optional horizontal toolbar */ + /** Optional horizontal toolbar. Positioned at the top-right. */ horizontalToolbar?: React.ReactNode; - /** Optional vertical toolbar */ + /** Optional vertical toolbar. Positioned at the top-right. */ verticalToolbar?: React.ReactNode; + /** Optional secondary horizontal toolbar. Positioned at the bottom-right. */ + secondaryHorizontalToolbar?: React.ReactNode; + /** Optional secondary vertical toolbar. Positioned at the bottom-right. */ + secondaryVerticalToolbar?: React.ReactNode; /** Optional navigation aid to override the default {@link NavigationAidHost}. */ navigationAidHost?: React.ReactNode; /** If true no navigation aid will be shown. Defaults to `false`. */ @@ -226,6 +230,8 @@ export function NavigationWidgetComposer(props: NavigationWidgetComposerProps) { navigationAidHost, horizontalToolbar, verticalToolbar, + secondaryHorizontalToolbar, + secondaryVerticalToolbar, hideNavigationAid, ...otherProps } = props; @@ -268,6 +274,8 @@ export function NavigationWidgetComposer(props: NavigationWidgetComposerProps) { navigationAid={navigationAid} horizontalToolbar={horizontalToolbar} verticalToolbar={verticalToolbar} + secondaryHorizontalToolbar={secondaryHorizontalToolbar} + secondaryVerticalToolbar={secondaryVerticalToolbar} {...otherProps} onMouseEnter={UiFramework.visibility.handleWidgetMouseEnter} hidden={!uiIsVisible} diff --git a/ui/appui-react/src/appui-react/widgets/ViewToolWidgetComposer.tsx b/ui/appui-react/src/appui-react/widgets/ViewToolWidgetComposer.tsx index 919e58a27f4..91949214d8f 100644 --- a/ui/appui-react/src/appui-react/widgets/ViewToolWidgetComposer.tsx +++ b/ui/appui-react/src/appui-react/widgets/ViewToolWidgetComposer.tsx @@ -56,6 +56,22 @@ export function ViewToolWidgetComposer(props: ViewToolWidgetComposerProps) { orientation={ToolbarOrientation.Vertical} /> } + secondaryHorizontalToolbar={ + + } + secondaryVerticalToolbar={ + + } /> ); } From 173b429105becb66714e58f7d873a1b299949124 Mon Sep 17 00:00:00 2001 From: GerardasB <10091419+GerardasB@users.noreply.github.com> Date: Fri, 28 Nov 2025 14:17:36 +0200 Subject: [PATCH 06/25] Add All story --- .../src/frontstage/Toolbar.stories.tsx | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/docs/storybook/src/frontstage/Toolbar.stories.tsx b/docs/storybook/src/frontstage/Toolbar.stories.tsx index 5e5a8b820c7..45347d4c9f0 100644 --- a/docs/storybook/src/frontstage/Toolbar.stories.tsx +++ b/docs/storybook/src/frontstage/Toolbar.stories.tsx @@ -10,7 +10,11 @@ import { } from "@itwin/appui-react"; import { Page } from "../AppUiStory"; import { ToolbarStory } from "./Toolbar"; -import { createToolbarItemFactory, enumArgType } from "../Utils"; +import { + createToolbarItemFactory, + enumArgType, + removeProperty, +} from "../Utils"; const meta = { title: "Frontstage/Toolbar", @@ -111,3 +115,67 @@ export const ViewSettingsVertical: Story = { orientation: ToolbarOrientation.Vertical, }, }; + +export const All: Story = { + args: { + getItemProvider: () => { + return { + id: "items", + getToolbarItems: () => { + const factory = createToolbarItemFactory(); + const allLayouts: ToolbarItemLayouts[] = [ + { + standard: { + usage: ToolbarUsage.ContentManipulation, + orientation: ToolbarOrientation.Horizontal, + }, + }, + { + standard: { + usage: ToolbarUsage.ContentManipulation, + orientation: ToolbarOrientation.Vertical, + }, + }, + { + standard: { + usage: ToolbarUsage.ViewNavigation, + orientation: ToolbarOrientation.Horizontal, + }, + }, + { + standard: { + usage: ToolbarUsage.ViewNavigation, + orientation: ToolbarOrientation.Vertical, + }, + }, + { + standard: { + usage: ToolbarUsage.ViewNavigation, + orientation: ToolbarOrientation.Horizontal, + advancedUsage: "view-settings", + }, + }, + { + standard: { + usage: ToolbarUsage.ViewNavigation, + orientation: ToolbarOrientation.Vertical, + advancedUsage: "view-settings", + }, + }, + ]; + return allLayouts.flatMap((layouts) => { + return [ + factory.createActionItem({ layouts }), + factory.createActionItem({ layouts }), + factory.createActionItem({ layouts }), + ]; + }); + }, + }; + }, + }, + argTypes: { + usage: removeProperty(), + orientation: removeProperty(), + }, +}; From 131558a183f12cb1827bb3723b98ef30e7162a3d Mon Sep 17 00:00:00 2001 From: GerardasB <10091419+GerardasB@users.noreply.github.com> Date: Fri, 28 Nov 2025 15:18:05 +0200 Subject: [PATCH 07/25] Add group item --- docs/storybook/src/frontstage/Toolbar.stories.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/storybook/src/frontstage/Toolbar.stories.tsx b/docs/storybook/src/frontstage/Toolbar.stories.tsx index 45347d4c9f0..e0e3550007f 100644 --- a/docs/storybook/src/frontstage/Toolbar.stories.tsx +++ b/docs/storybook/src/frontstage/Toolbar.stories.tsx @@ -42,6 +42,10 @@ const meta = { } satisfies ToolbarItemLayouts; return [ factory.createActionItem({ layouts }), + factory.createGroupItem({ + layouts, + items: [factory.createActionItem(), factory.createActionItem()], + }), factory.createActionItem({ layouts }), factory.createActionItem({ layouts }), ]; @@ -166,6 +170,10 @@ export const All: Story = { return allLayouts.flatMap((layouts) => { return [ factory.createActionItem({ layouts }), + factory.createGroupItem({ + layouts, + items: [factory.createActionItem(), factory.createActionItem()], + }), factory.createActionItem({ layouts }), factory.createActionItem({ layouts }), ]; From cd90b6e616cf16709d2cb6559019ff343939872c Mon Sep 17 00:00:00 2001 From: GerardasB <10091419+GerardasB@users.noreply.github.com> Date: Fri, 28 Nov 2025 15:38:01 +0200 Subject: [PATCH 08/25] Cleanup --- .../appui-react/layout/widget/NavigationArea.scss | 6 ------ .../appui-react/layout/widget/NavigationArea.tsx | 14 ++++---------- .../widgets/NavigationWidgetComposer.tsx | 2 +- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/ui/appui-react/src/appui-react/layout/widget/NavigationArea.scss b/ui/appui-react/src/appui-react/layout/widget/NavigationArea.scss index ebeac05a917..a742b3d7aae 100644 --- a/ui/appui-react/src/appui-react/layout/widget/NavigationArea.scss +++ b/ui/appui-react/src/appui-react/layout/widget/NavigationArea.scss @@ -67,12 +67,6 @@ } } - > .nz-secondary-vertical-toolbar-container { - grid-area: vtools; - - @include nz-widget-opacity; - } - > .nz-secondary-horizontal-toolbar-container { grid-area: secondary-htools; diff --git a/ui/appui-react/src/appui-react/layout/widget/NavigationArea.tsx b/ui/appui-react/src/appui-react/layout/widget/NavigationArea.tsx index fbdc460c558..5a32f2d8bd7 100644 --- a/ui/appui-react/src/appui-react/layout/widget/NavigationArea.tsx +++ b/ui/appui-react/src/appui-react/layout/widget/NavigationArea.tsx @@ -9,13 +9,8 @@ import "./NavigationArea.scss"; import classnames from "classnames"; import * as React from "react"; -import type { CommonProps, NoChildrenProps } from "@itwin/core-react"; -/** Properties of [[NavigationArea]] component. - * @internal - */ -// eslint-disable-next-line @typescript-eslint/no-deprecated -export interface NavigationAreaProps extends CommonProps, NoChildrenProps { +interface NavigationAreaProps { /** * Button displayed between horizontal and vertical toolbars. * I.e. [[AppButton]] in NavigationArea zone or navigation aid control in Navigation zone. @@ -42,11 +37,10 @@ export interface NavigationAreaProps extends CommonProps, NoChildrenProps { export function NavigationArea(props: NavigationAreaProps) { const className = classnames( "nz-widget-navigationArea", - props.hidden && "nz-hidden", - props.className + props.hidden && "nz-hidden" ); return ( -
+
{props.verticalToolbar} -
{props.secondaryVerticalToolbar}
+ {props.secondaryVerticalToolbar}
{props.secondaryHorizontalToolbar && (