react-native-inner-shadow
Version: 
react native inner shadows with linear gradient design UI
231 lines (218 loc) • 7.89 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.computeShadowProperties = computeShadowProperties;
exports.getBackgroundColor = getBackgroundColor;
exports.getBorderRadius = getBorderRadius;
exports.getLinearDirection = getLinearDirection;
exports.getOuterShadowOffset = getOuterShadowOffset;
exports.isLinearProps = isLinearProps;
exports.numerify = numerify;
var _reactNativeSkia = require("@shopify/react-native-skia");
var _constants = require("./constants.js");
/**
 * Converts a value to a number, returning a default value if the conversion fails.
 *
 * @privateRemarks
 * At this time(17.Feb.2025), we do not support the way to convert the string (percentage) to a number.
 *
 * @template T - The type of the default value.
 *
 * @param value - The value to convert to a number.
 *
 * @returns The converted number, or the default value if the conversion fails.
 */
function numerify(value, defaultValue) {
  const num = Number(value); // if value === null return 0
  return Number.isNaN(num) ? defaultValue : num;
}
function getBorderRadius(style) {
  const borderRadius = numerify(style?.borderRadius, null);
  const topStartRadius = numerify(style?.borderTopStartRadius, borderRadius);
  const topLeftRadius = numerify(style?.borderTopLeftRadius, topStartRadius ?? 0);
  const topEndRadius = numerify(style?.borderTopEndRadius, borderRadius);
  const topRightRadius = numerify(style?.borderTopRightRadius, topEndRadius ?? 0);
  const bottomEndRadius = numerify(style?.borderBottomEndRadius, borderRadius);
  const bottomRightRadius = numerify(style?.borderBottomRightRadius, bottomEndRadius ?? 0);
  const bottomStartRadius = numerify(style?.borderBottomStartRadius, borderRadius);
  const bottomLeftRadius = numerify(style?.borderBottomLeftRadius, bottomStartRadius ?? 0);
  return {
    borderRadius,
    topLeftRadius,
    topRightRadius,
    bottomRightRadius,
    bottomLeftRadius
  };
}
/**
 * getBackgroundColor retrieves the final background color
 * from either:
 *   1) props.backgroundColor
 *   2) props.style.backgroundColor
 *   3) BACKGROUND_COLOR
 *
 * This ensures there is always a valid color for the component’s background.
 *
 * {@link GetBackgroundColorProps | props} - The props object containing background color settings.
 *
 * @returns The final background color for the component.
 */
function getBackgroundColor({
  backgroundColor,
  styleBackground
}) {
  const bgColor = backgroundColor ?? styleBackground ?? _constants.BACKGROUND_COLOR;
  return bgColor;
}
/**
 * computeShadowProperties determines the final configuration for both
 * the main shadow and any reflected light. It merges default values
 * with provided props to form a complete “shadow settings” object.
 *
 * - `shadowOffset` / `reflectedLightOffset`: how far the shadows/highlights
 *   are shifted in x and y.
 * - `shadowColor` / `reflectedLightColor`: colors used for each effect.
 * - `shadowBlur` / `reflectedLightBlur`: blur radius for the softness/spread
 *   of the shadow or highlight.
 *
 * {@link ShadowPropertyConfig} - The props object containing shadow-related settings.
 *
 * @returns `{
 * shadowOffset, reflectedLightOffset, shadowColor, reflectedLightColor, shadowBlur, reflectedLightBlur }`
 */
function computeShadowProperties({
  inset,
  shadowOffset,
  shadowBlur,
  shadowColor,
  reflectedLightOffset,
  reflectedLightBlur,
  reflectedLightColor
}) {
  const shadowOffsetX = numerify(shadowOffset?.width, _constants.SHADOW_OFFSET_SCALE);
  const shadowOffsetY = numerify(shadowOffset?.height, _constants.SHADOW_OFFSET_SCALE);
  // By default, the reflected light offset is the inverse of the main shadow
  // so it appears on the opposite corner/side.
  // when `inset` property is `true`, the reflected light offset is opposite to the shadow offset
  const reflectedLightOffsetX = calculateReflectedLightPosition({
    inset,
    reflectedLightScale: reflectedLightOffset?.width,
    baseShadowOffset: shadowOffsetX
  });
  const reflectedLightOffsetY = calculateReflectedLightPosition({
    inset,
    reflectedLightScale: reflectedLightOffset?.height,
    baseShadowOffset: shadowOffsetY
  });
  // "Blur" here maps to how soft or large the shadow/highlight is.
  // The higher the number, the more diffuse the effect.
  const finalShadowBlur = Math.max(shadowBlur ?? _constants.SHADOW_BLUR, 0);
  const finalReflectedLightBlur = Math.max(reflectedLightBlur ?? _constants.REFLECTED_LIGHT_BLUR, 0);
  // Fallback to the provided defaults if the user doesn't specify a color.
  const finalShadowColor = shadowColor ?? _constants.SHADOW_COLOR;
  const finalReflectedLightColor = reflectedLightColor ?? _constants.REFLECTED_LIGHT_COLOR;
  // Construct the final offsets as objects for clarity.
  const finalShadowOffset = {
    width: shadowOffsetX,
    height: shadowOffsetY
  };
  const finalReflectedLightOffset = {
    width: reflectedLightOffsetX,
    height: reflectedLightOffsetY
  };
  return {
    shadowOffset: finalShadowOffset,
    reflectedLightOffset: finalReflectedLightOffset,
    shadowColor: finalShadowColor,
    reflectedLightColor: finalReflectedLightColor,
    shadowBlur: finalShadowBlur,
    reflectedLightBlur: finalReflectedLightBlur
  };
}
function calculateReflectedLightPosition({
  inset,
  reflectedLightScale,
  baseShadowOffset
}) {
  // When user provides a reflected light offset, use that. - which allows `0` and `null`
  if (reflectedLightScale !== undefined) return reflectedLightScale;
  // When shadow is 0, reflected light should be 0.
  if (baseShadowOffset === 0) return 0;
  // for matching reflected light offset direction based on inset
  const scaleFactor = (baseShadowOffset + _constants.REFLECTED_LIGHT_OFFSET_SCALE) / 2;
  // When inset is true, the reflected light should be opposite the shadow.
  return inset ? -scaleFactor : scaleFactor;
}
/**
 * `getOuterShadowOffset` calculates the outer shadow offset properties.
 *
 * {@link GetOuterShadowOffsetProps} - The props object containing outer shadow offset settings.
 *
 * @returns `{ shadowColor, shadowOffset, shadowBlur, shadowOpacity, shadowRadius, elevation, boxShadow }`
 */
function getOuterShadowOffset({
  inset,
  shadowColor,
  shadowOffset,
  shadowBlur,
  shadowOpacity = _constants.SHADOW_OPACITY,
  shadowRadius = _constants.SHADOW_RADIUS,
  elevation = _constants.SHADOW_ELEVATION,
  boxShadow
}) {
  if (inset) return {};
  return {
    shadowColor,
    shadowOffset,
    shadowBlur,
    shadowOpacity,
    shadowRadius,
    elevation,
    boxShadow
  };
}
/**
 * `getLinearDirection` calculates the start and end points for a linear gradient
 * based on the provided direction (from, to).
 *
 * - The direction is specified as a string, e.g., 'top', 'bottom', 'left', 'right'.
 * - The width and height are used to calculate the midpoints for each direction.
 *
 * {@link GetLinearDirectionProps} - The props object containing linear direction settings.
 *
 * @returns `{ start, end }`
 */
function getLinearDirection({
  width,
  height,
  from,
  to
}) {
  const top = (0, _reactNativeSkia.vec)(width / 2, 0);
  const bottom = (0, _reactNativeSkia.vec)(width / 2, height);
  const left = (0, _reactNativeSkia.vec)(0, height / 2);
  const right = (0, _reactNativeSkia.vec)(width, height / 2);
  const direction = {
    top,
    bottom,
    left,
    right
  };
  return {
    start: direction[from],
    end: direction[to]
  };
}
/**
 * `isLinearProps` checks if the provided props are for a linear gradient.
 * If the `colors` property is an array, we assume it's a linear gradient.
 *
 * @param props - see {@link InnerShadowProps} and {@link LinearInnerShadowProps}
 *
 * @returns `true` if the props are for a linear gradient, `false` otherwise.
 */
function isLinearProps(props) {
  return 'colors' in props && Array.isArray(props.colors);
}
//# sourceMappingURL=utils.js.map