# Vertical Apps - V9 Migration
The purpose of this design doc is to present and describe the different decisions and research required to achieve a smooth migration from Fluent UI Northstar (v0) to Fluent UI v9 in the Teams verticals monorepo.
## Encapsulating v9 usage
The verticals repository has all reusable UI code in the `@microsoft/modernworkplace-ui-core` repo. All northstar exports are done through `@microsoft/modernworkplace-ui-core/src/components/northstar`. Although the deep import is considered an antipattern in the JS ecosystem, the good thing is that this import is used consistently throughout.
We can simply create a new export path `@microsoft/modernworkplace-ui-core/src/components/fluentui` as the primary export path of v9 components and do a gradual migration.
The existence of this export path if no code is used should not impact any of the production code.
## Styling
Currently the packages inside the verticals apps repo are using `mergeStyleSets` from `@uifabric`:
```javascript
import { mergeStyleSets } from "@uifabric/merge-styles";
```
It's convenient, since the syntax is very similar to `@griffel`
All style files are under the pattern `ComponentName.styles.ts`, what can facilitate automation with codemodes.
Let's take the `FacePile` styles for example, currently we have:
```typescript
import { avatarClassName, pxToRem } from "@fluentui/react-northstar";
import { mergeStyleSets } from "@uifabric/merge-styles";
import { ComponentClassNames } from "../../styles/classNames";
type IFacePileClasses = ComponentClassNames<"facePile" | "userName">;
/**
* Get class names.
* @returns Class names.
*/
export const getClassNames = (): IFacePileClasses => {
return mergeStyleSets({
facePile: {
selectors: {
[`.${avatarClassName}`]: {
marginRight: pxToRem(3)
},
[`.${avatarClassName}.disabled`]: {
filter: "grayscale(1)"
}
}
},
userName: {
marginLeft: pxToRem(2),
display: "inline-block",
overflow: "hidden",
textOverflow: "ellipsis"
}
});
};
```
By convernting it to `griffel` it will look like:
```typescript
import { avatarClassName, pxToRem } from "@fluentui/react-northstar";
import { makeStyles, shorthands } from "@griffel/react";
/**
* Get class names.
* @returns Class names.
*/
export const getClassNames = makeStyles({
facePile: {
selectors: {
[`.${avatarClassName}`]: {
...shorthands.margin("0", pxToRem(3), "0", "0")
},
[`.${avatarClassName}.disabled`]: {
filter: "grayscale(1)"
}
}
},
userName: {
...shorthands.margin("0", "0", "0", pxToRem(2)),
...shorthands.overflow("hidden"),
display: "inline-block",
textOverflow: "ellipsis"
}
});
```
### Automated migration
There is already a library that will automatically try to migrate northstar styles to v9 styles: https://github.com/YuanboXue-Amber/style-transform-tool
A demo of this library can be seen: https://yellow-meadow-005156603.1.azurestaticapps.net/
This library will not work out of the box on mergeStyleSets but the expected format is similar so we can reuse some of the code in there to help create tooling that will.
Since the styles are all consistently in `.styles.ts` files, once we have tooling in place to do this automatically, it should be fairly easy to run.
### Potential performance gain
The modern workplace repo currently uses `@uifabric/merge-styles@7.19.1` as the main styling solution of choice. Running some benchmarks that were implemented in https://github.com/microsoft/griffel/pull/252 we can see the following results
> NOTE: That build time optimizations were not applied for Griffel (v9) styling, the values could potentially be further improved by applying build time optimizations


You can see that in the purely runtime scenario Griffel could already be ~30% increase to styling performance.
## Deprecating northstar usage
Northstar will be deprecated since Teams is heavily investing into the migration of northstar to v9 for improved performance and other API related benefits.
We can create an eslint rule in `eslint-plugin-modernworkplace` that will throw an error for any new imports of northstar components to promote new code using v9 components.
## Gradual migration
v9 can can be used side by side with northstar without any problems. If there are any problems discovered with using the two component libraries together, that is a bug that should be reported to the Fluent UI team.
### Components
Below is the usage statistics of northstar component usage at the moment of 11/10/2022
<details>
| Count | Component |
|-------|------------------|
| 149 | Flex |
| 99 | Text |
| 67 | Button |
| 26 | Box |
| 17 | Divider |
| 14 | Input |
| 13 | Dropdown |
| 12 | Header |
| 12 | Avatar |
| 11 | Dialog |
| 10 | Skeleton |
| 10 | FormField |
| 10 | Ref |
| 9 | FlexItem |
| 9 | Checkbox |
| 8 | Provider |
| 8 | MenuButton |
| 8 | Table |
| 8 | Tooltip |
| 6 | TextArea |
| 6 | List |
| 6 | Loader |
| 6 | Popup |
| 5 | FormLabel |
| 5 | FormTextArea |
| 5 | Accordion |
| 4 | ListItem |
| 4 | Label |
| 4 | Image |
| 3 | Datepicker |
| 2 | Breadcrumb |
| 2 | Pill |
| 2 | FormInput |
| 1 | Grid |
| 1 | Segment |
| 1 | FormDatepicker |
| 1 | Alert |
| 1 | SplitButton |
| 1 | Portal |
| 1 | Status |
| 1 | RadioGroup |
| 1 | Toolbar |
</details>
It's quite clear the the most often used components are simpler layout components. The top three components add up in usages more than all other compnents combined:
* Flex
* Text
* Button
For simpler components, (especially the most used components as they are quite simple) we should look into the possibility of creating codemods for automatic migration to avoid as much developer manual work as possible.
### Icons
Below is a snapshot of all icons used in the MWT repo as of 11/10/2022
<details>
| Current Icon | V9 Icon Equivalent |
|-------------------------|--------------------|
| CloseIcon | |
| CalendarIcon | |
| AcceptIcon | |
| ChevronDownIcon | |
| ParticipantAddIcon | |
| AddIcon | |
| SearchIcon | |
| LocationIcon | |
| CheckmarkCircleIcon | |
| CircleIcon | |
| ChevronStartIcon | |
| RedbangIcon | |
| ExclamationCircleIcon | |
| EditIcon | |
| MoreIcon | |
| ArrowDownIcon | |
| FilesEmptyIcon | |
| TrashCanIcon | |
| InfoIcon | |
| OpenOutsideIcon | |
| ArrowRightIcon | |
| ChevronEndIcon | |
| ErrorIcon | |
| ArchiveIcon | |
| LinkIcon | |
| SettingsIcon | |
| ChevronEndMediumIcon | |
| UserFriendsIcon | |
| ArrowUpIcon | |
| ApprovalsAppbarIcon | |
| TeamCreateIcon | |
| CalendarAgendaIcon | |
| QuestionCircleIcon | |
| BookmarkIcon | |
| FormatIcon | |
| FilesUploadIcon | |
| OneDriveIcon | |
| ThumbtackIcon | |
| ThumbtackSlashIcon | |
| UndoIcon | |
| ExcelColorIcon | |
| FilesCodeIcon | |
| FilesHtmlColoredIcon | |
| FilesPdfColoredIcon | |
| FilesPictureColoredIcon | |
| FilesSoundIcon | |
| FilesTextColoredIcon | |
| FilesVideoIcon | |
| FilesZipIcon | |
| FluidFileIcon | |
| OneNoteColorIcon | |
| PowerPointColorIcon | |
| VisioColorIcon | |
| WordColorIcon | |
| SyncIcon | |
| TabsIcon | |
| ExclamationTriangleIcon | |
| UrgentIcon | |
| AttachmentIcon | |
| RetryIcon | |
| FilterIcon | |
| ShiftActivityIcon | |
| WorkOrSchoolIcon | |
</details>
We should finish the above mapping of V9 icons to northstar icons.
Icon migration can be done separately to any components and the intended design of V9 icons is that they can be used with northstar components.
We should also look into codemods for icon migration once we have a complete mapping.
A catalog of v9 icons is available https://react.fluentui.dev/?path=/docs/concepts-developer-icons-icons-catalog--page
## Component Migration
We can assume that we have the encapsulation of v9 setup described earlier.
### Extending TeamsContextProvider
The `TeamsContextProvider` componnet is responsible for applying all providers that is used by modern workplace controls. In this way, we can simply add the `FluentProvider` to the main provider. This would incur some minor bundlesize, but would not impact any northstar code and enables the use of v9 components in the entire repo.
**packages\modernworkplace-react-hooks\src\useTeamsContext\TeamsContextProvider.tsx**
```diff=
import { Provider as NorthstarThemeProvider, RendererContext } from "@fluentui/react-northstar";
+import { FluentProvider, teamsLightTheme } from "@microsoft/modernworkplace-ui-core-v9";
import { createEmotionRenderer } from "@fluentui/react-northstar-emotion-renderer";
import React from "react";
import { TextDirection, useTextDirection } from "../useTextDirection";
import { getClassNames } from "./TeamsContextProvider.styles";
import { TeamsThemeType, getComposedTeamsTheme } from "./getComposedTeamsTheme";
import { TeamsContext } from "./useTeamsContext";
import { useTeamsTheme } from "./useTeamsTheme";
/**
* Component that connects to Teams and fetches its context.
* @param props Children.
* @param props.children Children of this component.
* @returns Child component wrapped in Teams Context Provider.
*/
export const TeamsContextProvider: React.FC = props => {
- /* Other code removed for simplicity */
return (
<RendererContext.Provider value={createEmotionRenderer()}>
<TeamsContext.Provider value={{ theme: themeWithoutStaticStyles, themeType: theme as TeamsThemeType }}>
+ {/* We can also map v9 themes to current themes */}
+ <FluentProvider theme={teamsLightTheme}>
<NorthstarThemeProvider
className={containerStyles}
theme={themeWithoutStaticStyles}
rtl={direction === TextDirection.RightToLeft}>
{props.children}
</NorthstarThemeProvider>
+ </FluentProvider>
</TeamsContext.Provider>
</RendererContext.Provider>
);
};
```
### Example
Here is an example of a more complex migration.
lets take `FacePile` as example and see what would need to be done in order to migrate the `Avatar` Component used inside.
First we would need to use `FluentProvider` in the following places:
So the replacement in **packages\modernworkplace-ui-core\src\components\facePile\FacePile.tsx** is simple enough as:
```diff=
-import { Avatar, Flex, Text } from "@fluentui/react-northstar";
+import { Avatar, AvatarSizes } from "@fluentui/react-components";
+import { Flex, SizeValue, Text } from "@fluentui/react-northstar";
import { useTeamsContext } from "@microsoft/modernworkplace-react-hooks";
import * as React from "react";
@@ -14,6 +15,16 @@ import { IFacePile } from "./IFacePile";
// Default, maximum number of users to display.
const defaultVisibleMax = 11;
+const sizesMap: Record<SizeValue, AvatarSizes> = {
+ smallest: 20,
+ smaller: 24,
+ small: 28,
+ medium: 32,
+ large: 64,
+ larger: 64,
+ largest: 96
+};
+
/**
* Renders face pile component.
* @param props Component properties.
@@ -27,7 +38,7 @@ export const FacePile: React.FC<IFacePile> = props => {
const classes = getClassNames();
const maxVisible = props.maxNumberOfVisibleAvatars ?? defaultVisibleMax;
const overflow = props.users.length > maxVisible;
- const overflowLabel = (name: string) => `+${props.users.length - maxVisible}`;
+ const overflowLabel = `+${props.users.length - maxVisible}`;
const avatarClass = props.disabled ? "disabled" : "";
const size = props.size ?? "medium";
@@ -39,11 +50,9 @@ export const FacePile: React.FC<IFacePile> = props => {
return (
<Flex vAlign="center" className={classes.facePile}>
{props.users.slice(0, maxVisible).map((user, idx) => {
- return (
- <Avatar className={avatarClass} key={`${idx}-${user.id}`} name={user.displayName} image={user.imageUrl} size={size} />
- );
+ return <Avatar key={`${idx}-${user.id}`} name={user.displayName} image={{ src: user.imageUrl }} size={sizesMap[size]} />;
})}
- {overflow && <Avatar getInitials={overflowLabel} size={size} className={avatarClass} />}
+ {overflow && <Avatar initials={overflowLabel} size={sizesMap[size]} className={avatarClass} />}
{props.users.length === 1 && (
<Text
variables={textVariables}
```
One final touch, we need one more update in the styles since we are not exporting `avatarClassName` in v9 but `avatarClassNames` which is an object with class names for each slots. So the final change in the `styles` file would be:
```diff=
- import { avatarClassName } from "@fluentui/react-northstar";
+ import { avatarClassNames } from "@microsoft/modernworkplace-ui-core-v9";
- [`.${avatarClassName}`]: {
+ [`.${avatarClassNames.root}`]: {
```
We have migration instructions for several components, here we used [Avatar Migration](https://react.fluentui.dev/?path=/docs/concepts-migration-from-v0-components-avatar-migration--page)
### Accessibility
Check the [accessibility migration doc](https://react.fluentui.dev/?path=/docs/concepts-migration-from-v0-custom-accessibility--page) where it exaplains how to migrate the 4 possible properties that an accessibility behavior has. For attributes we should add them directly to the component.
Let's take **packages\modernworkplace-ui-members\src\Members.tsx** as example, where it's using `gridBehavior`:
```diff=
+import { useTabsterAttributes } from "@fluentui/react-tabster";
export const Members: React.FC<IMembersProps> = props => {
const { members } = useTranslations();
+ const arrowKeyNavigationAttributes = useTabsterAttributes({
+ mover: {
+ direction: 3 // grid
+ }
+ });
+
<Table
- accessibility={gridNestedBehavior}
+ {...arrowKeyNavigationAttributes}
```
Here we are replacing the focus zone property by using `react-tabster`
If we had a custom behavior such has:
```javascript=
const customBehavior = (props) => {
attributes: {
role="dialog",
disabled: props.disabled
}
}
<Component accessibility={customBehavior} />
```
That could be replaced by:
```jsx=
<Component role="dialog" disabled={props.disabled} />
```
## Validation
### Visual Regression
There is no visual regression testing in the modern workplace repo, therefore any visual regression will have to be done manually during migration. However what level of visual regression allowed should be discussed with designers since v9 is using the Fluent language which is intended to be the future design of all M365 experiences
### Unit tests
All unit tests that break as a result of migration should be investigated and fixed.
### Performance
There are performance tests setup for the `LoanManager` scenario. In order have an accurate perf tests between northstar and v0 we should try to quickly migrate `LoanManager` related controls to v9 and run the existing performance tests
### Bundle size
Bundle size is measured by looking at the final size of the js bundle that is built with the production webpack config. We can measure the bundle size of just integrating the FluentProvider at the start to see the initial impact and keep measuring as components as migrated