@butternutbox/pawprint-native 1.6.2 → 2.0.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.
- package/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +60 -0
- package/COMPONENT_GUIDELINES.md +57 -1
- package/dist/index.cjs +1459 -1380
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -117
- package/dist/index.d.ts +81 -117
- package/dist/index.js +1206 -1125
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/__mocks__/react-native.tsx +33 -5
- package/src/__mocks__/rn-primitives/portal.tsx +71 -0
- package/src/components/atoms/Input/InputField.tsx +12 -2
- package/src/components/molecules/Drawer/Drawer.test.tsx +54 -2
- package/src/components/molecules/Drawer/DrawerBody.tsx +2 -17
- package/src/components/molecules/Drawer/DrawerContent.tsx +49 -2
- package/src/components/molecules/Drawer/DrawerOverlay.tsx +28 -16
- package/src/components/molecules/SelectField/AsyncSearch.test.tsx +373 -0
- package/src/components/molecules/SelectField/SelectDrawerField.stories.tsx +192 -0
- package/src/components/molecules/SelectField/SelectDrawerField.test.tsx +210 -0
- package/src/components/molecules/SelectField/SelectDrawerField.tsx +245 -0
- package/src/components/molecules/SelectField/SelectField.stories.tsx +221 -421
- package/src/components/molecules/SelectField/SelectField.test.tsx +214 -662
- package/src/components/molecules/SelectField/SelectField.tsx +269 -539
- package/src/components/molecules/SelectField/SelectFieldDropdown.tsx +90 -0
- package/src/components/molecules/SelectField/SelectFieldList.tsx +88 -0
- package/src/components/molecules/SelectField/SelectFieldOption.tsx +90 -0
- package/src/components/molecules/SelectField/core/index.ts +8 -0
- package/src/components/molecules/SelectField/core/listHeight.test.ts +22 -0
- package/src/components/molecules/SelectField/core/listHeight.ts +8 -0
- package/src/components/molecules/SelectField/core/options.ts +35 -0
- package/src/components/molecules/SelectField/core/useSelectOptions.ts +116 -0
- package/src/components/molecules/SelectField/index.ts +12 -9
- package/src/index.ts +0 -1
- package/src/test-utils.tsx +5 -1
- package/vitest.config.ts +7 -0
- package/src/components/molecules/SelectField/SelectFieldContent.tsx +0 -81
- package/src/components/molecules/SelectField/SelectFieldItem.tsx +0 -133
- package/src/components/molecules/SelectField/SelectFieldTrigger.tsx +0 -188
- package/src/components/molecules/SelectField/SelectFieldValue.tsx +0 -33
- package/src/components/molecules/SelectField/hooks/index.ts +0 -2
- package/src/components/molecules/SelectField/hooks/useSelectField.ts +0 -84
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useDrawerKeyboardAvoidance.ts +0 -29
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import { Pressable, View, Text, PressableProps } from "react-native"
|
|
3
|
-
import styled from "@emotion/native"
|
|
4
|
-
import { useTheme } from "@emotion/react"
|
|
5
|
-
import * as SelectPrimitive from "@rn-primitives/select"
|
|
6
|
-
import { Slot } from "@rn-primitives/slot"
|
|
7
|
-
|
|
8
|
-
type SelectFieldItemOwnProps = {
|
|
9
|
-
value: string
|
|
10
|
-
leadingIcon?: React.ReactNode
|
|
11
|
-
hintText?: string
|
|
12
|
-
disabled?: boolean
|
|
13
|
-
isSelected?: boolean
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type SelectFieldItemProps = SelectFieldItemOwnProps &
|
|
17
|
-
Omit<PressableProps, keyof SelectFieldItemOwnProps>
|
|
18
|
-
|
|
19
|
-
const parseTokenValue = (value: string): number => parseFloat(value)
|
|
20
|
-
|
|
21
|
-
const StyledItem = styled(Pressable)<{ disabled?: boolean }>(({ theme }) => {
|
|
22
|
-
const { dropdown } = theme.tokens.components.dropdownList
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
flexDirection: "row",
|
|
26
|
-
alignItems: "center",
|
|
27
|
-
gap: parseTokenValue(dropdown.listItem.content.gap),
|
|
28
|
-
height: parseTokenValue(dropdown.listItem.size.height),
|
|
29
|
-
paddingVertical: parseTokenValue(dropdown.listItem.spacing.verticalPadding),
|
|
30
|
-
paddingHorizontal: parseTokenValue(
|
|
31
|
-
dropdown.listItem.spacing.horiztonalPadding
|
|
32
|
-
),
|
|
33
|
-
backgroundColor: dropdown.listItem.colour.background.default
|
|
34
|
-
}
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
const StyledIconWrapper = styled(View)({
|
|
38
|
-
flexShrink: 0,
|
|
39
|
-
alignItems: "center",
|
|
40
|
-
justifyContent: "center"
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
const StyledContentWrapper = styled(View)(({ theme }) => {
|
|
44
|
-
const { dropdown } = theme.tokens.components.dropdownList
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
flexDirection: "row",
|
|
48
|
-
alignItems: "center",
|
|
49
|
-
gap: parseTokenValue(dropdown.listItem.content.gap),
|
|
50
|
-
flex: 1,
|
|
51
|
-
minWidth: 0
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
const StyledTextWrapper = styled(View)({
|
|
56
|
-
flex: 1,
|
|
57
|
-
minWidth: 0
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const StyledItemText = styled(Text)(({ theme }) => {
|
|
61
|
-
const { dropdown } = theme.tokens.components.dropdownList
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
color: dropdown.listItem.colour.text.placeholder.default
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
const StyledHintText = styled(Text)(({ theme }) => {
|
|
69
|
-
const { dropdown } = theme.tokens.components.dropdownList
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
color: dropdown.listItem.colour.text.hint.default,
|
|
73
|
-
flexShrink: 0
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
export const SelectFieldItem = React.forwardRef<View, SelectFieldItemProps>(
|
|
78
|
-
(
|
|
79
|
-
{ value, leadingIcon, hintText, children, disabled, isSelected, ...rest },
|
|
80
|
-
ref
|
|
81
|
-
) => {
|
|
82
|
-
const theme = useTheme()
|
|
83
|
-
const { dropdown } = theme.tokens.components.dropdownList
|
|
84
|
-
const itemText = children as React.ReactNode
|
|
85
|
-
|
|
86
|
-
const backgroundColor = disabled
|
|
87
|
-
? dropdown.listItem.colour.background.disabled
|
|
88
|
-
: isSelected
|
|
89
|
-
? dropdown.listItem.colour.background.hover
|
|
90
|
-
: dropdown.listItem.colour.background.default
|
|
91
|
-
|
|
92
|
-
const textColor = disabled
|
|
93
|
-
? dropdown.listItem.colour.text.placeholder.disabled
|
|
94
|
-
: dropdown.listItem.colour.text.placeholder.default
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<SelectPrimitive.Item
|
|
98
|
-
label={String(children)}
|
|
99
|
-
value={value}
|
|
100
|
-
disabled={disabled}
|
|
101
|
-
asChild
|
|
102
|
-
>
|
|
103
|
-
<Slot ref={ref}>
|
|
104
|
-
<StyledItem disabled={disabled} style={{ backgroundColor }} {...rest}>
|
|
105
|
-
{leadingIcon && (
|
|
106
|
-
<StyledIconWrapper>{leadingIcon}</StyledIconWrapper>
|
|
107
|
-
)}
|
|
108
|
-
<StyledContentWrapper>
|
|
109
|
-
<StyledTextWrapper>
|
|
110
|
-
<StyledItemText style={{ color: textColor }}>
|
|
111
|
-
{itemText}
|
|
112
|
-
</StyledItemText>
|
|
113
|
-
</StyledTextWrapper>
|
|
114
|
-
{hintText && (
|
|
115
|
-
<StyledHintText
|
|
116
|
-
style={{
|
|
117
|
-
color: disabled
|
|
118
|
-
? dropdown.listItem.colour.text.hint.disabled
|
|
119
|
-
: dropdown.listItem.colour.text.hint.default
|
|
120
|
-
}}
|
|
121
|
-
>
|
|
122
|
-
{hintText}
|
|
123
|
-
</StyledHintText>
|
|
124
|
-
)}
|
|
125
|
-
</StyledContentWrapper>
|
|
126
|
-
</StyledItem>
|
|
127
|
-
</Slot>
|
|
128
|
-
</SelectPrimitive.Item>
|
|
129
|
-
)
|
|
130
|
-
}
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
SelectFieldItem.displayName = "SelectField.Item"
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from "react"
|
|
2
|
-
import { Pressable, View, PressableProps, Animated, Easing } from "react-native"
|
|
3
|
-
import styled from "@emotion/native"
|
|
4
|
-
import { useTheme } from "@emotion/react"
|
|
5
|
-
import * as SelectPrimitive from "@rn-primitives/select"
|
|
6
|
-
import { Slot } from "@rn-primitives/slot"
|
|
7
|
-
import { Icon } from "../../atoms/Icon"
|
|
8
|
-
import {
|
|
9
|
-
KeyboardArrowDown,
|
|
10
|
-
Error as ErrorIcon,
|
|
11
|
-
CheckCircle as SuccessIcon
|
|
12
|
-
} from "@butternutbox/pawprint-icons/core"
|
|
13
|
-
import type { InputState } from "../../atoms/Input/InputField"
|
|
14
|
-
|
|
15
|
-
type SelectFieldTriggerOwnProps = {
|
|
16
|
-
state?: InputState
|
|
17
|
-
leadingIcon?: React.ReactNode
|
|
18
|
-
children?: React.ReactNode
|
|
19
|
-
isOpen?: boolean
|
|
20
|
-
isSearching?: boolean
|
|
21
|
-
actionIcon?: React.ReactNode
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type SelectFieldTriggerProps = SelectFieldTriggerOwnProps &
|
|
25
|
-
Omit<PressableProps, keyof SelectFieldTriggerOwnProps>
|
|
26
|
-
|
|
27
|
-
const parseTokenValue = (value: string): number => parseFloat(value)
|
|
28
|
-
|
|
29
|
-
const StyledTriggerWrapper = styled(Animated.View)<{ state: InputState }>(({
|
|
30
|
-
theme,
|
|
31
|
-
state: _state
|
|
32
|
-
}) => {
|
|
33
|
-
const { spacing, colour, borderRadius, size } = theme.tokens.components.inputs
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
flexDirection: "row",
|
|
37
|
-
alignItems: "center",
|
|
38
|
-
gap: parseTokenValue(spacing.field.gap),
|
|
39
|
-
minWidth: parseTokenValue(size.field.minWidth),
|
|
40
|
-
width: "100%",
|
|
41
|
-
height: parseTokenValue(size.field.height),
|
|
42
|
-
backgroundColor: colour.field.background.default,
|
|
43
|
-
borderRadius: parseTokenValue(borderRadius.field.default)
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const StyledIconWrapper = styled(View)({
|
|
48
|
-
flexShrink: 0,
|
|
49
|
-
alignItems: "center",
|
|
50
|
-
justifyContent: "center"
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
const StyledArrowIcon = styled(Animated.View)({
|
|
54
|
-
flexShrink: 0,
|
|
55
|
-
alignItems: "center",
|
|
56
|
-
justifyContent: "center"
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
export const SelectFieldTrigger = React.forwardRef<
|
|
60
|
-
View,
|
|
61
|
-
SelectFieldTriggerProps
|
|
62
|
-
>(
|
|
63
|
-
(
|
|
64
|
-
{
|
|
65
|
-
state = "default",
|
|
66
|
-
leadingIcon,
|
|
67
|
-
children,
|
|
68
|
-
isOpen = false,
|
|
69
|
-
isSearching = false,
|
|
70
|
-
actionIcon,
|
|
71
|
-
...rest
|
|
72
|
-
},
|
|
73
|
-
ref
|
|
74
|
-
) => {
|
|
75
|
-
const theme = useTheme()
|
|
76
|
-
const { colour, borderWidth, spacing } = theme.tokens.components.inputs
|
|
77
|
-
|
|
78
|
-
const hasState = state === "error" || state === "success"
|
|
79
|
-
|
|
80
|
-
const baseBorderWidth = parseTokenValue(borderWidth.field.default)
|
|
81
|
-
const selectedBorderWidth = parseTokenValue(borderWidth.field.selected)
|
|
82
|
-
const baseVerticalPadding = parseTokenValue(spacing.field.verticalPadding)
|
|
83
|
-
const baseHorizontalPadding = parseTokenValue(
|
|
84
|
-
spacing.field.horizontalPadding
|
|
85
|
-
)
|
|
86
|
-
const borderDelta = selectedBorderWidth - baseBorderWidth
|
|
87
|
-
|
|
88
|
-
const borderColor =
|
|
89
|
-
state === "error"
|
|
90
|
-
? colour.field.border.error
|
|
91
|
-
: state === "success"
|
|
92
|
-
? colour.field.border.success
|
|
93
|
-
: isOpen
|
|
94
|
-
? colour.field.border.selected
|
|
95
|
-
: colour.field.border.default
|
|
96
|
-
|
|
97
|
-
const isActiveBorder = isOpen || hasState
|
|
98
|
-
const borderAnim = useRef(
|
|
99
|
-
new Animated.Value(isActiveBorder ? 1 : 0)
|
|
100
|
-
).current
|
|
101
|
-
const rotationAnim = useRef(new Animated.Value(isOpen ? 1 : 0)).current
|
|
102
|
-
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
Animated.parallel([
|
|
105
|
-
Animated.timing(borderAnim, {
|
|
106
|
-
toValue: isActiveBorder ? 1 : 0,
|
|
107
|
-
duration: 50,
|
|
108
|
-
easing: Easing.ease,
|
|
109
|
-
useNativeDriver: false
|
|
110
|
-
}),
|
|
111
|
-
Animated.timing(rotationAnim, {
|
|
112
|
-
toValue: isOpen ? 1 : 0,
|
|
113
|
-
duration: 250,
|
|
114
|
-
easing: Easing.bezier(0.4, 0.0, 0.2, 1),
|
|
115
|
-
useNativeDriver: true
|
|
116
|
-
})
|
|
117
|
-
]).start()
|
|
118
|
-
}, [isActiveBorder, isOpen, borderAnim, rotationAnim])
|
|
119
|
-
|
|
120
|
-
const animatedBorderWidth = borderAnim.interpolate({
|
|
121
|
-
inputRange: [0, 1],
|
|
122
|
-
outputRange: [baseBorderWidth, selectedBorderWidth]
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
const animatedPaddingVertical = borderAnim.interpolate({
|
|
126
|
-
inputRange: [0, 1],
|
|
127
|
-
outputRange: [baseVerticalPadding, baseVerticalPadding - borderDelta]
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
const animatedPaddingHorizontal = borderAnim.interpolate({
|
|
131
|
-
inputRange: [0, 1],
|
|
132
|
-
outputRange: [baseHorizontalPadding, baseHorizontalPadding - borderDelta]
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
const animatedRotation = rotationAnim.interpolate({
|
|
136
|
-
inputRange: [0, 1],
|
|
137
|
-
outputRange: ["0deg", "180deg"]
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
const stateIcon =
|
|
141
|
-
state === "error" ? (
|
|
142
|
-
<Icon icon={ErrorIcon} size="md" colour="error" />
|
|
143
|
-
) : state === "success" ? (
|
|
144
|
-
<Icon icon={SuccessIcon} size="md" colour="success" />
|
|
145
|
-
) : null
|
|
146
|
-
|
|
147
|
-
const triggerContent = (
|
|
148
|
-
<StyledTriggerWrapper
|
|
149
|
-
state={state}
|
|
150
|
-
style={[
|
|
151
|
-
{
|
|
152
|
-
borderColor,
|
|
153
|
-
borderWidth: animatedBorderWidth,
|
|
154
|
-
paddingVertical: animatedPaddingVertical,
|
|
155
|
-
paddingHorizontal: animatedPaddingHorizontal
|
|
156
|
-
}
|
|
157
|
-
]}
|
|
158
|
-
>
|
|
159
|
-
{leadingIcon && <StyledIconWrapper>{leadingIcon}</StyledIconWrapper>}
|
|
160
|
-
{children}
|
|
161
|
-
{stateIcon && <StyledIconWrapper>{stateIcon}</StyledIconWrapper>}
|
|
162
|
-
{actionIcon ? (
|
|
163
|
-
<StyledIconWrapper>{actionIcon}</StyledIconWrapper>
|
|
164
|
-
) : (
|
|
165
|
-
<StyledArrowIcon
|
|
166
|
-
style={{ transform: [{ rotate: animatedRotation }] }}
|
|
167
|
-
>
|
|
168
|
-
<Icon icon={KeyboardArrowDown} size="md" colour="primary" />
|
|
169
|
-
</StyledArrowIcon>
|
|
170
|
-
)}
|
|
171
|
-
</StyledTriggerWrapper>
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
if (isSearching) {
|
|
175
|
-
return <View ref={ref}>{triggerContent}</View>
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return (
|
|
179
|
-
<SelectPrimitive.Trigger asChild>
|
|
180
|
-
<Slot ref={ref}>
|
|
181
|
-
<Pressable {...rest}>{triggerContent}</Pressable>
|
|
182
|
-
</Slot>
|
|
183
|
-
</SelectPrimitive.Trigger>
|
|
184
|
-
)
|
|
185
|
-
}
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
SelectFieldTrigger.displayName = "SelectField.Trigger"
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import { View, ViewProps } from "react-native"
|
|
3
|
-
import { useTheme } from "@emotion/react"
|
|
4
|
-
import * as SelectPrimitive from "@rn-primitives/select"
|
|
5
|
-
import { Typography } from "../../atoms/Typography"
|
|
6
|
-
|
|
7
|
-
type SelectFieldValueProps = {
|
|
8
|
-
placeholder?: string
|
|
9
|
-
hasValue?: boolean
|
|
10
|
-
} & Omit<ViewProps, "children">
|
|
11
|
-
|
|
12
|
-
export const SelectFieldValue = React.forwardRef<View, SelectFieldValueProps>(
|
|
13
|
-
({ placeholder, hasValue = true, ...rest }, ref) => {
|
|
14
|
-
const theme = useTheme()
|
|
15
|
-
const { colour, field } = theme.tokens.components.inputs
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<View ref={ref} style={{ flex: 1, minWidth: 0 }} {...rest}>
|
|
19
|
-
<Typography
|
|
20
|
-
token={field.placeholder.default}
|
|
21
|
-
color={
|
|
22
|
-
hasValue ? colour.field.text.default : colour.field.text.placeholder
|
|
23
|
-
}
|
|
24
|
-
align="left"
|
|
25
|
-
>
|
|
26
|
-
<SelectPrimitive.Value placeholder={placeholder ?? ""} />
|
|
27
|
-
</Typography>
|
|
28
|
-
</View>
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
SelectFieldValue.displayName = "SelectField.Value"
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { useState, useMemo } from "react"
|
|
2
|
-
import type { SelectFieldProps } from "../SelectField"
|
|
3
|
-
import type { InputState } from "../../../atoms/Input/InputField"
|
|
4
|
-
import type { Option } from "@rn-primitives/select"
|
|
5
|
-
|
|
6
|
-
export type SelectValidationRule<T = string | Option> = {
|
|
7
|
-
test: (value: T | null) => boolean
|
|
8
|
-
message: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
type SelectOptions<T = string | Option> = {
|
|
12
|
-
initialValue?: T | null
|
|
13
|
-
onValueChange?: (value: T | null) => void
|
|
14
|
-
validationRule?: SelectValidationRule<T>
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type SelectReturn<T = string | Option> = {
|
|
18
|
-
value: T | null
|
|
19
|
-
onValueChange: (value: T | null) => void
|
|
20
|
-
state?: InputState
|
|
21
|
-
error?: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Hook for managing SelectField state with built-in validation.
|
|
26
|
-
* Handles value, validation state, and error message automatically.
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```tsx
|
|
30
|
-
* // Simple usage without validation
|
|
31
|
-
* const selectProps = useSelectField()
|
|
32
|
-
* <SelectField {...selectProps} label="Country" />
|
|
33
|
-
*
|
|
34
|
-
* // With validation rule
|
|
35
|
-
* const selectProps = useSelectField({
|
|
36
|
-
* validationRule: {
|
|
37
|
-
* test: (v) => v !== null,
|
|
38
|
-
* message: "Please select an option"
|
|
39
|
-
* }
|
|
40
|
-
* })
|
|
41
|
-
* <SelectField {...selectProps} label="Category" />
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
export function useSelectField<T = string | Option>(
|
|
45
|
-
options: SelectOptions<T> = {}
|
|
46
|
-
): SelectReturn<T> {
|
|
47
|
-
const {
|
|
48
|
-
initialValue,
|
|
49
|
-
onValueChange: onValueChangeCallback,
|
|
50
|
-
validationRule
|
|
51
|
-
} = options
|
|
52
|
-
|
|
53
|
-
const [value, setValue] = useState<T | null>(initialValue ?? null)
|
|
54
|
-
|
|
55
|
-
const handleValueChange: SelectFieldProps<T>["onValueChange"] = (
|
|
56
|
-
newValue
|
|
57
|
-
) => {
|
|
58
|
-
const typedValue = Array.isArray(newValue) ? newValue[0] : newValue
|
|
59
|
-
setValue(typedValue ?? null)
|
|
60
|
-
onValueChangeCallback?.(typedValue as any)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const { state, error } = useMemo(() => {
|
|
64
|
-
if (!validationRule) {
|
|
65
|
-
return { state: undefined, error: undefined }
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (value === null || value === initialValue) {
|
|
69
|
-
return { state: "default" as InputState, error: undefined }
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const isValid = validationRule.test(value)
|
|
73
|
-
return {
|
|
74
|
-
state: (isValid ? "success" : "error") as InputState,
|
|
75
|
-
error: isValid ? undefined : validationRule.message
|
|
76
|
-
}
|
|
77
|
-
}, [value, validationRule, initialValue])
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
value,
|
|
81
|
-
onValueChange: handleValueChange,
|
|
82
|
-
...(validationRule && { state, error })
|
|
83
|
-
}
|
|
84
|
-
}
|
package/src/hooks/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { useDrawerKeyboardAvoidance } from "./useDrawerKeyboardAvoidance"
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react"
|
|
2
|
-
import { Keyboard, StyleProp, ViewStyle } from "react-native"
|
|
3
|
-
|
|
4
|
-
const useDrawerKeyboardAvoidance = (
|
|
5
|
-
minPadding: number = 16
|
|
6
|
-
): { contentContainerStyle: StyleProp<ViewStyle> } => {
|
|
7
|
-
const [keyboardHeight, setKeyboardHeight] = useState(0)
|
|
8
|
-
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
const showListener = Keyboard.addListener("keyboardDidShow", (e) => {
|
|
11
|
-
setKeyboardHeight(e.endCoordinates.height)
|
|
12
|
-
})
|
|
13
|
-
const hideListener = Keyboard.addListener("keyboardDidHide", () => {
|
|
14
|
-
setKeyboardHeight(0)
|
|
15
|
-
})
|
|
16
|
-
return () => {
|
|
17
|
-
showListener.remove()
|
|
18
|
-
hideListener.remove()
|
|
19
|
-
}
|
|
20
|
-
}, [])
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
contentContainerStyle: {
|
|
24
|
-
paddingBottom: Math.max(minPadding, keyboardHeight)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export { useDrawerKeyboardAvoidance }
|