react-native-gleam 1.0.0-beta.6 → 1.0.0-beta.8
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 +49 -3
- package/android/src/main/java/com/gleam/GleamView.kt +10 -4
- package/ios/GleamView.mm +10 -3
- package/lib/module/GleamContext.js +5 -0
- package/lib/module/GleamContext.js.map +1 -0
- package/lib/module/GleamLine.js +50 -0
- package/lib/module/GleamLine.js.map +1 -0
- package/lib/module/index.js +123 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/GleamContext.d.ts +14 -0
- package/lib/typescript/src/GleamContext.d.ts.map +1 -0
- package/lib/typescript/src/GleamLine.d.ts +12 -0
- package/lib/typescript/src/GleamLine.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +23 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/GleamContext.ts +16 -0
- package/src/GleamLine.tsx +65 -0
- package/src/index.tsx +167 -2
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
|
|
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
|
|
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
|
|
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
|
|
@@ -12,6 +12,7 @@ import android.graphics.Shader
|
|
|
12
12
|
import android.os.SystemClock
|
|
13
13
|
import android.view.Choreographer
|
|
14
14
|
import android.view.animation.DecelerateInterpolator
|
|
15
|
+
import androidx.annotation.UiThread
|
|
15
16
|
import com.facebook.react.bridge.Arguments
|
|
16
17
|
import com.facebook.react.bridge.ReactContext
|
|
17
18
|
import com.facebook.react.bridge.WritableMap
|
|
@@ -325,6 +326,9 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
325
326
|
|
|
326
327
|
private fun applyLoadingState(wasLoading: Boolean) {
|
|
327
328
|
if (loading) {
|
|
329
|
+
if (isTransitioning) {
|
|
330
|
+
emitTransitionEnd(false)
|
|
331
|
+
}
|
|
328
332
|
// Set isTransitioning=false BEFORE cancel to prevent stale onAnimationEnd
|
|
329
333
|
isTransitioning = false
|
|
330
334
|
transitionGeneration++
|
|
@@ -409,9 +413,9 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
409
413
|
*/
|
|
410
414
|
companion object SharedClock {
|
|
411
415
|
private val views = mutableListOf<GleamView>()
|
|
412
|
-
private val iterationSnapshot = mutableListOf<GleamView>()
|
|
413
416
|
private var frameCallback: Choreographer.FrameCallback? = null
|
|
414
417
|
|
|
418
|
+
@UiThread
|
|
415
419
|
fun register(view: GleamView) {
|
|
416
420
|
if (!views.contains(view)) {
|
|
417
421
|
views.add(view)
|
|
@@ -419,6 +423,7 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
419
423
|
if (views.size == 1) start()
|
|
420
424
|
}
|
|
421
425
|
|
|
426
|
+
@UiThread
|
|
422
427
|
fun unregister(view: GleamView) {
|
|
423
428
|
views.remove(view)
|
|
424
429
|
if (views.isEmpty()) stop()
|
|
@@ -427,9 +432,10 @@ class GleamView(context: Context) : ReactViewGroup(context) {
|
|
|
427
432
|
private fun start() {
|
|
428
433
|
frameCallback = Choreographer.FrameCallback { frameTimeNanos ->
|
|
429
434
|
val timeMs = frameTimeNanos / 1_000_000f
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
435
|
+
// Reverse index iteration — safe if onFrame triggers unregister (shrinks tail)
|
|
436
|
+
for (i in views.indices.reversed()) {
|
|
437
|
+
views[i].onFrame(timeMs)
|
|
438
|
+
}
|
|
433
439
|
frameCallback?.let { Choreographer.getInstance().postFrameCallback(it) }
|
|
434
440
|
}
|
|
435
441
|
Choreographer.getInstance().postFrameCallback(frameCallback!!)
|
package/ios/GleamView.mm
CHANGED
|
@@ -223,6 +223,12 @@ static void _unregisterView(GleamView *view) {
|
|
|
223
223
|
_didInitialSetup = NO;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
// Invariant: a registered view (_isRegistered=YES) is held by the static
|
|
227
|
+
// __strong _views array, which prevents ARC dealloc while registered.
|
|
228
|
+
// Off-main dealloc with _isRegistered=YES should therefore be unreachable.
|
|
229
|
+
// If the invariant is violated in release, we skip _unregisterView to avoid
|
|
230
|
+
// racing on the statics (_views, _viewCount, _displayLink) that are read/written
|
|
231
|
+
// by the display link on the main thread. The view leaks in _views but no crash.
|
|
226
232
|
- (void)dealloc
|
|
227
233
|
{
|
|
228
234
|
if (_isRegistered) {
|
|
@@ -230,9 +236,7 @@ static void _unregisterView(GleamView *view) {
|
|
|
230
236
|
if ([NSThread isMainThread]) {
|
|
231
237
|
_unregisterView(self);
|
|
232
238
|
} else {
|
|
233
|
-
|
|
234
|
-
_stopDisplayLinkIfNeeded();
|
|
235
|
-
});
|
|
239
|
+
NSAssert(NO, @"GleamView deallocated off main thread — static __strong array should prevent this");
|
|
236
240
|
}
|
|
237
241
|
}
|
|
238
242
|
}
|
|
@@ -494,6 +498,9 @@ static void _unregisterView(GleamView *view) {
|
|
|
494
498
|
- (void)_applyLoadingState
|
|
495
499
|
{
|
|
496
500
|
if (_loading) {
|
|
501
|
+
if (_isTransitioning) {
|
|
502
|
+
[self _emitTransitionEnd:NO];
|
|
503
|
+
}
|
|
497
504
|
_isTransitioning = NO;
|
|
498
505
|
_contentAlpha = 0.0;
|
|
499
506
|
_lastSetChildrenAlpha = -1.0;
|
|
@@ -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":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
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,115 @@ export let GleamTransition = /*#__PURE__*/function (GleamTransition) {
|
|
|
13
24
|
GleamTransition["Collapse"] = "collapse";
|
|
14
25
|
return GleamTransition;
|
|
15
26
|
}({});
|
|
27
|
+
|
|
28
|
+
// Shimmer-specific keys that must be destructured from props before spreading
|
|
29
|
+
// viewProps onto <View> in Line mode.
|
|
30
|
+
// Direction 1 (compile-time): `satisfies` catches stale/typo keys.
|
|
31
|
+
// Direction 2 (DEV runtime): check inside GleamViewComponent catches
|
|
32
|
+
// new NativeProps keys that weren't added to this list or destructured.
|
|
33
|
+
const SHIMMER_KEYS = new Set(['loading', 'speed', 'direction', 'delay', 'transitionDuration', 'transitionType', 'intensity', 'baseColor', 'highlightColor', 'onTransitionEnd']);
|
|
34
|
+
function hasLineChildren(children) {
|
|
35
|
+
let found = false;
|
|
36
|
+
React.Children.forEach(children, child => {
|
|
37
|
+
if (found) return;
|
|
38
|
+
if (! /*#__PURE__*/React.isValidElement(child)) return;
|
|
39
|
+
if (child.type === GleamLine) {
|
|
40
|
+
found = true;
|
|
41
|
+
} else if (child.type === React.Fragment) {
|
|
42
|
+
found = hasLineChildren(child.props.children);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return found;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// React 19: ref is a regular prop, no forwardRef needed.
|
|
49
|
+
// Internal ref type is loosened to avoid monorepo type conflicts between
|
|
50
|
+
// root and example workspace @types/react copies. The exported GleamViewProps
|
|
51
|
+
// provides the correct consumer-facing type.
|
|
52
|
+
function GleamViewComponent({
|
|
53
|
+
ref,
|
|
54
|
+
...props
|
|
55
|
+
}) {
|
|
56
|
+
const {
|
|
57
|
+
loading,
|
|
58
|
+
speed,
|
|
59
|
+
direction,
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
61
|
+
delay,
|
|
62
|
+
transitionDuration,
|
|
63
|
+
transitionType,
|
|
64
|
+
intensity,
|
|
65
|
+
baseColor,
|
|
66
|
+
highlightColor,
|
|
67
|
+
onTransitionEnd,
|
|
68
|
+
children,
|
|
69
|
+
...viewProps
|
|
70
|
+
} = props;
|
|
71
|
+
if (__DEV__) {
|
|
72
|
+
for (const key of Object.keys(viewProps)) {
|
|
73
|
+
if (SHIMMER_KEYS.has(key)) {
|
|
74
|
+
console.error(`GleamView: shimmer prop "${key}" leaked into viewProps. ` + 'Add it to the destructuring in GleamViewComponent and to SHIMMER_KEYS.');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const lineCountRef = useRef(0);
|
|
79
|
+
const warnedTransitionRef = useRef(false);
|
|
80
|
+
const [hasLines, setHasLines] = useState(() => hasLineChildren(children));
|
|
81
|
+
const registerLine = useCallback(() => {
|
|
82
|
+
lineCountRef.current++;
|
|
83
|
+
setHasLines(true);
|
|
84
|
+
return () => {
|
|
85
|
+
lineCountRef.current--;
|
|
86
|
+
if (lineCountRef.current === 0) {
|
|
87
|
+
queueMicrotask(() => {
|
|
88
|
+
if (lineCountRef.current === 0) {
|
|
89
|
+
setHasLines(false);
|
|
90
|
+
warnedTransitionRef.current = false;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}, []);
|
|
96
|
+
const contextValue = useMemo(() => ({
|
|
97
|
+
loading,
|
|
98
|
+
speed,
|
|
99
|
+
direction,
|
|
100
|
+
transitionDuration,
|
|
101
|
+
transitionType,
|
|
102
|
+
intensity,
|
|
103
|
+
baseColor,
|
|
104
|
+
highlightColor,
|
|
105
|
+
registerLine
|
|
106
|
+
}), [loading, speed, direction, transitionDuration, transitionType, intensity, baseColor, highlightColor, registerLine]);
|
|
107
|
+
|
|
108
|
+
// Cast needed: View and NativeGleamView accept Ref<ReactNativeElement>
|
|
109
|
+
// but that type isn't publicly exported from react-native. Safe at runtime.
|
|
110
|
+
const nativeRef = ref;
|
|
111
|
+
if (hasLines) {
|
|
112
|
+
if (__DEV__ && onTransitionEnd && !warnedTransitionRef.current) {
|
|
113
|
+
warnedTransitionRef.current = true;
|
|
114
|
+
console.warn('GleamView: onTransitionEnd is ignored when GleamView.Line children are present. ' + 'Use onTransitionEnd on individual GleamView.Line components instead.');
|
|
115
|
+
}
|
|
116
|
+
return /*#__PURE__*/_jsx(GleamContext.Provider, {
|
|
117
|
+
value: contextValue,
|
|
118
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
119
|
+
ref: nativeRef,
|
|
120
|
+
...viewProps,
|
|
121
|
+
children: children
|
|
122
|
+
})
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return /*#__PURE__*/_jsx(GleamContext.Provider, {
|
|
126
|
+
value: contextValue,
|
|
127
|
+
children: /*#__PURE__*/_jsx(NativeGleamView, {
|
|
128
|
+
ref: nativeRef,
|
|
129
|
+
...props,
|
|
130
|
+
children: children
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
GleamViewComponent.displayName = 'GleamView';
|
|
135
|
+
export const GleamView = Object.assign(GleamViewComponent, {
|
|
136
|
+
Line: GleamLine
|
|
137
|
+
});
|
|
16
138
|
//# sourceMappingURL=index.js.map
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["
|
|
1
|
+
{"version":3,"names":["React","useCallback","useMemo","useRef","useState","View","NativeGleamView","GleamContext","GleamLine","jsx","_jsx","GleamDirection","GleamTransition","SHIMMER_KEYS","Set","hasLineChildren","children","found","Children","forEach","child","isValidElement","type","Fragment","props","GleamViewComponent","ref","loading","speed","direction","delay","transitionDuration","transitionType","intensity","baseColor","highlightColor","onTransitionEnd","viewProps","__DEV__","key","Object","keys","has","console","error","lineCountRef","warnedTransitionRef","hasLines","setHasLines","registerLine","current","queueMicrotask","contextValue","nativeRef","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
|
-
|
|
2
|
-
|
|
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,
|
|
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;AAwCD,iBAAS,kBAAkB,CAAC,EAC1B,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;CAAE,2CAqG5C;kBAxGQ,kBAAkB;;;AA4G3B,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.
|
|
3
|
+
"version": "1.0.0-beta.8",
|
|
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": ">=
|
|
96
|
-
"react-native": ">=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
|
-
|
|
2
|
-
|
|
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,153 @@ export enum GleamTransition {
|
|
|
12
27
|
Shrink = 'shrink',
|
|
13
28
|
Collapse = 'collapse',
|
|
14
29
|
}
|
|
30
|
+
|
|
31
|
+
// Shimmer-specific keys that must be destructured from props before spreading
|
|
32
|
+
// viewProps onto <View> in Line mode.
|
|
33
|
+
// Direction 1 (compile-time): `satisfies` catches stale/typo keys.
|
|
34
|
+
// Direction 2 (DEV runtime): check inside GleamViewComponent catches
|
|
35
|
+
// new NativeProps keys that weren't added to this list or destructured.
|
|
36
|
+
const SHIMMER_KEYS: ReadonlySet<string> = new Set([
|
|
37
|
+
'loading',
|
|
38
|
+
'speed',
|
|
39
|
+
'direction',
|
|
40
|
+
'delay',
|
|
41
|
+
'transitionDuration',
|
|
42
|
+
'transitionType',
|
|
43
|
+
'intensity',
|
|
44
|
+
'baseColor',
|
|
45
|
+
'highlightColor',
|
|
46
|
+
'onTransitionEnd',
|
|
47
|
+
] as const satisfies ReadonlyArray<keyof NativeProps>);
|
|
48
|
+
|
|
49
|
+
function hasLineChildren(children: React.ReactNode): boolean {
|
|
50
|
+
let found = false;
|
|
51
|
+
React.Children.forEach(children, (child) => {
|
|
52
|
+
if (found) return;
|
|
53
|
+
if (!React.isValidElement(child)) return;
|
|
54
|
+
if (child.type === GleamLine) {
|
|
55
|
+
found = true;
|
|
56
|
+
} else if (child.type === React.Fragment) {
|
|
57
|
+
found = hasLineChildren(
|
|
58
|
+
(child.props as { children?: React.ReactNode }).children
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// React 19: ref is a regular prop, no forwardRef needed.
|
|
66
|
+
// Internal ref type is loosened to avoid monorepo type conflicts between
|
|
67
|
+
// root and example workspace @types/react copies. The exported GleamViewProps
|
|
68
|
+
// provides the correct consumer-facing type.
|
|
69
|
+
function GleamViewComponent({
|
|
70
|
+
ref,
|
|
71
|
+
...props
|
|
72
|
+
}: NativeProps & { ref?: React.Ref<unknown> }) {
|
|
73
|
+
const {
|
|
74
|
+
loading,
|
|
75
|
+
speed,
|
|
76
|
+
direction,
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
78
|
+
delay,
|
|
79
|
+
transitionDuration,
|
|
80
|
+
transitionType,
|
|
81
|
+
intensity,
|
|
82
|
+
baseColor,
|
|
83
|
+
highlightColor,
|
|
84
|
+
onTransitionEnd,
|
|
85
|
+
children,
|
|
86
|
+
...viewProps
|
|
87
|
+
} = props;
|
|
88
|
+
|
|
89
|
+
if (__DEV__) {
|
|
90
|
+
for (const key of Object.keys(viewProps)) {
|
|
91
|
+
if (SHIMMER_KEYS.has(key)) {
|
|
92
|
+
console.error(
|
|
93
|
+
`GleamView: shimmer prop "${key}" leaked into viewProps. ` +
|
|
94
|
+
'Add it to the destructuring in GleamViewComponent and to SHIMMER_KEYS.'
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const lineCountRef = useRef(0);
|
|
101
|
+
const warnedTransitionRef = useRef(false);
|
|
102
|
+
const [hasLines, setHasLines] = useState(() => hasLineChildren(children));
|
|
103
|
+
|
|
104
|
+
const registerLine = useCallback(() => {
|
|
105
|
+
lineCountRef.current++;
|
|
106
|
+
setHasLines(true);
|
|
107
|
+
return () => {
|
|
108
|
+
lineCountRef.current--;
|
|
109
|
+
if (lineCountRef.current === 0) {
|
|
110
|
+
queueMicrotask(() => {
|
|
111
|
+
if (lineCountRef.current === 0) {
|
|
112
|
+
setHasLines(false);
|
|
113
|
+
warnedTransitionRef.current = false;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}, []);
|
|
119
|
+
|
|
120
|
+
const contextValue = useMemo<GleamContextValue>(
|
|
121
|
+
() => ({
|
|
122
|
+
loading,
|
|
123
|
+
speed,
|
|
124
|
+
direction,
|
|
125
|
+
transitionDuration,
|
|
126
|
+
transitionType,
|
|
127
|
+
intensity,
|
|
128
|
+
baseColor,
|
|
129
|
+
highlightColor,
|
|
130
|
+
registerLine,
|
|
131
|
+
}),
|
|
132
|
+
[
|
|
133
|
+
loading,
|
|
134
|
+
speed,
|
|
135
|
+
direction,
|
|
136
|
+
transitionDuration,
|
|
137
|
+
transitionType,
|
|
138
|
+
intensity,
|
|
139
|
+
baseColor,
|
|
140
|
+
highlightColor,
|
|
141
|
+
registerLine,
|
|
142
|
+
]
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Cast needed: View and NativeGleamView accept Ref<ReactNativeElement>
|
|
146
|
+
// but that type isn't publicly exported from react-native. Safe at runtime.
|
|
147
|
+
const nativeRef = ref as any;
|
|
148
|
+
|
|
149
|
+
if (hasLines) {
|
|
150
|
+
if (__DEV__ && onTransitionEnd && !warnedTransitionRef.current) {
|
|
151
|
+
warnedTransitionRef.current = true;
|
|
152
|
+
console.warn(
|
|
153
|
+
'GleamView: onTransitionEnd is ignored when GleamView.Line children are present. ' +
|
|
154
|
+
'Use onTransitionEnd on individual GleamView.Line components instead.'
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return (
|
|
158
|
+
<GleamContext.Provider value={contextValue}>
|
|
159
|
+
<View ref={nativeRef} {...viewProps}>
|
|
160
|
+
{children}
|
|
161
|
+
</View>
|
|
162
|
+
</GleamContext.Provider>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<GleamContext.Provider value={contextValue}>
|
|
168
|
+
<NativeGleamView ref={nativeRef} {...props}>
|
|
169
|
+
{children}
|
|
170
|
+
</NativeGleamView>
|
|
171
|
+
</GleamContext.Provider>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
GleamViewComponent.displayName = 'GleamView';
|
|
176
|
+
|
|
177
|
+
export const GleamView = Object.assign(GleamViewComponent, {
|
|
178
|
+
Line: GleamLine,
|
|
179
|
+
});
|