diff --git a/Breadcrumb/Breadcrumb/components/CanvasBreadcrumb.tsx b/Breadcrumb/Breadcrumb/components/CanvasBreadcrumb.tsx
index d86a6448..ed0d568f 100644
--- a/Breadcrumb/Breadcrumb/components/CanvasBreadcrumb.tsx
+++ b/Breadcrumb/Breadcrumb/components/CanvasBreadcrumb.tsx
@@ -51,6 +51,10 @@ export const CanvasBreadcrumb = React.memo((props: IBreadcrumbProps): React.Reac
const breadcrumbItems: IBreadcrumbItem[] = getBreadcrumbItems(items, onClick);
+ // onReduceData is required to prevent breadcrumb from shrinking
+ // when item changes dynamically
+ const returnUndefined = () => undefined;
+
return (
);
diff --git a/Breadcrumb/package-lock.json b/Breadcrumb/package-lock.json
index e1955369..3c81301b 100644
--- a/Breadcrumb/package-lock.json
+++ b/Breadcrumb/package-lock.json
@@ -9,7 +9,7 @@
"version": "1.0.0",
"dependencies": {
"@fluentui/react": "8.29.0",
- "@fluentui/react-hooks": "^8.5.4",
+ "@fluentui/react-hooks": "8.5.4",
"react": "16.8.6",
"react-dom": "16.8.6"
},
diff --git a/Breadcrumb/package.json b/Breadcrumb/package.json
index c7f9d8a9..220f63d3 100644
--- a/Breadcrumb/package.json
+++ b/Breadcrumb/package.json
@@ -14,7 +14,7 @@
},
"dependencies": {
"@fluentui/react": "8.29.0",
- "@fluentui/react-hooks": "^8.5.4",
+ "@fluentui/react-hooks": "8.5.4",
"react": "16.8.6",
"react-dom": "16.8.6"
},
diff --git a/DetailsList/DetailsList/index.ts b/DetailsList/DetailsList/index.ts
index f0e78a48..e254181a 100644
--- a/DetailsList/DetailsList/index.ts
+++ b/DetailsList/DetailsList/index.ts
@@ -12,6 +12,7 @@ const SelectionTypes: Record<'0' | '1' | '2', SelectionMode> = {
};
export class FluentDetailsList implements ComponentFramework.ReactControl {
+ private static readonly COLUMN_LIMIT: number = 125;
notifyOutputChanged: () => void;
container: HTMLDivElement;
context: ComponentFramework.Context;
@@ -51,6 +52,12 @@ export class FluentDetailsList implements ComponentFramework.ReactControl): React.ReactElement {
@@ -167,12 +174,12 @@ export class FluentDetailsList implements ComponentFramework.ReactControl
-
+
@@ -14,6 +14,8 @@
WizardComplete
Error
+
+
@@ -26,8 +28,8 @@
-
-
+
+
diff --git a/SubwayNav/SubwayNav/__mocks__/mock-parameters.ts b/SubwayNav/SubwayNav/__mocks__/mock-parameters.ts
index 266405e2..c2c77a6b 100644
--- a/SubwayNav/SubwayNav/__mocks__/mock-parameters.ts
+++ b/SubwayNav/SubwayNav/__mocks__/mock-parameters.ts
@@ -13,5 +13,6 @@ export function getMockParameters(): IInputs {
ApplyDarkTheme: new MockTwoOptionsProperty(),
WizardCompleteorError: new MockEnumProperty(),
StepsSchema: new MockStringProperty(),
+ ShowAnimation: new MockTwoOptionsProperty(),
};
}
diff --git a/SubwayNav/SubwayNav/_test_/__snapshots__/subwaynav-lifecycle.test.ts.snap b/SubwayNav/SubwayNav/_test_/__snapshots__/subwaynav-lifecycle.test.ts.snap
index e8f41fba..2d5d906a 100644
--- a/SubwayNav/SubwayNav/_test_/__snapshots__/subwaynav-lifecycle.test.ts.snap
+++ b/SubwayNav/SubwayNav/_test_/__snapshots__/subwaynav-lifecycle.test.ts.snap
@@ -138,6 +138,7 @@ exports[`SubwayNav renders 1`] = `
}
onSelected={[Function]}
setFocus=""
+ showAnimation={true}
tabIndex={0}
themeJSON="{\\"palette\\":{\\"themePrimary\\":\\"#005ba1\\",\\"themeLighterAlt\\":\\"#f1f7fb\\",\\"themeLighter\\":\\"#cadff0\\",\\"themeLight\\":\\"#9fc5e3\\",\\"themeTertiary\\":\\"#4f93c6\\",\\"themeSecondary\\":\\"#156aac\\",\\"themeDarkAlt\\":\\"#005291\\",\\"themeDark\\":\\"#00457a\\",\\"themeDarker\\":\\"#00335a\\",\\"neutralLighterAlt\\":\\"#faf9f8\\",\\"neutralLighter\\":\\"#f3f2f1\\",\\"neutralLight\\":\\"#edebe9\\",\\"neutralQuaternaryAlt\\":\\"#e1dfdd\\",\\"neutralQuaternary\\":\\"#d0d0d0\\",\\"neutralTertiaryAlt\\":\\"#c8c6c4\\",\\"neutralTertiary\\":\\"#a19f9d\\",\\"neutralSecondary\\":\\"#605e5c\\",\\"neutralPrimaryAlt\\":\\"#3b3a39\\",\\"neutralPrimary\\":\\"#323130\\",\\"neutralDark\\":\\"#201f1e\\",\\"black\\":\\"#000000\\",\\"white\\":\\"#ffffff\\"}}"
width={-1}
@@ -184,6 +185,7 @@ exports[`SubwayNav renders dummy items when no items configured 1`] = `
}
onSelected={[Function]}
setFocus=""
+ showAnimation={true}
tabIndex={0}
themeJSON="{\\"palette\\":{\\"themePrimary\\":\\"#005ba1\\",\\"themeLighterAlt\\":\\"#f1f7fb\\",\\"themeLighter\\":\\"#cadff0\\",\\"themeLight\\":\\"#9fc5e3\\",\\"themeTertiary\\":\\"#4f93c6\\",\\"themeSecondary\\":\\"#156aac\\",\\"themeDarkAlt\\":\\"#005291\\",\\"themeDark\\":\\"#00457a\\",\\"themeDarker\\":\\"#00335a\\",\\"neutralLighterAlt\\":\\"#faf9f8\\",\\"neutralLighter\\":\\"#f3f2f1\\",\\"neutralLight\\":\\"#edebe9\\",\\"neutralQuaternaryAlt\\":\\"#e1dfdd\\",\\"neutralQuaternary\\":\\"#d0d0d0\\",\\"neutralTertiaryAlt\\":\\"#c8c6c4\\",\\"neutralTertiary\\":\\"#a19f9d\\",\\"neutralSecondary\\":\\"#605e5c\\",\\"neutralPrimaryAlt\\":\\"#3b3a39\\",\\"neutralPrimary\\":\\"#323130\\",\\"neutralDark\\":\\"#201f1e\\",\\"black\\":\\"#000000\\",\\"white\\":\\"#ffffff\\"}}"
width={-1}
diff --git a/SubwayNav/SubwayNav/_test_/subwaynav-lifecycle.test.ts b/SubwayNav/SubwayNav/_test_/subwaynav-lifecycle.test.ts
index 5f9a10b8..b347f3ad 100644
--- a/SubwayNav/SubwayNav/_test_/subwaynav-lifecycle.test.ts
+++ b/SubwayNav/SubwayNav/_test_/subwaynav-lifecycle.test.ts
@@ -101,6 +101,7 @@ function createComponent() {
const component = new SubwayNav();
const notifyOutputChanged = jest.fn();
const context = new MockContext(getMockParameters());
+ context.parameters.ShowAnimation.raw = true;
context.parameters.items = new MockDataSet([
new MockEntityRecord('1', {
[ItemColumns.Key]: 'Item 1',
diff --git a/SubwayNav/SubwayNav/components/CanvasSubwayNav.tsx b/SubwayNav/SubwayNav/components/CanvasSubwayNav.tsx
index 1df26db6..1000a313 100644
--- a/SubwayNav/SubwayNav/components/CanvasSubwayNav.tsx
+++ b/SubwayNav/SubwayNav/components/CanvasSubwayNav.tsx
@@ -1,16 +1,19 @@
import * as React from 'react';
import { useState } from 'react';
-import { ThemeProvider, createTheme, ITheme, IFocusZoneProps, mergeStyles } from '@fluentui/react';
+import { ThemeProvider, createTheme, ITheme, IFocusZoneProps } from '@fluentui/react';
import { ISubwayNavNodeProps, SubwayNavNodeState } from '../utilities/subway-nav/subway-node.types';
import { goToStepById, completeAllSteps, errorAllSteps } from '../utilities/utilities';
-import { SubwayNav as CustomSubwayNav } from '../utilities/subway-nav/subway-nav';
+import {
+ SubwayNav as CustomSubwayNav,
+ SubwayNavNoAnimation as CustomSubwayNavNoAnimation,
+} from '../utilities/subway-nav/subway-nav';
import { ISubNavItem, ISubNavProps } from './components.types';
import { M365Styles, IM365ExtendedSemanticColors } from '../utilities/customizations/src';
import { useAsync, usePrevious } from '@fluentui/react-hooks';
import { getSubwayNavNodeState } from './DatasetMapping';
import { PPACActualLightTheme, PPACActualDarkTheme } from '../utilities/themes';
-import { subwayNavWidth, wizardContentMaxWidth } from '../utilities/wizard';
+import { ISubwayNavProps } from '../utilities/subway-nav/subway-nav.types';
// reference : https://admincontrolsdemoapps.blob.core.windows.net/demo-app/latest/DemoApp/index.html#/examples/subwaynav
@@ -27,7 +30,17 @@ const ariaLabelStrings = {
};
export const CanvasSubwayNav = React.memo((props: ISubNavProps): React.ReactElement => {
- const { items, themeJSON, onSelected, setFocus, applyDarkTheme, tabIndex, disabled, wizardComplete } = props;
+ const {
+ items,
+ themeJSON,
+ onSelected,
+ setFocus,
+ applyDarkTheme,
+ tabIndex,
+ disabled,
+ wizardComplete,
+ showAnimation,
+ } = props;
const [isNavCompleteOrError, setIsNavCompleteOrError] = useState(false);
let allSteps: ISubwayNavNodeProps[];
const prevItems = usePrevious(items);
@@ -51,10 +64,10 @@ export const CanvasSubwayNav = React.memo((props: ISubNavProps): React.ReactElem
}
return themeJSON
? createTheme({
- palette: { ...JSON.parse(themeJSON).palette },
- semanticColors: semanticColorsCopy,
- components: M365Styles,
- })
+ palette: { ...JSON.parse(themeJSON).palette },
+ semanticColors: semanticColorsCopy,
+ components: M365Styles,
+ })
: copyofM365Theme;
} catch (ex) {
/* istanbul ignore next */
@@ -99,6 +112,7 @@ export const CanvasSubwayNav = React.memo((props: ISubNavProps): React.ReactElem
disabled: item.disabled ?? false,
parentId: item.parentId,
onClickStep,
+ index: 10,
isVisuallyDisabled: item.visuallyDisabled ?? false,
};
});
@@ -114,6 +128,7 @@ export const CanvasSubwayNav = React.memo((props: ISubNavProps): React.ReactElem
...(subSteps.length > 0 && { subSteps: subSteps }),
disabled: group.disabled ?? false,
onClickStep,
+ index: 10,
isVisuallyDisabled: group.visuallyDisabled ?? false,
};
}) as unknown as ISubwayNavNodeProps[];
@@ -149,17 +164,23 @@ export const CanvasSubwayNav = React.memo((props: ISubNavProps): React.ReactElem
}
}, [wizardComplete, steps, items, getSteps, handleClickStep, prevItems, prevNavState]);
+ const subwaynavProps = {
+ disabled: disabled,
+ focusZoneProps: focusZoneProps,
+ stateAriaLabels: ariaLabelStrings,
+ steps: steps,
+ // This is required to override width styles in custom pages
+ styles: { root: { width: props.width } },
+ ...(wizardComplete !== 'None' && { wizardComplete: isNavCompleteOrError }),
+ } as ISubwayNavProps;
+
return (
-
+ {showAnimation ? (
+
+ ) : (
+
+ )}
);
});
diff --git a/SubwayNav/SubwayNav/components/components.types.ts b/SubwayNav/SubwayNav/components/components.types.ts
index d5493e12..a42efa5e 100644
--- a/SubwayNav/SubwayNav/components/components.types.ts
+++ b/SubwayNav/SubwayNav/components/components.types.ts
@@ -37,4 +37,5 @@ export interface ISubNavProps {
applyDarkTheme?: boolean;
disabled: boolean;
wizardComplete: string;
+ showAnimation?: boolean;
}
diff --git a/SubwayNav/SubwayNav/index.ts b/SubwayNav/SubwayNav/index.ts
index cc48b00a..45226f64 100644
--- a/SubwayNav/SubwayNav/index.ts
+++ b/SubwayNav/SubwayNav/index.ts
@@ -67,6 +67,7 @@ export class SubwayNav implements ComponentFramework.ReactControl
SubwayNav state
+
+ Show animation
+
\ No newline at end of file
diff --git a/SubwayNav/SubwayNav/utilities/README.md b/SubwayNav/SubwayNav/utilities/README.md
new file mode 100644
index 00000000..29fe7ffd
--- /dev/null
+++ b/SubwayNav/SubwayNav/utilities/README.md
@@ -0,0 +1,17 @@
+# Subway Nav new property - Show Animation
+
+Subwaynav is primarily used in wizard based experience where user navigates through different steps of a process.
+While the implementation of Subwaynav is fairly straight forward when there is a canvas app/ custom page with one screen, the appearance of it varies when used in different screen for each step, because of an animation it brings along with it during intial load or whenever the item collection is changed.
+
+Inorder to disable the animation, a new property - Show animation is included, to give app makers an option to enable or disable the animation effect based on the requirement.
+
+## Related changes are performed in file following files under this utility folder.
+
+- subway-nav.types.ts
+- subway-nav.tsx
+- subway-nav-types.ts
+- subway-nav-no-animation.styles.tsx
+
+## Clip explaining purpose of this change
+
+
\ No newline at end of file
diff --git a/SubwayNav/SubwayNav/utilities/subway-nav/subway-nav-no-animation.styles.tsx b/SubwayNav/SubwayNav/utilities/subway-nav/subway-nav-no-animation.styles.tsx
new file mode 100644
index 00000000..5f6b7d10
--- /dev/null
+++ b/SubwayNav/SubwayNav/utilities/subway-nav/subway-nav-no-animation.styles.tsx
@@ -0,0 +1,175 @@
+/* eslint-disable */
+/* istanbul ignore file */
+// # FEATURE - Disable animation: Related changes are performed in this file
+// any changes related to this feature will be prefixed with => // # FEATURE - Disable animation
+// since this is not available OOTB in subwaynav of admin controls, hence it is driven by showAnimation property
+// Note: This file is a copy of subway-nav.styles.tsx
+import type { IStyle } from '@fluentui/react';
+import { isIE11, keyframes } from '@fluentui/react';
+import { throwOnUndefinedColor } from '../customizations/src/index';
+
+import type { ISubwayNavNodeProps } from './subway-node.types';
+import { SubwayNavNodeState } from './wizard.types';
+import type {
+ ISubwayNavItemStyleProps,
+ ISubwayNavItemStyles,
+ ISubwayNavStyleProps,
+ ISubwayNavStyles,
+} from './subway-nav.types';
+
+const nolineFadeIn = keyframes({
+ from: {
+ opacity: '1.0',
+ },
+ to: {
+ opacity: '1.0',
+ },
+});
+
+const nofadeIn = keyframes({
+ from: {
+ opacity: '1.0',
+ visibility: 'visible',
+ },
+ to: {
+ opacity: '1.0',
+ },
+});
+
+export const getSubwayNavNoAnimationStyles = (props: ISubwayNavStyleProps): ISubwayNavStyles => {
+ const { steps, disabled, wizardComplete, theme } = props;
+ let stepIndex = 0;
+ let totalStepsVisible = steps.length;
+
+ function getSelectedStep(nodeSteps: ISubwayNavNodeProps[], isSubSteps: boolean): void {
+ nodeSteps.some((step: ISubwayNavNodeProps, index: number) => {
+ if (
+ step.state === SubwayNavNodeState.Current ||
+ step.state === SubwayNavNodeState.CurrentWithSubSteps
+ ) {
+ stepIndex += index + (isSubSteps ? 1 : 0);
+
+ if (step.subSteps) {
+ totalStepsVisible += step.subSteps.length;
+ getSelectedStep(step.subSteps, true);
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ });
+ }
+
+ getSelectedStep(steps, false);
+ const stepCompletedColor = throwOnUndefinedColor(
+ theme.semanticColors.stepCompleted,
+ 'stepCompleted',
+ 'SubwayNav',
+ );
+ const wizardCompletedColor = throwOnUndefinedColor(
+ theme.semanticColors.allStepsComplete,
+ 'allStepsComplete',
+ 'SubwayNav',
+ );
+ const notStartedColor = throwOnUndefinedColor(
+ theme.semanticColors.stepNotStarted,
+ 'stepNotStarted',
+ 'SubwayNav',
+ );
+
+ // TODO: #1869
+ // we have to cast to IStyle because -ms-grid isn't part of the display typedef
+ const displayStyle = { display: isIE11() ? '-ms-grid' : 'grid' } as IStyle;
+
+ return {
+ root: [
+ displayStyle,
+ {
+ // @ts-ignore Fabric doesn't support the MS specific grid rules
+ MsGridColumns: '7px 2px minmax(0, 1fr)',
+ gridTemplateColumns: '7px 2px minmax(0, 1fr)',
+ listStyle: 'none',
+ margin: 0,
+ padding: 0,
+ position: 'relative',
+ selectors: {
+ ':before': {
+ content: '\'\'',
+ display: 'block',
+ // @ts-ignore Fabric doesn't support the MS specific grid rules
+ MsGridColumn: '2',
+ gridColumn: '2',
+ // @ts-ignore Fabric doesn't support the MS specific grid rules
+ MsGridRow: '1',
+ // @ts-ignore Fabric doesn't support the MS specific grid rules
+ MsGridRowSpan: `${totalStepsVisible - 1}`,
+ gridRow: `1 / ${totalStepsVisible}`,
+ backgroundColor: wizardComplete ? wizardCompletedColor : notStartedColor,
+ // # FEATURE - Disable animation
+ animationName: nolineFadeIn,
+ // # FEATURE - Disable animation
+ animationDuration: '0s',
+ position: 'static',
+ opacity: disabled ? 0.5 : 1,
+ },
+ ':after': {
+ content: '\'\'',
+ display: stepIndex === 0 || wizardComplete ? 'none' : 'block',
+ // @ts-ignore Fabric doesn't support the MS specific grid rules
+ MsGridColumn: '2',
+ gridColumn: '2',
+ // @ts-ignore Fabric doesn't support the MS specific grid rules
+ MsGridRow: '1',
+ // @ts-ignore Fabric doesn't support the MS specific grid rules
+ MsGridRowSpan: `${stepIndex}`,
+ gridRow: `1 / ${stepIndex + 1}`,
+ backgroundColor: stepCompletedColor,
+ position: 'static',
+ },
+ '& > :not([data-substep="true"])': {
+ marginBottom: '36px',
+ },
+ '& > [data-substep="true"]:nth-child(n)': {
+ marginBottom: '27px',
+ },
+ '& > [data-substep="true"]:nth-child(n) + :not([data-substep="true"]):not(:last-child)':
+ {
+ marginTop: '12px',
+ marginBottom: '36px',
+ },
+ '& > :last-child, & > [data-substep="true"]:last-child': {
+ marginBottom: '0px',
+ },
+ },
+ },
+ ],
+ subComponentStyles: {
+ item: (itemProps: ISubwayNavItemStyleProps): ISubwayNavItemStyles => {
+ const { index } = itemProps;
+
+ return {
+ root: {
+ listStyle: 'none',
+ opacity: 0,
+ // # FEATURE - Disable animation
+ animationName: nofadeIn,
+ // # FEATURE - Disable animation
+ animationDuration: '0s',
+ animationFillMode: 'forwards',
+ animationTimingFunction: 'cubic-bezier(.33, 0, .60, 1)',
+ // @ts-ignore Fabric doesn't support the MS specific grid rules
+ '-ms-grid-column': `${1}`,
+ // @ts-ignore Fabric doesn't support the MS specific grid rules
+ '-ms-grid-column-span': `${3}`,
+ gridColumn: '1 / 4',
+ // @ts-ignore Fabric doesn't support the MS specific grid rules
+ '-ms-grid-row': `${index + 1}`,
+ gridRow: `${index + 1}`,
+ animationDelay: `0s`,
+ },
+ };
+ },
+ },
+ };
+};
diff --git a/SubwayNav/SubwayNav/utilities/subway-nav/subway-nav.tsx b/SubwayNav/SubwayNav/utilities/subway-nav/subway-nav.tsx
index f28f39d0..2f0bb3ba 100644
--- a/SubwayNav/SubwayNav/utilities/subway-nav/subway-nav.tsx
+++ b/SubwayNav/SubwayNav/utilities/subway-nav/subway-nav.tsx
@@ -5,6 +5,7 @@ import type { FC } from 'react';
import { SubwayNavBase } from './subway-nav.base';
import { getSubwayNavStyles } from './subway-nav.styles';
+import { getSubwayNavNoAnimationStyles } from './subway-nav-no-animation.styles';
import type {
ISubwayNavProps,
ISubwayNavStyleProps,
@@ -16,3 +17,9 @@ export const SubwayNav: FC = styled<
ISubwayNavStyleProps,
ISubwayNavStyles
>(SubwayNavBase, getSubwayNavStyles, undefined, { scope: 'SubwayNav' });
+
+export const SubwayNavNoAnimation: FC = styled<
+ ISubwayNavProps,
+ ISubwayNavStyleProps,
+ ISubwayNavStyles
+>(SubwayNavBase, getSubwayNavNoAnimationStyles, undefined, { scope: 'SubwayNav' });
diff --git a/SubwayNav/SubwayNav/utilities/subway-nav/subway-nav.types.ts b/SubwayNav/SubwayNav/utilities/subway-nav/subway-nav.types.ts
index aeab8c4e..d9e8b92e 100644
--- a/SubwayNav/SubwayNav/utilities/subway-nav/subway-nav.types.ts
+++ b/SubwayNav/SubwayNav/utilities/subway-nav/subway-nav.types.ts
@@ -48,6 +48,11 @@ export interface ISubwayNavProps {
* Theme provided by higher order component
*/
theme?: IM365Theme;
+ // # FEATURE - Disable animation
+ /**
+ * To show or hide initial animation effect
+ */
+ showAnimation?: boolean;
}
export interface ISubwayNavStyles {
diff --git a/SubwayNav/media/SubwayNav-Animation.gif b/SubwayNav/media/SubwayNav-Animation.gif
new file mode 100644
index 00000000..650bb455
Binary files /dev/null and b/SubwayNav/media/SubwayNav-Animation.gif differ