Introduction
Hi friends, this is part 2 where I will explain the usage of the Variant Theme Provider control. We will how we can leverage this control on our SPFx project and make use of the features provided by this control. Thanks to the community.
About the Control
This control is a superset of the Theme Provider control which allows us to pass the FluentUI theme, variants and also the custom theme colors. It also allows us to not only themes but also provides us different variants which can cause the change to all the controls in the web part.
Focus on the Code
You can refer Part 1 post on the creation of the SPFx project and installing all the dependencies.
Let us see the file webpart.ts
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { IReadonlyTheme } from '@microsoft/sp-component-base';
import * as strings from 'VariantThemeWebPartStrings';
import VariantTheme from './components/VariantTheme';
import { IVariantThemeProps } from './components/IVariantThemeProps';
export interface IVariantThemeWebPartProps {
description: string;
}
export default class VariantThemeWebPart extends BaseClientSideWebPart<IVariantThemeWebPartProps> {
private _isDarkTheme: boolean = false;
private _environmentMessage: string = '';
protected themeVariant: IReadonlyTheme;
protected onInit(): Promise<void> {
this._environmentMessage = this._getEnvironmentMessage();
return super.onInit();
}
public render(): void {
const element: React.ReactElement<IVariantThemeProps> = React.createElement(
VariantTheme,
{
description: this.properties.description,
isDarkTheme: this._isDarkTheme,
environmentMessage: this._environmentMessage,
hasTeamsContext: !!this.context.sdks.microsoftTeams,
userDisplayName: this.context.pageContext.user.displayName,
theme: this.themeVariant
}
);
ReactDom.render(element, this.domElement);
}
private _getEnvironmentMessage(): string {
if (!!this.context.sdks.microsoftTeams) { // running in Teams
return this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
}
return this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment;
}
protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
if (!currentTheme) {
return;
}
this.themeVariant = currentTheme;
this._isDarkTheme = !!currentTheme.isInverted;
const {
semanticColors
} = currentTheme;
this.domElement.style.setProperty('--bodyText', semanticColors.bodyText);
this.domElement.style.setProperty('--link', semanticColors.link);
this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}
Below are the changes that are done to the default file
- Created a private variable for themeVariant so that we could pass it to the component and use it in the control. Since the props has to be passed to the component, we need to create a new props variable in the webpart props file.
- Since from SPFx version 1.14.0, there is an enhancement to the themeChanged event, we can use the built-in method and assign our variable with the current theme.
Let us the file webpart.tsx
import * as React from 'react';
import styles from './VariantTheme.module.scss';
import { IVariantThemeProps } from './IVariantThemeProps';
import { VariantThemeProvider, VariantType } from "@pnp/spfx-controls-react/lib/VariantThemeProvider";
import { ITheme, Separator } from 'office-ui-fabric-react';
import { SampleControls } from './SampleControls';
export default class VariantTheme extends React.Component<IVariantThemeProps, {}> {
private themeVariant: ITheme = this.props.theme as ITheme;
public render(): React.ReactElement<IVariantThemeProps> {
const {
description,
isDarkTheme,
environmentMessage,
hasTeamsContext,
userDisplayName
} = this.props;
return (
<>
<VariantThemeProvider theme={this.props.theme} variantType={VariantType.None}>
<SampleControls />
</VariantThemeProvider>
<Separator />
<VariantThemeProvider theme={this.props.theme} variantType={VariantType.Neutral}>
<SampleControls />
</VariantThemeProvider>
<Separator />
<VariantThemeProvider theme={this.props.theme} variantType={VariantType.Soft}>
<SampleControls />
</VariantThemeProvider>
<Separator />
<VariantThemeProvider theme={this.props.theme} variantType={VariantType.Strong}>
<SampleControls />
</VariantThemeProvider>
</>
);
}
}
Below are the changes done
- Import VariantThemeProvider and VariantType from pnp controls
- Created a separate component with the different controls to show the theme update
- The separate component is used inside the VariantThemeProvider control where we pass the actual theme and also the variants provided by the control.
- Below are the 4 different variant types that is used
- None
- Neutral
- Soft
- Strong
Let us see the file SampleControls.tsx
import { ActionButton, CommandButton, DefaultButton, IconButton, PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
import { ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
import { IContextualMenuProps } from 'office-ui-fabric-react/lib/ContextualMenu';
import { IIconProps } from 'office-ui-fabric-react/lib/Icon';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { Link } from 'office-ui-fabric-react/lib/Link';
import { Rating, RatingSize } from 'office-ui-fabric-react/lib/Rating';
import { Slider } from 'office-ui-fabric-react/lib/Slider';
import { Stack } from 'office-ui-fabric-react/lib/Stack';
import { Toggle } from 'office-ui-fabric-react/lib/Toggle';
import * as React from 'react';
const menuProps: IContextualMenuProps = {
items: [
{
key: 'emailMessage',
text: 'Email message',
iconProps: { iconName: 'Mail' },
},
{
key: 'calendarEvent',
text: 'Calendar event',
iconProps: { iconName: 'Calendar' },
},
],
};
const addIcon: IIconProps = { iconName: 'Add' };
const emojiIcon: IIconProps = { iconName: 'Emoji2' };
const addFriendIcon: IIconProps = { iconName: 'AddFriend' };
const options: IChoiceGroupOption[] = [
{ key: 'A', text: 'Option A' },
{ key: 'B', text: 'Option B' },
];
const options1: IChoiceGroupOption[] = [
{ key: 'day', text: 'Day', iconProps: { iconName: 'CalendarDay' } },
{ key: 'week', text: 'Week', iconProps: { iconName: 'CalendarWeek' } },
{ key: 'month', text: 'Month', iconProps: { iconName: 'Calendar' }, disabled: true },
];
export const SampleControls: React.FC<{}> = (props) => {
return (
<Stack tokens={{ childrenGap: 20, padding: 15 }} verticalAlign={'start'}>
<Stack.Item>
<Stack horizontal horizontalAlign='space-evenly'>
<Stack.Item>
<PrimaryButton text='Button 1'></PrimaryButton>
</Stack.Item>
<Stack.Item>
<DefaultButton text='Default Button 1'></DefaultButton>
</Stack.Item>
<Stack.Item>
<CommandButton iconProps={addIcon} text="New item" menuProps={menuProps} />
</Stack.Item>
<Stack.Item>
<IconButton iconProps={emojiIcon} title="Emoji" ariaLabel="Emoji" />
</Stack.Item>
<Stack.Item>
<IconButton menuProps={menuProps} iconProps={emojiIcon} title="Emoji" ariaLabel="Emoji" />
</Stack.Item>
<Stack.Item>
<ActionButton iconProps={addFriendIcon} allowDisabledFocus>Create account</ActionButton>
</Stack.Item>
</Stack>
<Stack horizontal horizontalAlign='space-evenly'>
<Stack.Item>
<Checkbox label="Unchecked checkbox (uncontrolled)" />
<Checkbox label="Disabled checked checkbox" disabled defaultChecked />
</Stack.Item>
<Stack.Item>
<ChoiceGroup defaultSelectedKey="B" options={options} label="Pick one" required={true} />
</Stack.Item>
<Stack.Item>
<ChoiceGroup label="Pick one icon" defaultSelectedKey="day" options={options1} />
</Stack.Item>
</Stack>
<Stack horizontal horizontalAlign='space-evenly'>
<Stack.Item>
<Label>I'm a Label</Label>
</Stack.Item>
<Stack.Item>
<Toggle label="Enabled and checked" defaultChecked onText="On" offText="Off" />
</Stack.Item>
<Stack.Item>
<Slider label="Snapping slider example" min={0} max={50} step={10} defaultValue={20} showValue snapToStep />
</Stack.Item>
</Stack>
<Stack horizontal horizontalAlign='space-evenly'>
<Stack.Item>
When a link has an href,{' '}
<Link href="https://developer.microsoft.com/en-us/fluentui#/controls/web/link" underline>
it renders as an anchor tag.
</Link>{' '}
Without an href,{' '}
<Link underline>
the link is rendered as a button
</Link>
</Stack.Item>
</Stack>
<Stack horizontal horizontalAlign='space-evenly'>
<Stack.Item>
<Rating max={5} size={RatingSize.Large} ariaLabel="Large stars" ariaLabelFormat="{0} of {1} stars" />
</Stack.Item>
<Stack.Item>
<Rating max={5} disabled ariaLabel="Disabled" ariaLabelFormat="{0} of {1} stars" />
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
);
};
The above code doesn’t have any unique code. All the controls used in the above file were taken from FluentUI for demo purpose. Since we are wrapping all our controls inside the VariantThemeProvider control, the theme and the variant is automatically applied to the controls.
Following are some of the key properties provided by the VariantThemeProvider control
- theme – The oob theme or a custom theme that control should use
- variantType – The different types of variant type that can be applied.
- themeColors – Custom theme colors define based on the three colors mentioned below
- primaryColor
- textColor
- backgroundColor
Reference URL
VariantThemeProvider – @pnp/spfx-controls-react
Conclussion
I hope you had learned something about one of the pnp control. There are lot to come in future.
Please free to post your comments and let me know if you want me to post article on any particular feature or plugins that relates to SharePoint. Thanks for your support.
sudharsank/pnpcontrols-demo: Demo project on different PnP React Controls. (github.com)
Happy Coding…