react-native-shadow-2
Version:
Cross-platform shadow for React Native. Improved version of the abandoned react-native-shadow package
101 lines (100 loc) • 5.17 kB
JavaScript
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-imports
import React from 'react';
import { PixelRatio, Platform } from 'react-native';
import { RadialGradient, Stop } from 'react-native-svg';
export const cornersArray = ['topStart', 'topEnd', 'bottomStart', 'bottomEnd'];
const isWeb = Platform.OS === 'web';
/** Rounds the given size to a pixel perfect size. */
export const R = isWeb
? // In Web, 1dp=1px. But it accepts decimal sizes, and it's somewhat problematic.
// The size rounding is browser-dependent, so we do the decimal rounding for web by ourselves to have a
// consistent behavior. We floor it, because it's better for the child to overlap by a pixel the right/bottom shadow part
// than to have a pixel wide gap between them.
Math.floor
: PixelRatio.roundToNearestPixel;
/** Converts dp to pixels. */
export const P = isWeb ? (v) => v : PixelRatio.getPixelSizeForLayoutSize;
/** How many pixels for each dp. scale = pixels/dp */
export const scale = isWeb ? 1 : PixelRatio.get();
/** Converts two sizes to pixel for perfect math, sums them and converts the result back to dp. */
export const sumDps = isWeb
? (a, b) => a + b
: (a, b) => R((P(a) + P(b)) / scale);
/** Converts two sizes to pixel for perfect math, divides them and converts the result back to dp. */
export const divDps = isWeb
? (a, b) => a / b
: (a, b) => P(a) / P(b);
/**
* [Android/ios?] [*4] A small safe margin for the svg sizes.
*
* It fixes some gaps that we had, as even that the svg size and the svg rect for example size were the same, this rect
* would still strangely be cropped/clipped. We give this additional size to the svg so our rect/etc won't be unintendedly clipped.
*
* It doesn't mean 1 pixel, as RN uses dp sizing, it's just an arbitrary and big enough number.
* */
export const additional = isWeb ? 0 : 1;
/** Auxilary function to shorten code */
export function objFromKeys(keys, fun) {
const result = {};
for (const key of keys)
result[key] = fun(key);
return result;
}
export const cornerToStyle = {
topLeft: ['borderTopLeftRadius', 'borderTopStartRadius'],
topRight: ['borderTopRightRadius', 'borderTopEndRadius'],
bottomLeft: ['borderBottomLeftRadius', 'borderBottomStartRadius'],
bottomRight: ['borderBottomRightRadius', 'borderBottomEndRadius'],
};
/**
For iOS this is the last value before rounding to 1.
We do this because react-native-svg in iOS won't consider Stops after the one with offset=1.
This doesn't seem to affect the look of the corners on iOS.
If it does, we will need to go back to the previous (<v7) path solution.
*/
const finalStopOffset = Platform.OS === 'ios' ? 0.9999999999999999 : 1;
export function radialGradient({ id, left, radius, shadowRadius, top, startColorWoOpacity, startColorOpacity, endColorWoOpacity, endColorOpacity, paintInside, }) {
/*
On Android !paintInside && <Stop/> would throw [#56](https://github.com/ftzi/react-native-shadow-2/issues/56).
I tried {paintInside ? <Stop/> : <></>}, but it caused the another reported bug in the same issue.
This if/else solution solves those react-native-svg strange limitations.
I could try to have a wrapper function / dynamic children but those bugs were very unexpected, so I chose the Will-Work solution.
*/
if (paintInside)
return (<RadialGradient id={id} cx={left ? shadowRadius : 0} cy={top ? shadowRadius : 0} r={shadowRadius} gradientUnits='userSpaceOnUse' // won't show if this isn't set
>
<Stop offset={radius / shadowRadius} stopColor={startColorWoOpacity} stopOpacity={startColorOpacity}/>
<Stop offset={finalStopOffset} stopColor={endColorWoOpacity} stopOpacity={endColorOpacity}/>
{/* Ensure it stops painting after the radius if endColorOpacity isn't 0. */}
<Stop offset={1} stopColor={endColorWoOpacity} stopOpacity={0}/>
</RadialGradient>);
else
return (<RadialGradient id={id} cx={left ? shadowRadius : 0} cy={top ? shadowRadius : 0} r={shadowRadius} gradientUnits='userSpaceOnUse' // won't show if this isn't set
>
{/* Don't paint the inner circle if not paintInside */}
<Stop offset={radius / shadowRadius} stopOpacity={0}/>
<Stop offset={radius / shadowRadius} stopColor={startColorWoOpacity} stopOpacity={startColorOpacity}/>
<Stop offset={finalStopOffset} stopColor={endColorWoOpacity} stopOpacity={endColorOpacity}/>
<Stop offset={1} stopColor={endColorWoOpacity} stopOpacity={0}/>
</RadialGradient>);
}
/**
* Generates a sufficiently unique suffix to add to gradient ids and prevent collisions.
*
* https://github.com/ftzi/react-native-shadow-2/pull/54
*/
export const generateGradientIdSuffix = (() => {
let shadowGradientIdCounter = 0;
return () => String(shadowGradientIdCounter++);
})();
export const rtlScaleX = { transform: [{ scaleX: -1 }] };
/**
* https://github.com/ftzi/react-native-shadow-2/issues/67
*/
export const rtlAbsoluteFillObject = {
position: 'absolute',
start: 0,
end: 0,
top: 0,
bottom: 0,
};