react-native-skelo 0.0.1 → 0.0.3

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 (269) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -20
  3. package/lib/commonjs/Skelo.js +104 -0
  4. package/lib/commonjs/Skelo.js.map +1 -0
  5. package/lib/commonjs/animations/AnimationWrapper.js +76 -0
  6. package/lib/commonjs/animations/AnimationWrapper.js.map +1 -0
  7. package/lib/commonjs/animations/PulseAnimation.js +52 -0
  8. package/lib/commonjs/animations/PulseAnimation.js.map +1 -0
  9. package/lib/commonjs/animations/ShimmerAnimation.js +73 -0
  10. package/lib/commonjs/animations/ShimmerAnimation.js.map +1 -0
  11. package/lib/commonjs/animations/index.js +27 -0
  12. package/lib/commonjs/animations/index.js.map +1 -0
  13. package/lib/commonjs/components/MeasuredBlock.js +64 -0
  14. package/lib/commonjs/components/MeasuredBlock.js.map +1 -0
  15. package/lib/commonjs/components/Skeleton.js +144 -0
  16. package/lib/commonjs/components/Skeleton.js.map +1 -0
  17. package/lib/commonjs/components/SkeletonIgnore.js +30 -0
  18. package/lib/commonjs/components/SkeletonIgnore.js.map +1 -0
  19. package/lib/commonjs/components/SkeletonRenderer.js +270 -0
  20. package/lib/commonjs/components/SkeletonRenderer.js.map +1 -0
  21. package/lib/commonjs/components/primitives/SkeletonBox.js +47 -0
  22. package/lib/commonjs/components/primitives/SkeletonBox.js.map +1 -0
  23. package/lib/commonjs/components/primitives/SkeletonCircle.js +44 -0
  24. package/lib/commonjs/components/primitives/SkeletonCircle.js.map +1 -0
  25. package/lib/commonjs/components/primitives/SkeletonImage.js +46 -0
  26. package/lib/commonjs/components/primitives/SkeletonImage.js.map +1 -0
  27. package/lib/commonjs/components/primitives/SkeletonText.js +60 -0
  28. package/lib/commonjs/components/primitives/SkeletonText.js.map +1 -0
  29. package/lib/commonjs/components/primitives/index.js +34 -0
  30. package/lib/commonjs/components/primitives/index.js.map +1 -0
  31. package/lib/commonjs/components/withSkeleton.js +45 -0
  32. package/lib/commonjs/components/withSkeleton.js.map +1 -0
  33. package/lib/commonjs/constants/defaults.js +43 -0
  34. package/lib/commonjs/constants/defaults.js.map +1 -0
  35. package/lib/commonjs/core/deepRegistry.js +18 -0
  36. package/lib/commonjs/core/deepRegistry.js.map +1 -0
  37. package/lib/commonjs/core/generator/StyleSkeleton.js +101 -0
  38. package/lib/commonjs/core/generator/StyleSkeleton.js.map +1 -0
  39. package/lib/commonjs/core/parser/Parser.js +84 -0
  40. package/lib/commonjs/core/parser/Parser.js.map +1 -0
  41. package/lib/commonjs/core/parser/expandOffline.js +83 -0
  42. package/lib/commonjs/core/parser/expandOffline.js.map +1 -0
  43. package/lib/commonjs/core/parser/hostTreeReconciler.js +228 -0
  44. package/lib/commonjs/core/parser/hostTreeReconciler.js.map +1 -0
  45. package/lib/commonjs/core/parser/index.js +13 -0
  46. package/lib/commonjs/core/parser/index.js.map +1 -0
  47. package/lib/commonjs/core/registry/SkeletonRegistry.js +113 -0
  48. package/lib/commonjs/core/registry/SkeletonRegistry.js.map +1 -0
  49. package/lib/commonjs/core/registry/index.js +19 -0
  50. package/lib/commonjs/core/registry/index.js.map +1 -0
  51. package/lib/commonjs/core/resolver/StyleResolver.js +83 -0
  52. package/lib/commonjs/core/resolver/StyleResolver.js.map +1 -0
  53. package/lib/commonjs/core/resolver/index.js +13 -0
  54. package/lib/commonjs/core/resolver/index.js.map +1 -0
  55. package/lib/commonjs/deep.js +18 -0
  56. package/lib/commonjs/deep.js.map +1 -0
  57. package/lib/commonjs/index.js +66 -0
  58. package/lib/commonjs/index.js.map +1 -0
  59. package/lib/commonjs/plugins/ImagePlugin.js +60 -0
  60. package/lib/commonjs/plugins/ImagePlugin.js.map +1 -0
  61. package/lib/commonjs/plugins/TextPlugin.js +70 -0
  62. package/lib/commonjs/plugins/TextPlugin.js.map +1 -0
  63. package/lib/commonjs/plugins/ViewPlugin.js +71 -0
  64. package/lib/commonjs/plugins/ViewPlugin.js.map +1 -0
  65. package/lib/commonjs/plugins/index.js +27 -0
  66. package/lib/commonjs/plugins/index.js.map +1 -0
  67. package/lib/commonjs/types/index.js +2 -0
  68. package/lib/commonjs/types/index.js.map +1 -0
  69. package/lib/commonjs/types/plugin.js +6 -0
  70. package/lib/commonjs/types/plugin.js.map +1 -0
  71. package/lib/commonjs/types/skeleton.js +6 -0
  72. package/lib/commonjs/types/skeleton.js.map +1 -0
  73. package/lib/commonjs/utils/componentUtils.js +138 -0
  74. package/lib/commonjs/utils/componentUtils.js.map +1 -0
  75. package/lib/commonjs/utils/index.js +62 -0
  76. package/lib/commonjs/utils/index.js.map +1 -0
  77. package/lib/commonjs/utils/styleUtils.js +76 -0
  78. package/lib/commonjs/utils/styleUtils.js.map +1 -0
  79. package/lib/module/Skelo.js +98 -0
  80. package/lib/module/Skelo.js.map +1 -0
  81. package/lib/module/animations/AnimationWrapper.js +69 -0
  82. package/lib/module/animations/AnimationWrapper.js.map +1 -0
  83. package/lib/module/animations/PulseAnimation.js +45 -0
  84. package/lib/module/animations/PulseAnimation.js.map +1 -0
  85. package/lib/module/animations/ShimmerAnimation.js +66 -0
  86. package/lib/module/animations/ShimmerAnimation.js.map +1 -0
  87. package/lib/module/animations/index.js +4 -0
  88. package/lib/module/animations/index.js.map +1 -0
  89. package/lib/module/components/MeasuredBlock.js +57 -0
  90. package/lib/module/components/MeasuredBlock.js.map +1 -0
  91. package/lib/module/components/Skeleton.js +138 -0
  92. package/lib/module/components/Skeleton.js.map +1 -0
  93. package/lib/module/components/SkeletonIgnore.js +23 -0
  94. package/lib/module/components/SkeletonIgnore.js.map +1 -0
  95. package/lib/module/components/SkeletonRenderer.js +264 -0
  96. package/lib/module/components/SkeletonRenderer.js.map +1 -0
  97. package/lib/module/components/primitives/SkeletonBox.js +40 -0
  98. package/lib/module/components/primitives/SkeletonBox.js.map +1 -0
  99. package/lib/module/components/primitives/SkeletonCircle.js +37 -0
  100. package/lib/module/components/primitives/SkeletonCircle.js.map +1 -0
  101. package/lib/module/components/primitives/SkeletonImage.js +39 -0
  102. package/lib/module/components/primitives/SkeletonImage.js.map +1 -0
  103. package/lib/module/components/primitives/SkeletonText.js +53 -0
  104. package/lib/module/components/primitives/SkeletonText.js.map +1 -0
  105. package/lib/module/components/primitives/index.js +5 -0
  106. package/lib/module/components/primitives/index.js.map +1 -0
  107. package/lib/module/components/withSkeleton.js +38 -0
  108. package/lib/module/components/withSkeleton.js.map +1 -0
  109. package/lib/module/constants/defaults.js +37 -0
  110. package/lib/module/constants/defaults.js.map +1 -0
  111. package/lib/module/core/deepRegistry.js +11 -0
  112. package/lib/module/core/deepRegistry.js.map +1 -0
  113. package/lib/module/core/generator/StyleSkeleton.js +95 -0
  114. package/lib/module/core/generator/StyleSkeleton.js.map +1 -0
  115. package/lib/module/core/parser/Parser.js +77 -0
  116. package/lib/module/core/parser/Parser.js.map +1 -0
  117. package/lib/module/core/parser/expandOffline.js +77 -0
  118. package/lib/module/core/parser/expandOffline.js.map +1 -0
  119. package/lib/module/core/parser/hostTreeReconciler.js +222 -0
  120. package/lib/module/core/parser/hostTreeReconciler.js.map +1 -0
  121. package/lib/module/core/parser/index.js +2 -0
  122. package/lib/module/core/parser/index.js.map +1 -0
  123. package/lib/module/core/registry/SkeletonRegistry.js +106 -0
  124. package/lib/module/core/registry/SkeletonRegistry.js.map +1 -0
  125. package/lib/module/core/registry/index.js +2 -0
  126. package/lib/module/core/registry/index.js.map +1 -0
  127. package/lib/module/core/resolver/StyleResolver.js +77 -0
  128. package/lib/module/core/resolver/StyleResolver.js.map +1 -0
  129. package/lib/module/core/resolver/index.js +2 -0
  130. package/lib/module/core/resolver/index.js.map +1 -0
  131. package/lib/module/deep.js +11 -0
  132. package/lib/module/deep.js.map +1 -0
  133. package/lib/module/index.js +21 -0
  134. package/lib/module/index.js.map +1 -0
  135. package/lib/module/plugins/ImagePlugin.js +53 -0
  136. package/lib/module/plugins/ImagePlugin.js.map +1 -0
  137. package/lib/module/plugins/TextPlugin.js +63 -0
  138. package/lib/module/plugins/TextPlugin.js.map +1 -0
  139. package/lib/module/plugins/ViewPlugin.js +64 -0
  140. package/lib/module/plugins/ViewPlugin.js.map +1 -0
  141. package/lib/module/plugins/index.js +4 -0
  142. package/lib/module/plugins/index.js.map +1 -0
  143. package/lib/module/types/index.js +2 -0
  144. package/lib/module/types/index.js.map +1 -0
  145. package/lib/module/types/plugin.js +2 -0
  146. package/lib/module/types/plugin.js.map +1 -0
  147. package/lib/module/types/skeleton.js +2 -0
  148. package/lib/module/types/skeleton.js.map +1 -0
  149. package/lib/module/utils/componentUtils.js +123 -0
  150. package/lib/module/utils/componentUtils.js.map +1 -0
  151. package/lib/module/utils/index.js +3 -0
  152. package/lib/module/utils/index.js.map +1 -0
  153. package/lib/module/utils/styleUtils.js +66 -0
  154. package/lib/module/utils/styleUtils.js.map +1 -0
  155. package/lib/typescript/Skelo.d.ts +76 -0
  156. package/lib/typescript/Skelo.d.ts.map +1 -0
  157. package/lib/typescript/animations/AnimationWrapper.d.ts +66 -0
  158. package/lib/typescript/animations/AnimationWrapper.d.ts.map +1 -0
  159. package/lib/typescript/animations/PulseAnimation.d.ts +15 -0
  160. package/lib/typescript/animations/PulseAnimation.d.ts.map +1 -0
  161. package/lib/typescript/animations/ShimmerAnimation.d.ts +15 -0
  162. package/lib/typescript/animations/ShimmerAnimation.d.ts.map +1 -0
  163. package/lib/typescript/animations/index.d.ts +5 -0
  164. package/lib/typescript/animations/index.d.ts.map +1 -0
  165. package/lib/typescript/components/MeasuredBlock.d.ts +32 -0
  166. package/lib/typescript/components/MeasuredBlock.d.ts.map +1 -0
  167. package/lib/typescript/components/Skeleton.d.ts +37 -0
  168. package/lib/typescript/components/Skeleton.d.ts.map +1 -0
  169. package/lib/typescript/components/SkeletonIgnore.d.ts +26 -0
  170. package/lib/typescript/components/SkeletonIgnore.d.ts.map +1 -0
  171. package/lib/typescript/components/SkeletonRenderer.d.ts +39 -0
  172. package/lib/typescript/components/SkeletonRenderer.d.ts.map +1 -0
  173. package/lib/typescript/components/primitives/SkeletonBox.d.ts +21 -0
  174. package/lib/typescript/components/primitives/SkeletonBox.d.ts.map +1 -0
  175. package/lib/typescript/components/primitives/SkeletonCircle.d.ts +21 -0
  176. package/lib/typescript/components/primitives/SkeletonCircle.d.ts.map +1 -0
  177. package/lib/typescript/components/primitives/SkeletonImage.d.ts +21 -0
  178. package/lib/typescript/components/primitives/SkeletonImage.d.ts.map +1 -0
  179. package/lib/typescript/components/primitives/SkeletonText.d.ts +21 -0
  180. package/lib/typescript/components/primitives/SkeletonText.d.ts.map +1 -0
  181. package/lib/typescript/components/primitives/index.d.ts +5 -0
  182. package/lib/typescript/components/primitives/index.d.ts.map +1 -0
  183. package/lib/typescript/components/withSkeleton.d.ts +37 -0
  184. package/lib/typescript/components/withSkeleton.d.ts.map +1 -0
  185. package/lib/typescript/constants/defaults.d.ts +29 -0
  186. package/lib/typescript/constants/defaults.d.ts.map +1 -0
  187. package/lib/typescript/core/deepRegistry.d.ts +7 -0
  188. package/lib/typescript/core/deepRegistry.d.ts.map +1 -0
  189. package/lib/typescript/core/generator/StyleSkeleton.d.ts +36 -0
  190. package/lib/typescript/core/generator/StyleSkeleton.d.ts.map +1 -0
  191. package/lib/typescript/core/parser/Parser.d.ts +30 -0
  192. package/lib/typescript/core/parser/Parser.d.ts.map +1 -0
  193. package/lib/typescript/core/parser/expandOffline.d.ts +10 -0
  194. package/lib/typescript/core/parser/expandOffline.d.ts.map +1 -0
  195. package/lib/typescript/core/parser/hostTreeReconciler.d.ts +28 -0
  196. package/lib/typescript/core/parser/hostTreeReconciler.d.ts.map +1 -0
  197. package/lib/typescript/core/parser/index.d.ts +2 -0
  198. package/lib/typescript/core/parser/index.d.ts.map +1 -0
  199. package/lib/typescript/core/registry/SkeletonRegistry.d.ts +57 -0
  200. package/lib/typescript/core/registry/SkeletonRegistry.d.ts.map +1 -0
  201. package/lib/typescript/core/registry/index.d.ts +2 -0
  202. package/lib/typescript/core/registry/index.d.ts.map +1 -0
  203. package/lib/typescript/core/resolver/StyleResolver.d.ts +54 -0
  204. package/lib/typescript/core/resolver/StyleResolver.d.ts.map +1 -0
  205. package/lib/typescript/core/resolver/index.d.ts +2 -0
  206. package/lib/typescript/core/resolver/index.d.ts.map +1 -0
  207. package/lib/typescript/deep.d.ts +2 -0
  208. package/lib/typescript/deep.d.ts.map +1 -0
  209. package/lib/typescript/index.d.ts +17 -0
  210. package/lib/typescript/index.d.ts.map +1 -0
  211. package/lib/typescript/plugins/ImagePlugin.d.ts +11 -0
  212. package/lib/typescript/plugins/ImagePlugin.d.ts.map +1 -0
  213. package/lib/typescript/plugins/TextPlugin.d.ts +12 -0
  214. package/lib/typescript/plugins/TextPlugin.d.ts.map +1 -0
  215. package/lib/typescript/plugins/ViewPlugin.d.ts +11 -0
  216. package/lib/typescript/plugins/ViewPlugin.d.ts.map +1 -0
  217. package/lib/typescript/plugins/index.d.ts +4 -0
  218. package/lib/typescript/plugins/index.d.ts.map +1 -0
  219. package/lib/typescript/types/index.d.ts +3 -0
  220. package/lib/typescript/types/index.d.ts.map +1 -0
  221. package/lib/typescript/types/plugin.d.ts +144 -0
  222. package/lib/typescript/types/plugin.d.ts.map +1 -0
  223. package/lib/typescript/types/skeleton.d.ts +176 -0
  224. package/lib/typescript/types/skeleton.d.ts.map +1 -0
  225. package/lib/typescript/utils/componentUtils.d.ts +40 -0
  226. package/lib/typescript/utils/componentUtils.d.ts.map +1 -0
  227. package/lib/typescript/utils/index.d.ts +3 -0
  228. package/lib/typescript/utils/index.d.ts.map +1 -0
  229. package/lib/typescript/utils/styleUtils.d.ts +41 -0
  230. package/lib/typescript/utils/styleUtils.d.ts.map +1 -0
  231. package/package.json +169 -11
  232. package/src/Skelo.ts +103 -0
  233. package/src/animations/AnimationWrapper.tsx +139 -0
  234. package/src/animations/PulseAnimation.tsx +76 -0
  235. package/src/animations/ShimmerAnimation.tsx +101 -0
  236. package/src/animations/index.ts +4 -0
  237. package/src/components/MeasuredBlock.tsx +85 -0
  238. package/src/components/Skeleton.tsx +149 -0
  239. package/src/components/SkeletonIgnore.tsx +27 -0
  240. package/src/components/SkeletonRenderer.tsx +341 -0
  241. package/src/components/primitives/SkeletonBox.tsx +53 -0
  242. package/src/components/primitives/SkeletonCircle.tsx +48 -0
  243. package/src/components/primitives/SkeletonImage.tsx +50 -0
  244. package/src/components/primitives/SkeletonText.tsx +69 -0
  245. package/src/components/primitives/index.ts +4 -0
  246. package/src/components/withSkeleton.tsx +62 -0
  247. package/src/constants/defaults.ts +38 -0
  248. package/src/core/deepRegistry.ts +16 -0
  249. package/src/core/generator/StyleSkeleton.tsx +138 -0
  250. package/src/core/parser/Parser.ts +86 -0
  251. package/src/core/parser/expandOffline.tsx +83 -0
  252. package/src/core/parser/hostTreeReconciler.tsx +271 -0
  253. package/src/core/parser/index.ts +1 -0
  254. package/src/core/registry/SkeletonRegistry.ts +111 -0
  255. package/src/core/registry/index.ts +1 -0
  256. package/src/core/resolver/StyleResolver.ts +80 -0
  257. package/src/core/resolver/index.ts +1 -0
  258. package/src/deep.ts +12 -0
  259. package/src/index.ts +30 -0
  260. package/src/plugins/ImagePlugin.tsx +65 -0
  261. package/src/plugins/TextPlugin.tsx +71 -0
  262. package/src/plugins/ViewPlugin.tsx +67 -0
  263. package/src/plugins/index.ts +3 -0
  264. package/src/types/index.ts +14 -0
  265. package/src/types/plugin.ts +164 -0
  266. package/src/types/skeleton.ts +197 -0
  267. package/src/utils/componentUtils.ts +166 -0
  268. package/src/utils/index.ts +9 -0
  269. package/src/utils/styleUtils.ts +72 -0
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import type { ImageProps } from '../../types';
4
+ import { AnimationWrapper } from '../../animations';
5
+ import type { AnimationType } from '../../animations';
6
+
7
+ /**
8
+ * Image skeleton primitive
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * <SkeletonImage width={80} height={80} borderRadius={40} />
13
+ * ```
14
+ */
15
+ export function SkeletonImage({
16
+ width = 100,
17
+ height = 100,
18
+ borderRadius = 0,
19
+ style,
20
+ animation = 'shimmer',
21
+ baseColor,
22
+ highlightColor,
23
+ duration,
24
+ }: ImageProps & {
25
+ animation?: AnimationType;
26
+ baseColor?: string;
27
+ highlightColor?: string;
28
+ duration?: number;
29
+ }) {
30
+ return (
31
+ <AnimationWrapper
32
+ animation={animation}
33
+ width={width}
34
+ height={height}
35
+ borderRadius={borderRadius}
36
+ baseColor={baseColor}
37
+ highlightColor={highlightColor}
38
+ duration={duration}
39
+ style={[styles.image, style]}
40
+ />
41
+ );
42
+ }
43
+
44
+ const styles = StyleSheet.create({
45
+ image: {
46
+ overflow: 'hidden',
47
+ },
48
+ });
49
+
50
+ SkeletonImage.displayName = 'Skeleton.Image';
@@ -0,0 +1,69 @@
1
+ import React from 'react';
2
+ import { View, StyleSheet } from 'react-native';
3
+ import type { TextProps } from '../../types';
4
+ import { AnimationWrapper } from '../../animations';
5
+ import type { AnimationType } from '../../animations';
6
+
7
+ /**
8
+ * Text skeleton primitive with multiple lines
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * <SkeletonText lines={3} fontSize={16} lastLineWidth="70%" />
13
+ * ```
14
+ */
15
+ export function SkeletonText({
16
+ lines = 1,
17
+ fontSize = 14,
18
+ lineHeight,
19
+ width = '100%',
20
+ lastLineWidth = '60%',
21
+ style,
22
+ animation = 'shimmer',
23
+ baseColor,
24
+ highlightColor,
25
+ duration,
26
+ }: TextProps & {
27
+ animation?: AnimationType;
28
+ baseColor?: string;
29
+ highlightColor?: string;
30
+ duration?: number;
31
+ }) {
32
+ const calculatedLineHeight = lineHeight || fontSize * 1.4;
33
+
34
+ return (
35
+ <View style={style}>
36
+ {Array.from({ length: lines }).map((_, index) => {
37
+ const isLastLine = index === lines - 1;
38
+ const lineWidth = isLastLine && lines > 1 ? lastLineWidth : width;
39
+
40
+ return (
41
+ <AnimationWrapper
42
+ key={index}
43
+ animation={animation}
44
+ width={lineWidth}
45
+ height={fontSize}
46
+ borderRadius={4}
47
+ baseColor={baseColor}
48
+ highlightColor={highlightColor}
49
+ duration={duration}
50
+ style={[
51
+ styles.line,
52
+ {
53
+ marginBottom: index < lines - 1 ? calculatedLineHeight - fontSize : 0,
54
+ },
55
+ ]}
56
+ />
57
+ );
58
+ })}
59
+ </View>
60
+ );
61
+ }
62
+
63
+ const styles = StyleSheet.create({
64
+ line: {
65
+ overflow: 'hidden',
66
+ },
67
+ });
68
+
69
+ SkeletonText.displayName = 'Skeleton.Text';
@@ -0,0 +1,4 @@
1
+ export { SkeletonBox } from './SkeletonBox';
2
+ export { SkeletonText } from './SkeletonText';
3
+ export { SkeletonImage } from './SkeletonImage';
4
+ export { SkeletonCircle } from './SkeletonCircle';
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import type { ComponentType } from 'react';
3
+ import type { SkeletonConfig } from '../types';
4
+ import type { StyleInput } from '../core/generator/StyleSkeleton';
5
+ import { Skeleton } from './Skeleton';
6
+
7
+ export interface WithSkeletonOptions extends Partial<SkeletonConfig> {
8
+ /**
9
+ * Styles used to generate the skeleton while loading. Required when the
10
+ * wrapped component is opaque (a typical screen/component), since Skelo can't
11
+ * introspect its internals.
12
+ */
13
+ styles?: StyleInput;
14
+
15
+ /**
16
+ * Style keys to skip when generating the skeleton (e.g. layout containers).
17
+ */
18
+ excludeStyles?: string[];
19
+ }
20
+
21
+ /**
22
+ * Higher-order component that adds a loading skeleton to any component.
23
+ *
24
+ * The returned component accepts a `loading` prop (plus the wrapped
25
+ * component's own props). While `loading` is true it renders a skeleton
26
+ * derived from `options.styles`; otherwise it renders the real component.
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * const Home = withSkeleton(HomeScreen, { styles });
31
+ *
32
+ * <Home loading={isLoading} user={user} />
33
+ * ```
34
+ */
35
+ export function withSkeleton<P extends object>(
36
+ Component: ComponentType<P>,
37
+ options: WithSkeletonOptions = {}
38
+ ) {
39
+ const WithSkeleton = (props: P & { loading: boolean }) => {
40
+ const { loading, ...componentProps } = props;
41
+
42
+ return (
43
+ <Skeleton
44
+ loading={loading}
45
+ styles={options.styles}
46
+ excludeStyles={options.excludeStyles}
47
+ animation={options.animation}
48
+ duration={options.duration}
49
+ baseColor={options.baseColor}
50
+ highlightColor={options.highlightColor}
51
+ borderRadius={options.borderRadius}
52
+ >
53
+ <Component {...(componentProps as unknown as P)} />
54
+ </Skeleton>
55
+ );
56
+ };
57
+
58
+ const name = Component.displayName || Component.name || 'Component';
59
+ WithSkeleton.displayName = `withSkeleton(${name})`;
60
+
61
+ return WithSkeleton;
62
+ }
@@ -0,0 +1,38 @@
1
+ import type { SkeletonConfig } from '../types';
2
+
3
+ /**
4
+ * Default skeleton configuration
5
+ */
6
+ export const DEFAULT_CONFIG: Required<SkeletonConfig> = {
7
+ animation: 'shimmer',
8
+ duration: 1200,
9
+ baseColor: '#E1E9EE',
10
+ highlightColor: '#F0F4F8',
11
+ borderRadius: 4,
12
+ debug: false,
13
+ };
14
+
15
+ /**
16
+ * Dark mode colors
17
+ */
18
+ export const DARK_MODE_COLORS = {
19
+ baseColor: '#2A3F4F',
20
+ highlightColor: '#3A5364',
21
+ };
22
+
23
+ /**
24
+ * Default dimensions for components without explicit sizing
25
+ */
26
+ export const DEFAULT_DIMENSIONS = {
27
+ image: {
28
+ width: 100,
29
+ height: 100,
30
+ },
31
+ text: {
32
+ fontSize: 14,
33
+ lineHeight: 20,
34
+ },
35
+ box: {
36
+ height: 50,
37
+ },
38
+ };
@@ -0,0 +1,16 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { ComponentNode } from '../types';
3
+
4
+ // Deep expansion is opt-in so the core bundle never depends on react-reconciler.
5
+ // `react-native-skelo/deep` registers the expander; Skeleton reads it here.
6
+ type DeepExpander = (element: ReactElement) => ComponentNode[] | null;
7
+
8
+ let expander: DeepExpander | null = null;
9
+
10
+ export function setDeepExpander(fn: DeepExpander): void {
11
+ expander = fn;
12
+ }
13
+
14
+ export function getDeepExpander(): DeepExpander | null {
15
+ return expander;
16
+ }
@@ -0,0 +1,138 @@
1
+ import React from 'react';
2
+ import { StyleSheet, View } from 'react-native';
3
+ import type { DimensionValue } from 'react-native';
4
+ import type { SkeletonConfig } from '../../types';
5
+ import { SkeletonBox, SkeletonText, SkeletonCircle } from '../../components/primitives';
6
+
7
+ /**
8
+ * A single named style, or an ordered list of styles, or a whole StyleSheet
9
+ * object as returned by `StyleSheet.create`.
10
+ */
11
+ export type StyleInput = Record<string, unknown> | unknown[];
12
+
13
+ interface StyleSkeletonProps {
14
+ /**
15
+ * The styles to derive a skeleton from. Accepts a `StyleSheet.create` object
16
+ * (values used in definition order) or an array of styles (used in order).
17
+ */
18
+ styles: StyleInput;
19
+
20
+ /**
21
+ * Skeleton configuration (animation, colors, radius).
22
+ */
23
+ config: SkeletonConfig;
24
+
25
+ /**
26
+ * Optional list of style keys to skip (e.g. layout containers you don't want
27
+ * a skeleton for). Only applies when `styles` is a keyed object.
28
+ */
29
+ exclude?: string[];
30
+ }
31
+
32
+ interface FlatStyle {
33
+ width?: number | string;
34
+ height?: number | string;
35
+ borderRadius?: number;
36
+ fontSize?: number;
37
+ lineHeight?: number;
38
+ marginTop?: number;
39
+ marginBottom?: number;
40
+ aspectRatio?: number;
41
+ }
42
+
43
+ /**
44
+ * Turn one flattened style into the most appropriate skeleton primitive.
45
+ *
46
+ * Heuristics:
47
+ * - a `fontSize` with no explicit height → text bar
48
+ * - square with radius ≥ half its size → circle (avatar)
49
+ * - anything with width and/or height → box
50
+ * - a pure layout container (no visual dimensions) → skipped (returns null)
51
+ */
52
+ function styleToSkeleton(
53
+ rawStyle: unknown,
54
+ key: string | number,
55
+ config: SkeletonConfig
56
+ ): React.ReactElement | null {
57
+ const flat = (StyleSheet.flatten(rawStyle as never) || {}) as FlatStyle;
58
+ const { width, height, borderRadius, fontSize } = flat;
59
+
60
+ const anim = {
61
+ animation: config.animation,
62
+ baseColor: config.baseColor,
63
+ highlightColor: config.highlightColor,
64
+ duration: config.duration,
65
+ } as const;
66
+
67
+ const spacing = {
68
+ marginTop: flat.marginTop ?? 0,
69
+ marginBottom: flat.marginBottom ?? 12,
70
+ };
71
+
72
+ // Text-like: has a font size but isn't a fixed-height box.
73
+ if (fontSize != null && height == null) {
74
+ return (
75
+ <SkeletonText
76
+ key={key}
77
+ lines={1}
78
+ fontSize={fontSize}
79
+ lineHeight={flat.lineHeight}
80
+ width={(width as DimensionValue) ?? '60%'}
81
+ style={spacing}
82
+ {...anim}
83
+ />
84
+ );
85
+ }
86
+
87
+ // Circle / avatar: square with a radius that rounds it fully.
88
+ if (
89
+ typeof width === 'number' &&
90
+ width === height &&
91
+ typeof borderRadius === 'number' &&
92
+ borderRadius >= width / 2
93
+ ) {
94
+ return <SkeletonCircle key={key} size={width} style={spacing} {...anim} />;
95
+ }
96
+
97
+ // Box: anything with an explicit dimension.
98
+ if (width != null || height != null) {
99
+ return (
100
+ <SkeletonBox
101
+ key={key}
102
+ width={(width as DimensionValue) ?? '100%'}
103
+ height={(height as DimensionValue) ?? 20}
104
+ borderRadius={borderRadius ?? config.borderRadius ?? 4}
105
+ style={spacing}
106
+ {...anim}
107
+ />
108
+ );
109
+ }
110
+
111
+ // Pure layout container (no visual footprint) — nothing to draw.
112
+ return null;
113
+ }
114
+
115
+ /**
116
+ * Generates a skeleton directly from a set of styles.
117
+ *
118
+ * Because a StyleSheet is flat (no element hierarchy), the output is a vertical
119
+ * stack of skeleton shapes — one per style that has visual dimensions — sized
120
+ * from each style's own props. Layout-only styles are skipped.
121
+ */
122
+ export function StyleSkeleton({ styles, config, exclude }: StyleSkeletonProps) {
123
+ const entries: Array<[string | number, unknown]> = Array.isArray(styles)
124
+ ? styles.map((s, i) => [i, s])
125
+ : Object.entries(styles || {}).filter(([name]) => !exclude || !exclude.includes(name));
126
+
127
+ const nodes = entries
128
+ .map(([key, style]) => styleToSkeleton(style, key, config))
129
+ .filter((n): n is React.ReactElement => n !== null);
130
+
131
+ return (
132
+ <View accessibilityRole="progressbar" accessible>
133
+ {nodes}
134
+ </View>
135
+ );
136
+ }
137
+
138
+ StyleSkeleton.displayName = 'Skeleton.FromStyles';
@@ -0,0 +1,86 @@
1
+ import React, { isValidElement } from 'react';
2
+ import type { ReactElement, ReactNode } from 'react';
3
+ import type { ComponentNode } from '../../types';
4
+ import { StyleResolver } from '../resolver';
5
+ import { getComponentName, flattenChildren } from '../../utils';
6
+
7
+ /**
8
+ * Parser class for traversing and analyzing React component trees
9
+ */
10
+ export class Parser {
11
+ /**
12
+ * Parse a React element tree into a component node tree
13
+ *
14
+ * @param children - React children to parse
15
+ * @returns Array of component nodes
16
+ */
17
+ static parse(children: ReactNode): ComponentNode[] {
18
+ const flattened = flattenChildren(children);
19
+ const nodes: ComponentNode[] = [];
20
+
21
+ flattened.forEach(child => {
22
+ if (!isValidElement(child)) {
23
+ // Skip non-element children (strings, numbers, null, etc.)
24
+ return;
25
+ }
26
+
27
+ const node = this.parseElement(child);
28
+ if (node) {
29
+ nodes.push(node);
30
+ }
31
+ });
32
+
33
+ return nodes;
34
+ }
35
+
36
+ /**
37
+ * Parse a single React element
38
+ *
39
+ * @param element - React element
40
+ * @returns Component node or null
41
+ */
42
+ static parseElement(element: ReactElement): ComponentNode | null {
43
+ const type = getComponentName(element);
44
+ const props = element.props || {};
45
+ const style = StyleResolver.resolve(element);
46
+
47
+ // Parse children recursively
48
+ const children = props.children ? this.parse(props.children) : [];
49
+
50
+ return {
51
+ type,
52
+ props,
53
+ style,
54
+ children,
55
+ element,
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Estimate number of text lines from content and style
61
+ *
62
+ * @param children - Text content
63
+ * @param style - Resolved style
64
+ * @returns Estimated number of lines
65
+ */
66
+ static estimateTextLines(children: ReactNode, _style?: any): number {
67
+ if (!children) {
68
+ return 1;
69
+ }
70
+
71
+ // Convert children to string
72
+ const text = React.Children.toArray(children)
73
+ .filter(child => typeof child === 'string' || typeof child === 'number')
74
+ .join('');
75
+
76
+ if (!text) {
77
+ return 1;
78
+ }
79
+
80
+ // Simple heuristic: ~40 characters per line
81
+ const avgCharsPerLine = 40;
82
+ const estimatedLines = Math.ceil(text.length / avgCharsPerLine);
83
+
84
+ return Math.max(1, Math.min(estimatedLines, 5)); // Cap at 5 lines
85
+ }
86
+ }
@@ -0,0 +1,83 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { ComponentNode, ResolvedStyle } from '../../types';
3
+ import { resolveStyle } from '../../utils/styleUtils';
4
+ import { renderToHostTree } from './hostTreeReconciler';
5
+ import type { HostTreeNode } from './hostTreeReconciler';
6
+
7
+ /**
8
+ * Offline structural expansion.
9
+ *
10
+ * Renders an element (including opaque function/class components) through a
11
+ * JS-only `react-reconciler` renderer to recover its underlying host-element
12
+ * tree (View / Text / Image + resolved styles), then converts it to Skelo's
13
+ * `ComponentNode` tree.
14
+ *
15
+ * This is what lets `<Skeleton><AnyComponent/></Skeleton>` produce a *structured*
16
+ * skeleton without the developer inlining host elements — the core DX. No
17
+ * deprecated APIs, no `act()`.
18
+ *
19
+ * Caveats (inherent to rendering the real component):
20
+ * - The component's mount effects run during expansion. Effectful screens
21
+ * should be guarded or given an explicit skeleton via `Skelo.register`.
22
+ * - It renders outside the app's providers, so context-dependent components may
23
+ * throw — caught, returning null so the caller falls back to static parsing.
24
+ */
25
+
26
+ /**
27
+ * Normalize a host instance type to a canonical name the renderer understands.
28
+ * On device, RN host components may surface as native names (e.g. RCTView,
29
+ * RCTSinglelineTextInputView); map them to View/Text/Image/etc.
30
+ */
31
+ function normalizeType(type: string): string {
32
+ if (/image/i.test(type)) return 'Image';
33
+ if (/textinput/i.test(type)) return 'TextInput';
34
+ if (/text/i.test(type)) return 'Text';
35
+ if (/scroll/i.test(type)) return 'ScrollView';
36
+ if (/(flat|section|virtualized)list/i.test(type)) return 'FlatList';
37
+ // Everything else is treated as a plain container view.
38
+ return 'View';
39
+ }
40
+
41
+ function toComponentNode(node: HostTreeNode): ComponentNode | null {
42
+ if (node.type === '#text') {
43
+ return null; // text is folded into its parent's props.children
44
+ }
45
+
46
+ const textParts: string[] = [];
47
+ const elementChildren: HostTreeNode[] = [];
48
+ for (const child of node.children) {
49
+ if (child.type === '#text') {
50
+ if (child.text) textParts.push(child.text);
51
+ } else {
52
+ elementChildren.push(child);
53
+ }
54
+ }
55
+
56
+ const props: Record<string, unknown> = { ...node.props };
57
+ if (textParts.length > 0 && props.children == null) {
58
+ props.children = textParts.join('');
59
+ }
60
+
61
+ const style: ResolvedStyle = resolveStyle(node.props.style);
62
+
63
+ return {
64
+ type: normalizeType(node.type),
65
+ props,
66
+ style,
67
+ children: elementChildren.map(toComponentNode).filter((n): n is ComponentNode => n !== null),
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Expand an element to a `ComponentNode[]` via offline rendering.
73
+ *
74
+ * @returns the host-tree nodes, or `null` if offline rendering is unavailable
75
+ * (react-reconciler not installed) or the render failed.
76
+ */
77
+ export function expandToComponentNodes(element: ReactElement): ComponentNode[] | null {
78
+ const tree = renderToHostTree(element);
79
+ if (!tree) {
80
+ return null;
81
+ }
82
+ return tree.map(toComponentNode).filter((n): n is ComponentNode => n !== null);
83
+ }