Skip to content

Commit 0e4df75

Browse files
committed
feat: add elevation tokens and fix Android dp spec bug
1 parent f1e1cf1 commit 0e4df75

26 files changed

Lines changed: 368 additions & 313 deletions

src/components/Appbar/AppbarHeader.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
getAppbarBorders,
1919
} from './utils';
2020
import { useInternalTheme } from '../../core/theming';
21-
import shadow from '../../theme/shadow';
21+
import { shadow } from '../../theme/tokens/sys/elevation';
2222
import type { ThemeProp } from '../../types';
2323

2424
export type Props = Omit<
@@ -141,7 +141,7 @@ const AppbarHeader = ({
141141
paddingHorizontal: Math.max(left, right),
142142
},
143143
borderRadius,
144-
shadow(elevation) as ViewStyle,
144+
shadow(elevation, theme.colors.shadow) as ViewStyle,
145145
]}
146146
>
147147
<Appbar

src/components/Menu/Menu.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
2323

2424
import MenuItem from './MenuItem';
2525
import { useInternalTheme } from '../../core/theming';
26-
import type { Elevation, Theme, ThemeProp } from '../../types';
27-
import { ElevationLevels } from '../../types';
26+
import type { Elevation, ElevationLevel, Theme, ThemeProp } from '../../types';
2827
import { addEventListener } from '../../utils/addEventListener';
2928
import { BackHandler } from '../../utils/BackHandler/BackHandler';
3029
import Portal from '../Portal/Portal';
@@ -105,10 +104,6 @@ const EASING = Easing.bezier(0.4, 0, 0.2, 1);
105104
const WINDOW_LAYOUT = Dimensions.get('window');
106105

107106
const DEFAULT_ELEVATION: Elevation = 2;
108-
export const ELEVATION_LEVELS_MAP = Object.values(
109-
ElevationLevels
110-
) as ElevationLevels[];
111-
112107
const DEFAULT_MODE = 'elevated';
113108

114109
const focusFirstDOMNode = (el: View | null | undefined) => {
@@ -675,7 +670,9 @@ const Menu = ({
675670
shadowMenuContainerStyle,
676671
{
677672
backgroundColor:
678-
md3Colors.elevation[ELEVATION_LEVELS_MAP[elevation]],
673+
md3Colors.elevation[
674+
`level${elevation}` as ElevationLevel
675+
],
679676
},
680677
contentStyle,
681678
]}

src/components/Surface.tsx

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import {
1010
} from 'react-native';
1111

1212
import { useInternalTheme } from '../core/theming';
13-
import shadow from '../theme/shadow';
13+
import {
14+
androidElevationLevels,
15+
elevationInputRange,
16+
shadow,
17+
shadowLayers,
18+
} from '../theme/tokens/sys/elevation';
1419
import type { Elevation, Theme, ThemeProp } from '../types';
1520
import { isAnimatedValue } from '../utils/animations';
1621
import { forwardRef } from '../utils/forwardRef';
@@ -75,54 +80,41 @@ const outerLayerStyleProperties: (keyof ViewStyle)[] = [
7580
'opacity',
7681
];
7782

78-
const shadowColor = '#000';
79-
const iOSShadowOutputRanges = [
80-
{
81-
shadowOpacity: 0.15,
82-
height: [0, 1, 2, 4, 6, 8],
83-
shadowRadius: [0, 3, 6, 8, 10, 12],
84-
},
85-
{
86-
shadowOpacity: 0.3,
87-
height: [0, 1, 1, 1, 2, 4],
88-
shadowRadius: [0, 1, 2, 3, 3, 4],
89-
},
90-
];
91-
const inputRange = [0, 1, 2, 3, 4, 5];
9283
function getStyleForShadowLayer(
9384
elevation: SurfaceElevation,
94-
layer: 0 | 1
85+
layer: 0 | 1,
86+
shadowColor: string
9587
): Animated.WithAnimatedValue<ShadowStyleIOS> {
9688
if (isAnimatedValue(elevation)) {
9789
return {
9890
shadowColor,
9991
shadowOpacity: elevation.interpolate({
10092
inputRange: [0, 1],
101-
outputRange: [0, iOSShadowOutputRanges[layer].shadowOpacity],
93+
outputRange: [0, shadowLayers[layer].shadowOpacity],
10294
extrapolate: 'clamp',
10395
}),
10496
shadowOffset: {
10597
width: 0,
10698
height: elevation.interpolate({
107-
inputRange,
108-
outputRange: iOSShadowOutputRanges[layer].height,
99+
inputRange: [...elevationInputRange],
100+
outputRange: [...shadowLayers[layer].height],
109101
}),
110102
},
111103
shadowRadius: elevation.interpolate({
112-
inputRange,
113-
outputRange: iOSShadowOutputRanges[layer].shadowRadius,
104+
inputRange: [...elevationInputRange],
105+
outputRange: [...shadowLayers[layer].shadowRadius],
114106
}),
115107
};
116108
}
117109

118110
return {
119111
shadowColor,
120-
shadowOpacity: elevation ? iOSShadowOutputRanges[layer].shadowOpacity : 0,
112+
shadowOpacity: elevation ? shadowLayers[layer].shadowOpacity : 0,
121113
shadowOffset: {
122114
width: 0,
123-
height: iOSShadowOutputRanges[layer].height[elevation],
115+
height: shadowLayers[layer].height[elevation],
124116
},
125-
shadowRadius: iOSShadowOutputRanges[layer].shadowRadius[elevation],
117+
shadowRadius: shadowLayers[layer].shadowRadius[elevation],
126118
};
127119
}
128120

@@ -131,13 +123,15 @@ const SurfaceIOS = forwardRef<
131123
Omit<Props, 'elevation'> & {
132124
elevation: SurfaceElevation;
133125
backgroundColor?: string | Animated.AnimatedInterpolation<string | number>;
126+
shadowColor: string;
134127
}
135128
>(
136129
(
137130
{
138131
elevation,
139132
style,
140133
backgroundColor,
134+
shadowColor,
141135
testID,
142136
children,
143137
mode = 'elevated',
@@ -173,14 +167,14 @@ const SurfaceIOS = forwardRef<
173167
const isElevated = mode === 'elevated';
174168

175169
const outerLayerViewStyles = {
176-
...(isElevated && getStyleForShadowLayer(elevation, 0)),
170+
...(isElevated && getStyleForShadowLayer(elevation, 0, shadowColor)),
177171
...outerLayerStyles,
178172
...borderRadiusStyles,
179173
backgroundColor: bgColor,
180174
};
181175

182176
const innerLayerViewStyles = {
183-
...(isElevated && getStyleForShadowLayer(elevation, 1)),
177+
...(isElevated && getStyleForShadowLayer(elevation, 1, shadowColor)),
184178
...filteredStyles,
185179
...borderRadiusStyles,
186180
flex:
@@ -191,7 +185,7 @@ const SurfaceIOS = forwardRef<
191185
};
192186

193187
return [outerLayerViewStyles, innerLayerViewStyles];
194-
}, [style, elevation, backgroundColor, mode, container]);
188+
}, [style, elevation, backgroundColor, shadowColor, mode, container]);
195189

196190
return (
197191
<Animated.View
@@ -255,13 +249,11 @@ const Surface = forwardRef<View, Props>(
255249

256250
const { colors } = theme as Theme;
257251

258-
const inputRange = [0, 1, 2, 3, 4, 5];
259-
260252
const backgroundColor = (() => {
261253
if (isAnimatedValue(elevation)) {
262254
return elevation.interpolate({
263-
inputRange,
264-
outputRange: inputRange.map((elevation) => {
255+
inputRange: [...elevationInputRange],
256+
outputRange: [...elevationInputRange].map((elevation) => {
265257
return colors.elevation?.[`level${elevation as Elevation}`];
266258
}),
267259
});
@@ -282,7 +274,9 @@ const Surface = forwardRef<View, Props>(
282274
testID={testID}
283275
style={[
284276
{ backgroundColor },
285-
elevation && isElevated ? shadow(elevation) : null,
277+
elevation && isElevated
278+
? shadow(elevation, theme.colors.shadow)
279+
: null,
286280
style,
287281
]}
288282
>
@@ -292,12 +286,12 @@ const Surface = forwardRef<View, Props>(
292286
}
293287

294288
if (Platform.OS === 'android') {
295-
const elevationLevel = [0, 3, 6, 9, 12, 15];
289+
const elevationLevel = [...androidElevationLevels];
296290

297291
const getElevationAndroid = () => {
298292
if (isAnimatedValue(elevation)) {
299293
return elevation.interpolate({
300-
inputRange,
294+
inputRange: [...elevationInputRange],
301295
outputRange: elevationLevel,
302296
});
303297
}
@@ -340,6 +334,7 @@ const Surface = forwardRef<View, Props>(
340334
ref={ref}
341335
elevation={elevation}
342336
backgroundColor={backgroundColor}
337+
shadowColor={theme.colors.shadow}
343338
style={style}
344339
testID={testID}
345340
mode={mode}

src/components/__tests__/Appbar/__snapshots__/Appbar.test.tsx.snap

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ exports[`Appbar does not pass any additional props to Searchbar 1`] = `
77
{
88
"backgroundColor": "rgba(254, 247, 255, 1)",
99
"height": 64,
10-
"shadowColor": "#000",
10+
"shadowColor": "rgba(0, 0, 0, 1)",
1111
"shadowOffset": {
1212
"height": 0,
1313
"width": 0,
@@ -31,7 +31,7 @@ exports[`Appbar does not pass any additional props to Searchbar 1`] = `
3131
"paddingLeft": undefined,
3232
"paddingRight": undefined,
3333
"paddingTop": undefined,
34-
"shadowColor": "#000",
34+
"shadowColor": "rgba(0, 0, 0, 1)",
3535
"shadowOffset": {
3636
"height": 0,
3737
"width": 0,
@@ -48,7 +48,7 @@ exports[`Appbar does not pass any additional props to Searchbar 1`] = `
4848
{
4949
"backgroundColor": "rgba(236, 230, 240, 1)",
5050
"borderRadius": 28,
51-
"shadowColor": "#000",
51+
"shadowColor": "rgba(0, 0, 0, 1)",
5252
"shadowOffset": {
5353
"height": 0,
5454
"width": 0,
@@ -68,7 +68,7 @@ exports[`Appbar does not pass any additional props to Searchbar 1`] = `
6868
"borderRadius": 28,
6969
"flex": undefined,
7070
"flexDirection": "row",
71-
"shadowColor": "#000",
71+
"shadowColor": "rgba(0, 0, 0, 1)",
7272
"shadowOffset": {
7373
"height": 0,
7474
"width": 0,
@@ -87,7 +87,7 @@ exports[`Appbar does not pass any additional props to Searchbar 1`] = `
8787
"borderRadius": 20,
8888
"height": 40,
8989
"margin": 6,
90-
"shadowColor": "#000",
90+
"shadowColor": "rgba(0, 0, 0, 1)",
9191
"shadowOffset": {
9292
"height": 0,
9393
"width": 0,
@@ -110,7 +110,7 @@ exports[`Appbar does not pass any additional props to Searchbar 1`] = `
110110
"elevation": 0,
111111
"flex": 1,
112112
"overflow": "hidden",
113-
"shadowColor": "#000",
113+
"shadowColor": "rgba(0, 0, 0, 1)",
114114
"shadowOffset": {
115115
"height": 0,
116116
"width": 0,
@@ -279,7 +279,7 @@ exports[`Appbar does not pass any additional props to Searchbar 1`] = `
279279
"borderRadius": 20,
280280
"height": 40,
281281
"margin": 6,
282-
"shadowColor": "#000",
282+
"shadowColor": "rgba(0, 0, 0, 1)",
283283
"shadowOffset": {
284284
"height": 0,
285285
"width": 0,
@@ -302,7 +302,7 @@ exports[`Appbar does not pass any additional props to Searchbar 1`] = `
302302
"elevation": 0,
303303
"flex": 1,
304304
"overflow": "hidden",
305-
"shadowColor": "#000",
305+
"shadowColor": "rgba(0, 0, 0, 1)",
306306
"shadowOffset": {
307307
"height": 0,
308308
"width": 0,
@@ -427,7 +427,7 @@ exports[`Appbar passes additional props to AppbarBackAction, AppbarContent and A
427427
{
428428
"backgroundColor": "rgba(254, 247, 255, 1)",
429429
"height": 64,
430-
"shadowColor": "#000",
430+
"shadowColor": "rgba(0, 0, 0, 1)",
431431
"shadowOffset": {
432432
"height": 0,
433433
"width": 0,
@@ -451,7 +451,7 @@ exports[`Appbar passes additional props to AppbarBackAction, AppbarContent and A
451451
"paddingLeft": undefined,
452452
"paddingRight": undefined,
453453
"paddingTop": undefined,
454-
"shadowColor": "#000",
454+
"shadowColor": "rgba(0, 0, 0, 1)",
455455
"shadowOffset": {
456456
"height": 0,
457457
"width": 0,
@@ -470,7 +470,7 @@ exports[`Appbar passes additional props to AppbarBackAction, AppbarContent and A
470470
"borderRadius": 20,
471471
"height": 40,
472472
"margin": 6,
473-
"shadowColor": "#000",
473+
"shadowColor": "rgba(0, 0, 0, 1)",
474474
"shadowOffset": {
475475
"height": 0,
476476
"width": 0,
@@ -493,7 +493,7 @@ exports[`Appbar passes additional props to AppbarBackAction, AppbarContent and A
493493
"elevation": 0,
494494
"flex": 1,
495495
"overflow": "hidden",
496-
"shadowColor": "#000",
496+
"shadowColor": "rgba(0, 0, 0, 1)",
497497
"shadowOffset": {
498498
"height": 0,
499499
"width": 0,
@@ -721,7 +721,7 @@ exports[`Appbar passes additional props to AppbarBackAction, AppbarContent and A
721721
"borderRadius": 20,
722722
"height": 40,
723723
"margin": 6,
724-
"shadowColor": "#000",
724+
"shadowColor": "rgba(0, 0, 0, 1)",
725725
"shadowOffset": {
726726
"height": 0,
727727
"width": 0,
@@ -744,7 +744,7 @@ exports[`Appbar passes additional props to AppbarBackAction, AppbarContent and A
744744
"elevation": 0,
745745
"flex": 1,
746746
"overflow": "hidden",
747-
"shadowColor": "#000",
747+
"shadowColor": "rgba(0, 0, 0, 1)",
748748
"shadowOffset": {
749749
"height": 0,
750750
"width": 0,

src/components/__tests__/Card/__snapshots__/Card.test.tsx.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ exports[`Card renders an outlined card 1`] = `
77
{
88
"backgroundColor": "rgba(254, 247, 255, 1)",
99
"borderRadius": 12,
10-
"shadowColor": "#000",
10+
"shadowColor": "rgba(0, 0, 0, 1)",
1111
"shadowOffset": {
1212
"height": 0,
1313
"width": 0,
@@ -25,7 +25,7 @@ exports[`Card renders an outlined card 1`] = `
2525
"backgroundColor": "rgba(254, 247, 255, 1)",
2626
"borderRadius": 12,
2727
"flex": undefined,
28-
"shadowColor": "#000",
28+
"shadowColor": "rgba(0, 0, 0, 1)",
2929
"shadowOffset": {
3030
"height": 0,
3131
"width": 0,

src/components/__tests__/Menu.test.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { Animated, Dimensions, StyleSheet, View } from 'react-native';
44
import { act, render, screen, waitFor } from '@testing-library/react-native';
55

66
import { getTheme } from '../../core/theming';
7-
import { Elevation } from '../../types';
7+
import { Elevation, ElevationLevel } from '../../types';
88
import Button from '../Button/Button';
9-
import Menu, { ELEVATION_LEVELS_MAP } from '../Menu/Menu';
9+
import Menu from '../Menu/Menu';
1010
import Portal from '../Portal/Portal';
1111

1212
const styles = StyleSheet.create({
@@ -87,7 +87,8 @@ it('renders menu with content styles', () => {
8787
);
8888

8989
expect(getByTestId('menu-surface')).toHaveStyle({
90-
backgroundColor: theme.colors.elevation[ELEVATION_LEVELS_MAP[elevation]],
90+
backgroundColor:
91+
theme.colors.elevation[`level${elevation}` as ElevationLevel],
9192
});
9293
})
9394
);

0 commit comments

Comments
 (0)