Bladeren bron

Add skeleton view

vbea 1 jaar geleden
bovenliggende
commit
da60518d8d

+ 120 - 0
Strides-APP/app/skeleton/ShiverBone.tsx

@@ -0,0 +1,120 @@
+import React, { memo } from 'react';
+import { View, StyleSheet } from 'react-native';
+import LinearGradient from 'react-native-linear-gradient';
+import Animated, {
+  SharedValue,
+  useAnimatedStyle,
+} from 'react-native-reanimated';
+import { useGetGradientTransform } from './hooks';
+import {
+  useGetBoneStyles,
+  useGetGradientSize,
+  useGetGradientEndDirection,
+} from './hooks/index.js';
+import type {
+  ISkeletonProps,
+  ICustomViewStyle,
+  IComponentSize,
+} from './constants';
+
+type ShiverBoneProps = Pick<
+  ISkeletonProps,
+  'boneColor' | 'highlightColor' | 'animationType' | 'animationDirection'
+> & {
+  componentSize: IComponentSize;
+  boneLayout: ICustomViewStyle;
+  animationValue: SharedValue<number>;
+  index?: number | string;
+};
+
+/**
+ * ShiverBone is a component that renders a rectangle with a gradient animation.
+ * @componentSize is the size of the component.
+ * @boneLayout is the layout of the bone.
+ * @boneColor is the color of the bone.
+ * @highlightColor is the color of the highlight.
+ * @animationDirection is the direction of the animation.
+ * @animationValue is the value of the animation.
+ * @animationType is the type of the animation.
+ * @index is the index of the bone.
+ */
+const ShiverBone: React.FC<ShiverBoneProps> = ({
+  componentSize,
+  boneLayout,
+  boneColor,
+  highlightColor,
+  animationDirection,
+  animationValue,
+  animationType,
+  index,
+}) => {
+  const transform = useGetGradientTransform({
+    componentSize,
+    boneLayout,
+    animationDirection,
+    animationValue,
+  });
+
+  const getBoneStyles = useGetBoneStyles(componentSize);
+  const getGradientSize = useGetGradientSize(componentSize);
+  const getGradientEndDirection = useGetGradientEndDirection(componentSize);
+
+  const gradientSize = getGradientSize({ animationDirection, boneLayout });
+
+  const animatedStyle = useAnimatedStyle(() => ({
+    ...gradientSize,
+    transform: [
+      {
+        translateX: transform.value.translateX ?? 0,
+      },
+      {
+        translateY: transform.value.translateY ?? 0,
+      },
+      {
+        rotate: transform.value.rotate ?? '0deg',
+      },
+    ],
+  }));
+
+  return (
+    <View
+      key={index}
+      style={getBoneStyles({
+        animationDirection,
+        animationType,
+        boneLayout,
+        boneColor,
+      })}
+    >
+      <Animated.View style={[styles.absoluteGradient, animatedStyle]}>
+        <LinearGradient
+          colors={[
+            boneColor as string | number,
+            highlightColor as string | number,
+            boneColor as string | number,
+          ]}
+          start={{ x: 0, y: 0 }}
+          end={getGradientEndDirection({
+            animationType,
+            animationDirection,
+            boneLayout,
+          })}
+          style={styles.gradientChild}
+        />
+      </Animated.View>
+    </View>
+  );
+};
+
+export default memo(ShiverBone);
+
+const styles = StyleSheet.create({
+  absoluteGradient: {
+    height: '100%',
+    position: 'absolute',
+    width: '100%',
+  },
+  gradientChild: {
+    flex: 1
+  }
+});

+ 70 - 0
Strides-APP/app/skeleton/StaticBone.tsx

@@ -0,0 +1,70 @@
+import React, { memo } from 'react';
+import Animated, {
+  SharedValue,
+  useAnimatedStyle,
+  interpolateColor,
+} from 'react-native-reanimated';
+import { useGetBoneStyles } from './hooks';
+import type {
+  ICustomViewStyle,
+  ISkeletonProps,
+  IComponentSize,
+} from './constants';
+
+type StaticBoneProps = Pick<
+  ISkeletonProps,
+  'boneColor' | 'highlightColor' | 'animationType' | 'animationDirection'
+> & {
+  componentSize: IComponentSize;
+  boneLayout: ICustomViewStyle;
+  animationValue: SharedValue<number>;
+  index?: number | string;
+};
+
+/**
+ * StaticBone is a component that renders a rectangle with a static color.
+ * @boneLayout is the layout of the bone.
+ * @boneColor is the color of the bone.
+ * @highlightColor is the color of the highlight.
+ * @animationDirection is the direction of the animation.
+ * @animationValue is the value of the animation.
+ * @animationType is the type of the animation.
+ * @index is the index of the bone.
+ * @componentSize is the size of the component.
+ */
+const StaticBone: React.FC<StaticBoneProps> = ({
+  boneLayout,
+  index,
+  componentSize,
+  highlightColor,
+  animationValue,
+  animationDirection,
+  animationType,
+  boneColor,
+}) => {
+  const getBoneStyles = useGetBoneStyles(componentSize);
+
+  const boneStyle = getBoneStyles({
+    animationDirection,
+    animationType,
+    boneLayout,
+    boneColor,
+  });
+
+  const animatedStyle = useAnimatedStyle(() => ({
+    backgroundColor: interpolateColor(
+      animationValue.value,
+      [0, 1],
+      [boneColor as string, highlightColor as string],
+    ),
+  }));
+
+  return (
+    <Animated.View
+      key={index}
+      style={[boneStyle, animationType === 'none' ? {} : animatedStyle]}
+    />
+  );
+};
+
+export default memo(StaticBone);

+ 63 - 0
Strides-APP/app/skeleton/constants.ts

@@ -0,0 +1,63 @@
+import React from "react"
+import { StyleProp, ViewStyle } from 'react-native';
+import { Easing, SharedValue, EasingFn } from 'react-native-reanimated';
+
+type _animationType = 'none' | 'shiver' | 'pulse' | undefined;
+type _animationDirection =
+  | 'horizontalLeft'
+  | 'horizontalRight'
+  | 'verticalTop'
+  | 'verticalDown'
+  | 'diagonalDownLeft'
+  | 'diagonalDownRight'
+  | 'diagonalTopLeft'
+  | 'diagonalTopRight'
+  | undefined;
+
+export interface ICustomViewStyle extends ViewStyle {
+  children?: ICustomViewStyle[];
+  key?: number | string;
+}
+
+export interface ISkeletonProps {
+  isLoading: boolean;
+  layout?: ICustomViewStyle[];
+  duration?: number;
+  containerStyle?: StyleProp<ViewStyle>;
+  animationType?: _animationType;
+  animationDirection?: _animationDirection;
+  boneColor?: string | undefined;
+  hasFadeIn?: boolean;
+  highlightColor?: string | undefined;
+  easing?: { factory: () => EasingFn };
+  children?: React.ReactNode;
+}
+
+export interface IComponentSize {
+  width: number;
+  height: number;
+}
+
+export interface IGeneralStyles {
+  animationDirection: _animationDirection;
+  animationType: _animationType;
+  boneColor: string;
+  highlightColor: string;
+  animationValue: SharedValue<number>;
+}
+
+export interface IDirection {
+  x: number;
+  y: number;
+}
+
+export const DEFAULT_CONFIG = {
+  BORDER_RADIUS: 3,
+  DURATION: 1200,
+  ANIMATION_TYPE: 'shiver' as _animationType,
+  ANIMATION_DIRECTION: 'horizontalLeft' as _animationDirection,
+  BONE_COLOR: '#E1E9EE',
+  HIGHLIGHT_COLOR: '#F2F8FC',
+  EASING: Easing.bezier(0.5, 0, 0.25, 1),
+  LOADING: true,
+};