import type { MergeOptions } from '@fluentui-react-native/framework-base'; import { immutableMergeCore } from '@fluentui-react-native/framework-base'; import type { LegacyFunctionComponent, StagedComponent } from '@fluentui-react-native/framework-base'; import { stagedComponent } from '@fluentui-react-native/framework-base'; import type { UseSlotOptions, Slots } from '@fluentui-react-native/use-slots'; import { buildUseSlots } from '@fluentui-react-native/use-slots'; import { buildUseStyling } from '@fluentui-react-native/use-styling'; import type { UseStylingOptions, TokenSettings, ThemeHelper, HasLayer } from '@fluentui-react-native/use-styling'; export type UseStyledSlots = (props: TProps, lookup?: HasLayer) => Slots; export type ComposeFactoryOptions = UseStylingOptions< TProps, TSlotProps, TTokens, TTheme > & UseSlotOptions & { /** * Includes from UseStylingOptions: * */ displayName?: string; /** * staged render function that takes props and a useSlots hook as an input */ useRender: (props: TProps, useSlots: UseStyledSlots) => LegacyFunctionComponent; /** * optional statics to attach to the component */ statics?: TStatics; }; export type ComposeFactoryComponent = StagedComponent & { __options: ComposeFactoryOptions; customize: (...tokens: TokenSettings[]) => ComposeFactoryComponent; compose( options: Partial>, ): ComposeFactoryComponent; } & TStatics; /** * options get deep merged except the tokens array gets appended */ const mergeOptions: MergeOptions = { tokens: 'appendArray', object: true, }; export function composeFactory( options: ComposeFactoryOptions, themeHelper: ThemeHelper, base?: ComposeFactoryComponent, ): ComposeFactoryComponent { type LocalComponent = ComposeFactoryComponent; type LocalOptions = ComposeFactoryOptions; // merge options together if a base is specified const baseOptions: LocalOptions = base?.__options as LocalOptions; options = baseOptions ? immutableMergeCore(mergeOptions, baseOptions, options) : { ...options }; // build styling if styling options are specified options.useStyling = options.slotProps || options.tokens ? buildUseStyling(options, themeHelper) : () => ({}) as TSlotProps; // build the slots hook, which will use the styling hook if it has been built const useSlots = buildUseSlots(options) as UseStyledSlots; // build the staged component const component = stagedComponent((props) => options.useRender(props as TProps, useSlots)) as LocalComponent; // attach additional props to the returned component component.displayName = options.displayName; component.__options = options; component.customize = (...tokens: LocalOptions['tokens']) => composeFactory( immutableMergeCore(mergeOptions, options, { tokens: tokens } as LocalOptions), themeHelper, ); component.compose = ( customOptions: Partial>, ) => composeFactory( immutableMergeCore(mergeOptions, options, customOptions as object) as unknown as ComposeFactoryOptions< TProps, TOverrideSlotProps, TTokens, TTheme, TStatics >, themeHelper, ); // attach statics if specified if (options.statics) { Object.assign(component, options.statics); } return component; }