@webority-technologies/mobile 0.0.3 → 0.0.5

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 (34) hide show
  1. package/README.md +44 -0
  2. package/lib/commonjs/components/Avatar/Avatar.js +12 -2
  3. package/lib/commonjs/components/Badge/Badge.js +17 -10
  4. package/lib/commonjs/components/Card/Card.js +22 -10
  5. package/lib/commonjs/components/ListItem/ListItem.js +15 -8
  6. package/lib/commonjs/components/Skeleton/SkeletonContent.js +8 -2
  7. package/lib/commonjs/components/Skeleton/SkeletonList.js +82 -0
  8. package/lib/commonjs/components/Skeleton/index.js +7 -0
  9. package/lib/commonjs/components/index.js +6 -0
  10. package/lib/module/components/Avatar/Avatar.js +12 -2
  11. package/lib/module/components/Badge/Badge.js +17 -10
  12. package/lib/module/components/Card/Card.js +22 -10
  13. package/lib/module/components/ListItem/ListItem.js +15 -8
  14. package/lib/module/components/Skeleton/SkeletonContent.js +8 -2
  15. package/lib/module/components/Skeleton/SkeletonList.js +77 -0
  16. package/lib/module/components/Skeleton/index.js +1 -0
  17. package/lib/module/components/index.js +1 -1
  18. package/lib/typescript/commonjs/components/Avatar/Avatar.d.ts +6 -0
  19. package/lib/typescript/commonjs/components/Badge/Badge.d.ts +6 -0
  20. package/lib/typescript/commonjs/components/Card/Card.d.ts +6 -0
  21. package/lib/typescript/commonjs/components/ListItem/ListItem.d.ts +6 -0
  22. package/lib/typescript/commonjs/components/Skeleton/SkeletonContent.d.ts +6 -0
  23. package/lib/typescript/commonjs/components/Skeleton/SkeletonList.d.ts +27 -0
  24. package/lib/typescript/commonjs/components/Skeleton/index.d.ts +2 -0
  25. package/lib/typescript/commonjs/components/index.d.ts +2 -2
  26. package/lib/typescript/module/components/Avatar/Avatar.d.ts +6 -0
  27. package/lib/typescript/module/components/Badge/Badge.d.ts +6 -0
  28. package/lib/typescript/module/components/Card/Card.d.ts +6 -0
  29. package/lib/typescript/module/components/ListItem/ListItem.d.ts +6 -0
  30. package/lib/typescript/module/components/Skeleton/SkeletonContent.d.ts +6 -0
  31. package/lib/typescript/module/components/Skeleton/SkeletonList.d.ts +27 -0
  32. package/lib/typescript/module/components/Skeleton/index.d.ts +2 -0
  33. package/lib/typescript/module/components/index.d.ts +2 -2
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -394,6 +394,50 @@ For runnable usage of every component, see [`example/`](../../example/).
394
394
 
395
395
  ---
396
396
 
397
+ ## Designing skeleton-aware components
398
+
399
+ Presentational primitives (`Card`, `ListItem`, `Avatar`, `Badge`) accept an optional
400
+ `loading?: boolean` prop. When `true`, the component renders its normal layout wrapped
401
+ in `<SkeletonContent loading mode="auto">` — the walker replaces every `<Text>`,
402
+ `<Image>`, and sized leaf `<View>` with a shimmer block matching the element's
403
+ dimensions. When `loading` is `false` or omitted, behaviour is unchanged.
404
+
405
+ Conventions for components that opt into this pattern:
406
+
407
+ - **Render gracefully with undefined data.** Use optional chaining and string defaults
408
+ so the auto walker has leaves to skeletonize even before data arrives:
409
+ `{item?.title ?? ' '}`, `{item?.subtitle ?? ' '}`. Never let a `<Text>` render `undefined`.
410
+ - **Use explicit dimensions on visual elements.** Set `width`/`height` on `<Image>` and
411
+ `<View>` leaves, and `fontSize` on `<Text>`, so the walker can size each shimmer
412
+ block correctly. Inline styles, `StyleSheet.create`, and arrays all work — they get
413
+ flattened.
414
+ - **Wrap once at the top.** Don't sprinkle `<SkeletonContent>` inside subtrees; wrap the
415
+ component's final return so the entire layout becomes a single coherent placeholder.
416
+ - **For data-driven lists, prefer `<SkeletonList>`.** It owns the "show N placeholder
417
+ rows while loading, then real data" branching so callers don't hand-roll it.
418
+
419
+ Before / after with `Card`:
420
+
421
+ ```tsx
422
+ // Before — caller hand-rolls a placeholder branch
423
+ {loading ? (
424
+ <SkeletonCard />
425
+ ) : (
426
+ <Card>
427
+ <Text>{site.name}</Text>
428
+ <Text>{site.address}</Text>
429
+ </Card>
430
+ )}
431
+
432
+ // After — Card itself is skeleton-aware
433
+ <Card loading={loading}>
434
+ <Text>{site?.name ?? ' '}</Text>
435
+ <Text>{site?.address ?? ' '}</Text>
436
+ </Card>
437
+ ```
438
+
439
+ ---
440
+
397
441
  ## License
398
442
 
399
443
  MIT. Copyright © Webority Technologies.
@@ -7,6 +7,7 @@ exports.default = exports.AvatarGroup = exports.Avatar = void 0;
7
7
  var _react = _interopRequireWildcard(require("react"));
8
8
  var _reactNative = require("react-native");
9
9
  var _index = require("../../theme/index.js");
10
+ var _index2 = require("../Skeleton/index.js");
10
11
  var _jsxRuntime = require("react/jsx-runtime");
11
12
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
12
13
  const sizeMap = {
@@ -69,6 +70,7 @@ const Avatar = exports.Avatar = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
69
70
  statusPosition = 'bottomRight',
70
71
  backgroundColor,
71
72
  color,
73
+ loading = false,
72
74
  accessibilityLabel,
73
75
  style,
74
76
  textStyle,
@@ -89,7 +91,7 @@ const Avatar = exports.Avatar = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
89
91
  const statusSize = Math.max(8, Math.round(dimension * 0.25));
90
92
  const statusBorder = 2;
91
93
  const a11yLabel = accessibilityLabel ?? (name ? `${name}'s avatar` : 'Avatar');
92
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
94
+ const rendered = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
93
95
  ref: ref,
94
96
  style: [styles.root, {
95
97
  width: dimension,
@@ -119,7 +121,7 @@ const Avatar = exports.Avatar = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
119
121
  }, textStyle],
120
122
  numberOfLines: 1,
121
123
  allowFontScaling: false,
122
- children: initials
124
+ children: initials ?? ' '
123
125
  }), status ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
124
126
  style: [styles.status, statusPosition === 'topRight' ? styles.statusTopRight : styles.statusBottomRight, {
125
127
  width: statusSize,
@@ -133,6 +135,14 @@ const Avatar = exports.Avatar = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
133
135
  pointerEvents: "none"
134
136
  }) : null]
135
137
  });
138
+ if (loading) {
139
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.SkeletonContent, {
140
+ loading: true,
141
+ mode: "auto",
142
+ children: rendered
143
+ });
144
+ }
145
+ return rendered;
136
146
  });
137
147
  Avatar.displayName = 'Avatar';
138
148
  const styles = _reactNative.StyleSheet.create({
@@ -7,6 +7,7 @@ exports.default = exports.Badge = void 0;
7
7
  var _react = _interopRequireWildcard(require("react"));
8
8
  var _reactNative = require("react-native");
9
9
  var _index = require("../../theme/index.js");
10
+ var _index2 = require("../Skeleton/index.js");
10
11
  var _jsxRuntime = require("react/jsx-runtime");
11
12
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
12
13
  const toneFor = (theme, tone) => {
@@ -102,6 +103,7 @@ const Badge = exports.Badge = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =
102
103
  tone = 'error',
103
104
  size = 'sm',
104
105
  invisible = false,
106
+ loading = false,
105
107
  children,
106
108
  anchor = 'topRight',
107
109
  pulse = false,
@@ -174,23 +176,28 @@ const Badge = exports.Badge = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =
174
176
  }, textStyle],
175
177
  numberOfLines: 1,
176
178
  allowFontScaling: false,
177
- children: formatted
179
+ children: formatted ?? ' '
178
180
  }) : null
179
181
  }) : null;
180
- if (children) {
181
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
182
- ref: ref,
183
- style: [styles.wrapper, style],
184
- testID: testID,
185
- children: [children, badgeContent]
186
- });
187
- }
188
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
182
+ const rendered = children ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
183
+ ref: ref,
184
+ style: [styles.wrapper, style],
185
+ testID: testID,
186
+ children: [children, badgeContent]
187
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
189
188
  ref: ref,
190
189
  style: style,
191
190
  testID: testID,
192
191
  children: badgeContent
193
192
  });
193
+ if (loading) {
194
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.SkeletonContent, {
195
+ loading: true,
196
+ mode: "auto",
197
+ children: rendered
198
+ });
199
+ }
200
+ return rendered;
194
201
  });
195
202
  Badge.displayName = 'Badge';
196
203
  const styles = _reactNative.StyleSheet.create({
@@ -9,6 +9,7 @@ var _reactNative = require("react-native");
9
9
  var _index = require("../../theme/index.js");
10
10
  var _usePressAnimation = require("../../hooks/usePressAnimation.js");
11
11
  var _hapticUtils = require("../../utils/hapticUtils.js");
12
+ var _index2 = require("../Skeleton/index.js");
12
13
  var _jsxRuntime = require("react/jsx-runtime");
13
14
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
14
15
  const paddingMap = {
@@ -32,6 +33,7 @@ const Card = exports.Card = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
32
33
  imageHeight = 160,
33
34
  imageAspectRatio,
34
35
  imageOverlay,
36
+ loading = false,
35
37
  onPress,
36
38
  accessibilityLabel,
37
39
  accessibilityHint,
@@ -132,12 +134,13 @@ const Card = exports.Card = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
132
134
  children: innerContent
133
135
  })]
134
136
  }) : innerContent;
137
+ let rendered;
135
138
  if (isInteractive) {
136
139
  const handlePress = event => {
137
140
  (0, _hapticUtils.triggerHaptic)('selection');
138
141
  onPress?.(event);
139
142
  };
140
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
143
+ rendered = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
141
144
  style: [{
142
145
  transform: [{
143
146
  scale
@@ -161,16 +164,25 @@ const Card = exports.Card = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
161
164
  children: content
162
165
  })
163
166
  });
167
+ } else {
168
+ rendered = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
169
+ ref: ref,
170
+ accessibilityLabel: accessibilityLabel,
171
+ accessibilityHint: accessibilityHint,
172
+ accessibilityRole: accessibilityRole,
173
+ testID: testID,
174
+ style: [containerStyle, style],
175
+ children: content
176
+ });
164
177
  }
165
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
166
- ref: ref,
167
- accessibilityLabel: accessibilityLabel,
168
- accessibilityHint: accessibilityHint,
169
- accessibilityRole: accessibilityRole,
170
- testID: testID,
171
- style: [containerStyle, style],
172
- children: content
173
- });
178
+ if (loading) {
179
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.SkeletonContent, {
180
+ loading: true,
181
+ mode: "auto",
182
+ children: rendered
183
+ });
184
+ }
185
+ return rendered;
174
186
  });
175
187
  Card.displayName = 'Card';
176
188
  const buildStyles = theme => _reactNative.StyleSheet.create({
@@ -10,6 +10,7 @@ var _index = require("../../theme/index.js");
10
10
  var _usePressAnimation = require("../../hooks/usePressAnimation.js");
11
11
  var _hapticUtils = require("../../utils/hapticUtils.js");
12
12
  var _index2 = require("../Swipeable/index.js");
13
+ var _index3 = require("../Skeleton/index.js");
13
14
  var _jsxRuntime = require("react/jsx-runtime");
14
15
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
15
16
  const sizeFor = (theme, size) => {
@@ -56,6 +57,7 @@ const chevronStyles = _reactNative.StyleSheet.create({
56
57
  const ListItem = exports.ListItem = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
57
58
  const {
58
59
  title,
60
+ loading = false,
59
61
  subtitle,
60
62
  description,
61
63
  left,
@@ -108,7 +110,7 @@ const ListItem = exports.ListItem = /*#__PURE__*/(0, _react.forwardRef)((props,
108
110
  color: disabled ? theme.colors.text.disabled : theme.colors.text.primary
109
111
  }],
110
112
  numberOfLines: 1,
111
- children: title
113
+ children: title ?? ' '
112
114
  }), subtitle ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
113
115
  style: [styles.subtitle, {
114
116
  fontSize: sz.subtitleSize,
@@ -179,15 +181,20 @@ const ListItem = exports.ListItem = /*#__PURE__*/(0, _react.forwardRef)((props,
179
181
 
180
182
  // Wrap in Swipeable only when at least one side has actions; otherwise keep zero gesture overhead.
181
183
  const hasSwipe = leftActions && leftActions.length > 0 || rightActions && rightActions.length > 0;
182
- if (hasSwipe) {
183
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.Swipeable, {
184
- leftActions: leftActions,
185
- rightActions: rightActions,
186
- accessibilityLabel: a11yLabel,
187
- children: rendered
184
+ const final = hasSwipe ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.Swipeable, {
185
+ leftActions: leftActions,
186
+ rightActions: rightActions,
187
+ accessibilityLabel: a11yLabel,
188
+ children: rendered
189
+ }) : rendered;
190
+ if (loading) {
191
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_index3.SkeletonContent, {
192
+ loading: true,
193
+ mode: "auto",
194
+ children: final
188
195
  });
189
196
  }
190
- return rendered;
197
+ return final;
191
198
  });
192
199
  ListItem.displayName = 'ListItem';
193
200
  const buildStyles = theme => _reactNative.StyleSheet.create({
@@ -113,6 +113,7 @@ const SkeletonContent = ({
113
113
  variant = 'shimmer',
114
114
  speed = 'normal',
115
115
  mode = 'auto',
116
+ count = 1,
116
117
  style,
117
118
  testID
118
119
  }) => {
@@ -121,13 +122,18 @@ const SkeletonContent = ({
121
122
  children: children
122
123
  });
123
124
  }
125
+ const repeated = count > 1 ? Array.from({
126
+ length: count
127
+ }, (_, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_react.default.Fragment, {
128
+ children: children
129
+ }, `sk-rep-${i}`)) : children;
124
130
  if (mode === 'block') {
125
131
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(BlockSkeleton, {
126
132
  style: style,
127
133
  testID: testID,
128
134
  variant: variant,
129
135
  speed: speed,
130
- children: children
136
+ children: repeated
131
137
  });
132
138
  }
133
139
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -137,7 +143,7 @@ const SkeletonContent = ({
137
143
  accessibilityLabel: "Loading",
138
144
  accessibilityRole: "progressbar",
139
145
  accessibilityLiveRegion: "polite",
140
- children: skeletonizeNode(children, variant, speed)
146
+ children: skeletonizeNode(repeated, variant, speed)
141
147
  });
142
148
  };
143
149
  exports.SkeletonContent = SkeletonContent;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.SkeletonList = void 0;
7
+ var _react = _interopRequireDefault(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _SkeletonContent = require("./SkeletonContent.js");
10
+ var _jsxRuntime = require("react/jsx-runtime");
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ /**
13
+ * Drop-in FlatList replacement that renders `placeholderCount` skeleton rows while
14
+ * `loading` is true, then transitions to a real `<FlatList>` of `data` once loaded.
15
+ *
16
+ * Solves the "FlatList with empty data renders nothing" problem so consumer screens
17
+ * don't have to maintain a separate placeholder branch.
18
+ *
19
+ * @example
20
+ * <SkeletonList
21
+ * loading={isLoading}
22
+ * data={sites}
23
+ * renderItem={({ item }) => <SiteCard item={item} />}
24
+ * renderPlaceholder={() => <SiteCard loading />}
25
+ * placeholderCount={3}
26
+ * horizontal
27
+ * />
28
+ */
29
+ function SkeletonListInner(props) {
30
+ const {
31
+ loading,
32
+ data,
33
+ renderItem,
34
+ renderPlaceholder,
35
+ placeholderCount = 3,
36
+ variant,
37
+ speed,
38
+ placeholderContainerStyle,
39
+ horizontal,
40
+ contentContainerStyle,
41
+ style,
42
+ testID,
43
+ ...rest
44
+ } = props;
45
+ if (loading) {
46
+ const slots = Array.from({
47
+ length: Math.max(0, placeholderCount)
48
+ }, (_, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_react.default.Fragment, {
49
+ children: renderPlaceholder ? renderPlaceholder(index) : null
50
+ }, `sk-list-${index}`));
51
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_SkeletonContent.SkeletonContent, {
52
+ loading: true,
53
+ variant: variant,
54
+ speed: speed,
55
+ style: style,
56
+ testID: testID,
57
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
58
+ style: [horizontal ? styles.row : styles.column, placeholderContainerStyle],
59
+ children: slots
60
+ })
61
+ });
62
+ }
63
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
64
+ data: data ?? [],
65
+ renderItem: renderItem,
66
+ horizontal: horizontal,
67
+ contentContainerStyle: contentContainerStyle,
68
+ style: style,
69
+ testID: testID,
70
+ ...rest
71
+ });
72
+ }
73
+ const styles = {
74
+ row: {
75
+ flexDirection: 'row'
76
+ },
77
+ column: {
78
+ flexDirection: 'column'
79
+ }
80
+ };
81
+ const SkeletonList = exports.SkeletonList = SkeletonListInner;
82
+ //# sourceMappingURL=SkeletonList.js.map
@@ -33,6 +33,12 @@ Object.defineProperty(exports, "SkeletonContent", {
33
33
  return _SkeletonContent.SkeletonContent;
34
34
  }
35
35
  });
36
+ Object.defineProperty(exports, "SkeletonList", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _SkeletonList.SkeletonList;
40
+ }
41
+ });
36
42
  Object.defineProperty(exports, "SkeletonListItem", {
37
43
  enumerable: true,
38
44
  get: function () {
@@ -53,5 +59,6 @@ Object.defineProperty(exports, "default", {
53
59
  });
54
60
  var _Skeleton = _interopRequireWildcard(require("./Skeleton.js"));
55
61
  var _SkeletonContent = require("./SkeletonContent.js");
62
+ var _SkeletonList = require("./SkeletonList.js");
56
63
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
57
64
  //# sourceMappingURL=index.js.map
@@ -261,6 +261,12 @@ Object.defineProperty(exports, "SkeletonContent", {
261
261
  return _index35.SkeletonContent;
262
262
  }
263
263
  });
264
+ Object.defineProperty(exports, "SkeletonList", {
265
+ enumerable: true,
266
+ get: function () {
267
+ return _index35.SkeletonList;
268
+ }
269
+ });
264
270
  Object.defineProperty(exports, "SkeletonListItem", {
265
271
  enumerable: true,
266
272
  get: function () {
@@ -3,6 +3,7 @@
3
3
  import React, { Children, cloneElement, forwardRef, isValidElement, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { Animated, Easing, Image, StyleSheet, Text, View } from 'react-native';
5
5
  import { useTheme } from "../../theme/index.js";
6
+ import { SkeletonContent } from "../Skeleton/index.js";
6
7
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
8
  const sizeMap = {
8
9
  xs: 24,
@@ -64,6 +65,7 @@ const Avatar = /*#__PURE__*/forwardRef((props, ref) => {
64
65
  statusPosition = 'bottomRight',
65
66
  backgroundColor,
66
67
  color,
68
+ loading = false,
67
69
  accessibilityLabel,
68
70
  style,
69
71
  textStyle,
@@ -84,7 +86,7 @@ const Avatar = /*#__PURE__*/forwardRef((props, ref) => {
84
86
  const statusSize = Math.max(8, Math.round(dimension * 0.25));
85
87
  const statusBorder = 2;
86
88
  const a11yLabel = accessibilityLabel ?? (name ? `${name}'s avatar` : 'Avatar');
87
- return /*#__PURE__*/_jsxs(View, {
89
+ const rendered = /*#__PURE__*/_jsxs(View, {
88
90
  ref: ref,
89
91
  style: [styles.root, {
90
92
  width: dimension,
@@ -114,7 +116,7 @@ const Avatar = /*#__PURE__*/forwardRef((props, ref) => {
114
116
  }, textStyle],
115
117
  numberOfLines: 1,
116
118
  allowFontScaling: false,
117
- children: initials
119
+ children: initials ?? ' '
118
120
  }), status ? /*#__PURE__*/_jsx(View, {
119
121
  style: [styles.status, statusPosition === 'topRight' ? styles.statusTopRight : styles.statusBottomRight, {
120
122
  width: statusSize,
@@ -128,6 +130,14 @@ const Avatar = /*#__PURE__*/forwardRef((props, ref) => {
128
130
  pointerEvents: "none"
129
131
  }) : null]
130
132
  });
133
+ if (loading) {
134
+ return /*#__PURE__*/_jsx(SkeletonContent, {
135
+ loading: true,
136
+ mode: "auto",
137
+ children: rendered
138
+ });
139
+ }
140
+ return rendered;
131
141
  });
132
142
  Avatar.displayName = 'Avatar';
133
143
  const styles = StyleSheet.create({
@@ -3,6 +3,7 @@
3
3
  import React, { forwardRef, useEffect, useMemo, useRef } from 'react';
4
4
  import { Animated, Easing, StyleSheet, Text, View } from 'react-native';
5
5
  import { useTheme } from "../../theme/index.js";
6
+ import { SkeletonContent } from "../Skeleton/index.js";
6
7
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
8
  const toneFor = (theme, tone) => {
8
9
  switch (tone) {
@@ -97,6 +98,7 @@ const Badge = /*#__PURE__*/forwardRef((props, ref) => {
97
98
  tone = 'error',
98
99
  size = 'sm',
99
100
  invisible = false,
101
+ loading = false,
100
102
  children,
101
103
  anchor = 'topRight',
102
104
  pulse = false,
@@ -169,23 +171,28 @@ const Badge = /*#__PURE__*/forwardRef((props, ref) => {
169
171
  }, textStyle],
170
172
  numberOfLines: 1,
171
173
  allowFontScaling: false,
172
- children: formatted
174
+ children: formatted ?? ' '
173
175
  }) : null
174
176
  }) : null;
175
- if (children) {
176
- return /*#__PURE__*/_jsxs(View, {
177
- ref: ref,
178
- style: [styles.wrapper, style],
179
- testID: testID,
180
- children: [children, badgeContent]
181
- });
182
- }
183
- return /*#__PURE__*/_jsx(View, {
177
+ const rendered = children ? /*#__PURE__*/_jsxs(View, {
178
+ ref: ref,
179
+ style: [styles.wrapper, style],
180
+ testID: testID,
181
+ children: [children, badgeContent]
182
+ }) : /*#__PURE__*/_jsx(View, {
184
183
  ref: ref,
185
184
  style: style,
186
185
  testID: testID,
187
186
  children: badgeContent
188
187
  });
188
+ if (loading) {
189
+ return /*#__PURE__*/_jsx(SkeletonContent, {
190
+ loading: true,
191
+ mode: "auto",
192
+ children: rendered
193
+ });
194
+ }
195
+ return rendered;
189
196
  });
190
197
  Badge.displayName = 'Badge';
191
198
  const styles = StyleSheet.create({
@@ -5,6 +5,7 @@ import { Animated, Image, Pressable, StyleSheet, View } from 'react-native';
5
5
  import { useTheme } from "../../theme/index.js";
6
6
  import { usePressAnimation } from "../../hooks/usePressAnimation.js";
7
7
  import { triggerHaptic } from "../../utils/hapticUtils.js";
8
+ import { SkeletonContent } from "../Skeleton/index.js";
8
9
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
9
10
  const paddingMap = {
10
11
  none: 'none',
@@ -27,6 +28,7 @@ const Card = /*#__PURE__*/forwardRef((props, ref) => {
27
28
  imageHeight = 160,
28
29
  imageAspectRatio,
29
30
  imageOverlay,
31
+ loading = false,
30
32
  onPress,
31
33
  accessibilityLabel,
32
34
  accessibilityHint,
@@ -127,12 +129,13 @@ const Card = /*#__PURE__*/forwardRef((props, ref) => {
127
129
  children: innerContent
128
130
  })]
129
131
  }) : innerContent;
132
+ let rendered;
130
133
  if (isInteractive) {
131
134
  const handlePress = event => {
132
135
  triggerHaptic('selection');
133
136
  onPress?.(event);
134
137
  };
135
- return /*#__PURE__*/_jsx(Animated.View, {
138
+ rendered = /*#__PURE__*/_jsx(Animated.View, {
136
139
  style: [{
137
140
  transform: [{
138
141
  scale
@@ -156,16 +159,25 @@ const Card = /*#__PURE__*/forwardRef((props, ref) => {
156
159
  children: content
157
160
  })
158
161
  });
162
+ } else {
163
+ rendered = /*#__PURE__*/_jsx(View, {
164
+ ref: ref,
165
+ accessibilityLabel: accessibilityLabel,
166
+ accessibilityHint: accessibilityHint,
167
+ accessibilityRole: accessibilityRole,
168
+ testID: testID,
169
+ style: [containerStyle, style],
170
+ children: content
171
+ });
159
172
  }
160
- return /*#__PURE__*/_jsx(View, {
161
- ref: ref,
162
- accessibilityLabel: accessibilityLabel,
163
- accessibilityHint: accessibilityHint,
164
- accessibilityRole: accessibilityRole,
165
- testID: testID,
166
- style: [containerStyle, style],
167
- children: content
168
- });
173
+ if (loading) {
174
+ return /*#__PURE__*/_jsx(SkeletonContent, {
175
+ loading: true,
176
+ mode: "auto",
177
+ children: rendered
178
+ });
179
+ }
180
+ return rendered;
169
181
  });
170
182
  Card.displayName = 'Card';
171
183
  const buildStyles = theme => StyleSheet.create({
@@ -6,6 +6,7 @@ import { useTheme } from "../../theme/index.js";
6
6
  import { usePressAnimation } from "../../hooks/usePressAnimation.js";
7
7
  import { triggerHaptic } from "../../utils/hapticUtils.js";
8
8
  import { Swipeable } from "../Swipeable/index.js";
9
+ import { SkeletonContent } from "../Skeleton/index.js";
9
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
11
  const sizeFor = (theme, size) => {
11
12
  switch (size) {
@@ -51,6 +52,7 @@ const chevronStyles = StyleSheet.create({
51
52
  const ListItem = /*#__PURE__*/forwardRef((props, ref) => {
52
53
  const {
53
54
  title,
55
+ loading = false,
54
56
  subtitle,
55
57
  description,
56
58
  left,
@@ -103,7 +105,7 @@ const ListItem = /*#__PURE__*/forwardRef((props, ref) => {
103
105
  color: disabled ? theme.colors.text.disabled : theme.colors.text.primary
104
106
  }],
105
107
  numberOfLines: 1,
106
- children: title
108
+ children: title ?? ' '
107
109
  }), subtitle ? /*#__PURE__*/_jsx(Text, {
108
110
  style: [styles.subtitle, {
109
111
  fontSize: sz.subtitleSize,
@@ -174,15 +176,20 @@ const ListItem = /*#__PURE__*/forwardRef((props, ref) => {
174
176
 
175
177
  // Wrap in Swipeable only when at least one side has actions; otherwise keep zero gesture overhead.
176
178
  const hasSwipe = leftActions && leftActions.length > 0 || rightActions && rightActions.length > 0;
177
- if (hasSwipe) {
178
- return /*#__PURE__*/_jsx(Swipeable, {
179
- leftActions: leftActions,
180
- rightActions: rightActions,
181
- accessibilityLabel: a11yLabel,
182
- children: rendered
179
+ const final = hasSwipe ? /*#__PURE__*/_jsx(Swipeable, {
180
+ leftActions: leftActions,
181
+ rightActions: rightActions,
182
+ accessibilityLabel: a11yLabel,
183
+ children: rendered
184
+ }) : rendered;
185
+ if (loading) {
186
+ return /*#__PURE__*/_jsx(SkeletonContent, {
187
+ loading: true,
188
+ mode: "auto",
189
+ children: final
183
190
  });
184
191
  }
185
- return rendered;
192
+ return final;
186
193
  });
187
194
  ListItem.displayName = 'ListItem';
188
195
  const buildStyles = theme => StyleSheet.create({
@@ -108,6 +108,7 @@ const SkeletonContent = ({
108
108
  variant = 'shimmer',
109
109
  speed = 'normal',
110
110
  mode = 'auto',
111
+ count = 1,
111
112
  style,
112
113
  testID
113
114
  }) => {
@@ -116,13 +117,18 @@ const SkeletonContent = ({
116
117
  children: children
117
118
  });
118
119
  }
120
+ const repeated = count > 1 ? Array.from({
121
+ length: count
122
+ }, (_, i) => /*#__PURE__*/_jsx(React.Fragment, {
123
+ children: children
124
+ }, `sk-rep-${i}`)) : children;
119
125
  if (mode === 'block') {
120
126
  return /*#__PURE__*/_jsx(BlockSkeleton, {
121
127
  style: style,
122
128
  testID: testID,
123
129
  variant: variant,
124
130
  speed: speed,
125
- children: children
131
+ children: repeated
126
132
  });
127
133
  }
128
134
  return /*#__PURE__*/_jsx(View, {
@@ -132,7 +138,7 @@ const SkeletonContent = ({
132
138
  accessibilityLabel: "Loading",
133
139
  accessibilityRole: "progressbar",
134
140
  accessibilityLiveRegion: "polite",
135
- children: skeletonizeNode(children, variant, speed)
141
+ children: skeletonizeNode(repeated, variant, speed)
136
142
  });
137
143
  };
138
144
  SkeletonContent.displayName = 'SkeletonContent';
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+
3
+ import React from 'react';
4
+ import { FlatList, View } from 'react-native';
5
+ import { SkeletonContent } from "./SkeletonContent.js";
6
+ import { jsx as _jsx } from "react/jsx-runtime";
7
+ /**
8
+ * Drop-in FlatList replacement that renders `placeholderCount` skeleton rows while
9
+ * `loading` is true, then transitions to a real `<FlatList>` of `data` once loaded.
10
+ *
11
+ * Solves the "FlatList with empty data renders nothing" problem so consumer screens
12
+ * don't have to maintain a separate placeholder branch.
13
+ *
14
+ * @example
15
+ * <SkeletonList
16
+ * loading={isLoading}
17
+ * data={sites}
18
+ * renderItem={({ item }) => <SiteCard item={item} />}
19
+ * renderPlaceholder={() => <SiteCard loading />}
20
+ * placeholderCount={3}
21
+ * horizontal
22
+ * />
23
+ */
24
+ function SkeletonListInner(props) {
25
+ const {
26
+ loading,
27
+ data,
28
+ renderItem,
29
+ renderPlaceholder,
30
+ placeholderCount = 3,
31
+ variant,
32
+ speed,
33
+ placeholderContainerStyle,
34
+ horizontal,
35
+ contentContainerStyle,
36
+ style,
37
+ testID,
38
+ ...rest
39
+ } = props;
40
+ if (loading) {
41
+ const slots = Array.from({
42
+ length: Math.max(0, placeholderCount)
43
+ }, (_, index) => /*#__PURE__*/_jsx(React.Fragment, {
44
+ children: renderPlaceholder ? renderPlaceholder(index) : null
45
+ }, `sk-list-${index}`));
46
+ return /*#__PURE__*/_jsx(SkeletonContent, {
47
+ loading: true,
48
+ variant: variant,
49
+ speed: speed,
50
+ style: style,
51
+ testID: testID,
52
+ children: /*#__PURE__*/_jsx(View, {
53
+ style: [horizontal ? styles.row : styles.column, placeholderContainerStyle],
54
+ children: slots
55
+ })
56
+ });
57
+ }
58
+ return /*#__PURE__*/_jsx(FlatList, {
59
+ data: data ?? [],
60
+ renderItem: renderItem,
61
+ horizontal: horizontal,
62
+ contentContainerStyle: contentContainerStyle,
63
+ style: style,
64
+ testID: testID,
65
+ ...rest
66
+ });
67
+ }
68
+ const styles = {
69
+ row: {
70
+ flexDirection: 'row'
71
+ },
72
+ column: {
73
+ flexDirection: 'column'
74
+ }
75
+ };
76
+ export const SkeletonList = SkeletonListInner;
77
+ //# sourceMappingURL=SkeletonList.js.map
@@ -2,4 +2,5 @@
2
2
 
3
3
  export { Skeleton, SkeletonCircle, SkeletonText, SkeletonAvatar, SkeletonCard, SkeletonListItem, default } from "./Skeleton.js";
4
4
  export { SkeletonContent } from "./SkeletonContent.js";
5
+ export { SkeletonList } from "./SkeletonList.js";
5
6
  //# sourceMappingURL=index.js.map
@@ -36,7 +36,7 @@ export { SearchBar } from "./SearchBar/index.js";
36
36
  export { SegmentedControl } from "./SegmentedControl/index.js";
37
37
  export { Select } from "./Select/index.js";
38
38
  export { Stepper } from "./Stepper/index.js";
39
- export { Skeleton, SkeletonAvatar, SkeletonCard, SkeletonCircle, SkeletonContent, SkeletonListItem, SkeletonText } from "./Skeleton/index.js";
39
+ export { Skeleton, SkeletonAvatar, SkeletonCard, SkeletonCircle, SkeletonContent, SkeletonList, SkeletonListItem, SkeletonText } from "./Skeleton/index.js";
40
40
  export { Slider } from "./Slider/index.js";
41
41
  export { Swipeable } from "./Swipeable/index.js";
42
42
  export { Switch } from "./Switch/index.js";
@@ -14,6 +14,12 @@ export interface AvatarProps {
14
14
  statusPosition?: AvatarStatusPosition;
15
15
  backgroundColor?: string;
16
16
  color?: string;
17
+ /**
18
+ * When true, renders the component as a skeleton placeholder via SkeletonContent's auto walker.
19
+ * Component still renders its normal layout — the walker replaces Text/Image/sized View leaves
20
+ * with shimmer blocks. Use this when the data driving the component is still being fetched.
21
+ */
22
+ loading?: boolean;
17
23
  accessibilityLabel?: string;
18
24
  style?: StyleProp<ViewStyle>;
19
25
  textStyle?: StyleProp<TextStyle>;
@@ -12,6 +12,12 @@ export interface BadgeProps {
12
12
  tone?: BadgeTone;
13
13
  size?: BadgeSize;
14
14
  invisible?: boolean;
15
+ /**
16
+ * When true, renders the component as a skeleton placeholder via SkeletonContent's auto walker.
17
+ * Component still renders its normal layout — the walker replaces Text/Image/sized View leaves
18
+ * with shimmer blocks. Use this when the data driving the component is still being fetched.
19
+ */
20
+ loading?: boolean;
15
21
  children?: React.ReactNode;
16
22
  anchor?: BadgeAnchor;
17
23
  pulse?: boolean;
@@ -18,6 +18,12 @@ export interface CardProps extends Omit<PressableProps, 'style' | 'children'> {
18
18
  imageHeight?: number;
19
19
  imageAspectRatio?: number;
20
20
  imageOverlay?: React.ReactNode;
21
+ /**
22
+ * When true, renders the component as a skeleton placeholder via SkeletonContent's auto walker.
23
+ * Component still renders its normal layout — the walker replaces Text/Image/sized View leaves
24
+ * with shimmer blocks. Use this when the data driving the component is still being fetched.
25
+ */
26
+ loading?: boolean;
21
27
  onPress?: (event: GestureResponderEvent) => void;
22
28
  accessibilityLabel?: string;
23
29
  style?: StyleProp<ViewStyle>;
@@ -5,6 +5,12 @@ import type { SwipeableAction } from '../Swipeable';
5
5
  export type ListItemSize = 'sm' | 'md' | 'lg';
6
6
  export interface ListItemProps {
7
7
  title: string;
8
+ /**
9
+ * When true, renders the component as a skeleton placeholder via SkeletonContent's auto walker.
10
+ * Component still renders its normal layout — the walker replaces Text/Image/sized View leaves
11
+ * with shimmer blocks. Use this when the data driving the component is still being fetched.
12
+ */
13
+ loading?: boolean;
8
14
  subtitle?: string;
9
15
  description?: string;
10
16
  left?: React.ReactNode;
@@ -20,6 +20,12 @@ export interface SkeletonContentProps {
20
20
  * cannot inspect.
21
21
  */
22
22
  mode?: SkeletonContentMode;
23
+ /**
24
+ * Repeat `children` this many times while loading. Useful for list layouts where the
25
+ * placeholder shape should appear N times (e.g. 3 site cards, 5 list rows). Ignored when
26
+ * `loading` is false. Default: `1`.
27
+ */
28
+ count?: number;
23
29
  style?: StyleProp<ViewStyle>;
24
30
  testID?: string;
25
31
  }
@@ -0,0 +1,27 @@
1
+ import type { ReactElement, ReactNode } from 'react';
2
+ import type { FlatListProps, StyleProp, ViewStyle } from 'react-native';
3
+ import type { SkeletonSpeed, SkeletonVariant } from './Skeleton';
4
+ export interface SkeletonListProps<ItemT> extends Omit<FlatListProps<ItemT>, 'data' | 'renderItem'> {
5
+ /** Whether the list is in its initial loading state. */
6
+ loading: boolean;
7
+ /** The data to render once loading is complete. */
8
+ data: readonly ItemT[] | null | undefined;
9
+ /** Renders a single real item once data is available. */
10
+ renderItem: FlatListProps<ItemT>['renderItem'];
11
+ /**
12
+ * Renders one placeholder row. Index is provided so callers may vary
13
+ * placeholder shape per slot if desired. Defaults to `null`.
14
+ */
15
+ renderPlaceholder?: (index: number) => ReactNode;
16
+ /** How many placeholder rows to render while loading. Default: 3. */
17
+ placeholderCount?: number;
18
+ /** Animation style — same as Skeleton. */
19
+ variant?: SkeletonVariant;
20
+ /** Animation speed — same as Skeleton. */
21
+ speed?: SkeletonSpeed;
22
+ /** Style applied to the wrapper View around the placeholders. */
23
+ placeholderContainerStyle?: StyleProp<ViewStyle>;
24
+ testID?: string;
25
+ }
26
+ export declare const SkeletonList: <ItemT>(props: SkeletonListProps<ItemT>) => ReactElement;
27
+ //# sourceMappingURL=SkeletonList.d.ts.map
@@ -2,4 +2,6 @@ export { Skeleton, SkeletonCircle, SkeletonText, SkeletonAvatar, SkeletonCard, S
2
2
  export type { SkeletonProps, SkeletonCircleProps, SkeletonTextProps, SkeletonAvatarProps, SkeletonCardProps, SkeletonListItemProps, SkeletonVariant, SkeletonSpeed, SkeletonRadius, SkeletonWidth, SkeletonAvatarSize } from './Skeleton';
3
3
  export { SkeletonContent } from './SkeletonContent';
4
4
  export type { SkeletonContentProps, SkeletonContentMode } from './SkeletonContent';
5
+ export { SkeletonList } from './SkeletonList';
6
+ export type { SkeletonListProps } from './SkeletonList';
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -70,8 +70,8 @@ export { Select } from './Select';
70
70
  export type { SelectProps, SelectOption, SelectSize } from './Select';
71
71
  export { Stepper } from './Stepper';
72
72
  export type { StepperProps, StepperVariant, StepperStep, StepperTone } from './Stepper';
73
- export { Skeleton, SkeletonAvatar, SkeletonCard, SkeletonCircle, SkeletonContent, SkeletonListItem, SkeletonText } from './Skeleton';
74
- export type { SkeletonProps, SkeletonAvatarProps, SkeletonCardProps, SkeletonCircleProps, SkeletonContentMode, SkeletonContentProps, SkeletonListItemProps, SkeletonTextProps, SkeletonVariant, SkeletonSpeed } from './Skeleton';
73
+ export { Skeleton, SkeletonAvatar, SkeletonCard, SkeletonCircle, SkeletonContent, SkeletonList, SkeletonListItem, SkeletonText } from './Skeleton';
74
+ export type { SkeletonProps, SkeletonAvatarProps, SkeletonCardProps, SkeletonCircleProps, SkeletonContentMode, SkeletonContentProps, SkeletonListItemProps, SkeletonListProps, SkeletonTextProps, SkeletonVariant, SkeletonSpeed } from './Skeleton';
75
75
  export { Slider } from './Slider';
76
76
  export type { SliderProps, SliderTone, SliderSize } from './Slider';
77
77
  export { Swipeable } from './Swipeable';
@@ -14,6 +14,12 @@ export interface AvatarProps {
14
14
  statusPosition?: AvatarStatusPosition;
15
15
  backgroundColor?: string;
16
16
  color?: string;
17
+ /**
18
+ * When true, renders the component as a skeleton placeholder via SkeletonContent's auto walker.
19
+ * Component still renders its normal layout — the walker replaces Text/Image/sized View leaves
20
+ * with shimmer blocks. Use this when the data driving the component is still being fetched.
21
+ */
22
+ loading?: boolean;
17
23
  accessibilityLabel?: string;
18
24
  style?: StyleProp<ViewStyle>;
19
25
  textStyle?: StyleProp<TextStyle>;
@@ -12,6 +12,12 @@ export interface BadgeProps {
12
12
  tone?: BadgeTone;
13
13
  size?: BadgeSize;
14
14
  invisible?: boolean;
15
+ /**
16
+ * When true, renders the component as a skeleton placeholder via SkeletonContent's auto walker.
17
+ * Component still renders its normal layout — the walker replaces Text/Image/sized View leaves
18
+ * with shimmer blocks. Use this when the data driving the component is still being fetched.
19
+ */
20
+ loading?: boolean;
15
21
  children?: React.ReactNode;
16
22
  anchor?: BadgeAnchor;
17
23
  pulse?: boolean;
@@ -18,6 +18,12 @@ export interface CardProps extends Omit<PressableProps, 'style' | 'children'> {
18
18
  imageHeight?: number;
19
19
  imageAspectRatio?: number;
20
20
  imageOverlay?: React.ReactNode;
21
+ /**
22
+ * When true, renders the component as a skeleton placeholder via SkeletonContent's auto walker.
23
+ * Component still renders its normal layout — the walker replaces Text/Image/sized View leaves
24
+ * with shimmer blocks. Use this when the data driving the component is still being fetched.
25
+ */
26
+ loading?: boolean;
21
27
  onPress?: (event: GestureResponderEvent) => void;
22
28
  accessibilityLabel?: string;
23
29
  style?: StyleProp<ViewStyle>;
@@ -5,6 +5,12 @@ import type { SwipeableAction } from '../Swipeable';
5
5
  export type ListItemSize = 'sm' | 'md' | 'lg';
6
6
  export interface ListItemProps {
7
7
  title: string;
8
+ /**
9
+ * When true, renders the component as a skeleton placeholder via SkeletonContent's auto walker.
10
+ * Component still renders its normal layout — the walker replaces Text/Image/sized View leaves
11
+ * with shimmer blocks. Use this when the data driving the component is still being fetched.
12
+ */
13
+ loading?: boolean;
8
14
  subtitle?: string;
9
15
  description?: string;
10
16
  left?: React.ReactNode;
@@ -20,6 +20,12 @@ export interface SkeletonContentProps {
20
20
  * cannot inspect.
21
21
  */
22
22
  mode?: SkeletonContentMode;
23
+ /**
24
+ * Repeat `children` this many times while loading. Useful for list layouts where the
25
+ * placeholder shape should appear N times (e.g. 3 site cards, 5 list rows). Ignored when
26
+ * `loading` is false. Default: `1`.
27
+ */
28
+ count?: number;
23
29
  style?: StyleProp<ViewStyle>;
24
30
  testID?: string;
25
31
  }
@@ -0,0 +1,27 @@
1
+ import type { ReactElement, ReactNode } from 'react';
2
+ import type { FlatListProps, StyleProp, ViewStyle } from 'react-native';
3
+ import type { SkeletonSpeed, SkeletonVariant } from './Skeleton';
4
+ export interface SkeletonListProps<ItemT> extends Omit<FlatListProps<ItemT>, 'data' | 'renderItem'> {
5
+ /** Whether the list is in its initial loading state. */
6
+ loading: boolean;
7
+ /** The data to render once loading is complete. */
8
+ data: readonly ItemT[] | null | undefined;
9
+ /** Renders a single real item once data is available. */
10
+ renderItem: FlatListProps<ItemT>['renderItem'];
11
+ /**
12
+ * Renders one placeholder row. Index is provided so callers may vary
13
+ * placeholder shape per slot if desired. Defaults to `null`.
14
+ */
15
+ renderPlaceholder?: (index: number) => ReactNode;
16
+ /** How many placeholder rows to render while loading. Default: 3. */
17
+ placeholderCount?: number;
18
+ /** Animation style — same as Skeleton. */
19
+ variant?: SkeletonVariant;
20
+ /** Animation speed — same as Skeleton. */
21
+ speed?: SkeletonSpeed;
22
+ /** Style applied to the wrapper View around the placeholders. */
23
+ placeholderContainerStyle?: StyleProp<ViewStyle>;
24
+ testID?: string;
25
+ }
26
+ export declare const SkeletonList: <ItemT>(props: SkeletonListProps<ItemT>) => ReactElement;
27
+ //# sourceMappingURL=SkeletonList.d.ts.map
@@ -2,4 +2,6 @@ export { Skeleton, SkeletonCircle, SkeletonText, SkeletonAvatar, SkeletonCard, S
2
2
  export type { SkeletonProps, SkeletonCircleProps, SkeletonTextProps, SkeletonAvatarProps, SkeletonCardProps, SkeletonListItemProps, SkeletonVariant, SkeletonSpeed, SkeletonRadius, SkeletonWidth, SkeletonAvatarSize } from './Skeleton';
3
3
  export { SkeletonContent } from './SkeletonContent';
4
4
  export type { SkeletonContentProps, SkeletonContentMode } from './SkeletonContent';
5
+ export { SkeletonList } from './SkeletonList';
6
+ export type { SkeletonListProps } from './SkeletonList';
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -70,8 +70,8 @@ export { Select } from './Select';
70
70
  export type { SelectProps, SelectOption, SelectSize } from './Select';
71
71
  export { Stepper } from './Stepper';
72
72
  export type { StepperProps, StepperVariant, StepperStep, StepperTone } from './Stepper';
73
- export { Skeleton, SkeletonAvatar, SkeletonCard, SkeletonCircle, SkeletonContent, SkeletonListItem, SkeletonText } from './Skeleton';
74
- export type { SkeletonProps, SkeletonAvatarProps, SkeletonCardProps, SkeletonCircleProps, SkeletonContentMode, SkeletonContentProps, SkeletonListItemProps, SkeletonTextProps, SkeletonVariant, SkeletonSpeed } from './Skeleton';
73
+ export { Skeleton, SkeletonAvatar, SkeletonCard, SkeletonCircle, SkeletonContent, SkeletonList, SkeletonListItem, SkeletonText } from './Skeleton';
74
+ export type { SkeletonProps, SkeletonAvatarProps, SkeletonCardProps, SkeletonCircleProps, SkeletonContentMode, SkeletonContentProps, SkeletonListItemProps, SkeletonListProps, SkeletonTextProps, SkeletonVariant, SkeletonSpeed } from './Skeleton';
75
75
  export { Slider } from './Slider';
76
76
  export type { SliderProps, SliderTone, SliderSize } from './Slider';
77
77
  export { Swipeable } from './Swipeable';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority-technologies/mobile",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Beautiful, animated, accessible React Native components plus API/auth/logging/network/storage utilities for Webority projects.",
5
5
  "keywords": [
6
6
  "react-native",