@startupjs-ui/drawer 0.1.3

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ## [0.1.3](https://github.com/startupjs/startupjs-ui/compare/v0.1.2...v0.1.3) (2025-12-29)
7
+
8
+ **Note:** Version bump only for package @startupjs-ui/drawer
9
+
10
+
11
+
12
+
13
+
14
+ ## [0.1.2](https://github.com/startupjs/startupjs-ui/compare/v0.1.1...v0.1.2) (2025-12-29)
15
+
16
+
17
+ ### Features
18
+
19
+ * **abstract-popover:** refactor AbstractPopover component ([4a19018](https://github.com/startupjs/startupjs-ui/commit/4a190183b9e6903b6758d1d006fce7eca39bce0a))
20
+ * add mdx and docs packages. Refactor docs to get rid of any @startupjs/ui usage and use startupjs-ui instead ([703c926](https://github.com/startupjs/startupjs-ui/commit/703c92636efb0421ffd11783f692fc892b74018f))
21
+ * **drawer:** refactor Drawer component ([3b9bac2](https://github.com/startupjs/startupjs-ui/commit/3b9bac2b3d15b9b3676e7f2adfe6ca9c6a9e597a))
package/README.mdx ADDED
@@ -0,0 +1,206 @@
1
+ import { useState } from 'react'
2
+ import { View, TouchableWithoutFeedback, Text } from 'react-native'
3
+ import { Sandbox } from '@startupjs-ui/docs'
4
+ import { useRouter } from 'expo-router'
5
+ import Drawer, { _PropsJsonSchema as DrawerPropsJsonSchema, useDrawerDismiss } from './index'
6
+ import Div from '@startupjs-ui/div'
7
+ import Button from '@startupjs-ui/button'
8
+
9
+ # Drawer
10
+
11
+ Navigation bars are designed to provide links to different parts of your application
12
+ Sidebars provide additional information and dock to the left or right side of the browser window
13
+ ```jsx
14
+ import { Drawer } from 'startupjs-ui'
15
+ ```
16
+
17
+ ## Initialization
18
+
19
+ Before use you need to configure [Portal](/docs/components/Portal)
20
+
21
+ ## Simple example
22
+ ```jsx example
23
+ const [visible, setVisible] = useState(false)
24
+
25
+ return (
26
+ <View>
27
+ <Drawer
28
+ visible={visible}
29
+ style={{ width: 250 }}
30
+ onDismiss={()=> setVisible(false)}
31
+ >
32
+ <View style={{ padding: 16 }}>
33
+ <Text>Content</Text>
34
+ </View>
35
+ </Drawer>
36
+ <Button
37
+ style={{ width: 160 }}
38
+ onPress={()=> setVisible(true)}
39
+ >Show</Button>
40
+ </View>
41
+ )
42
+ ```
43
+
44
+ ## Position
45
+ Сomponent can be deployed from different directions (left, right, top, buttom)
46
+ ```jsx example
47
+ const [visible, setVisible] = useState('')
48
+
49
+ const data = {
50
+ left: {
51
+ name: 'Left',
52
+ style: { width: 240 }
53
+ },
54
+ right: {
55
+ name: 'Right',
56
+ style: { width: 240 }
57
+ },
58
+ top: {
59
+ name: 'Up',
60
+ style: { height: 240 }
61
+ },
62
+ bottom: {
63
+ name: 'Down',
64
+ style: { height: 240 }
65
+ }
66
+ }
67
+
68
+ return (
69
+ <Div row style={{ width: '100%', flexWrap: 'wrap' }}>
70
+ {
71
+ Object.keys(data).map((key, index) => (
72
+ <View key={index}>
73
+ <Drawer
74
+ position={key}
75
+ style={data[key].style}
76
+ visible={visible === key}
77
+ onDismiss={()=> setVisible('')}
78
+ >
79
+ <View style={{ padding: 16 }}>
80
+ <Text>{data[key].name}</Text>
81
+ </View>
82
+ </Drawer>
83
+ <Button
84
+ onPress={()=> setVisible(key)}
85
+ style={{ width: 120, marginRight: 16, marginTop: 16 }}
86
+ >{data[key].name}</Button>
87
+ </View>
88
+ ))
89
+ }
90
+ </Div>
91
+ )
92
+ ```
93
+
94
+ ## Swipe
95
+ The component has support for closing using a swipe
96
+ ```jsx example
97
+ const [visible, setVisible] = useState('')
98
+
99
+ return (
100
+ <Div row style={{ width: '100%', flexWrap: 'wrap' }}>
101
+ <Drawer
102
+ visible={visible === 'zone'}
103
+ onDismiss={()=> setVisible('')}
104
+ style={{ width: 250 }}
105
+ swipeStyle={{ backgroundColor: '#eeeeee' }}
106
+ />
107
+ <Button
108
+ style={{ width: 280, marginRight: 24, marginTop: 16 }}
109
+ onPress={()=> setVisible('zone')}
110
+ >Swipe zone</Button>
111
+ <Drawer
112
+ visible={visible === 'custom'}
113
+ onDismiss={()=> setVisible('')}
114
+ style={{ width: 250 }}
115
+ swipeStyle={{ backgroundColor: '#eeeeee', width: '30%',
116
+ height: 100, top: 30 }}
117
+ />
118
+ <Button
119
+ style={{ width: 280, marginTop: 16 }}
120
+ onPress={()=> setVisible('custom')}
121
+ >Custom swipe zone</Button>
122
+ </Div>
123
+ )
124
+ ```
125
+
126
+ ## Hook for smooth closing
127
+ If the component has events, before the execution of which the panel must be hidden, e.g. go to another page, and you need it to close smoothly with animation
128
+ There is a hook - `useDrawerDismiss`, into which the default function is passed, which works out for each event `onDismiss`, and additional ones that will be called after it
129
+ ```jsx
130
+ import { useDrawerDismiss } from 'startupjs-ui'
131
+ ```
132
+
133
+ ```jsx example
134
+ const router = useRouter()
135
+ const [leftDrawer, setLeftDrawer] = useState(false)
136
+ const [rightDrawer, setRightDrawer] = useState(false)
137
+ const [leftVisible, setLeftVisible] = useState(false)
138
+
139
+ const [onDismiss, setOnDismiss] = useDrawerDismiss({
140
+ rightDrawer: () => setRightDrawer(true),
141
+ goToPage: path => router.push(path),
142
+ default: () => setLeftDrawer(false)
143
+ })
144
+
145
+ return (
146
+ <Div row style={{ width: '100%', flexWrap: 'wrap' }}>
147
+ <Drawer
148
+ visible={rightDrawer}
149
+ position='right'
150
+ onDismiss={()=> setRightDrawer(false)}
151
+ style={{ width: 240 }}
152
+ />
153
+ <Drawer
154
+ visible={leftVisible}
155
+ onDismiss={()=> setLeftVisible(false)}
156
+ style={{ width: 240 }}
157
+ >
158
+ <TouchableWithoutFeedback onPress={()=> router.navigate('/docs/components/Button')}>
159
+ <View>
160
+ <Text>Open another page</Text>
161
+ </View>
162
+ </TouchableWithoutFeedback>
163
+ </Drawer>
164
+ <Button
165
+ onPress={() => setLeftVisible(true)}
166
+ style={{ width: 280, marginRight: 24, marginTop: 16 }}
167
+ >No hook</Button>
168
+ <Drawer
169
+ visible={leftDrawer}
170
+ onDismiss={onDismiss}
171
+ style={{ width: 240 }}
172
+ >
173
+ <TouchableWithoutFeedback onPress={()=> setOnDismiss('rightDrawer')}>
174
+ <View>
175
+ <Text>Open right Drawer</Text>
176
+ </View>
177
+ </TouchableWithoutFeedback>
178
+ <TouchableWithoutFeedback onPress={()=> setOnDismiss('goToPage', '/docs/components/Button')}>
179
+ <View>
180
+ <Text>Open another page</Text>
181
+ </View>
182
+ </TouchableWithoutFeedback>
183
+ </Drawer>
184
+ <Button
185
+ onPress={()=> setLeftDrawer(true)}
186
+ style={{ width: 280, marginRight: 24, marginTop: 16 }}
187
+ >With hook</Button>
188
+ </Div>
189
+ )
190
+ ```
191
+
192
+ ## Sandbox
193
+
194
+ <Sandbox
195
+ Component={Drawer}
196
+ propsJsonSchema={DrawerPropsJsonSchema}
197
+ props={{
198
+ visible: false,
199
+ position: 'left',
200
+ hasOverlay: true,
201
+ isSwipe: true,
202
+ showResponder: true,
203
+ onDismiss: () => undefined,
204
+ children: <View style={{ padding: 16 }}><Text>Content</Text></View>
205
+ }}
206
+ />
package/Swipe.tsx ADDED
@@ -0,0 +1,110 @@
1
+ import { useEffect, useMemo, useState, type ReactNode } from 'react'
2
+ import { PanResponder, View, StyleSheet, type StyleProp, type ViewStyle } from 'react-native'
3
+ import { pug, observer } from 'startupjs'
4
+ import { themed } from '@startupjs-ui/core'
5
+ import './index.cssx.styl'
6
+ import type { DrawerAnimateStates } from './animate'
7
+
8
+ const RESPONDER_STYLES = {
9
+ left: { right: 0, width: '10%', height: '100%' },
10
+ right: { left: 0, width: '10%', height: '100%' },
11
+ bottom: { top: 0, width: '100%', height: '10%' },
12
+ top: { bottom: 0, width: '100%', height: '10%' }
13
+ } satisfies Record<string, ViewStyle>
14
+
15
+ interface DrawerSwipeProps {
16
+ position: 'left' | 'right' | 'top' | 'bottom'
17
+ contentSize: { width?: number, height?: number }
18
+ swipeStyle?: StyleProp<ViewStyle>
19
+ isHorizontal: boolean
20
+ isSwipe: boolean
21
+ isInvertPosition: boolean
22
+ animateStates: DrawerAnimateStates
23
+ runHide: () => void
24
+ runShow: () => void
25
+ }
26
+
27
+ function Swipe ({
28
+ position,
29
+ contentSize,
30
+ swipeStyle,
31
+ isHorizontal,
32
+ isSwipe,
33
+ isInvertPosition,
34
+ animateStates,
35
+ runHide,
36
+ runShow
37
+ }: DrawerSwipeProps): ReactNode {
38
+ const [startDrag, setStartDrag] = useState<number | null>(null)
39
+ const [endDrag, setEndDrag] = useState(false)
40
+ const [offset, setOffset] = useState<number | null>(null)
41
+
42
+ const dragZoneValue = useMemo(() => {
43
+ // 15 percent
44
+ return isHorizontal
45
+ ? (((contentSize.width ?? 0) / 100) * 15)
46
+ : (((contentSize.height ?? 0) / 100) * 15)
47
+ }, [contentSize, isHorizontal])
48
+
49
+ useEffect(() => {
50
+ if (offset === null) return
51
+ if (endDrag) {
52
+ const validOffset = isInvertPosition ? -offset : offset
53
+
54
+ if (validOffset >= dragZoneValue) {
55
+ runHide()
56
+ } else {
57
+ runShow()
58
+ }
59
+
60
+ setOffset(null)
61
+ setStartDrag(null)
62
+ setEndDrag(false)
63
+ return
64
+ }
65
+
66
+ if (isInvertPosition && offset < 0) animateStates.position.setValue(offset)
67
+ if (!isInvertPosition && offset > 0) animateStates.position.setValue(offset)
68
+ // eslint-disable-next-line react-hooks/exhaustive-deps
69
+ }, [offset, endDrag])
70
+
71
+ const responder = useMemo(() => PanResponder.create({
72
+ onStartShouldSetPanResponder: () => true,
73
+ onMoveShouldSetPanResponder: () => false,
74
+ onPanResponderTerminationRequest: () => false,
75
+ onShouldBlockNativeResponder: () => false,
76
+ onStartShouldSetPanResponderCapture: (evt, gestureState) => {
77
+ return isHorizontal ? (gestureState.dx !== 0) : (gestureState.dy !== 0)
78
+ },
79
+ onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
80
+ return isHorizontal ? (gestureState.dx !== 0) : (gestureState.dy !== 0)
81
+ },
82
+ onPanResponderGrant: e => {
83
+ if (isHorizontal) {
84
+ setStartDrag(e.nativeEvent.locationX)
85
+ } else {
86
+ setStartDrag(e.nativeEvent.locationY)
87
+ }
88
+ },
89
+ onPanResponderMove: (e, gesture) => {
90
+ if (startDrag) {
91
+ setOffset(isHorizontal ? gesture.dx : gesture.dy)
92
+ }
93
+ },
94
+ onPanResponderEnd: () => {
95
+ if (startDrag) setEndDrag(true)
96
+ }
97
+ }), [startDrag, isHorizontal])
98
+
99
+ const _responder = !isSwipe ? { panHandlers: {} } : responder
100
+ const _responderStyle = StyleSheet.flatten([
101
+ RESPONDER_STYLES[position],
102
+ swipeStyle
103
+ ])
104
+
105
+ return pug`
106
+ View.responder(..._responder.panHandlers style=_responderStyle)
107
+ `
108
+ }
109
+
110
+ export default observer(themed('Drawer', Swipe))
package/animate.ts ADDED
@@ -0,0 +1,88 @@
1
+ import { Animated } from 'react-native'
2
+
3
+ export interface DrawerAnimateStates {
4
+ opacity: Animated.Value
5
+ position: Animated.Value
6
+ }
7
+
8
+ export interface DrawerAnimateParams {
9
+ width: number
10
+ height: number
11
+ animateStates: DrawerAnimateStates
12
+ hasOverlay: boolean
13
+ isHorizontal: boolean
14
+ isInvertPosition: boolean
15
+ isInit?: boolean
16
+ }
17
+
18
+ export default {
19
+ show ({
20
+ width,
21
+ height,
22
+ animateStates,
23
+ hasOverlay,
24
+ isHorizontal,
25
+ isInvertPosition,
26
+ isInit
27
+ }: DrawerAnimateParams, callback?: () => void) {
28
+ if (isInit) {
29
+ animateStates.position.setValue(
30
+ isHorizontal
31
+ ? (isInvertPosition ? -width : width)
32
+ : (isInvertPosition ? -height : height)
33
+ )
34
+ }
35
+
36
+ const animations = [
37
+ Animated.timing(animateStates.position, {
38
+ toValue: 0,
39
+ duration: 300,
40
+ useNativeDriver: false
41
+ })
42
+ ]
43
+
44
+ if (hasOverlay) {
45
+ animations.push(
46
+ Animated.timing(animateStates.opacity, {
47
+ toValue: 1,
48
+ duration: 300,
49
+ useNativeDriver: false
50
+ })
51
+ )
52
+ }
53
+
54
+ Animated.parallel(animations).start(callback)
55
+ },
56
+
57
+ hide ({
58
+ width,
59
+ height,
60
+ animateStates,
61
+ hasOverlay,
62
+ isHorizontal,
63
+ isInvertPosition
64
+ }: DrawerAnimateParams, callback?: () => void) {
65
+ const animations = [
66
+ Animated.timing(animateStates.position, {
67
+ toValue:
68
+ isHorizontal
69
+ ? (isInvertPosition ? -width : width)
70
+ : (isInvertPosition ? -height : height),
71
+ duration: 200,
72
+ useNativeDriver: false
73
+ })
74
+ ]
75
+
76
+ if (hasOverlay) {
77
+ animations.push(
78
+ Animated.timing(animateStates.opacity, {
79
+ toValue: 0,
80
+ duration: 200,
81
+ useNativeDriver: false
82
+ })
83
+ )
84
+ }
85
+
86
+ Animated.parallel(animations).start(callback)
87
+ }
88
+ }
@@ -0,0 +1,38 @@
1
+ .overlayCase
2
+ width 100%
3
+ height 100%
4
+ position absolute
5
+
6
+ .overlay
7
+ position absolute
8
+ width 100%
9
+ height 100%
10
+ background-color rgba(0, 0, 0, .5)
11
+
12
+ .case
13
+ height 100%
14
+ width 100%
15
+
16
+ .area
17
+ height 100%
18
+ width 100%
19
+ position absolute
20
+
21
+ .contentDefault
22
+ background-color #ffffff
23
+ shadow(2)
24
+
25
+ .fullVertical
26
+ height 80%
27
+ width 100%
28
+
29
+ .fullHorizontal
30
+ width 80%
31
+ height 100%
32
+
33
+ .contentBottom
34
+ height 60%
35
+ border-radius 2u 2u 0 0
36
+
37
+ .responder
38
+ position absolute
package/index.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ /* eslint-disable */
2
+ // DO NOT MODIFY THIS FILE - IT IS AUTOMATICALLY GENERATED ON COMMITS.
3
+
4
+ import { type ComponentType, type ReactNode } from 'react';
5
+ import { type StyleProp, type ViewStyle } from 'react-native';
6
+ import './index.cssx.styl';
7
+ export { default as useDrawerDismiss } from './useDrawerDismiss';
8
+ export declare const _PropsJsonSchema: {};
9
+ export interface DrawerProps {
10
+ /** Custom styles applied to the drawer container */
11
+ style?: StyleProp<ViewStyle>;
12
+ /** Root component wrapping the drawer area @default SafeAreaView */
13
+ AreaComponent?: ComponentType<any>;
14
+ /** Component used as the drawer content wrapper @default View */
15
+ ContentComponent?: ComponentType<any>;
16
+ /** Custom styles applied to the swipe responder zone */
17
+ swipeStyle?: StyleProp<ViewStyle>;
18
+ /** Content rendered inside the drawer */
19
+ children?: ReactNode;
20
+ /** Controlled visibility flag @default false */
21
+ visible?: boolean;
22
+ /** Drawer position relative to the screen @default 'left' */
23
+ position?: 'left' | 'right' | 'top' | 'bottom';
24
+ /** Enable swipe-to-close interaction @default true */
25
+ isSwipe?: boolean;
26
+ /** Render a dimming overlay behind the drawer @default true */
27
+ hasOverlay?: boolean;
28
+ /** Show swipe responder zone @default true */
29
+ showResponder?: boolean;
30
+ /** Called after drawer is dismissed (after hide animation completes) */
31
+ onDismiss: () => void;
32
+ /** Called after drawer becomes visible (after show animation completes) */
33
+ onRequestOpen?: () => void;
34
+ }
35
+ declare const _default: ComponentType<DrawerProps>;
36
+ export default _default;
package/index.tsx ADDED
@@ -0,0 +1,206 @@
1
+ import { useState, useEffect, useRef, type ComponentType, type ReactNode } from 'react'
2
+ import {
3
+ SafeAreaView,
4
+ Animated,
5
+ View,
6
+ TouchableWithoutFeedback,
7
+ StyleSheet,
8
+ type StyleProp,
9
+ type ViewStyle
10
+ } from 'react-native'
11
+ import { pug, observer } from 'startupjs'
12
+ import { themed } from '@startupjs-ui/core'
13
+ import Portal from '@startupjs-ui/portal'
14
+ import Swipe from './Swipe'
15
+ import animate, { type DrawerAnimateStates } from './animate'
16
+ import './index.cssx.styl'
17
+
18
+ export { default as useDrawerDismiss } from './useDrawerDismiss'
19
+
20
+ const POSITION_STYLES = {
21
+ left: { alignItems: 'flex-start' },
22
+ right: { alignItems: 'flex-end' },
23
+ top: { justifyContent: 'flex-start' },
24
+ bottom: { justifyContent: 'flex-end' }
25
+ } satisfies Record<string, ViewStyle>
26
+
27
+ const POSITION_NAMES = {
28
+ left: 'translateX',
29
+ right: 'translateX',
30
+ top: 'translateY',
31
+ bottom: 'translateY'
32
+ } as const
33
+
34
+ export const _PropsJsonSchema = {/* DrawerProps */}
35
+
36
+ export interface DrawerProps {
37
+ /** Custom styles applied to the drawer container */
38
+ style?: StyleProp<ViewStyle>
39
+ /** Root component wrapping the drawer area @default SafeAreaView */
40
+ AreaComponent?: ComponentType<any>
41
+ /** Component used as the drawer content wrapper @default View */
42
+ ContentComponent?: ComponentType<any>
43
+ /** Custom styles applied to the swipe responder zone */
44
+ swipeStyle?: StyleProp<ViewStyle>
45
+ /** Content rendered inside the drawer */
46
+ children?: ReactNode
47
+ /** Controlled visibility flag @default false */
48
+ visible?: boolean
49
+ /** Drawer position relative to the screen @default 'left' */
50
+ position?: 'left' | 'right' | 'top' | 'bottom'
51
+ /** Enable swipe-to-close interaction @default true */
52
+ isSwipe?: boolean
53
+ /** Render a dimming overlay behind the drawer @default true */
54
+ hasOverlay?: boolean
55
+ /** Show swipe responder zone @default true */
56
+ showResponder?: boolean
57
+ /** Called after drawer is dismissed (after hide animation completes) */
58
+ onDismiss: () => void
59
+ /** Called after drawer becomes visible (after show animation completes) */
60
+ onRequestOpen?: () => void
61
+ }
62
+
63
+ // TODO: more test for work responder with ScrollView
64
+ // https://material-ui.com/ru/components/drawers/#%D1%81%D1%82%D0%BE%D0%B9%D0%BA%D0%B0%D1%8F-%D0%BF%D0%B0%D0%BD%D0%B5%D0%BB%D1%8C
65
+ function Drawer ({
66
+ style,
67
+ AreaComponent = SafeAreaView,
68
+ ContentComponent = View,
69
+ swipeStyle,
70
+ children,
71
+ visible = false,
72
+ position = 'left',
73
+ isSwipe = true,
74
+ hasOverlay = true,
75
+ showResponder = true,
76
+ onDismiss,
77
+ onRequestOpen
78
+ }: DrawerProps): ReactNode {
79
+ const isHorizontal = position === 'left' || position === 'right'
80
+ const isInvertPosition = position === 'left' || position === 'top'
81
+
82
+ const refContent = useRef<any>(null)
83
+ const [isShow, setIsShow] = useState(false)
84
+ const [contentSize, setContentSize] = useState<{ width?: number, height?: number }>({})
85
+
86
+ const [animateStates] = useState<DrawerAnimateStates>({
87
+ opacity: new Animated.Value(visible ? 1 : 0),
88
+ position: new Animated.Value(0)
89
+ })
90
+
91
+ // -main
92
+ useEffect(() => {
93
+ if (visible) {
94
+ setIsShow(true)
95
+ setTimeout(() => { void runShow() }, 0)
96
+ } else {
97
+ runHide()
98
+ }
99
+ // eslint-disable-next-line react-hooks/exhaustive-deps
100
+ }, [visible])
101
+ // -
102
+
103
+ async function waitForDrawerRef () {
104
+ let attempts = 0
105
+
106
+ while (attempts < 5) {
107
+ if (refContent.current) return true
108
+ await new Promise(resolve => setTimeout(resolve, 30))
109
+ attempts++
110
+ }
111
+
112
+ return !!refContent.current
113
+ }
114
+
115
+ async function runShow () {
116
+ await waitForDrawerRef()
117
+
118
+ getValidNode(refContent.current).measure((x: number, y: number, width: number, height: number) => {
119
+ const isInit = !contentSize.width
120
+ setContentSize({ width, height })
121
+
122
+ animate.show({
123
+ width,
124
+ height,
125
+ animateStates,
126
+ hasOverlay,
127
+ isHorizontal,
128
+ isInvertPosition,
129
+ isInit
130
+ }, () => {
131
+ onRequestOpen && onRequestOpen()
132
+ })
133
+ })
134
+ }
135
+
136
+ async function runHide () {
137
+ if (!refContent.current) return
138
+
139
+ getValidNode(refContent.current).measure((x: number, y: number, width: number, height: number) => {
140
+ animate.hide({
141
+ width,
142
+ height,
143
+ animateStates,
144
+ hasOverlay,
145
+ isHorizontal,
146
+ isInvertPosition
147
+ }, () => {
148
+ setContentSize({})
149
+ setIsShow(false)
150
+ onDismiss()
151
+ })
152
+ })
153
+ }
154
+
155
+ const _styleCase = StyleSheet.flatten([
156
+ POSITION_STYLES[position],
157
+ { opacity: isShow ? 1 : 0 }
158
+ ])
159
+
160
+ const _styleContent = StyleSheet.flatten([
161
+ { transform: [{ [POSITION_NAMES[position]]: animateStates.position }] },
162
+ style
163
+ ])
164
+
165
+ return pug`
166
+ if isShow
167
+ Portal
168
+ AreaComponent.area
169
+ ContentComponent.case(style=_styleCase)
170
+ if hasOverlay
171
+ TouchableWithoutFeedback.overlayCase(onPress=onDismiss)
172
+ Animated.View.overlay(style={ opacity: animateStates.opacity })
173
+
174
+ Animated.View(
175
+ ref=refContent
176
+ styleName={
177
+ contentDefault: isShow,
178
+ contentBottom: isShow && position === 'bottom',
179
+ fullHorizontal: isShow && isHorizontal,
180
+ fullVertical: isShow && !isHorizontal
181
+ }
182
+ style=_styleContent
183
+ )
184
+ if showResponder
185
+ Swipe(
186
+ position=position
187
+ contentSize=contentSize
188
+ swipeStyle=swipeStyle
189
+ isHorizontal=isHorizontal
190
+ isSwipe=isSwipe
191
+ isInvertPosition=isInvertPosition
192
+ animateStates=animateStates
193
+ runHide=runHide
194
+ runShow=runShow
195
+ )
196
+ = children
197
+ `
198
+ }
199
+
200
+ function getValidNode (current: any) {
201
+ return current?.measure
202
+ ? current
203
+ : current?.getNode?.()
204
+ }
205
+
206
+ export default observer(themed('Drawer', Drawer))
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@startupjs-ui/drawer",
3
+ "version": "0.1.3",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "main": "index.tsx",
8
+ "types": "index.d.ts",
9
+ "type": "module",
10
+ "dependencies": {
11
+ "@startupjs-ui/core": "^0.1.3",
12
+ "@startupjs-ui/portal": "^0.1.3"
13
+ },
14
+ "peerDependencies": {
15
+ "react": "*",
16
+ "react-native": "*",
17
+ "startupjs": "*"
18
+ },
19
+ "gitHead": "fd964ebc3892d3dd0a6c85438c0af619cc50c3f0"
20
+ }
@@ -0,0 +1,36 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ interface DrawerDismissState {
4
+ tag: string
5
+ data: any
6
+ }
7
+
8
+ export interface DrawerDismissCallbacks {
9
+ default: (data?: any) => void
10
+ [tag: string]: ((data?: any) => void) | any
11
+ }
12
+
13
+ export default function useDrawerDismiss (
14
+ callbacks: DrawerDismissCallbacks
15
+ ): [
16
+ onDismiss: () => void,
17
+ setOnDismiss: (tag: string, data?: any) => void
18
+ ] {
19
+ const [state, setState] = useState<DrawerDismissState>({ tag: 'default', data: null })
20
+
21
+ useEffect(() => {
22
+ if (state.tag !== 'default') {
23
+ callbacks.default()
24
+ }
25
+ // preserve legacy behavior (no dependency array)
26
+ // eslint-disable-next-line react-hooks/exhaustive-deps
27
+ })
28
+
29
+ return [
30
+ () => {
31
+ callbacks[state.tag]?.(state.data)
32
+ setState({ tag: 'default', data: null })
33
+ },
34
+ (tag, data) => { setState({ tag, data }) }
35
+ ]
36
+ }