@@ -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,135 @@ 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
+ if (
149
+ process . env . NODE_ENV !== 'production' &&
150
+ filteredStyle . overflow === 'hidden' &&
151
+ elevation !== 0
152
+ ) {
153
+ console . warn (
154
+ '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.'
155
+ ) ;
156
+ }
157
+
158
+ const outerLayerViewStyles = {
159
+ ...getStyleForShadowLayer ( elevation , 0 ) ,
160
+ ...marginStyle ,
161
+ position,
162
+ alignSelf,
163
+ top,
164
+ right,
165
+ bottom,
166
+ left,
167
+ start,
168
+ end,
169
+ flex,
170
+ width,
171
+ height,
172
+ transform,
173
+ opacity,
174
+ } ;
175
+
176
+ const innerLayerViewStyles = {
177
+ ...getStyleForShadowLayer ( elevation , 1 ) ,
178
+ ...filteredStyle ,
179
+ flex : height ? 1 : undefined ,
180
+ backgroundColor : backgroundColorStyle || backgroundColor ,
181
+ } ;
182
+
183
+ return [ outerLayerViewStyles , innerLayerViewStyles ] ;
184
+ } , [ style , elevation , backgroundColor ] ) ;
185
+
186
+ return (
187
+ < Animated . View
188
+ ref = { ref }
189
+ style = { outerLayerViewStyles }
190
+ testID = { `${ testID } -outer-layer` }
191
+ >
192
+ < Animated . View { ...props } style = { innerLayerViewStyles } testID = { testID } >
193
+ { children }
194
+ </ Animated . View >
195
+ </ Animated . View >
196
+ ) ;
197
+ } ) ;
198
+
68
199
/**
69
200
* Surface is a basic container that can give depth to an element with elevation shadow.
70
201
* On dark theme with `adaptive` mode, surface is constructed by also placing a semi-transparent white overlay over a component surface.
@@ -205,146 +336,16 @@ const Surface = forwardRef<View, Props>(
205
336
) ;
206
337
}
207
338
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
339
return (
335
- < Animated . View
336
- ref = { ref }
337
- style = { [ getStyleForShadowLayer ( 0 ) , outerLayerViewStyles ] }
338
- testID = { `${ testID } -outer-layer` }
340
+ < SurfaceIOS
341
+ { ...props }
342
+ elevation = { elevation }
343
+ backgroundColor = { backgroundColor }
344
+ style = { style }
345
+ testID = { testID }
339
346
>
340
- < Animated . View
341
- { ...props }
342
- style = { [ getStyleForShadowLayer ( 1 ) , innerLayerViewStyles ] }
343
- testID = { testID }
344
- >
345
- { children }
346
- </ Animated . View >
347
- </ Animated . View >
347
+ { children }
348
+ </ SurfaceIOS >
348
349
) ;
349
350
}
350
351
) ;
0 commit comments