@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
import {
3
3
Animated ,
4
4
Platform ,
5
+ ShadowStyleIOS ,
5
6
StyleProp ,
6
7
StyleSheet ,
7
8
View ,
@@ -15,6 +16,8 @@ import type { ThemeProp, MD3Elevation } from '../types';
15
16
import { forwardRef } from '../utils/forwardRef' ;
16
17
import { splitStyles } from '../utils/splitStyles' ;
17
18
19
+ type Elevation = 0 | 1 | 2 | 3 | 4 | 5 | Animated . Value ;
20
+
18
21
export type Props = React . ComponentPropsWithRef < typeof View > & {
19
22
/**
20
23
* Content of the `Surface`.
@@ -29,7 +32,7 @@ export type Props = React.ComponentPropsWithRef<typeof View> & {
29
32
* Note: In version 2 the `elevation` prop was accepted via `style` prop i.e. `style={{ elevation: 4 }}`.
30
33
* It's no longer supported with theme version 3 and you should use `elevation` property instead.
31
34
*/
32
- elevation ?: 0 | 1 | 2 | 3 | 4 | 5 | Animated . Value ;
35
+ elevation ?: Elevation ;
33
36
/**
34
37
* @optional
35
38
*/
@@ -65,6 +68,138 @@ const MD2Surface = forwardRef<View, Props>(
65
68
}
66
69
) ;
67
70
71
+ const shadowColor = '#000' ;
72
+ const iOSShadowOutputRanges = [
73
+ {
74
+ shadowOpacity : 0.15 ,
75
+ height : [ 0 , 1 , 2 , 4 , 6 , 8 ] ,
76
+ shadowRadius : [ 0 , 3 , 6 , 8 , 10 , 12 ] ,
77
+ } ,
78
+ {
79
+ shadowOpacity : 0.3 ,
80
+ height : [ 0 , 1 , 1 , 1 , 2 , 4 ] ,
81
+ shadowRadius : [ 0 , 1 , 2 , 3 , 3 , 4 ] ,
82
+ } ,
83
+ ] ;
84
+ const inputRange = [ 0 , 1 , 2 , 3 , 4 , 5 ] ;
85
+ function getStyleForShadowLayer (
86
+ elevation : Elevation ,
87
+ layer : 0 | 1
88
+ ) : Animated . WithAnimatedValue < ShadowStyleIOS > {
89
+ if ( isAnimatedValue ( elevation ) ) {
90
+ return {
91
+ shadowColor,
92
+ shadowOpacity : elevation . interpolate ( {
93
+ inputRange : [ 0 , 1 ] ,
94
+ outputRange : [ 0 , iOSShadowOutputRanges [ layer ] . shadowOpacity ] ,
95
+ extrapolate : 'clamp' ,
96
+ } ) ,
97
+ shadowOffset : {
98
+ width : 0 ,
99
+ height : elevation . interpolate ( {
100
+ inputRange,
101
+ outputRange : iOSShadowOutputRanges [ layer ] . height ,
102
+ } ) ,
103
+ } ,
104
+ shadowRadius : elevation . interpolate ( {
105
+ inputRange,
106
+ outputRange : iOSShadowOutputRanges [ layer ] . shadowRadius ,
107
+ } ) ,
108
+ } ;
109
+ }
110
+
111
+ return {
112
+ shadowColor,
113
+ shadowOpacity : elevation ? iOSShadowOutputRanges [ layer ] . shadowOpacity : 0 ,
114
+ shadowOffset : {
115
+ width : 0 ,
116
+ height : iOSShadowOutputRanges [ layer ] . height [ elevation ] ,
117
+ } ,
118
+ shadowRadius : iOSShadowOutputRanges [ layer ] . shadowRadius [ elevation ] ,
119
+ } ;
120
+ }
121
+
122
+ const SurfaceIOS = forwardRef <
123
+ View ,
124
+ Omit < Props , 'elevation' > & {
125
+ elevation : Elevation ;
126
+ backgroundColor ?: string | Animated . AnimatedInterpolation < string | number > ;
127
+ }
128
+ > ( ( { elevation, style, backgroundColor, testID, children, ...props } , ref ) => {
129
+ const [ outerLayerViewStyles , innerLayerViewStyles ] = React . useMemo ( ( ) => {
130
+ const {
131
+ position,
132
+ alignSelf,
133
+ top,
134
+ left,
135
+ right,
136
+ bottom,
137
+ start,
138
+ end,
139
+ flex,
140
+ backgroundColor : backgroundColorStyle ,
141
+ width,
142
+ height,
143
+ transform,
144
+ opacity,
145
+ ...restStyle
146
+ } = ( StyleSheet . flatten ( style ) || { } ) as ViewStyle ;
147
+
148
+ const [ filteredStyles , marginStyles ] = splitStyles ( restStyle , ( style ) =>
149
+ style . startsWith ( 'margin' )
150
+ ) ;
151
+
152
+ if (
153
+ process . env . NODE_ENV !== 'production' &&
154
+ filteredStyles . overflow === 'hidden' &&
155
+ elevation !== 0
156
+ ) {
157
+ console . warn (
158
+ 'When setting overflow to hidden on Surface the shadow will not be displayed correctly. Wrap the content of your component in a separate View with the overflow style.'
159
+ ) ;
160
+ }
161
+
162
+ const outerLayerViewStyles = {
163
+ ...getStyleForShadowLayer ( elevation , 0 ) ,
164
+ ...marginStyles ,
165
+ position,
166
+ alignSelf,
167
+ top,
168
+ right,
169
+ bottom,
170
+ left,
171
+ start,
172
+ end,
173
+ flex,
174
+ width,
175
+ height,
176
+ transform,
177
+ opacity,
178
+ } ;
179
+
180
+ const innerLayerViewStyles = {
181
+ ...getStyleForShadowLayer ( elevation , 1 ) ,
182
+ ...filteredStyles ,
183
+ flex : height ? 1 : undefined ,
184
+ backgroundColor : backgroundColorStyle || backgroundColor ,
185
+ } ;
186
+
187
+ return [ outerLayerViewStyles , innerLayerViewStyles ] ;
188
+ } , [ style , elevation , backgroundColor ] ) ;
189
+
190
+ return (
191
+ < Animated . View
192
+ ref = { ref }
193
+ style = { outerLayerViewStyles }
194
+ testID = { `${ testID } -outer-layer` }
195
+ >
196
+ < Animated . View { ...props } style = { innerLayerViewStyles } testID = { testID } >
197
+ { children }
198
+ </ Animated . View >
199
+ </ Animated . View >
200
+ ) ;
201
+ } ) ;
202
+
68
203
/**
69
204
* Surface is a basic container that can give depth to an element with elevation shadow.
70
205
* On dark theme with `adaptive` mode, surface is constructed by also placing a semi-transparent white overlay over a component surface.
@@ -205,146 +340,16 @@ const Surface = forwardRef<View, Props>(
205
340
) ;
206
341
}
207
342
208
- const iOSShadowOutputRanges = [
209
- {
210
- shadowOpacity : 0.15 ,
211
- height : [ 0 , 1 , 2 , 4 , 6 , 8 ] ,
212
- shadowRadius : [ 0 , 3 , 6 , 8 , 10 , 12 ] ,
213
- } ,
214
- {
215
- shadowOpacity : 0.3 ,
216
- height : [ 0 , 1 , 1 , 1 , 2 , 4 ] ,
217
- shadowRadius : [ 0 , 1 , 2 , 3 , 3 , 4 ] ,
218
- } ,
219
- ] ;
220
-
221
- const shadowColor = '#000' ;
222
-
223
- const {
224
- position,
225
- alignSelf,
226
- top,
227
- left,
228
- right,
229
- bottom,
230
- start,
231
- end,
232
- flex,
233
- backgroundColor : backgroundColorStyle ,
234
- width,
235
- height,
236
- transform,
237
- opacity,
238
- ...restStyle
239
- } = ( StyleSheet . flatten ( style ) || { } ) as ViewStyle ;
240
-
241
- const [ filteredStyle , marginStyle ] = splitStyles ( restStyle , ( style ) =>
242
- style . startsWith ( 'margin' )
243
- ) ;
244
-
245
- if (
246
- process . env . NODE_ENV !== 'production' &&
247
- filteredStyle . overflow === 'hidden' &&
248
- elevation !== 0
249
- ) {
250
- console . warn (
251
- 'When setting overflow to hidden on Surface the shadow will not be displayed correctly. Wrap the content of your component in a separate View with the overflow style.'
252
- ) ;
253
- }
254
-
255
- const innerLayerViewStyles = [
256
- filteredStyle ,
257
- {
258
- flex : height ? 1 : undefined ,
259
- backgroundColor : backgroundColorStyle || backgroundColor ,
260
- } ,
261
- ] ;
262
-
263
- const outerLayerViewStyles = {
264
- position,
265
- alignSelf,
266
- top,
267
- right,
268
- bottom,
269
- left,
270
- start,
271
- end,
272
- flex,
273
- width,
274
- height,
275
- transform,
276
- opacity,
277
- ...marginStyle ,
278
- } ;
279
-
280
- if ( isAnimatedValue ( elevation ) ) {
281
- const inputRange = [ 0 , 1 , 2 , 3 , 4 , 5 ] ;
282
-
283
- const getStyleForAnimatedShadowLayer = ( layer : 0 | 1 ) => {
284
- return {
285
- shadowColor,
286
- shadowOpacity : elevation . interpolate ( {
287
- inputRange : [ 0 , 1 ] ,
288
- outputRange : [ 0 , iOSShadowOutputRanges [ layer ] . shadowOpacity ] ,
289
- extrapolate : 'clamp' ,
290
- } ) ,
291
- shadowOffset : {
292
- width : 0 ,
293
- height : elevation . interpolate ( {
294
- inputRange,
295
- outputRange : iOSShadowOutputRanges [ layer ] . height ,
296
- } ) ,
297
- } ,
298
- shadowRadius : elevation . interpolate ( {
299
- inputRange,
300
- outputRange : iOSShadowOutputRanges [ layer ] . shadowRadius ,
301
- } ) ,
302
- } ;
303
- } ;
304
-
305
- return (
306
- < Animated . View
307
- style = { [ getStyleForAnimatedShadowLayer ( 0 ) , outerLayerViewStyles ] }
308
- testID = { `${ testID } -outer-layer` }
309
- >
310
- < Animated . View
311
- style = { [ getStyleForAnimatedShadowLayer ( 1 ) , innerLayerViewStyles ] }
312
- testID = { testID }
313
- >
314
- { children }
315
- </ Animated . View >
316
- </ Animated . View >
317
- ) ;
318
- }
319
-
320
- const getStyleForShadowLayer = ( layer : 0 | 1 ) => {
321
- return {
322
- shadowColor,
323
- shadowOpacity : elevation
324
- ? iOSShadowOutputRanges [ layer ] . shadowOpacity
325
- : 0 ,
326
- shadowOffset : {
327
- width : 0 ,
328
- height : iOSShadowOutputRanges [ layer ] . height [ elevation ] ,
329
- } ,
330
- shadowRadius : iOSShadowOutputRanges [ layer ] . shadowRadius [ elevation ] ,
331
- } ;
332
- } ;
333
-
334
343
return (
335
- < Animated . View
336
- ref = { ref }
337
- style = { [ getStyleForShadowLayer ( 0 ) , outerLayerViewStyles ] }
338
- testID = { `${ testID } -outer-layer` }
344
+ < SurfaceIOS
345
+ { ...props }
346
+ elevation = { elevation }
347
+ backgroundColor = { backgroundColor }
348
+ style = { style }
349
+ testID = { testID }
339
350
>
340
- < Animated . View
341
- { ...props }
342
- style = { [ getStyleForShadowLayer ( 1 ) , innerLayerViewStyles ] }
343
- testID = { testID }
344
- >
345
- { children }
346
- </ Animated . View >
347
- </ Animated . View >
351
+ { children }
352
+ </ SurfaceIOS >
348
353
) ;
349
354
}
350
355
) ;
0 commit comments