@chem-po/react-native 0.0.52 → 0.0.53
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/package.json +5 -20
- package/src/components/box/Center.tsx +0 -19
- package/src/components/box/CollapseHorizontal.tsx +0 -44
- package/src/components/box/ContentBox.tsx +0 -24
- package/src/components/box/DropShadow.tsx +0 -28
- package/src/components/box/ExpandOnMount.tsx +0 -74
- package/src/components/box/Expandable.tsx +0 -143
- package/src/components/box/FullSizeContainer.tsx +0 -64
- package/src/components/box/index.ts +0 -7
- package/src/components/button/ActionButton.tsx +0 -196
- package/src/components/button/ButtonText.tsx +0 -60
- package/src/components/button/DeleteButton.tsx +0 -288
- package/src/components/button/LoadingButton.tsx +0 -41
- package/src/components/button/Toggle.tsx +0 -109
- package/src/components/button/hooks.ts +0 -66
- package/src/components/button/index.ts +0 -5
- package/src/components/feed/FeedContentPane.tsx +0 -97
- package/src/components/feed/MediaFeed.tsx +0 -199
- package/src/components/feed/MediaFeedBackground.tsx +0 -136
- package/src/components/feed/MediaFeedRefresh.tsx +0 -113
- package/src/components/feed/constants.ts +0 -2
- package/src/components/feed/context.tsx +0 -19
- package/src/components/feed/hooks.ts +0 -279
- package/src/components/feed/index.ts +0 -2
- package/src/components/form/Condition.tsx +0 -27
- package/src/components/form/Field.tsx +0 -44
- package/src/components/form/Form.tsx +0 -452
- package/src/components/form/FormFooter.tsx +0 -164
- package/src/components/form/UploadProgress/index.tsx +0 -50
- package/src/components/form/index.ts +0 -3
- package/src/components/form/input/Editable.tsx +0 -206
- package/src/components/form/input/InputSlider.tsx +0 -71
- package/src/components/form/input/OptionalTag.tsx +0 -43
- package/src/components/form/input/StandaloneInput.tsx +0 -49
- package/src/components/form/input/boolean/index.tsx +0 -53
- package/src/components/form/input/color/index.tsx +0 -145
- package/src/components/form/input/common/InputClearButton.tsx +0 -57
- package/src/components/form/input/date/index.tsx +0 -125
- package/src/components/form/input/datetime/index.tsx +0 -176
- package/src/components/form/input/file/index.tsx +0 -310
- package/src/components/form/input/hooks/index.ts +0 -2
- package/src/components/form/input/hooks/useInputColor.ts +0 -7
- package/src/components/form/input/hooks/useInputImperativeHandle.ts +0 -22
- package/src/components/form/input/hooks/useInputStyles.ts +0 -114
- package/src/components/form/input/index.ts +0 -4
- package/src/components/form/input/input.tsx +0 -218
- package/src/components/form/input/multipleSelect/index.tsx +0 -221
- package/src/components/form/input/number/index.tsx +0 -108
- package/src/components/form/input/select/index.tsx +0 -152
- package/src/components/form/input/socialMedia/index.tsx +0 -235
- package/src/components/form/input/text/AutoResizeTextarea.tsx +0 -41
- package/src/components/form/input/text/index.tsx +0 -99
- package/src/components/form/input/text/textarea.tsx +0 -32
- package/src/components/form/input/text/useWebAutoResize.tsx +0 -73
- package/src/components/form/input/time/index.tsx +0 -125
- package/src/components/form/types.ts +0 -8
- package/src/components/form/view/file.tsx +0 -80
- package/src/components/form/view/index.tsx +0 -125
- package/src/components/form/view/multipleSelect.tsx +0 -85
- package/src/components/form/view/select.tsx +0 -83
- package/src/components/form/view/styles.ts +0 -12
- package/src/components/icons/index.tsx +0 -28
- package/src/components/image/ImageViewModal.tsx +0 -319
- package/src/components/image/index.ts +0 -1
- package/src/components/index.ts +0 -8
- package/src/components/layout/CollapseHorizontal.tsx +0 -92
- package/src/components/loading/CircularProgress.tsx +0 -56
- package/src/components/loading/Loading.tsx +0 -146
- package/src/components/loading/LoadingImage.tsx +0 -163
- package/src/components/loading/LoadingOverlay.tsx +0 -74
- package/src/components/loading/LoadingSwitch.tsx +0 -110
- package/src/components/loading/ProgressBar.tsx +0 -75
- package/src/components/loading/index.ts +0 -6
- package/src/components/text/AnimatedText.tsx +0 -68
- package/src/components/text/Txt.tsx +0 -12
- package/src/components/text/index.ts +0 -1
- package/src/components/theme/colorMode/DarkModeToggle.tsx +0 -47
- package/src/components/theme/colorMode/index.ts +0 -1
- package/src/components/theme/index.ts +0 -1
- package/src/constants/index.ts +0 -1
- package/src/constants/toast.ts +0 -24
- package/src/contexts/fonts.tsx +0 -23
- package/src/contexts/index.ts +0 -1
- package/src/contexts/root.tsx +0 -190
- package/src/hooks/index.ts +0 -3
- package/src/hooks/useFadeIn.ts +0 -48
- package/src/hooks/useFont.ts +0 -25
- package/src/hooks/useRefreshFontScale.ts +0 -39
- package/src/hooks/useThemeState.ts +0 -43
- package/src/index.ts +0 -6
- package/src/store/index.ts +0 -2
- package/src/store/useFontScale.ts +0 -8
- package/src/store/useScreen.ts +0 -25
- package/src/styles/fill.ts +0 -19
- package/src/types/forms.ts +0 -14
- package/src/types/index.ts +0 -1
- package/src/utils/downloadFile.ts +0 -61
- package/src/utils/downloadFileLegacy.ts +0 -66
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { getDateString, InputRef } from '@chem-po/core'
|
|
2
|
-
import { DateField, useIconColor } from '@chem-po/react'
|
|
3
|
-
import { Ionicons } from '@expo/vector-icons'
|
|
4
|
-
import React, { forwardRef, useImperativeHandle, useMemo } from 'react'
|
|
5
|
-
import { StyleSheet, Text, View } from 'react-native'
|
|
6
|
-
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
|
|
7
|
-
import { Portal } from 'react-native-paper'
|
|
8
|
-
import { DatePickerModal } from 'react-native-paper-dates'
|
|
9
|
-
import { scheduleOnRN } from 'react-native-worklets'
|
|
10
|
-
import { FieldProps } from '../../types'
|
|
11
|
-
import { DateInputClearButton } from '../common/InputClearButton'
|
|
12
|
-
import { useInputColor } from '../hooks/useInputColor'
|
|
13
|
-
import { useInputStyles } from '../hooks/useInputStyles'
|
|
14
|
-
|
|
15
|
-
// const parseDate = (date?: Date) =>
|
|
16
|
-
// date
|
|
17
|
-
// ? `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(
|
|
18
|
-
// date.getDate(),
|
|
19
|
-
// ).padStart(2, '0')}`
|
|
20
|
-
// : undefined
|
|
21
|
-
|
|
22
|
-
export const DateInput = forwardRef<InputRef, FieldProps<DateField>>(
|
|
23
|
-
(
|
|
24
|
-
{
|
|
25
|
-
input: { onChange, value, onFocus, onBlur },
|
|
26
|
-
field: { placeholder, minDate, maxDate, optional, size },
|
|
27
|
-
meta: { active },
|
|
28
|
-
formSize,
|
|
29
|
-
inEditable,
|
|
30
|
-
},
|
|
31
|
-
ref,
|
|
32
|
-
) => {
|
|
33
|
-
const date = useMemo(() => (value ? new Date(`${value}T00:00:00.000`) : undefined), [value])
|
|
34
|
-
const {
|
|
35
|
-
container: inputStyles,
|
|
36
|
-
iconSize,
|
|
37
|
-
text,
|
|
38
|
-
clearButtonSize,
|
|
39
|
-
buttonContainer,
|
|
40
|
-
} = useInputStyles(inEditable, size, formSize)
|
|
41
|
-
|
|
42
|
-
useImperativeHandle(ref, () => ({
|
|
43
|
-
focus: () => {
|
|
44
|
-
onFocus()
|
|
45
|
-
},
|
|
46
|
-
blur: () => {
|
|
47
|
-
onBlur()
|
|
48
|
-
},
|
|
49
|
-
}))
|
|
50
|
-
|
|
51
|
-
const iconColor = useIconColor()
|
|
52
|
-
const inputColor = useInputColor(value)
|
|
53
|
-
|
|
54
|
-
const minDateObj = minDate === 'now' ? new Date() : minDate ? new Date(minDate) : undefined
|
|
55
|
-
const maxDateObj = maxDate === 'now' ? new Date() : maxDate ? new Date(maxDate) : undefined
|
|
56
|
-
|
|
57
|
-
const mainTap = Gesture.Tap().onStart(() => {
|
|
58
|
-
scheduleOnRN(onFocus)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<View style={styles.container}>
|
|
63
|
-
<View style={[styles.button, inputStyles]}>
|
|
64
|
-
<GestureDetector gesture={mainTap}>
|
|
65
|
-
<View style={styles.textContainer}>
|
|
66
|
-
<Text numberOfLines={1} style={[styles.text, text, { color: inputColor }]}>
|
|
67
|
-
{value ? getDateString(value, 'short') : placeholder}
|
|
68
|
-
</Text>
|
|
69
|
-
<Ionicons name="calendar" size={iconSize} color={iconColor} />
|
|
70
|
-
</View>
|
|
71
|
-
</GestureDetector>
|
|
72
|
-
<View style={buttonContainer}>
|
|
73
|
-
{optional && value ? (
|
|
74
|
-
<DateInputClearButton
|
|
75
|
-
size={clearButtonSize}
|
|
76
|
-
onPress={() => onChange(null)}
|
|
77
|
-
isActive={!!value}
|
|
78
|
-
/>
|
|
79
|
-
) : null}
|
|
80
|
-
</View>
|
|
81
|
-
</View>
|
|
82
|
-
<Portal>
|
|
83
|
-
<DatePickerModal
|
|
84
|
-
locale="en"
|
|
85
|
-
label="Date"
|
|
86
|
-
date={date}
|
|
87
|
-
startYear={minDateObj?.getFullYear()}
|
|
88
|
-
endYear={maxDateObj?.getFullYear()}
|
|
89
|
-
mode="single"
|
|
90
|
-
onConfirm={d => {
|
|
91
|
-
onChange(d?.date?.toISOString()?.split('T')[0] ?? null)
|
|
92
|
-
onBlur()
|
|
93
|
-
}}
|
|
94
|
-
onDismiss={onBlur}
|
|
95
|
-
visible={active}
|
|
96
|
-
/>
|
|
97
|
-
</Portal>
|
|
98
|
-
</View>
|
|
99
|
-
)
|
|
100
|
-
},
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
DateInput.displayName = 'DateInput'
|
|
104
|
-
|
|
105
|
-
const styles = StyleSheet.create({
|
|
106
|
-
container: {
|
|
107
|
-
width: '100%',
|
|
108
|
-
},
|
|
109
|
-
button: {
|
|
110
|
-
flexDirection: 'row',
|
|
111
|
-
alignItems: 'center',
|
|
112
|
-
justifyContent: 'space-between',
|
|
113
|
-
gap: 8,
|
|
114
|
-
width: '100%',
|
|
115
|
-
},
|
|
116
|
-
textContainer: {
|
|
117
|
-
flex: 1,
|
|
118
|
-
flexDirection: 'row',
|
|
119
|
-
alignItems: 'center',
|
|
120
|
-
gap: 8,
|
|
121
|
-
},
|
|
122
|
-
text: {
|
|
123
|
-
flex: 1,
|
|
124
|
-
},
|
|
125
|
-
})
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { getDateTimeString, InputRef } from '@chem-po/core'
|
|
2
|
-
import { DateTimeField, useIconColor } from '@chem-po/react'
|
|
3
|
-
import { Ionicons } from '@expo/vector-icons'
|
|
4
|
-
import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useState } from 'react'
|
|
5
|
-
import { StyleSheet, Text, View } from 'react-native'
|
|
6
|
-
import { Gesture, GestureDetector, Pressable } from 'react-native-gesture-handler'
|
|
7
|
-
import { Portal } from 'react-native-paper'
|
|
8
|
-
import { DatePickerModal, TimePickerModal } from 'react-native-paper-dates'
|
|
9
|
-
import { scheduleOnRN } from 'react-native-worklets'
|
|
10
|
-
import { FieldProps } from '../../types'
|
|
11
|
-
import { DateInputClearButton } from '../common/InputClearButton'
|
|
12
|
-
import { useInputColor } from '../hooks/useInputColor'
|
|
13
|
-
import { useInputStyles } from '../hooks/useInputStyles'
|
|
14
|
-
|
|
15
|
-
export const DateTimeInput = forwardRef<InputRef, FieldProps<DateTimeField>>(
|
|
16
|
-
(
|
|
17
|
-
{ input: { value, onBlur, onChange, onFocus }, field, meta: { active }, formSize, inEditable },
|
|
18
|
-
ref,
|
|
19
|
-
) => {
|
|
20
|
-
const { placeholder, optional } = field
|
|
21
|
-
const [showDatePicker, setShowDatePicker] = useState(true)
|
|
22
|
-
const [showTimePicker, setShowTimePicker] = useState(false)
|
|
23
|
-
const {
|
|
24
|
-
container: inputStyles,
|
|
25
|
-
iconSize,
|
|
26
|
-
text,
|
|
27
|
-
clearButtonSize,
|
|
28
|
-
buttonContainer,
|
|
29
|
-
} = useInputStyles(inEditable, field.size, formSize)
|
|
30
|
-
|
|
31
|
-
const selectedDate = useMemo(() => (value ? new Date(value) : new Date()), [value])
|
|
32
|
-
useImperativeHandle(ref, () => ({
|
|
33
|
-
focus: () => {
|
|
34
|
-
onFocus()
|
|
35
|
-
},
|
|
36
|
-
blur: () => {
|
|
37
|
-
onBlur()
|
|
38
|
-
},
|
|
39
|
-
}))
|
|
40
|
-
|
|
41
|
-
const handleDateChange = useCallback(
|
|
42
|
-
(date: string | null) => {
|
|
43
|
-
if (date) {
|
|
44
|
-
const newDate = new Date(date)
|
|
45
|
-
onChange(newDate.toISOString())
|
|
46
|
-
setShowTimePicker(true)
|
|
47
|
-
} else {
|
|
48
|
-
onBlur()
|
|
49
|
-
}
|
|
50
|
-
setShowDatePicker(false)
|
|
51
|
-
},
|
|
52
|
-
[onChange, onBlur],
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
const handleTimeChange = useCallback(
|
|
56
|
-
({ hours, minutes }: { hours: number; minutes: number }) => {
|
|
57
|
-
const newDate = selectedDate ? new Date(selectedDate) : new Date()
|
|
58
|
-
newDate.setHours(hours)
|
|
59
|
-
newDate.setMinutes(minutes)
|
|
60
|
-
onChange(newDate.toISOString())
|
|
61
|
-
setShowTimePicker(false)
|
|
62
|
-
onBlur()
|
|
63
|
-
},
|
|
64
|
-
[onChange, onBlur, selectedDate],
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
const handleDismiss = useCallback(() => {
|
|
68
|
-
setShowDatePicker(false)
|
|
69
|
-
setShowTimePicker(false)
|
|
70
|
-
onBlur()
|
|
71
|
-
}, [onBlur])
|
|
72
|
-
|
|
73
|
-
const handleFocusTime = useCallback(() => {
|
|
74
|
-
setShowTimePicker(true)
|
|
75
|
-
onFocus()
|
|
76
|
-
}, [onFocus])
|
|
77
|
-
|
|
78
|
-
const handleFocusDate = useCallback(() => {
|
|
79
|
-
setShowDatePicker(true)
|
|
80
|
-
onFocus()
|
|
81
|
-
}, [onFocus])
|
|
82
|
-
|
|
83
|
-
const date = useMemo(() => (value ? new Date(value) : undefined), [value])
|
|
84
|
-
|
|
85
|
-
const formattedValue = useMemo(() => {
|
|
86
|
-
if (!value) return null
|
|
87
|
-
return getDateTimeString(value)
|
|
88
|
-
}, [value])
|
|
89
|
-
|
|
90
|
-
const hourMinute = useMemo(() => {
|
|
91
|
-
if (!date) return { hours: 0, minutes: 0 }
|
|
92
|
-
return { hours: date.getHours(), minutes: date.getMinutes() }
|
|
93
|
-
}, [date])
|
|
94
|
-
|
|
95
|
-
const iconColor = useIconColor()
|
|
96
|
-
const inputColor = useInputColor(value)
|
|
97
|
-
|
|
98
|
-
const mainTap = Gesture.Tap().onStart(() => {
|
|
99
|
-
scheduleOnRN(handleFocusDate)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<View style={styles.container}>
|
|
104
|
-
<View style={[styles.button, inputStyles]}>
|
|
105
|
-
<GestureDetector gesture={mainTap}>
|
|
106
|
-
<View style={styles.textContainer}>
|
|
107
|
-
<Text numberOfLines={1} style={[styles.text, text, { color: inputColor }]}>
|
|
108
|
-
{formattedValue ?? placeholder}
|
|
109
|
-
</Text>
|
|
110
|
-
</View>
|
|
111
|
-
</GestureDetector>
|
|
112
|
-
<View style={buttonContainer}>
|
|
113
|
-
<Pressable onPress={handleFocusDate}>
|
|
114
|
-
<Ionicons name="calendar" size={iconSize} color={iconColor} />
|
|
115
|
-
</Pressable>
|
|
116
|
-
<Pressable onPress={handleFocusTime}>
|
|
117
|
-
<Ionicons name="time" size={iconSize} color={iconColor} />
|
|
118
|
-
</Pressable>
|
|
119
|
-
{optional && value ? (
|
|
120
|
-
<DateInputClearButton
|
|
121
|
-
size={clearButtonSize}
|
|
122
|
-
onPress={() => onChange(null)}
|
|
123
|
-
isActive={!!value}
|
|
124
|
-
/>
|
|
125
|
-
) : null}
|
|
126
|
-
</View>
|
|
127
|
-
</View>
|
|
128
|
-
<Portal>
|
|
129
|
-
{showDatePicker && (
|
|
130
|
-
<DatePickerModal
|
|
131
|
-
locale="en"
|
|
132
|
-
label="Date"
|
|
133
|
-
date={date}
|
|
134
|
-
mode="single"
|
|
135
|
-
onConfirm={d => {
|
|
136
|
-
handleDateChange(d?.date?.toISOString()?.split('T')[0] ?? null)
|
|
137
|
-
}}
|
|
138
|
-
onDismiss={handleDismiss}
|
|
139
|
-
visible={active && showDatePicker}
|
|
140
|
-
/>
|
|
141
|
-
)}
|
|
142
|
-
{showTimePicker && (
|
|
143
|
-
<TimePickerModal
|
|
144
|
-
visible={active && showTimePicker}
|
|
145
|
-
onDismiss={handleDismiss}
|
|
146
|
-
locale="en-US"
|
|
147
|
-
onConfirm={handleTimeChange}
|
|
148
|
-
hours={hourMinute.hours}
|
|
149
|
-
minutes={hourMinute.minutes}
|
|
150
|
-
/>
|
|
151
|
-
)}
|
|
152
|
-
</Portal>
|
|
153
|
-
</View>
|
|
154
|
-
)
|
|
155
|
-
},
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
DateTimeInput.displayName = 'DateTimeInput'
|
|
159
|
-
|
|
160
|
-
const styles = StyleSheet.create({
|
|
161
|
-
container: {
|
|
162
|
-
width: '100%',
|
|
163
|
-
},
|
|
164
|
-
button: {
|
|
165
|
-
flexDirection: 'row',
|
|
166
|
-
alignItems: 'center',
|
|
167
|
-
justifyContent: 'space-between',
|
|
168
|
-
width: '100%',
|
|
169
|
-
},
|
|
170
|
-
textContainer: {
|
|
171
|
-
flex: 1,
|
|
172
|
-
},
|
|
173
|
-
text: {
|
|
174
|
-
width: '100%',
|
|
175
|
-
},
|
|
176
|
-
})
|
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
import { FileValue, ImageViewOptions, InputRef, LocalFileValue } from '@chem-po/core'
|
|
2
|
-
import {
|
|
3
|
-
FileField,
|
|
4
|
-
useBackgroundColor,
|
|
5
|
-
useBorderColor,
|
|
6
|
-
useIconColor,
|
|
7
|
-
useObjectUrl,
|
|
8
|
-
usePlaceholderColor,
|
|
9
|
-
useTextColor,
|
|
10
|
-
useToast,
|
|
11
|
-
} from '@chem-po/react'
|
|
12
|
-
import { Ionicons } from '@expo/vector-icons'
|
|
13
|
-
import * as DocumentPicker from 'expo-document-picker'
|
|
14
|
-
import * as ImagePicker from 'expo-image-picker'
|
|
15
|
-
import React, { forwardRef, useCallback, useImperativeHandle, useMemo } from 'react'
|
|
16
|
-
import { Platform, StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle } from 'react-native'
|
|
17
|
-
import { Pressable } from 'react-native-gesture-handler'
|
|
18
|
-
import { useFontScale } from '../../../../store/useFontScale'
|
|
19
|
-
import { downloadFile } from '../../../../utils/downloadFile'
|
|
20
|
-
import { LoadingImage } from '../../../loading/LoadingImage'
|
|
21
|
-
import { FieldProps } from '../../types'
|
|
22
|
-
|
|
23
|
-
const fallbackAccept =
|
|
24
|
-
'image/jpg,image/jpeg,image/png,image/svg,image/gif,application/pdf,audio/mp3,audio/wav,audio/x-wav,audio/webm,audio/ogg'
|
|
25
|
-
const generateAccept = (field: FileField) => {
|
|
26
|
-
if (!field.accept) return fallbackAccept
|
|
27
|
-
const accept: string[] = []
|
|
28
|
-
if (field.accept.includes('image')) {
|
|
29
|
-
accept.push('image/jpg', 'image/jpeg', 'image/png', 'image/svg', 'image/gif')
|
|
30
|
-
}
|
|
31
|
-
if (field.accept.includes('pdf')) accept.push('application/pdf')
|
|
32
|
-
if (field.accept.includes('audio')) {
|
|
33
|
-
accept.push('audio/mp3', 'audio/wav', 'audio/x-wav', 'audio/webm', 'audio/ogg')
|
|
34
|
-
}
|
|
35
|
-
return accept.join(',')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const NoFileView = ({ hasUpload, onPress }: { hasUpload?: boolean; onPress?: () => void }) => {
|
|
39
|
-
const textColor = useTextColor()
|
|
40
|
-
const iconColor = useIconColor()
|
|
41
|
-
const borderColor = useBorderColor()
|
|
42
|
-
const fontScale = useFontScale(s => s.fontScale)
|
|
43
|
-
return (
|
|
44
|
-
<Pressable style={[styles.noFileContainer, { borderColor }]} onPress={onPress}>
|
|
45
|
-
<Text style={[styles.noFileText, { color: textColor }]}>
|
|
46
|
-
{hasUpload ? 'Tap to upload file' : 'No file uploaded'}
|
|
47
|
-
</Text>
|
|
48
|
-
<Ionicons name="cloud-upload" size={24 * fontScale} color={iconColor} />
|
|
49
|
-
</Pressable>
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export const FileView = ({
|
|
54
|
-
value,
|
|
55
|
-
hasUpload,
|
|
56
|
-
imageOptions,
|
|
57
|
-
withFullView,
|
|
58
|
-
nonImageContainerStyle,
|
|
59
|
-
withDownload,
|
|
60
|
-
onUploadPress,
|
|
61
|
-
textStyle,
|
|
62
|
-
}: {
|
|
63
|
-
value?: FileValue | null
|
|
64
|
-
hasUpload?: boolean
|
|
65
|
-
imageOptions?: ImageViewOptions
|
|
66
|
-
withFullView?: boolean
|
|
67
|
-
nonImageContainerStyle?: StyleProp<ViewStyle>
|
|
68
|
-
withDownload?: boolean
|
|
69
|
-
onUploadPress?: () => void
|
|
70
|
-
textStyle?: StyleProp<TextStyle>
|
|
71
|
-
}) => {
|
|
72
|
-
const { storagePath, dataUrl } = value ?? {}
|
|
73
|
-
const missingFile = !dataUrl && !storagePath
|
|
74
|
-
|
|
75
|
-
const { url, loading } = useObjectUrl(value)
|
|
76
|
-
const { showError, showSuccess } = useToast()
|
|
77
|
-
|
|
78
|
-
const iconColor = useIconColor()
|
|
79
|
-
const fileNameColor = useTextColor()
|
|
80
|
-
const borderColor = useBorderColor()
|
|
81
|
-
const backgroundColor = useBackgroundColor(100)
|
|
82
|
-
|
|
83
|
-
const handleDownload = useCallback(async () => {
|
|
84
|
-
if (!url) {
|
|
85
|
-
showError('File URL is not available')
|
|
86
|
-
return
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const filename = value?.filename?.split('/').pop()?.replace(/\s+/g, '_')
|
|
90
|
-
if (!filename) {
|
|
91
|
-
showError('Filename is not available')
|
|
92
|
-
return
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const fileType = value?.type
|
|
96
|
-
if (!fileType) {
|
|
97
|
-
showError('File type is not available')
|
|
98
|
-
return
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
await downloadFile(url, filename, fileType)
|
|
103
|
-
if (Platform.OS === 'android') {
|
|
104
|
-
showSuccess('File downloaded successfully')
|
|
105
|
-
return
|
|
106
|
-
}
|
|
107
|
-
} catch (error: unknown) {
|
|
108
|
-
if (error instanceof Error) {
|
|
109
|
-
console.error('Error downloading file:', error.message)
|
|
110
|
-
} else {
|
|
111
|
-
console.error('Error downloading file:', String(error))
|
|
112
|
-
}
|
|
113
|
-
showError('Failed to download file')
|
|
114
|
-
}
|
|
115
|
-
}, [url, value, showError, showSuccess])
|
|
116
|
-
if (!value || missingFile) {
|
|
117
|
-
return <NoFileView onPress={onUploadPress} hasUpload={hasUpload} />
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const body = value.type?.startsWith('image/') ? (
|
|
121
|
-
<LoadingImage
|
|
122
|
-
withDownload={withDownload}
|
|
123
|
-
filename={value.filename}
|
|
124
|
-
src={url}
|
|
125
|
-
fileType={value.type}
|
|
126
|
-
loadingOverride={loading}
|
|
127
|
-
width={imageOptions?.width ?? 120}
|
|
128
|
-
height={imageOptions?.height ?? 120}
|
|
129
|
-
withFullView={withFullView}
|
|
130
|
-
style={styles.image}
|
|
131
|
-
/>
|
|
132
|
-
) : (
|
|
133
|
-
<View style={[styles.fileContainer, { borderColor, backgroundColor }, nonImageContainerStyle]}>
|
|
134
|
-
<View style={styles.iconContainer}>
|
|
135
|
-
<Ionicons name="document" size={24} color={iconColor} />
|
|
136
|
-
</View>
|
|
137
|
-
<View style={styles.filenameContainer}>
|
|
138
|
-
<Text numberOfLines={1} style={[styles.filename, { color: fileNameColor }, textStyle]}>
|
|
139
|
-
{value.filename}
|
|
140
|
-
</Text>
|
|
141
|
-
</View>
|
|
142
|
-
{withDownload && (
|
|
143
|
-
<Pressable
|
|
144
|
-
style={styles.downloadButton}
|
|
145
|
-
onPress={() => void handleDownload()}
|
|
146
|
-
disabled={loading}>
|
|
147
|
-
<Ionicons name="download" size={20} color={iconColor} />
|
|
148
|
-
</Pressable>
|
|
149
|
-
)}
|
|
150
|
-
</View>
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
return onUploadPress ? <Pressable onPress={onUploadPress}>{body}</Pressable> : body
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export const FileComponent = forwardRef<InputRef, FieldProps<FileField>>(
|
|
157
|
-
({ input: { value, onChange }, field }, ref) => {
|
|
158
|
-
const { imageOptions } = field || {}
|
|
159
|
-
|
|
160
|
-
const handlePickFile = useCallback(async () => {
|
|
161
|
-
try {
|
|
162
|
-
let result: ImagePicker.ImagePickerResult | DocumentPicker.DocumentPickerResult
|
|
163
|
-
|
|
164
|
-
if (field.accept?.includes('image')) {
|
|
165
|
-
result = await ImagePicker.launchImageLibraryAsync({
|
|
166
|
-
mediaTypes: ['images'],
|
|
167
|
-
allowsEditing: true,
|
|
168
|
-
quality: 1,
|
|
169
|
-
base64: true,
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
if (!result.canceled && result.assets[0]) {
|
|
173
|
-
const asset = result.assets[0]
|
|
174
|
-
|
|
175
|
-
const fileExtension = asset.uri.split('.').pop() ?? 'jpeg'
|
|
176
|
-
const fileName = asset.uri.split('/').pop() ?? 'image'
|
|
177
|
-
|
|
178
|
-
onChange({
|
|
179
|
-
dataUrl: asset.uri,
|
|
180
|
-
type: asset.mimeType ?? `image/${fileExtension}`,
|
|
181
|
-
filename: fileName,
|
|
182
|
-
} as LocalFileValue)
|
|
183
|
-
}
|
|
184
|
-
} else {
|
|
185
|
-
result = await DocumentPicker.getDocumentAsync({
|
|
186
|
-
type: generateAccept(field),
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
if (!result.canceled) {
|
|
190
|
-
onChange({
|
|
191
|
-
...value,
|
|
192
|
-
dataUrl: result.assets[0].uri,
|
|
193
|
-
type: result.assets[0].mimeType,
|
|
194
|
-
filename: result.assets[0].name,
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
} catch (error: unknown) {
|
|
199
|
-
if (error instanceof Error) {
|
|
200
|
-
console.error('Error picking file:', error.message)
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}, [field, onChange, value])
|
|
204
|
-
|
|
205
|
-
useImperativeHandle(ref, () => ({
|
|
206
|
-
focus: () => {
|
|
207
|
-
void handlePickFile()
|
|
208
|
-
},
|
|
209
|
-
blur: () => {},
|
|
210
|
-
}))
|
|
211
|
-
|
|
212
|
-
const isImageField = useMemo(
|
|
213
|
-
() => field.accept?.length === 1 && field.accept[0] === 'image',
|
|
214
|
-
[field],
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
const imgOptions = useMemo(
|
|
218
|
-
() =>
|
|
219
|
-
isImageField || value?.type?.startsWith('image/')
|
|
220
|
-
? {
|
|
221
|
-
height: 120,
|
|
222
|
-
...imageOptions,
|
|
223
|
-
}
|
|
224
|
-
: undefined,
|
|
225
|
-
[imageOptions, isImageField, value],
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
const placeholderColor = usePlaceholderColor()
|
|
229
|
-
const borderColor = useBorderColor()
|
|
230
|
-
|
|
231
|
-
return (
|
|
232
|
-
<View style={[styles.container, { borderColor }]}>
|
|
233
|
-
<Text style={[styles.placeholder, { color: placeholderColor }]}>{field.placeholder}</Text>
|
|
234
|
-
<View style={styles.contentContainer}>
|
|
235
|
-
<FileView
|
|
236
|
-
hasUpload
|
|
237
|
-
onUploadPress={() => void handlePickFile()}
|
|
238
|
-
imageOptions={imgOptions}
|
|
239
|
-
value={value}
|
|
240
|
-
/>
|
|
241
|
-
</View>
|
|
242
|
-
</View>
|
|
243
|
-
)
|
|
244
|
-
},
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
FileComponent.displayName = 'FileComponent'
|
|
248
|
-
|
|
249
|
-
const styles = StyleSheet.create({
|
|
250
|
-
container: {
|
|
251
|
-
width: '100%',
|
|
252
|
-
alignItems: 'center',
|
|
253
|
-
justifyContent: 'center',
|
|
254
|
-
},
|
|
255
|
-
placeholder: {
|
|
256
|
-
fontSize: 14,
|
|
257
|
-
opacity: 0.8,
|
|
258
|
-
marginBottom: 4,
|
|
259
|
-
},
|
|
260
|
-
contentContainer: {
|
|
261
|
-
width: '100%',
|
|
262
|
-
position: 'relative',
|
|
263
|
-
alignItems: 'center',
|
|
264
|
-
justifyContent: 'center',
|
|
265
|
-
padding: 8,
|
|
266
|
-
overflow: 'hidden',
|
|
267
|
-
},
|
|
268
|
-
noFileContainer: {
|
|
269
|
-
flexDirection: 'row',
|
|
270
|
-
alignItems: 'center',
|
|
271
|
-
justifyContent: 'center',
|
|
272
|
-
padding: 16,
|
|
273
|
-
borderWidth: 1,
|
|
274
|
-
borderRadius: 4,
|
|
275
|
-
borderStyle: 'dashed',
|
|
276
|
-
},
|
|
277
|
-
noFileText: {
|
|
278
|
-
fontSize: 14,
|
|
279
|
-
textAlign: 'center',
|
|
280
|
-
opacity: 0.8,
|
|
281
|
-
marginRight: 10,
|
|
282
|
-
},
|
|
283
|
-
fileContainer: {
|
|
284
|
-
flexDirection: 'row',
|
|
285
|
-
alignItems: 'center',
|
|
286
|
-
borderWidth: 1,
|
|
287
|
-
borderRadius: 4,
|
|
288
|
-
maxWidth: '100%',
|
|
289
|
-
overflow: 'hidden',
|
|
290
|
-
},
|
|
291
|
-
filenameContainer: {
|
|
292
|
-
flex: 1,
|
|
293
|
-
paddingRight: 4,
|
|
294
|
-
justifyContent: 'center',
|
|
295
|
-
},
|
|
296
|
-
filename: {
|
|
297
|
-
fontSize: 14,
|
|
298
|
-
},
|
|
299
|
-
iconContainer: {
|
|
300
|
-
padding: 4,
|
|
301
|
-
},
|
|
302
|
-
downloadButton: {
|
|
303
|
-
padding: 8,
|
|
304
|
-
marginLeft: 4,
|
|
305
|
-
},
|
|
306
|
-
image: {
|
|
307
|
-
borderRadius: 4,
|
|
308
|
-
overflow: 'hidden',
|
|
309
|
-
},
|
|
310
|
-
})
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { usePlaceholderColor, useTextColor } from '@chem-po/react'
|
|
2
|
-
|
|
3
|
-
export const useInputColor = (value?: any) => {
|
|
4
|
-
const textColor = useTextColor()
|
|
5
|
-
const placeholderColor = usePlaceholderColor()
|
|
6
|
-
return value !== undefined && value !== null && value !== '' ? textColor : placeholderColor
|
|
7
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { InputRef } from '@chem-po/core'
|
|
2
|
-
import { ForwardedRef, useImperativeHandle, useRef } from 'react'
|
|
3
|
-
import { TextInput } from 'react-native'
|
|
4
|
-
|
|
5
|
-
export const useInputImperativeHandle = (ref: ForwardedRef<InputRef>) => {
|
|
6
|
-
const inputRef = useRef<TextInput>(null)
|
|
7
|
-
|
|
8
|
-
useImperativeHandle<InputRef, InputRef>(
|
|
9
|
-
ref,
|
|
10
|
-
() => ({
|
|
11
|
-
focus: () => {
|
|
12
|
-
inputRef.current?.focus()
|
|
13
|
-
},
|
|
14
|
-
blur: () => {
|
|
15
|
-
inputRef.current?.blur()
|
|
16
|
-
},
|
|
17
|
-
}),
|
|
18
|
-
[],
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
return inputRef
|
|
22
|
-
}
|