react-native#LayoutChangeEvent TypeScript Examples
The following examples show how to use
react-native#LayoutChangeEvent.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: AutoSizer.tsx From react-native-paper-dates with MIT License | 6 votes |
export default function AutoSizer({
children,
}: {
children: ({ width, height }: WidthAndHeight) => any
}) {
const [layout, setLayout] = React.useState<WidthAndHeight | null>(null)
const onLayout = React.useCallback(
(event: LayoutChangeEvent) => {
const nl = event.nativeEvent.layout
// https://github.com/necolas/react-native-web/issues/1704
if (!layout || layout.width !== nl.width || layout.height !== nl.height) {
setLayout({ width: nl.width, height: nl.height })
}
},
[layout, setLayout]
)
return (
<View style={[styles.autoSizer, layout && layout]} onLayout={onLayout}>
{layout ? children(layout) : null}
</View>
)
}
Example #2
Source File: withResponsiveWidth.tsx From selftrace with MIT License | 6 votes |
export function withResponsiveWidth<T extends ResponsiveWidthRenderProps>(
WrappedComponent: React.ComponentType<T>
) {
return (props: T): React.ClassicElement<Omit<T, 'responsiveWidth'>> => {
const [responsiveWidth, setResponsiveWidth] = React.useState<ResponsiveSize | undefined>(
undefined
);
const [screenWidth, setScreenWidth] = React.useState<number | undefined>(undefined);
const onLayout = (event: LayoutChangeEvent) => {
const newScreenWidth = Dimensions.get('window').width;
const newResponsiveWidth = getResponsiveSize(event.nativeEvent.layout.width);
if (newScreenWidth !== screenWidth) {
setScreenWidth(newScreenWidth);
}
if (newScreenWidth !== screenWidth && newResponsiveWidth !== responsiveWidth) {
setResponsiveWidth(newResponsiveWidth);
}
};
return (
<View onLayout={onLayout} style={{ flex: 1 }}>
<WrappedComponent {...(props as T)} responsiveWidth={responsiveWidth} />
</View>
);
};
}
Example #3
Source File: ProgressBar.tsx From react-native-jigsaw with MIT License | 6 votes |
handleLayout = (event: LayoutChangeEvent) => {
const { width = 150, onLayout } = this.props;
if (!width) {
this.setState({ width: event.nativeEvent.layout.width });
}
if (onLayout) {
onLayout(event);
}
};
Example #4
Source File: HeaderSegment.tsx From nlw2-proffy with MIT License | 6 votes |
private handleTitleLayout = (e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout;
this.setState(({ titleLayout }) => {
if (
titleLayout &&
height === titleLayout.height &&
width === titleLayout.width
) {
return null;
}
return {
titleLayout: { height, width },
};
});
};
Example #5
Source File: HeaderSegment.tsx From nlw2-proffy with MIT License | 6 votes |
private handleLeftLabelLayout = (e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout;
const { leftLabelLayout } = this.state;
if (
leftLabelLayout &&
height === leftLabelLayout.height &&
width === leftLabelLayout.width
) {
return;
}
this.setState({ leftLabelLayout: { height, width } });
};
Example #6
Source File: CardStack.tsx From nlw2-proffy with MIT License | 6 votes |
private handleLayout = (e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout;
const layout = { width, height };
this.setState((state, props) => {
if (height === state.layout.height && width === state.layout.width) {
return null;
}
return {
layout,
headerHeights: getHeaderHeights(
props.routes,
props.insets,
state.descriptors,
layout,
state.headerHeights
),
};
});
};
Example #7
Source File: BubbleChart.tsx From companion-kit with MIT License | 6 votes |
onLayout = (e: LayoutChangeEvent) => {
if (this._layoutReady) {
// for some reason onLayout fires twice
return;
}
const { height, width } = e.nativeEvent.layout;
this.setState({ height, width }, () => {
this._layoutReady = true;
this.activateChart();
});
}
Example #8
Source File: AlphabetScroller.tsx From jellyfin-audio-player with MIT License | 6 votes |
AlphabetScroller: React.FC<Props> = ({ onSelect }) => {
const [ height, setHeight ] = useState(0);
const [ index, setIndex ] = useState<number>();
// Handler for setting the correct height for a single alphabet item
const handleLayout = useCallback((event: LayoutChangeEvent) => {
setHeight(event.nativeEvent.layout.height);
}, []);
// Handler for passing on a new index when it is tapped or swiped
const handleGestureEvent = useCallback((event: PanGestureHandlerGestureEvent | TapGestureHandlerGestureEvent) => {
const newIndex = Math.floor(event.nativeEvent.y / height);
if (newIndex !== index) {
setIndex(newIndex);
onSelect(newIndex);
}
}, [height, index, onSelect]);
return (
<Container>
<TapGestureHandler onHandlerStateChange={handleGestureEvent}>
<PanGestureHandler onGestureEvent={handleGestureEvent}>
<View>
{ALPHABET_LETTERS.split('').map((l, i) => (
<View
key={l}
onLayout={i === 0 ? handleLayout : undefined}
>
<Letter>{l}</Letter>
</View>
))}
</View>
</PanGestureHandler>
</TapGestureHandler>
</Container>
);
}
Example #9
Source File: index.tsx From frontatish with MIT License | 5 votes |
Button = (props: ButtonProps) => {
const { customStyles, disabled, label, type, onPress, loading } = props;
const [width, setWidth] = useState<number>(0);
// getting the suitable color based on the theme
// activated inside the app
const Colors = useColors();
// getting base button styles defined
// by our design guidelines, component
// also takes custom styles so that can also
// be applied
const baseBtnStyles: StyleType = getBtnStyles(type, Colors, disabled);
const mainBtnStyles: StyleType = {
...baseBtnStyles,
...customStyles,
};
const baseLabelStyles: StyleType = getLabelStyles(type, Colors, disabled);
const measureLayout = (e: LayoutChangeEvent) => {
setWidth(e.nativeEvent.layout.width);
};
const renderProgressBar = () => {
const barColor = type === 'primary' ? Colors.white : Colors.primary;
return (
<View
style={{
position: 'absolute',
top: 0,
borderRadius: 5,
}}
>
<Progress width={width} barColor={barColor} />
</View>
);
};
return (
<Ripple
onPress={onPress}
disabled={disabled}
style={mainBtnStyles}
onLayout={measureLayout}
>
{loading && renderProgressBar()}
<Text style={baseLabelStyles}>{label}</Text>
</Ripple>
);
}
Example #10
Source File: Collapse.tsx From react-native-design-kit with MIT License | 5 votes |
export default function Collapse({
visible = false,
animationDuration = 250,
byPassAnimationCallback = false,
children,
}: CollapseProps) {
const [animating, setAnimating] = useState(false);
const height = useRef<number>(0);
const animation = useRef(new Animated.Value(visible ? 1 : 0)).current;
const toggle = useRef(visible);
const fakeContent = !animating && !toggle.current;
const handleLayoutView = useCallback(
(event: LayoutChangeEvent) => {
if (!animating) {
height.current = event.nativeEvent.layout.height;
}
},
[animating],
);
const handleRunAnimation = useCallback(() => {
setAnimating(true);
Animated.timing(animation, {
toValue: visible ? 1 : 0,
duration: animationDuration,
useNativeDriver: false,
}).start(callback => {
if (callback.finished || byPassAnimationCallback) {
toggle.current = visible;
setAnimating(false);
}
});
}, [animation, visible, animationDuration]);
useDidUpdate(handleRunAnimation, [handleRunAnimation]);
return (
<Animated.View
style={StyleSheet.flatten([
toggle.current && !animating
? undefined
: {
overflow: 'hidden',
height: animation.interpolate({
inputRange: [0, 1],
outputRange: [0, height.current],
}),
},
])}>
<View
testID="view"
pointerEvents={fakeContent ? 'none' : 'auto'}
style={StyleSheet.flatten([fakeContent && {position: 'absolute'}])}
onLayout={handleLayoutView}>
{children}
</View>
</Animated.View>
);
}
Example #11
Source File: SwitchableText.tsx From react-native-design-kit with MIT License | 5 votes |
export default function SwitchableText({
containerStyle,
texts,
textStyle,
duration = 2000,
progressBar = true,
byPassAnimationCallback = false,
progressBarStyle,
}: SwitchableTextProps) {
const [index, setIndex] = useState(0);
const [width, setWidth] = useState<number>();
const animation = useRef(new Animated.Value(0)).current;
const handleLayoutText = useCallback(
(event: LayoutChangeEvent) =>
progressBar && setWidth(event.nativeEvent.layout.width),
[progressBar],
);
const handleRenderText = useMemo(
() => (
<Text
testID="text"
style={StyleSheet.flatten([styles.text, textStyle])}
onLayout={handleLayoutText}>
{texts[index]}
</Text>
),
[index, texts, textStyle, handleLayoutText],
);
const handleRenderBar = useMemo(() => {
return (
progressBar &&
width !== undefined && (
<Animated.View
style={StyleSheet.flatten([
styles.progress,
progressBarStyle,
{
width: animation.interpolate({
inputRange: [0, 1],
outputRange: [0, width],
}),
},
])}
/>
)
);
}, [width, animation, progressBar, progressBarStyle]);
const handleRunAnimation = useCallback(() => {
animation.setValue(0);
Animated.timing(animation, {
toValue: 1,
duration,
useNativeDriver: false,
}).start(
callback =>
(byPassAnimationCallback || callback.finished) &&
setIndex(index + 1 >= texts.length ? 0 : index + 1),
);
}, [animation, index, duration, texts]);
useDidUpdate(() => {
setIndex(0);
}, [texts]);
useEffect(handleRunAnimation, [handleRunAnimation]);
return texts.length > 0 ? (
<View style={containerStyle}>
{handleRenderText}
{handleRenderBar}
</View>
) : null;
}
Example #12
Source File: index.d.ts From react-native-actions-sheet with MIT License | 5 votes |
_showModal: (event: LayoutChangeEvent) => Promise<void>;
Example #13
Source File: index.d.ts From react-native-actions-sheet with MIT License | 5 votes |
_onDeviceLayout: (_event: LayoutChangeEvent) => Promise<void>;
Example #14
Source File: index.tsx From react-native-actions-sheet with MIT License | 5 votes |
_showModal = async (event: LayoutChangeEvent) => {
let { gestureEnabled, delayActionSheetDraw, delayActionSheetDrawTime } =
this.props;
if (!event?.nativeEvent) return;
let height = event.nativeEvent.layout.height;
if (this.layoutHasCalled) {
this.actionSheetHeight = height;
this._returnToPrevScrollPosition(height);
return;
} else {
this.initialScrolling = true;
this.layoutHasCalled = true;
this.actionSheetHeight = height;
let scrollOffset = this.getInitialScrollPosition();
this.isRecoiling = false;
if (Platform.OS === "ios") {
await waitAsync(delayActionSheetDrawTime ?? 0);
} else {
if (delayActionSheetDraw) {
await waitAsync(delayActionSheetDrawTime ?? 0);
}
}
this._scrollTo(scrollOffset, false);
this.prevScroll = scrollOffset;
if (Platform.OS === "ios") {
await waitAsync(delayActionSheetDrawTime ?? 0 / 2);
} else {
if (delayActionSheetDraw) {
await waitAsync((delayActionSheetDrawTime ?? 0) / 2);
}
}
this._openAnimation(scrollOffset);
this.underlayScale.setValue(1);
this.underlayTranslateY.setValue(100);
if (!gestureEnabled) {
this.props.onPositionChanged && this.props.onPositionChanged(true);
}
this.updateActionSheetPosition(scrollOffset);
}
};
Example #15
Source File: index.tsx From react-native-actions-sheet with MIT License | 5 votes |
_onDeviceLayout = async (_event: LayoutChangeEvent) => {
let event = { ..._event };
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(async () => {
let safeMarginFromTop = 0;
let measuredPadding =
Platform.OS === "ios" ? await this.measure() : StatusBar.currentHeight;
if (!this.props.drawUnderStatusBar) {
if (Platform.OS === "android" && !this.props.statusBarTranslucent)
return;
safeMarginFromTop = measuredPadding ?? 0;
if (measuredPadding) {
this.indicatorTranslateY.setValue(-measuredPadding);
}
} else {
this.updateActionSheetPosition(this.offsetY);
}
let height = event.nativeEvent.layout.height - safeMarginFromTop;
let width = Dimensions.get("window").width;
if (
height?.toFixed(0) === calculatedDeviceHeight?.toFixed(0) &&
width?.toFixed(0) === this.state.deviceWidth?.toFixed(0) &&
this.deviceLayoutCalled
)
return;
this.deviceLayoutCalled = true;
calculatedDeviceHeight = height;
this.setState({
deviceHeight: height,
deviceWidth: width,
portrait: height > width,
paddingTop: measuredPadding ?? 0,
});
}, 1);
};
Example #16
Source File: index.tsx From frontatish with MIT License | 5 votes |
handleLayout = (e: LayoutChangeEvent) => {
this.setState({
measured: true,
height: e.nativeEvent.layout.height,
});
};
Example #17
Source File: SimplePopover.tsx From react-native-portal with MIT License | 5 votes |
SimplePopover = ({
position,
targetLayout,
onPress,
}: SimplePopoverProps) => {
const [layout, setLayout] = useState({
width: 0,
height: 0,
});
const popoverPosition = useMemo(
() => ({
opacity: layout.height === 0 || layout.width === 0 ? 0 : 1,
top:
position === 'bottom'
? targetLayout.y + targetLayout.height + SPACE
: targetLayout.y - layout.height - SPACE,
left: targetLayout.x + targetLayout.width / 2 - layout.width / 2,
}),
[position, layout, targetLayout]
);
const popoverContainerStyle = useMemo(
() => [styles.popoverContainer, popoverPosition],
[popoverPosition]
);
// callbacks
const handlePopoverLayout = useCallback(
({
nativeEvent: {
layout: { height, width },
},
}: LayoutChangeEvent) => {
setLayout(state => {
if (state.height === height && state.width === width) {
return state;
}
return {
height,
width,
};
});
},
[]
);
return (
<TouchableWithoutFeedback onPress={onPress} style={styles.buttonContainer}>
<View style={styles.backdropContainer}>
<View onLayout={handlePopoverLayout} style={popoverContainerStyle}>
<Text>Simple Popover</Text>
</View>
</View>
</TouchableWithoutFeedback>
);
}
Example #18
Source File: Notifier.tsx From react-native-notifier with MIT License | 5 votes |
private onLayout({ nativeEvent }: LayoutChangeEvent) {
const heightWithMargin = nativeEvent.layout.height + 50;
this.hiddenComponentValue = -Math.max(heightWithMargin, DEFAULT_COMPONENT_HEIGHT);
}
Example #19
Source File: PaperOnboarding.tsx From react-native-paper-onboarding with MIT License | 4 votes |
PaperOnboardingComponent = forwardRef<
PaperOnboarding,
PaperOnboardingProps
>(
(
{
data,
safeInsets: _safeInsets,
direction = DEFAULT_DIRECTION,
// indicator config
indicatorSize = DEFAULT_INDICATOR_SIZE,
indicatorBackgroundColor = DEFAULT_INDICATOR_BACKGROUND_COLOR,
indicatorBorderColor = DEFAULT_INDICATOR_BORDER_COLOR,
// override styles
titleStyle,
descriptionStyle,
// close button config
closeButton,
closeButtonTextStyle,
closeButtonText = DEFAULT_CLOSE_BUTTON_TEXT,
onCloseButtonPress = DEFAULT_CLOSE_BUTTON_CALLBACK,
onIndexChange,
},
ref
) => {
// state
const [dimensions, setDimensions] =
useState<PaperOnboardingScreenDimensions>({
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
});
// refs
const indexRef = useRef<number>(0);
const pagesRef = useRef<Array<Animated.View | null>>(data.map(() => null));
//#region variables
const safeInsets = useMemo<Required<Insets>>(() => {
return {
top: _safeInsets?.top ?? DEFAULT_SAFE_INSET,
bottom: _safeInsets?.bottom ?? DEFAULT_SAFE_INSET,
left: _safeInsets?.left ?? DEFAULT_SAFE_INSET,
right: _safeInsets?.right ?? DEFAULT_SAFE_INSET,
};
}, [_safeInsets]);
const indicatorsContainerLeftPadding = useMemo(() => {
const containerLeftPadding = dimensions.width / 2 - indicatorSize / 2;
return I18nManager.isRTL
? -containerLeftPadding + indicatorSize * (data.length - 1)
: containerLeftPadding;
}, [dimensions.width, indicatorSize, data.length]);
//#endregion
//#region animated variables
const { gestureHandler, state, translation, velocity } =
usePanGestureHandler();
const animatedStaticIndex = useValue<number>(0);
const animatedOverrideIndex = useValue<number>(0);
const animatedIndex = useTiming({
animatedStaticIndex,
animatedOverrideIndex,
value: direction === 'horizontal' ? translation.x : translation.y,
velocity: direction === 'horizontal' ? velocity.x : velocity.y,
state: state,
size: data.length,
screenWidth: dimensions.width,
});
const indicatorsContainerPosition = useMemo(
() => data.map((_, index) => index * indicatorSize * -1),
[data, indicatorSize]
);
const animatedIndicatorsContainerPosition = useMemo(
() =>
add(
interpolate(animatedIndex, {
inputRange: data.map((_, index) => index),
outputRange: I18nManager.isRTL
? indicatorsContainerPosition.reverse()
: indicatorsContainerPosition,
extrapolate: Animated.Extrapolate.CLAMP,
}),
indicatorsContainerLeftPadding
),
[
data,
animatedIndex,
indicatorsContainerLeftPadding,
indicatorsContainerPosition,
]
);
//#endregion
//#region callbacks
const handlePageRef = useCallback((pageRef, index) => {
pagesRef.current[index] = pageRef;
}, []);
const handleOnLayout = useCallback(
({
nativeEvent: {
layout: { width, height },
},
}: LayoutChangeEvent) => {
setDimensions({
width,
height,
});
},
[]
);
//#endregion
//#region public methods
const handleNavigateToNextPage = useCallback(() => {
const currentIndex = indexRef.current;
if (currentIndex === data.length - 1) {
return;
}
animatedOverrideIndex.setValue(currentIndex + 1);
}, [data, animatedOverrideIndex]);
const handleNavigateToPreviousPage = useCallback(() => {
const currentIndex = indexRef.current;
if (currentIndex === 0) {
return;
}
animatedOverrideIndex.setValue(currentIndex - 1);
}, [animatedOverrideIndex]);
useImperativeHandle(
ref,
() => ({
next: handleNavigateToNextPage,
previous: handleNavigateToPreviousPage,
}),
[handleNavigateToNextPage, handleNavigateToPreviousPage]
);
//#endregion
//#region effects
useCode(
() =>
onChange(
animatedStaticIndex,
call([animatedStaticIndex], args => {
indexRef.current = args[0];
/**
* @DEV
* here we directly manipulate pages native props by setting `pointerEvents`
* to `auto` for current page and `none` for others.
*/
pagesRef.current.map((pageRef, _index) => {
// @ts-ignore
pageRef.setNativeProps({
pointerEvents: _index === args[0] ? 'auto' : 'none',
});
});
if (onIndexChange) {
onIndexChange(args[0]);
}
})
),
[]
);
//#endregion
// renders
return (
<PanGestureHandler {...gestureHandler}>
<Animated.View onLayout={handleOnLayout} style={styles.container}>
<Background
animatedIndex={animatedIndex}
data={data}
safeInsets={safeInsets}
screenDimensions={dimensions}
indicatorSize={indicatorSize}
animatedIndicatorsContainerPosition={
animatedIndicatorsContainerPosition
}
/>
{data.map((item, index) => (
<Page
key={`page-${index}`}
index={index}
item={item}
animatedIndex={animatedIndex}
indicatorSize={indicatorSize}
titleStyle={titleStyle}
descriptionStyle={descriptionStyle}
safeInsets={safeInsets}
screenDimensions={dimensions}
handleRef={handlePageRef}
/>
))}
<IndicatorsContainer
data={data}
animatedIndex={animatedIndex}
animatedIndicatorsContainerPosition={
animatedIndicatorsContainerPosition
}
indicatorSize={indicatorSize}
indicatorBackgroundColor={indicatorBackgroundColor}
indicatorBorderColor={indicatorBorderColor}
safeInsets={safeInsets}
/>
<CloseButton
data={data}
animatedIndex={animatedIndex}
safeInsets={safeInsets}
closeButtonText={closeButtonText}
closeButtonTextStyle={closeButtonTextStyle}
closeButton={closeButton}
onCloseButtonPress={onCloseButtonPress}
/>
</Animated.View>
</PanGestureHandler>
);
}
)
Example #20
Source File: HeaderBackButton.tsx From nlw2-proffy with MIT License | 4 votes |
export default function HeaderBackButton({
disabled,
allowFontScaling,
backImage,
label,
labelStyle,
labelVisible = Platform.OS === 'ios',
onLabelLayout,
onPress,
pressColorAndroid: customPressColorAndroid,
screenLayout,
tintColor: customTintColor,
titleLayout,
truncatedLabel = 'Back',
accessibilityLabel = label && label !== 'Back' ? `${label}, back` : 'Go back',
style,
}: Props) {
const { dark, colors } = useTheme();
const [initialLabelWidth, setInitialLabelWidth] = React.useState<
undefined | number
>(undefined);
const tintColor =
customTintColor !== undefined
? customTintColor
: Platform.select({
ios: colors.primary,
default: colors.text,
});
const pressColorAndroid =
customPressColorAndroid !== undefined
? customPressColorAndroid
: dark
? 'rgba(255, 255, 255, .32)'
: 'rgba(0, 0, 0, .32)';
const handleLabelLayout = (e: LayoutChangeEvent) => {
onLabelLayout?.(e);
setInitialLabelWidth(e.nativeEvent.layout.x + e.nativeEvent.layout.width);
};
const shouldTruncateLabel = () => {
return (
!label ||
(initialLabelWidth &&
titleLayout &&
screenLayout &&
(screenLayout.width - titleLayout.width) / 2 < initialLabelWidth + 26)
);
};
const renderBackImage = () => {
if (backImage) {
return backImage({ tintColor });
} else {
return (
<Image
style={[
styles.icon,
Boolean(labelVisible) && styles.iconWithLabel,
Boolean(tintColor) && { tintColor },
]}
source={require('../assets/back-icon.png')}
fadeDuration={0}
/>
);
}
};
const renderLabel = () => {
const leftLabelText = shouldTruncateLabel() ? truncatedLabel : label;
if (!labelVisible || leftLabelText === undefined) {
return null;
}
const labelElement = (
<View
style={
screenLayout
? // We make the button extend till the middle of the screen
// Otherwise it appears to cut off when translating
[styles.labelWrapper, { minWidth: screenLayout.width / 2 - 27 }]
: null
}
>
<Animated.Text
accessible={false}
onLayout={
// This measurement is used to determine if we should truncate the label when it doesn't fit
// Only measure it when label is not truncated because we want the measurement of full label
leftLabelText === label ? handleLabelLayout : undefined
}
style={[
styles.label,
tintColor ? { color: tintColor } : null,
labelStyle,
]}
numberOfLines={1}
allowFontScaling={!!allowFontScaling}
>
{leftLabelText}
</Animated.Text>
</View>
);
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 (
<MaskedView
maskElement={
<View style={styles.iconMaskContainer}>
<Image
source={require('../assets/back-icon-mask.png')}
style={styles.iconMask}
/>
<View style={styles.iconMaskFillerRect} />
</View>
}
>
{labelElement}
</MaskedView>
);
};
const handlePress = () => onPress && requestAnimationFrame(onPress);
return (
<TouchableItem
disabled={disabled}
accessible
accessibilityRole="button"
accessibilityComponentType="button"
accessibilityLabel={accessibilityLabel}
accessibilityTraits="button"
testID="header-back"
delayPressIn={0}
onPress={disabled ? undefined : handlePress}
pressColor={pressColorAndroid}
style={[styles.container, disabled && styles.disabled, style]}
hitSlop={Platform.select({
ios: undefined,
default: { top: 16, right: 16, bottom: 16, left: 16 },
})}
borderless
>
<React.Fragment>
{renderBackImage()}
{renderLabel()}
</React.Fragment>
</TouchableItem>
);
}
Example #21
Source File: BottomTabBar.tsx From nlw2-proffy with MIT License | 4 votes |
export default function BottomTabBar({
state,
navigation,
descriptors,
activeBackgroundColor,
activeTintColor,
adaptive = true,
allowFontScaling,
inactiveBackgroundColor,
inactiveTintColor,
keyboardHidesTabBar = false,
labelPosition,
labelStyle,
iconStyle,
safeAreaInsets,
showLabel,
style,
tabStyle,
}: Props) {
const { colors } = useTheme();
const buildLink = useLinkBuilder();
const focusedRoute = state.routes[state.index];
const focusedDescriptor = descriptors[focusedRoute.key];
const focusedOptions = focusedDescriptor.options;
const dimensions = useWindowDimensions();
const isKeyboardShown = useIsKeyboardShown();
const shouldShowTabBar =
focusedOptions.tabBarVisible !== false &&
!(keyboardHidesTabBar && isKeyboardShown);
const visibilityAnimationConfigRef = React.useRef(
focusedOptions.tabBarVisibilityAnimationConfig
);
React.useEffect(() => {
visibilityAnimationConfigRef.current =
focusedOptions.tabBarVisibilityAnimationConfig;
});
const [isTabBarHidden, setIsTabBarHidden] = React.useState(!shouldShowTabBar);
const [visible] = React.useState(
() => new Animated.Value(shouldShowTabBar ? 1 : 0)
);
React.useEffect(() => {
const visibilityAnimationConfig = visibilityAnimationConfigRef.current;
if (shouldShowTabBar) {
const animation =
visibilityAnimationConfig?.show?.animation === 'spring'
? Animated.spring
: Animated.timing;
animation(visible, {
toValue: 1,
useNativeDriver,
duration: 250,
...visibilityAnimationConfig?.show?.config,
}).start(({ finished }) => {
if (finished) {
setIsTabBarHidden(false);
}
});
} else {
setIsTabBarHidden(true);
const animation =
visibilityAnimationConfig?.hide?.animation === 'spring'
? Animated.spring
: Animated.timing;
animation(visible, {
toValue: 0,
useNativeDriver,
duration: 200,
...visibilityAnimationConfig?.hide?.config,
}).start();
}
}, [visible, shouldShowTabBar]);
const [layout, setLayout] = React.useState({
height: 0,
width: dimensions.width,
});
const handleLayout = (e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout;
setLayout((layout) => {
if (height === layout.height && width === layout.width) {
return layout;
} else {
return {
height,
width,
};
}
});
};
const { routes } = state;
const shouldUseHorizontalLabels = () => {
if (labelPosition) {
return labelPosition === 'beside-icon';
}
if (!adaptive) {
return false;
}
if (layout.width >= 768) {
// Screen size matches a tablet
let maxTabItemWidth = DEFAULT_MAX_TAB_ITEM_WIDTH;
const flattenedStyle = StyleSheet.flatten(tabStyle);
if (flattenedStyle) {
if (typeof flattenedStyle.width === 'number') {
maxTabItemWidth = flattenedStyle.width;
} else if (typeof flattenedStyle.maxWidth === 'number') {
maxTabItemWidth = flattenedStyle.maxWidth;
}
}
return routes.length * maxTabItemWidth <= layout.width;
} else {
const isLandscape = dimensions.width > dimensions.height;
return isLandscape;
}
};
const defaultInsets = useSafeArea();
const insets = {
top: safeAreaInsets?.top ?? defaultInsets.top,
right: safeAreaInsets?.right ?? defaultInsets.right,
bottom: safeAreaInsets?.bottom ?? defaultInsets.bottom,
left: safeAreaInsets?.left ?? defaultInsets.left,
};
const paddingBottom = Math.max(
insets.bottom - Platform.select({ ios: 4, default: 0 }),
0
);
return (
<Animated.View
style={[
styles.tabBar,
{
backgroundColor: colors.card,
borderTopColor: colors.border,
},
{
transform: [
{
translateY: visible.interpolate({
inputRange: [0, 1],
outputRange: [layout.height + paddingBottom, 0],
}),
},
],
// Absolutely position the tab bar so that the content is below it
// This is needed to avoid gap at bottom when the tab bar is hidden
position: isTabBarHidden ? 'absolute' : (null as any),
},
{
height: DEFAULT_TABBAR_HEIGHT + paddingBottom,
paddingBottom,
paddingHorizontal: Math.max(insets.left, insets.right),
},
style,
]}
pointerEvents={isTabBarHidden ? 'none' : 'auto'}
>
<View style={styles.content} onLayout={handleLayout}>
{routes.map((route, index) => {
const focused = index === state.index;
const { options } = descriptors[route.key];
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!focused && !event.defaultPrevented) {
navigation.dispatch({
...CommonActions.navigate(route.name),
target: state.key,
});
}
};
const onLongPress = () => {
navigation.emit({
type: 'tabLongPress',
target: route.key,
});
};
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const accessibilityLabel =
options.tabBarAccessibilityLabel !== undefined
? options.tabBarAccessibilityLabel
: typeof label === 'string'
? `${label}, tab, ${index + 1} of ${routes.length}`
: undefined;
return (
<NavigationContext.Provider
key={route.key}
value={descriptors[route.key].navigation}
>
<NavigationRouteContext.Provider value={route}>
<BottomTabItem
route={route}
focused={focused}
horizontal={shouldUseHorizontalLabels()}
onPress={onPress}
onLongPress={onLongPress}
accessibilityLabel={accessibilityLabel}
to={buildLink(route.name, route.params)}
testID={options.tabBarTestID}
allowFontScaling={allowFontScaling}
activeTintColor={activeTintColor}
inactiveTintColor={inactiveTintColor}
activeBackgroundColor={activeBackgroundColor}
inactiveBackgroundColor={inactiveBackgroundColor}
button={options.tabBarButton}
icon={options.tabBarIcon}
badge={options.tabBarBadge}
label={label}
showLabel={showLabel}
labelStyle={labelStyle}
iconStyle={iconStyle}
style={tabStyle}
/>
</NavigationRouteContext.Provider>
</NavigationContext.Provider>
);
})}
</View>
</Animated.View>
);
}
Example #22
Source File: Slider.tsx From react-native-range-slider-expo with MIT License | 4 votes |
Slider = gestureHandlerRootHOC(({
min, max, valueOnChange,
step = 1,
styleSize = 'medium',
knobColor = '#00a2ff',
inRangeBarColor = 'rgb(200,200,200)',
outOfRangeBarColor = 'rgb(100,100,100)',
knobBubbleTextStyle = {},
valueLabelsBackgroundColor = '#3a4766',
rangeLabelsTextColor = 'rgb(60,60,60)',
showRangeLabels = true,
showValueLabels = true,
initialValue,
containerStyle: customContainerStyle = {},
labelFormatter,
}: SliderProps) => {
// settings
const [stepInPixels, setStepInPixels] = useState(0);
const [knobSize, setknobSize] = useState(0);
const [fontSize] = useState(15);
// rtl settings
const [flexDirection, setFlexDirection] = useState<"row" | "row-reverse" | "column" | "column-reverse" | undefined>('row');
const [svgOffset, setSvgOffset] = useState<object>({ left: (knobSize - 40) / 2 });
const [valueOffset, setValueOffset] = useState(0);
const [sliderWidth, setSliderWidth] = useState(0);
// animation values
const [translateX] = useState(new Animated.Value(0));
const [valueLabelScale] = useState(new Animated.Value(0.01));
const [inRangeScaleX] = useState(new Animated.Value(0.01));
// refs
const valueTextRef = React.createRef<TextInput>();
const opacity = React.useRef<Animated.Value>(new Animated.Value(0)).current;
const {decimalRound, formatLabel} = useUtils({step, labelFormatter});
// initalizing settings
useEffect(() => {
setFlexDirection(osRtl ? 'row-reverse' : 'row');
setSvgOffset(osRtl ? { right: (knobSize - 40) / 2 } : { left: (knobSize - 40) / 2 });
}, [knobSize]);
useEffect(() => {
if (sliderWidth > 0) {
const stepSize = setStepSize(max, min, step);
valueTextRef.current?.setNativeProps({ text: formatLabel(min) });
if (typeof initialValue === 'number' && initialValue >= min && initialValue <= max) {
const offset = ((initialValue - min) / step) * stepSize - (knobSize / 2);
setValueStatic(offset, knobSize, stepSize);
setValueText(offset);
}
Animated.timing(opacity, {
toValue: 1,
duration: 64,
useNativeDriver: true
}).start();
}
}, [min, max, step, initialValue, sliderWidth]);
useEffect(() => {
const size = typeof styleSize === 'number' ? styleSize : styleSize === 'small' ? SMALL_SIZE : styleSize === 'medium' ? MEDIUM_SIZE : LARGE_SIZE;
setknobSize(size);
translateX.setValue(-size / 4);
}, [styleSize]);
const setValueStatic = (newOffset: number, knobSize: number, stepInPixels: number) => {
newOffset = Math.round((newOffset + (knobSize / 2)) / stepInPixels) * stepInPixels - (knobSize / 2);
settingValue(newOffset);
setValueOffset(newOffset);
const changeTo = Math.round(((newOffset + (knobSize / 2)) * (max - min) / sliderWidth) / step) * step + min;
valueOnChange(decimalRound(changeTo));
}
const settingValue = (newOffset: number) => {
translateX.setValue(newOffset);
inRangeScaleX.setValue((newOffset + (knobSize / 2)) / sliderWidth + 0.01);
}
const setValueText = (totalOffset: number) => {
const numericValue: number = Math.floor(((totalOffset + (knobSize / 2)) * (max - min) / sliderWidth) / step) * step + min;
const text = formatLabel(numericValue);
valueTextRef.current?.setNativeProps({ text });
}
const setStepSize = (max: number, min: number, step: number) => {
const numberOfSteps = ((max - min) / step);
const stepSize = sliderWidth / numberOfSteps;
setStepInPixels(stepSize);
return stepSize;
}
// value gesture events ------------------------------------------------------------------------
const onGestureEvent = (event: PanGestureHandlerGestureEvent) => {
let totalOffset = event.nativeEvent.translationX + valueOffset;
if (totalOffset >= - knobSize / 2 && totalOffset <= sliderWidth - knobSize / 2) {
translateX.setValue(totalOffset);
if (valueTextRef != null) {
const labelValue = Math.round(((totalOffset + (knobSize / 2)) * (max - min) / sliderWidth) / step) * step + min;
valueTextRef.current?.setNativeProps({ text: formatLabel(labelValue) });
}
inRangeScaleX.setValue((totalOffset + (knobSize / 2)) / sliderWidth + 0.01);
}
}
const onHandlerStateChange = (event: PanGestureHandlerGestureEvent) => {
if (event.nativeEvent.state === State.BEGAN) {
scaleTo(valueLabelScale, 1);
}
if (event.nativeEvent.state === State.END) {
let newOffset = event.nativeEvent.translationX + valueOffset;
newOffset = Math.round((newOffset + (knobSize / 2)) / stepInPixels) * stepInPixels - (knobSize / 2);
if (newOffset < -knobSize / 2) {
newOffset = -knobSize / 2;
} else if (newOffset >= sliderWidth - knobSize / 2) {
newOffset = sliderWidth - knobSize / 2;
}
setValueStatic(newOffset, knobSize, stepInPixels);
scaleTo(valueLabelScale, 0.01);
}
}
// ------------------------------------------------------------------------------------------------
// gesture events help functions ------------------------------------------------------------------
const scaleTo = (param: Animated.Value, toValue: number) => Animated.timing(param,
{
toValue,
duration: 150,
useNativeDriver: true
}
).start();
// ------------------------------------------------------------------------------------------------
// setting bar width ------------------------------------------------------------------------------
const onLayout = (event: LayoutChangeEvent) => {
setSliderWidth(event.nativeEvent.layout.width);
}
// ------------------------------------------------------------------------------------------------
const padding = useMemo(() => styleSize === 'large' ? 17 : styleSize === 'medium' ? 24 : 31, [styleSize]);
return (
<GestureHandlerRootView>
<Animated.View style={[styles.container, { opacity, padding }, customContainerStyle]}>
{
showValueLabels &&
<View style={{ width: '100%', height: 1, flexDirection }}>
<Animated.View
style={{ position: 'absolute', bottom: 0, left: 0, transform: [{ translateX }, { scale: valueLabelScale }] }}
>
<Svg width={40} height={56} style={{ ...svgOffset, justifyContent: 'center', alignItems: 'center' }} >
<Path
d="M20.368027196163986,55.24077513402203 C20.368027196163986,55.00364778429386 37.12897994729114,42.11537830086061 39.19501224411266,22.754628132990383 C41.26104454093417,3.393877965120147 24.647119286738516,0.571820003300814 20.368027196163986,0.7019902620266703 C16.088935105589453,0.8321519518460209 -0.40167016290734386,3.5393865664909434 0.7742997013327574,21.806127302984205 C1.950269565572857,40.07286803947746 20.368027196163986,55.4779024837502 20.368027196163986,55.24077513402203 z"
strokeWidth={1}
fill={valueLabelsBackgroundColor}
stroke={valueLabelsBackgroundColor}
/>
</Svg>
<TextInput style={[styles.knobBubbleText, svgOffset, knobBubbleTextStyle]} ref={valueTextRef} />
</Animated.View>
</View>
}
<View style={{ width: '100%', height: knobSize, marginVertical: 4, position: 'relative', flexDirection, alignItems: 'center' }}>
<View style={[styles.bar, { backgroundColor: inRangeBarColor, left: knobSize / 4, marginLeft: -knobSize / 4, right: knobSize / 4, height: knobSize / 3 }]} onLayout={onLayout} />
<Animated.View style={{ width: sliderWidth, height: knobSize / 3, backgroundColor: outOfRangeBarColor, transform: [{ translateX: -sliderWidth / 2 }, { scaleX: inRangeScaleX }, { translateX: sliderWidth / 2 }] }} />
<Animated.View style={{ position: 'absolute', left: -knobSize / 4, width: knobSize / 2.5, height: knobSize / 3, borderRadius: knobSize / 3, backgroundColor: outOfRangeBarColor }} />
<PanGestureHandler {...{ onGestureEvent, onHandlerStateChange }}>
<Animated.View style={[styles.knob, { height: knobSize, width: knobSize, borderRadius: knobSize, backgroundColor: knobColor, transform: [{ translateX }] }]} />
</PanGestureHandler>
</View>
{
showRangeLabels &&
<View style={{ width: '100%', flexDirection, justifyContent: 'space-between' }}>
<Text style={{ color: rangeLabelsTextColor, fontWeight: "bold", fontSize, marginLeft: -7 }}>{min}</Text>
<Text style={{ color: rangeLabelsTextColor, fontWeight: "bold", fontSize, marginRight: 7 }}>{max}</Text>
</View>
}
</Animated.View>
</GestureHandlerRootView>
);
})
Example #23
Source File: InputSelect.tsx From react-native-paper-form-builder with MIT License | 4 votes |
function InputSelect(props: InputSelectProps) {
const {
formState,
field,
textInputProps,
options,
CustomTextInput,
onDismiss = () => {},
} = props;
const theme = useTheme();
const errorMessage = formState.errors?.[field.name]?.message;
const textColor = errorMessage ? theme.colors.error : theme.colors.text;
const [visible, setVisible] = useState(false);
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const INPUT = CustomTextInput ?? TextInput;
const styles = useMemo(
() =>
StyleSheet.create({
textInputStyle: {
color: textColor,
},
menuStyle: {
minWidth: width,
width: width,
marginTop: height,
},
}),
[height, textColor, theme.colors.onSurface, theme.colors.surface, width],
);
const onLayout = useCallback((event: LayoutChangeEvent) => {
const {width: _width, height: _height} = event.nativeEvent.layout;
setWidth(_width);
setHeight(_height);
}, []);
return (
<Fragment>
<Menu
visible={visible}
onDismiss={() => setVisible(false)}
style={styles.menuStyle}
anchor={
<TouchableRipple
onPress={() => {
Keyboard.dismiss();
if (!textInputProps.disabled) {
setVisible(true);
}
}}>
<View pointerEvents={'none'} onLayout={onLayout}>
<INPUT
ref={field.ref}
mode={'outlined'}
error={errorMessage ? true : false}
{...textInputProps}
value={
options.find(({value}) => `${value}` === `${field.value}`)
?.label
}
onFocus={() => {
Keyboard.dismiss();
if (!textInputProps.disabled) {
setVisible(true);
}
}}
style={[styles.textInputStyle, textInputProps?.style]}
/>
</View>
</TouchableRipple>
}>
{options.map(({label: _label, value: _value}, _index) => {
return (
<Fragment key={_value}>
<Menu.Item
title={_label}
style={{width, minWidth: width, maxWidth: width}}
onPress={() => {
field.onChange(`${_value}`);
setVisible(false);
!!onDismiss && onDismiss();
}}
titleStyle={{
color:
`${_value}` === `${field.value}`
? theme.colors.primary
: theme.colors.text,
}}
/>
{_index < options.length - 1 && <Divider />}
</Fragment>
);
})}
</Menu>
{errorMessage && <HelperText type={'error'}>{errorMessage}</HelperText>}
</Fragment>
);
}
Example #24
Source File: TextField.tsx From react-native-jigsaw with MIT License | 4 votes |
render() {
const {
Icon,
type = "underline",
disabled = false,
label,
error = false,
leftIconName,
leftIconMode,
rightIconName,
assistiveText,
underlineColor: underlineColorProp,
multiline = false,
numberOfLines = 4,
style,
theme: { colors, typography, roundness, disabledOpacity },
render = (props) => <NativeTextInput {...props} />,
...rest
} = this.props;
const MINIMIZED_LABEL_Y_OFFSET = -(typography.caption.lineHeight + 4);
const OUTLINE_MINIMIZED_LABEL_Y_OFFSET = -(16 * 0.5 + 4);
const MAXIMIZED_LABEL_FONT_SIZE = typography.subtitle1.fontSize;
const MINIMIZED_LABEL_FONT_SIZE = typography.caption.fontSize;
const hasActiveOutline = this.state.focused || error;
let inputTextColor,
activeColor,
underlineColor,
borderColor,
placeholderColor,
containerStyle: StyleProp<ViewStyle>,
backgroundColor,
inputStyle: StyleProp<TextStyle>;
inputTextColor = colors.strong;
if (disabled) {
activeColor = colors.light;
placeholderColor = colors.light;
borderColor = "transparent";
underlineColor = "transparent";
backgroundColor = colors.divider;
} else {
activeColor = error ? colors.error : colors.primary;
placeholderColor = borderColor = colors.light;
underlineColor = underlineColorProp;
backgroundColor = colors.background;
}
if (rest.placeholderTextColor) {
placeholderColor = rest.placeholderTextColor;
}
const { lineHeight, ...subtitle1 } = typography.subtitle1;
inputStyle = {
paddingVertical: 0,
color: inputTextColor,
paddingLeft:
leftIconName && leftIconMode === "inset"
? ICON_SIZE + 12 + (type === "solid" ? 16 : 0)
: 0,
paddingRight: rightIconName ? ICON_SIZE + 16 + 4 : 12,
...subtitle1,
};
if (!multiline) {
inputStyle.height = lineHeight;
}
let assistiveTextLeftMargin;
if (type === "underline") {
containerStyle = {
borderTopLeftRadius: roundness,
borderTopRightRadius: roundness,
paddingBottom: 12,
marginTop: 16,
};
if (leftIconName && leftIconMode === "outset") {
assistiveTextLeftMargin = ICON_SIZE + 8;
} else {
assistiveTextLeftMargin = 0;
}
} else {
containerStyle = {
borderRadius: roundness,
borderColor: hasActiveOutline ? activeColor : borderColor,
borderWidth: 1,
paddingTop: label ? 16 * 1.5 : 16,
paddingBottom: label ? 16 * 0.5 : 16,
opacity: disabled ? disabledOpacity : 1,
backgroundColor,
};
if (leftIconName && leftIconMode === "inset") {
assistiveTextLeftMargin = 16 + 4;
} else if (leftIconName && leftIconMode === "outset") {
assistiveTextLeftMargin = ICON_SIZE + 8 + 12;
} else {
assistiveTextLeftMargin = 12;
}
inputStyle.paddingHorizontal = 12;
}
if (leftIconName && leftIconMode === "outset") {
containerStyle.marginLeft = ICON_SIZE + 8;
}
let leftIconColor;
if (error) {
leftIconColor = colors.error;
} else if (this.state.focused) {
leftIconColor = colors.primary;
} else {
leftIconColor = colors.light;
}
const leftIconProps = {
size: 24,
color: leftIconColor,
name: leftIconName || "",
};
const leftIconStyle: ImageStyle = {
position: "absolute",
marginTop:
type === "solid"
? MINIMIZED_LABEL_FONT_SIZE + 4
: leftIconMode === "outset"
? 16
: 0,
marginLeft: leftIconMode === "inset" && type === "solid" ? 16 : 0,
};
const labelStyle = {
...typography.subtitle1,
top: type === "solid" ? 16 : 0,
left:
leftIconName && leftIconMode === "inset"
? ICON_SIZE + (type === "solid" ? 16 : 12)
: 0,
transform: [
{
// Move label to top
translateY: this.state.labeled.interpolate({
inputRange: [0, 1],
outputRange: [
type === "solid"
? OUTLINE_MINIMIZED_LABEL_Y_OFFSET
: MINIMIZED_LABEL_Y_OFFSET,
0,
],
}),
},
{
// Make label smaller
scale: this.state.labeled.interpolate({
inputRange: [0, 1],
outputRange: [
MINIMIZED_LABEL_FONT_SIZE / MAXIMIZED_LABEL_FONT_SIZE,
1,
],
}),
},
{
// Offset label scale since RN doesn't support transform origin
translateX: this.state.labeled.interpolate({
inputRange: [0, 1],
outputRange: [
-(1 - MINIMIZED_LABEL_FONT_SIZE / MAXIMIZED_LABEL_FONT_SIZE) *
(this.state.labelLayout.width / 2),
0,
],
}),
},
],
};
const { textStyles } = extractStyles(style);
const inputStyles = applyStyles(
[
styles.input,
inputStyle,
type === "solid" ? { marginHorizontal: 12 } : {},
],
textStyles
);
const {
backgroundColor: bgColor,
padding,
paddingTop,
paddingBottom,
paddingLeft,
paddingRight,
borderRadius,
borderWidth,
borderTopWidth,
borderRightWidth,
borderBottomWidth,
borderLeftWidth,
borderColor: borderCol,
...styleProp
} = StyleSheet.flatten(style || {}) as ViewStyle & { height?: number };
return (
<View style={[styles.container, styleProp]}>
{leftIconName && leftIconMode === "outset" ? (
<Icon {...leftIconProps} style={leftIconStyle} />
) : null}
<View
style={applyStyles([containerStyle], {
height: style?.height,
backgroundColor: bgColor,
padding,
paddingTop,
paddingBottom,
paddingLeft,
paddingRight,
borderRadius,
borderWidth,
borderTopWidth,
borderRightWidth,
borderBottomWidth,
borderLeftWidth,
borderColor: borderCol,
})}
>
{type === "underline" ? (
// When type === 'flat', render an underline
<Animated.View
style={[
styles.underline,
{
backgroundColor:
bgColor ||
(error
? colors.error
: this.state.focused
? activeColor
: underlineColor),
// Underlines is thinner when input is not focused
transform: [{ scaleY: this.state.focused ? 1 : 0.5 }],
},
]}
/>
) : null}
{label ? (
// Position colored placeholder and gray placeholder on top of each other and crossfade them
// This gives the effect of animating the color, but allows us to use native driver
<View
pointerEvents="none"
style={[
StyleSheet.absoluteFill,
{
opacity:
// Hide the label in minimized state until we measure its width
this.state.value || this.state.focused
? this.state.labelLayout.measured
? 1
: 0
: 1,
},
]}
>
<AnimatedText
onLayout={(e: LayoutChangeEvent) =>
this.setState({
labelLayout: {
width: e.nativeEvent.layout.width,
measured: true,
},
})
}
style={[
styles.placeholder,
type === "solid" ? { paddingHorizontal: 12 } : {},
labelStyle,
{
color: placeholderColor,
opacity: this.state.labeled.interpolate({
inputRange: [0, 1],
outputRange: [hasActiveOutline ? 1 : 0, 0],
}),
},
]}
numberOfLines={1}
>
{label}
</AnimatedText>
<AnimatedText
style={[
styles.placeholder,
type === "solid" ? { paddingHorizontal: 12 } : {},
labelStyle,
{
color: placeholderColor,
opacity: hasActiveOutline ? this.state.labeled : 1,
},
]}
numberOfLines={1}
>
{label}
</AnimatedText>
</View>
) : null}
{leftIconName && leftIconMode === "inset" ? (
<View
style={{
justifyContent: type === "solid" ? "center" : undefined,
}}
>
<Icon {...leftIconProps} style={leftIconStyle} />
</View>
) : null}
{render({
ref: (c: NativeTextInput) => {
this._root = c;
},
onChange: this._handleChangeText,
placeholder: label
? this.state.placeholder
: this.props.placeholder,
placeholderTextColor: placeholderColor,
editable: !disabled,
selectionColor: activeColor,
multiline,
numberOfLines,
onFocus: this._handleFocus,
onBlur: this._handleBlur,
underlineColorAndroid: "transparent",
style: inputStyles,
...rest,
value: this.state.value,
})}
</View>
{rightIconName ? (
<Icon
name={rightIconName}
size={ICON_SIZE}
color={colors.light}
style={{
position: "absolute",
right: 16,
marginTop: type === "solid" ? MINIMIZED_LABEL_FONT_SIZE + 4 : 16,
}}
/>
) : null}
{assistiveText ? (
<Text
style={[
{
color: error ? colors.error : colors.light,
marginTop: 8,
marginLeft: assistiveTextLeftMargin,
},
]}
>
{assistiveText}
</Text>
) : null}
</View>
);
}
Example #25
Source File: DatePicker.tsx From react-native-jigsaw with MIT License | 4 votes |
DatePicker: React.FC<Props> = ({
Icon,
style,
theme: { colors, typography, roundness, disabledOpacity },
date,
onDateChange = () => {},
defaultValue,
disabled = false,
mode = "date",
format,
type = "underline",
leftIconName,
rightIconName,
leftIconMode = "inset",
label,
placeholder,
...props
}) => {
const [value, setValue] = React.useState<any>(date || defaultValue);
React.useEffect(() => {
if (defaultValue != null) {
setValue(defaultValue);
}
}, [defaultValue]);
const [pickerVisible, setPickerVisible] = React.useState(false);
const [labeled] = React.useState<Animated.Value>(
new Animated.Value(date ? 0 : 1)
);
const [placeholder1, setPlaceholder1] = React.useState("");
const [focused, setFocused] = React.useState<boolean>(false);
const [labelLayout, setLabelLayout] = React.useState<{
measured: Boolean;
width: number;
}>({ measured: false, width: 0 });
const getValidDate = (): Date => {
if (!value) {
return new Date();
}
return typeof value?.getMonth === "function" ? value : new Date();
};
const formatDate = (): string => {
if (!value) return "";
let newDate = getValidDate();
if (format) return dateFormat(newDate, format);
if (mode === "time") {
return `${newDate.toLocaleTimeString()}`;
}
if (mode === "datetime") {
return `${newDate.toLocaleString()}`;
}
return `${
MONTHS[newDate.getMonth()]
} ${newDate.getDate()}, ${newDate.getFullYear()}`;
};
const toggleVisibility = async () => {
setPickerVisible(!pickerVisible);
focused ? _handleBlur() : _handleFocus();
};
const insets = useSafeAreaInsets();
// const _restoreLabel = () =>
// Animated.timing(labeled, {
// toValue: 1,
// duration: FOCUS_ANIMATION_DURATION,
// useNativeDriver: true,
// }).start();
// const _minmizeLabel = () =>
// Animated.timing(labeled, {
// toValue: 0,
// duration: BLUR_ANIMATION_DURATION,
// useNativeDriver: true,
// }).start();
// const _showPlaceholder = () =>
// setTimeout(() => setPlaceholder1(placeholder || ""), 50);
const _hidePlaceholder = () => {
setPlaceholder1("");
};
React.useEffect(() => {
setValue(date);
}, [date]);
React.useEffect(() => {
if (value || focused || placeholder1) {
// _minmizeLabel();
Animated.timing(labeled, {
toValue: 0,
duration: BLUR_ANIMATION_DURATION,
useNativeDriver: true,
}).start();
} else {
// _restoreLabel();
Animated.timing(labeled, {
toValue: 1,
duration: FOCUS_ANIMATION_DURATION,
useNativeDriver: true,
}).start();
}
}, [value, focused, placeholder1, labeled]);
React.useEffect(() => {
const _showPlaceholder = () =>
setTimeout(() => setPlaceholder1(placeholder || ""), 50);
if (focused || !label) {
_showPlaceholder();
} else {
_hidePlaceholder();
}
return () => {
clearTimeout(_showPlaceholder());
};
}, [focused, label, placeholder]);
const _handleFocus = () => {
if (disabled) {
return;
}
setFocused(true);
};
const _handleBlur = () => {
if (disabled) {
return;
}
setFocused(false);
};
const MINIMIZED_LABEL_Y_OFFSET = -(typography.caption.lineHeight + 4);
const OUTLINE_MINIMIZED_LABEL_Y_OFFSET = -(16 * 0.5 + 4);
const MAXIMIZED_LABEL_FONT_SIZE = typography.subtitle1.fontSize;
const MINIMIZED_LABEL_FONT_SIZE = typography.caption.fontSize;
const hasActiveOutline = focused;
let inputTextColor,
activeColor,
underlineColor,
borderColor,
placeholderColor,
containerStyle: StyleProp<ViewStyle>,
backgroundColor,
inputStyle: StyleProp<TextStyle>;
inputTextColor = colors.strong;
if (disabled) {
activeColor = colors.light;
placeholderColor = colors.light;
borderColor = "transparent";
underlineColor = "transparent";
backgroundColor = colors.divider;
} else {
activeColor = colors.primary;
placeholderColor = borderColor = colors.light;
underlineColor = colors.light;
backgroundColor = colors.background;
}
const { lineHeight, ...subtitle1 } = typography.subtitle1;
inputStyle = {
paddingVertical: 0,
color: inputTextColor,
paddingLeft:
leftIconName && leftIconMode === "inset"
? ICON_SIZE + (type === "solid" ? 16 : 12)
: 0,
paddingRight: rightIconName ? ICON_SIZE + 16 + 4 : 12,
...subtitle1,
height: lineHeight,
};
if (type === "underline") {
containerStyle = {
borderTopLeftRadius: roundness,
borderTopRightRadius: roundness,
paddingBottom: 12,
marginTop: 16,
};
} else {
containerStyle = {
borderRadius: roundness,
borderColor: hasActiveOutline ? activeColor : borderColor,
borderWidth: 1,
paddingTop: labeled ? 16 * 1.5 : 16,
paddingBottom: labeled ? 16 * 0.5 : 16,
opacity: disabled ? disabledOpacity : 1,
backgroundColor,
};
inputStyle.paddingHorizontal = 12;
}
if (leftIconName && leftIconMode === "outset") {
containerStyle.marginLeft = ICON_SIZE + 8;
}
let leftIconColor;
if (focused) {
leftIconColor = colors.primary;
} else {
leftIconColor = colors.light;
}
const leftIconProps = {
size: 24,
color: leftIconColor,
name: leftIconName || "",
};
const leftIconStyle: ImageStyle = {
position: "absolute",
marginTop:
type === "solid"
? leftIconMode === "inset"
? MINIMIZED_LABEL_FONT_SIZE + 4
: 16
: leftIconMode === "outset"
? 16
: 0,
};
const labelStyle = {
...typography.subtitle1,
top: type === "solid" ? 16 : 0,
left:
leftIconName && leftIconMode === "inset"
? ICON_SIZE + (type === "solid" ? 16 : 12)
: 0,
transform: [
{
// Move label to top
translateY: labeled.interpolate({
inputRange: [0, 1],
outputRange: [
type === "solid"
? OUTLINE_MINIMIZED_LABEL_Y_OFFSET
: MINIMIZED_LABEL_Y_OFFSET,
0,
],
}),
},
{
// Make label smaller
scale: labeled.interpolate({
inputRange: [0, 1],
outputRange: [
MINIMIZED_LABEL_FONT_SIZE / MAXIMIZED_LABEL_FONT_SIZE,
1,
],
}),
},
{
// Offset label scale since RN doesn't support transform origin
translateX: labeled.interpolate({
inputRange: [0, 1],
outputRange: [
-(1 - MINIMIZED_LABEL_FONT_SIZE / MAXIMIZED_LABEL_FONT_SIZE) *
(labelLayout.width / 2),
0,
],
}),
},
],
};
const inputStyles = [
styles.input,
inputStyle,
type === "solid" ? { marginHorizontal: 12 } : {},
];
// const render = (props) => <NativeTextInput {...props} />;
return (
<View style={[styles.container, style]}>
<Touchable disabled={disabled} onPress={toggleVisibility}>
<View pointerEvents="none">
<View style={[styles.container, style]}>
{leftIconName && leftIconMode === "outset" ? (
<Icon {...leftIconProps} style={leftIconStyle} />
) : null}
<View
style={[containerStyle, style ? { height: style.height } : {}]}
>
{type === "underline" ? (
// When type === 'flat', render an underline
<Animated.View
style={[
styles.underline,
{
backgroundColor: focused ? activeColor : underlineColor,
// Underlines is thinner when input is not focused
transform: [{ scaleY: focused ? 1 : 0.5 }],
},
]}
/>
) : null}
{label ? (
// Position colored placeholder and gray placeholder on top of each other and crossfade them
// This gives the effect of animating the color, but allows us to use native driver
<View
pointerEvents="none"
style={[
StyleSheet.absoluteFill,
{
opacity:
// Hide the label in minimized state until we measure its width
date || focused ? (labelLayout.measured ? 1 : 0) : 1,
},
]}
>
<AnimatedText
onLayout={(e: LayoutChangeEvent) =>
setLabelLayout({
width: e.nativeEvent.layout.width,
measured: true,
})
}
style={[
styles.placeholder,
type === "solid" ? { paddingHorizontal: 12 } : {},
labelStyle,
{
color: colors.light,
opacity: labeled.interpolate({
inputRange: [0, 1],
outputRange: [hasActiveOutline ? 1 : 0, 0],
}),
},
]}
numberOfLines={1}
>
{label}
</AnimatedText>
<AnimatedText
style={[
styles.placeholder,
type === "solid" ? { paddingHorizontal: 12 } : {},
labelStyle,
{
color: placeholderColor,
opacity: hasActiveOutline ? labeled : 1,
},
]}
numberOfLines={1}
>
{label}
</AnimatedText>
</View>
) : null}
{leftIconName && leftIconMode === "inset" ? (
<Icon
{...leftIconProps}
style={{
...leftIconStyle,
marginLeft: type === "solid" ? 16 : 0,
}}
/>
) : null}
<NativeTextInput
value={formatDate()}
placeholder={label ? placeholder1 : placeholder}
editable={!disabled}
placeholderTextColor={placeholderColor}
selectionColor={activeColor}
onFocus={_handleFocus}
onBlur={_handleBlur}
underlineColorAndroid={"transparent"}
style={inputStyles}
{...props}
/>
</View>
{rightIconName ? (
<Icon
name={rightIconName}
size={ICON_SIZE}
color={colors.light}
style={{
position: "absolute",
right: 16,
marginTop:
type === "solid" ? MINIMIZED_LABEL_FONT_SIZE + 4 : 16,
}}
/>
) : null}
</View>
</View>
</Touchable>
{pickerVisible && (
<Portal>
<View
style={[
styles.picker,
{
backgroundColor: colors.divider,
},
]}
>
<View
style={[
styles.pickerContainer,
{
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: insets.left,
paddingRight: insets.right,
},
]}
>
<DateTimePicker
value={getValidDate()}
mode={mode}
isVisible={pickerVisible}
toggleVisibility={toggleVisibility}
onChange={(_event: any, data: any) => {
toggleVisibility();
setValue(data);
onDateChange(data);
}}
/>
</View>
</View>
</Portal>
)}
</View>
);
}
Example #26
Source File: TextSlider.tsx From react-native-range-slider-expo with MIT License | 4 votes |
TextualSlider = gestureHandlerRootHOC(({
values, valueOnChange,
styleSize = 'medium',
knobColor = '#00a2ff',
inRangeBarColor = 'rgb(200,200,200)',
outOfRangeBarColor = 'rgb(100,100,100)',
valueLabelsTextColor = 'white',
valueLabelsBackgroundColor = '#3a4766',
rangeLabelsStyle,
showRangeLabels = true,
showValueLabels = true,
initialValue
}: TextualSliderProps) => {
// settings
const [stepInPixels, setStepInPixels] = useState(0);
const [knobSize, setknobSize] = useState(0);
const [max, setMax] = useState(1);
// rtl settings
const [flexDirection, setFlexDirection] = useState<"row" | "row-reverse" | "column" | "column-reverse" | undefined>('row');
const [svgOffset, setSvgOffset] = useState<object>({ left: (knobSize - 40) / 2 });
const [valueOffset, setValueOffset] = useState(0);
const [TextualSliderWidth, setTextualSliderWidth] = useState(0);
// animation values
const [translateX] = useState(new Animated.Value(0));
const [valueLabelScale] = useState(new Animated.Value(0.01));
const [inRangeScaleX] = useState(new Animated.Value(0.01));
// refs
const valueTextRef = React.createRef<TextInput>();
const opacity = React.useRef<Animated.Value>(new Animated.Value(0)).current;
// initalizing settings
useEffect(() => {
setMax(values.length - 1);
setFlexDirection(osRtl ? 'row-reverse' : 'row');
setSvgOffset(osRtl ? { right: (knobSize - 40) / 2 } : { left: (knobSize - 40) / 2 });
}, []);
useEffect(() => {
if (TextualSliderWidth > 0) {
const stepSize = setStepSize(max, min, step);
valueTextRef.current?.setNativeProps({ text: values[min].text });
if (typeof initialValue === 'number' && initialValue >= min && initialValue <= max) {
const offset = ((initialValue - min) / step) * stepSize - (knobSize / 2);
setValueStatic(offset, knobSize, stepSize);
setValueText(offset);
}
Animated.timing(opacity, {
toValue: 1,
duration: 64,
useNativeDriver: true
}).start();
}
}, [min, max, step, initialValue, TextualSliderWidth]);
useEffect(() => {
const size = typeof styleSize === 'number' ? styleSize : styleSize === 'small' ? SMALL_SIZE : styleSize === 'medium' ? MEDIUM_SIZE : LARGE_SIZE;
setknobSize(size);
translateX.setValue(-size / 4);
}, [styleSize]);
const setValueStatic = (newOffset: number, knobSize: number, stepInPixels: number) => {
newOffset = Math.round((newOffset + (knobSize / 2)) / stepInPixels) * stepInPixels - (knobSize / 2);
settingValue(newOffset);
setValueOffset(newOffset);
const index = Math.round(((newOffset + (knobSize / 2)) * (max - min) / TextualSliderWidth) / step) * step + min;
valueOnChange(values[index]);
}
const settingValue = (newOffset: number) => {
translateX.setValue(newOffset);
inRangeScaleX.setValue((newOffset + (knobSize / 2)) / TextualSliderWidth + 0.01);
}
const setValueText = (totalOffset: number) => {
const numericValue: number = Math.floor(((totalOffset + (knobSize / 2)) * (max - min) / TextualSliderWidth) / step) * step + min;
valueTextRef.current?.setNativeProps({ text: values[numericValue].text });
}
const setStepSize = (max: number, min: number, step: number) => {
const numberOfSteps = ((max - min) / step);
const stepSize = TextualSliderWidth / numberOfSteps;
setStepInPixels(stepSize);
return stepSize;
}
// value gesture events ------------------------------------------------------------------------
const onGestureEvent = (event: PanGestureHandlerGestureEvent) => {
let totalOffset = event.nativeEvent.translationX + valueOffset;
if (totalOffset >= - knobSize / 2 && totalOffset <= TextualSliderWidth - knobSize / 2) {
translateX.setValue(totalOffset);
if (valueTextRef != null) {
const index = Math.round(((totalOffset + (knobSize / 2)) * (max - min) / TextualSliderWidth) / step) * step + min;
valueTextRef.current?.setNativeProps({ text: values[index].text });
}
inRangeScaleX.setValue((totalOffset + (knobSize / 2)) / TextualSliderWidth + 0.01);
}
}
const onHandlerStateChange = (event: PanGestureHandlerGestureEvent) => {
if (event.nativeEvent.state === State.BEGAN) {
scaleTo(valueLabelScale, 1);
}
if (event.nativeEvent.state === State.END) {
let newOffset = event.nativeEvent.translationX + valueOffset;
newOffset = Math.round((newOffset + (knobSize / 2)) / stepInPixels) * stepInPixels - (knobSize / 2);
if (newOffset < -knobSize / 2) {
newOffset = -knobSize / 2;
} else if (newOffset >= TextualSliderWidth - knobSize / 2) {
newOffset = TextualSliderWidth - knobSize / 2;
}
setValueStatic(newOffset, knobSize, stepInPixels);
scaleTo(valueLabelScale, 0.01);
}
}
// ------------------------------------------------------------------------------------------------
// gesture events help functions ------------------------------------------------------------------
const scaleTo = (param: Animated.Value, toValue: number) => Animated.timing(param,
{
toValue,
duration: 150,
useNativeDriver: true
}
).start();
// ------------------------------------------------------------------------------------------------
// setting bar width ------------------------------------------------------------------------------
const onLayout = (event: LayoutChangeEvent) => {
setTextualSliderWidth(event.nativeEvent.layout.width);
}
// ------------------------------------------------------------------------------------------------
const labelOpacity = valueLabelScale.interpolate({
inputRange: [0.1, 1],
outputRange: [0, 1]
})
return (
<GestureHandlerRootView>
<Animated.View style={[styles.container, { opacity, padding: styleSize === 'large' ? 7 : styleSize === 'medium' ? 14 : 21 }]}>
{
showValueLabels &&
<View style={{ width: '100%', flexDirection }}>
<Animated.View
style={{ position: 'absolute', bottom: 0, left: 0, opacity: labelOpacity, transform: [{ translateX }, { scale: valueLabelScale }] }}
>
<View style={{ width: '100%', alignItems: 'center' }}>
<TextInput style={{ ...svgOffset, color: valueLabelsTextColor, fontWeight: 'bold', backgroundColor: valueLabelsBackgroundColor, paddingHorizontal: 20, paddingVertical: 5, borderRadius: 3 }} ref={valueTextRef} />
</View>
</Animated.View>
</View>
}
<View style={{ width: '100%', height: knobSize, marginVertical: 4, position: 'relative', flexDirection, alignItems: 'center' }}>
<View style={[styles.bar, { backgroundColor: inRangeBarColor, left: knobSize / 4, marginLeft: -knobSize / 4, right: knobSize / 4, height: knobSize / 3 }]} onLayout={onLayout} />
<Animated.View style={{ width: TextualSliderWidth, height: knobSize / 3, backgroundColor: outOfRangeBarColor, transform: [{ translateX: -TextualSliderWidth / 2 }, { scaleX: inRangeScaleX }, { translateX: TextualSliderWidth / 2 }] }} />
<Animated.View style={{ position: 'absolute', left: -knobSize / 4, width: knobSize / 2.5, height: knobSize / 3, borderRadius: knobSize / 3, backgroundColor: outOfRangeBarColor }} />
<PanGestureHandler {...{ onGestureEvent, onHandlerStateChange }}>
<Animated.View style={[styles.knob, { height: knobSize, width: knobSize, borderRadius: knobSize, backgroundColor: knobColor, transform: [{ translateX }] }]} />
</PanGestureHandler>
</View>
{
showRangeLabels &&
<View style={{ width: '100%', flexDirection, justifyContent: 'space-between' }}>
<Text style={[rangeLabelsStyle, { fontWeight: "bold", marginLeft: -7 }]}>{values.length > 1 ? values[0].text : ''}</Text>
<Text style={[rangeLabelsStyle, { fontWeight: "bold" }]}>{values.length > 1 ? values[max].text : ''}</Text>
</View>
}
</Animated.View>
</GestureHandlerRootView>
);
})
Example #27
Source File: index.tsx From react-native-expandable-list-view with MIT License | 4 votes |
ExpandableListView: React.FC<Props> = ({data,innerItemLabelStyle,renderItemSeparator,renderInnerItemSeparator,onInnerItemClick,onItemClick,defaultLoaderStyles,itemSeparatorStyle,itemLabelStyle,itemImageIndicatorStyle,itemContainerStyle,innerItemSeparatorStyle,innerItemContainerStyle,customLoader,customChevron,animated=true,chevronColor, ExpandableListViewStyles}) => {
const [state, dispatch] = useReducer(reducer, initialState);
const CustomLoader = customLoader;
useEffect(() => {
if (state.selectedIndex >= 0) {
if (state.animatedValues[state.selectedIndex] !== undefined) {
if (state.selectedIndex !== state.lastSelectedIndex) {
if (
state.lastSelectedIndex >= 0 &&
state.lastSelectedIndex < state.data.length
) {
Animated.parallel([
Animated.timing(state.animatedValues[state.lastSelectedIndex], {
useNativeDriver: false,
duration: 300,
easing: Easing.linear,
toValue: 0,
}),
Animated.timing(
state.rotateValueHolder[state.lastSelectedIndex],
{
toValue: 0,
duration: 300,
easing: Easing.linear,
useNativeDriver: true,
},
),
]).start();
}
Animated.parallel([
Animated.timing(state.animatedValues[state.selectedIndex], {
useNativeDriver: false,
duration: 300,
easing: Easing.linear,
toValue: state.height[state.selectedIndex],
}),
Animated.timing(state.rotateValueHolder[state.selectedIndex], {
toValue: 1,
duration: 300,
easing: Easing.linear,
useNativeDriver: true,
}),
]).start();
} else {
Animated.parallel([
Animated.timing(state.animatedValues[state.selectedIndex], {
useNativeDriver: false,
duration: 300,
easing: Easing.linear,
toValue:
state.opened &&
state.height !== undefined &&
state.height[state.selectedIndex] !== undefined
? state.height[state.selectedIndex]
: 0,
}),
Animated.timing(state.rotateValueHolder[state.selectedIndex], {
toValue: state.opened ? 1 : 0,
duration: 300,
easing: Easing.linear,
useNativeDriver: true,
}),
]).start();
}
dispatch({type: 'set', lastSelectedIndex: state.selectedIndex});
}
} else {
if (
state.isMounted.length === state.data.length &&
state.data.length > 0
) {
Animated.timing(state.opacityValues, {
toValue: 1,
duration: 300,
easing: Easing.linear,
useNativeDriver: true,
}).start();
}
}
}, [
state.data,
state.height,
state.opened,
state.isMounted,
state.opacityValues,
state.animatedValues,
state.rotateValueHolder,
state.selectedIndex,
state.lastSelectedIndex,
]);
useEffect(() => {
async function reset() {
await dispatch({type: 'reset'});
await dispatch({type: 'set', data: data});
}
reset();
}, [data]);
function handleLayout(evt: LayoutChangeEvent, index: number) {
if (!state.isMounted[index] && evt.nativeEvent.layout.height !== 0) {
let h = state.height;
h[index] = evt.nativeEvent.layout.height;
let m = state.isMounted;
m[index] = true;
let newAnimatedValues: Array<Animated.Value> = [...state.animatedValues];
let newRotateValueHolder: Array<Animated.Value> = [
...state.rotateValueHolder,
];
newAnimatedValues.push(new Animated.Value(0));
newRotateValueHolder.push(new Animated.Value(0));
dispatch({
type: 'set',
animatedValues: newAnimatedValues,
rotateValueHolder: newRotateValueHolder,
height: h,
isMounted: m,
});
}
}
function updateLayout(updatedIndex: number) {
dispatch({
type: 'set',
opened: updatedIndex === state.selectedIndex ? !state.opened : true,
selectedIndex: updatedIndex,
});
if (onItemClick) {
return onItemClick({index: updatedIndex});
}
return;
}
const List = useMemo(() => Animated.FlatList, []);
function renderInnerItem(itemO: any, headerItem: Item, headerIndex: number) {
let {item}: {item: InnerItem} = itemO;
let {index}: {index: number} = itemO;
let CustomComponent = item.customInnerItem;
let container = {
...styles.content,
...innerItemContainerStyle,
height: undefined,
};
innerItemLabelStyle = {
...styles.text,
...innerItemLabelStyle,
height: undefined,
};
innerItemSeparatorStyle = {
...styles.innerItemSeparator,
...innerItemSeparatorStyle,
};
return (
<>
<TouchableOpacity
activeOpacity={0.6}
key={Math.random()}
style={container}
onPress={() =>
onInnerItemClick &&
onInnerItemClick({
innerItemIndex: index,
item: headerItem,
itemIndex: headerIndex,
})
}>
{CustomComponent !== undefined ? (
CustomComponent
) : (
<Text style={innerItemLabelStyle}>{item.name}</Text>
)}
</TouchableOpacity>
{renderInnerItemSeparator !== undefined &&
renderInnerItemSeparator &&
index < headerItem.subCategory.length - 1 && (
<View style={innerItemSeparatorStyle} />
)}
</>
);
}
function renderItem({item, index}: ExpandableListItem) {
itemContainerStyle = {
...styles.header,
...itemContainerStyle,
height: undefined,
};
itemLabelStyle = {
...styles.headerText,
...itemLabelStyle,
};
itemImageIndicatorStyle = {
height: 15,
width: 15,
marginHorizontal: 5,
...itemImageIndicatorStyle,
};
itemSeparatorStyle = {...styles.headerSeparator, ...itemSeparatorStyle};
let CustomComponent = item.customItem;
return (
<Animated.View
style={{
height: undefined,
}}>
<TouchableOpacity
activeOpacity={0.6}
onPress={() => updateLayout(index)}
style={itemContainerStyle}>
{CustomComponent !== undefined ? (
CustomComponent
) : (
<>
<Animated.Image
source={
customChevron !== undefined
? customChevron
: chevronColor !== undefined &&
chevronColor === 'white'
? white_chevron
: black_chevron
}
resizeMethod="scale"
resizeMode="contain"
style={[
itemImageIndicatorStyle,
animated === undefined ||
(animated !== undefined && animated)
? state.rotateValueHolder[index] !== undefined && {
transform: [
{
rotate: state.rotateValueHolder[index].interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '90deg'],
}),
},
],
}
: {
transform: [
{
rotate:
state.opened && index === state.selectedIndex
? '90deg'
: '0deg',
},
],
},
]}
/>
<Text style={itemLabelStyle}>{item.categoryName}</Text>
</>
)}
</TouchableOpacity>
<Animated.View
style={[
animated === undefined ||
(animated !== undefined && animated)
? // eslint-disable-next-line react-native/no-inline-styles
{
height: !state.isMounted[index]
? undefined
: state.animatedValues[index],
overflow: 'hidden',
}
: // eslint-disable-next-line react-native/no-inline-styles
{
display:
state.opened && index === state.selectedIndex
? 'flex'
: 'none',
overflow: 'hidden',
},
]}
onLayout={(evt: any) => handleLayout(evt, index)}>
<FlatList
style={{height: undefined}}
contentContainerStyle={{height: undefined}}
updateCellsBatchingPeriod={50}
initialNumToRender={50}
windowSize={50}
maxToRenderPerBatch={50}
keyExtractor={() => Math.random().toString()}
listKey={String(Math.random())}
data={item.subCategory}
renderItem={(innerItem: any) =>
renderInnerItem(innerItem, item, index)
}
/>
</Animated.View>
{renderItemSeparator !== undefined &&
renderItemSeparator &&
(!state.opened || state.selectedIndex !== index) &&
index < state.data.length - 1 && <View style={itemSeparatorStyle} />}
</Animated.View>
);
}
return (
<>
{animated && data.length >0 && state.isMounted[data.length -1] === undefined && (CustomLoader !== undefined ? CustomLoader : <ActivityIndicator style={defaultLoaderStyles} color="#94bfda" size="large" />)}
<Animated.View
style={[
// eslint-disable-next-line react-native/no-inline-styles
{
opacity:
animated === undefined ||
(animated !== undefined && animated)
? state.isMounted.length === state.data.length &&
data.length > 0
? state.opacityValues
: 0
: 1,
},
{...ExpandableListViewStyles},
{height: animated && data.length >0 && state.isMounted[data.length -1] === undefined ? 0 : ExpandableListViewStyles?.height !== undefined ? ExpandableListViewStyles?.height : 'auto'},
]}>
<List
updateCellsBatchingPeriod={50}
initialNumToRender={50}
windowSize={50}
maxToRenderPerBatch={50}
keyExtractor={(_: any, itemIndex: number) => itemIndex.toString()}
data={state.data}
renderItem={(item: ExpandableListItem) => renderItem(item)}
/>
</Animated.View>
</>
);
}
Example #28
Source File: RangeSlider.tsx From react-native-range-slider-expo with MIT License | 4 votes |
RangeSlider = memo(({
min, max, fromValueOnChange, toValueOnChange,
step = 1,
styleSize = 'medium',
fromKnobColor = '#00a2ff',
toKnobColor = '#00a2ff',
inRangeBarColor = 'rgb(100,100,100)',
outOfRangeBarColor = 'rgb(200,200,200)',
valueLabelsBackgroundColor = '#3a4766',
rangeLabelsTextColor = 'rgb(60,60,60)',
showRangeLabels = true,
showValueLabels = true,
initialFromValue,
initialToValue,
knobSize: _knobSize,
knobBubbleTextStyle = {},
containerStyle: customContainerStyle = {},
barHeight: customBarHeight,
labelFormatter,
}: SliderProps) => {
// settings
const [wasInitialized, setWasInitialized] = useState(false);
const [knobSize, setknobSize] = useState(0);
const [barHeight, setBarHeight] = useState(0);
const [stepInPixels, setStepInPixels] = useState(0);
// rtl settings
const [flexDirection, setFlexDirection] = useState<"row" | "row-reverse" | "column" | "column-reverse" | undefined>('row');
const [fromValueOffset, setFromValueOffset] = useState(0);
const [toValueOffset, setToValueOffset] = useState(0);
const [sliderWidth, setSliderWidth] = useState(0);
const [fromElevation, setFromElevation] = useState(3);
const [toElevation, setToElevation] = useState(3);
// animation values
const [translateXfromValue] = useState(new Animated.Value(0));
const [translateXtoValue] = useState(new Animated.Value(0));
const [fromValueScale] = useState(new Animated.Value(0.01));
const [toValueScale] = useState(new Animated.Value(0.01));
const [rightBarScaleX] = useState(new Animated.Value(0.01));
const [leftBarScaleX] = useState(new Animated.Value(0.01));
// refs
const toValueTextRef = React.createRef<TextInput>();
const fromValueTextRef = React.createRef<TextInput>();
const opacity = React.useRef<Animated.Value>(new Animated.Value(0)).current;
const {formatLabel, decimalRound} = useUtils({step, labelFormatter});
// initalizing settings
useEffect(() => {
setFlexDirection(osRtl ? 'row-reverse' : 'row');
}, [knobSize]);
useEffect(() => {
if (wasInitialized) {
const stepSize = setStepSize(max, min, step);
fromValueTextRef.current?.setNativeProps({ text: formatLabel(min) });
toValueTextRef.current?.setNativeProps({ text: formatLabel(min) });
if (typeof initialFromValue === 'number' && initialFromValue >= min && initialFromValue <= max) {
const offset = ((initialFromValue - min) / step) * stepSize - (knobSize / 2);
setFromValueStatic(offset, knobSize, stepSize);
setValueText(offset + knobSize, true);
}
if (typeof initialToValue === 'number' && initialToValue >= min && initialToValue <= max && typeof initialFromValue === 'number' && initialToValue > initialFromValue) {
const offset = ((initialToValue - min) / step) * stepSize - (knobSize / 2);
setToValueStatic(offset, knobSize, stepSize);
setValueText(offset, false);
}
Animated.timing(opacity, {
toValue: 1,
duration: 64,
useNativeDriver: true
}).start();
}
}, [min, max, step, initialFromValue, initialToValue, wasInitialized]);
useEffect(() => {
const sizeBasedOnStyleSize = typeof styleSize === 'number' ? styleSize : styleSize === 'small' ? SMALL_SIZE : styleSize === 'medium' ? MEDIUM_SIZE : LARGE_SIZE;
const size = _knobSize ?? sizeBasedOnStyleSize;
setknobSize(customBarHeight ? Math.max(customBarHeight, size) : size);
setBarHeight(customBarHeight ?? sizeBasedOnStyleSize / 3)
translateXfromValue.setValue(-size / 4);
}, [styleSize, customBarHeight]);
// initalizing settings helpers
const setFromValueStatic = (newOffset: number, knobSize: number, stepInPixels: number) => {
newOffset = Math.floor((newOffset + (knobSize / 2)) / stepInPixels) * stepInPixels - (knobSize / 2);
setFromValue(newOffset);
setFromValueOffset(newOffset);
const changeTo = Math.floor(((newOffset + (knobSize / 2)) * (max - min) / sliderWidth) / step) * step + min;
fromValueOnChange(decimalRound(changeTo));
}
const setFromValue = (newOffset: number) => {
translateXfromValue.setValue(newOffset);
leftBarScaleX.setValue((newOffset + (knobSize / 2)) / sliderWidth + 0.01);
}
const setToValueStatic = (newOffset: number, knobSize: number, stepInPixels: number) => {
newOffset = Math.ceil((newOffset + (knobSize / 2)) / stepInPixels) * stepInPixels - (knobSize / 2);
setToValue(newOffset);
setToValueOffset(newOffset);
const changeTo = Math.ceil(((newOffset + (knobSize / 2)) * (max - min) / sliderWidth) / step) * step + min;
toValueOnChange(decimalRound(changeTo));
}
const setToValue = (newOffset: number) => {
translateXtoValue.setValue(newOffset);
rightBarScaleX.setValue(1.01 - ((newOffset + (knobSize / 2)) / sliderWidth));
}
const setStepSize = (max: number, min: number, step: number) => {
const numberOfSteps = ((max - min) / step);
const stepSize = sliderWidth / numberOfSteps;
setStepInPixels(stepSize);
return stepSize;
}
const setValueText = (totalOffset: number, from = true) => {
const isFrom = from && fromValueTextRef != null;
const isTo = !from && toValueTextRef != null;
if (isFrom || isTo) {
const numericValue: number = Math[isFrom ? 'floor' : 'ceil'](((totalOffset + (knobSize / 2)) * (max - min) / sliderWidth) / step) * step + min;
const text = formatLabel(numericValue);
(isFrom ? fromValueTextRef : toValueTextRef).current?.setNativeProps({ text });
}
}
// from value gesture events ------------------------------------------------------------------------
const onGestureEventFromValue = (event: PanGestureHandlerGestureEvent) => {
let totalOffset = event.nativeEvent.translationX + fromValueOffset;
if (totalOffset >= -knobSize / 2 && totalOffset < toValueOffset) {
translateXfromValue.setValue(totalOffset);
setValueText(totalOffset, true);
leftBarScaleX.setValue((totalOffset + (knobSize / 2)) / sliderWidth + 0.01);
}
}
const onHandlerStateChangeFromValue = (event: PanGestureHandlerGestureEvent) => {
if (event.nativeEvent.state === State.BEGAN) {
scaleTo(fromValueScale, 1);
setElevations(6, 5);
}
if (event.nativeEvent.state === State.END) {
let newOffset = event.nativeEvent.translationX + fromValueOffset;
newOffset = Math.floor((newOffset + (knobSize / 2)) / stepInPixels) * stepInPixels - (knobSize / 2);
if (newOffset < -knobSize / 2) {
newOffset = -knobSize / 2;
} else if (newOffset >= toValueOffset) {
newOffset = toValueOffset - stepInPixels;
}
setFromValueStatic(newOffset, knobSize, stepInPixels)
scaleTo(fromValueScale, 0.01);
}
}
// ------------------------------------------------------------------------------------------------
// to value gesture events ------------------------------------------------------------------------
const onGestureEventToValue = (event: PanGestureHandlerGestureEvent) => {
const totalOffset = event.nativeEvent.translationX + toValueOffset;
if (totalOffset <= sliderWidth - knobSize / 2 && totalOffset > fromValueOffset) {
translateXtoValue.setValue(totalOffset);
setValueText(totalOffset, false);
rightBarScaleX.setValue(1.01 - ((totalOffset + (knobSize / 2)) / sliderWidth));
}
}
const onHandlerStateChangeToValue = (event: PanGestureHandlerGestureEvent) => {
if (event.nativeEvent.state === State.BEGAN) {
scaleTo(toValueScale, 1);
setElevations(5, 6);
}
if (event.nativeEvent.state === State.END) {
let newOffset = event.nativeEvent.translationX + toValueOffset;
newOffset = Math.ceil((newOffset + (knobSize / 2)) / stepInPixels) * stepInPixels - (knobSize / 2);
if (newOffset > sliderWidth - knobSize / 2) {
newOffset = sliderWidth - knobSize / 2;
} else if (newOffset <= fromValueOffset) {
newOffset = fromValueOffset + stepInPixels;
}
setToValueOffset(newOffset);
translateXtoValue.setValue(newOffset);
rightBarScaleX.setValue(1.01 - ((newOffset + (knobSize / 2)) / sliderWidth));
scaleTo(toValueScale, 0.01);
const changeTo = Math.ceil(((newOffset + (knobSize / 2)) * (max - min) / sliderWidth) / step) * step + min;
toValueOnChange(decimalRound(changeTo));
}
}
// ------------------------------------------------------------------------------------------------
// gesture events help functions ------------------------------------------------------------------
const scaleTo = (param: Animated.Value, toValue: number) => Animated.timing(param, {
toValue,
duration: 150,
useNativeDriver: true
}).start();
const setElevations = (fromValue: number, toValue: number) => {
setFromElevation(fromValue);
setToElevation(toValue)
}
// ------------------------------------------------------------------------------------------------
// setting bar width ------------------------------------------------------------------------------
const onLayout = (event: LayoutChangeEvent) => {
if (wasInitialized === false) {
const { width } = event.nativeEvent.layout;
setSliderWidth(width);
translateXtoValue.setValue(width - knobSize / 2);
setToValueOffset(width - knobSize / 2);
setWasInitialized(true);
}
}
// ------------------------------------------------------------------------------------------------
const padding = useMemo(() => styleSize === 'large' ? 17 : styleSize === 'medium' ? 24 : 31, [styleSize]);
return (
<GestureHandlerRootView>
<Animated.View style={[styles.container, { opacity, padding }, customContainerStyle]}>
{
showValueLabels &&
<View style={{ width: '100%', height: 1, flexDirection }}>
<KnobBubble {...{ knobSize, valueLabelsBackgroundColor }}
translateX={translateXfromValue}
scale={fromValueScale}
textInputRef={fromValueTextRef}
textStyle={knobBubbleTextStyle}
/>
<KnobBubble {...{ knobSize, valueLabelsBackgroundColor }}
translateX={translateXtoValue}
scale={toValueScale}
textInputRef={toValueTextRef}
textStyle={knobBubbleTextStyle}
/>
</View>
}
<View style={{ width: '100%', height: knobSize, marginVertical: 4, position: 'relative', flexDirection, alignItems: 'center' }}>
<View style={{ position: 'absolute', backgroundColor: inRangeBarColor, left: knobSize / 4, marginLeft: -knobSize / 4, right: knobSize / 4, height: barHeight }} onLayout={onLayout} />
<Animated.View style={{ position: 'absolute', left: knobSize / 4, marginLeft: -knobSize / 4, right: knobSize / 4, height: barHeight, backgroundColor: outOfRangeBarColor, transform: [{ translateX: sliderWidth / 2 }, { scaleX: rightBarScaleX }, { translateX: -sliderWidth / 2 }] }} />
<Animated.View style={{ position: 'absolute', left: -knobSize / 4, width: knobSize / 2, height: barHeight, borderRadius: barHeight, backgroundColor: outOfRangeBarColor }} />
<Animated.View style={{ width: sliderWidth, height: barHeight, backgroundColor: outOfRangeBarColor, transform: [{ translateX: -sliderWidth / 2 }, { scaleX: leftBarScaleX }, { translateX: sliderWidth / 2 }] }} />
<Animated.View style={{ position: 'absolute', left: sliderWidth - knobSize / 4, width: knobSize / 2, height: barHeight, borderRadius: barHeight, backgroundColor: outOfRangeBarColor }} />
<PanGestureHandler onGestureEvent={onGestureEventFromValue} onHandlerStateChange={onHandlerStateChangeFromValue}>
<Animated.View style={[styles.knob, { height: knobSize, width: knobSize, borderRadius: knobSize, backgroundColor: fromKnobColor, elevation: fromElevation, transform: [{ translateX: translateXfromValue }] }]} />
</PanGestureHandler>
<PanGestureHandler onGestureEvent={onGestureEventToValue} onHandlerStateChange={onHandlerStateChangeToValue}>
<Animated.View style={[styles.knob, { height: knobSize, width: knobSize, borderRadius: knobSize, backgroundColor: toKnobColor, elevation: toElevation, transform: [{ translateX: translateXtoValue }] }]} />
</PanGestureHandler>
</View>
{
showRangeLabels &&
<View style={{ width: '100%', flexDirection, justifyContent: 'space-between' }}>
<Text style={{ color: rangeLabelsTextColor, fontWeight: "bold", fontSize }}>{min}</Text>
<Text style={{ color: rangeLabelsTextColor, fontWeight: "bold", fontSize }}>{max}</Text>
</View>
}
</Animated.View>
</GestureHandlerRootView>
);
})
Example #29
Source File: ExpansionPanel.tsx From react-native-design-kit with MIT License | 4 votes |
export default function ExpansionPanel({
visible = false,
title,
titleStyle,
iconStartRotation = '-90deg',
iconEndRotation = '0deg',
animationDuration = 250,
icon,
subtitle,
subtitleStyle,
containerStyle,
contentContainerStyle,
children,
onPress,
}: ExpansionPanelProps) {
const width = useRef<number>();
const animation = useRef(new Animated.Value(visible ? 1 : 0)).current;
const handleLayout = useCallback((event: LayoutChangeEvent) => {
width.current = event.nativeEvent.layout.width;
}, []);
const handleRenderPanel = useMemo(
() => (
<Touchable
testID="panel"
touchableType="normal"
onLayout={handleLayout}
style={StyleSheet.flatten([
styles.container,
containerStyle,
styles.fixedContainer,
])}
onPress={onPress}>
<Animated.View
style={StyleSheet.flatten([
styles.iconContainer,
{
transform: [
{
rotateZ: animation.interpolate({
inputRange: [0, 1],
outputRange: [iconStartRotation, iconEndRotation],
}),
},
],
},
])}>
{icon || <Icon name="chevron-down" />}
</Animated.View>
<View style={styles.sectionTitle}>
<View style={styles.titleContainer}>
<Text style={titleStyle}>{title}</Text>
</View>
{subtitle ? (
<View style={styles.subtitleContainer}>
<Text
style={StyleSheet.flatten([styles.subtitle, subtitleStyle])}>
{subtitle}
</Text>
</View>
) : null}
</View>
</Touchable>
),
[
icon,
iconStartRotation,
iconEndRotation,
titleStyle,
title,
subtitle,
subtitleStyle,
containerStyle,
animation,
onPress,
handleLayout,
],
);
const handleRenderContent = useMemo(
() => (
<Collapse visible={visible} animationDuration={animationDuration}>
<View
style={StyleSheet.flatten([
styles.contentContainer,
contentContainerStyle,
{width: width.current},
])}>
{children}
</View>
</Collapse>
),
[
width.current,
visible,
animationDuration,
contentContainerStyle,
children,
],
);
const handleRunAnimation = useCallback(
() =>
Animated.timing(animation, {
toValue: visible ? 1 : 0,
duration: animationDuration,
useNativeDriver: true,
}).start(),
[animation, visible, animationDuration],
);
useDidUpdate(handleRunAnimation, [handleRunAnimation]);
return (
<>
{handleRenderPanel}
{handleRenderContent}
</>
);
}