Skip to content

Commit b877fdc

Browse files
committed
fix(animated-fab): label styling (web)
1 parent 9162a1f commit b877fdc

File tree

3 files changed

+88
-11
lines changed

3 files changed

+88
-11
lines changed

src/components/FAB/AnimatedFAB.tsx

Lines changed: 39 additions & 3 deletions
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

Lines changed: 41 additions & 1 deletion
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,36 @@ export const getExtendedFabStyle = ({
428435

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

src/components/Typography/AnimatedText.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Animated, I18nManager, StyleSheet, TextStyle } from 'react-native';
44
import type { VariantProp } from './types';
55
import { useInternalTheme } from '../../core/theming';
66
import type { ThemeProp } from '../../types';
7+
import { forwardRef } from '../../utils/forwardRef';
78

89
type Props<T> = React.ComponentPropsWithRef<typeof Animated.Text> & {
910
/**
@@ -33,12 +34,10 @@ type Props<T> = React.ComponentPropsWithRef<typeof Animated.Text> & {
3334
*
3435
* @extends Text props https://reactnative.dev/docs/text#props
3536
*/
36-
function AnimatedText({
37-
style,
38-
theme: themeOverrides,
39-
variant,
40-
...rest
41-
}: Props<never>) {
37+
const AnimatedText = forwardRef(function AnimatedText(
38+
{ style, theme: themeOverrides, variant, ...rest }: Props<never>,
39+
ref
40+
) {
4241
const theme = useInternalTheme(themeOverrides);
4342
const writingDirection = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr';
4443

@@ -54,6 +53,7 @@ function AnimatedText({
5453

5554
return (
5655
<Animated.Text
56+
ref={ref}
5757
{...rest}
5858
style={[
5959
font,
@@ -71,6 +71,7 @@ function AnimatedText({
7171
};
7272
return (
7373
<Animated.Text
74+
ref={ref}
7475
{...rest}
7576
style={[
7677
styles.text,
@@ -83,7 +84,7 @@ function AnimatedText({
8384
/>
8485
);
8586
}
86-
}
87+
});
8788

8889
const styles = StyleSheet.create({
8990
text: {

0 commit comments

Comments
 (0)