Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(animated-fab): label styling (web) #4567

Merged
merged 2 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions src/components/FAB/AnimatedFAB.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<HTMLElement | null>(null);
const { current: visibility } = React.useRef<Animated.Value>(
new Animated.Value(visible ? 1 : 0)
);
Expand All @@ -239,11 +241,44 @@ const AnimatedFAB = ({
const { isV3, animation } = theme;
const { scale } = animation;

const [textWidth, setTextWidth] = React.useState<number>(0);
const [textHeight, setTextHeight] = React.useState<number>(0);
const labelSize = isWeb ? getLabelSizeWeb(labelRef) : null;
const [textWidth, setTextWidth] = React.useState<number>(
labelSize?.width ?? 0
);
const [textHeight, setTextHeight] = React.useState<number>(
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, {
Expand Down Expand Up @@ -470,6 +505,7 @@ const AnimatedFAB = ({

<View pointerEvents="none">
<AnimatedText
ref={isWeb ? labelRef : null}
variant="labelLarge"
numberOfLines={1}
onTextLayout={isIOS ? onTextLayout : undefined}
Expand Down
46 changes: 45 additions & 1 deletion src/components/FAB/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Animated, ColorValue, I18nManager, ViewStyle } from 'react-native';
import { MutableRefObject } from 'react';
import {
Animated,
ColorValue,
I18nManager,
Platform,
ViewStyle,
} from 'react-native';

import color from 'color';

Expand Down Expand Up @@ -428,3 +435,40 @@ export const getExtendedFabStyle = ({

return isV3 ? v3Extended : extended;
};

let cachedContext: CanvasRenderingContext2D | null = null;

const getCanvasContext = () => {
if (cachedContext) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not expert on web, won't impact the performance creating everytime a canvas? will work cache it? something like

let cachedCanvasContext: CanvasRenderingContext2D | null = null;

const getCanvasContext = () => {
  if (!cachedCanvasContext) {
    const canvas = document.createElement('canvas');
    cachedCanvasContext = canvas.getContext('2d');
  }
  return cachedCanvasContext;
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid suggestion

I did some performance testing for 100 FAB component instances rendered and TBH I didn't notice significant difference between variant with caching and the one without it except little memory consumption improvement, but still it's improvement, so caching added. 💪

Screenshot 2025-01-24 at 21 11 01

return cachedContext;
}

const canvas = document.createElement('canvas');
cachedContext = canvas.getContext('2d');

return cachedContext;
};

export const getLabelSizeWeb = (ref: MutableRefObject<HTMLElement | null>) => {
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),
};
};
15 changes: 8 additions & 7 deletions src/components/Typography/AnimatedText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = React.ComponentPropsWithRef<typeof Animated.Text> & {
/**
Expand Down Expand Up @@ -33,12 +34,10 @@ type Props<T> = React.ComponentPropsWithRef<typeof Animated.Text> & {
*
* @extends Text props https://reactnative.dev/docs/text#props
*/
function AnimatedText({
style,
theme: themeOverrides,
variant,
...rest
}: Props<never>) {
const AnimatedText = forwardRef(function AnimatedText(
{ style, theme: themeOverrides, variant, ...rest }: Props<never>,
ref
) {
const theme = useInternalTheme(themeOverrides);
const writingDirection = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr';

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

return (
<Animated.Text
ref={ref}
{...rest}
style={[
font,
Expand All @@ -71,6 +71,7 @@ function AnimatedText({
};
return (
<Animated.Text
ref={ref}
{...rest}
style={[
styles.text,
Expand All @@ -83,7 +84,7 @@ function AnimatedText({
/>
);
}
}
});

const styles = StyleSheet.create({
text: {
Expand Down
Loading