@webority-technologies/mobile 0.0.4 → 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.
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({
@@ -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({
@@ -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;
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority-technologies/mobile",
3
- "version": "0.0.4",
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",