Skip to content

Commit 366e0d6

Browse files
authored
fix(animated-fab): label styling (web) (#4567)
* fix(animated-fab): label styling (web) * fix(animated-fab): add canvasContext caching (web)
1 parent c488fc3 commit 366e0d6

File tree

3 files changed

+92
-11
lines changed

3 files changed

+92
-11
lines changed

src/components/FAB/AnimatedFAB.tsx

+39-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121

2222
import color from 'color';
2323

24-
import { getCombinedStyles, getFABColors } from './utils';
24+
import { getCombinedStyles, getFABColors, getLabelSizeWeb } from './utils';
2525
import { useInternalTheme } from '../../core/theming';
2626
import type { $Omit, $RemoveChildren, ThemeProp } from '../../types';
2727
import type { IconSource } from '../Icon';
@@ -227,9 +227,11 @@ const AnimatedFAB = ({
227227
const theme = useInternalTheme(themeOverrides);
228228
const uppercase: boolean = uppercaseProp ?? !theme.isV3;
229229
const isIOS = Platform.OS === 'ios';
230+
const isWeb = Platform.OS === 'web';
230231
const isAnimatedFromRight = animateFrom === 'right';
231232
const isIconStatic = iconMode === 'static';
232233
const { isRTL } = I18nManager;
234+
const labelRef = React.useRef<HTMLElement | null>(null);
233235
const { current: visibility } = React.useRef<Animated.Value>(
234236
new Animated.Value(visible ? 1 : 0)
235237
);
@@ -239,11 +241,44 @@ const AnimatedFAB = ({
239241
const { isV3, animation } = theme;
240242
const { scale } = animation;
241243

242-
const [textWidth, setTextWidth] = React.useState<number>(0);
243-
const [textHeight, setTextHeight] = React.useState<number>(0);
244+
const labelSize = isWeb ? getLabelSizeWeb(labelRef) : null;
245+
const [textWidth, setTextWidth] = React.useState<number>(
246+
labelSize?.width ?? 0
247+
);
248+
const [textHeight, setTextHeight] = React.useState<number>(
249+
labelSize?.height ?? 0
250+
);
244251

245252
const borderRadius = SIZE / (isV3 ? 3.5 : 2);
246253

254+
React.useEffect(() => {
255+
if (!isWeb) {
256+
return;
257+
}
258+
259+
const updateTextSize = () => {
260+
if (labelRef.current) {
261+
const labelSize = getLabelSizeWeb(labelRef);
262+
263+
if (labelSize) {
264+
setTextHeight(labelSize.height ?? 0);
265+
setTextWidth(labelSize.width ?? 0);
266+
}
267+
}
268+
};
269+
270+
updateTextSize();
271+
window.addEventListener('resize', updateTextSize);
272+
273+
return () => {
274+
if (!isWeb) {
275+
return;
276+
}
277+
278+
window.removeEventListener('resize', updateTextSize);
279+
};
280+
}, [isWeb]);
281+
247282
React.useEffect(() => {
248283
if (visible) {
249284
Animated.timing(visibility, {
@@ -470,6 +505,7 @@ const AnimatedFAB = ({
470505

471506
<View pointerEvents="none">
472507
<AnimatedText
508+
ref={isWeb ? labelRef : null}
473509
variant="labelLarge"
474510
numberOfLines={1}
475511
onTextLayout={isIOS ? onTextLayout : undefined}

src/components/FAB/utils.ts

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { Animated, ColorValue, I18nManager, ViewStyle } from 'react-native';
1+
import { MutableRefObject } from 'react';
2+
import {
3+
Animated,
4+
ColorValue,
5+
I18nManager,
6+
Platform,
7+
ViewStyle,
8+
} from 'react-native';
29

310
import color from 'color';
411

@@ -428,3 +435,40 @@ export const getExtendedFabStyle = ({
428435

429436
return isV3 ? v3Extended : extended;
430437
};
438+
439+
let cachedContext: CanvasRenderingContext2D | null = null;
440+
441+
const getCanvasContext = () => {
442+
if (cachedContext) {
443+
return cachedContext;
444+
}
445+
446+
const canvas = document.createElement('canvas');
447+
cachedContext = canvas.getContext('2d');
448+
449+
return cachedContext;
450+
};
451+
452+
export const getLabelSizeWeb = (ref: MutableRefObject<HTMLElement | null>) => {
453+
if (Platform.OS !== 'web' || ref.current === null) {
454+
return null;
455+
}
456+
457+
const canvasContext = getCanvasContext();
458+
459+
if (!canvasContext) {
460+
return null;
461+
}
462+
463+
const elementStyles = window.getComputedStyle(ref.current);
464+
canvasContext.font = elementStyles.font;
465+
466+
const metrics = canvasContext.measureText(ref.current.innerText);
467+
468+
return {
469+
width: metrics.width,
470+
height:
471+
(metrics.fontBoundingBoxAscent ?? 0) +
472+
(metrics.fontBoundingBoxDescent ?? 0),
473+
};
474+
};

src/components/Typography/AnimatedText.tsx

+8-7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import type { VariantProp } from './types';
1111
import { useInternalTheme } from '../../core/theming';
1212
import type { ThemeProp } from '../../types';
13+
import { forwardRef } from '../../utils/forwardRef';
1314

1415
type Props<T> = React.ComponentPropsWithRef<typeof Animated.Text> & {
1516
/**
@@ -39,12 +40,10 @@ type Props<T> = React.ComponentPropsWithRef<typeof Animated.Text> & {
3940
*
4041
* @extends Text props https://reactnative.dev/docs/text#props
4142
*/
42-
function AnimatedText({
43-
style,
44-
theme: themeOverrides,
45-
variant,
46-
...rest
47-
}: Props<never>) {
43+
const AnimatedText = forwardRef(function AnimatedText(
44+
{ style, theme: themeOverrides, variant, ...rest }: Props<never>,
45+
ref
46+
) {
4847
const theme = useInternalTheme(themeOverrides);
4948
const writingDirection = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr';
5049

@@ -60,6 +59,7 @@ function AnimatedText({
6059

6160
return (
6261
<Animated.Text
62+
ref={ref}
6363
{...rest}
6464
style={[
6565
font,
@@ -77,6 +77,7 @@ function AnimatedText({
7777
};
7878
return (
7979
<Animated.Text
80+
ref={ref}
8081
{...rest}
8182
style={[
8283
styles.text,
@@ -89,7 +90,7 @@ function AnimatedText({
8990
/>
9091
);
9192
}
92-
}
93+
});
9394

9495
const styles = StyleSheet.create({
9596
text: {

0 commit comments

Comments
 (0)