Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d83193a
feat: web example
kirillzyusko Dec 18, 2025
ed1988f
chore: update react-navigation
kirillzyusko Dec 18, 2025
39f67c8
fix: cspell
kirillzyusko Dec 18, 2025
0f8e325
fix: add web build pipeline
kirillzyusko Dec 19, 2025
024dffd
fix: return bottom sheet
kirillzyusko Dec 19, 2025
f2b61c8
fix: haptic feedback on web
kirillzyusko Dec 19, 2025
53e04a1
fix: web build
kirillzyusko Dec 19, 2025
c27d4fc
fix: unit tests
kirillzyusko Dec 19, 2025
d8e6841
fix: image displaying on web
kirillzyusko Dec 19, 2025
4cee358
feat: partially update iOS 16 assets
kirillzyusko Dec 19, 2025
f47481c
fix: update paper tests
kirillzyusko Dec 19, 2025
922d724
fix: blur view
kirillzyusko Dec 19, 2025
09d7c4f
fix: remaining iOS 16 assets
kirillzyusko Dec 19, 2025
6c9ee56
fix: iOS 15 assets
kirillzyusko Dec 19, 2025
1b6a593
fix: iOS 17 assets
kirillzyusko Dec 19, 2025
25eb540
fix: faster text selection action
kirillzyusko Dec 19, 2025
8dd62b3
fix: iOS 18 assets
kirillzyusko Dec 19, 2025
4172a0b
fix: iOS 26 + XCode 26 assets
kirillzyusko Dec 19, 2025
d2c4338
fix: XCode 16 + iOS 26
kirillzyusko Dec 19, 2025
ab7f563
fix: android 28 assets
kirillzyusko Dec 19, 2025
79a95c9
fix: android 31 assets
kirillzyusko Dec 19, 2025
3df0fa9
feat: e2e tests for web
kirillzyusko Dec 20, 2025
748fc79
fix: make e2e more robust
kirillzyusko Dec 20, 2025
c6cbeb9
fix: try to make toolbar tests more stable
kirillzyusko Dec 20, 2025
7ecd483
chore: port changes to paper example to keep both codebases in sync
kirillzyusko Dec 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
100 changes: 100 additions & 0 deletions .github/workflows/web-e2e-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: 🌐 Test Web
on:
pull_request:
paths:
- ".github/workflows/web-e2e-test.yml"
- "package.json"
- "yarn.lock"
- "FabricExample/**"
- "src/**"
- "e2e/**"
push:
branches:
- main
paths:
- ".github/workflows/web-e2e-test.yml"
- "package.json"
- "yarn.lock"
- "FabricExample/**"
- "src/**"
- "e2e/**"

jobs:
build-web:
name: 🏗️ Build web
defaults:
run:
working-directory: FabricExample
runs-on: macos-15
steps:
- uses: actions/checkout@v4

- name: Get yarn cache directory path
id: fabric-yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: Restore node_modules from cache
uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.fabric-yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-fabric-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-fabric-yarn-
- name: Install node_modules for FabricExample/
run: yarn install --frozen-lockfile
- name: Install root dependencies
run: yarn install --frozen-lockfile --cwd ..

- name: Build web
run: yarn build:web

- uses: actions/upload-artifact@v4
with:
name: web-e2e-app
path: FabricExample/dist/**

test-web:
name: ⚙️ Automated test cases
runs-on: macos-15
timeout-minutes: 60
needs: build-web
steps:
- uses: actions/checkout@v4
- name: Download a single artifact
uses: actions/download-artifact@v4
with:
name: web-e2e-app
path: FabricExample/dist/
- name: Start static server
run: |
cd FabricExample
npx http-server dist -p 8080 --silent --gzip --brotli --hostname 127.0.0.1 >/dev/null 2>&1 &
echo $! > .server-pid
- name: Install Maestro
run: |
curl -fsSL "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> $GITHUB_PATH
export MAESTRO_DRIVER_STARTUP_TIMEOUT=600000
- name: Specify web env
run: |
find e2e/flows -name "*.yml" -exec sh -c '
for file; do
printf "url: http://localhost:8080\n" | cat - "$file" > temp && mv temp "$file"
done
' sh {} +
shell: bash
- name: Run tests
run: |
export MAESTRO_CLI_NO_ANALYTICS="true"
export MAESTRO_CHROME_FLAGS="--no-sandbox --user-data-dir=/tmp/chrome-profile-$GITHUB_RUN_ID"
maestro --platform web test -e DEVICE="Chrome" --headless e2e/flows/* --format html ./e2e/reports/debug --debug-output ./e2e/reports/debug --flatten-debug-output
- name: Stop server
if: always()
run: |
kill $(cat .server-pid) || true
- name: Upload test report
if: failure()
uses: actions/upload-artifact@v4
with:
path: ./e2e/reports
name: e2e-report-web
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,7 @@ exports[`components rendering should render \`KeyboardStickyView\` 1`] = `

exports[`components rendering should render \`KeyboardToolbar\` 1`] = `
<View
content={
{
"$$typeof": Symbol(react.transitional.element),
"_owner": null,
"_store": {},
"key": null,
"props": {},
"type": [Function],
}
}
content={<EmptyView />}
/>
`;

Expand Down
19 changes: 19 additions & 0 deletions FabricExample/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Keyboard Controller Example</title>
<style>
#app-root {
display: flex;
flex: 1 1 100%;
height: 100vh;
}
</style>
</head>
<body>
<div id="app-root"></div>
</body>
</html>
10 changes: 10 additions & 0 deletions FabricExample/index.web.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { AppRegistry } from "react-native";

import name from "./app.json";
import App from "./src/App";

AppRegistry.registerComponent(name, () => App);
AppRegistry.runApplication(name, {
initialProps: {},
rootTag: document.getElementById("app-root"),
});
24 changes: 18 additions & 6 deletions FabricExample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"web": "webpack serve --mode=development --config webpack.config.js",
"build:web": "webpack --mode=production --config webpack.config.js",
"start": "react-native start",
"test": "jest",
"postinstall": "patch-package"
},
"dependencies": {
"@gorhom/bottom-sheet": "5.2.6",
"@lottiefiles/dotlottie-react": "^0.17.10",
"@react-native-community/blur": "^4.4.1",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-navigation/bottom-tabs": "^6.6.1",
"@react-navigation/elements": "^2.2.5",
"@react-navigation/native": "^6.1.18",
"@react-navigation/native-stack": "^6.11.0",
"@react-navigation/stack": "^6.4.1",
"@react-navigation/bottom-tabs": "^7.9.0",
"@react-navigation/elements": "2.6.4",
"@react-navigation/native": "7.1.17",
"@react-navigation/native-stack": "7.3.26",
"@react-navigation/stack": "7.3.6",
"lottie-react-native": "^7.3.4",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.81.4",
"react-native-advanced-input-mask": "^1.4.6",
"react-native-gesture-handler": "2.28.0",
Expand All @@ -29,6 +33,7 @@
"react-native-safe-area-context": "5.6.1",
"react-native-screens": "4.16.0",
"react-native-toast-message": "^2.3.0",
"react-native-web": "^0.21.2",
"react-native-worklets": "^0.6.0"
},
"devDependencies": {
Expand All @@ -47,9 +52,16 @@
"@types/jest": "^29.5.13",
"@types/react": "^19.1.0",
"@types/react-test-renderer": "^19.1.0",
"babel-loader": "^10.0.0",
"babel-plugin-react-native-web": "^0.21.2",
"html-webpack-plugin": "^5.6.5",
"jest": "^29.6.3",
"patch-package": "^6.4.7",
"react-test-renderer": "19.1.0"
"react-test-renderer": "19.1.0",
"url-loader": "^4.1.1",
"webpack": "^5.104.1",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.2"
},
"jest": {
"preset": "react-native",
Expand Down
3 changes: 3 additions & 0 deletions FabricExample/src/components/BlurView/index.native.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { BlurView } from "@react-native-community/blur";

export default BlurView;
24 changes: 24 additions & 0 deletions FabricExample/src/components/BlurView/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { ViewStyle } from "react-native";

type BlurViewProps = {
blurAmount: number;
blurType: string;
reducedTransparencyFallbackColor: string;
style: ViewStyle;
children?: React.ReactNode;
};

function BlurView({ blurAmount, children, style }: BlurViewProps) {
return (
<div
style={{
...(style as React.CSSProperties),
filter: `blur(${blurAmount}px)`,
}}
>
{children}
</div>
);
}

export default BlurView;
3 changes: 3 additions & 0 deletions FabricExample/src/modules/haptic/index.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { trigger } from "react-native-haptic-feedback";

export default trigger;
3 changes: 3 additions & 0 deletions FabricExample/src/modules/haptic/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const trigger = (_mode: string, _options: Record<string, boolean>) => {};

export default trigger;
13 changes: 11 additions & 2 deletions FabricExample/src/screens/Examples/ImageGallery/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,11 @@ const SharedTransition = ({

{!!image.img && (
<GestureDetector gesture={gesture}>
<Reanimated.Image fadeDuration={0} src={image.img} style={style} />
<Reanimated.Image
fadeDuration={0}
source={{ uri: image.img }}
style={style}
/>
</GestureDetector>
)}
</GestureHandlerRootView>
Expand Down Expand Up @@ -193,7 +197,12 @@ const ImagePreview = ({
{modal === src && isModalFullyVisible ? (
<View style={styles.image} />
) : (
<Image ref={ref} fadeDuration={0} src={src} style={styles.image} />
<Image
ref={ref}
fadeDuration={0}
source={{ uri: src }}
style={styles.image}
/>
)}
</TouchableOpacity>
);
Expand Down
4 changes: 2 additions & 2 deletions FabricExample/src/screens/Examples/Toolbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { BlurView } from "@react-native-community/blur";
import React, { useCallback, useEffect, useState } from "react";
import { Modal, Platform, StyleSheet, Text, View } from "react-native";
import { trigger } from "react-native-haptic-feedback";
import {
KeyboardAwareScrollView,
KeyboardToolbar,
} from "react-native-keyboard-controller";
import { useSafeAreaInsets } from "react-native-safe-area-context";

import BlurView from "../../../components/BlurView";
import TextInput from "../../../components/TextInput";
import trigger from "../../../modules/haptic";

import AutoFillContacts from "./Contacts";

Expand Down
Loading
Loading