first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,363 @@
"use strict";
import { useNavigation, useTheme } from '@react-navigation/native';
import Color from 'color';
import * as React from 'react';
import { Animated, Platform, StyleSheet, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import searchIcon from '../assets/search-icon.png';
import { useFrameSize } from "../useFrameSize.js";
import { getDefaultHeaderHeight } from "./getDefaultHeaderHeight.js";
import { HeaderBackButton } from "./HeaderBackButton.js";
import { HeaderBackground } from "./HeaderBackground.js";
import { HeaderButton } from "./HeaderButton.js";
import { HeaderIcon } from "./HeaderIcon.js";
import { HeaderSearchBar } from "./HeaderSearchBar.js";
import { HeaderShownContext } from "./HeaderShownContext.js";
import { HeaderTitle } from "./HeaderTitle.js";
// Width of the screen in split layout on portrait mode on iPad Mini
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
const IPAD_MINI_MEDIUM_WIDTH = 414;
const warnIfHeaderStylesDefined = styles => {
Object.keys(styles).forEach(styleProp => {
const value = styles[styleProp];
if (styleProp === 'position' && value === 'absolute') {
console.warn("position: 'absolute' is not supported on headerStyle. If you would like to render content under the header, use the 'headerTransparent' option.");
} else if (value !== undefined) {
console.warn(`${styleProp} was given a value of ${value}, this has no effect on headerStyle.`);
}
});
};
export function Header(props) {
const insets = useSafeAreaInsets();
const frame = useFrameSize(size => size, true);
const {
colors
} = useTheme();
const navigation = useNavigation();
const isParentHeaderShown = React.useContext(HeaderShownContext);
const [searchBarVisible, setSearchBarVisible] = React.useState(false);
const [titleLayout, setTitleLayout] = React.useState(undefined);
const onTitleLayout = e => {
const {
height,
width
} = e.nativeEvent.layout;
setTitleLayout(titleLayout => {
if (titleLayout && height === titleLayout.height && width === titleLayout.width) {
return titleLayout;
}
return {
height,
width
};
});
};
const {
layout = frame,
modal = false,
back,
title,
headerTitle: customTitle,
headerTitleAlign = Platform.OS === 'ios' ? 'center' : 'left',
headerLeft = back ? props => /*#__PURE__*/_jsx(HeaderBackButton, {
...props
}) : undefined,
headerSearchBarOptions,
headerTransparent,
headerTintColor,
headerBackground,
headerRight,
headerTitleAllowFontScaling: titleAllowFontScaling,
headerTitleStyle: titleStyle,
headerLeftContainerStyle: leftContainerStyle,
headerRightContainerStyle: rightContainerStyle,
headerTitleContainerStyle: titleContainerStyle,
headerBackButtonDisplayMode = Platform.OS === 'ios' ? 'default' : 'minimal',
headerBackTitleStyle,
headerBackgroundContainerStyle: backgroundContainerStyle,
headerStyle: customHeaderStyle,
headerShadowVisible,
headerPressColor,
headerPressOpacity,
headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top
} = props;
const defaultHeight = getDefaultHeaderHeight(layout, modal, headerStatusBarHeight);
const {
height = defaultHeight,
maxHeight,
minHeight,
backfaceVisibility,
backgroundColor,
borderBlockColor,
borderBlockEndColor,
borderBlockStartColor,
borderBottomColor,
borderBottomEndRadius,
borderBottomLeftRadius,
borderBottomRightRadius,
borderBottomStartRadius,
borderBottomWidth,
borderColor,
borderCurve,
borderEndColor,
borderEndEndRadius,
borderEndStartRadius,
borderEndWidth,
borderLeftColor,
borderLeftWidth,
borderRadius,
borderRightColor,
borderRightWidth,
borderStartColor,
borderStartEndRadius,
borderStartStartRadius,
borderStartWidth,
borderStyle,
borderTopColor,
borderTopEndRadius,
borderTopLeftRadius,
borderTopRightRadius,
borderTopStartRadius,
borderTopWidth,
borderWidth,
boxShadow,
elevation,
filter,
mixBlendMode,
opacity,
shadowColor,
shadowOffset,
shadowOpacity,
shadowRadius,
transform,
transformOrigin,
...unsafeStyles
} = StyleSheet.flatten(customHeaderStyle || {});
if (process.env.NODE_ENV !== 'production') {
warnIfHeaderStylesDefined(unsafeStyles);
}
const safeStyles = {
backfaceVisibility,
backgroundColor,
borderBlockColor,
borderBlockEndColor,
borderBlockStartColor,
borderBottomColor,
borderBottomEndRadius,
borderBottomLeftRadius,
borderBottomRightRadius,
borderBottomStartRadius,
borderBottomWidth,
borderColor,
borderCurve,
borderEndColor,
borderEndEndRadius,
borderEndStartRadius,
borderEndWidth,
borderLeftColor,
borderLeftWidth,
borderRadius,
borderRightColor,
borderRightWidth,
borderStartColor,
borderStartEndRadius,
borderStartStartRadius,
borderStartWidth,
borderStyle,
borderTopColor,
borderTopEndRadius,
borderTopLeftRadius,
borderTopRightRadius,
borderTopStartRadius,
borderTopWidth,
borderWidth,
boxShadow,
elevation,
filter,
mixBlendMode,
opacity,
shadowColor,
shadowOffset,
shadowOpacity,
shadowRadius,
transform,
transformOrigin
};
// Setting a property to undefined triggers default style
// So we need to filter them out
// Users can use `null` instead
for (const styleProp in safeStyles) {
// @ts-expect-error: typescript wrongly complains that styleProp cannot be used to index safeStyles
if (safeStyles[styleProp] === undefined) {
// @ts-expect-error don't need to care about index signature for deletion
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete safeStyles[styleProp];
}
}
const backgroundStyle = {
...(headerTransparent && {
backgroundColor: 'transparent'
}),
...((headerTransparent || headerShadowVisible === false) && {
borderBottomWidth: 0,
...Platform.select({
android: {
elevation: 0
},
web: {
boxShadow: 'none'
},
default: {
shadowOpacity: 0
}
})
}),
...safeStyles
};
const iconTintColor = headerTintColor ?? Platform.select({
ios: colors.primary,
default: colors.text
});
const leftButton = headerLeft ? headerLeft({
tintColor: iconTintColor,
pressColor: headerPressColor,
pressOpacity: headerPressOpacity,
displayMode: headerBackButtonDisplayMode,
titleLayout,
screenLayout: layout,
canGoBack: Boolean(back),
onPress: back ? navigation.goBack : undefined,
label: back?.title,
labelStyle: headerBackTitleStyle,
href: back?.href
}) : null;
const rightButton = headerRight ? headerRight({
tintColor: iconTintColor,
pressColor: headerPressColor,
pressOpacity: headerPressOpacity,
canGoBack: Boolean(back)
}) : null;
const headerTitle = typeof customTitle !== 'function' ? props => /*#__PURE__*/_jsx(HeaderTitle, {
...props
}) : customTitle;
return /*#__PURE__*/_jsxs(Animated.View, {
pointerEvents: "box-none",
style: [{
height,
minHeight,
maxHeight,
opacity,
transform
}],
children: [/*#__PURE__*/_jsx(Animated.View, {
pointerEvents: "box-none",
style: [StyleSheet.absoluteFill, backgroundContainerStyle],
children: headerBackground ? headerBackground({
style: backgroundStyle
}) : /*#__PURE__*/_jsx(HeaderBackground, {
pointerEvents:
// Allow touch through the header when background color is transparent
headerTransparent && (backgroundStyle.backgroundColor === 'transparent' || Color(backgroundStyle.backgroundColor).alpha() === 0) ? 'none' : 'auto',
style: backgroundStyle
})
}), /*#__PURE__*/_jsx(View, {
pointerEvents: "none",
style: {
height: headerStatusBarHeight
}
}), /*#__PURE__*/_jsxs(View, {
pointerEvents: "box-none",
style: [styles.content, Platform.OS === 'ios' && frame.width >= IPAD_MINI_MEDIUM_WIDTH ? styles.large : null],
children: [/*#__PURE__*/_jsx(Animated.View, {
pointerEvents: "box-none",
style: [styles.start, !searchBarVisible && headerTitleAlign === 'center' && styles.expand, {
marginStart: insets.left
}, leftContainerStyle],
children: leftButton
}), Platform.OS === 'ios' || !searchBarVisible ? /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(Animated.View, {
pointerEvents: "box-none",
style: [styles.title, {
// Avoid the title from going offscreen or overlapping buttons
maxWidth: headerTitleAlign === 'center' ? layout.width - ((leftButton ? headerBackButtonDisplayMode !== 'minimal' ? 80 : 32 : 16) + (rightButton || headerSearchBarOptions ? 16 : 0) + Math.max(insets.left, insets.right)) * 2 : layout.width - ((leftButton ? 52 : 16) + (rightButton || headerSearchBarOptions ? 52 : 16) + insets.left - insets.right)
}, headerTitleAlign === 'left' && leftButton ? {
marginStart: 4
} : {
marginHorizontal: 16
}, titleContainerStyle],
children: headerTitle({
children: title,
allowFontScaling: titleAllowFontScaling,
tintColor: headerTintColor,
onLayout: onTitleLayout,
style: titleStyle
})
}), /*#__PURE__*/_jsxs(Animated.View, {
pointerEvents: "box-none",
style: [styles.end, styles.expand, {
marginEnd: insets.right
}, rightContainerStyle],
children: [rightButton, headerSearchBarOptions ? /*#__PURE__*/_jsx(HeaderButton, {
tintColor: iconTintColor,
pressColor: headerPressColor,
pressOpacity: headerPressOpacity,
onPress: () => {
setSearchBarVisible(true);
headerSearchBarOptions?.onOpen?.();
},
children: /*#__PURE__*/_jsx(HeaderIcon, {
source: searchIcon,
tintColor: iconTintColor
})
}) : null]
})]
}) : null, Platform.OS === 'ios' || searchBarVisible ? /*#__PURE__*/_jsx(HeaderSearchBar, {
...headerSearchBarOptions,
visible: searchBarVisible,
onClose: () => {
setSearchBarVisible(false);
headerSearchBarOptions?.onClose?.();
},
tintColor: headerTintColor,
style: [Platform.OS === 'ios' ? [StyleSheet.absoluteFill, {
paddingTop: headerStatusBarHeight ? 0 : 4
}, {
backgroundColor: backgroundColor ?? colors.card
}] : !leftButton && {
marginStart: 8
}]
}) : null]
})]
});
}
const styles = StyleSheet.create({
content: {
flex: 1,
flexDirection: 'row',
alignItems: 'stretch'
},
large: {
marginHorizontal: 5
},
title: {
justifyContent: 'center'
},
start: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start'
},
end: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-end'
},
expand: {
flexGrow: 1,
flexBasis: 0
}
});
//# sourceMappingURL=Header.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,188 @@
"use strict";
import { useLocale, useTheme } from '@react-navigation/native';
import * as React from 'react';
import { Animated, Image, Platform, StyleSheet, View } from 'react-native';
import backIcon from '../assets/back-icon.png';
import backIconMask from '../assets/back-icon-mask.png';
import { MaskedView } from '../MaskedView';
import { HeaderButton } from "./HeaderButton.js";
import { HeaderIcon, ICON_MARGIN } from "./HeaderIcon.js";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
export function HeaderBackButton({
disabled,
allowFontScaling,
backImage,
label,
labelStyle,
displayMode = Platform.OS === 'ios' ? 'default' : 'minimal',
onLabelLayout,
onPress,
pressColor,
pressOpacity,
screenLayout,
tintColor,
titleLayout,
truncatedLabel = 'Back',
accessibilityLabel = label && label !== 'Back' ? `${label}, back` : 'Go back',
testID,
style,
href
}) {
const {
colors,
fonts
} = useTheme();
const {
direction
} = useLocale();
const [labelWidth, setLabelWidth] = React.useState(null);
const [truncatedLabelWidth, setTruncatedLabelWidth] = React.useState(null);
const renderBackImage = () => {
if (backImage) {
return backImage({
tintColor: tintColor ?? colors.text
});
} else {
return /*#__PURE__*/_jsx(HeaderIcon, {
source: backIcon,
tintColor: tintColor,
style: [styles.icon, displayMode !== 'minimal' && styles.iconWithLabel]
});
}
};
const renderLabel = () => {
if (displayMode === 'minimal') {
return null;
}
const availableSpace = titleLayout && screenLayout ? (screenLayout.width - titleLayout.width) / 2 - (ICON_WIDTH + ICON_MARGIN) : null;
const potentialLabelText = displayMode === 'default' ? label : truncatedLabel;
const finalLabelText = availableSpace && labelWidth && truncatedLabelWidth ? availableSpace > labelWidth ? potentialLabelText : availableSpace > truncatedLabelWidth ? truncatedLabel : null : potentialLabelText;
const commonStyle = [fonts.regular, styles.label, labelStyle];
const hiddenStyle = [commonStyle, {
position: 'absolute',
top: 0,
left: 0,
opacity: 0
}];
const labelElement = /*#__PURE__*/_jsxs(View, {
style: styles.labelWrapper,
children: [label && displayMode === 'default' ? /*#__PURE__*/_jsx(Animated.Text, {
style: hiddenStyle,
numberOfLines: 1,
onLayout: e => setLabelWidth(e.nativeEvent.layout.width),
children: label
}) : null, truncatedLabel ? /*#__PURE__*/_jsx(Animated.Text, {
style: hiddenStyle,
numberOfLines: 1,
onLayout: e => setTruncatedLabelWidth(e.nativeEvent.layout.width),
children: truncatedLabel
}) : null, finalLabelText ? /*#__PURE__*/_jsx(Animated.Text, {
accessible: false,
onLayout: onLabelLayout,
style: [tintColor ? {
color: tintColor
} : null, commonStyle],
numberOfLines: 1,
allowFontScaling: !!allowFontScaling,
children: finalLabelText
}) : null]
});
if (backImage || Platform.OS !== 'ios') {
// When a custom backimage is specified, we can't mask the label
// Otherwise there might be weird effect due to our mask not being the same as the image
return labelElement;
}
return /*#__PURE__*/_jsx(MaskedView, {
maskElement: /*#__PURE__*/_jsxs(View, {
style: [styles.iconMaskContainer,
// Extend the mask to the center of the screen so that label isn't clipped during animation
screenLayout ? {
minWidth: screenLayout.width / 2 - 27
} : null],
children: [/*#__PURE__*/_jsx(Image, {
source: backIconMask,
resizeMode: "contain",
style: [styles.iconMask, direction === 'rtl' && styles.flip]
}), /*#__PURE__*/_jsx(View, {
style: styles.iconMaskFillerRect
})]
}),
children: labelElement
});
};
const handlePress = () => {
if (onPress) {
requestAnimationFrame(() => onPress());
}
};
return /*#__PURE__*/_jsx(HeaderButton, {
disabled: disabled,
href: href,
accessibilityLabel: accessibilityLabel,
testID: testID,
onPress: handlePress,
pressColor: pressColor,
pressOpacity: pressOpacity,
style: [styles.container, style],
children: /*#__PURE__*/_jsxs(React.Fragment, {
children: [renderBackImage(), renderLabel()]
})
});
}
const ICON_WIDTH = Platform.OS === 'ios' ? 13 : 24;
const ICON_MARGIN_END = Platform.OS === 'ios' ? 22 : 3;
const styles = StyleSheet.create({
container: {
paddingHorizontal: 0,
minWidth: StyleSheet.hairlineWidth,
// Avoid collapsing when title is long
...Platform.select({
ios: null,
default: {
marginVertical: 3,
marginHorizontal: 11
}
})
},
label: {
fontSize: 17,
// Title and back label are a bit different width due to title being bold
// Adjusting the letterSpacing makes them coincide better
letterSpacing: 0.35
},
labelWrapper: {
// These styles will make sure that the label doesn't fill the available space
// Otherwise it messes with the measurement of the label
flexDirection: 'row',
alignItems: 'flex-start',
marginEnd: ICON_MARGIN
},
icon: {
width: ICON_WIDTH,
marginEnd: ICON_MARGIN_END
},
iconWithLabel: Platform.OS === 'ios' ? {
marginEnd: 6
} : {},
iconMaskContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center'
},
iconMaskFillerRect: {
flex: 1,
backgroundColor: '#000'
},
iconMask: {
height: 21,
width: 13,
marginStart: -14.5,
marginVertical: 12,
alignSelf: 'center'
},
flip: {
transform: 'scaleX(-1)'
}
});
//# sourceMappingURL=HeaderBackButton.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
"use strict";
import { getNamedContext } from "../getNamedContext.js";
export const HeaderBackContext = getNamedContext('HeaderBackContext', undefined);
//# sourceMappingURL=HeaderBackContext.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["getNamedContext","HeaderBackContext","undefined"],"sourceRoot":"../../../src","sources":["Header/HeaderBackContext.tsx"],"mappings":";;AAAA,SAASA,eAAe,QAAQ,uBAAoB;AAEpD,OAAO,MAAMC,iBAAiB,GAAGD,eAAe,CAE9C,mBAAmB,EAAEE,SAAS,CAAC","ignoreList":[]}

View File

@@ -0,0 +1,47 @@
"use strict";
import { useTheme } from '@react-navigation/native';
import * as React from 'react';
import { Animated, Platform, StyleSheet } from 'react-native';
import { jsx as _jsx } from "react/jsx-runtime";
export function HeaderBackground({
style,
...rest
}) {
const {
colors,
dark
} = useTheme();
return /*#__PURE__*/_jsx(Animated.View, {
style: [styles.container, {
backgroundColor: colors.card,
borderBottomColor: colors.border,
...(Platform.OS === 'ios' && {
shadowColor: dark ? 'rgba(255, 255, 255, 0.45)' : 'rgba(0, 0, 0, 1)'
})
}, style],
...rest
});
}
const styles = StyleSheet.create({
container: {
flex: 1,
...Platform.select({
android: {
elevation: 4
},
ios: {
shadowOpacity: 0.3,
shadowRadius: 0,
shadowOffset: {
width: 0,
height: StyleSheet.hairlineWidth
}
},
default: {
borderBottomWidth: StyleSheet.hairlineWidth
}
})
}
});
//# sourceMappingURL=HeaderBackground.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["useTheme","React","Animated","Platform","StyleSheet","jsx","_jsx","HeaderBackground","style","rest","colors","dark","View","styles","container","backgroundColor","card","borderBottomColor","border","OS","shadowColor","create","flex","select","android","elevation","ios","shadowOpacity","shadowRadius","shadowOffset","width","height","hairlineWidth","default","borderBottomWidth"],"sourceRoot":"../../../src","sources":["Header/HeaderBackground.tsx"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,0BAA0B;AACnD,OAAO,KAAKC,KAAK,MAAM,OAAO;AAC9B,SACEC,QAAQ,EACRC,QAAQ,EAERC,UAAU,QAGL,cAAc;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAOtB,OAAO,SAASC,gBAAgBA,CAAC;EAAEC,KAAK;EAAE,GAAGC;AAAY,CAAC,EAAE;EAC1D,MAAM;IAAEC,MAAM;IAAEC;EAAK,CAAC,GAAGX,QAAQ,CAAC,CAAC;EAEnC,oBACEM,IAAA,CAACJ,QAAQ,CAACU,IAAI;IACZJ,KAAK,EAAE,CACLK,MAAM,CAACC,SAAS,EAChB;MACEC,eAAe,EAAEL,MAAM,CAACM,IAAI;MAC5BC,iBAAiB,EAAEP,MAAM,CAACQ,MAAM;MAChC,IAAIf,QAAQ,CAACgB,EAAE,KAAK,KAAK,IAAI;QAC3BC,WAAW,EAAET,IAAI,GACb,2BAA2B,GAC3B;MACN,CAAC;IACH,CAAC,EACDH,KAAK,CACL;IAAA,GACEC;EAAI,CACT,CAAC;AAEN;AAEA,MAAMI,MAAM,GAAGT,UAAU,CAACiB,MAAM,CAAC;EAC/BP,SAAS,EAAE;IACTQ,IAAI,EAAE,CAAC;IACP,GAAGnB,QAAQ,CAACoB,MAAM,CAAC;MACjBC,OAAO,EAAE;QACPC,SAAS,EAAE;MACb,CAAC;MACDC,GAAG,EAAE;QACHC,aAAa,EAAE,GAAG;QAClBC,YAAY,EAAE,CAAC;QACfC,YAAY,EAAE;UACZC,KAAK,EAAE,CAAC;UACRC,MAAM,EAAE3B,UAAU,CAAC4B;QACrB;MACF,CAAC;MACDC,OAAO,EAAE;QACPC,iBAAiB,EAAE9B,UAAU,CAAC4B;MAChC;IACF,CAAC;EACH;AACF,CAAC,CAAC","ignoreList":[]}

View File

@@ -0,0 +1,61 @@
"use strict";
import * as React from 'react';
import { Platform, StyleSheet } from 'react-native';
import { PlatformPressable } from "../PlatformPressable.js";
import { jsx as _jsx } from "react/jsx-runtime";
function HeaderButtonInternal({
disabled,
onPress,
pressColor,
pressOpacity,
accessibilityLabel,
testID,
style,
href,
children
}, ref) {
return /*#__PURE__*/_jsx(PlatformPressable, {
ref: ref,
disabled: disabled,
href: href,
"aria-label": accessibilityLabel,
testID: testID,
onPress: onPress,
pressColor: pressColor,
pressOpacity: pressOpacity,
android_ripple: androidRipple,
style: [styles.container, disabled && styles.disabled, style],
hitSlop: Platform.select({
ios: undefined,
default: {
top: 16,
right: 16,
bottom: 16,
left: 16
}
}),
children: children
});
}
export const HeaderButton = /*#__PURE__*/React.forwardRef(HeaderButtonInternal);
HeaderButton.displayName = 'HeaderButton';
const androidRipple = {
borderless: true,
foreground: Platform.OS === 'android' && Platform.Version >= 23,
radius: 20
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
// Roundness for iPad hover effect
borderRadius: 10,
borderCurve: 'continuous'
},
disabled: {
opacity: 0.5
}
});
//# sourceMappingURL=HeaderButton.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["React","Platform","StyleSheet","PlatformPressable","jsx","_jsx","HeaderButtonInternal","disabled","onPress","pressColor","pressOpacity","accessibilityLabel","testID","style","href","children","ref","android_ripple","androidRipple","styles","container","hitSlop","select","ios","undefined","default","top","right","bottom","left","HeaderButton","forwardRef","displayName","borderless","foreground","OS","Version","radius","create","flexDirection","alignItems","paddingHorizontal","borderRadius","borderCurve","opacity"],"sourceRoot":"../../../src","sources":["Header/HeaderButton.tsx"],"mappings":";;AAAA,OAAO,KAAKA,KAAK,MAAM,OAAO;AAC9B,SAASC,QAAQ,EAAEC,UAAU,QAAQ,cAAc;AAEnD,SAASC,iBAAiB,QAAQ,yBAAsB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAGzD,SAASC,oBAAoBA,CAC3B;EACEC,QAAQ;EACRC,OAAO;EACPC,UAAU;EACVC,YAAY;EACZC,kBAAkB;EAClBC,MAAM;EACNC,KAAK;EACLC,IAAI;EACJC;AACiB,CAAC,EACpBC,GAA4D,EAC5D;EACA,oBACEX,IAAA,CAACF,iBAAiB;IAChBa,GAAG,EAAEA,GAAI;IACTT,QAAQ,EAAEA,QAAS;IACnBO,IAAI,EAAEA,IAAK;IACX,cAAYH,kBAAmB;IAC/BC,MAAM,EAAEA,MAAO;IACfJ,OAAO,EAAEA,OAAQ;IACjBC,UAAU,EAAEA,UAAW;IACvBC,YAAY,EAAEA,YAAa;IAC3BO,cAAc,EAAEC,aAAc;IAC9BL,KAAK,EAAE,CAACM,MAAM,CAACC,SAAS,EAAEb,QAAQ,IAAIY,MAAM,CAACZ,QAAQ,EAAEM,KAAK,CAAE;IAC9DQ,OAAO,EAAEpB,QAAQ,CAACqB,MAAM,CAAC;MACvBC,GAAG,EAAEC,SAAS;MACdC,OAAO,EAAE;QAAEC,GAAG,EAAE,EAAE;QAAEC,KAAK,EAAE,EAAE;QAAEC,MAAM,EAAE,EAAE;QAAEC,IAAI,EAAE;MAAG;IACtD,CAAC,CAAE;IAAAd,QAAA,EAEFA;EAAQ,CACQ,CAAC;AAExB;AAEA,OAAO,MAAMe,YAAY,gBAAG9B,KAAK,CAAC+B,UAAU,CAACzB,oBAAoB,CAAC;AAElEwB,YAAY,CAACE,WAAW,GAAG,cAAc;AAEzC,MAAMd,aAAa,GAAG;EACpBe,UAAU,EAAE,IAAI;EAChBC,UAAU,EAAEjC,QAAQ,CAACkC,EAAE,KAAK,SAAS,IAAIlC,QAAQ,CAACmC,OAAO,IAAI,EAAE;EAC/DC,MAAM,EAAE;AACV,CAAC;AAED,MAAMlB,MAAM,GAAGjB,UAAU,CAACoC,MAAM,CAAC;EAC/BlB,SAAS,EAAE;IACTmB,aAAa,EAAE,KAAK;IACpBC,UAAU,EAAE,QAAQ;IACpBC,iBAAiB,EAAE,CAAC;IACpB;IACAC,YAAY,EAAE,EAAE;IAChBC,WAAW,EAAE;EACf,CAAC;EACDpC,QAAQ,EAAE;IACRqC,OAAO,EAAE;EACX;AACF,CAAC,CAAC","ignoreList":[]}

View File

@@ -0,0 +1,5 @@
"use strict";
import { getNamedContext } from "../getNamedContext.js";
export const HeaderHeightContext = getNamedContext('HeaderHeightContext', undefined);
//# sourceMappingURL=HeaderHeightContext.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["getNamedContext","HeaderHeightContext","undefined"],"sourceRoot":"../../../src","sources":["Header/HeaderHeightContext.tsx"],"mappings":";;AAAA,SAASA,eAAe,QAAQ,uBAAoB;AAEpD,OAAO,MAAMC,mBAAmB,GAAGD,eAAe,CAChD,qBAAqB,EACrBE,SACF,CAAC","ignoreList":[]}

View File

@@ -0,0 +1,38 @@
"use strict";
import { useLocale, useTheme } from '@react-navigation/native';
import { Image, Platform, StyleSheet } from 'react-native';
import { jsx as _jsx } from "react/jsx-runtime";
export function HeaderIcon({
source,
style,
...rest
}) {
const {
colors
} = useTheme();
const {
direction
} = useLocale();
return /*#__PURE__*/_jsx(Image, {
source: source,
resizeMode: "contain",
fadeDuration: 0,
tintColor: colors.text,
style: [styles.icon, direction === 'rtl' && styles.flip, style],
...rest
});
}
export const ICON_SIZE = Platform.OS === 'ios' ? 21 : 24;
export const ICON_MARGIN = Platform.OS === 'ios' ? 8 : 3;
const styles = StyleSheet.create({
icon: {
width: ICON_SIZE,
height: ICON_SIZE,
margin: ICON_MARGIN
},
flip: {
transform: 'scaleX(-1)'
}
});
//# sourceMappingURL=HeaderIcon.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["useLocale","useTheme","Image","Platform","StyleSheet","jsx","_jsx","HeaderIcon","source","style","rest","colors","direction","resizeMode","fadeDuration","tintColor","text","styles","icon","flip","ICON_SIZE","OS","ICON_MARGIN","create","width","height","margin","transform"],"sourceRoot":"../../../src","sources":["Header/HeaderIcon.tsx"],"mappings":";;AAAA,SAASA,SAAS,EAAEC,QAAQ,QAAQ,0BAA0B;AAC9D,SAASC,KAAK,EAAmBC,QAAQ,EAAEC,UAAU,QAAQ,cAAc;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAE5E,OAAO,SAASC,UAAUA,CAAC;EAAEC,MAAM;EAAEC,KAAK;EAAE,GAAGC;AAAiB,CAAC,EAAE;EACjE,MAAM;IAAEC;EAAO,CAAC,GAAGV,QAAQ,CAAC,CAAC;EAC7B,MAAM;IAAEW;EAAU,CAAC,GAAGZ,SAAS,CAAC,CAAC;EAEjC,oBACEM,IAAA,CAACJ,KAAK;IACJM,MAAM,EAAEA,MAAO;IACfK,UAAU,EAAC,SAAS;IACpBC,YAAY,EAAE,CAAE;IAChBC,SAAS,EAAEJ,MAAM,CAACK,IAAK;IACvBP,KAAK,EAAE,CAACQ,MAAM,CAACC,IAAI,EAAEN,SAAS,KAAK,KAAK,IAAIK,MAAM,CAACE,IAAI,EAAEV,KAAK,CAAE;IAAA,GAC5DC;EAAI,CACT,CAAC;AAEN;AAEA,OAAO,MAAMU,SAAS,GAAGjB,QAAQ,CAACkB,EAAE,KAAK,KAAK,GAAG,EAAE,GAAG,EAAE;AACxD,OAAO,MAAMC,WAAW,GAAGnB,QAAQ,CAACkB,EAAE,KAAK,KAAK,GAAG,CAAC,GAAG,CAAC;AAExD,MAAMJ,MAAM,GAAGb,UAAU,CAACmB,MAAM,CAAC;EAC/BL,IAAI,EAAE;IACJM,KAAK,EAAEJ,SAAS;IAChBK,MAAM,EAAEL,SAAS;IACjBM,MAAM,EAAEJ;EACV,CAAC;EACDH,IAAI,EAAE;IACJQ,SAAS,EAAE;EACb;AACF,CAAC,CAAC","ignoreList":[]}

View File

@@ -0,0 +1,286 @@
"use strict";
import { useNavigation, useTheme } from '@react-navigation/native';
import Color from 'color';
import * as React from 'react';
import { Animated, Image, Platform, StyleSheet, TextInput, View } from 'react-native';
import clearIcon from '../assets/clear-icon.png';
import closeIcon from '../assets/close-icon.png';
import searchIcon from '../assets/search-icon.png';
import { PlatformPressable } from "../PlatformPressable.js";
import { Text } from "../Text.js";
import { HeaderButton } from "./HeaderButton.js";
import { HeaderIcon } from "./HeaderIcon.js";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const INPUT_TYPE_TO_MODE = {
text: 'text',
number: 'numeric',
phone: 'tel',
email: 'email'
};
const useNativeDriver = Platform.OS !== 'web';
function HeaderSearchBarInternal({
visible,
inputType,
autoFocus = true,
autoCapitalize,
placeholder = 'Search',
cancelButtonText = 'Cancel',
enterKeyHint = 'search',
onChangeText,
onClose,
tintColor,
style,
...rest
}, ref) {
const navigation = useNavigation();
const {
dark,
colors,
fonts
} = useTheme();
const [value, setValue] = React.useState('');
const [rendered, setRendered] = React.useState(visible);
const [visibleAnim] = React.useState(() => new Animated.Value(visible ? 1 : 0));
const [clearVisibleAnim] = React.useState(() => new Animated.Value(0));
const visibleValueRef = React.useRef(visible);
const clearVisibleValueRef = React.useRef(false);
const inputRef = React.useRef(null);
React.useEffect(() => {
// Avoid act warning in tests just by rendering header
if (visible === visibleValueRef.current) {
return;
}
Animated.timing(visibleAnim, {
toValue: visible ? 1 : 0,
duration: 100,
useNativeDriver
}).start(({
finished
}) => {
if (finished) {
setRendered(visible);
visibleValueRef.current = visible;
}
});
return () => {
visibleAnim.stopAnimation();
};
}, [visible, visibleAnim]);
const hasText = value !== '';
React.useEffect(() => {
if (clearVisibleValueRef.current === hasText) {
return;
}
Animated.timing(clearVisibleAnim, {
toValue: hasText ? 1 : 0,
duration: 100,
useNativeDriver
}).start(({
finished
}) => {
if (finished) {
clearVisibleValueRef.current = hasText;
}
});
}, [clearVisibleAnim, hasText]);
const clearText = React.useCallback(() => {
inputRef.current?.clear();
inputRef.current?.focus();
setValue('');
}, []);
const onClear = React.useCallback(() => {
clearText();
// FIXME: figure out how to create a SyntheticEvent
// @ts-expect-error: we don't have the native event here
onChangeText?.({
nativeEvent: {
text: ''
}
});
}, [clearText, onChangeText]);
const cancelSearch = React.useCallback(() => {
onClear();
onClose();
}, [onClear, onClose]);
React.useEffect(() => navigation?.addListener('blur', cancelSearch), [cancelSearch, navigation]);
React.useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current?.focus();
},
blur: () => {
inputRef.current?.blur();
},
setText: text => {
inputRef.current?.setNativeProps({
text
});
setValue(text);
},
clearText,
cancelSearch
}), [cancelSearch, clearText]);
if (!visible && !rendered) {
return null;
}
const textColor = tintColor ?? colors.text;
return /*#__PURE__*/_jsxs(Animated.View, {
pointerEvents: visible ? 'auto' : 'none',
"aria-live": "polite",
"aria-hidden": !visible,
style: [styles.container, {
opacity: visibleAnim
}, style],
children: [/*#__PURE__*/_jsxs(View, {
style: styles.searchbarContainer,
children: [/*#__PURE__*/_jsx(HeaderIcon, {
source: searchIcon,
tintColor: textColor,
style: styles.inputSearchIcon
}), /*#__PURE__*/_jsx(TextInput, {
...rest,
ref: inputRef,
onChange: onChangeText,
onChangeText: setValue,
autoFocus: autoFocus,
autoCapitalize: autoCapitalize === 'systemDefault' ? undefined : autoCapitalize,
inputMode: INPUT_TYPE_TO_MODE[inputType ?? 'text'],
enterKeyHint: enterKeyHint,
placeholder: placeholder,
placeholderTextColor: Color(textColor).alpha(0.5).string(),
cursorColor: colors.primary,
selectionHandleColor: colors.primary,
selectionColor: Color(colors.primary).alpha(0.3).string(),
style: [fonts.regular, styles.searchbar, {
backgroundColor: Platform.select({
ios: dark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)',
default: 'transparent'
}),
color: textColor,
borderBottomColor: Color(textColor).alpha(0.2).string()
}]
}), Platform.OS === 'ios' ? /*#__PURE__*/_jsx(PlatformPressable, {
onPress: onClear,
style: [{
opacity: clearVisibleAnim,
transform: [{
scale: clearVisibleAnim
}]
}, styles.clearButton],
children: /*#__PURE__*/_jsx(Image, {
source: clearIcon,
resizeMode: "contain",
tintColor: textColor,
style: styles.clearIcon
})
}) : null]
}), Platform.OS !== 'ios' ? /*#__PURE__*/_jsx(HeaderButton, {
onPress: () => {
if (value) {
onClear();
} else {
onClose();
}
},
style: styles.closeButton,
children: /*#__PURE__*/_jsx(HeaderIcon, {
source: closeIcon,
tintColor: textColor
})
}) : null, Platform.OS === 'ios' ? /*#__PURE__*/_jsx(PlatformPressable, {
onPress: cancelSearch,
style: styles.cancelButton,
children: /*#__PURE__*/_jsx(Text, {
style: [fonts.regular, {
color: tintColor ?? colors.primary
}, styles.cancelText],
children: cancelButtonText
})
}) : null]
});
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'stretch'
},
inputSearchIcon: {
position: 'absolute',
opacity: 0.5,
left: Platform.select({
ios: 16,
default: 4
}),
top: Platform.select({
ios: -1,
default: 17
}),
...Platform.select({
ios: {
height: 18,
width: 18
},
default: {}
})
},
closeButton: {
position: 'absolute',
opacity: 0.5,
right: Platform.select({
ios: 0,
default: 8
}),
top: Platform.select({
ios: -2,
default: 17
})
},
clearButton: {
position: 'absolute',
right: 0,
top: -7,
bottom: 0,
justifyContent: 'center',
padding: 8
},
clearIcon: {
height: 16,
width: 16,
opacity: 0.5
},
cancelButton: {
alignSelf: 'center',
top: -4
},
cancelText: {
fontSize: 17,
marginHorizontal: 12
},
searchbarContainer: {
flex: 1
},
searchbar: Platform.select({
ios: {
flex: 1,
fontSize: 17,
paddingHorizontal: 32,
marginLeft: 16,
marginTop: -1,
marginBottom: 4,
borderRadius: 8,
borderCurve: 'continuous'
},
default: {
flex: 1,
fontSize: 18,
paddingHorizontal: 36,
marginRight: 8,
marginTop: 8,
marginBottom: 8,
borderBottomWidth: 1
}
})
});
export const HeaderSearchBar = /*#__PURE__*/React.forwardRef(HeaderSearchBarInternal);
//# sourceMappingURL=HeaderSearchBar.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
"use strict";
import { getNamedContext } from "../getNamedContext.js";
export const HeaderShownContext = getNamedContext('HeaderShownContext', false);
//# sourceMappingURL=HeaderShownContext.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["getNamedContext","HeaderShownContext"],"sourceRoot":"../../../src","sources":["Header/HeaderShownContext.tsx"],"mappings":";;AAAA,SAASA,eAAe,QAAQ,uBAAoB;AAEpD,OAAO,MAAMC,kBAAkB,GAAGD,eAAe,CAAC,oBAAoB,EAAE,KAAK,CAAC","ignoreList":[]}

View File

@@ -0,0 +1,41 @@
"use strict";
import { useTheme } from '@react-navigation/native';
import { Animated, Platform, StyleSheet } from 'react-native';
import { jsx as _jsx } from "react/jsx-runtime";
export function HeaderTitle({
tintColor,
style,
...rest
}) {
const {
colors,
fonts
} = useTheme();
return /*#__PURE__*/_jsx(Animated.Text, {
role: "heading",
"aria-level": "1",
numberOfLines: 1,
...rest,
style: [{
color: tintColor === undefined ? colors.text : tintColor
}, Platform.select({
ios: fonts.bold,
default: fonts.medium
}), styles.title, style]
});
}
const styles = StyleSheet.create({
title: Platform.select({
ios: {
fontSize: 17
},
android: {
fontSize: 20
},
default: {
fontSize: 18
}
})
});
//# sourceMappingURL=HeaderTitle.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["useTheme","Animated","Platform","StyleSheet","jsx","_jsx","HeaderTitle","tintColor","style","rest","colors","fonts","Text","role","numberOfLines","color","undefined","text","select","ios","bold","default","medium","styles","title","create","fontSize","android"],"sourceRoot":"../../../src","sources":["Header/HeaderTitle.tsx"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,0BAA0B;AACnD,SACEC,QAAQ,EACRC,QAAQ,EAERC,UAAU,QAGL,cAAc;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAQtB,OAAO,SAASC,WAAWA,CAAC;EAAEC,SAAS;EAAEC,KAAK;EAAE,GAAGC;AAAY,CAAC,EAAE;EAChE,MAAM;IAAEC,MAAM;IAAEC;EAAM,CAAC,GAAGX,QAAQ,CAAC,CAAC;EAEpC,oBACEK,IAAA,CAACJ,QAAQ,CAACW,IAAI;IACZC,IAAI,EAAC,SAAS;IACd,cAAW,GAAG;IACdC,aAAa,EAAE,CAAE;IAAA,GACbL,IAAI;IACRD,KAAK,EAAE,CACL;MAAEO,KAAK,EAAER,SAAS,KAAKS,SAAS,GAAGN,MAAM,CAACO,IAAI,GAAGV;IAAU,CAAC,EAC5DL,QAAQ,CAACgB,MAAM,CAAC;MAAEC,GAAG,EAAER,KAAK,CAACS,IAAI;MAAEC,OAAO,EAAEV,KAAK,CAACW;IAAO,CAAC,CAAC,EAC3DC,MAAM,CAACC,KAAK,EACZhB,KAAK;EACL,CACH,CAAC;AAEN;AAEA,MAAMe,MAAM,GAAGpB,UAAU,CAACsB,MAAM,CAAC;EAC/BD,KAAK,EAAEtB,QAAQ,CAACgB,MAAM,CAAC;IACrBC,GAAG,EAAE;MACHO,QAAQ,EAAE;IACZ,CAAC;IACDC,OAAO,EAAE;MACPD,QAAQ,EAAE;IACZ,CAAC;IACDL,OAAO,EAAE;MACPK,QAAQ,EAAE;IACZ;EACF,CAAC;AACH,CAAC,CAAC","ignoreList":[]}

View File

@@ -0,0 +1,34 @@
"use strict";
import { PixelRatio, Platform } from 'react-native';
export function getDefaultHeaderHeight(layout, modalPresentation, topInset) {
let headerHeight;
// On models with Dynamic Island the status bar height is smaller than the safe area top inset.
const hasDynamicIsland = Platform.OS === 'ios' && topInset > 50;
const statusBarHeight = hasDynamicIsland ? topInset - (5 + 1 / PixelRatio.get()) : topInset;
const isLandscape = layout.width > layout.height;
if (Platform.OS === 'ios') {
if (Platform.isPad || Platform.isTV) {
if (modalPresentation) {
headerHeight = 56;
} else {
headerHeight = 50;
}
} else {
if (isLandscape) {
headerHeight = 32;
} else {
if (modalPresentation) {
headerHeight = 56;
} else {
headerHeight = 44;
}
}
}
} else {
headerHeight = 64;
}
return headerHeight + statusBarHeight;
}
//# sourceMappingURL=getDefaultHeaderHeight.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["PixelRatio","Platform","getDefaultHeaderHeight","layout","modalPresentation","topInset","headerHeight","hasDynamicIsland","OS","statusBarHeight","get","isLandscape","width","height","isPad","isTV"],"sourceRoot":"../../../src","sources":["Header/getDefaultHeaderHeight.tsx"],"mappings":";;AAAA,SAASA,UAAU,EAAEC,QAAQ,QAAQ,cAAc;AAInD,OAAO,SAASC,sBAAsBA,CACpCC,MAAc,EACdC,iBAA0B,EAC1BC,QAAgB,EACR;EACR,IAAIC,YAAY;;EAEhB;EACA,MAAMC,gBAAgB,GAAGN,QAAQ,CAACO,EAAE,KAAK,KAAK,IAAIH,QAAQ,GAAG,EAAE;EAC/D,MAAMI,eAAe,GAAGF,gBAAgB,GACpCF,QAAQ,IAAI,CAAC,GAAG,CAAC,GAAGL,UAAU,CAACU,GAAG,CAAC,CAAC,CAAC,GACrCL,QAAQ;EAEZ,MAAMM,WAAW,GAAGR,MAAM,CAACS,KAAK,GAAGT,MAAM,CAACU,MAAM;EAEhD,IAAIZ,QAAQ,CAACO,EAAE,KAAK,KAAK,EAAE;IACzB,IAAIP,QAAQ,CAACa,KAAK,IAAIb,QAAQ,CAACc,IAAI,EAAE;MACnC,IAAIX,iBAAiB,EAAE;QACrBE,YAAY,GAAG,EAAE;MACnB,CAAC,MAAM;QACLA,YAAY,GAAG,EAAE;MACnB;IACF,CAAC,MAAM;MACL,IAAIK,WAAW,EAAE;QACfL,YAAY,GAAG,EAAE;MACnB,CAAC,MAAM;QACL,IAAIF,iBAAiB,EAAE;UACrBE,YAAY,GAAG,EAAE;QACnB,CAAC,MAAM;UACLA,YAAY,GAAG,EAAE;QACnB;MACF;IACF;EACF,CAAC,MAAM;IACLA,YAAY,GAAG,EAAE;EACnB;EAEA,OAAOA,YAAY,GAAGG,eAAe;AACvC","ignoreList":[]}

View File

@@ -0,0 +1,6 @@
"use strict";
export function getHeaderTitle(options, fallback) {
return typeof options.headerTitle === 'string' ? options.headerTitle : options.title !== undefined ? options.title : fallback;
}
//# sourceMappingURL=getHeaderTitle.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["getHeaderTitle","options","fallback","headerTitle","title","undefined"],"sourceRoot":"../../../src","sources":["Header/getHeaderTitle.tsx"],"mappings":";;AAEA,OAAO,SAASA,cAAcA,CAC5BC,OAAuE,EACvEC,QAAgB,EACR;EACR,OAAO,OAAOD,OAAO,CAACE,WAAW,KAAK,QAAQ,GAC1CF,OAAO,CAACE,WAAW,GACnBF,OAAO,CAACG,KAAK,KAAKC,SAAS,GACzBJ,OAAO,CAACG,KAAK,GACbF,QAAQ;AAChB","ignoreList":[]}

View File

@@ -0,0 +1,12 @@
"use strict";
import * as React from 'react';
import { HeaderHeightContext } from "./HeaderHeightContext.js";
export function useHeaderHeight() {
const height = React.useContext(HeaderHeightContext);
if (height === undefined) {
throw new Error("Couldn't find the header height. Are you inside a screen in a navigator with a header?");
}
return height;
}
//# sourceMappingURL=useHeaderHeight.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["React","HeaderHeightContext","useHeaderHeight","height","useContext","undefined","Error"],"sourceRoot":"../../../src","sources":["Header/useHeaderHeight.tsx"],"mappings":";;AAAA,OAAO,KAAKA,KAAK,MAAM,OAAO;AAE9B,SAASC,mBAAmB,QAAQ,0BAAuB;AAE3D,OAAO,SAASC,eAAeA,CAAA,EAAG;EAChC,MAAMC,MAAM,GAAGH,KAAK,CAACI,UAAU,CAACH,mBAAmB,CAAC;EAEpD,IAAIE,MAAM,KAAKE,SAAS,EAAE;IACxB,MAAM,IAAIC,KAAK,CACb,wFACF,CAAC;EACH;EAEA,OAAOH,MAAM;AACf","ignoreList":[]}