diff --git a/apps/fluent-tester/src/TestComponents/Switch/SwitchTest.tsx b/apps/fluent-tester/src/TestComponents/Switch/SwitchTest.tsx index d52285588a..84f3be88d0 100644 --- a/apps/fluent-tester/src/TestComponents/Switch/SwitchTest.tsx +++ b/apps/fluent-tester/src/TestComponents/Switch/SwitchTest.tsx @@ -96,6 +96,7 @@ const OnOffText: React.FunctionComponent = () => { + ); }; diff --git a/change/@fluentui-react-native-switch-407aa2c6-eb30-4bca-9b93-dc14436e8786.json b/change/@fluentui-react-native-switch-407aa2c6-eb30-4bca-9b93-dc14436e8786.json new file mode 100644 index 0000000000..436156816f --- /dev/null +++ b/change/@fluentui-react-native-switch-407aa2c6-eb30-4bca-9b93-dc14436e8786.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "On/off text renders with labelPosition=before", + "packageName": "@fluentui-react-native/switch", + "email": "winlarry@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-native-tester-3b07212a-3b7c-48bd-a9ba-516497167dc8.json b/change/@fluentui-react-native-tester-3b07212a-3b7c-48bd-a9ba-516497167dc8.json new file mode 100644 index 0000000000..bd4600bc4d --- /dev/null +++ b/change/@fluentui-react-native-tester-3b07212a-3b7c-48bd-a9ba-516497167dc8.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Add new example for Switch on/off text section", + "packageName": "@fluentui-react-native/tester", + "email": "winlarry@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/components/Switch/src/Switch.styling.ts b/packages/components/Switch/src/Switch.styling.ts index 7bae5dec0b..364bd4d797 100644 --- a/packages/components/Switch/src/Switch.styling.ts +++ b/packages/components/Switch/src/Switch.styling.ts @@ -102,6 +102,16 @@ export const stylingSettings: UseStylingOptions ({ + style: { + flexDirection: 'column', + alignItems: 'flex-start', + justifyContent: 'center', + }, + }), + [], + ), onOffText: buildProps( (tokens: SwitchTokens, theme: Theme) => ({ style: { diff --git a/packages/components/Switch/src/Switch.tsx b/packages/components/Switch/src/Switch.tsx index 8a701ac09c..17ccf5eb80 100644 --- a/packages/components/Switch/src/Switch.tsx +++ b/packages/components/Switch/src/Switch.tsx @@ -1,9 +1,11 @@ /** @jsx withSlots */ +import type { StyleProp } from 'react-native'; import { View, AccessibilityInfo, Pressable, Animated, Platform } from 'react-native'; import type { UseSlots } from '@fluentui-react-native/framework'; -import { compose, mergeProps, withSlots } from '@fluentui-react-native/framework'; +import { compose, memoize, mergeProps, withSlots } from '@fluentui-react-native/framework'; import { Text } from '@fluentui-react-native/text'; +import type { TextStyle } from '@office-iss/react-native-win32'; import { stylingSettings } from './Switch.styling'; import type { SwitchType, SwitchState, SwitchProps } from './Switch.types'; @@ -44,6 +46,7 @@ export const Switch = compose({ track: Animated.View, // Conversion from View to Animated.View for Animated API to work thumb: Animated.View, toggleContainer: View, + onOffTextContainer: View, onOffText: Text, }, useRender: (userProps: SwitchProps, useSlots: UseSlots) => { @@ -68,10 +71,10 @@ export const Switch = compose({ // now return the handler for finishing render return (final: SwitchProps) => { const { label, offText, onText, labelPosition, ...mergedProps } = mergeProps(switchInfo.props, final); - const onOffText = switchInfo.state.toggled ? onText : offText; const displayOnOffText = !!offText || !!onText; const isReduceMotionEnabled = AccessibilityInfo.isReduceMotionEnabled; const thumbAnimation = isReduceMotionEnabled ? { animationClass: 'Ribbon_SwitchThumb' } : null; + return ( {label} @@ -80,10 +83,25 @@ export const Switch = compose({ - {displayOnOffText && {onOffText}} + {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); diff --git a/packages/components/Switch/src/Switch.types.ts b/packages/components/Switch/src/Switch.types.ts index 25f17db8b3..049251505c 100644 --- a/packages/components/Switch/src/Switch.types.ts +++ b/packages/components/Switch/src/Switch.types.ts @@ -202,6 +202,7 @@ export interface SwitchSlotProps { track: IViewProps; thumb: IViewProps; toggleContainer: IViewProps; + onOffTextContainer: IViewProps; onOffText: TextProps; } diff --git a/packages/components/Switch/src/useSwitch.ts b/packages/components/Switch/src/useSwitch.ts index faa6e12539..a06eac16bf 100644 --- a/packages/components/Switch/src/useSwitch.ts +++ b/packages/components/Switch/src/useSwitch.ts @@ -97,16 +97,6 @@ export const useSwitch = (props: SwitchProps, animationConfig?: AnimationConfig) console.warn("The props 'defaultChecked' and 'checked' are mutually exclusive. Use only one of the props, do not use both."); } - if (labelPosition === 'after' || labelPosition === undefined) { - if (__DEV__ && (!!props.onText || !!props.offText)) { - console.warn( - "The prop labelPosition's value of \"after\" and the props 'onText' or 'offText' are mutually exclusive. Try setting 'labelPosition' value to \"before\" or \"above\" instead.", - ); - } - props.onText = null; - props.offText = null; - } - const onClickWithFocus = useOnPressWithFocus(focusRef, toggleCallback); const pressable = usePressableState({ ...rest, onPress: onClickWithFocus }); const onKeyUpProps = useKeyProps(toggleCallback, ' ', 'Enter');