Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions example/.ondevice/storybook.requires.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ try {
const getStories = () => {
return {
"./src/stories/Accordion.stories.tsx": require("../src/stories/Accordion.stories.tsx"),
"./src/stories/BottomSheet.stories.tsx": require("../src/stories/BottomSheet.stories.tsx"),
"./src/stories/Button.stories.tsx": require("../src/stories/Button.stories.tsx"),
"./src/stories/Card.stories.tsx": require("../src/stories/Card.stories.tsx"),
"./src/stories/Checkbox.stories.tsx": require("../src/stories/Checkbox.stories.tsx"),
Expand Down
12 changes: 6 additions & 6 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ PODS:
- React-Core
- RNGestureHandler (2.12.0):
- React-Core
- RNReanimated (3.3.0):
- RNReanimated (3.5.4):
- DoubleConversion
- FBLazyVector
- FBReactNativeSpec
Expand Down Expand Up @@ -645,11 +645,11 @@ SPEC CHECKSUMS:
Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541
FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 791fe035093b84822da7f0870421a25839ca7870
hermes-engine: 5d86dc4303697a1251c830f0ee45e6e9f33877d4
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
hermes-engine: a1f157c49ea579c28b0296bda8530e980c45bdb3
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
RCT-Folly: 186619bc27c1f94f2f7c6ef60927516c71005915
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCTRequired: 5a024fdf458fa8c0d82fc262e76f982d4dcdecdd
RCTTypeSafety: b6c253064466411c6810b45f66bc1e43ce0c54ba
React: 715292db5bd46989419445a5547954b25d2090f0
Expand Down Expand Up @@ -682,11 +682,11 @@ SPEC CHECKSUMS:
RNCAsyncStorage: 4b98ac3c64efa4e35c1197cb0c5ca5e9f5d4c666
RNDateTimePicker: 65e1d202799460b286ff5e741d8baf54695e8abd
RNGestureHandler: dec4645026e7401a0899f2846d864403478ff6a5
RNReanimated: 9976fbaaeb8a188c36026154c844bf374b3b7eeb
RNReanimated: be07c7ae209074d0e8a84cf38b7909457ac59a32
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
Yoga: 79dd7410de6f8ad73a77c868d3d368843f0c93e0
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a

PODFILE CHECKSUM: 41b345e700f785c9a103f926104c4c507c89b32e

COCOAPODS: 1.11.3
COCOAPODS: 1.12.1
7 changes: 3 additions & 4 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"dependencies": {
"react": "18.2.0",
"react-native": "0.71.4",
"react-native-reanimated": "^3.0.2"
"react-native-reanimated": "^3.5.4",
"react-native-gesture-handler": "^2.13.1",
"styled-components": "^5.3.9"
},
"devDependencies": {
"@storybook/addon-actions": "^6.5.14",
Expand All @@ -39,9 +41,6 @@
"@babel/runtime": "^7.20.0",
"metro-react-native-babel-preset": "0.73.8",
"babel-plugin-module-resolver": "^4.1.0",
"styled-components": "^5.3.9",
"react-native-gesture-handler": "^2.9.0",
"react-native-reanimated": "^3.0.2",
"@types/styled-components-react-native": "^5.2.1"
}
}
79 changes: 79 additions & 0 deletions example/src/stories/BottomSheet.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type {ComponentMeta, ComponentStory} from '@storybook/react'
import React, {useRef, useState} from 'react'
import {Text, TextInput} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {BottomSheet, Button} from 'rn-base-component'
import type {BottomSheetMethods} from 'src/components/BottomSheet/BottomSheet'

export default {
title: 'components/BottomSheet',
component: BottomSheet,
} as ComponentMeta<typeof BottomSheet>

export const Basic: ComponentStory<typeof BottomSheet> = rest => {
const ref = useRef<BottomSheetMethods>(null)
const insets = useSafeAreaInsets()

const [isVisible, setIsVisible] = useState(false)

return (
<>
<Button text="Open Bottom Sheet" onPress={() => ref?.current?.open?.()} />

<BottomSheet
{...rest}
ref={ref}
isVisible={isVisible}
onChangeValue={setIsVisible}
bottomInset={insets.bottom}
title="Header">
<Text>Header</Text>
<TextInput placeholder="Search" />
<Text>Content</Text>
<Text>Content</Text>
<Text>Content</Text>
<Text>Content</Text>
<Text>Content</Text>
<Text>Content</Text>
<Text>footer</Text>
</BottomSheet>
</>
)
}

export const ContentHeight: ComponentStory<typeof BottomSheet> = rest => {
const ref = useRef<BottomSheetMethods>(null)
const insets = useSafeAreaInsets()

const [isVisible, setIsVisible] = useState(false)

return (
<>
<Button text="Open Bottom Sheet" onPress={() => ref?.current?.open?.()} />

<BottomSheet
{...rest}
ref={ref}
isVisible={isVisible}
onChangeValue={setIsVisible}
bottomInset={insets.bottom}
title="Header">
<Text>Header</Text>
<TextInput placeholder="Search" />
<Text>Content</Text>
<Text>Content</Text>
<Text>Content</Text>
<Text>Content</Text>
<Text>Content</Text>
<Text>Content</Text>
<Text>footer</Text>
</BottomSheet>
</>
)
}

Basic.args = {}

ContentHeight.args = {
contentHeight: 400,
}
6 changes: 3 additions & 3 deletions example/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9790,9 +9790,9 @@ react-native-modal-selector@^2.1.1:
prop-types "^15.5.10"

react-native-reanimated@^3.0.2:
version "3.3.0"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.3.0.tgz#80f9d58e28fddf62fe4c1bc792337b8ab57936ab"
integrity sha512-LzfpPZ1qXBGy5BcUHqw3pBC0qSd22qXS3t8hWSbozXNrBkzMhhOrcILE/nEg/PHpNNp1xvGOW8NwpAMF006roQ==
version "3.5.4"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.5.4.tgz#a6c2b0c43b6dad246f5d276213974afedb8e3fc7"
integrity sha512-8we9LLDO1o4Oj9/DICeEJ2K1tjfqkJagqQUglxeUAkol/HcEJ6PGxIrpBcNryLqCDYEcu6FZWld/FzizBIw6bg==
dependencies:
"@babel/plugin-transform-object-assign" "^7.16.7"
"@babel/preset-typescript" "^7.16.7"
Expand Down
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
"react-addons-test-utils": "^15.6.2",
"react-native": "0.71.4",
"react-native-builder-bob": "^0.20.4",
"react-native-gesture-handler": "^2.13.1",
"react-native-reanimated": "^3.5.4",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need this lib in dev dependencies?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because i'll get an error 'not found lib'. if i don't add 2 libs in dev

"react-test-renderer": "^18.2.0",
"release-it": "^15.0.0",
"styled-components": "^5.3.6",
Expand All @@ -95,6 +97,8 @@
"peerDependencies": {
"react": "*",
"react-native": "*",
"react-native-gesture-handler": "*",
"react-native-reanimated": "*",
"styled-components": "*"
},
"engines": {
Expand Down Expand Up @@ -136,10 +140,5 @@
}
]
]
},
"dependencies": {
"eslint-plugin-ft-flow": "^2.0.3",
"react-native-gesture-handler": "^2.9.0",
"react-native-reanimated": "3.0.2"
}
}
117 changes: 117 additions & 0 deletions src/components/BottomSheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import React, {forwardRef, memo, useCallback, useImperativeHandle, useMemo} from 'react'
import {runOnJS, useAnimatedKeyboard, useDerivedValue, useSharedValue} from 'react-native-reanimated'
import BottomSheetBackdrop from './components/BottomSheetBackdrop'
import BottomSheetContainer from './components/BottomSheetContainer'
import BottomSheetContent from './components/BottomSheetContent'
import BottomSheetContentContainer from './components/BottomSheetContentContainer'
import BottomSheetHeader from './components/BottomSheetHeader'
import {DEFAULT_CONTENT_HEIGHT, INITIAL_CONTENT_HEIGHT_POSITIVE} from './constants'
import {BottomSheetContextType, BottomSheetProvider} from './contexts'
import type {ViewStyle} from 'react-native'

export interface BottomSheetProps {
// configurations
isVisible: boolean
contentHeight?: number
title?: string
bottomInset: number
shouldPushContentWithKeyboardSize?: boolean
// callbacks
onChangeValue: (value: boolean) => void
onConfirm?: () => void
// styles
style?: ViewStyle
backdropStyle?: ViewStyle
contentContainerStyle?: ViewStyle
contentHeaderStyle?: ViewStyle
contentStyle?: ViewStyle
// components
children?: React.ReactNode
}

export type BottomSheetMethods = BottomSheetContextType

const BottomSheetComponent = forwardRef<BottomSheetMethods, BottomSheetProps>(
(
{
// configurations
isVisible,
contentHeight = DEFAULT_CONTENT_HEIGHT,
title,
bottomInset,
shouldPushContentWithKeyboardSize,
// callbacks
onChangeValue: _onChangeValue,
onConfirm,
// styles
style,
backdropStyle,
contentContainerStyle,
contentHeaderStyle,
contentStyle,
// components
children,
},
ref,
) => {
// variables
const keyboard = useAnimatedKeyboard()
const animatedIsVisible = useSharedValue(isVisible)
const animatedCalculateContentHeight = useSharedValue(0)
const animatedContentHeight = useDerivedValue(() => {
if (contentHeight > 0) {
return contentHeight
}
return animatedCalculateContentHeight.value
}, [])
const animatedContentTranslateY = useDerivedValue(() => {
runOnJS(_onChangeValue)(animatedIsVisible.value)

if (animatedIsVisible.value) {
return shouldPushContentWithKeyboardSize ? -keyboard.height.value : 0
} else {
return animatedContentHeight.value + bottomInset || INITIAL_CONTENT_HEIGHT_POSITIVE
}
}, [])

// callbacks
const handleOpen = useCallback(() => {
animatedIsVisible.value = true
}, [animatedIsVisible])
const handleClose = useCallback(() => {
animatedIsVisible.value = false
}, [animatedIsVisible])

// context
const contextValues: BottomSheetContextType = useMemo(
() => ({
animatedIsVisible,
open: handleOpen,
close: handleClose,
}),
[animatedIsVisible, handleClose, handleOpen],
)

useImperativeHandle(ref, () => contextValues)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need export contextValues?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because we can use this context inside and we can custom component outside

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get what you mean. can you give some example?


return (
<BottomSheetProvider value={contextValues}>
<BottomSheetContainer style={style}>
<BottomSheetBackdrop style={backdropStyle} />
<BottomSheetContentContainer
style={contentContainerStyle}
{...{animatedCalculateContentHeight, animatedContentHeight, animatedContentTranslateY}}>
<BottomSheetHeader style={contentHeaderStyle} {...{title, onConfirm}} />
<BottomSheetContent style={contentStyle}>{children}</BottomSheetContent>
</BottomSheetContentContainer>
</BottomSheetContainer>
</BottomSheetProvider>
)
},
)

BottomSheetComponent.displayName = 'BottomSheetComponent'
const BottomSheet = memo(BottomSheetComponent)
BottomSheet.displayName = 'BottomSheet'

export default BottomSheet
56 changes: 56 additions & 0 deletions src/components/BottomSheet/components/BottomSheetBackdrop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, {memo, useMemo} from 'react'
import {TapGestureHandler, TapGestureHandlerGestureEvent} from 'react-native-gesture-handler'
import Animated, {
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
withTiming,
} from 'react-native-reanimated'
import styled from 'styled-components'
import {useBottomSheet} from '../hooks'
import type {ITheme} from 'src/theme'
import {StyleSheet, type StyleProp, type ViewStyle} from 'react-native'

const Container = styled(Animated.View)<{theme: ITheme}>(({theme}) => ({
...StyleSheet.absoluteFillObject,
backgroundColor: theme?.colors?.black,
opacity: theme?.opacity?.partiallyOpaque,
}))

export interface BottomSheetBackdropProps {
style?: StyleProp<ViewStyle>
children?: React.ReactNode
}

const BottomSheetBackdropComponent: React.FC<BottomSheetBackdropProps> = ({style, children}) => {
const {animatedIsVisible, close} = useBottomSheet()

const animatedStyle = useAnimatedStyle(
() => ({
opacity: withTiming(animatedIsVisible.value ? 0.5 : 0),
}),
[],
)

const containerStyle = useMemo(() => StyleSheet.flatten([animatedStyle, style]), [animatedStyle, style])

const gestureHandler = useAnimatedGestureHandler<TapGestureHandlerGestureEvent>(
{
onFinish: () => {
runOnJS(close)()
},
},
[],
)

return (
<TapGestureHandler onGestureEvent={gestureHandler}>
<Container style={containerStyle}>{children}</Container>
</TapGestureHandler>
)
}

const BottomSheetBackdrop = memo(BottomSheetBackdropComponent)
BottomSheetBackdrop.displayName = 'BottomSheetBackdrop'

export default BottomSheetBackdrop
Loading