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,218 @@
1
+ import React, { useCallback, useContext, useState } from "react";
2
+ import {
3
+ I18nManager,
4
+ View,
5
+ type LayoutChangeEvent,
6
+ type StyleProp,
7
+ type ViewStyle,
8
+ } from "react-native";
9
+ import Animated from "react-native-reanimated";
10
+
11
+ import { SkeletonContext } from "../providers/SkeletonProvider";
12
+ import { useSkeletonTheme } from "../hooks/useSkeletonTheme";
13
+ import { useReducedMotion } from "../hooks/useReducedMotion";
14
+ import { useShimmerTransform } from "../animations/shimmer";
15
+ import { usePulseStyle } from "../animations/pulse";
16
+ import { useWaveTransform } from "../animations/wave";
17
+ import { useBreathingStyle } from "../animations/breathing";
18
+ import { buildSweepGradient } from "../utils/colors";
19
+ import type { SkeletonAnimation, SkeletonProps } from "../types";
20
+
21
+ const LinearGradientComponent = (() => {
22
+ try {
23
+ return require("expo-linear-gradient").LinearGradient as React.ComponentType<any>;
24
+ } catch {
25
+ try {
26
+ return require("react-native-linear-gradient").default as React.ComponentType<any>;
27
+ } catch {
28
+ return View as unknown as React.ComponentType<any>;
29
+ }
30
+ }
31
+ })();
32
+
33
+ const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradientComponent);
34
+
35
+ /**
36
+ * The base building block of react-native-smart-skeleton. Renders a single
37
+ * rectangular (optionally rounded) placeholder shape with the requested
38
+ * animation, fully theme-aware and accessible.
39
+ *
40
+ * Most consumers will reach for `Skeleton.Rect`, `Skeleton.Circle`,
41
+ * `Skeleton.Square`, or `Skeleton.Pill` instead of this directly, but those
42
+ * are all thin wrappers around this component.
43
+ *
44
+ * @example
45
+ * <Skeleton width={200} height={20} animation="shimmer" />
46
+ */
47
+ export default function Skeleton({
48
+ width = "100%",
49
+ height = 16,
50
+ borderRadius = 4,
51
+ backgroundColor,
52
+ highlightColor,
53
+ gradientColors,
54
+ animation,
55
+ duration,
56
+ delay = 0,
57
+ rtl,
58
+ colorVariant,
59
+ style,
60
+ accessibilityLabel = "Loading content",
61
+ accessible = true,
62
+ testID,
63
+ }: SkeletonProps) {
64
+ const ctx = useContext(SkeletonContext);
65
+ const themeColors = useSkeletonTheme(colorVariant);
66
+ const osReducedMotion = useReducedMotion();
67
+
68
+ const [measuredWidth, setMeasuredWidth] = useState(0);
69
+
70
+ const onLayout = useCallback((e: LayoutChangeEvent) => {
71
+ setMeasuredWidth(e.nativeEvent.layout.width);
72
+ }, []);
73
+
74
+ // Resolve effective settings, falling back through: prop -> provider -> library default.
75
+ const resolvedAnimation: SkeletonAnimation =
76
+ animation ?? ctx?.animation ?? "shimmer";
77
+ const resolvedDuration = duration ?? ctx?.duration ?? 1200;
78
+ const resolvedPerformanceMode = ctx?.performanceMode ?? "balanced";
79
+ const reducedMotion = ctx?.reducedMotion ?? osReducedMotion;
80
+ const resolvedRtl = rtl ?? ctx?.rtl ?? I18nManager.isRTL;
81
+
82
+ const bg = backgroundColor ?? themeColors.background;
83
+ const highlight = highlightColor ?? themeColors.highlight;
84
+ const colors = gradientColors ?? buildSweepGradient(bg, highlight);
85
+
86
+ // Reduced motion: never run shimmer/wave/pulse at full intensity. Fall back to a
87
+ // slow "breathing" effect unless the consumer explicitly asked for "none".
88
+ const effectiveAnimation: SkeletonAnimation =
89
+ reducedMotion && resolvedAnimation !== "none" ? "breathing" : resolvedAnimation;
90
+
91
+ const disabled = effectiveAnimation === "none";
92
+
93
+ const shimmer = useShimmerTransform(measuredWidth, {
94
+ duration: resolvedDuration,
95
+ delay,
96
+ disabled: disabled || effectiveAnimation !== "shimmer",
97
+ performanceMode: resolvedPerformanceMode,
98
+ });
99
+
100
+ const wave = useWaveTransform(measuredWidth, {
101
+ duration: resolvedDuration,
102
+ delay,
103
+ disabled: disabled || effectiveAnimation !== "wave",
104
+ performanceMode: resolvedPerformanceMode,
105
+ });
106
+
107
+ const pulseStyle = usePulseStyle({
108
+ duration: resolvedDuration,
109
+ delay,
110
+ disabled: disabled || effectiveAnimation !== "pulse",
111
+ performanceMode: resolvedPerformanceMode,
112
+ });
113
+
114
+ const breathingStyle = useBreathingStyle({
115
+ duration: reducedMotion ? resolvedDuration * 1.6 : resolvedDuration,
116
+ delay,
117
+ disabled: disabled || effectiveAnimation !== "breathing",
118
+ performanceMode: resolvedPerformanceMode,
119
+ // Reduced-motion breathing should be extra subtle.
120
+ minScale: reducedMotion ? 0.99 : 0.97,
121
+ minOpacity: reducedMotion ? 0.92 : 0.85,
122
+ });
123
+
124
+ const containerStyle: StyleProp<ViewStyle> = [
125
+ {
126
+ width,
127
+ height,
128
+ borderRadius,
129
+ overflow: "hidden" as const,
130
+ backgroundColor: bg,
131
+ } as ViewStyle,
132
+ style,
133
+ ];
134
+ const renderOverlay = () => {
135
+ if (disabled) return null;
136
+
137
+ if (effectiveAnimation === "pulse") {
138
+ return (
139
+ <Animated.View
140
+ pointerEvents="none"
141
+ style={[
142
+ {
143
+ position: "absolute",
144
+ top: 0,
145
+ right: 0,
146
+ bottom: 0,
147
+ left: 0,
148
+ backgroundColor: highlight,
149
+ },
150
+ pulseStyle,
151
+ ]}
152
+ />
153
+ );
154
+ }
155
+
156
+ if (effectiveAnimation === "breathing") {
157
+ return (
158
+ <Animated.View
159
+ pointerEvents="none"
160
+ style={[
161
+ {
162
+ position: "absolute",
163
+ top: 0,
164
+ right: 0,
165
+ bottom: 0,
166
+ left: 0,
167
+ backgroundColor: highlight,
168
+ },
169
+ breathingStyle,
170
+ ]}
171
+ />
172
+ );
173
+ }
174
+
175
+ if (effectiveAnimation === "shimmer" || effectiveAnimation === "wave") {
176
+ const transform = effectiveAnimation === "shimmer" ? shimmer : wave;
177
+ const flip = resolvedRtl ? { transform: [{ scaleX: -1 }] } : null;
178
+
179
+ return (
180
+ <Animated.View
181
+ pointerEvents="none"
182
+ style={[
183
+ { position: "absolute", top: 0, bottom: 0, left: 0 },
184
+ { width: transform.sweepWidth },
185
+ transform.style,
186
+ flip,
187
+ ]}
188
+ >
189
+ <AnimatedLinearGradient
190
+ colors={colors as any}
191
+ start={{ x: 0, y: 0 }}
192
+ end={{ x: 1, y: 0 }}
193
+ style={{ flex: 1 }}
194
+ />
195
+ </Animated.View>
196
+ );
197
+ }
198
+
199
+ return null;
200
+ };
201
+
202
+ return (
203
+ <View
204
+ onLayout={onLayout}
205
+ style={containerStyle}
206
+ accessible={accessible}
207
+ accessibilityRole={accessible ? "image" : undefined}
208
+ accessibilityLabel={accessible ? accessibilityLabel : undefined}
209
+ accessibilityState={accessible ? { busy: true } : undefined}
210
+ // importantForAccessibility keeps these decorative-but-informative
211
+ // nodes from over-announcing on Android when nested in lists.
212
+ importantForAccessibility={accessible ? "yes" : "no-hide-descendants"}
213
+ testID={testID}
214
+ >
215
+ {renderOverlay()}
216
+ </View>
217
+ );
218
+ }
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import Skeleton from "./Skeleton";
3
+ import type { SkeletonSquareProps } from "../types";
4
+
5
+ /**
6
+ * A square skeleton placeholder with equal width and height.
7
+ *
8
+ * @example
9
+ * <Skeleton.Square size={64} borderRadius={8} />
10
+ */
11
+ export default function Square({ size = 50, borderRadius = 4, ...props }: SkeletonSquareProps) {
12
+ return <Skeleton width={size} height={size} borderRadius={borderRadius} {...props} />;
13
+ }
Binary file
@@ -0,0 +1,46 @@
1
+ import { useEffect, useState } from "react";
2
+ import { AccessibilityInfo } from "react-native";
3
+
4
+ /**
5
+ * Tracks the OS-level "reduce motion" accessibility setting
6
+ * (Reduce Motion on iOS, Remove Animations on Android) and keeps it
7
+ * up to date if the user changes the setting while the app is running.
8
+ *
9
+ * Skeleton animations should fall back to a static or near-static
10
+ * appearance (or the "breathing" animation at a slow, low-amplitude
11
+ * rate) when this returns true.
12
+ */
13
+ export function useReducedMotion(): boolean {
14
+ const [reducedMotion, setReducedMotion] = useState(false);
15
+
16
+ useEffect(() => {
17
+ let isMounted = true;
18
+
19
+ AccessibilityInfo.isReduceMotionEnabled?.()
20
+ .then((enabled) => {
21
+ if (isMounted) setReducedMotion(!!enabled);
22
+ })
23
+ .catch(() => {
24
+ // Some platforms/environments (e.g. older RN, web shims) may not
25
+ // implement this — default to false (animations enabled).
26
+ });
27
+
28
+ const subscription = AccessibilityInfo.addEventListener(
29
+ "reduceMotionChanged",
30
+ (enabled: boolean) => {
31
+ setReducedMotion(!!enabled);
32
+ }
33
+ );
34
+
35
+ return () => {
36
+ isMounted = false;
37
+ // RN >= 0.65 returns an EmitterSubscription with `.remove()`.
38
+ // Older versions returned void and relied on removeEventListener.
39
+ if (subscription && typeof (subscription as any).remove === "function") {
40
+ (subscription as any).remove();
41
+ }
42
+ };
43
+ }, []);
44
+
45
+ return reducedMotion;
46
+ }
@@ -0,0 +1,45 @@
1
+ import { useContext, useMemo } from "react";
2
+ import { useColorScheme } from "react-native";
3
+ import { SkeletonContext } from "../providers/SkeletonProvider";
4
+ import { getDefaultThemeColors } from "../utils/theme";
5
+ import { resolveThemeColors } from "../utils/colors";
6
+ import type { ResolvedSkeletonTheme } from "../types";
7
+
8
+ /**
9
+ * Resolves the active light/dark colors for a skeleton.
10
+ *
11
+ * If used inside a <SkeletonProvider>, honors its `theme` prop ("light" | "dark" | "system")
12
+ * and any registered brand `colors`. If used standalone (no provider), falls back to the
13
+ * device's OS color scheme and the library's default palette.
14
+ *
15
+ * @param colorVariant optional brand color variant name registered on the provider
16
+ */
17
+ export function useSkeletonTheme(colorVariant?: string): ResolvedSkeletonTheme {
18
+ const ctx = useContext(SkeletonContext);
19
+ const systemScheme = useColorScheme();
20
+
21
+ return useMemo(() => {
22
+ if (ctx) {
23
+ const colors = ctx.getVariantColors(colorVariant);
24
+ return {
25
+ isDark: ctx.isDark,
26
+ background: colors.background,
27
+ highlight: colors.highlight,
28
+ };
29
+ }
30
+
31
+ const isDark = systemScheme === "dark";
32
+ const fallback = getDefaultThemeColors(isDark);
33
+
34
+ const colors = resolveThemeColors({
35
+ isDark,
36
+ ambient: fallback,
37
+ });
38
+
39
+ return {
40
+ isDark,
41
+ background: colors.background,
42
+ highlight: colors.highlight,
43
+ };
44
+ }, [ctx, systemScheme, colorVariant]);
45
+ }
package/src/index.ts ADDED
@@ -0,0 +1,94 @@
1
+ import SkeletonBase from "./components/Skeleton";
2
+ import Rect from "./components/Rect";
3
+ import Circle from "./components/Circle";
4
+ import Square from "./components/Square";
5
+ import Pill from "./components/Pill";
6
+ import Group from "./components/Group";
7
+
8
+ import Profile from "./presets/Profile";
9
+ import Card from "./presets/Card";
10
+ import Chat from "./presets/Chat";
11
+ import Product from "./presets/Product";
12
+ import Video from "./presets/Video";
13
+
14
+ import SkeletonClone from "./clone/SkeletonClone";
15
+
16
+ export { SkeletonProvider, SkeletonContext } from "./providers/SkeletonProvider";
17
+ export { useSkeletonTheme } from "./hooks/useSkeletonTheme";
18
+ export { useReducedMotion } from "./hooks/useReducedMotion";
19
+
20
+ export type {
21
+ SkeletonAnimation,
22
+ SkeletonPerformanceMode,
23
+ SkeletonThemeMode,
24
+ SkeletonThemeColors,
25
+ SkeletonBrandColors,
26
+ ResolvedSkeletonTheme,
27
+ SkeletonProps,
28
+ SkeletonCircleProps,
29
+ SkeletonSquareProps,
30
+ SkeletonPillProps,
31
+ SkeletonGroupProps,
32
+ SkeletonContextValue,
33
+ SkeletonProviderProps,
34
+ SkeletonCloneShape,
35
+ SkeletonCloneNode,
36
+ SkeletonCloneProps,
37
+ SkeletonPresetProps,
38
+ SkeletonProfilePresetProps,
39
+ SkeletonCardPresetProps,
40
+ SkeletonChatPresetProps,
41
+ SkeletonProductPresetProps,
42
+ SkeletonVideoPresetProps,
43
+ ScreenSizeClass,
44
+ } from "./types";
45
+
46
+ /**
47
+ * `Skeleton` is the library's primary export: a callable base component
48
+ * (`<Skeleton />`) that also carries every shape, the layout-grouping
49
+ * helper, and every smart preset as static properties — so you only ever
50
+ * need a single import.
51
+ *
52
+ * @example
53
+ * import { Skeleton, SkeletonProvider } from "react-native-smart-skeleton";
54
+ *
55
+ * <SkeletonProvider>
56
+ * <Skeleton.Rect width={200} height={20} />
57
+ * <Skeleton.Circle size={48} />
58
+ * <Skeleton.Profile lines={2} />
59
+ * </SkeletonProvider>
60
+ */
61
+ type SkeletonComponent = typeof SkeletonBase & {
62
+ Rect: typeof Rect;
63
+ Circle: typeof Circle;
64
+ Square: typeof Square;
65
+ Pill: typeof Pill;
66
+ Group: typeof Group;
67
+ Clone: typeof SkeletonClone;
68
+ Profile: typeof Profile;
69
+ Card: typeof Card;
70
+ Chat: typeof Chat;
71
+ Product: typeof Product;
72
+ Video: typeof Video;
73
+ };
74
+
75
+ const Skeleton = SkeletonBase as SkeletonComponent;
76
+
77
+ Skeleton.Rect = Rect;
78
+ Skeleton.Circle = Circle;
79
+ Skeleton.Square = Square;
80
+ Skeleton.Pill = Pill;
81
+ Skeleton.Group = Group;
82
+ Skeleton.Clone = SkeletonClone;
83
+ Skeleton.Profile = Profile;
84
+ Skeleton.Card = Card;
85
+ Skeleton.Chat = Chat;
86
+ Skeleton.Product = Product;
87
+ Skeleton.Video = Video;
88
+
89
+ export { Skeleton };
90
+ export { SkeletonBase };
91
+ export { Rect, Circle, Square, Pill, Group, SkeletonClone };
92
+ export { Profile, Card, Chat, Product, Video };
93
+
94
+ export default Skeleton;
@@ -0,0 +1,56 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import Skeleton from "../components/Skeleton";
4
+ import Circle from "../components/Circle";
5
+ import Group from "../components/Group";
6
+ import type { SkeletonCardPresetProps, SkeletonProps } from "../types";
7
+
8
+ /**
9
+ * A ready-made skeleton block mimicking a typical content card: an image
10
+ * area on top, optionally a small avatar + name row, then body text lines.
11
+ *
12
+ * @example
13
+ * <Skeleton.Card imageHeight={160} lines={3} />
14
+ */
15
+ export default function Card({
16
+ imageHeight = 160,
17
+ lines = 3,
18
+ showAvatar = false,
19
+ count = 1,
20
+ gap = 20,
21
+ animation,
22
+ style,
23
+ testID,
24
+ }: SkeletonCardPresetProps) {
25
+ return (
26
+ <View style={style} testID={testID}>
27
+ {Array.from({ length: count }).map((_, blockIndex) => (
28
+ <View key={blockIndex} style={{ marginTop: blockIndex === 0 ? 0 : gap }}>
29
+ <Skeleton width="100%" height={imageHeight} borderRadius={12} animation={animation} />
30
+
31
+ {showAvatar && (
32
+ <View style={{ flexDirection: "row", alignItems: "center", marginTop: 12 }}>
33
+ <Circle size={32} animation={animation} />
34
+ <Skeleton width="40%" height={12} style={{ marginLeft: 8 }} animation={animation} />
35
+ </View>
36
+ )}
37
+
38
+ <Group
39
+ staggerDelay={60}
40
+ animation={animation}
41
+ style={{ marginTop: showAvatar ? 12 : 16 }}
42
+ >
43
+ {Array.from({ length: lines }, (__, lineIndex) => {
44
+ const lineProps: SkeletonProps = {
45
+ width: lineIndex === lines - 1 ? "50%" : "100%",
46
+ height: 12,
47
+ style: { marginTop: lineIndex === 0 ? 0 : 8 },
48
+ };
49
+ return <Skeleton key={lineIndex} {...lineProps} />;
50
+ })}
51
+ </Group>
52
+ </View>
53
+ ))}
54
+ </View>
55
+ );
56
+ }
@@ -0,0 +1,52 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import Skeleton from "../components/Skeleton";
4
+ import type { SkeletonChatPresetProps } from "../types";
5
+
6
+ const BUBBLE_WIDTHS = ["55%", "70%", "45%", "62%"];
7
+
8
+ /**
9
+ * A ready-made skeleton block mimicking a chat conversation: a series of
10
+ * message bubbles, alternating left/right alignment by default to read as
11
+ * a two-party conversation while loading.
12
+ *
13
+ * @example
14
+ * <Skeleton.Chat messages={5} />
15
+ */
16
+ export default function Chat({
17
+ messages = 4,
18
+ alternate = true,
19
+ gap = 12,
20
+ animation,
21
+ style,
22
+ testID,
23
+ }: SkeletonChatPresetProps) {
24
+ return (
25
+ <View style={style} testID={testID}>
26
+ {Array.from({ length: messages }).map((_, index) => {
27
+ const isRight = alternate && index % 2 === 1;
28
+ const width = BUBBLE_WIDTHS[index % BUBBLE_WIDTHS.length];
29
+ const height = 18 + ((index % 3) * 6);
30
+
31
+ return (
32
+ <View
33
+ key={index}
34
+ style={{
35
+ flexDirection: "row",
36
+ justifyContent: isRight ? "flex-end" : "flex-start",
37
+ marginTop: index === 0 ? 0 : gap,
38
+ }}
39
+ >
40
+ <Skeleton
41
+ width={width}
42
+ height={height}
43
+ borderRadius={14}
44
+ animation={animation}
45
+ delay={index * 70}
46
+ />
47
+ </View>
48
+ );
49
+ })}
50
+ </View>
51
+ );
52
+ }
@@ -0,0 +1,45 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import Skeleton from "../components/Skeleton";
4
+ import { getRecommendedColumns } from "../utils/responsive";
5
+ import type { SkeletonProductPresetProps } from "../types";
6
+
7
+ /**
8
+ * A ready-made skeleton grid mimicking a product listing (e.g. e-commerce
9
+ * catalog or search results): square-ish image, title line, and price line,
10
+ * tiled into a responsive grid.
11
+ *
12
+ * @example
13
+ * <Skeleton.Product count={6} columns={2} />
14
+ */
15
+ export default function Product({
16
+ imageHeight = 140,
17
+ columns,
18
+ count = 4,
19
+ gap = 16,
20
+ animation,
21
+ style,
22
+ testID,
23
+ }: SkeletonProductPresetProps) {
24
+ const resolvedColumns = columns ?? getRecommendedColumns();
25
+ const itemWidth = `${100 / resolvedColumns}%`;
26
+
27
+ return (
28
+ <View style={[{ flexDirection: "row", flexWrap: "wrap" }, style]} testID={testID}>
29
+ {Array.from({ length: count }).map((_, index) => (
30
+ <View
31
+ key={index}
32
+ style={{
33
+ width: itemWidth as any,
34
+ paddingHorizontal: gap / 2,
35
+ marginBottom: gap,
36
+ }}
37
+ >
38
+ <Skeleton width="100%" height={imageHeight} borderRadius={10} animation={animation} delay={index * 40} />
39
+ <Skeleton width="80%" height={12} style={{ marginTop: 8 }} animation={animation} delay={index * 40} />
40
+ <Skeleton width="40%" height={12} style={{ marginTop: 6 }} animation={animation} delay={index * 40} />
41
+ </View>
42
+ ))}
43
+ </View>
44
+ );
45
+ }
@@ -0,0 +1,54 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import Skeleton from "../components/Skeleton";
4
+ import Circle from "../components/Circle";
5
+ import Group from "../components/Group";
6
+ import type { SkeletonProfilePresetProps, SkeletonProps } from "../types";
7
+
8
+ /**
9
+ * A ready-made skeleton block mimicking a profile header: a circular avatar
10
+ * beside a name line and one or more secondary text lines (e.g. handle/bio).
11
+ *
12
+ * @example
13
+ * <Skeleton.Profile avatarSize={56} lines={2} />
14
+ */
15
+ export default function Profile({
16
+ avatarSize = 56,
17
+ lines = 2,
18
+ count = 1,
19
+ gap = 16,
20
+ animation,
21
+ style,
22
+ testID,
23
+ }: SkeletonProfilePresetProps) {
24
+ return (
25
+ <View style={style} testID={testID}>
26
+ {Array.from({ length: count }).map((_, blockIndex) => (
27
+ <View
28
+ key={blockIndex}
29
+ style={{
30
+ flexDirection: "row",
31
+ alignItems: "center",
32
+ marginTop: blockIndex === 0 ? 0 : gap,
33
+ }}
34
+ >
35
+ <Circle size={avatarSize} animation={animation} />
36
+ <Group
37
+ staggerDelay={60}
38
+ animation={animation}
39
+ style={{ marginLeft: 12, flex: 1 }}
40
+ >
41
+ {Array.from({ length: lines }, (__, lineIndex) => {
42
+ const lineProps: SkeletonProps = {
43
+ width: lineIndex === 0 ? "60%" : `${85 - lineIndex * 15}%`,
44
+ height: lineIndex === 0 ? 16 : 12,
45
+ style: { marginTop: lineIndex === 0 ? 0 : 8 },
46
+ };
47
+ return <Skeleton key={lineIndex} {...lineProps} />;
48
+ })}
49
+ </Group>
50
+ </View>
51
+ ))}
52
+ </View>
53
+ );
54
+ }
@@ -0,0 +1,53 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import Skeleton from "../components/Skeleton";
4
+ import Circle from "../components/Circle";
5
+ import type { SkeletonVideoPresetProps } from "../types";
6
+
7
+ /**
8
+ * A ready-made skeleton block mimicking a video list item: a video
9
+ * thumbnail at a given aspect ratio, optionally followed by a channel
10
+ * avatar + title row and a meta line (views / date).
11
+ *
12
+ * @example
13
+ * <Skeleton.Video aspectRatio={16 / 9} showTitle showMeta />
14
+ */
15
+ export default function Video({
16
+ aspectRatio = 16 / 9,
17
+ showTitle = true,
18
+ showMeta = true,
19
+ count = 1,
20
+ gap = 20,
21
+ animation,
22
+ style,
23
+ testID,
24
+ }: SkeletonVideoPresetProps) {
25
+ return (
26
+ <View style={style} testID={testID}>
27
+ {Array.from({ length: count }).map((_, index) => (
28
+ <View key={index} style={{ marginTop: index === 0 ? 0 : gap }}>
29
+ <View style={{ width: "100%", aspectRatio }}>
30
+ <Skeleton width="100%" height="100%" borderRadius={10} animation={animation} />
31
+ </View>
32
+
33
+ {(showTitle || showMeta) && (
34
+ <View style={{ flexDirection: "row", marginTop: 10 }}>
35
+ <Circle size={36} animation={animation} />
36
+ <View style={{ marginLeft: 10, flex: 1 }}>
37
+ {showTitle && <Skeleton width="90%" height={14} animation={animation} />}
38
+ {showMeta && (
39
+ <Skeleton
40
+ width="50%"
41
+ height={11}
42
+ style={{ marginTop: 6 }}
43
+ animation={animation}
44
+ />
45
+ )}
46
+ </View>
47
+ </View>
48
+ )}
49
+ </View>
50
+ ))}
51
+ </View>
52
+ );
53
+ }