# 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 ### 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" } }); ``` ### Component's Migratoin All northstar components are exported from `@microsoft/modernworkplace-ui-core`, in order to use v9 components without affection production and allowing less invasive migration, it would be ideal to create a new exporting package for v9 such as `@microsoft/modernworkplace-ui-core-v9`. Once we have that set up, we need to use `FluentProvider` so tokens will be available, lets take again `FacePile` as example and see what would need to be done in order to migrate `Avatar` used inside. First we would need to use `FluentProvider` in the following places: **packages\modernworkplace-ui-core\src\testing\renderWithCoreContext.tsx** ```diff= @@ -5,6 +5,7 @@ import { Provider } from "@fluentui/react-northstar"; import { LegacyTranslationsProvider, LocalizersProvider } from "@microsoft/modernworkplace-i18n"; import { TeamsContextProvider } from "@microsoft/modernworkplace-react-hooks"; +import { FluentProvider, teamsLightTheme } from "@microsoft/modernworkplace-ui-core-v9"; import { RenderOptions, RenderResult, render } from "@testing-library/react"; import React from "react"; @@ -33,9 +34,11 @@ export const wrapInProviders = ( return ( <Provider> - <TeamsContextProvider> - <CoreUITranslationsProvider>{contentWithLocalizers}</CoreUITranslationsProvider> - </TeamsContextProvider> + <FluentProvider theme={teamsLightTheme}> + <TeamsContextProvider> + <CoreUITranslationsProvider>{contentWithLocalizers}</CoreUITranslationsProvider> + </TeamsContextProvider> + </FluentProvider> </Provider> ); }; ``` **packages/modernworkplace-ui-core/.storybook/preview.js** ```diff= @@ -1,6 +1,7 @@ import * as React from "react"; import { LocalizersProvider } from "@microsoft/modernworkplace-i18n" +import {FluentProvider, teamsLightTheme} from "@microsoft/modernworkplace-ui-core-v9"; import { TeamsContext, TeamsThemeType, getComposedTeamsTheme, TextDirection } from "@microsoft/modernworkplace-react-hooks"; import { Provider as NorthstarThemeProvider } from "@fluentui/react-northstar"; import { withPerformance } from "storybook-addon-performance"; @@ -42,6 +43,7 @@ const withNecessaryProviders = (Story, context) => { const isRTL = context.globals.direction === TextDirection.RightToLeft; return ( + <FluentProvider theme={teamsLightTheme}> <TeamsContext.Provider value={{ theme, themeType }}> <NorthstarThemeProvider theme={theme} rtl={isRTL}> <LocalizersProvider> @@ -51,6 +53,7 @@ const withNecessaryProviders = (Story, context) => { </LocalizersProvider> </NorthstarThemeProvider> </TeamsContext.Provider> + </FluentProvider> ); } ``` 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 migrations instructions for several components, here we used [Avatar Migrition](https://react.fluentui.dev/?path=/docs/concepts-migration-from-v0-components-avatar-migration--page)