Skip to content
Open
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
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
example/build
example
docs/build
node_modules
lib
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ yarn

While developing, you can run the [example app](/example/) to test your changes. Any changes you make in your library's JavaScript code will be reflected in the example app without a rebuild. If you change any native code, then you'll need to rebuild the example app.

> **Known debt:** the library itself was migrated to build and type-check against React Native 0.86
> (see [`docs/rn-0.86-migration.md`](./docs/rn-0.86-migration.md)), but `/example` is still pinned to
> React Native 0.65.1 with an old Storybook 6 / Detox 18 / React Navigation 6 / Reanimated
> 2.3-alpha stack. It needs its own separate upgrade (or a from-scratch regeneration) before it can
> be used to visually validate the library on RN 0.86 — that work is intentionally out of scope for
> the library-compatibility migration above.

To start the packager:

```sh
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@

[Check out the docs](https://zobeirhamid.github.io/react-native-scrollable-navigation-bar)

## Compatibility

Tested and building cleanly against **React Native 0.86** (New Architecture, mandatory since RN
0.82). `peerDependencies` require `react >=18.0.0` and `react-native >=0.72.0`.

The library itself has no native code — it's pure JS/TSX built on top of the `Animated` API — so it
should keep working across a wide range of RN versions as long as `react-native`'s own JS APIs stay
compatible. See [`docs/rn-0.86-migration.md`](./docs/rn-0.86-migration.md) for the full compatibility
audit and known limitations, including an Android edge-to-edge caveat around `StatusBar` behavior.

## Features

- 60 FPS scroll animations
Expand All @@ -26,3 +36,4 @@
- Web[iOS]: Bouncing won't scale up the HeaderBackground Component
- Web: Border flickering
- RegularNavigationBar: If `collapsible=true`, `stickyCollapsible=true`, and a `SnapComponent` exists, so the `StickyComponent` collapses too early, as it does not count in `snapHeight`
- Android: `StatusBar.setTranslucent()` (used internally to draw the header behind the status bar) becomes a no-op once the host app targets Android 15+ (`targetSdkVersion 35+`), which enforces edge-to-edge and deprecates that API at the OS level — this is already the default `targetSdkVersion` in the RN 0.86 app template. Actual visual impact hasn't been validated on a real Android 15+ device yet; see the "Etapa 4" findings in [`docs/rn-0.86-migration.md`](./docs/rn-0.86-migration.md)
2 changes: 1 addition & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
presets: ['module:@react-native/babel-preset'],
};
68 changes: 29 additions & 39 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,24 @@
"types": "lib/typescript/index.d.ts",
"react-native": "src/index",
"source": "src/index",
"exports": {
".": {
"types": "./lib/typescript/index.d.ts",
"react-native": "./src/index.tsx",
"default": "./lib/commonjs/index.js"
},
"./package.json": "./package.json"
},
"files": [
"src",
"lib",
"android",
"ios",
"cpp",
"react-native-scrollable-navigation-bar.podspec",
"!lib/typescript/example",
"!android/build",
"!ios/build",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__"
],
"scripts": {
"test": "jest",
"test": "jest --passWithNoTests",
"typescript": "tsc --noEmit",
"lint": "eslint \"**/*.{js,ts,tsx}\"",
"prepare": "bob build",
Expand All @@ -46,37 +48,40 @@
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >=25.0.0"
},
"devDependencies": {
"@commitlint/config-conventional": "^11.0.0",
"@react-native-community/eslint-config": "^2.0.0",
"@react-native/babel-preset": "0.86.0",
"@react-native/eslint-config": "0.86.0",
"@react-native/jest-preset": "0.86.0",
"@release-it/conventional-changelog": "^2.0.0",
"@types/jest": "^26.0.0",
"@types/react": "^16.9.19",
"@types/react-native": "0.62.13",
"@types/jest": "^29.5.14",
"@types/react": "^19.2.0",
"commitlint": "^11.0.0",
"eslint": "^7.2.0",
"eslint-config-prettier": "^7.0.0",
"eslint-plugin-prettier": "^3.1.3",
"eslint": "^8.57.1",
"husky": "^4.2.5",
"jest": "^26.0.1",
"jest": "^29.7.0",
"pod-install": "^0.1.0",
"prettier": "^2.0.5",
"react": "16.13.1",
"react-native": "0.63.4",
"react-native-builder-bob": "^0.18.1",
"react": "19.2.3",
"react-native": "0.86.0",
"react-native-builder-bob": "^0.43.0",
"release-it": "^14.2.2",
"typescript": "^4.1.3"
"typescript": "^5.9.3"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
"react": ">=18.0.0",
"react-native": ">=0.72.0"
},
"jest": {
"preset": "react-native",
"preset": "@react-native/jest-preset",
"modulePathIgnorePatterns": [
"<rootDir>/example/node_modules",
"<rootDir>/lib/"
]
],
"testPathIgnorePatterns": ["<rootDir>/example/"]
},
"husky": {
"hooks": {
Expand Down Expand Up @@ -108,22 +113,7 @@
},
"eslintConfig": {
"root": true,
"extends": [
"@react-native-community",
"prettier"
],
"rules": {
"prettier/prettier": [
"error",
{
"quoteProps": "consistent",
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false
}
]
}
"extends": "@react-native"
},
"eslintIgnore": [
"node_modules/",
Expand Down
3 changes: 2 additions & 1 deletion src/Appearer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useMeasurements } from './contexts/MeasurementsContext';
import React, { useMemo, useRef, useEffect } from 'react';
import { Animated, ViewStyle } from 'react-native';
import { useHasReachedTransitionPoint } from './contexts/HasReachedTransitionPoint.tsx';
import { useHasReachedTransitionPoint } from './contexts/HasReachedTransitionPoint';

interface AppearerProps {
style?: ViewStyle;
children?: React.ReactNode;
}

const Appearer: React.FC<AppearerProps> = (props) => {
Expand Down
4 changes: 2 additions & 2 deletions src/BorderContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ const BorderContainer: React.FC<BorderContainerProps> = ({
<Animated.View style={{ height: borderHeight }}>
<Animated.View
style={{
...StyleSheet.absoluteFillObject,
...StyleSheet.absoluteFill,
}}
>
<HeaderBorderComponent />
</Animated.View>
<Transitioner
style={{
...StyleSheet.absoluteFillObject,
...StyleSheet.absoluteFill,
}}
>
<BorderComponent />
Expand Down
1 change: 1 addition & 0 deletions src/Collapser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface CollapserProps {
offset?: number;
stayCollapsed?: boolean;
height?: number;
children?: React.ReactNode;
}

const Collapser: React.FC<CollapserProps> = ({
Expand Down
6 changes: 3 additions & 3 deletions src/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import HeaderContainer from './HeaderContainer';
import NavigationBarContainer from './NavigationBarContainer';
import Scroller from './Scroller';
import StatusBarContainer from './StatusBarContainer';
import { HasReachedTransitionPointProvider } from './contexts/HasReachedTransitionPoint.tsx';
import { HasReachedTransitionPointProvider } from './contexts/HasReachedTransitionPoint';

export interface ContainerProps
extends React.ComponentProps<typeof ScrollView> {
Expand Down Expand Up @@ -62,7 +62,7 @@ class Container extends React.PureComponent<ContainerProps, ContainerState> {
transform: [{ translateY: this.containerAnimatedValue }],
};
overlayStyle = {
...StyleSheet.absoluteFillObject,
...StyleSheet.absoluteFill,
zIndex: 9999,
opacity: this.overlayAnimatedValue,
backgroundColor: 'black',
Expand Down Expand Up @@ -236,7 +236,7 @@ const styles = StyleSheet.create({
},

navigationBarContainer: {
...StyleSheet.absoluteFillObject,
...StyleSheet.absoluteFill,
zIndex: 9999,
},
});
Expand Down
4 changes: 2 additions & 2 deletions src/HeaderContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const HeaderContainer: React.FC<HeaderContainerProps> = (props) => {

const containerStyle = useMemo(() => {
return {
//...StyleSheet.absoluteFillObject,
//...StyleSheet.absoluteFill,
height: headerHeight,
transform: [
{
Expand Down Expand Up @@ -77,7 +77,7 @@ const HeaderContainer: React.FC<HeaderContainerProps> = (props) => {
fadeOut,
]);
const foregroundStyle = useMemo(() => {
return [StyleSheet.absoluteFillObject, { bottom: snapHeight }];
return [StyleSheet.absoluteFill, { bottom: snapHeight }];
}, [snapHeight]);

if (headerHeight === navigationBarHeight) return null;
Expand Down
4 changes: 2 additions & 2 deletions src/NavigationBarContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const NavigationBarContainer: React.FC<NavigationBarContainerProps> = (

const style = useMemo(() => {
return {
...StyleSheet.absoluteFillObject,
...StyleSheet.absoluteFill,
height: headerHeight + snapHeight + borderHeight + stickyHeight,
top: statusBarHeight,
zIndex: 1,
Expand All @@ -76,7 +76,7 @@ const NavigationBarContainer: React.FC<NavigationBarContainerProps> = (

const navigationBarStyle = useMemo(() => {
return {
...StyleSheet.absoluteFillObject,
...StyleSheet.absoluteFill,
height: navigationBarHeight,
};
}, [navigationBarHeight]);
Expand Down
2 changes: 1 addition & 1 deletion src/Scroller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from 'react-native';
import { useMeasurements } from './contexts/MeasurementsContext';
import { useAnimatedValue } from './contexts/AnimatedValueContext';
import { useHasReachedTransitionPoint } from './contexts/HasReachedTransitionPoint.tsx';
import { useHasReachedTransitionPoint } from './contexts/HasReachedTransitionPoint';

interface ScrollerProps extends React.ComponentProps<typeof ScrollView> {
beforeTransitionPoint?: () => void;
Expand Down
6 changes: 4 additions & 2 deletions src/Snapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { Animated, StyleSheet } from 'react-native';
import { useMeasurements } from './contexts/MeasurementsContext';
import { useAnimatedValue } from './contexts/AnimatedValueContext';

interface SnapperProps {}
interface SnapperProps {
children?: React.ReactNode;
}

const Snapper: React.FC<SnapperProps> = ({ children }) => {
const { snapHeight, navigationBarHeight, headerHeight, statusBarHeight } =
Expand All @@ -17,7 +19,7 @@ const Snapper: React.FC<SnapperProps> = ({ children }) => {

const style = useMemo(() => {
return {
...StyleSheet.absoluteFillObject,
...StyleSheet.absoluteFill,
height: snapHeight,
transform: [
{
Expand Down
2 changes: 1 addition & 1 deletion src/StatusBarContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const StatusBarContainer: React.FC<StatusBarContainerProps> = (props) => {

const statusBarStyle = useMemo(() => {
return {
...StyleSheet.absoluteFillObject,
...StyleSheet.absoluteFill,
height: statusBarHeight,
zIndex: 1,
};
Expand Down
3 changes: 2 additions & 1 deletion src/Sticker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface StickerProps {
stayCollapsed?: boolean;
stickyStayCollapsed?: boolean;
height?: number;
children?: React.ReactNode;
}

const Sticker: React.FC<StickerProps> = (props) => {
Expand Down Expand Up @@ -77,7 +78,7 @@ const Sticker: React.FC<StickerProps> = (props) => {

const style = useMemo(() => {
return {
...StyleSheet.absoluteFillObject,
...StyleSheet.absoluteFill,
transform: [
{
translateY,
Expand Down
1 change: 1 addition & 0 deletions src/Transitioner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface TransitionerProps {
style?: ViewStyle;
offset?: number;
distance?: number;
children?: React.ReactNode;
}

const Transitioner: React.FC<TransitionerProps> = (props) => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/NavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const styles = StyleSheet.create({
flex: 1,
},
titleContainer: {
...StyleSheet.absoluteFillObject,
...StyleSheet.absoluteFill,
alignItems: 'center',
justifyContent: 'center',
},
Expand Down
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function isIphoneX() {
return (
Platform.OS === 'ios' &&
!Platform.isPad &&
!Platform.isTVOS &&
!Platform.isTV &&
(dimen.height === 780 ||
dimen.width === 780 ||
dimen.height === 812 ||
Expand Down
1 change: 1 addition & 0 deletions src/contexts/AnimatedValueContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const useAnimatedValue = () => {

export const AnimatedValueProvider: React.FC<{
animatedValue?: Animated.Value;
children?: React.ReactNode;
}> = ({ children, animatedValue }) => {
const animatedValueContext = useRef(
animatedValue !== undefined ? animatedValue : new Animated.Value(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export const useHasReachedTransitionPoint = () => {
return context;
};

export const HasReachedTransitionPointProvider: React.FC<{}> = ({
children,
}) => {
export const HasReachedTransitionPointProvider: React.FC<{
children?: React.ReactNode;
}> = ({ children }) => {
const [hasReachedTransitionPoint, setHasReachedTransitionPoint] =
useState(false);
const hasReachedTransitionPointContext = useMemo(() => {
Expand Down
4 changes: 3 additions & 1 deletion src/contexts/MeasurementsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export const useMeasurements = () => {
return context;
};

export const MeasurementsProvider: React.FC<MeasurementsState> = ({
export const MeasurementsProvider: React.FC<
MeasurementsState & { children?: React.ReactNode }
> = ({
children,
statusBarHeight,
navigationBarHeight,
Expand Down
2 changes: 1 addition & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import constants from './constants';
import AppleStyle from './styles/AppleStyle';
import { useAnimatedValue } from './contexts/AnimatedValueContext';
import { useMeasurements } from './contexts/MeasurementsContext';
import { useHasReachedTransitionPoint } from './contexts/HasReachedTransitionPoint.tsx';
import { useHasReachedTransitionPoint } from './contexts/HasReachedTransitionPoint';
import Transitioner from './Transitioner';
import Appearer from './Appearer';

Expand Down
Loading