native-base
Version:
Essential cross-platform UI components for React Native
337 lines (321 loc) • 8.81 kB
text/typescript
import omitBy from 'lodash.omitby';
import isNil from 'lodash.isnil';
import pick from 'lodash.pick';
import omit from 'lodash.omit';
import get from 'lodash.get';
import type { ITheme } from '../index';
import { Platform } from 'react-native';
export const stylingProps = {
margin: [
'margin',
'm',
'marginTop',
'mt',
'marginRight',
'mr',
'marginBottom',
'mb',
'marginLeft',
'ml',
'marginX',
'mx',
'marginY',
'my',
],
padding: [
'padding',
'p',
'paddingTop',
'pt',
'paddingRight',
'pr',
'paddingBottom',
'pb',
'paddingLeft',
'pl',
'paddingX',
'px',
'paddingY',
'py',
],
border: [
'border',
'borderWidth',
'borderStyle',
'borderColor',
'borderRadius',
'borderTop',
'borderTopWidth',
'borderTopStyle',
'borderTopColor',
'borderTopLeftRadius',
'borderTopRightRadius',
'borderRight',
'borderRightWidth',
'borderRightStyle',
'borderRightColor',
'borderBottom',
'borderBottomWidth',
'borderBottomStyle',
'borderBottomColor',
'borderBottomLeftRadius',
'borderBottomRightRadius',
'borderLeft',
'borderLeftWidth',
'borderLeftStyle',
'borderLeftColor',
'borderX',
'borderY',
'rounded',
],
layout: [
'width',
'w',
'height',
'h',
'display',
'minWidth',
'minW',
'minH',
'minHeight',
'maxWidth',
'maxW',
'maxHeight',
'maxH',
'size',
'verticalAlign',
'overflow',
'overflowX',
'overflowY',
],
flexbox: [
'alignItems',
'alignContent',
'justifyItems',
'justifyContent',
'flexWrap',
'flexDirection',
'flex',
'flexGrow',
'flexShrink',
'flexBasis',
'justifySelf',
'alignSelf',
'order',
],
position: ['position', 'zIndex', 'top', 'right', 'bottom', 'left'],
outline: ['outlineWidth', 'outlineColor', 'outlineStyle'],
background: ['bg', 'backgroundColor', 'bgColor'],
};
export type Dict = Record<string, any>;
export function omitUndefined(obj: any) {
return omitBy(obj, isNil);
}
export function getRandomString(length: number) {
var result = '';
var characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
// Inefficient way for pick, but retains order of props.
function orderedPick(obj: any, values: any) {
const ret: any = {};
Object.keys(obj).forEach((key: string) => {
if (values.includes(key)) {
ret[key] = obj[key];
}
});
return ret;
}
export function orderedExtractInObject(parent: any, values: Array<string>) {
return [
omitUndefined(orderedPick(parent, values)),
omitUndefined(omit(parent, values)),
];
}
/**
*
* @param parent The object from which data needs to extracted
* @param values Keys which needs to be extracted
* @returns [extractedProps, remainingProps]
*/
export function extractInObject(parent: any, values: Array<string>) {
return [
omitUndefined(pick(parent, values)),
omitUndefined(omit(parent, values)),
];
}
export function getColorFormColorScheme(props: Record<string, any>) {
const { theme, colorScheme, isDisabled } = props;
const simpleColorScheme = colorScheme.split('.')[0];
if (isDisabled) return 'gray.300';
else if (simpleColorScheme in theme.colors) {
return theme.colors[simpleColorScheme][0] === '#'
? simpleColorScheme
: theme.colors[simpleColorScheme][400] ||
theme.colors[simpleColorScheme][200];
} else return 'primary.200';
}
// TODO: This function can be removed.
export function getColorScheme(
props: Record<string, any>,
customColorScheme?: string
) {
let { theme, colorScheme } = props;
colorScheme = customColorScheme || colorScheme;
if (!(colorScheme in theme.colors)) return 'primary';
else {
if (typeof theme.colors[colorScheme] === 'object') return colorScheme;
}
}
export const inValidBreakpointProps = ['style', 'children', 'shadowOffset'];
export function hasValidBreakpointFormat(
breaks: any,
themeBreakpoints?: any,
property?: string
) {
if (property && inValidBreakpointProps.indexOf(property) !== -1) {
return false;
} else if (Array.isArray(breaks)) {
return breaks.length ? true : false;
} else if (typeof breaks === 'object' && breaks !== null) {
const keys = Object.keys(breaks);
const themeBreakPointKeys = Object.keys(themeBreakpoints);
for (let i = 0; i < keys.length; i++) {
if (themeBreakPointKeys.indexOf(keys[i]) === -1) {
return false;
}
}
return true;
} else {
return false;
}
}
export function findLastValidBreakpoint(
values: any,
themeBreakpoints: any,
currentBreakpoint: number
) {
const valArray = Array.isArray(values)
? values
: Object.keys(themeBreakpoints).map((bPoint: string) => values[bPoint]);
return (
valArray[currentBreakpoint] ??
valArray
.slice(0, currentBreakpoint + 1)
.filter((v: any) => !isNil(v))
.pop()
);
}
export function getClosestBreakpoint(
values: Record<string, any>,
point: number
) {
const dimValues = Object.values(values);
let index = -1;
let breakpointsObj: any = {};
for (let i = 0; i < dimValues.length; i++) {
breakpointsObj[dimValues[i]] = i;
}
const breakpoints = Object.keys(breakpointsObj);
for (let i = 0; i < breakpoints.length; i++) {
if (parseInt(breakpoints[i]) === point) {
index = breakpointsObj[breakpoints[i]];
break;
} else if (parseInt(breakpoints[i]) > point && i !== 0) {
index = breakpointsObj[breakpoints[i - 1]];
break;
}
// If windowWidth is greater than last available breakpoint clamp it to last index
else if (parseInt(breakpoints[i]) < point && i === dimValues.length - 1) {
index = breakpointsObj[breakpoints[i]];
break;
}
}
return index;
}
export const baseFontSize = 16;
export const convertAbsoluteToRem = (px: number) => {
return `${px / baseFontSize}rem`;
};
export const convertRemToAbsolute = (rem: number) => {
return rem * baseFontSize;
};
export const convertToDp = (value: number | string): number => {
const numberRegex = /^\d+$/;
if (typeof value === 'number') {
return value;
} else {
const isAbsolute = numberRegex.test(value);
const isPx = !isAbsolute && value.endsWith('px');
const isRem = !isAbsolute && value.endsWith('rem');
const isEm = !isAbsolute && value.endsWith('em');
let finalDpValue = 0;
if (isAbsolute || isPx) {
finalDpValue = parseFloat(value);
} else if (isEm) {
finalDpValue = convertRemToAbsolute(parseFloat(value));
} else if (isRem) {
finalDpValue = convertRemToAbsolute(parseFloat(value));
}
return finalDpValue;
}
};
/**
*
* @param theme
* @description
- Converts space/sizes/lineHeights/letterSpacings/fontSizes to `rem` on web if the token value specified is an absolute number.
- Converts space/sizes/lineHeights/letterSpacings/fontSizes to absolute number on native if the token value specified is in `px` or `rem`
*/
export const platformSpecificSpaceUnits = (theme: ITheme) => {
const scales = ['space', 'sizes', 'fontSizes'];
const newTheme = { ...theme };
const isWeb = Platform.OS === 'web';
scales.forEach((key) => {
const scale = get(theme, key, {});
const newScale = { ...scale };
for (const scaleKey in scale) {
const val = scale[scaleKey];
if (typeof val !== 'object') {
const isAbsolute = typeof val === 'number';
const isPx = !isAbsolute && val.endsWith('px');
const isRem = !isAbsolute && val.endsWith('rem');
// If platform is web, we need to convert absolute unit to rem. e.g. 16 to 1rem
if (isWeb) {
if (isAbsolute) {
newScale[scaleKey] = convertAbsoluteToRem(val);
}
}
// If platform is not web, we need to convert px unit to absolute and rem unit to absolute. e.g. 16px to 16. 1rem to 16.
else {
if (isRem) {
newScale[scaleKey] = convertRemToAbsolute(parseFloat(val));
} else if (isPx) {
newScale[scaleKey] = parseFloat(val);
}
}
}
}
//@ts-ignore
newTheme[key] = newScale;
});
return newTheme;
};
export function isResponsiveAnyProp(props: Record<string, any>, theme: any) {
if (props) {
const keys = Object.keys(props);
for (let i = 0; i < keys.length; i++) {
if (
hasValidBreakpointFormat(props[keys[i]], theme.breakpoints, keys[i])
) {
return true;
}
}
}
return false;
}