react-native-skia-box-shadow 0.1.0 → 0.2.0

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
@@ -12,6 +12,7 @@ Powered by [`@shopify/react-native-skia`](https://shopify.github.io/react-native
12
12
  - **Colors & Gradients** — solid colors or Skia shader fills (linear, radial, sweep)
13
13
  - **Shapes** — rect, roundedRect, circle, or arbitrary SVG path
14
14
  - **Multi-layer** — stack multiple shadow layers, just like in Figma
15
+ - **Animated** — 60fps animations via Reanimated SharedValues (optional)
15
16
  - **Cross-platform** — iOS & Android
16
17
 
17
18
  ## Installation
@@ -21,6 +22,8 @@ npm install react-native-skia-box-shadow @shopify/react-native-skia
21
22
  ```
22
23
 
23
24
  > **Peer dependency**: `@shopify/react-native-skia` >= 1.0.0
25
+ >
26
+ > For animated shadows: `react-native-reanimated` >= 3.0.0 (optional)
24
27
 
25
28
  ## Usage
26
29
 
@@ -43,6 +46,49 @@ import { Shadow } from 'react-native-skia-box-shadow';
43
46
  </Shadow>
44
47
  ```
45
48
 
49
+ ### Animated shadow
50
+
51
+ ```tsx
52
+ import { AnimatedShadow } from 'react-native-skia-box-shadow';
53
+ import { useSharedValue, withSpring } from 'react-native-reanimated';
54
+
55
+ const MyCard = () => {
56
+ const blur = useSharedValue(16);
57
+ const offsetY = useSharedValue(4);
58
+ const spread = useSharedValue(0);
59
+
60
+ const onPressIn = () => {
61
+ blur.value = withSpring(32);
62
+ offsetY.value = withSpring(12);
63
+ spread.value = withSpring(4);
64
+ };
65
+
66
+ const onPressOut = () => {
67
+ blur.value = withSpring(16);
68
+ offsetY.value = withSpring(4);
69
+ spread.value = withSpring(0);
70
+ };
71
+
72
+ return (
73
+ <AnimatedShadow
74
+ shadows={{
75
+ fillStyle: { kind: 'color', color: 'rgba(0,0,0,0.15)' },
76
+ blurRadius: blur,
77
+ offsetY,
78
+ spread,
79
+ }}
80
+ shape={{ kind: 'roundedRect', radius: 16 }}
81
+ >
82
+ <Pressable onPressIn={onPressIn} onPressOut={onPressOut}>
83
+ <View style={styles.card}>
84
+ <Text>Press me</Text>
85
+ </View>
86
+ </Pressable>
87
+ </AnimatedShadow>
88
+ );
89
+ };
90
+ ```
91
+
46
92
  ### Multiple shadow layers
47
93
 
48
94
  ```tsx
@@ -142,6 +188,16 @@ The component renders a Skia `<Canvas>` behind your children. Each shadow layer
142
188
 
143
189
  This is a React Native port of the Compose Multiplatform library [`vasyl-stetsiuk/shadow`](https://github.com/vasyl-stetsiuk/shadow).
144
190
 
191
+ ## `<AnimatedShadow>`
192
+
193
+ Same API as `<Shadow>`, but numeric props (`blurRadius`, `spread`, `offsetX`, `offsetY`) accept Reanimated `SharedValue<number>` for 60fps UI-thread animations.
194
+
195
+ | Additional Prop | Type | Default | Description |
196
+ | -------------------- | -------- | ------- | ---------------------------------------------- |
197
+ | `maxCanvasPadding` | `number` | `120` | Max extent for animated values (prevents clip) |
198
+
199
+ Since animated values change at runtime, the canvas padding can't be auto-calculated. Set `maxCanvasPadding` to the largest extent your shadow can reach (blur × 3 + spread + offset).
200
+
145
201
  ## License
146
202
 
147
203
  MIT © [Vasyl Stetsiuk](https://stetsiuk.dev)
@@ -0,0 +1,261 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = exports.AnimatedShadow = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _reactNativeSkia = require("@shopify/react-native-skia");
10
+ var _reactNativeReanimated = require("react-native-reanimated");
11
+ var _jsxRuntime = require("react/jsx-runtime");
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); }
13
+ // ── Defaults ────────────────────────────────────────────────────
14
+ const DEFAULTS = {
15
+ fillStyle: {
16
+ kind: 'color',
17
+ color: 'rgba(0,0,0,0.10)'
18
+ },
19
+ blurRadius: 24,
20
+ spread: 4,
21
+ offsetX: 0,
22
+ offsetY: 0
23
+ };
24
+ const pixelRatio = _reactNative.PixelRatio.get();
25
+
26
+ /**
27
+ * Read a value that may be static or a SharedValue.
28
+ * Called inside useDerivedValue / on UI thread.
29
+ */
30
+ const readValue = (v, fallback) => {
31
+ 'worklet';
32
+
33
+ if (typeof v === 'number') return v;
34
+ if (v === undefined || v === null) return fallback;
35
+ return v.value;
36
+ };
37
+
38
+ // ── Animated single shadow layer ────────────────────────────────
39
+ const AnimatedShadowLayer = ({
40
+ params,
41
+ width,
42
+ height,
43
+ defaultShape
44
+ }) => {
45
+ const {
46
+ fillStyle = DEFAULTS.fillStyle,
47
+ blurRadius = DEFAULTS.blurRadius,
48
+ spread = DEFAULTS.spread,
49
+ offsetX = DEFAULTS.offsetX,
50
+ offsetY = DEFAULTS.offsetY,
51
+ shape: shapeOverride
52
+ } = params;
53
+ const shape = shapeOverride ?? defaultShape;
54
+
55
+ // ── Paint (static — color changes don't need 60fps) ───────────
56
+ const paint = (0, _react.useMemo)(() => {
57
+ const p = _reactNativeSkia.Skia.Paint();
58
+ if (fillStyle.kind === 'color') {
59
+ p.setColor(_reactNativeSkia.Skia.Color(fillStyle.color));
60
+ } else {
61
+ p.setShader(fillStyle.factory(width, height));
62
+ }
63
+ return p;
64
+ }, [fillStyle, width, height]);
65
+
66
+ // ── Animated blur (derived on UI thread) ──────────────────────
67
+ const derivedBlur = (0, _reactNativeReanimated.useDerivedValue)(() => {
68
+ return readValue(blurRadius, DEFAULTS.blurRadius) / pixelRatio;
69
+ }, [blurRadius]);
70
+
71
+ // ── Animated offset transform ─────────────────────────────────
72
+ const offsetTransform = (0, _reactNativeReanimated.useDerivedValue)(() => {
73
+ return [{
74
+ translateX: readValue(offsetX, DEFAULTS.offsetX)
75
+ }, {
76
+ translateY: readValue(offsetY, DEFAULTS.offsetY)
77
+ }];
78
+ }, [offsetX, offsetY]);
79
+
80
+ // ── Animated spread transform ─────────────────────────────────
81
+ const scaleTransform = (0, _reactNativeReanimated.useDerivedValue)(() => {
82
+ const s = readValue(spread, DEFAULTS.spread);
83
+ const sw = width + s * 2;
84
+ const sh = height + s * 2;
85
+ return [{
86
+ scaleX: width > 0 ? sw / width : 1
87
+ }, {
88
+ scaleY: height > 0 ? sh / height : 1
89
+ }];
90
+ }, [spread, width, height]);
91
+
92
+ // ── Shape element ─────────────────────────────────────────────
93
+ const shapeElement = (0, _react.useMemo)(() => {
94
+ const blurChild = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSkia.Blur, {
95
+ blur: derivedBlur
96
+ });
97
+ switch (shape.kind) {
98
+ case 'roundedRect':
99
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSkia.RoundedRect, {
100
+ x: 0,
101
+ y: 0,
102
+ width: width,
103
+ height: height,
104
+ r: shape.radius,
105
+ paint: paint,
106
+ children: blurChild
107
+ });
108
+ case 'circle':
109
+ {
110
+ const r = Math.min(width, height) / 2;
111
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSkia.Circle, {
112
+ cx: width / 2,
113
+ cy: height / 2,
114
+ r: r,
115
+ paint: paint,
116
+ children: blurChild
117
+ });
118
+ }
119
+ case 'path':
120
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSkia.Path, {
121
+ path: shape.svgPath,
122
+ paint: paint,
123
+ children: blurChild
124
+ });
125
+ case 'rect':
126
+ default:
127
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSkia.Rect, {
128
+ x: 0,
129
+ y: 0,
130
+ width: width,
131
+ height: height,
132
+ paint: paint,
133
+ children: blurChild
134
+ });
135
+ }
136
+ }, [shape, width, height, paint, derivedBlur]);
137
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSkia.Group, {
138
+ transform: offsetTransform,
139
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSkia.Group, {
140
+ transform: scaleTransform,
141
+ origin: {
142
+ x: width / 2,
143
+ y: height / 2
144
+ },
145
+ children: shapeElement
146
+ })
147
+ });
148
+ };
149
+
150
+ /**
151
+ * `<AnimatedShadow>` — Animated CSS-style box shadows.
152
+ *
153
+ * Same API as `<Shadow>`, but numeric props accept Reanimated
154
+ * `SharedValue<number>` for 60fps animations on the UI thread.
155
+ *
156
+ * Requires `react-native-reanimated` >= 3.0.0.
157
+ *
158
+ * @example
159
+ * ```tsx
160
+ * import { AnimatedShadow } from 'react-native-skia-box-shadow';
161
+ * import { useSharedValue, withSpring } from 'react-native-reanimated';
162
+ *
163
+ * const MyCard = () => {
164
+ * const blur = useSharedValue(16);
165
+ * const offsetY = useSharedValue(4);
166
+ * const spread = useSharedValue(0);
167
+ *
168
+ * const onPressIn = () => {
169
+ * blur.value = withSpring(32);
170
+ * offsetY.value = withSpring(12);
171
+ * spread.value = withSpring(4);
172
+ * };
173
+ *
174
+ * const onPressOut = () => {
175
+ * blur.value = withSpring(16);
176
+ * offsetY.value = withSpring(4);
177
+ * spread.value = withSpring(0);
178
+ * };
179
+ *
180
+ * return (
181
+ * <AnimatedShadow
182
+ * shadows={{
183
+ * fillStyle: { kind: 'color', color: 'rgba(0,0,0,0.15)' },
184
+ * blurRadius: blur,
185
+ * offsetY,
186
+ * spread,
187
+ * }}
188
+ * shape={{ kind: 'roundedRect', radius: 16 }}
189
+ * >
190
+ * <Pressable onPressIn={onPressIn} onPressOut={onPressOut}>
191
+ * <View style={styles.card}>
192
+ * <Text>Press me</Text>
193
+ * </View>
194
+ * </Pressable>
195
+ * </AnimatedShadow>
196
+ * );
197
+ * };
198
+ * ```
199
+ */
200
+ const AnimatedShadow = ({
201
+ shadows,
202
+ shape = {
203
+ kind: 'rect'
204
+ },
205
+ width: _width,
206
+ height: _height,
207
+ maxCanvasPadding = 120,
208
+ style,
209
+ children
210
+ }) => {
211
+ const [layout, setLayout] = (0, _react.useState)(null);
212
+ const onLayout = e => {
213
+ const {
214
+ width,
215
+ height
216
+ } = e.nativeEvent.layout;
217
+ setLayout({
218
+ width,
219
+ height
220
+ });
221
+ };
222
+ const width = _width ?? layout?.width ?? 0;
223
+ const height = _height ?? layout?.height ?? 0;
224
+ const shadowList = Array.isArray(shadows) ? shadows : [shadows];
225
+ const hasSize = width > 0 && height > 0;
226
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
227
+ style: [styles.container, style],
228
+ onLayout: onLayout,
229
+ children: [hasSize && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSkia.Canvas, {
230
+ style: [styles.canvas, {
231
+ top: -maxCanvasPadding,
232
+ left: -maxCanvasPadding,
233
+ width: width + maxCanvasPadding * 2,
234
+ height: height + maxCanvasPadding * 2
235
+ }],
236
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSkia.Group, {
237
+ transform: [{
238
+ translateX: maxCanvasPadding
239
+ }, {
240
+ translateY: maxCanvasPadding
241
+ }],
242
+ children: shadowList.map((params, idx) => /*#__PURE__*/(0, _jsxRuntime.jsx)(AnimatedShadowLayer, {
243
+ params: params,
244
+ width: width,
245
+ height: height,
246
+ defaultShape: shape
247
+ }, idx))
248
+ })
249
+ }), children]
250
+ });
251
+ };
252
+ exports.AnimatedShadow = AnimatedShadow;
253
+ const styles = _reactNative.StyleSheet.create({
254
+ container: {},
255
+ canvas: {
256
+ position: 'absolute',
257
+ pointerEvents: 'none'
258
+ }
259
+ });
260
+ var _default = exports.default = AnimatedShadow;
261
+ //# sourceMappingURL=AnimatedShadow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_reactNativeSkia","_reactNativeReanimated","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","DEFAULTS","fillStyle","kind","color","blurRadius","spread","offsetX","offsetY","pixelRatio","PixelRatio","readValue","v","fallback","undefined","value","AnimatedShadowLayer","params","width","height","defaultShape","shape","shapeOverride","paint","useMemo","p","Skia","Paint","setColor","Color","setShader","factory","derivedBlur","useDerivedValue","offsetTransform","translateX","translateY","scaleTransform","s","sw","sh","scaleX","scaleY","shapeElement","blurChild","jsx","Blur","blur","RoundedRect","x","y","radius","children","Math","min","Circle","cx","cy","Path","path","svgPath","Rect","Group","transform","origin","AnimatedShadow","shadows","_width","_height","maxCanvasPadding","style","layout","setLayout","useState","onLayout","nativeEvent","shadowList","Array","isArray","hasSize","jsxs","View","styles","container","Canvas","canvas","top","left","map","idx","exports","StyleSheet","create","position","pointerEvents","_default"],"sourceRoot":"../../src","sources":["AnimatedShadow.tsx"],"mappings":"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","ignoreList":[]}
@@ -3,6 +3,12 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ Object.defineProperty(exports, "AnimatedShadow", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _AnimatedShadow.AnimatedShadow;
10
+ }
11
+ });
6
12
  Object.defineProperty(exports, "Shadow", {
7
13
  enumerable: true,
8
14
  get: function () {
@@ -22,5 +28,6 @@ Object.defineProperty(exports, "ShadowView", {
22
28
  }
23
29
  });
24
30
  var _Shadow = _interopRequireWildcard(require("./Shadow"));
31
+ var _AnimatedShadow = require("./AnimatedShadow");
25
32
  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); }
26
33
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_Shadow","_interopRequireWildcard","require","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,OAAA,GAAAC,uBAAA,CAAAC,OAAA;AAAmF,SAAAD,wBAAAE,CAAA,EAAAC,CAAA,6BAAAC,OAAA,MAAAC,CAAA,OAAAD,OAAA,IAAAE,CAAA,OAAAF,OAAA,YAAAJ,uBAAA,YAAAA,CAAAE,CAAA,EAAAC,CAAA,SAAAA,CAAA,IAAAD,CAAA,IAAAA,CAAA,CAAAK,UAAA,SAAAL,CAAA,MAAAM,CAAA,EAAAC,CAAA,EAAAC,CAAA,KAAAC,SAAA,QAAAC,OAAA,EAAAV,CAAA,iBAAAA,CAAA,uBAAAA,CAAA,yBAAAA,CAAA,SAAAQ,CAAA,MAAAF,CAAA,GAAAL,CAAA,GAAAG,CAAA,GAAAD,CAAA,QAAAG,CAAA,CAAAK,GAAA,CAAAX,CAAA,UAAAM,CAAA,CAAAM,GAAA,CAAAZ,CAAA,GAAAM,CAAA,CAAAO,GAAA,CAAAb,CAAA,EAAAQ,CAAA,gBAAAP,CAAA,IAAAD,CAAA,gBAAAC,CAAA,OAAAa,cAAA,CAAAC,IAAA,CAAAf,CAAA,EAAAC,CAAA,OAAAM,CAAA,IAAAD,CAAA,GAAAU,MAAA,CAAAC,cAAA,KAAAD,MAAA,CAAAE,wBAAA,CAAAlB,CAAA,EAAAC,CAAA,OAAAM,CAAA,CAAAK,GAAA,IAAAL,CAAA,CAAAM,GAAA,IAAAP,CAAA,CAAAE,CAAA,EAAAP,CAAA,EAAAM,CAAA,IAAAC,CAAA,CAAAP,CAAA,IAAAD,CAAA,CAAAC,CAAA,WAAAO,CAAA,KAAAR,CAAA,EAAAC,CAAA","ignoreList":[]}
1
+ {"version":3,"names":["_Shadow","_interopRequireWildcard","require","_AnimatedShadow","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,OAAA,GAAAC,uBAAA,CAAAC,OAAA;AACA,IAAAC,eAAA,GAAAD,OAAA;AAAkD,SAAAD,wBAAAG,CAAA,EAAAC,CAAA,6BAAAC,OAAA,MAAAC,CAAA,OAAAD,OAAA,IAAAE,CAAA,OAAAF,OAAA,YAAAL,uBAAA,YAAAA,CAAAG,CAAA,EAAAC,CAAA,SAAAA,CAAA,IAAAD,CAAA,IAAAA,CAAA,CAAAK,UAAA,SAAAL,CAAA,MAAAM,CAAA,EAAAC,CAAA,EAAAC,CAAA,KAAAC,SAAA,QAAAC,OAAA,EAAAV,CAAA,iBAAAA,CAAA,uBAAAA,CAAA,yBAAAA,CAAA,SAAAQ,CAAA,MAAAF,CAAA,GAAAL,CAAA,GAAAG,CAAA,GAAAD,CAAA,QAAAG,CAAA,CAAAK,GAAA,CAAAX,CAAA,UAAAM,CAAA,CAAAM,GAAA,CAAAZ,CAAA,GAAAM,CAAA,CAAAO,GAAA,CAAAb,CAAA,EAAAQ,CAAA,gBAAAP,CAAA,IAAAD,CAAA,gBAAAC,CAAA,OAAAa,cAAA,CAAAC,IAAA,CAAAf,CAAA,EAAAC,CAAA,OAAAM,CAAA,IAAAD,CAAA,GAAAU,MAAA,CAAAC,cAAA,KAAAD,MAAA,CAAAE,wBAAA,CAAAlB,CAAA,EAAAC,CAAA,OAAAM,CAAA,CAAAK,GAAA,IAAAL,CAAA,CAAAM,GAAA,IAAAP,CAAA,CAAAE,CAAA,EAAAP,CAAA,EAAAM,CAAA,IAAAC,CAAA,CAAAP,CAAA,IAAAD,CAAA,CAAAC,CAAA,WAAAO,CAAA,KAAAR,CAAA,EAAAC,CAAA","ignoreList":[]}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ //# sourceMappingURL=types.animated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["types.animated.ts"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+
3
+ import React, { useMemo, useState } from 'react';
4
+ import { StyleSheet, View, PixelRatio } from 'react-native';
5
+ import { Canvas, Group, RoundedRect, Rect, Circle, Path, Blur, Skia } from '@shopify/react-native-skia';
6
+ import { useDerivedValue } from "react-native-reanimated";
7
+
8
+ // ── Defaults ────────────────────────────────────────────────────
9
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
+ const DEFAULTS = {
11
+ fillStyle: {
12
+ kind: 'color',
13
+ color: 'rgba(0,0,0,0.10)'
14
+ },
15
+ blurRadius: 24,
16
+ spread: 4,
17
+ offsetX: 0,
18
+ offsetY: 0
19
+ };
20
+ const pixelRatio = PixelRatio.get();
21
+
22
+ /**
23
+ * Read a value that may be static or a SharedValue.
24
+ * Called inside useDerivedValue / on UI thread.
25
+ */
26
+ const readValue = (v, fallback) => {
27
+ 'worklet';
28
+
29
+ if (typeof v === 'number') return v;
30
+ if (v === undefined || v === null) return fallback;
31
+ return v.value;
32
+ };
33
+
34
+ // ── Animated single shadow layer ────────────────────────────────
35
+ const AnimatedShadowLayer = ({
36
+ params,
37
+ width,
38
+ height,
39
+ defaultShape
40
+ }) => {
41
+ const {
42
+ fillStyle = DEFAULTS.fillStyle,
43
+ blurRadius = DEFAULTS.blurRadius,
44
+ spread = DEFAULTS.spread,
45
+ offsetX = DEFAULTS.offsetX,
46
+ offsetY = DEFAULTS.offsetY,
47
+ shape: shapeOverride
48
+ } = params;
49
+ const shape = shapeOverride ?? defaultShape;
50
+
51
+ // ── Paint (static — color changes don't need 60fps) ───────────
52
+ const paint = useMemo(() => {
53
+ const p = Skia.Paint();
54
+ if (fillStyle.kind === 'color') {
55
+ p.setColor(Skia.Color(fillStyle.color));
56
+ } else {
57
+ p.setShader(fillStyle.factory(width, height));
58
+ }
59
+ return p;
60
+ }, [fillStyle, width, height]);
61
+
62
+ // ── Animated blur (derived on UI thread) ──────────────────────
63
+ const derivedBlur = useDerivedValue(() => {
64
+ return readValue(blurRadius, DEFAULTS.blurRadius) / pixelRatio;
65
+ }, [blurRadius]);
66
+
67
+ // ── Animated offset transform ─────────────────────────────────
68
+ const offsetTransform = useDerivedValue(() => {
69
+ return [{
70
+ translateX: readValue(offsetX, DEFAULTS.offsetX)
71
+ }, {
72
+ translateY: readValue(offsetY, DEFAULTS.offsetY)
73
+ }];
74
+ }, [offsetX, offsetY]);
75
+
76
+ // ── Animated spread transform ─────────────────────────────────
77
+ const scaleTransform = useDerivedValue(() => {
78
+ const s = readValue(spread, DEFAULTS.spread);
79
+ const sw = width + s * 2;
80
+ const sh = height + s * 2;
81
+ return [{
82
+ scaleX: width > 0 ? sw / width : 1
83
+ }, {
84
+ scaleY: height > 0 ? sh / height : 1
85
+ }];
86
+ }, [spread, width, height]);
87
+
88
+ // ── Shape element ─────────────────────────────────────────────
89
+ const shapeElement = useMemo(() => {
90
+ const blurChild = /*#__PURE__*/_jsx(Blur, {
91
+ blur: derivedBlur
92
+ });
93
+ switch (shape.kind) {
94
+ case 'roundedRect':
95
+ return /*#__PURE__*/_jsx(RoundedRect, {
96
+ x: 0,
97
+ y: 0,
98
+ width: width,
99
+ height: height,
100
+ r: shape.radius,
101
+ paint: paint,
102
+ children: blurChild
103
+ });
104
+ case 'circle':
105
+ {
106
+ const r = Math.min(width, height) / 2;
107
+ return /*#__PURE__*/_jsx(Circle, {
108
+ cx: width / 2,
109
+ cy: height / 2,
110
+ r: r,
111
+ paint: paint,
112
+ children: blurChild
113
+ });
114
+ }
115
+ case 'path':
116
+ return /*#__PURE__*/_jsx(Path, {
117
+ path: shape.svgPath,
118
+ paint: paint,
119
+ children: blurChild
120
+ });
121
+ case 'rect':
122
+ default:
123
+ return /*#__PURE__*/_jsx(Rect, {
124
+ x: 0,
125
+ y: 0,
126
+ width: width,
127
+ height: height,
128
+ paint: paint,
129
+ children: blurChild
130
+ });
131
+ }
132
+ }, [shape, width, height, paint, derivedBlur]);
133
+ return /*#__PURE__*/_jsx(Group, {
134
+ transform: offsetTransform,
135
+ children: /*#__PURE__*/_jsx(Group, {
136
+ transform: scaleTransform,
137
+ origin: {
138
+ x: width / 2,
139
+ y: height / 2
140
+ },
141
+ children: shapeElement
142
+ })
143
+ });
144
+ };
145
+
146
+ /**
147
+ * `<AnimatedShadow>` — Animated CSS-style box shadows.
148
+ *
149
+ * Same API as `<Shadow>`, but numeric props accept Reanimated
150
+ * `SharedValue<number>` for 60fps animations on the UI thread.
151
+ *
152
+ * Requires `react-native-reanimated` >= 3.0.0.
153
+ *
154
+ * @example
155
+ * ```tsx
156
+ * import { AnimatedShadow } from 'react-native-skia-box-shadow';
157
+ * import { useSharedValue, withSpring } from 'react-native-reanimated';
158
+ *
159
+ * const MyCard = () => {
160
+ * const blur = useSharedValue(16);
161
+ * const offsetY = useSharedValue(4);
162
+ * const spread = useSharedValue(0);
163
+ *
164
+ * const onPressIn = () => {
165
+ * blur.value = withSpring(32);
166
+ * offsetY.value = withSpring(12);
167
+ * spread.value = withSpring(4);
168
+ * };
169
+ *
170
+ * const onPressOut = () => {
171
+ * blur.value = withSpring(16);
172
+ * offsetY.value = withSpring(4);
173
+ * spread.value = withSpring(0);
174
+ * };
175
+ *
176
+ * return (
177
+ * <AnimatedShadow
178
+ * shadows={{
179
+ * fillStyle: { kind: 'color', color: 'rgba(0,0,0,0.15)' },
180
+ * blurRadius: blur,
181
+ * offsetY,
182
+ * spread,
183
+ * }}
184
+ * shape={{ kind: 'roundedRect', radius: 16 }}
185
+ * >
186
+ * <Pressable onPressIn={onPressIn} onPressOut={onPressOut}>
187
+ * <View style={styles.card}>
188
+ * <Text>Press me</Text>
189
+ * </View>
190
+ * </Pressable>
191
+ * </AnimatedShadow>
192
+ * );
193
+ * };
194
+ * ```
195
+ */
196
+ const AnimatedShadow = ({
197
+ shadows,
198
+ shape = {
199
+ kind: 'rect'
200
+ },
201
+ width: _width,
202
+ height: _height,
203
+ maxCanvasPadding = 120,
204
+ style,
205
+ children
206
+ }) => {
207
+ const [layout, setLayout] = useState(null);
208
+ const onLayout = e => {
209
+ const {
210
+ width,
211
+ height
212
+ } = e.nativeEvent.layout;
213
+ setLayout({
214
+ width,
215
+ height
216
+ });
217
+ };
218
+ const width = _width ?? layout?.width ?? 0;
219
+ const height = _height ?? layout?.height ?? 0;
220
+ const shadowList = Array.isArray(shadows) ? shadows : [shadows];
221
+ const hasSize = width > 0 && height > 0;
222
+ return /*#__PURE__*/_jsxs(View, {
223
+ style: [styles.container, style],
224
+ onLayout: onLayout,
225
+ children: [hasSize && /*#__PURE__*/_jsx(Canvas, {
226
+ style: [styles.canvas, {
227
+ top: -maxCanvasPadding,
228
+ left: -maxCanvasPadding,
229
+ width: width + maxCanvasPadding * 2,
230
+ height: height + maxCanvasPadding * 2
231
+ }],
232
+ children: /*#__PURE__*/_jsx(Group, {
233
+ transform: [{
234
+ translateX: maxCanvasPadding
235
+ }, {
236
+ translateY: maxCanvasPadding
237
+ }],
238
+ children: shadowList.map((params, idx) => /*#__PURE__*/_jsx(AnimatedShadowLayer, {
239
+ params: params,
240
+ width: width,
241
+ height: height,
242
+ defaultShape: shape
243
+ }, idx))
244
+ })
245
+ }), children]
246
+ });
247
+ };
248
+ const styles = StyleSheet.create({
249
+ container: {},
250
+ canvas: {
251
+ position: 'absolute',
252
+ pointerEvents: 'none'
253
+ }
254
+ });
255
+ export default AnimatedShadow;
256
+ export { AnimatedShadow };
257
+ //# sourceMappingURL=AnimatedShadow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["React","useMemo","useState","StyleSheet","View","PixelRatio","Canvas","Group","RoundedRect","Rect","Circle","Path","Blur","Skia","useDerivedValue","jsx","_jsx","jsxs","_jsxs","DEFAULTS","fillStyle","kind","color","blurRadius","spread","offsetX","offsetY","pixelRatio","get","readValue","v","fallback","undefined","value","AnimatedShadowLayer","params","width","height","defaultShape","shape","shapeOverride","paint","p","Paint","setColor","Color","setShader","factory","derivedBlur","offsetTransform","translateX","translateY","scaleTransform","s","sw","sh","scaleX","scaleY","shapeElement","blurChild","blur","x","y","r","radius","children","Math","min","cx","cy","path","svgPath","transform","origin","AnimatedShadow","shadows","_width","_height","maxCanvasPadding","style","layout","setLayout","onLayout","e","nativeEvent","shadowList","Array","isArray","hasSize","styles","container","canvas","top","left","map","idx","create","position","pointerEvents"],"sourceRoot":"../../src","sources":["AnimatedShadow.tsx"],"mappings":"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","ignoreList":[]}
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
2
 
3
3
  export { default as Shadow, Shadow as ShadowView, ShadowDefaults } from './Shadow';
4
+ export { AnimatedShadow } from './AnimatedShadow';
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["default","Shadow","ShadowView","ShadowDefaults"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,OAAO,IAAIC,MAAM,EAAEA,MAAM,IAAIC,UAAU,EAAEC,cAAc,QAAQ,UAAU","ignoreList":[]}
1
+ {"version":3,"names":["default","Shadow","ShadowView","ShadowDefaults","AnimatedShadow"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,OAAO,IAAIC,MAAM,EAAEA,MAAM,IAAIC,UAAU,EAAEC,cAAc,QAAQ,UAAU;AAClF,SAASC,cAAc,QAAQ,kBAAkB","ignoreList":[]}
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ export {};
4
+ //# sourceMappingURL=types.animated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["types.animated.ts"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import type { AnimatedShadowProps } from './types.animated';
3
+ /**
4
+ * `<AnimatedShadow>` — Animated CSS-style box shadows.
5
+ *
6
+ * Same API as `<Shadow>`, but numeric props accept Reanimated
7
+ * `SharedValue<number>` for 60fps animations on the UI thread.
8
+ *
9
+ * Requires `react-native-reanimated` >= 3.0.0.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * import { AnimatedShadow } from 'react-native-skia-box-shadow';
14
+ * import { useSharedValue, withSpring } from 'react-native-reanimated';
15
+ *
16
+ * const MyCard = () => {
17
+ * const blur = useSharedValue(16);
18
+ * const offsetY = useSharedValue(4);
19
+ * const spread = useSharedValue(0);
20
+ *
21
+ * const onPressIn = () => {
22
+ * blur.value = withSpring(32);
23
+ * offsetY.value = withSpring(12);
24
+ * spread.value = withSpring(4);
25
+ * };
26
+ *
27
+ * const onPressOut = () => {
28
+ * blur.value = withSpring(16);
29
+ * offsetY.value = withSpring(4);
30
+ * spread.value = withSpring(0);
31
+ * };
32
+ *
33
+ * return (
34
+ * <AnimatedShadow
35
+ * shadows={{
36
+ * fillStyle: { kind: 'color', color: 'rgba(0,0,0,0.15)' },
37
+ * blurRadius: blur,
38
+ * offsetY,
39
+ * spread,
40
+ * }}
41
+ * shape={{ kind: 'roundedRect', radius: 16 }}
42
+ * >
43
+ * <Pressable onPressIn={onPressIn} onPressOut={onPressOut}>
44
+ * <View style={styles.card}>
45
+ * <Text>Press me</Text>
46
+ * </View>
47
+ * </Pressable>
48
+ * </AnimatedShadow>
49
+ * );
50
+ * };
51
+ * ```
52
+ */
53
+ declare const AnimatedShadow: React.FC<AnimatedShadowProps>;
54
+ export default AnimatedShadow;
55
+ export { AnimatedShadow };
56
+ //# sourceMappingURL=AnimatedShadow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnimatedShadow.d.ts","sourceRoot":"","sources":["../../src/AnimatedShadow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAmBjD,OAAO,KAAK,EAGV,mBAAmB,EACpB,MAAM,kBAAkB,CAAC;AAuI1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA8DjD,CAAC;AAUF,eAAe,cAAc,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -1,3 +1,5 @@
1
1
  export { default as Shadow, Shadow as ShadowView, ShadowDefaults } from './Shadow';
2
+ export { AnimatedShadow } from './AnimatedShadow';
2
3
  export type { ShadowProps, ShadowParams, ShadowShape, ShadowFillStyle, } from './types';
4
+ export type { AnimatedShadowProps, AnimatedShadowParams, Animatable, } from './types.animated';
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,IAAI,UAAU,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACnF,YAAY,EACV,WAAW,EACX,YAAY,EACZ,WAAW,EACX,eAAe,GAChB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,IAAI,UAAU,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EACV,WAAW,EACX,YAAY,EACZ,WAAW,EACX,eAAe,GAChB,MAAM,SAAS,CAAC;AACjB,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,UAAU,GACX,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { SharedValue } from 'react-native-reanimated';
2
+ import type { ReactNode } from 'react';
3
+ import type { ViewStyle, StyleProp } from 'react-native';
4
+ import type { ShadowShape, ShadowFillStyle } from './types';
5
+ /**
6
+ * A value that can be either static or a Reanimated SharedValue.
7
+ * When animated, Skia reads it directly on the UI thread — no React re-renders.
8
+ */
9
+ export type Animatable<T> = T | SharedValue<T>;
10
+ /**
11
+ * Animated shadow layer descriptor.
12
+ * Any numeric prop can be a SharedValue for 60fps animation.
13
+ */
14
+ export interface AnimatedShadowParams {
15
+ /** Fill style (not animatable — use opacity in color for fade effects) */
16
+ fillStyle?: ShadowFillStyle;
17
+ /** Gaussian blur radius. Animatable. Default: 24 */
18
+ blurRadius?: Animatable<number>;
19
+ /** Expands the shadow outline beyond element bounds. Animatable. Default: 4 */
20
+ spread?: Animatable<number>;
21
+ /** Horizontal offset. Animatable. Default: 0 */
22
+ offsetX?: Animatable<number>;
23
+ /** Vertical offset. Animatable. Default: 0 */
24
+ offsetY?: Animatable<number>;
25
+ /** Shape override for this layer */
26
+ shape?: ShadowShape;
27
+ }
28
+ /**
29
+ * Props for the `<AnimatedShadow>` component.
30
+ */
31
+ export interface AnimatedShadowProps {
32
+ /** One or more animated shadow layers */
33
+ shadows: AnimatedShadowParams | AnimatedShadowParams[];
34
+ /** Default shape. Default: rect */
35
+ shape?: ShadowShape;
36
+ /** Explicit width */
37
+ width?: number;
38
+ /** Explicit height */
39
+ height?: number;
40
+ /**
41
+ * Maximum canvas padding to accommodate animated values.
42
+ * Since blur/spread/offset may change at runtime, set this to
43
+ * the largest extent the shadow can reach.
44
+ * Default: 120
45
+ */
46
+ maxCanvasPadding?: number;
47
+ /** RN style for the outer container */
48
+ style?: StyleProp<ViewStyle>;
49
+ /** Content rendered above the shadow */
50
+ children?: ReactNode;
51
+ }
52
+ //# sourceMappingURL=types.animated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.animated.d.ts","sourceRoot":"","sources":["../../src/types.animated.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE5D;;;GAGG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,oDAAoD;IACpD,UAAU,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAChC,+EAA+E;IAC/E,MAAM,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC5B,gDAAgD;IAChD,OAAO,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC7B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC7B,oCAAoC;IACpC,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,yCAAyC;IACzC,OAAO,EAAE,oBAAoB,GAAG,oBAAoB,EAAE,CAAC;IACvD,mCAAmC;IACnC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uCAAuC;IACvC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-skia-box-shadow",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "CSS-style box shadows for React Native — blur, spread, offset, colors, gradients, and arbitrary shapes. Powered by @shopify/react-native-skia.",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -30,11 +30,13 @@
30
30
  "spread",
31
31
  "ios",
32
32
  "android",
33
- "react-native-skia"
33
+ "react-native-skia",
34
+ "animated",
35
+ "reanimated"
34
36
  ],
35
37
  "repository": {
36
38
  "type": "git",
37
- "url": "git+https://github.com/nickstetsiuk/react-native-skia-box-shadow.git"
39
+ "url": "git+https://github.com/vasyl-stetsiuk/react-native-skia-box-shadow.git"
38
40
  },
39
41
  "author": {
40
42
  "name": "Vasyl Stetsiuk",
@@ -42,26 +44,31 @@
42
44
  },
43
45
  "license": "MIT",
44
46
  "bugs": {
45
- "url": "https://github.com/nickstetsiuk/react-native-skia-box-shadow/issues"
47
+ "url": "https://github.com/vasyl-stetsiuk/react-native-skia-box-shadow/issues"
46
48
  },
47
- "homepage": "https://github.com/nickstetsiuk/react-native-skia-box-shadow#readme",
49
+ "homepage": "https://github.com/vasyl-stetsiuk/react-native-skia-box-shadow#readme",
48
50
  "peerDependencies": {
49
51
  "@shopify/react-native-skia": ">=1.0.0",
50
52
  "react": ">=18.0.0",
51
- "react-native": ">=0.71.0"
53
+ "react-native": ">=0.71.0",
54
+ "react-native-reanimated": ">=3.0.0"
52
55
  },
53
56
  "peerDependenciesMeta": {
54
57
  "@shopify/react-native-skia": {
55
58
  "optional": false
59
+ },
60
+ "react-native-reanimated": {
61
+ "optional": true
56
62
  }
57
63
  },
58
64
  "devDependencies": {
59
65
  "@shopify/react-native-skia": "^1.11.0",
60
66
  "@types/react": "^18.2.0",
61
- "@types/react-native": "^0.72.0",
67
+ "@types/react-native": "^0.73.0",
62
68
  "react": "^18.2.0",
63
69
  "react-native": "^0.75.0",
64
70
  "react-native-builder-bob": "^0.30.0",
71
+ "react-native-reanimated": "^3.16.0",
65
72
  "typescript": "^5.4.0"
66
73
  },
67
74
  "react-native-builder-bob": {
@@ -0,0 +1,282 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import {
3
+ StyleSheet,
4
+ View,
5
+ PixelRatio,
6
+ type LayoutChangeEvent,
7
+ } from 'react-native';
8
+ import {
9
+ Canvas,
10
+ Group,
11
+ RoundedRect,
12
+ Rect,
13
+ Circle,
14
+ Path,
15
+ Blur,
16
+ Skia,
17
+ } from '@shopify/react-native-skia';
18
+
19
+ import type { ShadowShape, ShadowFillStyle } from './types';
20
+ import type {
21
+ Animatable,
22
+ AnimatedShadowParams,
23
+ AnimatedShadowProps,
24
+ } from './types.animated';
25
+ import {useDerivedValue} from "react-native-reanimated";
26
+
27
+ // ── Defaults ────────────────────────────────────────────────────
28
+ const DEFAULTS = {
29
+ fillStyle: { kind: 'color', color: 'rgba(0,0,0,0.10)' } as ShadowFillStyle,
30
+ blurRadius: 24,
31
+ spread: 4,
32
+ offsetX: 0,
33
+ offsetY: 0,
34
+ } as const;
35
+
36
+ const pixelRatio = PixelRatio.get();
37
+
38
+ /**
39
+ * Read a value that may be static or a SharedValue.
40
+ * Called inside useDerivedValue / on UI thread.
41
+ */
42
+ const readValue = (v: Animatable<number>, fallback: number): number => {
43
+ 'worklet';
44
+ if (typeof v === 'number') return v;
45
+ if (v === undefined || v === null) return fallback;
46
+ return v.value;
47
+ };
48
+
49
+ // ── Animated single shadow layer ────────────────────────────────
50
+ const AnimatedShadowLayer: React.FC<{
51
+ params: AnimatedShadowParams;
52
+ width: number;
53
+ height: number;
54
+ defaultShape: ShadowShape;
55
+ }> = ({ params, width, height, defaultShape }) => {
56
+ const {
57
+ fillStyle = DEFAULTS.fillStyle,
58
+ blurRadius = DEFAULTS.blurRadius,
59
+ spread = DEFAULTS.spread,
60
+ offsetX = DEFAULTS.offsetX,
61
+ offsetY = DEFAULTS.offsetY,
62
+ shape: shapeOverride,
63
+ } = params;
64
+
65
+ const shape = shapeOverride ?? defaultShape;
66
+
67
+ // ── Paint (static — color changes don't need 60fps) ───────────
68
+ const paint = useMemo(() => {
69
+ const p = Skia.Paint();
70
+ if (fillStyle.kind === 'color') {
71
+ p.setColor(Skia.Color(fillStyle.color));
72
+ } else {
73
+ p.setShader(fillStyle.factory(width, height));
74
+ }
75
+ return p;
76
+ }, [fillStyle, width, height]);
77
+
78
+ // ── Animated blur (derived on UI thread) ──────────────────────
79
+ const derivedBlur = useDerivedValue(() => {
80
+ return readValue(blurRadius, DEFAULTS.blurRadius) / pixelRatio;
81
+ }, [blurRadius]);
82
+
83
+ // ── Animated offset transform ─────────────────────────────────
84
+ const offsetTransform = useDerivedValue(() => {
85
+ return [
86
+ { translateX: readValue(offsetX, DEFAULTS.offsetX) },
87
+ { translateY: readValue(offsetY, DEFAULTS.offsetY) },
88
+ ];
89
+ }, [offsetX, offsetY]);
90
+
91
+ // ── Animated spread transform ─────────────────────────────────
92
+ const scaleTransform = useDerivedValue(() => {
93
+ const s = readValue(spread, DEFAULTS.spread);
94
+ const sw = width + s * 2;
95
+ const sh = height + s * 2;
96
+ return [
97
+ { scaleX: width > 0 ? sw / width : 1 },
98
+ { scaleY: height > 0 ? sh / height : 1 },
99
+ ];
100
+ }, [spread, width, height]);
101
+
102
+ // ── Shape element ─────────────────────────────────────────────
103
+ const shapeElement = useMemo(() => {
104
+ const blurChild = <Blur blur={derivedBlur} />;
105
+
106
+ switch (shape.kind) {
107
+ case 'roundedRect':
108
+ return (
109
+ <RoundedRect
110
+ x={0}
111
+ y={0}
112
+ width={width}
113
+ height={height}
114
+ r={shape.radius}
115
+ paint={paint}
116
+ >
117
+ {blurChild}
118
+ </RoundedRect>
119
+ );
120
+
121
+ case 'circle': {
122
+ const r = Math.min(width, height) / 2;
123
+ return (
124
+ <Circle cx={width / 2} cy={height / 2} r={r} paint={paint}>
125
+ {blurChild}
126
+ </Circle>
127
+ );
128
+ }
129
+
130
+ case 'path':
131
+ return (
132
+ <Path path={shape.svgPath} paint={paint}>
133
+ {blurChild}
134
+ </Path>
135
+ );
136
+
137
+ case 'rect':
138
+ default:
139
+ return (
140
+ <Rect x={0} y={0} width={width} height={height} paint={paint}>
141
+ {blurChild}
142
+ </Rect>
143
+ );
144
+ }
145
+ }, [shape, width, height, paint, derivedBlur]);
146
+
147
+ return (
148
+ <Group transform={offsetTransform}>
149
+ <Group
150
+ transform={scaleTransform}
151
+ origin={{ x: width / 2, y: height / 2 }}
152
+ >
153
+ {shapeElement}
154
+ </Group>
155
+ </Group>
156
+ );
157
+ };
158
+
159
+ /**
160
+ * `<AnimatedShadow>` — Animated CSS-style box shadows.
161
+ *
162
+ * Same API as `<Shadow>`, but numeric props accept Reanimated
163
+ * `SharedValue<number>` for 60fps animations on the UI thread.
164
+ *
165
+ * Requires `react-native-reanimated` >= 3.0.0.
166
+ *
167
+ * @example
168
+ * ```tsx
169
+ * import { AnimatedShadow } from 'react-native-skia-box-shadow';
170
+ * import { useSharedValue, withSpring } from 'react-native-reanimated';
171
+ *
172
+ * const MyCard = () => {
173
+ * const blur = useSharedValue(16);
174
+ * const offsetY = useSharedValue(4);
175
+ * const spread = useSharedValue(0);
176
+ *
177
+ * const onPressIn = () => {
178
+ * blur.value = withSpring(32);
179
+ * offsetY.value = withSpring(12);
180
+ * spread.value = withSpring(4);
181
+ * };
182
+ *
183
+ * const onPressOut = () => {
184
+ * blur.value = withSpring(16);
185
+ * offsetY.value = withSpring(4);
186
+ * spread.value = withSpring(0);
187
+ * };
188
+ *
189
+ * return (
190
+ * <AnimatedShadow
191
+ * shadows={{
192
+ * fillStyle: { kind: 'color', color: 'rgba(0,0,0,0.15)' },
193
+ * blurRadius: blur,
194
+ * offsetY,
195
+ * spread,
196
+ * }}
197
+ * shape={{ kind: 'roundedRect', radius: 16 }}
198
+ * >
199
+ * <Pressable onPressIn={onPressIn} onPressOut={onPressOut}>
200
+ * <View style={styles.card}>
201
+ * <Text>Press me</Text>
202
+ * </View>
203
+ * </Pressable>
204
+ * </AnimatedShadow>
205
+ * );
206
+ * };
207
+ * ```
208
+ */
209
+ const AnimatedShadow: React.FC<AnimatedShadowProps> = ({
210
+ shadows,
211
+ shape = { kind: 'rect' },
212
+ width: _width,
213
+ height: _height,
214
+ maxCanvasPadding = 120,
215
+ style,
216
+ children,
217
+ }) => {
218
+ const [layout, setLayout] = useState<{
219
+ width: number;
220
+ height: number;
221
+ } | null>(null);
222
+
223
+ const onLayout = (e: LayoutChangeEvent) => {
224
+ const { width, height } = e.nativeEvent.layout;
225
+ setLayout({ width, height });
226
+ };
227
+
228
+ const width = _width ?? layout?.width ?? 0;
229
+ const height = _height ?? layout?.height ?? 0;
230
+
231
+ const shadowList = Array.isArray(shadows) ? shadows : [shadows];
232
+
233
+ const hasSize = width > 0 && height > 0;
234
+
235
+ return (
236
+ <View style={[styles.container, style]} onLayout={onLayout}>
237
+ {hasSize && (
238
+ <Canvas
239
+ style={[
240
+ styles.canvas,
241
+ {
242
+ top: -maxCanvasPadding,
243
+ left: -maxCanvasPadding,
244
+ width: width + maxCanvasPadding * 2,
245
+ height: height + maxCanvasPadding * 2,
246
+ },
247
+ ]}
248
+ >
249
+ <Group
250
+ transform={[
251
+ { translateX: maxCanvasPadding },
252
+ { translateY: maxCanvasPadding },
253
+ ]}
254
+ >
255
+ {shadowList.map((params, idx) => (
256
+ <AnimatedShadowLayer
257
+ key={idx}
258
+ params={params}
259
+ width={width}
260
+ height={height}
261
+ defaultShape={shape}
262
+ />
263
+ ))}
264
+ </Group>
265
+ </Canvas>
266
+ )}
267
+
268
+ {children}
269
+ </View>
270
+ );
271
+ };
272
+
273
+ const styles = StyleSheet.create({
274
+ container: {},
275
+ canvas: {
276
+ position: 'absolute',
277
+ pointerEvents: 'none',
278
+ },
279
+ });
280
+
281
+ export default AnimatedShadow;
282
+ export { AnimatedShadow };
package/src/index.ts CHANGED
@@ -1,7 +1,13 @@
1
1
  export { default as Shadow, Shadow as ShadowView, ShadowDefaults } from './Shadow';
2
+ export { AnimatedShadow } from './AnimatedShadow';
2
3
  export type {
3
4
  ShadowProps,
4
5
  ShadowParams,
5
6
  ShadowShape,
6
7
  ShadowFillStyle,
7
8
  } from './types';
9
+ export type {
10
+ AnimatedShadowProps,
11
+ AnimatedShadowParams,
12
+ Animatable,
13
+ } from './types.animated';
@@ -0,0 +1,54 @@
1
+ import type { SharedValue } from 'react-native-reanimated';
2
+ import type { ReactNode } from 'react';
3
+ import type { ViewStyle, StyleProp } from 'react-native';
4
+ import type { ShadowShape, ShadowFillStyle } from './types';
5
+
6
+ /**
7
+ * A value that can be either static or a Reanimated SharedValue.
8
+ * When animated, Skia reads it directly on the UI thread — no React re-renders.
9
+ */
10
+ export type Animatable<T> = T | SharedValue<T>;
11
+
12
+ /**
13
+ * Animated shadow layer descriptor.
14
+ * Any numeric prop can be a SharedValue for 60fps animation.
15
+ */
16
+ export interface AnimatedShadowParams {
17
+ /** Fill style (not animatable — use opacity in color for fade effects) */
18
+ fillStyle?: ShadowFillStyle;
19
+ /** Gaussian blur radius. Animatable. Default: 24 */
20
+ blurRadius?: Animatable<number>;
21
+ /** Expands the shadow outline beyond element bounds. Animatable. Default: 4 */
22
+ spread?: Animatable<number>;
23
+ /** Horizontal offset. Animatable. Default: 0 */
24
+ offsetX?: Animatable<number>;
25
+ /** Vertical offset. Animatable. Default: 0 */
26
+ offsetY?: Animatable<number>;
27
+ /** Shape override for this layer */
28
+ shape?: ShadowShape;
29
+ }
30
+
31
+ /**
32
+ * Props for the `<AnimatedShadow>` component.
33
+ */
34
+ export interface AnimatedShadowProps {
35
+ /** One or more animated shadow layers */
36
+ shadows: AnimatedShadowParams | AnimatedShadowParams[];
37
+ /** Default shape. Default: rect */
38
+ shape?: ShadowShape;
39
+ /** Explicit width */
40
+ width?: number;
41
+ /** Explicit height */
42
+ height?: number;
43
+ /**
44
+ * Maximum canvas padding to accommodate animated values.
45
+ * Since blur/spread/offset may change at runtime, set this to
46
+ * the largest extent the shadow can reach.
47
+ * Default: 120
48
+ */
49
+ maxCanvasPadding?: number;
50
+ /** RN style for the outer container */
51
+ style?: StyleProp<ViewStyle>;
52
+ /** Content rendered above the shadow */
53
+ children?: ReactNode;
54
+ }