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