diff --git a/Composer/cypress/integration/Breadcrumb.spec.ts b/Composer/cypress/integration/Breadcrumb.spec.ts
index 9f7d373374..a50f8967bd 100644
--- a/Composer/cypress/integration/Breadcrumb.spec.ts
+++ b/Composer/cypress/integration/Breadcrumb.spec.ts
@@ -15,10 +15,11 @@ context('breadcrumb', () => {
function hasBreadcrumbItems(cy: Cypress.cy, items: (string | RegExp)[]) {
cy.get('[data-testid="Breadcrumb"]')
.last()
- .get('li')
- .should(($li) => {
- items.forEach((item, idx) => {
- expect($li.eq(idx)).to.contain(item);
+ .within(() => {
+ cy.get('li').should(($li) => {
+ items.forEach((item, idx) => {
+ expect($li.eq(idx)).to.contain(item);
+ });
});
});
}
diff --git a/Composer/packages/client/__tests__/components/expandableNode.test.tsx b/Composer/packages/client/__tests__/components/expandableNode.test.tsx
index 1fa7739519..6d9388b42d 100644
--- a/Composer/packages/client/__tests__/components/expandableNode.test.tsx
+++ b/Composer/packages/client/__tests__/components/expandableNode.test.tsx
@@ -14,16 +14,17 @@ describe('', () => {
it('closes and opens on click', async () => {
if (component == null) fail();
+
const triangle = await component.findByTestId('summaryTag');
let details = await component.findByTestId('dialog');
- expect(details.attributes.getNamedItem('open')).not.toBeNull();
+ expect(details.childNodes.length).toBe(2); // 1 for the summary itself, 1 for the details
fireEvent.click(triangle);
details = await component.findByTestId('dialog');
- expect(details.attributes.getNamedItem('open')).toBeNull();
+ expect(details.childNodes.length).toBe(1); // when the node is closed, the details don't render at all
fireEvent.click(triangle);
details = await component.findByTestId('dialog');
- expect(details.attributes.getNamedItem('open')).not.toBeNull();
+ expect(details.childNodes.length).toBe(2);
});
});
diff --git a/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx b/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx
index 2fd0b5d1b1..746b53a985 100644
--- a/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ExpandableNode.tsx
@@ -6,8 +6,6 @@ import { jsx, css } from '@emotion/core';
import { useState, MouseEvent, KeyboardEvent } from 'react';
import { NeutralColors } from '@uifabric/fluent-theme';
-import { INDENT_PER_LEVEL } from './constants';
-
type Props = {
children: React.ReactNode;
summary: React.ReactNode;
@@ -18,29 +16,29 @@ type Props = {
isActive?: boolean;
};
-const summaryStyle = (depth: number, isActive: boolean, isOpen: boolean) => css`
- label: summary;
- padding-left: ${depth * INDENT_PER_LEVEL + 12}px;
- padding-top: 6px;
- display: list-item;
+const listItemStyle = (isActive: boolean, isOpen: boolean) => css`
+ label: listItem;
:hover {
background: ${isActive ? NeutralColors.gray40 : NeutralColors.gray20};
}
background: ${isActive ? NeutralColors.gray30 : NeutralColors.white};
${isOpen
- ? `list-style-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' height='10' width='10' viewBox='0 0 16 16'%3E%3Cpath style='fill:black;' d='M 0 8 H 16 L 8 16 L 0 8'/%3E%3C/svg%3E");`
- : `list-style-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' height='10' width='10' viewBox='0 0 16 16'%3E%3Cpath style='fill:black;' d='M 8 0 V 16 L 16 8 L 8 0'/%3E%3C/svg%3E");`}
+ ? `list-style-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' height='16' width='16' viewBox='0 0 24 16'%3E%3Cpath style='fill:black%3B' d='M 8 8 h 16 l -8 8 l -8 -8'/%3E%3C/svg%3E");`
+ : `list-style-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' height='16' width='16' viewBox='0 0 24 16'%3E%3Cpath style='fill:black%3B' d='M 16 0 v 16 l 8 -8 l -8 -8'/%3E%3C/svg%3E");`}
`;
-const nodeStyle = css`
- margin-top: 2px;
+const listStyle = css`
+ label: list;
+ margin-top: 0;
+ margin-left: 16px;
+ padding-inline-start: 6px;
+ margin-block-end: 0px;
`;
export const ExpandableNode = ({
children,
summary,
detailsRef,
- depth = 0,
onToggle,
defaultState = true,
isActive = false,
@@ -53,8 +51,10 @@ export const ExpandableNode = ({
}
function handleClick(ev: MouseEvent) {
- if ((ev.target as Element)?.tagName.toLowerCase() === 'summary') {
+ const target = ev.target as Element;
+ if (target.tagName.toLowerCase() === 'li') {
setExpandedWithCallback(!isExpanded);
+ ev.stopPropagation();
}
ev.preventDefault();
}
@@ -64,25 +64,20 @@ export const ExpandableNode = ({
}
return (
-
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex */}
-
+
{summary}
-
- {children}
-
+
+ {isExpanded &&
{children}
}
+
);
};
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectHeader.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectHeader.tsx
index 2f79907ee4..d49c58067a 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectHeader.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectHeader.tsx
@@ -19,8 +19,6 @@ import { TreeItem } from './treeItem';
import { ProjectTreeOptions, TreeLink } from './types';
const headerCSS = (label: string) => css`
- margin-top: -6px;
- width: 100%;
label: ${label};
`;
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
index 774ce13829..704d8bc94e 100644
--- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx
@@ -30,7 +30,7 @@ import { getBaseName } from '../../utils/fileUtil';
import { TreeItem } from './treeItem';
import { ExpandableNode } from './ExpandableNode';
-import { INDENT_PER_LEVEL, LEVEL_PADDING, TREE_PADDING } from './constants';
+import { INDENT_PER_LEVEL, TREE_PADDING } from './constants';
import { ProjectTreeHeader, ProjectTreeHeaderMenuItem } from './ProjectTreeHeader';
import { isChildTriggerLinkSelected, doesLinkMatch } from './helpers';
import { ProjectHeader } from './ProjectHeader';
@@ -61,7 +61,6 @@ const tree = css`
`;
export const headerCSS = (label: string, isActive?: boolean) => css`
- margin-top: -6px;
width: 100%;
label: ${label};
:hover {
@@ -281,7 +280,6 @@ export const ProjectTree: React.FC = ({
link={dialogLink}
menu={options.showMenu ? menu : options.showQnAMenu ? [QnAMenuItem] : []}
menuOpenCallback={setMenuOpen}
- padLeft={depth * LEVEL_PADDING}
showErrors={false}
textWidth={leftSplitWidth - TREE_PADDING}
onSelect={handleOnSelect}
@@ -292,7 +290,7 @@ export const ProjectTree: React.FC = ({
};
};
- const renderCommonDialogHeader = (skillId: string, depth: number) => {
+ const renderCommonDialogHeader = (skillId: string) => {
const dialogLink: TreeLink = {
dialogId: 'common',
displayName: formatMessage('Common'),
@@ -304,7 +302,7 @@ export const ProjectTree: React.FC = ({
};
return (
-
+
= ({
itemType={'dialog'}
link={dialogLink}
menuOpenCallback={setMenuOpen}
- padLeft={depth * LEVEL_PADDING}
showErrors={false}
textWidth={leftSplitWidth - TREE_PADDING}
onSelect={handleOnSelect}
@@ -330,8 +327,7 @@ export const ProjectTree: React.FC = ({
},
dialog: DialogInfo,
projectId: string,
- dialogLink: TreeLink,
- depth: number
+ dialogLink: TreeLink
): React.ReactNode => {
const link: TreeLink = {
projectId: rootProjectId,
@@ -349,12 +345,11 @@ export const ProjectTree: React.FC = ({
= ({
return scope.toLowerCase().includes(filter.toLowerCase());
};
- const renderTriggerList = (
- triggers: ITrigger[],
- dialog: DialogInfo,
- projectId: string,
- dialogLink: TreeLink,
- depth: number
- ) => {
+ const renderTriggerList = (triggers: ITrigger[], dialog: DialogInfo, projectId: string, dialogLink: TreeLink) => {
return triggers
.filter((tr) => filterMatch(dialog.displayName) || filterMatch(getTriggerName(tr)))
.map((tr) => {
@@ -406,8 +395,7 @@ export const ProjectTree: React.FC = ({
{ ...tr, index, displayName: getTriggerName(tr), warningContent, errorContent },
dialog,
projectId,
- dialogLink,
- depth
+ dialogLink
);
});
};
@@ -464,7 +452,7 @@ export const ProjectTree: React.FC = ({
summary={renderTriggerGroupHeader(groupDisplayName, dialog, projectId)}
onToggle={(newState) => setPageElement(key, newState)}
>
- {renderTriggerList(triggers, dialog, projectId, link, 1)}
+ {renderTriggerList(triggers, dialog, projectId, link)}
);
};
@@ -485,7 +473,7 @@ export const ProjectTree: React.FC = ({
const renderDialogTriggers = (dialog: DialogInfo, projectId: string, startDepth: number, dialogLink: TreeLink) => {
return dialogIsFormDialog(dialog)
? renderDialogTriggersByProperty(dialog, projectId, startDepth + 1)
- : renderTriggerList(dialog.triggers, dialog, projectId, dialogLink, 1);
+ : renderTriggerList(dialog.triggers, dialog, projectId, dialogLink);
};
// flatten lg imports url is same to dialog, to match correct link need render it as dialog
@@ -634,7 +622,7 @@ export const ProjectTree: React.FC = ({
// eventually we will filter on topic trigger phrases
const filteredTopics =
filter == null || filter.length === 0 ? topics : topics.filter((topic) => filterMatch(topic.displayName));
- const commonLink = options.showCommonLinks ? [renderCommonDialogHeader(projectId, 1)] : [];
+ const commonLink = options.showCommonLinks ? [renderCommonDialogHeader(projectId)] : [];
const importedLgLinks = options.showLgImports
? lgImportsList.map((file) => renderLgImportAsDialog(file, projectId))
diff --git a/Composer/packages/client/src/components/ProjectTree/TopicsList.tsx b/Composer/packages/client/src/components/ProjectTree/TopicsList.tsx
index c2c4b26236..f050de5634 100644
--- a/Composer/packages/client/src/components/ProjectTree/TopicsList.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/TopicsList.tsx
@@ -10,7 +10,7 @@ import get from 'lodash/get';
import { ExpandableNode } from './ExpandableNode';
import { TreeItem } from './treeItem';
-import { LEVEL_PADDING, INDENT_PER_LEVEL } from './constants';
+import { INDENT_PER_LEVEL } from './constants';
import { headerCSS } from './ProjectTree';
type TopicsListProps = {
@@ -69,8 +69,10 @@ export const TopicsList: React.FC = ({ topics, onToggle, textWi
link={{
displayName: formatMessage('Power Virtual Agents Topics ({count})', { count: topics.length }),
projectId,
+ isRoot: false,
+ isRemote: false,
+ diagnostics: [],
}}
- padLeft={0 * LEVEL_PADDING}
showErrors={false}
textWidth={textWidth}
/>
diff --git a/Composer/packages/client/src/components/ProjectTree/constants.ts b/Composer/packages/client/src/components/ProjectTree/constants.ts
index 64133fa6c2..77028671af 100644
--- a/Composer/packages/client/src/components/ProjectTree/constants.ts
+++ b/Composer/packages/client/src/components/ProjectTree/constants.ts
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-export const SUMMARY_ARROW_SPACE = 28; // the rough pixel size of the dropdown arrow to the left of a Details/Summary element
export const INDENT_PER_LEVEL = 16;
export const ACTION_ICON_WIDTH = 28;
export const THREE_DOTS_ICON_WIDTH = 28;
diff --git a/Composer/packages/client/src/components/ProjectTree/treeItem.tsx b/Composer/packages/client/src/components/ProjectTree/treeItem.tsx
index 2a2d7051fd..d4a575a8c2 100644
--- a/Composer/packages/client/src/components/ProjectTree/treeItem.tsx
+++ b/Composer/packages/client/src/components/ProjectTree/treeItem.tsx
@@ -21,13 +21,13 @@ import { DiagnosticSeverity, Diagnostic, Icons } from '@bfc/shared';
import isEmpty from 'lodash/isEmpty';
import uniqueId from 'lodash/uniqueId';
-import { SUMMARY_ARROW_SPACE, THREE_DOTS_ICON_WIDTH } from './constants';
+import { THREE_DOTS_ICON_WIDTH } from './constants';
import { TreeLink, TreeMenuItem } from './types';
import { TreeItemContent } from './TreeItemContent';
// -------------------- Styles -------------------- //
-const projectTreeItemContainer = css`
+const projectTreeItemContainer = (extraSpace: number) => css`
outline: none;
:focus {
outline: rgb(102, 102, 102) solid 1px;
@@ -39,6 +39,8 @@ const projectTreeItemContainer = css`
text-align: left;
cursor: pointer;
+ padding-left: ${extraSpace}px;
+
label: ProjectTreeItemContainer;
`;
@@ -94,16 +96,13 @@ const navContainer = (
isActive: boolean,
menuOpenHere: boolean,
textWidth: number,
- isBroken: boolean,
- padLeft: number,
- marginLeft: number
+ isBroken: boolean
) => css`
${isAnyMenuOpen
? ''
: `
&:hover {
- background: ${isActive ? NeutralColors.gray40 : NeutralColors.gray20};
-
+ background: ${isActive ? NeutralColors.gray40 : NeutralColors.gray20};
.dialog-more-btn {
visibility: visible;
}
@@ -125,17 +124,11 @@ const navContainer = (
label: navItem;
- height: 24px;
font-size: 12px;
- padding-left: ${padLeft}px;
- margin-left: ${marginLeft}px;
- min-width: calc(100% - ${padLeft + 24}px);
+ min-width: 100%;
opacity: ${isBroken ? 0.5 : 1};
align-items: center;
- position: relative;
- top: -4px;
-
:hover {
background: ${isActive ? NeutralColors.gray40 : NeutralColors.gray20};
}
@@ -285,7 +278,6 @@ type ITreeItemProps = {
dialogName?: string;
textWidth?: number;
extraSpace?: number;
- padLeft?: number;
marginLeft?: number;
hasChildren?: boolean;
menu?: TreeMenuItem[];
@@ -293,6 +285,8 @@ type ITreeItemProps = {
isMenuOpen?: boolean;
showErrors?: boolean;
role?: string;
+ href?: string;
+ tooltip?: string;
};
const renderTreeMenuItem = (link: TreeLink) => (item: TreeMenuItem) => {
@@ -424,11 +418,7 @@ export const TreeItem: React.FC = ({
dialogName,
onSelect,
textWidth = 100,
- hasChildren = false,
menu = [],
- extraSpace = 0,
- padLeft = 0,
- marginLeft = 0,
menuOpenCallback = () => {},
isMenuOpen = false,
showErrors = true,
@@ -444,7 +434,6 @@ export const TreeItem: React.FC = ({
const linkString = `${link.projectId}_DialogTreeItem${link.dialogId}_${link.trigger ?? ''}`;
const isBroken = !!link.botError;
- const spacerWidth = hasChildren && !isBroken ? 0 : SUMMARY_ARROW_SPACE + extraSpace;
const overflowIconWidthOnHover = overflowMenu.length > 0 ? THREE_DOTS_ICON_WIDTH : 0;
@@ -517,7 +506,7 @@ export const TreeItem: React.FC = ({
);
},
- [textWidth, spacerWidth, extraSpace, overflowIconWidthActiveOrChildSelected, showErrors]
+ [textWidth, overflowIconWidthActiveOrChildSelected, showErrors]
);
const onRenderOverflowButton = useCallback(
@@ -574,15 +563,7 @@ export const TreeItem: React.FC = ({
return (
= ({
: undefined
}
>
-
= ({
]}
overflowItems={overflowMenu}
styles={{ item: { flex: 1 } }}
- onRenderItem={onRenderItem(
- textWidth - spacerWidth + extraSpace - overflowIconWidthActiveOrChildSelected,
- showErrors
- )}
+ onRenderItem={onRenderItem(textWidth, showErrors)}
onRenderOverflowButton={onRenderOverflowButton(
!!isActive,
isChildSelected,