/** @jsxImportSource @fluentui-react-native/framework-base */ import type { StyleProp, TextStyle } from 'react-native'; import { View, AccessibilityInfo, Pressable, Animated, Platform } from 'react-native'; import type { UseSlots } from '@fluentui-react-native/framework'; import { compose, memoize, mergeProps } from '@fluentui-react-native/framework'; import { extractStyle } from '@fluentui-react-native/framework-base'; import { Text } from '@fluentui-react-native/text'; import { stylingSettings } from './Switch.styling'; import type { SwitchType, SwitchState, SwitchProps } from './Switch.types'; import { switchName } from './Switch.types'; import { useSwitch } from './useSwitch'; /** * A function which determines if a set of styles should be applied to the component given the current state and props of the switch. * * @param layer The name of the state that is being checked for * @param state The current state of the switch * @param userProps The props that were passed into the switch * @returns Whether the styles that are assigned to the layer should be applied to the switch */ export const switchLookup = (layer: string, state: SwitchState, userProps: SwitchProps): boolean => { const onOffTextExists = !!userProps['onText'] || !!userProps['offText']; return ( state[layer] || userProps[layer] || layer === userProps['labelPosition'] || (userProps['labelPosition'] === 'before' && layer === 'beforeContent') || (userProps['labelPosition'] === 'before' && onOffTextExists && layer === 'afterContent') || (userProps['labelPosition'] === 'after' && layer === 'afterContent') || (userProps['labelPosition'] === 'above' && onOffTextExists && layer === 'afterContent') || (state['toggled'] && layer === 'toggleOn') || (!state['toggled'] && layer === 'toggleOff') ); }; const isMobile = Platform.OS === 'android' || Platform.OS == 'ios'; export const Switch = compose({ displayName: switchName, ...stylingSettings, slots: { root: Pressable, label: Text, track: Animated.View, thumb: Animated.View, toggleContainer: View, onOffTextContainer: View, onOffText: Text, }, useRender: (userProps: SwitchProps, useSlots: UseSlots) => { const switchOnSlot = useSlots(userProps, (layer) => switchLookup(layer, { toggled: true, disabled: userProps.disabled }, {})); const switchOffSlot = useSlots(userProps, (layer) => switchLookup(layer, { toggled: false, disabled: userProps.disabled }, {})); /** * The following is hacky and completely breaks typings, but keeping it in to not break legacy behaviors. */ const switchInfo = useSwitch( userProps, isMobile && { toggleOnBgColor: extractStyle(switchOnSlot.track({})).backgroundColor as string, toggleOffBgColor: extractStyle(switchOffSlot.track({})).backgroundColor as string, trackWidth: extractStyle(switchOnSlot.track({})).width as number, thumbWidth: extractStyle(switchOnSlot.thumb({})).width as number, thumbMargin: extractStyle(switchOnSlot.thumb({})).margin as number, }, ); // grab the styled slots const Slots = useSlots(userProps, (layer) => switchLookup(layer, switchInfo.state, switchInfo.props)); // now return the handler for finishing render return (final: SwitchProps) => { const { label, offText, onText, labelPosition, ...mergedProps } = mergeProps(switchInfo.props, final); const displayOnOffText = !!offText || !!onText; const isReduceMotionEnabled = AccessibilityInfo.isReduceMotionEnabled; const thumbAnimation = isReduceMotionEnabled ? { animationClass: 'Ribbon_SwitchThumb' } : null; return ( {label} {/* For the Mobile platform the animated styles are applied */} {displayOnOffText && ( /** * If the onText and offText are different lengths, this can cause unwanted shifting of the track, if labelPosition = "after", * or the label, if labelPosition = "above". We fix this by rendering both texts and setting the height of the text to hide to * zero. This way, even when not visible, the hidden text still takes up its width to prevent shifting. */ {onText} {offText} )} ); }; }, }); const onOffTextStyleWorker = (text: 'on' | 'off', isOn: boolean): StyleProp => ({ height: (text === 'on' && isOn) || (text === 'off' && !isOn) ? undefined : 0, }); const getOnOffTextStyle = memoize(onOffTextStyleWorker);