react-native-gleam 1.0.0-beta.6 → 1.0.0-beta.7

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
@@ -52,6 +52,32 @@ function UserCard({ loading, user }) {
52
52
 
53
53
  When `loading={true}`, children are hidden and a shimmer animation plays. When `loading={false}`, the shimmer fades out and children fade in.
54
54
 
55
+ ### Multi-line skeleton (`GleamView.Line`)
56
+
57
+ Use `GleamView.Line` to create individual shimmer bars that inherit props from a parent `GleamView`. No conditional rendering — the wrapper pattern works for multi-line skeletons too.
58
+
59
+ ```tsx
60
+ <GleamView loading={loading} speed={800} baseColor="#E0E0E0">
61
+ <GleamView.Line style={{ height: 22, borderRadius: 6, width: '70%' }}>
62
+ <Text style={{ fontSize: 16 }}>{title}</Text>
63
+ </GleamView.Line>
64
+ <GleamView.Line
65
+ style={{ height: 16, borderRadius: 4, width: '50%' }}
66
+ delay={100}
67
+ >
68
+ <Text style={{ fontSize: 13 }}>{subtitle}</Text>
69
+ </GleamView.Line>
70
+ </GleamView>
71
+ ```
72
+
73
+ When `loading={true}`, each `GleamView.Line` renders its own shimmer bar, sized by `style`. The parent acts as a plain container (no block shimmer). When `loading={false}`, Lines become transparent and children render normally.
74
+
75
+ Lines inherit `loading`, `speed`, `direction`, `baseColor`, `highlightColor`, `intensity`, `transitionDuration`, and `transitionType` from the parent. `delay` and `onTransitionEnd` are per-line.
76
+
77
+ For best performance, place `GleamView.Line` as direct children of `GleamView` (or inside fragments). Lines nested inside intermediate wrappers (e.g., `<View>`) still work, but require an extra render cycle to detect.
78
+
79
+ Every `GleamView` provides context to its subtree. A `GleamView.Line` always binds to its nearest `GleamView` ancestor — nested `GleamView` components each control their own Lines independently.
80
+
55
81
  ### Staggered skeleton
56
82
 
57
83
  ```tsx
@@ -95,10 +121,21 @@ When `loading={true}`, children are hidden and a shimmer animation plays. When `
95
121
  | `intensity` | `number` | `1` | Highlight strength (0-1). Lower = more subtle shimmer |
96
122
  | `baseColor` | `string` | `#E0E0E0` | Background color of the shimmer |
97
123
  | `highlightColor` | `string` | `#F5F5F5` | Color of the moving highlight |
98
- | `onTransitionEnd` | `function` | — | Called when the fade transition completes. Receives `{ nativeEvent: { finished: boolean } }` |
124
+ | `onTransitionEnd` | `function` | — | Called when the transition completes or is interrupted. Receives `{ nativeEvent: { finished: boolean } }` — `true` if completed, `false` if interrupted (e.g., `loading` toggled back to `true`) |
99
125
 
100
126
  All standard `View` props are also supported (`style`, `testID`, etc.). Note: the shimmer overlay supports uniform `borderRadius` only — per-corner radii are not applied to the shimmer.
101
127
 
128
+ ### GleamView.Line Props
129
+
130
+ | Prop | Type | Default | Description |
131
+ |------|------|---------|-------------|
132
+ | `style` | `ViewStyle` | — | Style for the shimmer bar (height, width, borderRadius) |
133
+ | `delay` | `number` | `0` | Phase offset for this line (useful for stagger) |
134
+ | `onTransitionEnd` | `function` | — | Called when this line's transition completes |
135
+ | `testID` | `string` | — | Test identifier |
136
+
137
+ All standard accessibility props (`accessibilityLabel`, `accessibilityRole`, etc.) are accepted directly. Shimmer props (`loading`, `speed`, `direction`, etc.) cannot be passed to `GleamView.Line` — they are inherited automatically from the parent `GleamView`.
138
+
102
139
  ### GleamDirection
103
140
 
104
141
  ```tsx
@@ -121,7 +158,8 @@ GleamTransition.Collapse // 'collapse' — shimmer collapses vertically then ho
121
158
 
122
159
  ## Requirements
123
160
 
124
- - React Native **0.76+** (New Architecture / Fabric)
161
+ - React **19+**
162
+ - React Native **0.78+** (New Architecture / Fabric)
125
163
  - iOS 15+
126
164
  - Android SDK 24+
127
165
 
@@ -139,12 +177,20 @@ When `loading` switches to `false`:
139
177
 
140
178
  1. The shimmer transitions out over `transitionDuration` ms (style depends on `transitionType`)
141
179
  2. Children fade in simultaneously
142
- 3. `onTransitionEnd` fires when complete
180
+ 3. `onTransitionEnd` fires with `finished: true` (or `finished: false` if interrupted)
143
181
 
144
182
  All shimmer instances sharing the same `speed` are automatically synchronized via a shared clock.
145
183
 
146
184
  The shimmer respects uniform `borderRadius` and standard view styles.
147
185
 
186
+ ## Breaking changes (beta)
187
+
188
+ - When `GleamView.Line` children are present, the parent `GleamView` renders as a plain `View` container. `onTransitionEnd` on the parent is ignored in this mode — use `onTransitionEnd` on individual `GleamView.Line` components instead. A dev warning is emitted if this happens.
189
+
190
+ ## Limitations
191
+
192
+ - The shimmer overlay supports uniform `borderRadius` only — per-corner radii are not applied to the shimmer.
193
+
148
194
  ## License
149
195
 
150
196
  MIT
@@ -325,6 +325,9 @@ class GleamView(context: Context) : ReactViewGroup(context) {
325
325
 
326
326
  private fun applyLoadingState(wasLoading: Boolean) {
327
327
  if (loading) {
328
+ if (isTransitioning) {
329
+ emitTransitionEnd(false)
330
+ }
328
331
  // Set isTransitioning=false BEFORE cancel to prevent stale onAnimationEnd
329
332
  isTransitioning = false
330
333
  transitionGeneration++
package/ios/GleamView.mm CHANGED
@@ -494,6 +494,9 @@ static void _unregisterView(GleamView *view) {
494
494
  - (void)_applyLoadingState
495
495
  {
496
496
  if (_loading) {
497
+ if (_isTransitioning) {
498
+ [self _emitTransitionEnd:NO];
499
+ }
497
500
  _isTransitioning = NO;
498
501
  _contentAlpha = 0.0;
499
502
  _lastSetChildrenAlpha = -1.0;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { createContext } from 'react';
4
+ export const GleamContext = /*#__PURE__*/createContext(null);
5
+ //# sourceMappingURL=GleamContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["createContext","GleamContext"],"sourceRoot":"../../src","sources":["GleamContext.ts"],"mappings":";;AAAA,SAASA,aAAa,QAAQ,OAAO;AAerC,OAAO,MAAMC,YAAY,gBAAGD,aAAa,CAA2B,IAAI,CAAC","ignoreList":[]}
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+
3
+ import { useContext, useLayoutEffect } from 'react';
4
+ import { View } from 'react-native';
5
+ import NativeGleamView from './GleamViewNativeComponent';
6
+ import { GleamContext } from "./GleamContext.js";
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ export function GleamLine({
9
+ children,
10
+ style,
11
+ testID,
12
+ delay,
13
+ onTransitionEnd,
14
+ ...accessibilityProps
15
+ }) {
16
+ const ctx = useContext(GleamContext);
17
+ const register = ctx?.registerLine;
18
+ useLayoutEffect(() => {
19
+ if (!register) return;
20
+ return register();
21
+ }, [register]);
22
+ if (!ctx) {
23
+ if (__DEV__) {
24
+ console.warn('GleamView.Line must be used inside a GleamView');
25
+ }
26
+ return /*#__PURE__*/_jsx(View, {
27
+ style: style,
28
+ testID: testID,
29
+ ...accessibilityProps,
30
+ children: children
31
+ });
32
+ }
33
+ return /*#__PURE__*/_jsx(NativeGleamView, {
34
+ loading: ctx.loading,
35
+ speed: ctx.speed,
36
+ direction: ctx.direction,
37
+ delay: delay,
38
+ transitionDuration: ctx.transitionDuration,
39
+ transitionType: ctx.transitionType,
40
+ intensity: ctx.intensity,
41
+ baseColor: ctx.baseColor,
42
+ highlightColor: ctx.highlightColor,
43
+ onTransitionEnd: onTransitionEnd,
44
+ style: style,
45
+ testID: testID,
46
+ ...accessibilityProps,
47
+ children: children
48
+ });
49
+ }
50
+ //# sourceMappingURL=GleamLine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useContext","useLayoutEffect","View","NativeGleamView","GleamContext","jsx","_jsx","GleamLine","children","style","testID","delay","onTransitionEnd","accessibilityProps","ctx","register","registerLine","__DEV__","console","warn","loading","speed","direction","transitionDuration","transitionType","intensity","baseColor","highlightColor"],"sourceRoot":"../../src","sources":["GleamLine.tsx"],"mappings":";;AAAA,SAASA,UAAU,EAAEC,eAAe,QAAwB,OAAO;AACnE,SACEC,IAAI,QAIC,cAAc;AACrB,OAAOC,eAAe,MAA4B,4BAA4B;AAC9E,SAASC,YAAY,QAAQ,mBAAgB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAU9C,OAAO,SAASC,SAASA,CAAC;EACxBC,QAAQ;EACRC,KAAK;EACLC,MAAM;EACNC,KAAK;EACLC,eAAe;EACf,GAAGC;AACW,CAAC,EAAE;EACjB,MAAMC,GAAG,GAAGd,UAAU,CAACI,YAAY,CAAC;EACpC,MAAMW,QAAQ,GAAGD,GAAG,EAAEE,YAAY;EAElCf,eAAe,CAAC,MAAM;IACpB,IAAI,CAACc,QAAQ,EAAE;IACf,OAAOA,QAAQ,CAAC,CAAC;EACnB,CAAC,EAAE,CAACA,QAAQ,CAAC,CAAC;EAEd,IAAI,CAACD,GAAG,EAAE;IACR,IAAIG,OAAO,EAAE;MACXC,OAAO,CAACC,IAAI,CAAC,gDAAgD,CAAC;IAChE;IACA,oBACEb,IAAA,CAACJ,IAAI;MAACO,KAAK,EAAEA,KAAM;MAACC,MAAM,EAAEA,MAAO;MAAA,GAAKG,kBAAkB;MAAAL,QAAA,EACvDA;IAAQ,CACL,CAAC;EAEX;EAEA,oBACEF,IAAA,CAACH,eAAe;IACdiB,OAAO,EAAEN,GAAG,CAACM,OAAQ;IACrBC,KAAK,EAAEP,GAAG,CAACO,KAAM;IACjBC,SAAS,EAAER,GAAG,CAACQ,SAAU;IACzBX,KAAK,EAAEA,KAAM;IACbY,kBAAkB,EAAET,GAAG,CAACS,kBAAmB;IAC3CC,cAAc,EAAEV,GAAG,CAACU,cAAe;IACnCC,SAAS,EAAEX,GAAG,CAACW,SAAU;IACzBC,SAAS,EAAEZ,GAAG,CAACY,SAAU;IACzBC,cAAc,EAAEb,GAAG,CAACa,cAAe;IACnCf,eAAe,EAAEA,eAAgB;IACjCH,KAAK,EAAEA,KAAM;IACbC,MAAM,EAAEA,MAAO;IAAA,GACXG,kBAAkB;IAAAL,QAAA,EAErBA;EAAQ,CACM,CAAC;AAEtB","ignoreList":[]}
@@ -1,6 +1,17 @@
1
1
  "use strict";
2
2
 
3
- export { default as GleamView } from './GleamViewNativeComponent';
3
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
4
+ import { View } from 'react-native';
5
+ import NativeGleamView from './GleamViewNativeComponent';
6
+ import { GleamContext } from "./GleamContext.js";
7
+ import { GleamLine } from "./GleamLine.js";
8
+
9
+ /**
10
+ * Props accepted by GleamView, including ref (React 19 ref-as-prop).
11
+ * Use this type instead of `ComponentProps<typeof GleamView>` for
12
+ * accurate ref typing.
13
+ */
14
+ import { jsx as _jsx } from "react/jsx-runtime";
4
15
  export let GleamDirection = /*#__PURE__*/function (GleamDirection) {
5
16
  GleamDirection["LeftToRight"] = "ltr";
6
17
  GleamDirection["RightToLeft"] = "rtl";
@@ -13,4 +24,107 @@ export let GleamTransition = /*#__PURE__*/function (GleamTransition) {
13
24
  GleamTransition["Collapse"] = "collapse";
14
25
  return GleamTransition;
15
26
  }({});
27
+
28
+ // Shimmer-specific prop keys to exclude when rendering as a plain View container.
29
+ // Typed against NativeProps so a missing key triggers a compile error.
30
+ const SHIMMER_KEY_LIST = ['loading', 'speed', 'direction', 'delay', 'transitionDuration', 'transitionType', 'intensity', 'baseColor', 'highlightColor', 'onTransitionEnd', 'children'];
31
+ const SHIMMER_KEYS = new Set(SHIMMER_KEY_LIST);
32
+ function pickViewProps(props) {
33
+ const viewProps = {};
34
+ for (const key of Object.keys(props)) {
35
+ if (!SHIMMER_KEYS.has(key)) {
36
+ viewProps[key] = props[key];
37
+ }
38
+ }
39
+ return viewProps;
40
+ }
41
+ function hasLineChildren(children) {
42
+ let found = false;
43
+ React.Children.forEach(children, child => {
44
+ if (found) return;
45
+ if (! /*#__PURE__*/React.isValidElement(child)) return;
46
+ if (child.type === GleamLine) {
47
+ found = true;
48
+ } else if (child.type === React.Fragment) {
49
+ found = hasLineChildren(child.props.children);
50
+ }
51
+ });
52
+ return found;
53
+ }
54
+
55
+ // React 19: ref is a regular prop, no forwardRef needed.
56
+ // Internal ref type is loosened to avoid monorepo type conflicts between
57
+ // root and example workspace @types/react copies. The exported GleamViewProps
58
+ // provides the correct consumer-facing type.
59
+ function GleamViewComponent({
60
+ ref,
61
+ ...props
62
+ }) {
63
+ const {
64
+ loading,
65
+ speed,
66
+ direction,
67
+ transitionDuration,
68
+ transitionType,
69
+ intensity,
70
+ baseColor,
71
+ highlightColor,
72
+ children
73
+ } = props;
74
+ const lineCountRef = useRef(0);
75
+ const warnedTransitionRef = useRef(false);
76
+ const [hasLines, setHasLines] = useState(() => hasLineChildren(children));
77
+ const registerLine = useCallback(() => {
78
+ lineCountRef.current++;
79
+ setHasLines(true);
80
+ return () => {
81
+ lineCountRef.current--;
82
+ if (lineCountRef.current === 0) {
83
+ setHasLines(false);
84
+ warnedTransitionRef.current = false;
85
+ }
86
+ };
87
+ }, []);
88
+ const contextValue = useMemo(() => ({
89
+ loading,
90
+ speed,
91
+ direction,
92
+ transitionDuration,
93
+ transitionType,
94
+ intensity,
95
+ baseColor,
96
+ highlightColor,
97
+ registerLine
98
+ }), [loading, speed, direction, transitionDuration, transitionType, intensity, baseColor, highlightColor, registerLine]);
99
+
100
+ // Cast needed: View and NativeGleamView accept Ref<ReactNativeElement>
101
+ // but that type isn't publicly exported from react-native. Safe at runtime.
102
+ const nativeRef = ref;
103
+ if (hasLines) {
104
+ if (__DEV__ && props.onTransitionEnd && !warnedTransitionRef.current) {
105
+ warnedTransitionRef.current = true;
106
+ console.warn('GleamView: onTransitionEnd is ignored when GleamView.Line children are present. ' + 'Use onTransitionEnd on individual GleamView.Line components instead.');
107
+ }
108
+ return /*#__PURE__*/_jsx(GleamContext.Provider, {
109
+ value: contextValue,
110
+ children: /*#__PURE__*/_jsx(View, {
111
+ ref: nativeRef,
112
+ ...pickViewProps(props),
113
+ children: children
114
+ })
115
+ });
116
+ }
117
+ return /*#__PURE__*/_jsx(GleamContext.Provider, {
118
+ value: contextValue,
119
+ children: /*#__PURE__*/_jsx(NativeGleamView, {
120
+ ref: nativeRef,
121
+ ...props,
122
+ children: children
123
+ })
124
+ });
125
+ }
126
+ GleamViewComponent.displayName = 'GleamView';
127
+ export const GleamView = Object.assign(GleamViewComponent, {
128
+ Line: GleamLine
129
+ });
16
130
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["default","GleamView","GleamDirection","GleamTransition"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,OAAO,IAAIC,SAAS,QAAQ,4BAA4B;AAGjE,WAAYC,cAAc,0BAAdA,cAAc;EAAdA,cAAc;EAAdA,cAAc;EAAdA,cAAc;EAAA,OAAdA,cAAc;AAAA;AAM1B,WAAYC,eAAe,0BAAfA,eAAe;EAAfA,eAAe;EAAfA,eAAe;EAAfA,eAAe;EAAA,OAAfA,eAAe;AAAA","ignoreList":[]}
1
+ {"version":3,"names":["React","useCallback","useMemo","useRef","useState","View","NativeGleamView","GleamContext","GleamLine","jsx","_jsx","GleamDirection","GleamTransition","SHIMMER_KEY_LIST","SHIMMER_KEYS","Set","pickViewProps","props","viewProps","key","Object","keys","has","hasLineChildren","children","found","Children","forEach","child","isValidElement","type","Fragment","GleamViewComponent","ref","loading","speed","direction","transitionDuration","transitionType","intensity","baseColor","highlightColor","lineCountRef","warnedTransitionRef","hasLines","setHasLines","registerLine","current","contextValue","nativeRef","__DEV__","onTransitionEnd","console","warn","Provider","value","displayName","GleamView","assign","Line"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,14 @@
1
+ import { type NativeProps } from './GleamViewNativeComponent';
2
+ export interface GleamContextValue {
3
+ loading: NativeProps['loading'];
4
+ speed: NativeProps['speed'];
5
+ direction: NativeProps['direction'];
6
+ transitionDuration: NativeProps['transitionDuration'];
7
+ transitionType: NativeProps['transitionType'];
8
+ intensity: NativeProps['intensity'];
9
+ baseColor: NativeProps['baseColor'];
10
+ highlightColor: NativeProps['highlightColor'];
11
+ registerLine: () => () => void;
12
+ }
13
+ export declare const GleamContext: import("react").Context<GleamContextValue | null>;
14
+ //# sourceMappingURL=GleamContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GleamContext.d.ts","sourceRoot":"","sources":["../../../src/GleamContext.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IAChC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IAC5B,SAAS,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC;IACpC,kBAAkB,EAAE,WAAW,CAAC,oBAAoB,CAAC,CAAC;IACtD,cAAc,EAAE,WAAW,CAAC,gBAAgB,CAAC,CAAC;IAC9C,SAAS,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC;IACpC,SAAS,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC;IACpC,cAAc,EAAE,WAAW,CAAC,gBAAgB,CAAC,CAAC;IAC9C,YAAY,EAAE,MAAM,MAAM,IAAI,CAAC;CAChC;AAED,eAAO,MAAM,YAAY,mDAAgD,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type AccessibilityProps, type StyleProp, type ViewStyle } from 'react-native';
3
+ import { type NativeProps } from './GleamViewNativeComponent';
4
+ export interface GleamLineProps extends AccessibilityProps {
5
+ children?: ReactNode;
6
+ style?: StyleProp<ViewStyle>;
7
+ testID?: string;
8
+ delay?: NativeProps['delay'];
9
+ onTransitionEnd?: NativeProps['onTransitionEnd'];
10
+ }
11
+ export declare function GleamLine({ children, style, testID, delay, onTransitionEnd, ...accessibilityProps }: GleamLineProps): import("react/jsx-runtime").JSX.Element;
12
+ //# sourceMappingURL=GleamLine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GleamLine.d.ts","sourceRoot":"","sources":["../../../src/GleamLine.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AACtB,OAAwB,EAAE,KAAK,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAG/E,MAAM,WAAW,cAAe,SAAQ,kBAAkB;IACxD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IAC7B,eAAe,CAAC,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAC;CAClD;AAED,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,KAAK,EACL,MAAM,EACN,KAAK,EACL,eAAe,EACf,GAAG,kBAAkB,EACtB,EAAE,cAAc,2CAuChB"}
@@ -1,5 +1,17 @@
1
- export { default as GleamView } from './GleamViewNativeComponent';
2
- export type { NativeProps as GleamViewProps } from './GleamViewNativeComponent';
1
+ import React from 'react';
2
+ import { View } from 'react-native';
3
+ import { type NativeProps } from './GleamViewNativeComponent';
4
+ import { GleamLine } from './GleamLine';
5
+ export type { NativeProps } from './GleamViewNativeComponent';
6
+ export type { GleamLineProps } from './GleamLine';
7
+ /**
8
+ * Props accepted by GleamView, including ref (React 19 ref-as-prop).
9
+ * Use this type instead of `ComponentProps<typeof GleamView>` for
10
+ * accurate ref typing.
11
+ */
12
+ export type GleamViewProps = NativeProps & {
13
+ ref?: React.Ref<View>;
14
+ };
3
15
  export declare enum GleamDirection {
4
16
  LeftToRight = "ltr",
5
17
  RightToLeft = "rtl",
@@ -10,4 +22,13 @@ export declare enum GleamTransition {
10
22
  Shrink = "shrink",
11
23
  Collapse = "collapse"
12
24
  }
25
+ declare function GleamViewComponent({ ref, ...props }: NativeProps & {
26
+ ref?: React.Ref<unknown>;
27
+ }): import("react/jsx-runtime").JSX.Element;
28
+ declare namespace GleamViewComponent {
29
+ var displayName: string;
30
+ }
31
+ export declare const GleamView: typeof GleamViewComponent & {
32
+ Line: typeof GleamLine;
33
+ };
13
34
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAClE,YAAY,EAAE,WAAW,IAAI,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEhF,oBAAY,cAAc;IACxB,WAAW,QAAQ;IACnB,WAAW,QAAQ;IACnB,WAAW,QAAQ;CACpB;AAED,oBAAY,eAAe;IACzB,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,QAAQ,aAAa;CACtB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiD,MAAM,OAAO,CAAC;AACtE,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAwB,EAAE,KAAK,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE/E,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG;IACzC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;CACvB,CAAC;AAEF,oBAAY,cAAc;IACxB,WAAW,QAAQ;IACnB,WAAW,QAAQ;IACnB,WAAW,QAAQ;CACpB;AAED,oBAAY,eAAe;IACzB,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,QAAQ,aAAa;CACtB;AAkDD,iBAAS,kBAAkB,CAAC,EAC1B,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;CAAE,2CAkF5C;kBArFQ,kBAAkB;;;AAyF3B,eAAO,MAAM,SAAS;;CAEpB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-gleam",
3
- "version": "1.0.0-beta.6",
3
+ "version": "1.0.0-beta.7",
4
4
  "description": "Native-powered shimmer loading effect for React Native",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -92,8 +92,8 @@
92
92
  "typescript": "^5.9.2"
93
93
  },
94
94
  "peerDependencies": {
95
- "react": ">=18.0.0",
96
- "react-native": ">=0.76.0"
95
+ "react": ">=19.0.0",
96
+ "react-native": ">=0.78.0"
97
97
  },
98
98
  "workspaces": [
99
99
  "example"
@@ -0,0 +1,16 @@
1
+ import { createContext } from 'react';
2
+ import { type NativeProps } from './GleamViewNativeComponent';
3
+
4
+ export interface GleamContextValue {
5
+ loading: NativeProps['loading'];
6
+ speed: NativeProps['speed'];
7
+ direction: NativeProps['direction'];
8
+ transitionDuration: NativeProps['transitionDuration'];
9
+ transitionType: NativeProps['transitionType'];
10
+ intensity: NativeProps['intensity'];
11
+ baseColor: NativeProps['baseColor'];
12
+ highlightColor: NativeProps['highlightColor'];
13
+ registerLine: () => () => void;
14
+ }
15
+
16
+ export const GleamContext = createContext<GleamContextValue | null>(null);
@@ -0,0 +1,65 @@
1
+ import { useContext, useLayoutEffect, type ReactNode } from 'react';
2
+ import {
3
+ View,
4
+ type AccessibilityProps,
5
+ type StyleProp,
6
+ type ViewStyle,
7
+ } from 'react-native';
8
+ import NativeGleamView, { type NativeProps } from './GleamViewNativeComponent';
9
+ import { GleamContext } from './GleamContext';
10
+
11
+ export interface GleamLineProps extends AccessibilityProps {
12
+ children?: ReactNode;
13
+ style?: StyleProp<ViewStyle>;
14
+ testID?: string;
15
+ delay?: NativeProps['delay'];
16
+ onTransitionEnd?: NativeProps['onTransitionEnd'];
17
+ }
18
+
19
+ export function GleamLine({
20
+ children,
21
+ style,
22
+ testID,
23
+ delay,
24
+ onTransitionEnd,
25
+ ...accessibilityProps
26
+ }: GleamLineProps) {
27
+ const ctx = useContext(GleamContext);
28
+ const register = ctx?.registerLine;
29
+
30
+ useLayoutEffect(() => {
31
+ if (!register) return;
32
+ return register();
33
+ }, [register]);
34
+
35
+ if (!ctx) {
36
+ if (__DEV__) {
37
+ console.warn('GleamView.Line must be used inside a GleamView');
38
+ }
39
+ return (
40
+ <View style={style} testID={testID} {...accessibilityProps}>
41
+ {children}
42
+ </View>
43
+ );
44
+ }
45
+
46
+ return (
47
+ <NativeGleamView
48
+ loading={ctx.loading}
49
+ speed={ctx.speed}
50
+ direction={ctx.direction}
51
+ delay={delay}
52
+ transitionDuration={ctx.transitionDuration}
53
+ transitionType={ctx.transitionType}
54
+ intensity={ctx.intensity}
55
+ baseColor={ctx.baseColor}
56
+ highlightColor={ctx.highlightColor}
57
+ onTransitionEnd={onTransitionEnd}
58
+ style={style}
59
+ testID={testID}
60
+ {...accessibilityProps}
61
+ >
62
+ {children}
63
+ </NativeGleamView>
64
+ );
65
+ }
package/src/index.tsx CHANGED
@@ -1,5 +1,20 @@
1
- export { default as GleamView } from './GleamViewNativeComponent';
2
- export type { NativeProps as GleamViewProps } from './GleamViewNativeComponent';
1
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
2
+ import { View } from 'react-native';
3
+ import NativeGleamView, { type NativeProps } from './GleamViewNativeComponent';
4
+ import { GleamContext, type GleamContextValue } from './GleamContext';
5
+ import { GleamLine } from './GleamLine';
6
+
7
+ export type { NativeProps } from './GleamViewNativeComponent';
8
+ export type { GleamLineProps } from './GleamLine';
9
+
10
+ /**
11
+ * Props accepted by GleamView, including ref (React 19 ref-as-prop).
12
+ * Use this type instead of `ComponentProps<typeof GleamView>` for
13
+ * accurate ref typing.
14
+ */
15
+ export type GleamViewProps = NativeProps & {
16
+ ref?: React.Ref<View>;
17
+ };
3
18
 
4
19
  export enum GleamDirection {
5
20
  LeftToRight = 'ltr',
@@ -12,3 +27,144 @@ export enum GleamTransition {
12
27
  Shrink = 'shrink',
13
28
  Collapse = 'collapse',
14
29
  }
30
+
31
+ // Shimmer-specific prop keys to exclude when rendering as a plain View container.
32
+ // Typed against NativeProps so a missing key triggers a compile error.
33
+ const SHIMMER_KEY_LIST = [
34
+ 'loading',
35
+ 'speed',
36
+ 'direction',
37
+ 'delay',
38
+ 'transitionDuration',
39
+ 'transitionType',
40
+ 'intensity',
41
+ 'baseColor',
42
+ 'highlightColor',
43
+ 'onTransitionEnd',
44
+ 'children',
45
+ ] as const satisfies ReadonlyArray<keyof NativeProps | 'children'>;
46
+
47
+ const SHIMMER_KEYS: ReadonlySet<string> = new Set(SHIMMER_KEY_LIST);
48
+
49
+ function pickViewProps(props: NativeProps) {
50
+ const viewProps: Record<string, unknown> = {};
51
+ for (const key of Object.keys(props)) {
52
+ if (!SHIMMER_KEYS.has(key)) {
53
+ viewProps[key] = (props as Record<string, unknown>)[key];
54
+ }
55
+ }
56
+ return viewProps;
57
+ }
58
+
59
+ function hasLineChildren(children: React.ReactNode): boolean {
60
+ let found = false;
61
+ React.Children.forEach(children, (child) => {
62
+ if (found) return;
63
+ if (!React.isValidElement(child)) return;
64
+ if (child.type === GleamLine) {
65
+ found = true;
66
+ } else if (child.type === React.Fragment) {
67
+ found = hasLineChildren(
68
+ (child.props as { children?: React.ReactNode }).children
69
+ );
70
+ }
71
+ });
72
+ return found;
73
+ }
74
+
75
+ // React 19: ref is a regular prop, no forwardRef needed.
76
+ // Internal ref type is loosened to avoid monorepo type conflicts between
77
+ // root and example workspace @types/react copies. The exported GleamViewProps
78
+ // provides the correct consumer-facing type.
79
+ function GleamViewComponent({
80
+ ref,
81
+ ...props
82
+ }: NativeProps & { ref?: React.Ref<unknown> }) {
83
+ const {
84
+ loading,
85
+ speed,
86
+ direction,
87
+ transitionDuration,
88
+ transitionType,
89
+ intensity,
90
+ baseColor,
91
+ highlightColor,
92
+ children,
93
+ } = props;
94
+
95
+ const lineCountRef = useRef(0);
96
+ const warnedTransitionRef = useRef(false);
97
+ const [hasLines, setHasLines] = useState(() => hasLineChildren(children));
98
+
99
+ const registerLine = useCallback(() => {
100
+ lineCountRef.current++;
101
+ setHasLines(true);
102
+ return () => {
103
+ lineCountRef.current--;
104
+ if (lineCountRef.current === 0) {
105
+ setHasLines(false);
106
+ warnedTransitionRef.current = false;
107
+ }
108
+ };
109
+ }, []);
110
+
111
+ const contextValue = useMemo<GleamContextValue>(
112
+ () => ({
113
+ loading,
114
+ speed,
115
+ direction,
116
+ transitionDuration,
117
+ transitionType,
118
+ intensity,
119
+ baseColor,
120
+ highlightColor,
121
+ registerLine,
122
+ }),
123
+ [
124
+ loading,
125
+ speed,
126
+ direction,
127
+ transitionDuration,
128
+ transitionType,
129
+ intensity,
130
+ baseColor,
131
+ highlightColor,
132
+ registerLine,
133
+ ]
134
+ );
135
+
136
+ // Cast needed: View and NativeGleamView accept Ref<ReactNativeElement>
137
+ // but that type isn't publicly exported from react-native. Safe at runtime.
138
+ const nativeRef = ref as any;
139
+
140
+ if (hasLines) {
141
+ if (__DEV__ && props.onTransitionEnd && !warnedTransitionRef.current) {
142
+ warnedTransitionRef.current = true;
143
+ console.warn(
144
+ 'GleamView: onTransitionEnd is ignored when GleamView.Line children are present. ' +
145
+ 'Use onTransitionEnd on individual GleamView.Line components instead.'
146
+ );
147
+ }
148
+ return (
149
+ <GleamContext.Provider value={contextValue}>
150
+ <View ref={nativeRef} {...pickViewProps(props)}>
151
+ {children}
152
+ </View>
153
+ </GleamContext.Provider>
154
+ );
155
+ }
156
+
157
+ return (
158
+ <GleamContext.Provider value={contextValue}>
159
+ <NativeGleamView ref={nativeRef} {...props}>
160
+ {children}
161
+ </NativeGleamView>
162
+ </GleamContext.Provider>
163
+ );
164
+ }
165
+
166
+ GleamViewComponent.displayName = 'GleamView';
167
+
168
+ export const GleamView = Object.assign(GleamViewComponent, {
169
+ Line: GleamLine,
170
+ });