diff --git a/common/changes/@uifabric/example-app-base/jg-promote-slots_2019-01-30-01-52.json b/common/changes/@uifabric/example-app-base/jg-promote-slots_2019-01-30-01-52.json new file mode 100644 index 00000000000000..0f1a2f2dc4c3c1 --- /dev/null +++ b/common/changes/@uifabric/example-app-base/jg-promote-slots_2019-01-30-01-52.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@uifabric/example-app-base", + "comment": "Changes to support Slots Foundation.", + "type": "patch" + } + ], + "packageName": "@uifabric/example-app-base", + "email": "jagore@microsoft.com" +} \ No newline at end of file diff --git a/common/changes/@uifabric/experiments/jg-promote-slots_2019-01-30-01-52.json b/common/changes/@uifabric/experiments/jg-promote-slots_2019-01-30-01-52.json new file mode 100644 index 00000000000000..e6a759cb971189 --- /dev/null +++ b/common/changes/@uifabric/experiments/jg-promote-slots_2019-01-30-01-52.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@uifabric/experiments", + "comment": "Remove Slots/Tokens Foundation implementation.", + "type": "patch" + } + ], + "packageName": "@uifabric/experiments", + "email": "jagore@microsoft.com" +} \ No newline at end of file diff --git a/common/changes/@uifabric/foundation/jg-promote-slots_2019-01-30-01-52.json b/common/changes/@uifabric/foundation/jg-promote-slots_2019-01-30-01-52.json new file mode 100644 index 00000000000000..919c39780db341 --- /dev/null +++ b/common/changes/@uifabric/foundation/jg-promote-slots_2019-01-30-01-52.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@uifabric/foundation", + "comment": "Promote Slots and Tokens implementation of Foundation.", + "type": "minor" + } + ], + "packageName": "@uifabric/foundation", + "email": "jagore@microsoft.com" +} \ No newline at end of file diff --git a/packages/example-app-base/src/components/ExampleCard/ExampleCard.tsx b/packages/example-app-base/src/components/ExampleCard/ExampleCard.tsx index cd6a7e048abcf9..16914b53fd618f 100644 --- a/packages/example-app-base/src/components/ExampleCard/ExampleCard.tsx +++ b/packages/example-app-base/src/components/ExampleCard/ExampleCard.tsx @@ -53,10 +53,10 @@ const _schemeOptions: IDropdownOption[] = _schemes.map((item: string, index: num })); // tslint:disable-next-line:typedef -const regionStyles: IExampleCardComponent['styles'] = props => ({ +const regionStyles: IExampleCardComponent['styles'] = (props, theme) => ({ root: { - backgroundColor: props.theme.semanticColors.bodyBackground, - color: props.theme.semanticColors.bodyText + backgroundColor: theme.semanticColors.bodyBackground, + color: theme.semanticColors.bodyText } }); diff --git a/packages/example-app-base/src/components/ExampleCard/ExampleCardComponent.tsx b/packages/example-app-base/src/components/ExampleCard/ExampleCardComponent.tsx index 2a8edd36b975b3..7ac42d29d00e5a 100644 --- a/packages/example-app-base/src/components/ExampleCard/ExampleCardComponent.tsx +++ b/packages/example-app-base/src/components/ExampleCard/ExampleCardComponent.tsx @@ -1,8 +1,18 @@ -import * as React from 'react'; -import { createStatelessComponent, IStatelessComponent, IStyleableComponentProps } from '@uifabric/foundation'; +/** @jsx withSlots */ +import { + createComponent, + getSlots, + IHTMLSlot, + IComponent, + IComponentStyles, + IStyleableComponentProps, + withSlots +} from '@uifabric/foundation'; import { Customizations, CustomizerContext, + divProperties, + getNativeProps, getThemedContext, ICustomizerContext, IProcessedStyleSet, @@ -15,20 +25,32 @@ import { // This file exists only to create a temporary stateless component for applying styles. // TODO: Once Stack (or any other Foundation created layout component) is promoted out of experiments, // we can remove this file AND remove foundation as a dependency of example-app-base. -export type IExampleCardComponent = IStatelessComponent; - -export interface IExampleCardComponentProps extends IStyleableComponentProps {} +export type IExampleCardComponent = IComponent; -export interface IExampleCardComponentStyles { - root: IStyle; +export interface IExampleCardComponentSlots { + root?: IHTMLSlot; } +export interface IExampleCardComponentProps + extends IExampleCardComponentSlots, + IStyleableComponentProps {} + +export type IExampleCardComponentStyles = IComponentStyles; + // tslint:disable-next-line:typedef const ExampleCardComponentView: IExampleCardComponent['view'] = props => { - return props.children ?
{props.children}
: null; + if (!props.children) { + return null; + } + + const Slots = getSlots(props, { + root: 'div' + }); + + return ; }; -export const ExampleCardComponent: React.StatelessComponent = createStatelessComponent({ +export const ExampleCardComponent: React.StatelessComponent = createComponent({ displayName: 'ExampleCardComponent', styles: undefined, view: ExampleCardComponentView diff --git a/packages/experiments/src/Foundation.ts b/packages/experiments/src/Foundation.ts index 865a418e602ada..e06118ad55d588 100644 --- a/packages/experiments/src/Foundation.ts +++ b/packages/experiments/src/Foundation.ts @@ -1,7 +1 @@ -export * from './utilities/createComponent'; -export * from './utilities/IComponent'; -export * from './utilities/IHTMLSlots'; -export * from './utilities/ISlots'; -export * from './utilities/slots'; - -export { ThemeProvider } from '@uifabric/foundation'; +export * from '@uifabric/foundation'; diff --git a/packages/experiments/src/slots/SlotsPage.tsx b/packages/experiments/src/slots/SlotsPage.tsx index 44d3305c610834..7493bbbebc4579 100644 --- a/packages/experiments/src/slots/SlotsPage.tsx +++ b/packages/experiments/src/slots/SlotsPage.tsx @@ -10,7 +10,7 @@ import { SlotsContentExample } from './examples/Slots.Content.Example'; import { SlotsStyledExample } from './examples/Slots.Styled.Example'; const SlotsExampleCode = require('!raw-loader!@uifabric/experiments/src/slots/examples/Slots.Example.tsx') as string; -const SlotsAsyncExampleCode = require('!raw-loader!@uifabric/experiments/src/slots/examples/Slots.Example.tsx') as string; +const SlotsAsyncExampleCode = require('!raw-loader!@uifabric/experiments/src/slots/examples/Slots.Async.Example.tsx') as string; const SlotsRootExampleCode = require('!raw-loader!@uifabric/experiments/src/slots/examples/Slots.Root.Example.tsx') as string; const SlotsStackExampleCode = require('!raw-loader!@uifabric/experiments/src/slots/examples/Slots.Stack.Example.tsx') as string; const SlotsIconExampleCode = require('!raw-loader!@uifabric/experiments/src/slots/examples/Slots.Icon.Example.tsx') as string; diff --git a/packages/experiments/src/utilities/createComponent.tsx b/packages/experiments/src/utilities/createComponent.tsx deleted file mode 100644 index 55749c48240292..00000000000000 --- a/packages/experiments/src/utilities/createComponent.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import * as React from 'react'; -import { concatStyleSets, IStyleSet, ITheme } from '@uifabric/styling'; -import { Customizations, CustomizerContext, ICustomizerContext } from '@uifabric/utilities'; -import { assign } from './utilities'; - -import { IComponent, ICustomizationProps, IStyleableComponentProps, IStylesFunctionOrObject, IToken } from './IComponent'; -import { IDefaultSlotProps } from './ISlots'; - -/** - * Assembles a higher order component based on the following: styles, theme, view, and state. - * Imposes a separation of concern and centralizes styling processing to increase ease of use and robustness - * in how components use and apply styling and theming. - * - * Automatically merges and applies themes and styles with theme / styleprops having the highest priority. - * State component, if provided, is passed in props for processing. Props from state / user are automatically processed - * and styled before finally being passed to view. - * - * State components should contain all stateful behavior and should not generate any JSX, but rather simply call the view prop. - * Views should simply be stateless pure functions that receive all props needed for rendering their output. - * State component is optional. If state is not provided, created component is essentially a functional stateless component. - * - * TComponentProps: A styleable props interface for the created component. - * TTokens: The type for tokens props. - * TStyleSet: The type for styles properties. - * TViewProps: The props specific to the view, including processed properties outputted by optional state component. If state - * component is not provided, TComponentProps is the same as TViewProps. - * TStatics: Static type for statics applied to created component object. - * - * @param {IComponent} component - * @param {IComponentProviders} providers - */ -export function createComponent< - TComponentProps, - TTokens, - TStyleSet extends IStyleSet, - TViewProps = TComponentProps, - TStatics = {} ->(component: IComponent): React.StatelessComponent & TStatics { - const result: React.StatelessComponent = (componentProps: TComponentProps) => { - return ( - // TODO: createComponent is also affected by https://github.com/OfficeDev/office-ui-fabric-react/issues/6603 - - {(context: ICustomizerContext) => { - // TODO: this next line is basically saying 'theme' prop will ALWAYS be available from getCustomizations - // via ICustomizationProps cast. Is there mechanism that guarantees theme and other request fields will be defined? - // is there a static init that guarantees theme will be provided? - // what happens if createTheme/loadTheme is not called? - // if so, convey through getCustomizations typing keying off fields. can we convey this - // all the way from Customizations with something like { { K in fields }: object}? hmm - // if not, how does existing "theme!" styles code work without risk of failing (assuming it doesn't fail)? - // For now cast return value as if theme is always available. - const settings: ICustomizationProps = _getCustomizations( - component.displayName, - context, - component.fields - ); - - const renderView = (viewProps?: TViewProps & IStyleableComponentProps) => { - // The approach here is to allow state components to provide only the props they care about, automatically - // merging user props and state props together. This ensures all props are passed properly to view, - // including children and styles. - // TODO: for full 'fields' support, 'rest' props from customizations need to pass onto view. - // however, customized props like theme will break snapshots. how is styled not showing theme output in snapshots? - const mergedProps: TViewProps & IStyleableComponentProps = viewProps - ? { - ...(componentProps as any), - ...(viewProps as any) - } - : componentProps; - - const theme = mergedProps.theme || settings.theme; - - const tokens = _resolveTokens(mergedProps, theme, component.tokens, settings.tokens, mergedProps.tokens); - const styles = _resolveStyles(mergedProps, theme, tokens, component.styles, settings.styles, mergedProps.styles); - - const viewComponentProps: typeof mergedProps & IDefaultSlotProps = { - ...(mergedProps as any), - _defaultStyles: styles - }; - - return ; - }; - return component.state ? : renderView(); - }} - - ); - }; - - result.displayName = component.displayName; - - assign(result, component.statics); - - // Later versions of TypeSript should allow us to merge objects in a type safe way and avoid this cast. - return result as React.StatelessComponent & TStatics; -} - -/** - * Resolve all styles functions with both props and tokens and flatten results along with all styles objects. - */ -function _resolveStyles>( - props: TProps, - theme: ITheme, - tokens: TTokens, - ...allStyles: (IStylesFunctionOrObject | undefined)[] -): ReturnType { - return concatStyleSets( - ...allStyles.map((styles: IStylesFunctionOrObject | undefined) => - typeof styles === 'function' ? styles(props, theme, tokens) : styles - ) - ); -} - -/** - * Resolve all tokens functions with props flatten results along with all tokens objects. - */ -function _resolveTokens( - props: TViewProps, - theme: ITheme, - ...allTokens: (IToken | false | null | undefined)[] -): TTokens { - const tokens = {}; - - for (let currentTokens of allTokens) { - if (currentTokens) { - currentTokens = typeof currentTokens === 'function' ? currentTokens(props, theme) : currentTokens; - - if (Array.isArray(currentTokens)) { - currentTokens = _resolveTokens(props, theme, ...currentTokens); - } - - assign(tokens, ...(currentTokens as any)); - } - } - - return tokens as TTokens; -} - -/** - * Helper function for calling Customizations.getSettings falling back to default fields. - * - * @param displayName Displayable name for component. - * @param context React context passed to component containing contextual settings. - * @param fields Optional list of properties to grab from global store and context. - */ -function _getCustomizations>( - displayName: string, - context: ICustomizerContext, - fields?: string[] -): ICustomizationProps { - // TODO: do we want field props? should fields be part of IComponent and used here? - // TODO: should we centrally define DefaultFields? (not exported from styling) - // TOOD: tie this array to ICustomizationProps, such that each array element is keyof ICustomizationProps - const DefaultFields = ['theme', 'styles', 'tokens']; - return Customizations.getSettings(fields || DefaultFields, displayName, context.customizations); -} diff --git a/packages/experiments/src/utilities/utilities.ts b/packages/experiments/src/utilities/utilities.ts deleted file mode 100644 index 3146eabfcb3301..00000000000000 --- a/packages/experiments/src/utilities/utilities.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { __assign } from 'tslib'; -export const assign = __assign; diff --git a/packages/foundation/config/tests.js b/packages/foundation/config/tests.js index 4a094dc4615c56..ec8b0ea5994186 100644 --- a/packages/foundation/config/tests.js +++ b/packages/foundation/config/tests.js @@ -1,5 +1,6 @@ /** Jest test setup file. */ +const { setIconOptions } = require('@uifabric/styling'); const { configure } = require('enzyme'); const Adapter = require('enzyme-adapter-react-16'); diff --git a/packages/experiments/src/utilities/IComponent.ts b/packages/foundation/src/IComponent.ts similarity index 100% rename from packages/experiments/src/utilities/IComponent.ts rename to packages/foundation/src/IComponent.ts diff --git a/packages/experiments/src/utilities/IHTMLSlots.ts b/packages/foundation/src/IHTMLSlots.ts similarity index 100% rename from packages/experiments/src/utilities/IHTMLSlots.ts rename to packages/foundation/src/IHTMLSlots.ts diff --git a/packages/experiments/src/utilities/ISlots.ts b/packages/foundation/src/ISlots.ts similarity index 100% rename from packages/experiments/src/utilities/ISlots.ts rename to packages/foundation/src/ISlots.ts diff --git a/packages/experiments/src/utilities/__snapshots__/slots.test.tsx.snap b/packages/foundation/src/__snapshots__/slots.test.tsx.snap similarity index 100% rename from packages/experiments/src/utilities/__snapshots__/slots.test.tsx.snap rename to packages/foundation/src/__snapshots__/slots.test.tsx.snap diff --git a/packages/foundation/src/createComponent.tsx b/packages/foundation/src/createComponent.tsx index e363fe9bfc8276..3e5a8383b0b59d 100644 --- a/packages/foundation/src/createComponent.tsx +++ b/packages/foundation/src/createComponent.tsx @@ -1,107 +1,10 @@ import * as React from 'react'; -import { IProcessedStyleSet, IStyleSet, ITheme, mergeStyleSets } from '@uifabric/styling'; -import { Customizations, CustomizerContext, ICustomizerContext, IStyleFunctionOrObject } from '@uifabric/utilities'; - +import { concatStyleSets, IStyleSet, ITheme } from '@uifabric/styling'; +import { Customizations, CustomizerContext, ICustomizerContext } from '@uifabric/utilities'; import { assign } from './utilities'; -export type Omit = Pick>; - -/** - * Optional props for styleable components. If these props are present, they will automatically be - * used by Foundation when applying theming and styling. - */ -export interface IStyleableComponentProps> { - styles?: IStyleFunctionOrObject; - theme?: ITheme; -} - -/** - * Props added by Foundation for styles functions. - */ -export interface IStyledProps { - theme: TTheme; -} - -/** - * Enforce props contract on state components, including the view prop and its shape. - */ -export type IStateComponentProps = TComponentProps & { - renderView: React.StatelessComponent; -}; - -/** - * Imposed state component props contract with styling props as well as a renderView - * prop that the StateComponent should make use of in its render output (and should be its only render output.) - */ -export type IStateComponentType = React.ComponentType>; - -/** - * The props that get passed to view components. - */ -export type IViewComponentProps = TViewProps & { - classNames: TProcessedStyleSet; -}; - -/** - * A helper type for defining view components, including its properties. - */ -export type IViewComponent = React.StatelessComponent>; - -/** - * Component used by foundation to tie elements together. - * @see createComponent for generic type documentation. - */ -export interface IComponentOptions, TStatics = {}> { - /** - * Display name to identify component in React hierarchy. - */ - displayName: string; - /** - * List of fields which can be customized. - */ - fields?: string[]; - /** - * Styles prop to pass into component. - */ - styles?: IStyleFunctionOrObject, TStyleSet>; - /** - * React view stateless component. - */ - view: IViewComponent>; - /** - * Optional state component that processes TComponentProps into TViewProps. - */ - state?: IStateComponentType; - /** - * Optional static object to assign to constructed component. - */ - statics?: TStatics; -} - -// TODO: Known TypeScript issue is widening return type checks when using function type declarations. -// Effect is that mistyped property keys on returned style objects will not generate errors. -// This affects lookup types used as functional decorations on IComponent and IStatelessComponent, e.g.: -// export const styles: IStackComponent['styles'] = props => { -// Existing issue: https://github.com/Microsoft/TypeScript/issues/241 - -/** - * Variant of IComponentOptions for stateful components with appropriate typing and required properties. - */ -export type IComponent, TStatics = {}> = IComponentOptions< - TComponentProps, - TViewProps, - TStyleSet, - TStatics -> & - Required, 'state'>>; - -/** - * Variant of IComponentOptions for stateless components with appropriate typing and required properties. - */ -export type IStatelessComponent, TStatics = {}> = Omit< - IComponentOptions, - 'state' ->; +import { IComponent, ICustomizationProps, IStyleableComponentProps, IStylesFunctionOrObject, IToken } from './IComponent'; +import { IDefaultSlotProps } from './ISlots'; /** * Assembles a higher order component based on the following: styles, theme, view, and state. @@ -114,79 +17,67 @@ export type IStatelessComponent, TStatics = {}>( - component: IComponent -): React.StatelessComponent & TStatics { +export function createComponent< + TComponentProps, + TTokens, + TStyleSet extends IStyleSet, + TViewProps = TComponentProps, + TStatics = {} +>(component: IComponent): React.StatelessComponent & TStatics { const result: React.StatelessComponent = (componentProps: TComponentProps) => { return ( - // TODO: createComponent is also probably affected by https://github.com/OfficeDev/office-ui-fabric-react/issues/6603 + // TODO: createComponent is also affected by https://github.com/OfficeDev/office-ui-fabric-react/issues/6603 {(context: ICustomizerContext) => { - const settings: IStyleableComponentProps = _getCustomizations( + // TODO: this next line is basically saying 'theme' prop will ALWAYS be available from getCustomizations + // via ICustomizationProps cast. Is there mechanism that guarantees theme and other request fields will be defined? + // is there a static init that guarantees theme will be provided? + // what happens if createTheme/loadTheme is not called? + // if so, convey through getCustomizations typing keying off fields. can we convey this + // all the way from Customizations with something like { { K in fields }: object}? hmm + // if not, how does existing "theme!" styles code work without risk of failing (assuming it doesn't fail)? + // For now cast return value as if theme is always available. + const settings: ICustomizationProps = _getCustomizations( component.displayName, context, component.fields ); - const renderView = (viewProps?: TViewProps) => { + const renderView = (viewProps?: TViewProps & IStyleableComponentProps) => { // The approach here is to allow state components to provide only the props they care about, automatically // merging user props and state props together. This ensures all props are passed properly to view, // including children and styles. - // What we really need to be able to do here either type force TViewProps to be TComponentProps when StateComponent - // is undefined OR logically something like code below. Until we figure out how to do this, cast mergedProps as - // IStyleableComponentProps since componentProps does not necessarily extend TViewProps. Until then we're sacrificing - // a bit of type safety to prevent the need of duplicating this function. - // if (StateComponent) { - // type TViewProps = TViewProps; - // } else { - // type TViewProps = TComponentProps; - // } // TODO: for full 'fields' support, 'rest' props from customizations need to pass onto view. // however, customized props like theme will break snapshots. how is styled not showing theme output in snapshots? - const mergedProps: IStyleableComponentProps = viewProps + const mergedProps: TViewProps & IStyleableComponentProps = viewProps ? { ...(componentProps as any), ...(viewProps as any) } : componentProps; - const { styles: settingsStyles, ...settingsRest } = settings; - // TODO: this next line is basically saying 'theme' prop will ALWAYS be available from getCustomizations. - // is there mechanism that guarantees theme and other request fields will be defined? - // is there a static init that guarantees theme will be provided? - // what happens if createTheme/loadTheme is not called? - // if so, convey through getCustomizations typing keying off fields. can we convey this - // all the way from Customizations with something like { { K in fields }: object}? hmm - // if not, how does existing "theme!" styles code work without risk of failing (assuming it doesn't fail)? - // For now cast return value as if theme is always available. - const styledProps: TViewProps & IStyledProps = { ...settingsRest, ...(mergedProps as any) }; - const viewComponentProps: IViewComponentProps> = { + const theme = mergedProps.theme || settings.theme; + + const tokens = _resolveTokens(mergedProps, theme, component.tokens, settings.tokens, mergedProps.tokens); + const styles = _resolveStyles(mergedProps, theme, tokens, component.styles, settings.styles, mergedProps.styles); + + const viewComponentProps: typeof mergedProps & IDefaultSlotProps = { ...(mergedProps as any), - ...{ - classNames: mergeStyleSets( - _evaluateStyle(styledProps, component.styles), - _evaluateStyle(styledProps, settingsStyles), - _evaluateStyle(styledProps, mergedProps.styles) - ) - } + _defaultStyles: styles }; - // If a new context has been generated, instantiate a Provider to provide it. - return component.view(viewComponentProps); + return ; }; return component.state ? : renderView(); }} @@ -203,28 +94,44 @@ export function createComponent, TStatics = {}>( - component: IStatelessComponent -): React.StatelessComponent & TStatics { - return createComponent(component as IComponent); +function _resolveStyles>( + props: TProps, + theme: ITheme, + tokens: TTokens, + ...allStyles: (IStylesFunctionOrObject | undefined)[] +): ReturnType { + return concatStyleSets( + ...allStyles.map((styles: IStylesFunctionOrObject | undefined) => + typeof styles === 'function' ? styles(props, theme, tokens) : styles + ) + ); } /** - * Evaluate styles based on type to return consistent TStyleSet. + * Resolve all tokens functions with props flatten results along with all tokens objects. */ -function _evaluateStyle, TStyleSet extends IStyleSet>( - props: TViewProps & TStyledProps, - styles?: IStyleFunctionOrObject -): Partial | undefined { - if (typeof styles === 'function') { - return styles(props); +function _resolveTokens( + props: TViewProps, + theme: ITheme, + ...allTokens: (IToken | false | null | undefined)[] +): TTokens { + const tokens = {}; + + for (let currentTokens of allTokens) { + if (currentTokens) { + currentTokens = typeof currentTokens === 'function' ? currentTokens(props, theme) : currentTokens; + + if (Array.isArray(currentTokens)) { + currentTokens = _resolveTokens(props, theme, ...currentTokens); + } + + assign(tokens, ...(currentTokens as any)); + } } - return styles; + return tokens as TTokens; } /** @@ -232,15 +139,16 @@ function _evaluateStyle, T * * @param displayName Displayable name for component. * @param context React context passed to component containing contextual settings. - * @param fields Optional list of properties of to grab from global store and context. + * @param fields Optional list of properties to grab from global store and context. */ -function _getCustomizations>( +function _getCustomizations>( displayName: string, context: ICustomizerContext, fields?: string[] -): IStyleableComponentProps { +): ICustomizationProps { // TODO: do we want field props? should fields be part of IComponent and used here? // TODO: should we centrally define DefaultFields? (not exported from styling) - const DefaultFields = ['theme', 'styles', 'styleVariables']; + // TOOD: tie this array to ICustomizationProps, such that each array element is keyof ICustomizationProps + const DefaultFields = ['theme', 'styles', 'tokens']; return Customizations.getSettings(fields || DefaultFields, displayName, context.customizations); } diff --git a/packages/foundation/src/index.ts b/packages/foundation/src/index.ts index 18c490f00368d2..f495b058a3a2de 100644 --- a/packages/foundation/src/index.ts +++ b/packages/foundation/src/index.ts @@ -1,4 +1,9 @@ export * from './createComponent'; +export * from './createComponent'; +export * from './IComponent'; +export * from './IHTMLSlots'; +export * from './ISlots'; +export * from './slots'; export * from './ThemeProvider'; import './version'; diff --git a/packages/experiments/src/utilities/slots.test.tsx b/packages/foundation/src/slots.test.tsx similarity index 100% rename from packages/experiments/src/utilities/slots.test.tsx rename to packages/foundation/src/slots.test.tsx diff --git a/packages/experiments/src/utilities/slots.tsx b/packages/foundation/src/slots.tsx similarity index 100% rename from packages/experiments/src/utilities/slots.tsx rename to packages/foundation/src/slots.tsx diff --git a/packages/foundation/tslint.json b/packages/foundation/tslint.json index be9508c1adfdde..4d3ea9453a27a2 100644 --- a/packages/foundation/tslint.json +++ b/packages/foundation/tslint.json @@ -1,6 +1,11 @@ { - "extends": ["@uifabric/tslint-rules"], + "extends": [ + "@uifabric/tslint-rules" + ], "rules": { - "no-any": false + "no-any": false, + "typedef": [ + false + ] } -} +} \ No newline at end of file diff --git a/scripts/create-component.js b/scripts/create-component.js index a378e47370b695..18663cdec4cf84 100644 --- a/scripts/create-component.js +++ b/scripts/create-component.js @@ -2,13 +2,12 @@ const mustache = require('mustache'); const argv = require('yargs').argv; const newComponentName = argv.name; -const slots = argv.slots; const stateless = argv.stateless; const fs = require('fs'); // Template Sequences -const statefulSequence = ['GlobalIndex', 'Styles', 'Index', 'View', 'ViewTest', 'Types', 'Component', 'State', 'StateTest']; -const statelessSequence = ['GlobalIndex', 'Styles', 'Index', 'View', 'ViewTest', 'TypesStateless', 'ComponentStateless']; +const statefulSequence = ['GlobalIndex', 'Styles', 'Test', 'Index', 'View', 'ViewTest', 'Types', 'Component', 'State', 'StateTest']; +const statelessSequence = ['GlobalIndex', 'Styles', 'Test', 'Index', 'View', 'ViewTest', 'TypesStateless', 'ComponentStateless']; // Paths/File Names const rootComponentFolderPath = './packages/experiments/src/'; @@ -25,6 +24,7 @@ const outputFiles = { State: componentPathNamePrefix + '.state.ts', StateTest: componentPathNamePrefix + '.state.test.tsx', Styles: componentPathNamePrefix + '.styles.ts', + Test: componentPathNamePrefix + '.test.tsx', Types: componentPathNamePrefix + '.types.ts', TypesStateless: componentPathNamePrefix + '.types.ts', View: componentPathNamePrefix + '.view.tsx', @@ -99,11 +99,6 @@ function makeComponent(error) { return; } - // TODO: obsolete existing templates and promote "slots" templates upon slots promotion - if (slots) { - templateFolderPath += '/slots'; - } - if (stateless) { console.log('Creating stateless component...'); createComponentFiles(statelessSequence, 0); diff --git a/scripts/templates/create-component/EmptyComponent.mustache b/scripts/templates/create-component/EmptyComponent.mustache index da20e63b4d5cbe..9f1495d789d11b 100644 --- a/scripts/templates/create-component/EmptyComponent.mustache +++ b/scripts/templates/create-component/EmptyComponent.mustache @@ -1,16 +1,13 @@ import { {{componentName}}View } from './{{componentName}}.view'; -import { {{componentName}}Styles } from './{{componentName}}.styles'; +import { {{componentName}}Styles, {{componentName}}Tokens } from './{{componentName}}.styles'; import { {{componentName}}State } from './{{componentName}}.state'; -import { I{{componentName}}Props, I{{componentName}}ViewProps, I{{componentName}}Styles } from './{{componentName}}.types'; +import { I{{componentName}}Props } from './{{componentName}}.types'; import { createComponent } from '../../Foundation'; -export const {{componentName}}: React.StatelessComponent< I{{componentName}}Props> = createComponent< - I{{componentName}}Props, - I{{componentName}}ViewProps, - I{{componentName}}Styles ->({ +export const {{componentName}}: React.StatelessComponent = createComponent({ displayName: '{{componentName}}', view: {{componentName}}View, state: {{componentName}}State, - styles: {{componentName}}Styles -}); \ No newline at end of file + styles: {{componentName}}Styles, + tokens: {{componentName}}Tokens +}); diff --git a/scripts/templates/create-component/EmptyComponentStateless.mustache b/scripts/templates/create-component/EmptyComponentStateless.mustache index cbd6d19117f76e..5ea643267ad92c 100644 --- a/scripts/templates/create-component/EmptyComponentStateless.mustache +++ b/scripts/templates/create-component/EmptyComponentStateless.mustache @@ -1,13 +1,11 @@ import { {{componentName}}View } from './{{componentName}}.view'; -import { {{componentName}}Styles } from './{{componentName}}.styles'; -import { I{{componentName}}Props, I{{componentName}}Styles } from './{{componentName}}.types'; -import { createStatelessComponent } from '../../Foundation'; +import { {{componentName}}Styles, {{componentName}}Tokens } from './{{componentName}}.styles'; +import { I{{componentName}}Props } from './{{componentName}}.types'; +import { createComponent } from '../../Foundation'; -export const {{componentName}}: React.StatelessComponent< I{{componentName}}Props> = createStatelessComponent< - I{{componentName}}Props, - I{{componentName}}Styles ->({ +export const {{componentName}}: React.StatelessComponent = createComponent({ displayName: '{{componentName}}', view: {{componentName}}View, - styles: {{componentName}}Styles -}); \ No newline at end of file + styles: {{componentName}}Styles, + tokens: {{componentName}}Tokens +}); diff --git a/scripts/templates/create-component/EmptyState.mustache b/scripts/templates/create-component/EmptyState.mustache index 0aad78eda31980..657b7ca29e1e0a 100644 --- a/scripts/templates/create-component/EmptyState.mustache +++ b/scripts/templates/create-component/EmptyState.mustache @@ -4,7 +4,7 @@ import { BaseState } from '../../utilities/BaseState'; // Internal state will most likely include a subset of your ViewProps. This template just equates them to start with. export type I{{componentName}}State = I{{componentName}}ViewProps; -export class {{componentName}}State extends BaseState< I{{componentName}}Props, I{{componentName}}ViewProps, I{{componentName}}State > { +export class {{componentName}}State extends BaseState { constructor(props: {{componentName}}State['props']) { super(props, { // Mark controlledProps to ensure that they get priority when provided as a component prop. diff --git a/scripts/templates/create-component/EmptyStateTest.mustache b/scripts/templates/create-component/EmptyStateTest.mustache index 82d9c3c2c4cc9f..a7a191368e2070 100644 --- a/scripts/templates/create-component/EmptyStateTest.mustache +++ b/scripts/templates/create-component/EmptyStateTest.mustache @@ -7,11 +7,11 @@ const testView = () => { }; // States do not generate rendered output so unit tests test that state initializes and reacts -// to inputs and events as expected. +// to inputs and events as expected. describe('{{componentName}}State', () => { it('initializes default state correctly', () => { - const test{{componentName}}State = mount(< {{componentName}}State renderView={testView} />); + const test{{componentName}}State = mount(<{{componentName}}State renderView={testView} />); expect(test{{componentName}}State.state('text')).toBe('Default Text'); expect(test{{componentName}}State.state('status')).toBe('State Text'); @@ -19,7 +19,7 @@ describe('{{componentName}}State', () => { it('uses props to initialize state correctly', () => { const defaultText = 'Prop Default Text'; - const test{{componentName}}State = mount(< {{componentName}}State defaultText={defaultText} renderView={testView} />); + const test{{componentName}}State = mount(<{{componentName}}State defaultText={defaultText} renderView={testView} />); expect(test{{componentName}}State.state('text')).toBe(defaultText); }); diff --git a/scripts/templates/create-component/EmptyStyles.mustache b/scripts/templates/create-component/EmptyStyles.mustache index a6095ebb81573f..2f0dcf6300c20a 100644 --- a/scripts/templates/create-component/EmptyStyles.mustache +++ b/scripts/templates/create-component/EmptyStyles.mustache @@ -1,20 +1,45 @@ -import { I{{componentName}}Component } from './{{componentName}}.types'; +import { + I{{componentName}}Component, + I{{componentName}}StylesReturnType, + I{{componentName}}TokenReturnType +} from './{{componentName}}.types'; import { getGlobalClassNames } from '../../Styling'; const GlobalClassNames = { - root: 'ms-{{componentName}}' + root: 'ms-{{componentName}}', + text: 'ms-{{componentName}}-text', }; -export const {{componentName}}Styles: I{{componentName}}Component['styles'] = props => { - const { theme } = props; +const baseTokens: I{{componentName}}Component['tokens'] = { + textColor: 'blue' +}; + +const warningTokens: I{{componentName}}Component['tokens'] = { + textColor: 'red' +}; +export const {{componentName}}Tokens: I{{componentName}}Component['tokens'] = (props, theme): I{{componentName}}TokenReturnType => [ + baseTokens, + props.warning && warningTokens +]; + +export const {{componentName}}Styles: I{{componentName}}Component['styles'] = (props, theme, tokens): I{{componentName}}StylesReturnType => { const classNames = getGlobalClassNames(GlobalClassNames, theme); return { root: [ classNames.root, { - color: props.theme.semanticColors.bodyText + borderWidth: '1px', + borderStyle: 'solid', + margin: 8, + padding: 8 + } + ], + text: [ + classNames.text, + { + color: tokens.textColor } ] }; diff --git a/scripts/templates/create-component/EmptyTest.mustache b/scripts/templates/create-component/EmptyTest.mustache new file mode 100644 index 00000000000000..5c3ed3e78d0bc6 --- /dev/null +++ b/scripts/templates/create-component/EmptyTest.mustache @@ -0,0 +1,24 @@ +import * as React from 'react'; +import * as renderer from 'react-test-renderer'; + +import { {{componentName}} } from './{{componentName}}'; + +describe('{{componentName}}', () => { + it('renders correctly with no props', () => { + const tree = renderer + .create( + <{{componentName}} /> + ) + .toJSON(); + expect(tree).toMatchSnapshot(); + }); + + it('renders text prop correctly', () => { + const tree = renderer + .create( + <{{componentName}} text="textProp" /> + ) + .toJSON(); + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/scripts/templates/create-component/EmptyTypes.mustache b/scripts/templates/create-component/EmptyTypes.mustache index 8f9f3c6f0abbc2..ff3c9dea0e46a7 100644 --- a/scripts/templates/create-component/EmptyTypes.mustache +++ b/scripts/templates/create-component/EmptyTypes.mustache @@ -1,45 +1,63 @@ -import { IStyle } from 'office-ui-fabric-react/lib/Styling'; -import { IComponent, IStyleableComponentProps } from '../../Foundation'; +import { IComponent, IComponentStyles, IHTMLSlot, IStyleableComponentProps } from '../../Foundation'; +import { ITextSlot } from '../../Text'; +import { IBaseProps } from '../../Utilities'; -export type I{{componentName}}Component = IComponent< I{{componentName}}Props, I{{componentName}}ViewProps, I{{componentName}}Styles>; +export type I{{componentName}}Component = + IComponent; -// Extending IStyleableComponentProps will automatically add stylable props for you, such as styles and theme. -// If you don't want these props to be included in your component, just remove this extension. -export interface I{{componentName}}Props extends IStyleableComponentProps< I{{componentName}}ViewProps, I{{componentName}}Styles> { +// These types are redundant with I{{componentName}}Component but are needed until TS function return widening issue is resolved: +// https://github.com/Microsoft/TypeScript/issues/241 +// For now, these helper types can be used to provide return type safety for tokens and styles functions. +export type I{{componentName}}TokenReturnType = ReturnType>; +export type I{{componentName}}StylesReturnType = ReturnType>; + +// Optional interface to use for componentRef. This should be limited in scope with the most common scenario being for focusing elements. +export interface I{{componentName}} { } + +export interface I{{componentName}}Slots { // All props for your component are to be defined here. /** - * Component sample default prop for use if component is uncontrolled. - * @default 'Default Text' + * Root element. */ - defaultText?: string; + root?: IHTMLSlot; /** * Component sample prop. If provided, component is controlled. - * @default defaultText + * @defaultValue defaultText */ - text?: string; + text?: ITextSlot; } -export type I{{componentName}}ViewProps = - // You can carry over props from your I{{componentName}}Props interface here. - // Typically props are required by the time they trickle down to the view, - // which is reflected by using 'Required'. - Required< Pick< I{{componentName}}Props, 'text'>> & - // You can also optionally include props by removing 'Required': - // Pick< I{{componentName}}Props, 'text'>> & - { - // You can define view only props here. - /** - * Sample prop internal to component. These types of props aren't exposed - * externally to consumers and their values are typically determined by component state. - */ - status: string; - }; - -export interface I{{componentName}}Styles { +// Extending IStyleableComponentProps will automatically add styleable props for you, such as styles, tokens and theme. +// If you don't want these props to be included in your component, just remove this extension. +export interface I{{componentName}}Props extends + I{{componentName}}Slots, + IStyleableComponentProps, + IBaseProps { + // All props for your component are to be defined here. /** - * Styling for the root element. + * Component sample default prop for use if component is uncontrolled. + * @defaultValue 'Default Text' */ - root: IStyle; - // Add other styles elements for your component here. + defaultText?: string; + + // Setting this prop to true will apply different styling to the text slot. + warning?: boolean; +} + +export interface I{{componentName}}ViewProps extends I{{componentName}}Props { + // You can define view only props here. + /** + * Sample prop internal to component. These types of props aren't exposed + * externally to consumers and their values are typically determined by component state. + */ + status: string; } + +export interface I{{componentName}}Tokens { + // Define tokens for your component here. Tokens are styling 'knobs' that your component will automatically + // apply to styling sections in the styles file. + textColor?: string; +} + +export type I{{componentName}}Styles = IComponentStyles; diff --git a/scripts/templates/create-component/EmptyTypesStateless.mustache b/scripts/templates/create-component/EmptyTypesStateless.mustache index fa1f0c1650fa34..c626b1fa730a6c 100644 --- a/scripts/templates/create-component/EmptyTypesStateless.mustache +++ b/scripts/templates/create-component/EmptyTypesStateless.mustache @@ -1,27 +1,50 @@ -import { IStyle } from 'office-ui-fabric-react/lib/Styling'; -import { IStatelessComponent, IStyleableComponentProps } from '../../Foundation'; +import { IComponent, IComponentStyles, IHTMLSlot, IStyleableComponentProps } from '../../Foundation'; +import { ITextSlot } from '../../Text'; +import { IBaseProps } from '../../Utilities'; -export type I{{componentName}}Component = IStatelessComponent< I{{componentName}}Props, I{{componentName}}Styles>; +export type I{{componentName}}Component = IComponent; -// Extending IStyleableComponentProps will automatically add stylable props for you, such as styles and theme. -// If you don't want these props to be included in your component, just remove this extension. -export interface I{{componentName}}Props extends IStyleableComponentProps< I{{componentName}}Props, I{{componentName}}Styles> { +// These types are redundant with I{{componentName}}Component but are needed until TS function return widening issue is resolved: +// https://github.com/Microsoft/TypeScript/issues/241 +// For now, these helper types can be used to provide return type safety for tokens and styles functions. +export type I{{componentName}}TokenReturnType = ReturnType>; +export type I{{componentName}}StylesReturnType = ReturnType>; + +// Optional interface to use for componentRef. This should be limited in scope with the most common scenario being for focusing elements. +export interface I{{componentName}} { } + +export interface I{{componentName}}Slots { // All props for your component are to be defined here. + /** + * Root element. + */ + root?: IHTMLSlot; + /** * Component sample prop. If provided, component is controlled. - * @default defaultText + * @defaultValue defaultText */ - text: string; + text?: ITextSlot; +} + +// Extending IStyleableComponentProps will automatically add styleable props for you, such as styles and theme. +// If you don't want these props to be included in your component, just remove this extension. +export interface I{{componentName}}Props extends + I{{componentName}}Slots, + IStyleableComponentProps, + IBaseProps { + // All props for your component are to be defined here. /** * Component sample prop. */ status?: string; + warning?: boolean; } -export interface I{{componentName}}Styles { - /** - * Styling for the root element. - */ - root: IStyle; - // Add other styles elements for your component here. +export interface I{{componentName}}Tokens { + // Define tokens for your component here. Tokens are styling 'knobs' that your component will automatically + // apply to styling sections in the styles file. + textColor?: string; } + +export type I{{componentName}}Styles = IComponentStyles; diff --git a/scripts/templates/create-component/EmptyView.mustache b/scripts/templates/create-component/EmptyView.mustache index 6418d8b03da525..8888c2bdc098e4 100644 --- a/scripts/templates/create-component/EmptyView.mustache +++ b/scripts/templates/create-component/EmptyView.mustache @@ -1,12 +1,20 @@ -import * as React from 'react'; +/** @jsx withSlots */ +import { withSlots, getSlots } from '../../Foundation'; +import { Text } from '../../Text'; -import { I{{componentName}}Component } from './{{componentName}}.types'; +import { I{{componentName}}Component, I{{componentName}}Props, I{{componentName}}Slots } from './{{componentName}}.types'; export const {{componentName}}View: I{{componentName}}Component['view'] = props => { - return( -
-
Text: {props.text}
-
Status: {props.status}
-
+ + const Slots = getSlots(props, { + root: 'div', + text: Text + }); + + return ( + + + Status: {props.status} + ); }; \ No newline at end of file diff --git a/scripts/templates/create-component/EmptyViewTest.mustache b/scripts/templates/create-component/EmptyViewTest.mustache index 8c5cbee45eb6e2..cd83c7aa3e0e7a 100644 --- a/scripts/templates/create-component/EmptyViewTest.mustache +++ b/scripts/templates/create-component/EmptyViewTest.mustache @@ -1,24 +1,21 @@ import * as React from 'react'; import * as renderer from 'react-test-renderer'; -import { I{{componentName}}Styles } from './{{componentName}}.types'; import { {{componentName}}View } from './{{componentName}}.view'; -import { IProcessedStyleSet } from 'office-ui-fabric-react/lib/Styling'; -// These tests will ensure that your styles regions have classname representation in snapshot output. -// (Also, classNames is a required prop for views, so we have to supply it for tests.) -const test{{componentName}}ClassNames: IProcessedStyleSet< I{{componentName}}Styles> = { - root: 'test-cn-root', - subComponentStyles: {} -}; +// PLEASE NOTE: +// It's recommended that you do snapshot tests against the component (see {{componentName}}Test.tsx) +// in order to capture styling and guard against styling regressions. However, view tests can be helpful for testing +// view-only props that are not exposed via the component's API. (If styling is important for view props cases, consider +// a component test exercising these props as well.) // Views are just pure functions with no statefulness, which means they can get full code coverage -// with snapshot tests exercising permutations of the props. +// with snapshot tests exercising permutations of the props. describe('{{componentName}}View', () => { - it('renders correctly', () => { + it('renders status view prop correctly', () => { const tree = renderer .create( - < {{componentName}}View text="textProp" status="statusProp" classNames={test{{componentName}}ClassNames} /> + <{{componentName}}View status="statusProp" /> ) .toJSON(); expect(tree).toMatchSnapshot(); diff --git a/scripts/templates/create-component/slots/EmptyComponent.mustache b/scripts/templates/create-component/slots/EmptyComponent.mustache deleted file mode 100644 index 494c3f6b5be9bd..00000000000000 --- a/scripts/templates/create-component/slots/EmptyComponent.mustache +++ /dev/null @@ -1,13 +0,0 @@ -import { {{componentName}}View } from './{{componentName}}.view'; -import { {{componentName}}Styles, {{componentName}}Tokens } from './{{componentName}}.styles'; -import { {{componentName}}State } from './{{componentName}}.state'; -import { I{{componentName}}Props } from './{{componentName}}.types'; -import { createComponent } from '../../Foundation'; - -export const {{componentName}}: React.StatelessComponent< I{{componentName}}Props> = createComponent({ - displayName: '{{componentName}}', - view: {{componentName}}View, - state: {{componentName}}State, - styles: {{componentName}}Styles, - tokens: {{componentName}}Tokens -}); diff --git a/scripts/templates/create-component/slots/EmptyComponentStateless.mustache b/scripts/templates/create-component/slots/EmptyComponentStateless.mustache deleted file mode 100644 index 865ce6b6794d33..00000000000000 --- a/scripts/templates/create-component/slots/EmptyComponentStateless.mustache +++ /dev/null @@ -1,11 +0,0 @@ -import { {{componentName}}View } from './{{componentName}}.view'; -import { {{componentName}}Styles, {{componentName}}Tokens } from './{{componentName}}.styles'; -import { I{{componentName}}Props } from './{{componentName}}.types'; -import { createComponent } from '../../Foundation'; - -export const {{componentName}}: React.StatelessComponent< I{{componentName}}Props> = createComponent({ - displayName: '{{componentName}}', - view: {{componentName}}View, - styles: {{componentName}}Styles, - tokens: {{componentName}}Tokens -}); diff --git a/scripts/templates/create-component/slots/EmptyGlobalIndex.mustache b/scripts/templates/create-component/slots/EmptyGlobalIndex.mustache deleted file mode 100644 index fb2898a489862f..00000000000000 --- a/scripts/templates/create-component/slots/EmptyGlobalIndex.mustache +++ /dev/null @@ -1 +0,0 @@ -export * from './components/{{componentName}}/index'; \ No newline at end of file diff --git a/scripts/templates/create-component/slots/EmptyIndex.mustache b/scripts/templates/create-component/slots/EmptyIndex.mustache deleted file mode 100644 index bcb93e78c51323..00000000000000 --- a/scripts/templates/create-component/slots/EmptyIndex.mustache +++ /dev/null @@ -1,2 +0,0 @@ -export * from './{{componentName}}'; -export * from './{{componentName}}.types'; \ No newline at end of file diff --git a/scripts/templates/create-component/slots/EmptyState.mustache b/scripts/templates/create-component/slots/EmptyState.mustache deleted file mode 100644 index 0aad78eda31980..00000000000000 --- a/scripts/templates/create-component/slots/EmptyState.mustache +++ /dev/null @@ -1,20 +0,0 @@ -import { I{{componentName}}Props, I{{componentName}}ViewProps } from './{{componentName}}.types'; -import { BaseState } from '../../utilities/BaseState'; - -// Internal state will most likely include a subset of your ViewProps. This template just equates them to start with. -export type I{{componentName}}State = I{{componentName}}ViewProps; - -export class {{componentName}}State extends BaseState< I{{componentName}}Props, I{{componentName}}ViewProps, I{{componentName}}State > { - constructor(props: {{componentName}}State['props']) { - super(props, { - // Mark controlledProps to ensure that they get priority when provided as a component prop. - // For props not marked controlled, component state will get priority over component props. - controlledProps: ['text'] - }); - - this.state = { - text: props.defaultText || 'Default Text', - status: 'State Text' - }; - } -} diff --git a/scripts/templates/create-component/slots/EmptyStateTest.mustache b/scripts/templates/create-component/slots/EmptyStateTest.mustache deleted file mode 100644 index 82d9c3c2c4cc9f..00000000000000 --- a/scripts/templates/create-component/slots/EmptyStateTest.mustache +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from 'react'; -import { mount } from 'enzyme'; -import { {{componentName}}State } from './{{componentName}}.state'; - -const testView = () => { - return
; -}; - -// States do not generate rendered output so unit tests test that state initializes and reacts -// to inputs and events as expected. -describe('{{componentName}}State', () => { - it('initializes default state correctly', () => { - - const test{{componentName}}State = mount(< {{componentName}}State renderView={testView} />); - - expect(test{{componentName}}State.state('text')).toBe('Default Text'); - expect(test{{componentName}}State.state('status')).toBe('State Text'); - }); - - it('uses props to initialize state correctly', () => { - const defaultText = 'Prop Default Text'; - const test{{componentName}}State = mount(< {{componentName}}State defaultText={defaultText} renderView={testView} />); - - expect(test{{componentName}}State.state('text')).toBe(defaultText); - }); -}); diff --git a/scripts/templates/create-component/slots/EmptyStyles.mustache b/scripts/templates/create-component/slots/EmptyStyles.mustache deleted file mode 100644 index 2f0dcf6300c20a..00000000000000 --- a/scripts/templates/create-component/slots/EmptyStyles.mustache +++ /dev/null @@ -1,46 +0,0 @@ -import { - I{{componentName}}Component, - I{{componentName}}StylesReturnType, - I{{componentName}}TokenReturnType -} from './{{componentName}}.types'; -import { getGlobalClassNames } from '../../Styling'; - -const GlobalClassNames = { - root: 'ms-{{componentName}}', - text: 'ms-{{componentName}}-text', -}; - -const baseTokens: I{{componentName}}Component['tokens'] = { - textColor: 'blue' -}; - -const warningTokens: I{{componentName}}Component['tokens'] = { - textColor: 'red' -}; - -export const {{componentName}}Tokens: I{{componentName}}Component['tokens'] = (props, theme): I{{componentName}}TokenReturnType => [ - baseTokens, - props.warning && warningTokens -]; - -export const {{componentName}}Styles: I{{componentName}}Component['styles'] = (props, theme, tokens): I{{componentName}}StylesReturnType => { - const classNames = getGlobalClassNames(GlobalClassNames, theme); - - return { - root: [ - classNames.root, - { - borderWidth: '1px', - borderStyle: 'solid', - margin: 8, - padding: 8 - } - ], - text: [ - classNames.text, - { - color: tokens.textColor - } - ] - }; -}; diff --git a/scripts/templates/create-component/slots/EmptyTypes.mustache b/scripts/templates/create-component/slots/EmptyTypes.mustache deleted file mode 100644 index 8298a273675493..00000000000000 --- a/scripts/templates/create-component/slots/EmptyTypes.mustache +++ /dev/null @@ -1,63 +0,0 @@ -import { IComponent, IComponentStyles, IHTMLDivSlot, IStyleableComponentProps } from '../../Foundation'; -import { ITextSlot } from '../../Text'; -import { IBaseProps } from '../../Utilities'; - -export type I{{componentName}}Component = - IComponent< I{{componentName}}Props, I{{componentName}}Tokens, I{{componentName}}Styles, I{{componentName}}ViewProps>; - -// These types are redundant with I{{componentName}}Component but are needed until TS function return widening issue is resolved: -// https://github.com/Microsoft/TypeScript/issues/241 -// For now, these helper types can be used to provide return type safety for tokens and styles functions. -export type I{{componentName}}TokenReturnType = ReturnType< Extract< I{{componentName}}Component['tokens'], Function>>; -export type I{{componentName}}StylesReturnType = ReturnType< Extract< I{{componentName}}Component['styles'], Function>>; - -// Optional interface to use for componentRef. This should be limited in scope with the most common scenario being for focusing elements. -export interface I{{componentName}} { } - -export interface I{{componentName}}Slots { - // All props for your component are to be defined here. - /** - * Root element. - */ - root?: IHTMLDivSlot; - - /** - * Component sample prop. If provided, component is controlled. - * @default defaultText - */ - text?: ITextSlot; -} - -// Extending IStyleableComponentProps will automatically add styleable props for you, such as styles, tokens and theme. -// If you don't want these props to be included in your component, just remove this extension. -export interface I{{componentName}}Props extends - I{{componentName}}Slots, - IStyleableComponentProps< I{{componentName}}ViewProps, I{{componentName}}Tokens, I{{componentName}}Styles>, - IBaseProps< I{{componentName}}> { - // All props for your component are to be defined here. - /** - * Component sample default prop for use if component is uncontrolled. - * @default 'Default Text' - */ - defaultText?: string; - - // Setting this prop to true will apply different styling to the text slot. - warning?: boolean; -} - -export interface I{{componentName}}ViewProps extends I{{componentName}}Props { - // You can define view only props here. - /** - * Sample prop internal to component. These types of props aren't exposed - * externally to consumers and their values are typically determined by component state. - */ - status: string; -} - -export interface I{{componentName}}Tokens { - // Define tokens for your component here. Tokens are styling 'knobs' that your component will automatically - // apply to styling sections in the styles file. - textColor?: string; -} - -export type I{{componentName}}Styles = IComponentStyles< I{{componentName}}Slots>; diff --git a/scripts/templates/create-component/slots/EmptyTypesStateless.mustache b/scripts/templates/create-component/slots/EmptyTypesStateless.mustache deleted file mode 100644 index 8de7c7f5c6736e..00000000000000 --- a/scripts/templates/create-component/slots/EmptyTypesStateless.mustache +++ /dev/null @@ -1,50 +0,0 @@ -import { IComponent, IComponentStyles, IHTMLDivSlot, IStyleableComponentProps } from '../../Foundation'; -import { ITextSlot } from '../../Text'; -import { IBaseProps } from '../../Utilities'; - -export type I{{componentName}}Component = IComponent< I{{componentName}}Props, I{{componentName}}Tokens, I{{componentName}}Styles>; - -// These types are redundant with I{{componentName}}Component but are needed until TS function return widening issue is resolved: -// https://github.com/Microsoft/TypeScript/issues/241 -// For now, these helper types can be used to provide return type safety for tokens and styles functions. -export type I{{componentName}}TokenReturnType = ReturnType< Extract< I{{componentName}}Component['tokens'], Function>>; -export type I{{componentName}}StylesReturnType = ReturnType< Extract< I{{componentName}}Component['styles'], Function>>; - -// Optional interface to use for componentRef. This should be limited in scope with the most common scenario being for focusing elements. -export interface I{{componentName}} { } - -export interface I{{componentName}}Slots { - // All props for your component are to be defined here. - /** - * Root element. - */ - root?: IHTMLDivSlot; - - /** - * Component sample prop. If provided, component is controlled. - * @default defaultText - */ - text?: ITextSlot; -} - -// Extending IStyleableComponentProps will automatically add stylable props for you, such as styles and theme. -// If you don't want these props to be included in your component, just remove this extension. -export interface I{{componentName}}Props extends - I{{componentName}}Slots, - IStyleableComponentProps< I{{componentName}}Props, I{{componentName}}Tokens, I{{componentName}}Styles>, - IBaseProps< I{{componentName}}> { - // All props for your component are to be defined here. - /** - * Component sample prop. - */ - status?: string; - warning?: boolean; -} - -export interface I{{componentName}}Tokens { - // Define tokens for your component here. Tokens are styling 'knobs' that your component will automatically - // apply to styling sections in the styles file. - textColor?: string; -} - -export type I{{componentName}}Styles = IComponentStyles< I{{componentName}}Slots>; diff --git a/scripts/templates/create-component/slots/EmptyView.mustache b/scripts/templates/create-component/slots/EmptyView.mustache deleted file mode 100644 index 10a92314cadc9a..00000000000000 --- a/scripts/templates/create-component/slots/EmptyView.mustache +++ /dev/null @@ -1,20 +0,0 @@ -/** @jsx withSlots */ -import { withSlots, getSlots } from '../../Foundation'; -import { Text } from '../../Text'; - -import { I{{componentName}}Component, I{{componentName}}Props, I{{componentName}}Slots } from './{{componentName}}.types'; - -export const {{componentName}}View: I{{componentName}}Component['view'] = props => { - - const Slots = getSlots< I{{componentName}}Props, I{{componentName}}Slots>(props, { - root: 'div', - text: Text - }); - - return ( - < Slots.root> - < Slots.text /> - Status: {props.status} - - ); -}; \ No newline at end of file diff --git a/scripts/templates/create-component/slots/EmptyViewTest.mustache b/scripts/templates/create-component/slots/EmptyViewTest.mustache deleted file mode 100644 index bf09cd1e85abff..00000000000000 --- a/scripts/templates/create-component/slots/EmptyViewTest.mustache +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; -import * as renderer from 'react-test-renderer'; - -import { {{componentName}}View } from './{{componentName}}.view'; - -// Views are just pure functions with no statefulness, which means they can get full code coverage -// with snapshot tests exercising permutations of the props. -describe('{{componentName}}View', () => { - it('renders correctly', () => { - const tree = renderer - .create( - < {{componentName}}View text="textProp" status="statusProp" /> - ) - .toJSON(); - expect(tree).toMatchSnapshot(); - }); -});