diff --git a/apps/fabric-website-resources/webpack.serve.config.js b/apps/fabric-website-resources/webpack.serve.config.js index 055d93aac3e89e..e6ea55d7807aa0 100644 --- a/apps/fabric-website-resources/webpack.serve.config.js +++ b/apps/fabric-website-resources/webpack.serve.config.js @@ -15,6 +15,7 @@ module.exports = resources.createServeConfig({ resolve: { alias: { + 'office-ui-fabric-react$': path.resolve(__dirname, '../../packages/office-ui-fabric-react/src'), 'office-ui-fabric-react/src': path.resolve(__dirname, '../../packages/office-ui-fabric-react/src'), 'office-ui-fabric-react/lib/codepen': path.resolve( __dirname, diff --git a/apps/todo-app/package.json b/apps/todo-app/package.json index 6c60607a13f5fe..c43c8c0578dd26 100644 --- a/apps/todo-app/package.json +++ b/apps/todo-app/package.json @@ -26,4 +26,4 @@ "typescript": "2.8.4", "tslib": "^1.7.1" } -} \ No newline at end of file +} diff --git a/common/changes/@uifabric/experiments/customizer-fix_2018-08-20-22-50.json b/common/changes/@uifabric/experiments/customizer-fix_2018-08-20-22-50.json new file mode 100644 index 00000000000000..8295746e009294 --- /dev/null +++ b/common/changes/@uifabric/experiments/customizer-fix_2018-08-20-22-50.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@uifabric/experiments", + "comment": "Adjusting foundation usage, using new React 16 context.", + "type": "minor" + } + ], + "packageName": "@uifabric/experiments", + "email": "dzearing@microsoft.com" +} \ No newline at end of file diff --git a/common/changes/@uifabric/fabric-website-resources/customizer-fix_2018-07-27-01-09.json b/common/changes/@uifabric/fabric-website-resources/customizer-fix_2018-07-27-01-09.json new file mode 100644 index 00000000000000..2d5b01fc5b436f --- /dev/null +++ b/common/changes/@uifabric/fabric-website-resources/customizer-fix_2018-07-27-01-09.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@uifabric/fabric-website-resources", + "comment": "Updating serve config to respect oufr imports.", + "type": "patch" + } + ], + "packageName": "@uifabric/fabric-website-resources", + "email": "dzearing@microsoft.com" +} \ No newline at end of file diff --git a/common/changes/@uifabric/foundation/customizer-fix_2018-08-20-22-50.json b/common/changes/@uifabric/foundation/customizer-fix_2018-08-20-22-50.json new file mode 100644 index 00000000000000..eb69728cb1d716 --- /dev/null +++ b/common/changes/@uifabric/foundation/customizer-fix_2018-08-20-22-50.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "Adjusting foundation usage, using new React 16 context.", + "packageName": "@uifabric/foundation", + "type": "minor" + } + ], + "packageName": "@uifabric/foundation", + "email": "dzearing@microsoft.com" +} \ No newline at end of file diff --git a/common/changes/@uifabric/utilities/customizer-fix_2018-07-27-01-09.json b/common/changes/@uifabric/utilities/customizer-fix_2018-07-27-01-09.json new file mode 100644 index 00000000000000..4f1b11fc61b86d --- /dev/null +++ b/common/changes/@uifabric/utilities/customizer-fix_2018-07-27-01-09.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@uifabric/utilities", + "comment": "Customizer: moving to use React 16 context.", + "type": "minor" + } + ], + "packageName": "@uifabric/utilities", + "email": "dzearing@microsoft.com" +} \ No newline at end of file diff --git a/common/changes/office-ui-fabric-react/customizer-fix_2018-07-27-01-09.json b/common/changes/office-ui-fabric-react/customizer-fix_2018-07-27-01-09.json new file mode 100644 index 00000000000000..2d4f7ac89569f0 --- /dev/null +++ b/common/changes/office-ui-fabric-react/customizer-fix_2018-07-27-01-09.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "Check: adjusting shouldComponentUpdate to not ignore theme changes.", + "type": "patch" + } + ], + "packageName": "office-ui-fabric-react", + "email": "dzearing@microsoft.com" +} \ No newline at end of file diff --git a/packages/experiments/src/Foundation.ts b/packages/experiments/src/Foundation.ts index c686f0bb31af55..0243b807ed9e00 100644 --- a/packages/experiments/src/Foundation.ts +++ b/packages/experiments/src/Foundation.ts @@ -5,7 +5,6 @@ import { IComponentOptions, IViewComponentProps, IStateComponent, - IStyleableComponent, IStyleableComponentProps, IStylingProviders, IThemedComponent, @@ -13,7 +12,7 @@ import { } from '@uifabric/foundation'; export { IStateComponentProps } from '@uifabric/foundation'; import { IProcessedStyleSet, IStyleSet } from './Styling'; -import { Customizations, CustomizableContextTypes, ICustomizations } from './Utilities'; +import { Customizations, CustomizerContext, ICustomizations } from './Utilities'; // Centralize Foundation interaction for use throughout this package. These convenience types provide types // that are global for all of OUFR, such as ITheme and IProcessedStyleSet. @@ -25,11 +24,12 @@ export type IViewComponentProps> TProps, IProcessedStyleSet >; + /** * Required properties for styleable components. */ -export type IStyleableComponent = IStyleableComponent; export type IStyleableComponentProps = IStyleableComponentProps; + /** * Required properties for themed components. */ @@ -45,7 +45,7 @@ type IContextCustomization = { customizations: ICustomizations }; const providers: IStylingProviders = { mergeStyleSets, getCustomizations, - CustomizableContextTypes + CustomizerContext }; /** @@ -54,7 +54,7 @@ const providers: IStylingProviders * @param {IComponentOptions} options */ export function createStatelessComponent< - TComponentProps extends IStyleableComponent, + TComponentProps extends IStyleableComponentProps, TStyleSet extends IStyleSet, TStatics = {} >( @@ -77,7 +77,7 @@ export function createStatelessComponent< * @param {IStateComponent} state */ export function createComponent< - TComponentProps extends IStyleableComponent, + TComponentProps extends IStyleableComponentProps, TViewProps, TStyleSet extends IStyleSet, TStatics = {} diff --git a/packages/experiments/src/components/Accordion/Accordion.tsx b/packages/experiments/src/components/Accordion/Accordion.tsx index d46f2ff62d0601..57f00845a4d8d9 100644 --- a/packages/experiments/src/components/Accordion/Accordion.tsx +++ b/packages/experiments/src/components/Accordion/Accordion.tsx @@ -1,11 +1,11 @@ import * as React from 'react'; -import { createStatelessComponent, IViewComponentProps, IStyleableComponent } from '../../Foundation'; +import { createStatelessComponent, IViewComponentProps, IStyleableComponentProps } from '../../Foundation'; import { CollapsibleSection, ICollapsibleSectionProps, ICollapsibleSectionStyles } from '../../CollapsibleSection'; import { IAccordionProps, IAccordionStyles } from './Accordion.types'; import { styles } from './Accordion.styles'; const AccordionItemType = ( as React.ReactElement & - IStyleableComponent).type; + IStyleableComponentProps).type; const view = (props: IViewComponentProps) => { const { renderAs: RootType = 'div', classNames, collapseItems } = props; diff --git a/packages/experiments/src/components/Accordion/Accordion.types.ts b/packages/experiments/src/components/Accordion/Accordion.types.ts index cbeb9f8178fa4c..a47d993f4321ab 100644 --- a/packages/experiments/src/components/Accordion/Accordion.types.ts +++ b/packages/experiments/src/components/Accordion/Accordion.types.ts @@ -1,7 +1,7 @@ import { IStyle } from '../../Styling'; -import { IStyleableComponent } from '../../Foundation'; +import { IStyleableComponentProps } from '../../Foundation'; -export interface IAccordionProps extends IStyleableComponent { +export interface IAccordionProps extends IStyleableComponentProps { renderAs?: string | React.ReactType; className?: string; diff --git a/packages/experiments/src/components/CollapsibleSection/CollapsibleSection.types.ts b/packages/experiments/src/components/CollapsibleSection/CollapsibleSection.types.ts index 830281cea9077d..e9665e1e7f8506 100644 --- a/packages/experiments/src/components/CollapsibleSection/CollapsibleSection.types.ts +++ b/packages/experiments/src/components/CollapsibleSection/CollapsibleSection.types.ts @@ -1,12 +1,12 @@ import * as React from 'react'; import { IStyle } from 'office-ui-fabric-react'; -import { IStyleableComponent, IStyleableComponentProps, IThemedProps } from '../../Foundation'; +import { IStyleableComponentProps, IThemedProps } from '../../Foundation'; import { RefObject } from '../../Utilities'; import { ICollapsibleSectionTitleProps } from './CollapsibleSectionTitle.types'; export interface ICollapsibleSectionProps - extends IStyleableComponent { + extends IStyleableComponentProps { /** * Additional class name to provide on the root element. */ diff --git a/packages/experiments/src/components/CollapsibleSection/CollapsibleSectionTitle.types.ts b/packages/experiments/src/components/CollapsibleSection/CollapsibleSectionTitle.types.ts index 44644f6dc90c7b..6306a831be5e10 100644 --- a/packages/experiments/src/components/CollapsibleSection/CollapsibleSectionTitle.types.ts +++ b/packages/experiments/src/components/CollapsibleSection/CollapsibleSectionTitle.types.ts @@ -1,8 +1,8 @@ import { IStyle, RefObject } from 'office-ui-fabric-react'; -import { IStyleableComponent, IThemedProps } from '../../Foundation'; +import { IStyleableComponentProps, IThemedProps } from '../../Foundation'; export interface ICollapsibleSectionTitleProps - extends IStyleableComponent { + extends IStyleableComponentProps { focusElementRef?: RefObject; /** * Collapsed state of body associated with this component. diff --git a/packages/experiments/src/components/Stack/Stack.tsx b/packages/experiments/src/components/Stack/Stack.tsx index 55719e4df15f77..04e1bfc855ea95 100644 --- a/packages/experiments/src/components/Stack/Stack.tsx +++ b/packages/experiments/src/components/Stack/Stack.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { createStatelessComponent, IStyleableComponent, IViewComponentProps } from '../../Foundation'; +import { createStatelessComponent, IStyleableComponentProps, IViewComponentProps } from '../../Foundation'; import StackItem from './StackItem/StackItem'; import { IStackItemProps, IStackItemStyles } from './StackItem/StackItem.types'; import { IStackProps, IStackStyles } from './Stack.types'; @@ -7,7 +7,7 @@ import { styles } from './Stack.styles'; import { mergeStyles } from 'office-ui-fabric-react/lib/Styling'; const StackItemType = ( as React.ReactElement & - IStyleableComponent).type; + IStyleableComponentProps).type; const view = (props: IViewComponentProps) => { const { renderAs: RootType = 'div', classNames, gap, horizontal, shrinkItems } = props; diff --git a/packages/experiments/src/components/Stack/Stack.types.ts b/packages/experiments/src/components/Stack/Stack.types.ts index 132559fc4fe2f9..6f28a5fa9931a2 100644 --- a/packages/experiments/src/components/Stack/Stack.types.ts +++ b/packages/experiments/src/components/Stack/Stack.types.ts @@ -1,5 +1,5 @@ import { IStyle } from '../../Styling'; -import { IStyleableComponent } from '../../Foundation'; +import { IStyleableComponentProps } from '../../Foundation'; export type Alignment = | 'start' @@ -16,7 +16,7 @@ export type Omit = Pick>; // contains the members of IStackProps that are common to both VerticalStack and HorizontalStack export type IPartialStackProps = Omit; -export interface IStackProps extends IStyleableComponent { +export interface IStackProps extends IStyleableComponentProps { /** * How to render the Stack. */ diff --git a/packages/experiments/src/components/Stack/StackItem/StackItem.types.ts b/packages/experiments/src/components/Stack/StackItem/StackItem.types.ts index d0277b33e5a4c2..5c80785a1b808a 100644 --- a/packages/experiments/src/components/Stack/StackItem/StackItem.types.ts +++ b/packages/experiments/src/components/Stack/StackItem/StackItem.types.ts @@ -1,7 +1,7 @@ import { IStyle } from '../../../Styling'; -import { IStyleableComponent } from '../../../Foundation'; +import { IStyleableComponentProps } from '../../../Foundation'; -export interface IStackItemProps extends IStyleableComponent { +export interface IStackItemProps extends IStyleableComponentProps { /** * CSS class name used to style the StackItem. */ diff --git a/packages/experiments/src/components/Text/Text.types.tsx b/packages/experiments/src/components/Text/Text.types.tsx index 0e1372a1b88c40..db9650985d6779 100644 --- a/packages/experiments/src/components/Text/Text.types.tsx +++ b/packages/experiments/src/components/Text/Text.types.tsx @@ -1,5 +1,5 @@ import { IStyle, IPalette, ISemanticColors } from '../../Styling'; -import { IStyleableComponent } from '../../Foundation'; +import { IStyleableComponentProps } from '../../Foundation'; import { IFontVariants, IFontFamilies, IFontSizes, IFontWeights } from '../../Styling'; // Styles for the component @@ -11,7 +11,7 @@ export interface ITextStyles { } // Inputs to the component -export interface ITextProps extends IStyleableComponent { +export interface ITextProps extends IStyleableComponentProps { /** * Optionaly render the component as another component type or primative. */ diff --git a/packages/foundation/src/createComponent.tsx b/packages/foundation/src/createComponent.tsx index d518d395ef5789..5c9f1dee2030e4 100644 --- a/packages/foundation/src/createComponent.tsx +++ b/packages/foundation/src/createComponent.tsx @@ -17,12 +17,13 @@ export type IStylesProp = IStyleFunction { +export interface IStyleableComponentProps { styles?: IStylesProp; theme?: TTheme; } -export type IStyleableComponentProps = TProps & - IStyleableComponent; + +export type IStyleableComponentCombinedProps = TProps & + IStyleableComponentProps; /** * Enforce props contract on state components, including the view prop and its shape. @@ -72,10 +73,11 @@ export interface IComponentOptions { mergeStyleSets: (...styles: (Partial | undefined)[]) => TProcessedStyleSet; - getCustomizations: (scope: string, context: TContext) => IStyleableComponent; - // TODO: remove any if possible - // tslint:disable-next-line:no-any - CustomizableContextTypes: any; + getCustomizations: ( + scope: string, + context: TContext + ) => IStyleableComponentCombinedProps; + CustomizerContext: React.Context; } /** @@ -117,7 +119,7 @@ export interface IStylingProviders, + TComponentProps extends IStyleableComponentProps, TViewProps, TStyleSet, TProcessedStyleSet, @@ -133,40 +135,49 @@ export function createComponent< TProcessedStyleSet > ): React.StatelessComponent & TStatics { - const result: React.StatelessComponent = (userProps: TComponentProps, context: TContext) => { + const { CustomizerContext } = providers; + const result: React.StatelessComponent = (userProps: TComponentProps) => { // Theming and styling values are provided by state component and createComponent - type TProcessedProps = TViewProps & IStyleableComponent; - - const settings = providers.getCustomizations(options.displayName, context); - const { styles: contextStyles, ...rest } = settings; - - const content = (processedProps: TProcessedProps) => { - // The approach here is to allow state components to provide only the props they care about, automatically - // merging user props and processed props together. This ensures all props are passed properly to view, - // including children and styles. - const propStyles = processedProps.styles || userProps.styles; - const themedProps: TProcessedProps = Object.assign({}, rest, userProps, processedProps); - const viewProps: IViewComponentProps = Object.assign( - {}, - processedProps, - userProps, - { - classNames: providers.mergeStyleSets( - _evaluateStyle(themedProps, options.styles), - _evaluateStyle(themedProps, contextStyles), - _evaluateStyle(themedProps, propStyles) - ) - } - ); - - // TODO: consider rendering view as JSX component with display name in debug mode to aid in debugging - return options.view(viewProps); - }; - - return ; + type TProcessedProps = TViewProps & IStyleableComponentProps; + + return ( + + {(context: TContext) => { + const settings = providers.getCustomizations(options.displayName, context); + const { styles: contextStyles, ...rest } = settings as IStyleableComponentProps< + TViewProps, + TStyleSet, + TTheme + >; + + const content = (processedProps: TProcessedProps) => { + // The approach here is to allow state components to provide only the props they care about, automatically + // merging user props and processed props together. This ensures all props are passed properly to view, + // including children and styles. + const propStyles = processedProps.styles || userProps.styles; + const themedProps: TProcessedProps = Object.assign({}, rest, userProps, processedProps); + const viewProps: IViewComponentProps = Object.assign( + {}, + processedProps, + userProps, + { + classNames: providers.mergeStyleSets( + _evaluateStyle(themedProps, options.styles), + _evaluateStyle(themedProps, contextStyles), + _evaluateStyle(themedProps, propStyles) + ) + } + ); + + // TODO: consider rendering view as JSX component with display name in debug mode to aid in debugging + return options.view(viewProps); + }; + return ; + }} + + ); }; - result.contextTypes = providers.CustomizableContextTypes; result.displayName = options.displayName; Object.assign(result, options.statics); @@ -182,7 +193,7 @@ export function createComponent< * @see {@link createComponent} for more information. */ export function createStatelessComponent< - TComponentProps extends IStyleableComponent, + TComponentProps extends IStyleableComponentProps, TStyleSet, TProcessedStyleSet, TContext, @@ -192,32 +203,46 @@ export function createStatelessComponent< options: IComponentOptions, providers: IStylingProviders ): React.StatelessComponent & TStatics { - const result: React.StatelessComponent = (userProps: TComponentProps, context: TContext) => { + const result: React.StatelessComponent = (userProps: TComponentProps) => { // Theming and styling values are provided by state component and createComponent - type TProcessedProps = TComponentProps & IStyleableComponent; - - const settings = providers.getCustomizations(options.displayName, context); - const { styles: contextStyles, ...rest } = settings; - - const content = (processedProps: TProcessedProps) => { - const { styles: propStyles } = processedProps; - const themedProps: TProcessedProps = Object.assign({}, rest, processedProps); - const viewProps: IViewComponentProps = Object.assign({}, processedProps, { - classNames: providers.mergeStyleSets( - _evaluateStyle(themedProps, options.styles), - _evaluateStyle(themedProps, contextStyles), - _evaluateStyle(themedProps, propStyles) - ) - }); - - // TODO: consider rendering view as JSX component with display name in debug mode to aid in debugging - return options.view(viewProps); - }; - - return content(userProps); + const { CustomizerContext } = providers; + type TProcessedProps = TComponentProps & IStyleableComponentProps; + + return ( + + {(context: TContext) => { + const settings = providers.getCustomizations(options.displayName, context); + const { styles: contextStyles, ...rest } = settings as IStyleableComponentProps< + TComponentProps, + TStyleSet, + TTheme + >; + + const content = (processedProps: TProcessedProps) => { + const { styles: propStyles } = processedProps; + const themedProps: TProcessedProps = Object.assign({}, rest, processedProps); + const viewProps: IViewComponentProps = Object.assign( + {}, + processedProps, + { + classNames: providers.mergeStyleSets( + _evaluateStyle(themedProps, options.styles), + _evaluateStyle(themedProps, contextStyles), + _evaluateStyle(themedProps, propStyles) + ) + } + ); + + // TODO: consider rendering view as JSX component with display name in debug mode to aid in debugging + return options.view(viewProps); + }; + + return content(userProps); + }} + + ); }; - result.contextTypes = providers.CustomizableContextTypes; result.displayName = options.displayName; Object.assign(result, options.statics); diff --git a/packages/office-ui-fabric-react/jest.config.js b/packages/office-ui-fabric-react/jest.config.js index 4bfb5c2bf98f7a..6e9aefe1b14cb7 100644 --- a/packages/office-ui-fabric-react/jest.config.js +++ b/packages/office-ui-fabric-react/jest.config.js @@ -6,6 +6,7 @@ const config = createConfig({ moduleNameMapper: { // These mappings allow Jest to run snapshot tests against Example files. + 'office-ui-fabric-react/lib/codepen/(.*)$': '/lib/codepen/$1', 'office-ui-fabric-react/lib/(.*)$': '/src/$1' }, diff --git a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.test.tsx b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.test.tsx index 2fb92c55498c54..87d402f0d5d97e 100644 --- a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.test.tsx +++ b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.test.tsx @@ -4,11 +4,12 @@ import * as ReactDOM from 'react-dom'; /* tslint:enable:no-unused-variable */ import * as ReactTestUtils from 'react-dom/test-utils'; import * as renderer from 'react-test-renderer'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; import { KeyCodes, resetIds } from '../../Utilities'; import { Dropdown } from './Dropdown'; -import { DropdownMenuItemType, IDropdownOption } from './Dropdown.types'; +import { DropdownBase } from './Dropdown.base'; +import { DropdownMenuItemType, IDropdownOption, IDropdown } from './Dropdown.types'; const DEFAULT_OPTIONS: IDropdownOption[] = [ { key: 'Header1', text: 'Header 1', itemType: DropdownMenuItemType.Header }, @@ -190,24 +191,18 @@ describe('Dropdown', () => { it('sets the selected item even when key is number 0', () => { const options = [{ key: 0, text: 'item1' }, { key: 1, text: 'item2' }]; const selectedKey = 0; + const dropdown = React.createRef(); - const wrapper = shallow(); + const wrapper = mount(); - // Use .dive() because Dropdown is a decorated component - let state = wrapper - .dive() // styled - .dive() // withResponsiveMode - .state('selectedIndices'); - expect(state).toEqual([]); + expect((dropdown.current as DropdownBase).state.selectedIndices).toEqual([]); const newProps = { options, selectedKey }; + wrapper.setProps(newProps); wrapper.update(); - state = wrapper - .dive() - .dive() - .state('selectedIndices'); - expect(state).toEqual([selectedKey]); + + expect((dropdown.current as DropdownBase).state.selectedIndices).toEqual([selectedKey]); }); it('issues the onChanged callback when the selected item is different', () => { @@ -378,23 +373,17 @@ describe('Dropdown', () => { it('sets the selected items even when key is number 0', () => { const options = [{ key: 0, text: 'item1' }, { key: 1, text: 'item2' }]; const selectedKeys = [0, 1]; - - const wrapper = shallow(); + const dropdown = React.createRef(); + const wrapper = mount(); // Use .dive() because Dropdown is a decorated component - let state = wrapper - .dive() // styled - .dive() // withresponsivemode - .state('selectedIndices'); + let state = (dropdown.current as DropdownBase).state.selectedIndices; expect(state).toEqual([]); const newProps = { options, selectedKeys }; wrapper.setProps(newProps); wrapper.update(); - state = wrapper - .dive() - .dive() - .state('selectedIndices'); + state = (dropdown.current as DropdownBase).state.selectedIndices; expect(state).toEqual(selectedKeys); }); diff --git a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.types.ts b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.types.ts index 9c6a2516dc771d..5bd3a5fb2fe53c 100644 --- a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.types.ts +++ b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.types.ts @@ -14,7 +14,7 @@ export interface IDropdown { focus: (shouldOpenOnFocus?: boolean) => void; } -export interface IDropdownProps extends ISelectableDroppableTextProps { +export interface IDropdownProps extends ISelectableDroppableTextProps { /** * Input placeholder text. Displayed until option is selected. */ diff --git a/packages/utilities/src/Customizer.tsx b/packages/utilities/src/Customizer.tsx index e176ef18cdfe4f..b38cf3fa7719b4 100644 --- a/packages/utilities/src/Customizer.tsx +++ b/packages/utilities/src/Customizer.tsx @@ -1,12 +1,18 @@ import * as React from 'react'; -import * as PropTypes from 'prop-types'; import { BaseComponent, IBaseProps } from './BaseComponent'; -import { ICustomizations, Settings, SettingsFunction } from './Customizations'; +import { Customizations, ICustomizations, Settings, SettingsFunction } from './Customizations'; export interface ICustomizerContext { customizations: ICustomizations; } +export const CustomizerContext = React.createContext({ + customizations: { + settings: {}, + scopedSettings: {} + } +}); + export type ICustomizerProps = IBaseProps & Partial<{ /** @@ -54,7 +60,8 @@ export type ICustomizerProps = IBaseProps & /** * The Customizer component allows for default props to be mixed into components which - * are decorated with the customizable() decorator. This enables injection scenarios like: + * are decorated with the customizable() decorator, or use the styled HOC. This enables + * injection scenarios like: * * 1. render svg icons instead of the icon font within all buttons * 2. inject a custom theme object into a component @@ -65,39 +72,31 @@ export type ICustomizerProps = IBaseProps & * * @public */ -export class Customizer extends BaseComponent { - public static contextTypes: { - customizations: PropTypes.Requireable<{}>; - } = { - customizations: PropTypes.object - }; - - public static childContextTypes: { - customizations: PropTypes.Requireable<{}>; - } = Customizer.contextTypes; - - // tslint:disable-next-line:no-any - constructor(props: ICustomizerProps, context: any) { - super(props); - - this.state = this._getCustomizations(props, context); - } - - public getChildContext(): ICustomizerContext { - return this.state; +export class Customizer extends BaseComponent { + public componentDidMount(): void { + Customizations.observe(this._onCustomizationChange); } - // tslint:disable-next-line:no-any - public componentWillReceiveProps(newProps: any, newContext: any): void { - this.setState(this._getCustomizations(newProps, newContext)); + public componentWillUnmount(): void { + Customizations.unobserve(this._onCustomizationChange); } public render(): React.ReactElement<{}> { - return React.Children.only(this.props.children); + return ( + + {(parentContext: ICustomizerContext) => { + const newContext = this._getCustomizations(this.props, parentContext); + + return {this.props.children}; + }} + + ); } - private _getCustomizations(props: ICustomizerProps, context: ICustomizerContext): ICustomizerContext { - const { customizations = { settings: {}, scopedSettings: {} } } = context; + private _onCustomizationChange = () => this.forceUpdate(); + + private _getCustomizations(props: ICustomizerProps, parentContext: ICustomizerContext): ICustomizerContext { + const { customizations = { settings: {}, scopedSettings: {} } } = parentContext || {}; return { customizations: { diff --git a/packages/utilities/src/customizable.tsx b/packages/utilities/src/customizable.tsx index 62644423844c43..b6ed0bb4975101 100644 --- a/packages/utilities/src/customizable.tsx +++ b/packages/utilities/src/customizable.tsx @@ -1,13 +1,9 @@ import * as React from 'react'; -import * as PropTypes from 'prop-types'; import { Customizations } from './Customizations'; import { hoistStatics } from './hoistStatics'; +import { CustomizerContext, ICustomizerContext } from './Customizer'; import { concatStyleSets } from '@uifabric/merge-styles'; -export const CustomizableContextTypes = { - customizations: PropTypes.object -}; - export function customizable( scope: string, fields: string[], @@ -23,11 +19,9 @@ export function customizable( const resultClass = class ComponentWithInjectedProps extends React.Component { public static displayName: string = 'Customized' + scope; - public static contextTypes = CustomizableContextTypes; - // tslint:disable-next-line:no-any - constructor(props: P, context: any) { - super(props, context); + constructor(props: P) { + super(props); this._onSettingChanged = this._onSettingChanged.bind(this); } @@ -41,17 +35,23 @@ export function customizable( } public render(): JSX.Element { - const defaultProps = Customizations.getSettings(fields, scope, this.context.customizations); + return ( + + {(context: ICustomizerContext) => { + const defaultProps = Customizations.getSettings(fields, scope, context.customizations); - // tslint:disable-next-line:no-any - const componentProps = this.props as any; + // tslint:disable-next-line:no-any + const componentProps = this.props as any; - if (concatStyles) { - const mergedStyles = concatStyleSets(defaultProps.styles, componentProps.styles); - return ; - } + if (concatStyles) { + const mergedStyles = concatStyleSets(defaultProps.styles, componentProps.styles); + return ; + } - return ; + return ; + }} + + ); } private _onSettingChanged(): void { diff --git a/packages/utilities/src/styled.tsx b/packages/utilities/src/styled.tsx index e5ae684c5507d8..89b2c0921e1c09 100644 --- a/packages/utilities/src/styled.tsx +++ b/packages/utilities/src/styled.tsx @@ -1,8 +1,7 @@ import * as React from 'react'; import { concatStyleSets, IStyleSet, IStyleFunctionOrObject, IConcatenatedStyleSet } from '@uifabric/merge-styles'; -import { IStyleFunction } from './IStyleFunction'; -import { CustomizableContextTypes } from './customizable'; -import { Customizations, ICustomizations } from './Customizations'; +import { Customizations } from './Customizations'; +import { CustomizerContext, ICustomizerContext } from './Customizer'; export interface IPropsWithStyles> { styles?: IStyleFunctionOrObject; @@ -49,24 +48,26 @@ export function styled< getProps?: (props: TComponentProps) => Partial, customizable?: ICustomizableProps ): (props: TComponentProps) => JSX.Element { - const Wrapped: React.StatelessComponent = ( - componentProps: TComponentProps, - context: { customizations: ICustomizations } - ) => { + const Wrapped: React.StatelessComponent = (componentProps: TComponentProps) => { customizable = customizable || { scope: '', fields: undefined }; const { scope, fields = DefaultFields } = customizable; - const settings = Customizations.getSettings(fields, scope, context.customizations); - const { styles: customizedStyles, ...rest } = settings; - const styles = (styleProps: TStyleProps) => - _resolve(styleProps, baseStyles, customizedStyles, componentProps.styles); - const additionalProps = getProps ? getProps(componentProps) : undefined; + return ( + + {(context: ICustomizerContext) => { + const settings = Customizations.getSettings(fields, scope, context.customizations); + const { styles: customizedStyles, ...rest } = settings; + const styles = (styleProps: TStyleProps) => + _resolve(styleProps, baseStyles, customizedStyles, componentProps.styles); - return ; + const additionalProps = getProps ? getProps(componentProps) : undefined; + return ; + }} + + ); }; - Wrapped.contextTypes = CustomizableContextTypes; Wrapped.displayName = `Styled${Component.displayName || Component.name}`; return Wrapped as (props: TComponentProps) => JSX.Element;