react-native-paper
Version:
Material design for React Native
475 lines (412 loc) • 10.5 kB
text/typescript
import { MutableRefObject } from 'react';
import {
Animated,
ColorValue,
I18nManager,
Platform,
ViewStyle,
} from 'react-native';
import color from 'color';
import { black, white } from '../../styles/themes/v2/colors';
import type { InternalTheme } from '../../types';
import getContrastingColor from '../../utils/getContrastingColor';
type GetCombinedStylesProps = {
isAnimatedFromRight: boolean;
isIconStatic: boolean;
distance: number;
animFAB: Animated.Value;
};
type CombinedStyles = {
innerWrapper: Animated.WithAnimatedValue<ViewStyle>;
iconWrapper: Animated.WithAnimatedValue<ViewStyle>;
absoluteFill: Animated.WithAnimatedValue<ViewStyle>;
};
type Variant = 'primary' | 'secondary' | 'tertiary' | 'surface';
type BaseProps = {
isVariant: (variant: Variant) => boolean;
theme: InternalTheme;
disabled?: boolean;
};
export const getCombinedStyles = ({
isAnimatedFromRight,
isIconStatic,
distance,
animFAB,
}: GetCombinedStylesProps): CombinedStyles => {
const { isRTL } = I18nManager;
const defaultPositionStyles = { left: -distance, right: undefined };
const combinedStyles: CombinedStyles = {
innerWrapper: {
...defaultPositionStyles,
},
iconWrapper: {
...defaultPositionStyles,
},
absoluteFill: {},
};
const animatedFromRight = isAnimatedFromRight && !isRTL;
const animatedFromRightRTL = isAnimatedFromRight && isRTL;
const animatedFromLeft = !isAnimatedFromRight && !isRTL;
const animatedFromLeftRTL = !isAnimatedFromRight && isRTL;
if (animatedFromRight) {
combinedStyles.innerWrapper.transform = [
{
translateX: animFAB.interpolate({
inputRange: [distance, 0],
outputRange: [distance, 0],
}),
},
];
combinedStyles.iconWrapper.transform = [
{
translateX: isIconStatic ? 0 : animFAB,
},
];
combinedStyles.absoluteFill.transform = [
{
translateX: animFAB.interpolate({
inputRange: [distance, 0],
outputRange: [Math.abs(distance) / 2, Math.abs(distance)],
}),
},
];
} else if (animatedFromRightRTL) {
combinedStyles.iconWrapper.transform = [
{
translateX: isIconStatic
? 0
: animFAB.interpolate({
inputRange: [distance, 0],
outputRange: [-distance, 0],
}),
},
];
combinedStyles.innerWrapper.transform = [
{
translateX: animFAB.interpolate({
inputRange: [distance, 0],
outputRange: [-distance, 0],
}),
},
];
combinedStyles.absoluteFill.transform = [
{
translateX: animFAB.interpolate({
inputRange: [distance, 0],
outputRange: [0, distance],
}),
},
];
} else if (animatedFromLeft) {
combinedStyles.iconWrapper.transform = [
{
translateX: isIconStatic
? distance
: animFAB.interpolate({
inputRange: [0, distance],
outputRange: [distance, distance * 2],
}),
},
];
combinedStyles.innerWrapper.transform = [
{
translateX: animFAB,
},
];
combinedStyles.absoluteFill.transform = [
{
translateX: animFAB.interpolate({
inputRange: [0, distance],
outputRange: [0, Math.abs(distance) / 2],
}),
},
];
} else if (animatedFromLeftRTL) {
combinedStyles.iconWrapper.transform = [
{
translateX: isIconStatic
? animFAB.interpolate({
inputRange: [0, distance],
outputRange: [-distance, -distance * 2],
})
: -distance,
},
];
combinedStyles.innerWrapper.transform = [
{
translateX: animFAB.interpolate({
inputRange: [0, distance],
outputRange: [0, -distance],
}),
},
];
combinedStyles.absoluteFill.transform = [
{
translateX: animFAB.interpolate({
inputRange: [0, distance],
outputRange: [0, -distance],
}),
},
];
}
return combinedStyles;
};
const getBackgroundColor = ({
theme,
isVariant,
disabled,
customBackgroundColor,
}: BaseProps & { customBackgroundColor?: ColorValue }) => {
if (customBackgroundColor && !disabled) {
return customBackgroundColor;
}
if (theme.isV3) {
if (disabled) {
return theme.colors.surfaceDisabled;
}
if (isVariant('primary')) {
return theme.colors.primaryContainer;
}
if (isVariant('secondary')) {
return theme.colors.secondaryContainer;
}
if (isVariant('tertiary')) {
return theme.colors.tertiaryContainer;
}
if (isVariant('surface')) {
return theme.colors.elevation.level3;
}
}
if (disabled) {
if (theme.dark) {
return color(white).alpha(0.12).rgb().string();
}
return color(black).alpha(0.12).rgb().string();
}
//@ts-ignore
return theme.colors?.accent;
};
const getForegroundColor = ({
theme,
isVariant,
disabled,
backgroundColor,
customColor,
}: BaseProps & { backgroundColor: string; customColor?: string }) => {
if (typeof customColor !== 'undefined' && !disabled) {
return customColor;
}
if (theme.isV3) {
if (disabled) {
return theme.colors.onSurfaceDisabled;
}
if (isVariant('primary')) {
return theme.colors.onPrimaryContainer;
}
if (isVariant('secondary')) {
return theme.colors.onSecondaryContainer;
}
if (isVariant('tertiary')) {
return theme.colors.onTertiaryContainer;
}
if (isVariant('surface')) {
return theme.colors.primary;
}
}
if (disabled) {
if (theme.dark) {
return color(white).alpha(0.32).rgb().string();
}
return color(black).alpha(0.32).rgb().string();
}
if (backgroundColor) {
return getContrastingColor(
backgroundColor || white,
white,
'rgba(0, 0, 0, .54)'
);
}
return getContrastingColor(white, white, 'rgba(0, 0, 0, .54)');
};
export const getFABColors = ({
theme,
variant,
disabled,
customColor,
customBackgroundColor,
customRippleColor,
}: {
theme: InternalTheme;
variant: string;
disabled?: boolean;
customColor?: string;
customBackgroundColor?: ColorValue;
customRippleColor?: ColorValue;
}) => {
const isVariant = (variantToCompare: Variant) => {
return variant === variantToCompare;
};
const baseFABColorProps = { theme, isVariant, disabled };
const backgroundColor = getBackgroundColor({
...baseFABColorProps,
customBackgroundColor,
});
const foregroundColor = getForegroundColor({
...baseFABColorProps,
customColor,
backgroundColor,
});
return {
backgroundColor,
foregroundColor,
rippleColor:
customRippleColor || color(foregroundColor).alpha(0.12).rgb().string(),
};
};
const getLabelColor = ({ theme }: { theme: InternalTheme }) => {
if (theme.isV3) {
return theme.colors.onSurface;
}
if (theme.dark) {
return theme.colors.text;
}
return color(theme.colors.text).fade(0.54).rgb().string();
};
const getBackdropColor = ({
theme,
customBackdropColor,
}: {
theme: InternalTheme;
customBackdropColor?: string;
}) => {
if (customBackdropColor) {
return customBackdropColor;
}
if (theme.isV3) {
return color(theme.colors.background).alpha(0.95).rgb().string();
}
return theme.colors?.backdrop;
};
const getStackedFABBackgroundColor = ({ theme }: { theme: InternalTheme }) => {
if (theme.isV3) {
return theme.colors.elevation.level3;
}
return theme.colors.surface;
};
export const getFABGroupColors = ({
theme,
customBackdropColor,
}: {
theme: InternalTheme;
customBackdropColor?: string;
}) => {
return {
labelColor: getLabelColor({ theme }),
backdropColor: getBackdropColor({ theme, customBackdropColor }),
stackedFABBackgroundColor: getStackedFABBackgroundColor({ theme }),
};
};
const standardSize = {
height: 56,
width: 56,
borderRadius: 28,
};
const smallSize = {
height: 40,
width: 40,
borderRadius: 28,
};
const v3SmallSize = {
height: 40,
width: 40,
};
const v3MediumSize = {
height: 56,
width: 56,
};
const v3LargeSize = {
height: 96,
width: 96,
};
const getCustomFabSize = (customSize: number, roundness: number) => ({
height: customSize,
width: customSize,
borderRadius: roundness === 0 ? 0 : customSize / roundness,
});
export const getFabStyle = ({
size,
theme,
customSize,
}: {
customSize?: number;
size: 'small' | 'medium' | 'large';
theme: InternalTheme;
}) => {
const { isV3, roundness } = theme;
if (customSize) return getCustomFabSize(customSize, roundness);
if (isV3) {
switch (size) {
case 'small':
return { ...v3SmallSize, borderRadius: 3 * roundness };
case 'medium':
return { ...v3MediumSize, borderRadius: 4 * roundness };
case 'large':
return { ...v3LargeSize, borderRadius: 7 * roundness };
}
}
if (size === 'small') {
return smallSize;
}
return standardSize;
};
const extended = {
height: 48,
paddingHorizontal: 16,
};
const v3Extended = {
height: 56,
borderRadius: 16,
paddingHorizontal: 16,
};
const getExtendedFabDimensions = (customSize: number) => ({
height: customSize,
paddingHorizontal: 16,
});
export const getExtendedFabStyle = ({
customSize,
theme,
}: {
customSize?: number;
theme: InternalTheme;
}) => {
if (customSize) return getExtendedFabDimensions(customSize);
const { isV3 } = theme;
return isV3 ? v3Extended : extended;
};
let cachedContext: CanvasRenderingContext2D | null = null;
const getCanvasContext = () => {
if (cachedContext) {
return cachedContext;
}
const canvas = document.createElement('canvas');
cachedContext = canvas.getContext('2d');
return cachedContext;
};
export const getLabelSizeWeb = (ref: MutableRefObject<HTMLElement | null>) => {
if (Platform.OS !== 'web' || ref.current === null) {
return null;
}
const canvasContext = getCanvasContext();
if (!canvasContext) {
return null;
}
const elementStyles = window.getComputedStyle(ref.current);
canvasContext.font = elementStyles.font;
const metrics = canvasContext.measureText(ref.current.innerText);
return {
width: metrics.width,
height:
(metrics.fontBoundingBoxAscent ?? 0) +
(metrics.fontBoundingBoxDescent ?? 0),
};
};