react-native-smart-skeleton 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +255 -0
  2. package/lib/commonjs/animations/breathing.js +53 -0
  3. package/lib/commonjs/animations/breathing.js.map +1 -0
  4. package/lib/commonjs/animations/pulse.js +50 -0
  5. package/lib/commonjs/animations/pulse.js.map +1 -0
  6. package/lib/commonjs/animations/shimmer.js +68 -0
  7. package/lib/commonjs/animations/shimmer.js.map +1 -0
  8. package/lib/commonjs/animations/wave.js +54 -0
  9. package/lib/commonjs/animations/wave.js.map +1 -0
  10. package/lib/commonjs/clone/SkeletonClone.js +91 -0
  11. package/lib/commonjs/clone/SkeletonClone.js.map +1 -0
  12. package/lib/commonjs/components/Circle.js +27 -0
  13. package/lib/commonjs/components/Circle.js.map +1 -0
  14. package/lib/commonjs/components/Group.js +57 -0
  15. package/lib/commonjs/components/Group.js.map +1 -0
  16. package/lib/commonjs/components/Pill.js +32 -0
  17. package/lib/commonjs/components/Pill.js.map +1 -0
  18. package/lib/commonjs/components/Rect.js +21 -0
  19. package/lib/commonjs/components/Rect.js.map +1 -0
  20. package/lib/commonjs/components/Skeleton.js +196 -0
  21. package/lib/commonjs/components/Skeleton.js.map +1 -0
  22. package/lib/commonjs/components/Square.js +28 -0
  23. package/lib/commonjs/components/Square.js.map +1 -0
  24. package/lib/commonjs/demo/demo.gif +0 -0
  25. package/lib/commonjs/hooks/useReducedMotion.js +42 -0
  26. package/lib/commonjs/hooks/useReducedMotion.js.map +1 -0
  27. package/lib/commonjs/hooks/useSkeletonTheme.js +46 -0
  28. package/lib/commonjs/hooks/useSkeletonTheme.js.map +1 -0
  29. package/lib/commonjs/index.js +149 -0
  30. package/lib/commonjs/index.js.map +1 -0
  31. package/lib/commonjs/presets/Card.js +83 -0
  32. package/lib/commonjs/presets/Card.js.map +1 -0
  33. package/lib/commonjs/presets/Chat.js +54 -0
  34. package/lib/commonjs/presets/Chat.js.map +1 -0
  35. package/lib/commonjs/presets/Product.js +70 -0
  36. package/lib/commonjs/presets/Product.js.map +1 -0
  37. package/lib/commonjs/presets/Profile.js +67 -0
  38. package/lib/commonjs/presets/Profile.js.map +1 -0
  39. package/lib/commonjs/presets/Video.js +76 -0
  40. package/lib/commonjs/presets/Video.js.map +1 -0
  41. package/lib/commonjs/providers/SkeletonProvider.js +70 -0
  42. package/lib/commonjs/providers/SkeletonProvider.js.map +1 -0
  43. package/lib/commonjs/types/index.js +6 -0
  44. package/lib/commonjs/types/index.js.map +1 -0
  45. package/lib/commonjs/utils/colors.js +144 -0
  46. package/lib/commonjs/utils/colors.js.map +1 -0
  47. package/lib/commonjs/utils/responsive.js +84 -0
  48. package/lib/commonjs/utils/responsive.js.map +1 -0
  49. package/lib/commonjs/utils/theme.js +39 -0
  50. package/lib/commonjs/utils/theme.js.map +1 -0
  51. package/lib/module/animations/breathing.js +47 -0
  52. package/lib/module/animations/breathing.js.map +1 -0
  53. package/lib/module/animations/pulse.js +44 -0
  54. package/lib/module/animations/pulse.js.map +1 -0
  55. package/lib/module/animations/shimmer.js +61 -0
  56. package/lib/module/animations/shimmer.js.map +1 -0
  57. package/lib/module/animations/wave.js +48 -0
  58. package/lib/module/animations/wave.js.map +1 -0
  59. package/lib/module/clone/SkeletonClone.js +84 -0
  60. package/lib/module/clone/SkeletonClone.js.map +1 -0
  61. package/lib/module/components/Circle.js +20 -0
  62. package/lib/module/components/Circle.js.map +1 -0
  63. package/lib/module/components/Group.js +50 -0
  64. package/lib/module/components/Group.js.map +1 -0
  65. package/lib/module/components/Pill.js +25 -0
  66. package/lib/module/components/Pill.js.map +1 -0
  67. package/lib/module/components/Rect.js +14 -0
  68. package/lib/module/components/Rect.js.map +1 -0
  69. package/lib/module/components/Skeleton.js +188 -0
  70. package/lib/module/components/Skeleton.js.map +1 -0
  71. package/lib/module/components/Square.js +21 -0
  72. package/lib/module/components/Square.js.map +1 -0
  73. package/lib/module/demo/demo.gif +0 -0
  74. package/lib/module/hooks/useReducedMotion.js +37 -0
  75. package/lib/module/hooks/useReducedMotion.js.map +1 -0
  76. package/lib/module/hooks/useSkeletonTheme.js +40 -0
  77. package/lib/module/hooks/useSkeletonTheme.js.map +1 -0
  78. package/lib/module/index.js +50 -0
  79. package/lib/module/index.js.map +1 -0
  80. package/lib/module/presets/Card.js +76 -0
  81. package/lib/module/presets/Card.js.map +1 -0
  82. package/lib/module/presets/Chat.js +47 -0
  83. package/lib/module/presets/Chat.js.map +1 -0
  84. package/lib/module/presets/Product.js +63 -0
  85. package/lib/module/presets/Product.js.map +1 -0
  86. package/lib/module/presets/Profile.js +60 -0
  87. package/lib/module/presets/Profile.js.map +1 -0
  88. package/lib/module/presets/Video.js +69 -0
  89. package/lib/module/presets/Video.js.map +1 -0
  90. package/lib/module/providers/SkeletonProvider.js +61 -0
  91. package/lib/module/providers/SkeletonProvider.js.map +1 -0
  92. package/lib/module/types/index.js +2 -0
  93. package/lib/module/types/index.js.map +1 -0
  94. package/lib/module/utils/colors.js +132 -0
  95. package/lib/module/utils/colors.js.map +1 -0
  96. package/lib/module/utils/responsive.js +74 -0
  97. package/lib/module/utils/responsive.js.map +1 -0
  98. package/lib/module/utils/theme.js +32 -0
  99. package/lib/module/utils/theme.js.map +1 -0
  100. package/lib/typescript/animations/breathing.d.ts +26 -0
  101. package/lib/typescript/animations/breathing.d.ts.map +1 -0
  102. package/lib/typescript/animations/pulse.d.ts +22 -0
  103. package/lib/typescript/animations/pulse.d.ts.map +1 -0
  104. package/lib/typescript/animations/shimmer.d.ts +34 -0
  105. package/lib/typescript/animations/shimmer.d.ts.map +1 -0
  106. package/lib/typescript/animations/wave.d.ts +23 -0
  107. package/lib/typescript/animations/wave.d.ts.map +1 -0
  108. package/lib/typescript/clone/SkeletonClone.d.ts +30 -0
  109. package/lib/typescript/clone/SkeletonClone.d.ts.map +1 -0
  110. package/lib/typescript/components/Circle.d.ts +10 -0
  111. package/lib/typescript/components/Circle.d.ts.map +1 -0
  112. package/lib/typescript/components/Group.d.ts +21 -0
  113. package/lib/typescript/components/Group.d.ts.map +1 -0
  114. package/lib/typescript/components/Pill.d.ts +12 -0
  115. package/lib/typescript/components/Pill.d.ts.map +1 -0
  116. package/lib/typescript/components/Rect.d.ts +12 -0
  117. package/lib/typescript/components/Rect.d.ts.map +1 -0
  118. package/lib/typescript/components/Skeleton.d.ts +16 -0
  119. package/lib/typescript/components/Skeleton.d.ts.map +1 -0
  120. package/lib/typescript/components/Square.d.ts +10 -0
  121. package/lib/typescript/components/Square.d.ts.map +1 -0
  122. package/lib/typescript/hooks/useReducedMotion.d.ts +11 -0
  123. package/lib/typescript/hooks/useReducedMotion.d.ts.map +1 -0
  124. package/lib/typescript/hooks/useSkeletonTheme.d.ts +12 -0
  125. package/lib/typescript/hooks/useSkeletonTheme.d.ts.map +1 -0
  126. package/lib/typescript/index.d.ts +51 -0
  127. package/lib/typescript/index.d.ts.map +1 -0
  128. package/lib/typescript/presets/Card.d.ts +11 -0
  129. package/lib/typescript/presets/Card.d.ts.map +1 -0
  130. package/lib/typescript/presets/Chat.d.ts +12 -0
  131. package/lib/typescript/presets/Chat.d.ts.map +1 -0
  132. package/lib/typescript/presets/Product.d.ts +12 -0
  133. package/lib/typescript/presets/Product.d.ts.map +1 -0
  134. package/lib/typescript/presets/Profile.d.ts +11 -0
  135. package/lib/typescript/presets/Profile.d.ts.map +1 -0
  136. package/lib/typescript/presets/Video.d.ts +12 -0
  137. package/lib/typescript/presets/Video.d.ts.map +1 -0
  138. package/lib/typescript/providers/SkeletonProvider.d.ts +18 -0
  139. package/lib/typescript/providers/SkeletonProvider.d.ts.map +1 -0
  140. package/lib/typescript/types/index.d.ts +149 -0
  141. package/lib/typescript/types/index.d.ts.map +1 -0
  142. package/lib/typescript/utils/colors.d.ts +51 -0
  143. package/lib/typescript/utils/colors.d.ts.map +1 -0
  144. package/lib/typescript/utils/responsive.d.ts +27 -0
  145. package/lib/typescript/utils/responsive.d.ts.map +1 -0
  146. package/lib/typescript/utils/theme.d.ts +16 -0
  147. package/lib/typescript/utils/theme.d.ts.map +1 -0
  148. package/package.json +74 -0
  149. package/src/animations/breathing.tsx +81 -0
  150. package/src/animations/pulse.tsx +75 -0
  151. package/src/animations/shimmer.tsx +88 -0
  152. package/src/animations/wave.tsx +74 -0
  153. package/src/clone/SkeletonClone.tsx +82 -0
  154. package/src/components/Circle.tsx +13 -0
  155. package/src/components/Group.tsx +55 -0
  156. package/src/components/Pill.tsx +23 -0
  157. package/src/components/Rect.tsx +15 -0
  158. package/src/components/Skeleton.tsx +218 -0
  159. package/src/components/Square.tsx +13 -0
  160. package/src/demo/demo.gif +0 -0
  161. package/src/hooks/useReducedMotion.ts +46 -0
  162. package/src/hooks/useSkeletonTheme.ts +45 -0
  163. package/src/index.ts +94 -0
  164. package/src/presets/Card.tsx +56 -0
  165. package/src/presets/Chat.tsx +52 -0
  166. package/src/presets/Product.tsx +45 -0
  167. package/src/presets/Profile.tsx +54 -0
  168. package/src/presets/Video.tsx +53 -0
  169. package/src/providers/SkeletonProvider.tsx +67 -0
  170. package/src/types/index.ts +246 -0
  171. package/src/utils/colors.ts +132 -0
  172. package/src/utils/responsive.ts +73 -0
  173. package/src/utils/theme.ts +35 -0
@@ -0,0 +1,81 @@
1
+ import { useEffect } from "react";
2
+ import {
3
+ Easing,
4
+ cancelAnimation,
5
+ useAnimatedStyle,
6
+ useSharedValue,
7
+ withDelay,
8
+ withRepeat,
9
+ withTiming,
10
+ } from "react-native-reanimated";
11
+ import type { SkeletonPerformanceMode } from "../types";
12
+
13
+ export interface BreathingOptions {
14
+ duration?: number;
15
+ delay?: number;
16
+ disabled?: boolean;
17
+ performanceMode?: SkeletonPerformanceMode;
18
+ /** Smallest scale factor during the "exhale" phase. Defaults to 0.97. */
19
+ minScale?: number;
20
+ /** Largest scale factor during the "inhale" phase. Defaults to 1. */
21
+ maxScale?: number;
22
+ /** Paired opacity dip alongside the scale-down. Defaults to 0.85. */
23
+ minOpacity?: number;
24
+ }
25
+
26
+ /**
27
+ * A slow, very gentle scale + opacity oscillation — softer and slower than
28
+ * "pulse" — intended as the most reduced-motion-friendly animation. This is
29
+ * the animation `Skeleton` falls back to (at a slowed rate) when the OS
30
+ * reduce-motion setting is on and the consumer hasn't explicitly chosen "none".
31
+ */
32
+ export function useBreathingStyle({
33
+ duration = 2200,
34
+ delay = 0,
35
+ disabled = false,
36
+ performanceMode = "balanced",
37
+ minScale = 0.97,
38
+ maxScale = 1,
39
+ minOpacity = 0.85,
40
+ }: BreathingOptions) {
41
+ const progress = useSharedValue(0);
42
+
43
+ useEffect(() => {
44
+ if (disabled) {
45
+ progress.value = 0;
46
+ return;
47
+ }
48
+
49
+ const effectiveDuration =
50
+ performanceMode === "battery-saver" ? duration * 1.2 : duration;
51
+
52
+ progress.value = 0;
53
+ progress.value = withDelay(
54
+ delay,
55
+ withRepeat(
56
+ withTiming(1, {
57
+ duration: effectiveDuration / 2,
58
+ easing: Easing.inOut(Easing.ease),
59
+ }),
60
+ -1,
61
+ true
62
+ )
63
+ );
64
+
65
+ return () => {
66
+ cancelAnimation(progress);
67
+ };
68
+ // eslint-disable-next-line react-hooks/exhaustive-deps
69
+ }, [duration, delay, disabled, performanceMode]);
70
+
71
+ const style = useAnimatedStyle(() => {
72
+ const scale = minScale + progress.value * (maxScale - minScale);
73
+ const opacity = minOpacity + progress.value * (1 - minOpacity);
74
+ return {
75
+ opacity,
76
+ transform: [{ scale }],
77
+ };
78
+ }, [minScale, maxScale, minOpacity]);
79
+
80
+ return style;
81
+ }
@@ -0,0 +1,75 @@
1
+ import { useEffect } from "react";
2
+ import {
3
+ Easing,
4
+ cancelAnimation,
5
+ useAnimatedStyle,
6
+ useSharedValue,
7
+ withDelay,
8
+ withRepeat,
9
+ withTiming,
10
+ } from "react-native-reanimated";
11
+ import type { SkeletonPerformanceMode } from "../types";
12
+
13
+ export interface PulseOptions {
14
+ duration?: number;
15
+ delay?: number;
16
+ disabled?: boolean;
17
+ performanceMode?: SkeletonPerformanceMode;
18
+ /** Minimum opacity of the "dim" phase. Defaults to 0.4. */
19
+ minOpacity?: number;
20
+ /** Maximum opacity of the "bright" phase. Defaults to 1. */
21
+ maxOpacity?: number;
22
+ }
23
+
24
+ /**
25
+ * Classic fade in/out pulse: opacity oscillates between minOpacity and
26
+ * maxOpacity using an ease-in-out curve, looping indefinitely.
27
+ *
28
+ * Returns an animated style with just `opacity`, ready to spread onto the
29
+ * skeleton's base View.
30
+ */
31
+ export function usePulseStyle({
32
+ duration = 1000,
33
+ delay = 0,
34
+ disabled = false,
35
+ performanceMode = "balanced",
36
+ minOpacity = 0.4,
37
+ maxOpacity = 1,
38
+ }: PulseOptions) {
39
+ const progress = useSharedValue(0);
40
+
41
+ useEffect(() => {
42
+ if (disabled) {
43
+ progress.value = 0;
44
+ return;
45
+ }
46
+
47
+ const effectiveDuration =
48
+ performanceMode === "battery-saver" ? duration * 1.3 : duration;
49
+
50
+ progress.value = 0;
51
+ progress.value = withDelay(
52
+ delay,
53
+ withRepeat(
54
+ withTiming(1, {
55
+ duration: effectiveDuration / 2,
56
+ easing: Easing.inOut(Easing.ease),
57
+ }),
58
+ -1,
59
+ true // reverse, giving the fade-in/fade-out loop
60
+ )
61
+ );
62
+
63
+ return () => {
64
+ cancelAnimation(progress);
65
+ };
66
+ // eslint-disable-next-line react-hooks/exhaustive-deps
67
+ }, [duration, delay, disabled, performanceMode]);
68
+
69
+ const style = useAnimatedStyle(() => {
70
+ const opacity = minOpacity + progress.value * (maxOpacity - minOpacity);
71
+ return { opacity };
72
+ }, [minOpacity, maxOpacity]);
73
+
74
+ return style;
75
+ }
@@ -0,0 +1,88 @@
1
+ import { useEffect } from "react";
2
+ import {
3
+ Easing,
4
+ cancelAnimation,
5
+ useAnimatedStyle,
6
+ useSharedValue,
7
+ withDelay,
8
+ withRepeat,
9
+ withTiming,
10
+ } from "react-native-reanimated";
11
+ import type { SkeletonPerformanceMode } from "../types";
12
+
13
+ export interface ShimmerOptions {
14
+ duration?: number;
15
+ delay?: number;
16
+ /** When true, freezes the animation at progress 0 (used for reduced motion / "none"). */
17
+ disabled?: boolean;
18
+ performanceMode?: SkeletonPerformanceMode;
19
+ }
20
+
21
+ /**
22
+ * Drives a 0 -> 1 -> 0 progress value looping indefinitely, intended to be
23
+ * mapped (by the consuming component) onto a translateX of a gradient
24
+ * overlay to create the classic "shimmer sweep" effect.
25
+ *
26
+ * Returns both the raw shared value-derived animated style (opacity passthrough,
27
+ * so it can be spread directly) and the progress itself isn't exposed —
28
+ * components should use `useShimmerTransform` below to get a transform style
29
+ * sized to their own width.
30
+ */
31
+ export function useShimmerProgress({
32
+ duration = 1200,
33
+ delay = 0,
34
+ disabled = false,
35
+ performanceMode = "balanced",
36
+ }: ShimmerOptions) {
37
+ const progress = useSharedValue(0);
38
+
39
+ useEffect(() => {
40
+ if (disabled) {
41
+ progress.value = 0;
42
+ return;
43
+ }
44
+
45
+ const effectiveDuration =
46
+ performanceMode === "battery-saver" ? duration * 1.4 : duration;
47
+
48
+ progress.value = 0;
49
+ progress.value = withDelay(
50
+ delay,
51
+ withRepeat(
52
+ withTiming(1, {
53
+ duration: effectiveDuration,
54
+ easing: Easing.linear,
55
+ }),
56
+ -1,
57
+ false
58
+ )
59
+ );
60
+
61
+ return () => {
62
+ cancelAnimation(progress);
63
+ };
64
+ // eslint-disable-next-line react-hooks/exhaustive-deps
65
+ }, [duration, delay, disabled, performanceMode]);
66
+
67
+ return progress;
68
+ }
69
+
70
+ /**
71
+ * Convenience hook: given a measured width, returns an animated style that
72
+ * translates a highlight overlay (assumed ~60% of `width` wide) from off-screen
73
+ * left to off-screen right, looping. Use on an absolutely-positioned overlay
74
+ * View/LinearGradient layered on top of the base skeleton rect.
75
+ */
76
+ export function useShimmerTransform(width: number, options: ShimmerOptions) {
77
+ const progress = useShimmerProgress(options);
78
+ const sweepWidth = Math.max(width, 1) * 0.6;
79
+
80
+ const style = useAnimatedStyle(() => {
81
+ const translateX = -sweepWidth + progress.value * (width + sweepWidth);
82
+ return {
83
+ transform: [{ translateX }],
84
+ };
85
+ }, [width, sweepWidth]);
86
+
87
+ return { style, sweepWidth };
88
+ }
@@ -0,0 +1,74 @@
1
+ import { useEffect } from "react";
2
+ import {
3
+ Easing,
4
+ cancelAnimation,
5
+ interpolate,
6
+ useAnimatedStyle,
7
+ useSharedValue,
8
+ withDelay,
9
+ withRepeat,
10
+ withTiming,
11
+ } from "react-native-reanimated";
12
+ import type { SkeletonPerformanceMode } from "../types";
13
+
14
+ export interface WaveOptions {
15
+ duration?: number;
16
+ delay?: number;
17
+ disabled?: boolean;
18
+ performanceMode?: SkeletonPerformanceMode;
19
+ }
20
+
21
+ /**
22
+ * Similar to shimmer, but drives a smoother sine-like sweep (ease-in-out
23
+ * instead of linear) intended for a softer "ripple" look, typically paired
24
+ * with a wider, lower-contrast gradient than shimmer uses.
25
+ *
26
+ * Returns a translateX-based animated style for a given measured width.
27
+ */
28
+ export function useWaveTransform(width: number, options: WaveOptions) {
29
+ const { duration = 1500, delay = 0, disabled = false, performanceMode = "balanced" } = options;
30
+ const progress = useSharedValue(0);
31
+
32
+ useEffect(() => {
33
+ if (disabled) {
34
+ progress.value = 0;
35
+ return;
36
+ }
37
+
38
+ const effectiveDuration =
39
+ performanceMode === "battery-saver" ? duration * 1.4 : duration;
40
+
41
+ progress.value = 0;
42
+ progress.value = withDelay(
43
+ delay,
44
+ withRepeat(
45
+ withTiming(1, {
46
+ duration: effectiveDuration,
47
+ easing: Easing.inOut(Easing.sin),
48
+ }),
49
+ -1,
50
+ false
51
+ )
52
+ );
53
+
54
+ return () => {
55
+ cancelAnimation(progress);
56
+ };
57
+ // eslint-disable-next-line react-hooks/exhaustive-deps
58
+ }, [duration, delay, disabled, performanceMode]);
59
+
60
+ const sweepWidth = Math.max(width, 1) * 0.8;
61
+
62
+ const style = useAnimatedStyle(() => {
63
+ const translateX = interpolate(
64
+ progress.value,
65
+ [0, 1],
66
+ [-sweepWidth, width + sweepWidth]
67
+ );
68
+ return {
69
+ transform: [{ translateX }],
70
+ };
71
+ }, [width, sweepWidth]);
72
+
73
+ return { style, sweepWidth };
74
+ }
@@ -0,0 +1,82 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import Skeleton from "../components/Skeleton";
4
+ import type { SkeletonCloneNode, SkeletonCloneProps } from "../types";
5
+
6
+ function renderNode(node: SkeletonCloneNode, animation: SkeletonCloneProps["animation"], keyPrefix: string) {
7
+ const { shape = "rect", width, height, borderRadius, style, children, direction = "column", gap = 8 } = node;
8
+
9
+ let resolvedBorderRadius = borderRadius;
10
+ if (shape === "circle") {
11
+ const numericSize = typeof width === "number" ? width : typeof height === "number" ? height : 50;
12
+ resolvedBorderRadius = numericSize / 2;
13
+ } else if (shape === "pill") {
14
+ const numericHeight = typeof height === "number" ? height : 24;
15
+ resolvedBorderRadius = resolvedBorderRadius ?? numericHeight / 2;
16
+ }
17
+
18
+ if (children && children.length > 0) {
19
+ return (
20
+ <View
21
+ key={keyPrefix}
22
+ style={[
23
+ { flexDirection: direction === "row" ? "row" : "column" },
24
+ style,
25
+ ]}
26
+ >
27
+ {children.map((child, i) => (
28
+ <View
29
+ key={`${keyPrefix}-${i}`}
30
+ style={
31
+ direction === "row"
32
+ ? { marginLeft: i === 0 ? 0 : gap }
33
+ : { marginTop: i === 0 ? 0 : gap }
34
+ }
35
+ >
36
+ {renderNode(child, animation, `${keyPrefix}-${i}`)}
37
+ </View>
38
+ ))}
39
+ </View>
40
+ );
41
+ }
42
+
43
+ return (
44
+ <Skeleton
45
+ width={width ?? "100%"}
46
+ height={height ?? 16}
47
+ borderRadius={resolvedBorderRadius ?? 4}
48
+ animation={animation}
49
+ style={style}
50
+ />
51
+ );
52
+ }
53
+
54
+ /**
55
+ * Clones an arbitrary layout tree as skeleton placeholders. Useful when you
56
+ * have a complex, bespoke screen layout and don't want to hand-place a
57
+ * Skeleton for every element — instead describe the rough shape once as a
58
+ * `SkeletonCloneNode` tree (mirroring your real layout's structure) and let
59
+ * SkeletonClone render matching placeholders.
60
+ *
61
+ * @example
62
+ * <SkeletonClone
63
+ * layout={{
64
+ * direction: "row",
65
+ * gap: 12,
66
+ * children: [
67
+ * { shape: "circle", width: 48, height: 48 },
68
+ * {
69
+ * direction: "column",
70
+ * gap: 6,
71
+ * children: [
72
+ * { width: 120, height: 14 },
73
+ * { width: 80, height: 12 },
74
+ * ],
75
+ * },
76
+ * ],
77
+ * }}
78
+ * />
79
+ */
80
+ export default function SkeletonClone({ layout, animation, style, testID }: SkeletonCloneProps) {
81
+ return <View style={style} testID={testID}>{renderNode(layout, animation, "clone-root")}</View>;
82
+ }
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import Skeleton from "./Skeleton";
3
+ import type { SkeletonCircleProps } from "../types";
4
+
5
+ /**
6
+ * A circular skeleton placeholder, commonly used for avatars.
7
+ *
8
+ * @example
9
+ * <Skeleton.Circle size={48} />
10
+ */
11
+ export default function Circle({ size = 50, ...props }: SkeletonCircleProps) {
12
+ return <Skeleton width={size} height={size} borderRadius={size / 2} {...props} />;
13
+ }
@@ -0,0 +1,55 @@
1
+ import React, { Children, isValidElement, cloneElement } from "react";
2
+ import { View } from "react-native";
3
+ import type { SkeletonGroupProps } from "../types";
4
+
5
+ /**
6
+ * Groups multiple skeleton children together and automatically staggers
7
+ * their animation start so they don't all pulse/shimmer perfectly in sync —
8
+ * a subtle touch that reads as more natural for lists of skeleton rows.
9
+ *
10
+ * Any direct child that accepts a `delay` prop (i.e. any Skeleton.* shape,
11
+ * or a custom component that forwards `delay` to one) will have an
12
+ * incremental delay injected automatically. Children that already specify
13
+ * their own `delay` are left untouched.
14
+ *
15
+ * @example
16
+ * <Skeleton.Group staggerDelay={80}>
17
+ * <Skeleton.Rect width="100%" height={16} />
18
+ * <Skeleton.Rect width="80%" height={16} />
19
+ * <Skeleton.Rect width="60%" height={16} />
20
+ * </Skeleton.Group>
21
+ */
22
+ export default function Group({
23
+ children,
24
+ staggerDelay = 80,
25
+ animation,
26
+ direction = "column",
27
+ style,
28
+ testID,
29
+ }: SkeletonGroupProps) {
30
+ const items = Children.toArray(children);
31
+
32
+ return (
33
+ <View
34
+ style={[{ flexDirection: direction === "row" ? "row" : "column" }, style]}
35
+ testID={testID}
36
+ >
37
+ {items.map((child, index) => {
38
+ if (!isValidElement(child)) return child;
39
+
40
+ const existingProps = child.props as Record<string, unknown>;
41
+ const injectedProps: Record<string, unknown> = {};
42
+
43
+ if (existingProps.delay === undefined) {
44
+ injectedProps.delay = index * staggerDelay;
45
+ }
46
+
47
+ if (animation && existingProps.animation === undefined) {
48
+ injectedProps.animation = animation;
49
+ }
50
+
51
+ return cloneElement(child, { key: child.key ?? index, ...injectedProps });
52
+ })}
53
+ </View>
54
+ );
55
+ }
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+ import Skeleton from "./Skeleton";
3
+ import type { SkeletonPillProps } from "../types";
4
+
5
+ /**
6
+ * A fully-rounded "pill" skeleton placeholder, commonly used for buttons,
7
+ * tags/chips, or badges. Border radius is automatically derived from height
8
+ * unless overridden.
9
+ *
10
+ * @example
11
+ * <Skeleton.Pill width={80} height={28} />
12
+ */
13
+ export default function Pill({ width = 80, height = 24, borderRadius, ...props }: SkeletonPillProps) {
14
+ const numericHeight = typeof height === "number" ? height : 24;
15
+ return (
16
+ <Skeleton
17
+ width={width}
18
+ height={height}
19
+ borderRadius={borderRadius ?? numericHeight / 2}
20
+ {...props}
21
+ />
22
+ );
23
+ }
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import Skeleton from "./Skeleton";
3
+ import type { SkeletonProps } from "../types";
4
+
5
+ /**
6
+ * A rectangular skeleton placeholder. Equivalent to `<Skeleton />` directly,
7
+ * provided for a consistent, explicit naming convention alongside
8
+ * `Skeleton.Circle`, `Skeleton.Square`, and `Skeleton.Pill`.
9
+ *
10
+ * @example
11
+ * <Skeleton.Rect width={200} height={20} />
12
+ */
13
+ export default function Rect(props: SkeletonProps) {
14
+ return <Skeleton {...props} />;
15
+ }