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(blur): Migrate web to a function component and fix reanimated errors #27721

Merged
merged 4 commits into from Mar 18, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/expo-blur/CHANGELOG.md
Expand Up @@ -10,6 +10,8 @@

### 🐛 Bug fixes

- Migrate web to a function component and fix reanimated errors related to [`setNativeProps` being removed](https://github.com/necolas/react-native-web/commit/e68c32770757194af103cca0095c0c204995505b). ([#27721](https://github.com/expo/expo/pull/27721) by [@EvanBacon](https://github.com/EvanBacon))

### 💡 Others

## 12.9.2 - 2024-02-16
Expand Down
20 changes: 9 additions & 11 deletions packages/expo-blur/build/BlurView.web.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-blur/build/BlurView.web.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 34 additions & 45 deletions packages/expo-blur/build/BlurView.web.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-blur/build/BlurView.web.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

99 changes: 49 additions & 50 deletions packages/expo-blur/src/BlurView.web.tsx
@@ -1,65 +1,64 @@
// Copyright © 2024 650 Industries.

'use client';

import * as React from 'react';
import { forwardRef, useImperativeHandle, useRef } from 'react';
import { View } from 'react-native';

import { BlurViewProps } from './BlurView.types';
import getBackgroundColor from './getBackgroundColor';

// TODO: Class components are not supported with React Server Components.
export default class BlurView extends React.Component<BlurViewProps> {
private blurViewRef = React.createRef<View>();
const BlurView = forwardRef<{ setNativeProps: (props: BlurViewProps) => void }, BlurViewProps>(
({ tint = 'default', intensity = 50, style, ...props }, ref) => {
const blurViewRef = useRef<HTMLDivElement>(null);
const blurStyle = getBlurStyle({ tint, intensity });

/**
* Reanimated will detect and call this function with animated styles passed as props on every
* animation frame. We want to extract intensity from the props, then create and apply new styles,
* which create the blur based on the intensity and current tint.
*/
setNativeProps(nativeProps) {
const { style, tint, intensity: standardIntensity } = this.props;
const intensity = nativeProps.style.intensity ?? standardIntensity;
const blurStyle = getBlurStyle({ intensity, tint });
this.blurViewRef?.current?.setNativeProps({
...nativeProps,
style: [style, blurStyle, nativeProps.style],
});
}
useImperativeHandle(
ref,
() => ({
setNativeProps: (nativeProps: BlurViewProps) => {
if (!blurViewRef.current?.style) {
return;
}

render() {
const { tint = 'default', intensity = 50, style, ...props } = this.props;
const blurStyle = getBlurStyle({ tint, intensity });
return <View {...props} style={[style, blurStyle]} ref={this.blurViewRef} />;
}
}
// @ts-expect-error: `style.intensity` is not defined in the types
const nextIntensity = nativeProps.style?.intensity ?? intensity;
const blurStyle = getBlurStyle({ intensity: nextIntensity, tint: tint ?? 'default' });
if (nativeProps.style) {
for (const key in nativeProps.style) {
if (key !== 'intensity') {
blurViewRef.current.style[key] = nativeProps.style[key];
}
}
}

function isBlurSupported(): boolean {
// TODO: Replace with CSS or static extraction to ensure hydration errors cannot happen.
// Enable by default in Node.js
if (typeof window === 'undefined') {
return true;
blurViewRef.current.style.backgroundColor = blurStyle.backgroundColor;
blurViewRef.current.style.backdropFilter = blurStyle.backdropFilter;
blurViewRef.current.style['webkitBackdropFilter'] = blurStyle.WebkitBackdropFilter;
},
}),
[intensity, tint]
);

return (
<View
{...props}
style={[style, blurStyle]}
/** @ts-expect-error: mismatch in ref type to support manually setting style props. */
ref={blurViewRef}
/>
);
}
// https://developer.mozilla.org/en-US/docs/Web/API/CSS/supports
// https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter#Browser_compatibility
return (
typeof CSS !== 'undefined' &&
(CSS.supports('-webkit-backdrop-filter', 'blur(1px)') ||
CSS.supports('backdrop-filter', 'blur(1px)'))
);
}
);

function getBlurStyle({ intensity, tint }): Record<string, string> {
const style: Record<string, string> = {
function getBlurStyle({
intensity,
tint,
}: Required<Pick<BlurViewProps, 'intensity' | 'tint'>>): Record<string, string> {
const blur = `saturate(180%) blur(${Math.min(intensity, 100) * 0.2}px)`;
return {
backgroundColor: getBackgroundColor(Math.min(intensity, 100), tint),
backdropFilter: blur,
WebkitBackdropFilter: blur,
};

if (isBlurSupported()) {
const blur = `saturate(180%) blur(${Math.min(intensity, 100) * 0.2}px)`;
style.backdropFilter = blur;
// Safari support
style.WebkitBackdropFilter = blur;
}

return style;
}

export default BlurView;
11 changes: 0 additions & 11 deletions packages/expo-blur/src/__tests__/BlurView-test.web.tsx
Expand Up @@ -32,17 +32,6 @@ it(`prefers filters to background color`, () => {
expect(view.style['backdropFilter']).toBeDefined();
});

it(`uses a transparent background color when filters aren't supported`, () => {
// @ts-ignore
global.CSS = undefined;

const withoutNativeBlur = render(<BlurView tint="light" testID="blur" />);
const view = getByTestId(withoutNativeBlur.container, 'blur');

expect(view.style['backdropFilter']).toBeUndefined();
expect(view.style['backgroundColor']).toBeDefined();
});

it(`supports Animated API`, () => {
// react-native-web 0.11 doesn't support createAnimatedComponent with functional components.
// This test ensures that the current version of RNW in Expo works with expo-blur.
Expand Down