diff --git a/src/components/FAB/AnimatedFAB.tsx b/src/components/FAB/AnimatedFAB.tsx index c66038d925..6c5fedbb22 100644 --- a/src/components/FAB/AnimatedFAB.tsx +++ b/src/components/FAB/AnimatedFAB.tsx @@ -21,7 +21,7 @@ import { import color from 'color'; -import { getCombinedStyles, getFABColors } from './utils'; +import { getCombinedStyles, getFABColors, getLabelSizeWeb } from './utils'; import { useInternalTheme } from '../../core/theming'; import type { $Omit, $RemoveChildren, ThemeProp } from '../../types'; import type { IconSource } from '../Icon'; @@ -227,9 +227,11 @@ const AnimatedFAB = ({ const theme = useInternalTheme(themeOverrides); const uppercase: boolean = uppercaseProp ?? !theme.isV3; const isIOS = Platform.OS === 'ios'; + const isWeb = Platform.OS === 'web'; const isAnimatedFromRight = animateFrom === 'right'; const isIconStatic = iconMode === 'static'; const { isRTL } = I18nManager; + const labelRef = React.useRef(null); const { current: visibility } = React.useRef( new Animated.Value(visible ? 1 : 0) ); @@ -239,11 +241,44 @@ const AnimatedFAB = ({ const { isV3, animation } = theme; const { scale } = animation; - const [textWidth, setTextWidth] = React.useState(0); - const [textHeight, setTextHeight] = React.useState(0); + const labelSize = isWeb ? getLabelSizeWeb(labelRef) : null; + const [textWidth, setTextWidth] = React.useState( + labelSize?.width ?? 0 + ); + const [textHeight, setTextHeight] = React.useState( + labelSize?.height ?? 0 + ); const borderRadius = SIZE / (isV3 ? 3.5 : 2); + React.useEffect(() => { + if (!isWeb) { + return; + } + + const updateTextSize = () => { + if (labelRef.current) { + const labelSize = getLabelSizeWeb(labelRef); + + if (labelSize) { + setTextHeight(labelSize.height ?? 0); + setTextWidth(labelSize.width ?? 0); + } + } + }; + + updateTextSize(); + window.addEventListener('resize', updateTextSize); + + return () => { + if (!isWeb) { + return; + } + + window.removeEventListener('resize', updateTextSize); + }; + }, [isWeb]); + React.useEffect(() => { if (visible) { Animated.timing(visibility, { @@ -470,6 +505,7 @@ const AnimatedFAB = ({ { + if (cachedContext) { + return cachedContext; + } + + const canvas = document.createElement('canvas'); + cachedContext = canvas.getContext('2d'); + + return cachedContext; +}; + +export const getLabelSizeWeb = (ref: MutableRefObject) => { + if (Platform.OS !== 'web' || ref.current === null) { + return null; + } + + const canvasContext = getCanvasContext(); + + if (!canvasContext) { + return null; + } + + const elementStyles = window.getComputedStyle(ref.current); + canvasContext.font = elementStyles.font; + + const metrics = canvasContext.measureText(ref.current.innerText); + + return { + width: metrics.width, + height: + (metrics.fontBoundingBoxAscent ?? 0) + + (metrics.fontBoundingBoxDescent ?? 0), + }; +}; diff --git a/src/components/Typography/AnimatedText.tsx b/src/components/Typography/AnimatedText.tsx index 43c2ff818d..9998f77de8 100644 --- a/src/components/Typography/AnimatedText.tsx +++ b/src/components/Typography/AnimatedText.tsx @@ -4,6 +4,7 @@ import { Animated, I18nManager, StyleSheet, TextStyle } from 'react-native'; import type { VariantProp } from './types'; import { useInternalTheme } from '../../core/theming'; import type { ThemeProp } from '../../types'; +import { forwardRef } from '../../utils/forwardRef'; type Props = React.ComponentPropsWithRef & { /** @@ -33,12 +34,10 @@ type Props = React.ComponentPropsWithRef & { * * @extends Text props https://reactnative.dev/docs/text#props */ -function AnimatedText({ - style, - theme: themeOverrides, - variant, - ...rest -}: Props) { +const AnimatedText = forwardRef(function AnimatedText( + { style, theme: themeOverrides, variant, ...rest }: Props, + ref +) { const theme = useInternalTheme(themeOverrides); const writingDirection = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr'; @@ -54,6 +53,7 @@ function AnimatedText({ return ( ); } -} +}); const styles = StyleSheet.create({ text: {