@fluentui-react-native/drawer 0.1.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.
Files changed (74) hide show
  1. package/.eslintrc.js +3 -0
  2. package/CHANGELOG.json +20 -0
  3. package/CHANGELOG.md +13 -0
  4. package/SPEC.md +1 -0
  5. package/babel.config.js +1 -0
  6. package/jest.config.js +2 -0
  7. package/just.config.js +3 -0
  8. package/lib/Drawer.d.ts +4 -0
  9. package/lib/Drawer.d.ts.map +1 -0
  10. package/lib/Drawer.js +51 -0
  11. package/lib/Drawer.js.map +1 -0
  12. package/lib/Drawer.styling.d.ts +4 -0
  13. package/lib/Drawer.styling.d.ts.map +1 -0
  14. package/lib/Drawer.styling.js +47 -0
  15. package/lib/Drawer.styling.js.map +1 -0
  16. package/lib/Drawer.types.d.ts +184 -0
  17. package/lib/Drawer.types.d.ts.map +1 -0
  18. package/lib/Drawer.types.js +2 -0
  19. package/lib/Drawer.types.js.map +1 -0
  20. package/lib/DrawerTokens.d.ts +5 -0
  21. package/lib/DrawerTokens.d.ts.map +1 -0
  22. package/lib/DrawerTokens.js +37 -0
  23. package/lib/DrawerTokens.js.map +1 -0
  24. package/lib/__tests__/Drawer.test.d.ts +2 -0
  25. package/lib/__tests__/Drawer.test.d.ts.map +1 -0
  26. package/lib/__tests__/Drawer.test.js +16 -0
  27. package/lib/__tests__/Drawer.test.js.map +1 -0
  28. package/lib/index.d.ts +4 -0
  29. package/lib/index.d.ts.map +1 -0
  30. package/lib/index.js +3 -0
  31. package/lib/index.js.map +1 -0
  32. package/lib/useDrawer.d.ts +3 -0
  33. package/lib/useDrawer.d.ts.map +1 -0
  34. package/lib/useDrawer.js +85 -0
  35. package/lib/useDrawer.js.map +1 -0
  36. package/lib-commonjs/Drawer.d.ts +4 -0
  37. package/lib-commonjs/Drawer.d.ts.map +1 -0
  38. package/lib-commonjs/Drawer.js +55 -0
  39. package/lib-commonjs/Drawer.js.map +1 -0
  40. package/lib-commonjs/Drawer.styling.d.ts +4 -0
  41. package/lib-commonjs/Drawer.styling.d.ts.map +1 -0
  42. package/lib-commonjs/Drawer.styling.js +50 -0
  43. package/lib-commonjs/Drawer.styling.js.map +1 -0
  44. package/lib-commonjs/Drawer.types.d.ts +184 -0
  45. package/lib-commonjs/Drawer.types.d.ts.map +1 -0
  46. package/lib-commonjs/Drawer.types.js +5 -0
  47. package/lib-commonjs/Drawer.types.js.map +1 -0
  48. package/lib-commonjs/DrawerTokens.d.ts +5 -0
  49. package/lib-commonjs/DrawerTokens.d.ts.map +1 -0
  50. package/lib-commonjs/DrawerTokens.js +41 -0
  51. package/lib-commonjs/DrawerTokens.js.map +1 -0
  52. package/lib-commonjs/__tests__/Drawer.test.d.ts +2 -0
  53. package/lib-commonjs/__tests__/Drawer.test.d.ts.map +1 -0
  54. package/lib-commonjs/__tests__/Drawer.test.js +41 -0
  55. package/lib-commonjs/__tests__/Drawer.test.js.map +1 -0
  56. package/lib-commonjs/index.d.ts +4 -0
  57. package/lib-commonjs/index.d.ts.map +1 -0
  58. package/lib-commonjs/index.js +8 -0
  59. package/lib-commonjs/index.js.map +1 -0
  60. package/lib-commonjs/useDrawer.d.ts +3 -0
  61. package/lib-commonjs/useDrawer.d.ts.map +1 -0
  62. package/lib-commonjs/useDrawer.js +89 -0
  63. package/lib-commonjs/useDrawer.js.map +1 -0
  64. package/package.json +59 -0
  65. package/src/Drawer.styling.ts +59 -0
  66. package/src/Drawer.tsx +51 -0
  67. package/src/Drawer.types.ts +221 -0
  68. package/src/DrawerTokens.ts +40 -0
  69. package/src/__tests__/Drawer.test.tsx +22 -0
  70. package/src/__tests__/__snapshots__/Drawer.test.tsx.snap +20 -0
  71. package/src/index.ts +3 -0
  72. package/src/useDrawer.ts +90 -0
  73. package/svgs.d.ts +4 -0
  74. package/tsconfig.json +8 -0
@@ -0,0 +1,59 @@
1
+ import type { Theme, UseStylingOptions } from '@fluentui-react-native/framework';
2
+ import { buildProps } from '@fluentui-react-native/framework';
3
+
4
+ import { DrawerName } from './Drawer.types';
5
+ import type { DrawerTokens, DrawerSlotProps, DrawerProps } from './Drawer.types';
6
+ import { defaultDrawerTokens } from './DrawerTokens';
7
+
8
+ export const stylingSettings: UseStylingOptions<DrawerProps, DrawerSlotProps, DrawerTokens> = {
9
+ tokens: [defaultDrawerTokens, DrawerName],
10
+ tokensThatAreAlsoProps: 'all',
11
+ states: ['left', 'right', 'bottom'],
12
+ slotProps: {
13
+ scrimContent: buildProps(
14
+ (tokens: DrawerTokens, _theme: Theme) => ({
15
+ style: {
16
+ position: 'absolute',
17
+ top: 0,
18
+ left: 0,
19
+ width: '100%',
20
+ height: '100%',
21
+ backgroundColor: tokens.scrimColor,
22
+ opacity: tokens.scrimOpacity,
23
+ },
24
+ }),
25
+ ['scrimColor'],
26
+ ),
27
+ content: buildProps(
28
+ (tokens: DrawerTokens, _theme: Theme) => ({
29
+ style: {
30
+ position: 'absolute',
31
+ height: tokens.height,
32
+ width: tokens.width,
33
+ elevation: tokens.drawerElevation,
34
+ borderTopEndRadius: tokens.drawerCornerRadius,
35
+ borderTopStartRadius: tokens.drawerCornerRadius,
36
+ right: tokens.rightPosition,
37
+ left: tokens.leftPosition,
38
+ bottom: tokens.bottomPosition,
39
+ top: tokens.topPosition,
40
+ backgroundColor: tokens.drawerBackgroundColor,
41
+ },
42
+ }),
43
+ ['drawerBackgroundColor', 'drawerElevation', 'height', 'width'],
44
+ ),
45
+ handle: buildProps(
46
+ (tokens: DrawerTokens, _theme: Theme) => ({
47
+ style: {
48
+ width: tokens.handleWidth,
49
+ height: tokens.handleHeight,
50
+ borderRadius: tokens.handleCornerRadius,
51
+ backgroundColor: tokens.handleBackgroundColor,
52
+ alignSelf: tokens.handleAlignment,
53
+ marginTop: tokens.handleMarginTop,
54
+ },
55
+ }),
56
+ ['handleWidth', 'handleHeight', 'handleCornerRadius', 'handleBackgroundColor', 'handleAlignment', 'handleMarginTop'],
57
+ ),
58
+ },
59
+ };
package/src/Drawer.tsx ADDED
@@ -0,0 +1,51 @@
1
+ /** @jsx withSlots */
2
+ import { Animated, Modal, TouchableWithoutFeedback, View } from 'react-native';
3
+
4
+ import type { UseSlots } from '@fluentui-react-native/framework';
5
+ import { compose, mergeProps, withSlots } from '@fluentui-react-native/framework';
6
+
7
+ import { stylingSettings } from './Drawer.styling';
8
+ import type { DrawerType, DrawerProps } from './Drawer.types';
9
+ import { DrawerName } from './Drawer.types';
10
+ import { useDrawer } from './useDrawer';
11
+
12
+ export const drawerLookup = (layer: string, userProps: DrawerProps): boolean => {
13
+ return userProps[layer] || layer === userProps['drawerPosition'];
14
+ };
15
+
16
+ export const Drawer = compose<DrawerType>({
17
+ displayName: DrawerName,
18
+ ...stylingSettings,
19
+ slots: {
20
+ modal: Modal,
21
+ scrim: TouchableWithoutFeedback,
22
+ scrimContent: Animated.View,
23
+ content: Animated.View,
24
+ handle: View,
25
+ },
26
+ useRender: (userProps: DrawerProps, useSlots: UseSlots<DrawerType>) => {
27
+ const drawerProps = useDrawer(userProps).props;
28
+ const Slots = useSlots(userProps, (layer) => drawerLookup(layer, drawerProps));
29
+ return (final: DrawerProps, children: React.ReactNode) => {
30
+ const { open, onClose, onScrimClick, animationConfig, drawerPosition, showHandle, ...rest } = mergeProps(drawerProps, final);
31
+ return (
32
+ <Slots.modal
33
+ {...rest}
34
+ visible={open}
35
+ onRequestClose={onClose}
36
+ supportedOrientations={['portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right']}
37
+ animationType="none"
38
+ transparent
39
+ >
40
+ <Slots.scrim onPress={onScrimClick}>
41
+ <Slots.scrimContent style={[{ opacity: animationConfig.animatedOpacity }]} />
42
+ </Slots.scrim>
43
+ <Slots.content style={animationConfig.animatedStyle}>
44
+ {drawerPosition === 'bottom' && showHandle && <Slots.handle />}
45
+ {children}
46
+ </Slots.content>
47
+ </Slots.modal>
48
+ );
49
+ };
50
+ },
51
+ });
@@ -0,0 +1,221 @@
1
+ import type { Animated, ColorValue, FlexAlignType, ModalProps, TouchableWithoutFeedbackProps, ViewProps } from 'react-native';
2
+
3
+ import type { InteractionEvent, PressableFocusProps } from '@fluentui-react-native/interactive-hooks';
4
+
5
+ export const DrawerName = 'Drawer';
6
+
7
+ /**
8
+ * Specifies the possible position of the Drawer.
9
+ */
10
+ export type DrawerPositionType = 'bottom' | 'left' | 'right';
11
+
12
+ export interface DrawerTokens {
13
+ /**
14
+ * The color of the scrim.
15
+ * */
16
+ scrimColor?: ColorValue;
17
+
18
+ /**
19
+ * The opacity of the scrim.
20
+ * */
21
+ scrimOpacity?: number;
22
+
23
+ /**
24
+ * Width of the handle.
25
+ */
26
+ handleWidth?: number;
27
+
28
+ /**
29
+ * Height of the handle.
30
+ */
31
+ handleHeight?: number;
32
+
33
+ /**
34
+ * The background color of the handle.
35
+ * */
36
+ handleBackgroundColor?: ColorValue;
37
+
38
+ /**
39
+ * The corner radius of the handle.
40
+ * */
41
+ handleCornerRadius?: number;
42
+
43
+ /**
44
+ * The top margin of the handle.
45
+ * */
46
+ handleMarginTop?: number;
47
+
48
+ /**
49
+ * The bottom margin of the handle.
50
+ * */
51
+
52
+ handleMarginBottom?: number;
53
+
54
+ /**
55
+ * The start margin of the handle.
56
+ * */
57
+
58
+ handleMarginStart?: number;
59
+
60
+ /**
61
+ * The end margin of the handle.
62
+ * */
63
+
64
+ handleMarginEnd?: number;
65
+
66
+ /**
67
+ * The flex alignment of the handle.
68
+ * */
69
+ handleAlignment?: FlexAlignType;
70
+
71
+ /**
72
+ * The background for drawer
73
+ * */
74
+
75
+ drawerBackgroundColor?: ColorValue;
76
+
77
+ /**
78
+ * The corner radius of the Drawer
79
+ * @default 0
80
+ * */
81
+ drawerCornerRadius?: number;
82
+
83
+ /**
84
+ * The elevation of the Drawer
85
+ * @default 5
86
+ * @platform android
87
+ * */
88
+ drawerElevation?: number;
89
+
90
+ /**
91
+ * The width of the Drawer
92
+ * Note: Only applicable when position is 'left' or 'right'
93
+ * @default '100%'
94
+ * */
95
+ width?: number | string;
96
+
97
+ /**
98
+ * The height of the Drawer
99
+ * Note: Only applicable when position is 'bottom'
100
+ * @default '40%'
101
+ * */
102
+ height?: number | string;
103
+
104
+ /**
105
+ * The shadow color of the Drawer
106
+ * @platform iOS
107
+ * */
108
+ shadowColor?: ColorValue;
109
+
110
+ /**
111
+ * The shadow offset of the Drawer
112
+ * @platform iOS
113
+ * */
114
+ shadowOffset?: { width: number; height: number };
115
+
116
+ /**
117
+ * The shadow opacity of the Drawer
118
+ * @platform iOS
119
+ * */
120
+ shadowOpacity?: number;
121
+
122
+ /**
123
+ * The shadow radius of the Drawer
124
+ * @platform iOS
125
+ * */
126
+
127
+ shadowRadius?: number;
128
+
129
+ /**
130
+ * Positions/Behaviours of the Drawer, This is used to apply different tokens for different positions.
131
+ */
132
+
133
+ left?: DrawerTokens;
134
+ right?: DrawerTokens;
135
+ bottom?: DrawerTokens;
136
+
137
+ /**
138
+ * Coordinate position of the Drawer, This is used to position the Drawer at a particular coordinate.
139
+ */
140
+ leftPosition?: number;
141
+ rightPosition?: number;
142
+ bottomPosition?: number;
143
+ topPosition?: number;
144
+ }
145
+
146
+ export interface DrawerProps extends PressableFocusProps {
147
+ /*
148
+ ** An accessibility label for screen readers. Set on the text Drawer.
149
+ */
150
+ accessibilityLabel?: string;
151
+
152
+ /**
153
+ * Visibility of the Drawer
154
+ */
155
+ open?: boolean;
156
+
157
+ /**
158
+ * Callback when the Drawer is closed
159
+ * */
160
+ onClose?: (e: InteractionEvent) => void;
161
+
162
+ /**
163
+ * Callback when the Drawer is opened
164
+ * */
165
+ onOpen?: (e: InteractionEvent) => void;
166
+
167
+ /**
168
+ * Callback when the scrim is clicked
169
+ * */
170
+ onScrimClick?: (e: InteractionEvent) => void;
171
+
172
+ /**
173
+ * The content of the Drawer
174
+ * */
175
+ children?: React.ReactNode;
176
+
177
+ /**
178
+ * The behavior of the Drawer
179
+ * @default 'left'
180
+ * */
181
+ drawerPosition?: DrawerPositionType;
182
+
183
+ /**
184
+ * The behavior is for only bottom drawer in which content is scrollable without expanding the bottom drawer
185
+ * @default false
186
+ * */
187
+ isContentScrollableUnexpanded?: boolean;
188
+
189
+ /**
190
+ * The behavior is for only bottom drawer in which handle can be hidden for some usecases
191
+ * @default true
192
+ * */
193
+ showHandle?: boolean;
194
+
195
+ /**
196
+ * The width of the Drawer
197
+ * Animation configuration for the Drawer
198
+ * */
199
+ animationConfig?: {
200
+ animatedOpacity: Animated.AnimatedInterpolation;
201
+ animatedStyle: { transform: { translateX: Animated.AnimatedInterpolation }[] | { translateY: Animated.AnimatedInterpolation }[] };
202
+ };
203
+ }
204
+
205
+ export interface DrawerInfo {
206
+ props: DrawerProps;
207
+ }
208
+
209
+ export interface DrawerSlotProps {
210
+ modal: ModalProps;
211
+ scrim: TouchableWithoutFeedbackProps;
212
+ scrimContent: Animated.AnimatedProps<ViewProps>;
213
+ content: Animated.AnimatedProps<ViewProps>;
214
+ handle: ViewProps;
215
+ }
216
+
217
+ export interface DrawerType {
218
+ props: DrawerProps;
219
+ tokens: DrawerTokens;
220
+ slotProps: DrawerSlotProps;
221
+ }
@@ -0,0 +1,40 @@
1
+ import type { Theme } from '@fluentui-react-native/framework';
2
+ import { globalTokens } from '@fluentui-react-native/theme-tokens';
3
+ import type { TokenSettings } from '@fluentui-react-native/use-styling';
4
+
5
+ import type { DrawerTokens } from './Drawer.types';
6
+
7
+ export const defaultDrawerTokens: TokenSettings<DrawerTokens, Theme> = (t: Theme) =>
8
+ ({
9
+ scrimColor: '#00000080', // to be replace with token in future based on design guidance
10
+ drawerBackgroundColor: t.colors.neutralBackground2,
11
+ handleBackgroundColor: t.colors.neutralStroke1,
12
+ handleWidth: globalTokens.size360,
13
+ handleHeight: globalTokens.size40,
14
+ handleCornerRadius: globalTokens.size20,
15
+ handleAlignment: 'center',
16
+ handleMarginTop: globalTokens.size80,
17
+ handleMarginBottom: globalTokens.sizeNone,
18
+ handleMarginEnd: globalTokens.sizeNone,
19
+ handleMarginStart: globalTokens.sizeNone,
20
+ drawerElevation: globalTokens.size100,
21
+ topPosition: 0,
22
+ left: {
23
+ height: '100%',
24
+ width: '80%',
25
+ leftPosition: 0,
26
+ drawerCornerRadius: 0,
27
+ },
28
+ right: {
29
+ height: '100%',
30
+ width: '80%',
31
+ rightPosition: 0,
32
+ drawerCornerRadius: 0,
33
+ },
34
+ bottom: {
35
+ bottomPosition: 0,
36
+ width: '100%',
37
+ drawerCornerRadius: globalTokens.corner.radius120,
38
+ height: '50%',
39
+ },
40
+ } as DrawerTokens);
@@ -0,0 +1,22 @@
1
+ import * as React from 'react';
2
+ import { Text } from 'react-native';
3
+
4
+ import * as renderer from 'react-test-renderer';
5
+
6
+ import { Drawer } from '../Drawer';
7
+
8
+ // mocks out setTimeout and other timer functions with mock functions , test will fail without this as we're using Animated API
9
+ jest.useFakeTimers();
10
+
11
+ describe('Drawer component tests', () => {
12
+ it('Drawer default', () => {
13
+ const tree = renderer
14
+ .create(
15
+ <Drawer open={false} drawerPosition="left">
16
+ <Text>Hello</Text>
17
+ </Drawer>,
18
+ )
19
+ .toJSON();
20
+ expect(tree).toMatchSnapshot();
21
+ });
22
+ });
@@ -0,0 +1,20 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`Drawer component tests Drawer default 1`] = `
4
+ <Modal
5
+ animationType="none"
6
+ hardwareAccelerated={false}
7
+ onRequestClose={[Function]}
8
+ supportedOrientations={
9
+ Array [
10
+ "portrait",
11
+ "portrait-upside-down",
12
+ "landscape",
13
+ "landscape-left",
14
+ "landscape-right",
15
+ ]
16
+ }
17
+ transparent={true}
18
+ visible={false}
19
+ />
20
+ `;
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { Drawer } from './Drawer';
2
+ export type { DrawerProps, DrawerSlotProps, DrawerTokens, DrawerType, DrawerPositionType } from './Drawer.types';
3
+ export { DrawerName } from './Drawer.types';
@@ -0,0 +1,90 @@
1
+ import { useRef, useEffect, useState, useCallback } from 'react';
2
+ import { Animated, Dimensions } from 'react-native';
3
+
4
+ import type { InteractionEvent } from '@fluentui-react-native/interactive-hooks';
5
+
6
+ import type { DrawerProps, DrawerInfo } from './Drawer.types';
7
+
8
+ const { height, width } = Dimensions.get('window');
9
+
10
+ export const useDrawer = (props: DrawerProps): DrawerInfo => {
11
+ const { onBlur, onFocus, accessibilityLabel, open, drawerPosition = 'left', showHandle = true, children, ...rest } = props;
12
+ const animatedValue = useRef(new Animated.Value(0)).current;
13
+ const [internalOpen, setInternalOpen] = useState(open);
14
+ const [isFirstOpen, setIsFirstOpen] = useState(true);
15
+
16
+ useEffect(() => {
17
+ if (open) {
18
+ setInternalOpen(true);
19
+ Animated.timing(animatedValue, {
20
+ toValue: 1,
21
+ duration: 300,
22
+ useNativeDriver: true,
23
+ }).start();
24
+ } else {
25
+ if (isFirstOpen) {
26
+ setIsFirstOpen(false);
27
+ } else {
28
+ Animated.parallel([
29
+ Animated.timing(animatedValue, {
30
+ toValue: 0,
31
+ duration: 300,
32
+ useNativeDriver: true,
33
+ }),
34
+ ]).start(() => {
35
+ setInternalOpen(false);
36
+ });
37
+ }
38
+ }
39
+ }, [animatedValue, open]);
40
+
41
+ const onClose = useCallback(
42
+ (e: InteractionEvent) => {
43
+ props?.onClose && props.onClose(e);
44
+ },
45
+ [props?.onClose],
46
+ );
47
+
48
+ const onScrimClick = useCallback(
49
+ (e: InteractionEvent) => {
50
+ props?.onScrimClick && props.onScrimClick(e);
51
+ },
52
+ [props?.onScrimClick],
53
+ );
54
+
55
+ const animatedTranslateX = animatedValue.interpolate({
56
+ inputRange: [0, 1],
57
+ outputRange: drawerPosition === 'left' ? [-width * 0.8, 0] : drawerPosition === 'right' ? [width * 0.8, 0] : [0, 0],
58
+ });
59
+
60
+ const animatedTranslateY = animatedValue.interpolate({
61
+ inputRange: [0, 1],
62
+ outputRange: [height, height * 0.5],
63
+ });
64
+
65
+ const animatedOpacity = animatedValue.interpolate({
66
+ inputRange: [0, 1],
67
+ outputRange: [0, 0.5],
68
+ });
69
+
70
+ const animatedStyle = {
71
+ transform:
72
+ drawerPosition === 'left' || drawerPosition === 'right' ? [{ translateX: animatedTranslateX }] : [{ translateY: animatedTranslateY }],
73
+ };
74
+
75
+ return {
76
+ props: {
77
+ ...rest,
78
+ onClose,
79
+ onScrimClick,
80
+ animationConfig: {
81
+ animatedOpacity,
82
+ animatedStyle,
83
+ },
84
+ drawerPosition: drawerPosition ?? 'left',
85
+ children,
86
+ showHandle,
87
+ open: internalOpen,
88
+ },
89
+ };
90
+ };
package/svgs.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ declare module '*.svg' {
2
+ const content: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
3
+ export default content;
4
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "@fluentui-react-native/scripts/tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "lib",
5
+ "types": ["node", "jest"]
6
+ },
7
+ "include": ["src", "svgs.d.ts"]
8
+ }