@originallyus/feedback-rn-sdk 3.1.0 → 4.0.0-beta.1
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.
Potentially problematic release.
This version of @originallyus/feedback-rn-sdk might be problematic. Click here for more details.
- package/{LICENSE.txt → LICENSE} +1 -2
- package/README.md +69 -176
- package/lib/module/AIAContentUsefulness.js +273 -0
- package/lib/module/AIAContentUsefulness.js.map +1 -0
- package/lib/module/AIAFeedback.js +295 -0
- package/lib/module/AIAFeedback.js.map +1 -0
- package/lib/module/AIAFeedbackForm.js +212 -0
- package/lib/module/AIAFeedbackForm.js.map +1 -0
- package/lib/module/AIAFeedbackSplash.js +57 -0
- package/lib/module/AIAFeedbackSplash.js.map +1 -0
- package/lib/module/AIAFeedbackStyles.js +329 -0
- package/lib/module/AIAFeedbackStyles.js.map +1 -0
- package/lib/module/AIAFeedbackSuccess.js +68 -0
- package/lib/module/AIAFeedbackSuccess.js.map +1 -0
- package/lib/module/assets/CheckIcon.js +21 -0
- package/lib/module/assets/CheckIcon.js.map +1 -0
- package/lib/module/assets/CloseIcon.js +21 -0
- package/lib/module/assets/CloseIcon.js.map +1 -0
- package/lib/module/assets/ErrorIcon.js +23 -0
- package/lib/module/assets/ErrorIcon.js.map +1 -0
- package/lib/module/assets/PlusIcon.js +21 -0
- package/lib/module/assets/PlusIcon.js.map +1 -0
- package/lib/module/assets/StarIcon.js +21 -0
- package/lib/module/assets/StarIcon.js.map +1 -0
- package/lib/module/component/Button.js +49 -0
- package/lib/module/component/Button.js.map +1 -0
- package/lib/module/component/ButtonSubmit.js +194 -0
- package/lib/module/component/ButtonSubmit.js.map +1 -0
- package/lib/module/component/Input.js +172 -0
- package/lib/module/component/Input.js.map +1 -0
- package/lib/module/component/MultiSelectButtons.js +174 -0
- package/lib/module/component/MultiSelectButtons.js.map +1 -0
- package/lib/module/component/README.md +215 -0
- package/lib/module/component/READMEVI.md +192 -0
- package/lib/module/component/Rating.js +168 -0
- package/lib/module/component/Rating.js.map +1 -0
- package/lib/module/component/RatingNumber.js +268 -0
- package/lib/module/component/RatingNumber.js.map +1 -0
- package/lib/module/component/Textarea.js +163 -0
- package/lib/module/component/Textarea.js.map +1 -0
- package/lib/module/component/YesNoButtons.js +161 -0
- package/lib/module/component/YesNoButtons.js.map +1 -0
- package/lib/module/index.js +13 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/service/feedbackService.js +77 -0
- package/lib/module/service/feedbackService.js.map +1 -0
- package/lib/module/utils/common.js +199 -0
- package/lib/module/utils/common.js.map +1 -0
- package/lib/module/utils/constants.js +48 -0
- package/lib/module/utils/constants.js.map +1 -0
- package/lib/module/utils/index.js +140 -0
- package/lib/module/utils/index.js.map +1 -0
- package/lib/module/utils/networking.js +121 -0
- package/lib/module/utils/networking.js.map +1 -0
- package/lib/typescript/AIAContentUsefulness.d.ts +14 -0
- package/lib/typescript/AIAContentUsefulness.d.ts.map +1 -0
- package/lib/typescript/AIAFeedback.d.ts +15 -0
- package/lib/typescript/AIAFeedback.d.ts.map +1 -0
- package/lib/typescript/AIAFeedbackForm.d.ts +22 -0
- package/lib/typescript/AIAFeedbackForm.d.ts.map +1 -0
- package/lib/typescript/AIAFeedbackSplash.d.ts +10 -0
- package/lib/typescript/AIAFeedbackSplash.d.ts.map +1 -0
- package/lib/typescript/AIAFeedbackStyles.d.ts +342 -0
- package/lib/typescript/AIAFeedbackStyles.d.ts.map +1 -0
- package/lib/typescript/AIAFeedbackSuccess.d.ts +11 -0
- package/lib/typescript/AIAFeedbackSuccess.d.ts.map +1 -0
- package/lib/typescript/assets/CheckIcon.d.ts +7 -0
- package/lib/typescript/assets/CheckIcon.d.ts.map +1 -0
- package/lib/typescript/assets/CloseIcon.d.ts +7 -0
- package/lib/typescript/assets/CloseIcon.d.ts.map +1 -0
- package/lib/typescript/assets/ErrorIcon.d.ts +7 -0
- package/lib/typescript/assets/ErrorIcon.d.ts.map +1 -0
- package/lib/typescript/assets/PlusIcon.d.ts +7 -0
- package/lib/typescript/assets/PlusIcon.d.ts.map +1 -0
- package/lib/typescript/assets/StarIcon.d.ts +7 -0
- package/lib/typescript/assets/StarIcon.d.ts.map +1 -0
- package/lib/typescript/component/Button.d.ts +30 -0
- package/lib/typescript/component/Button.d.ts.map +1 -0
- package/lib/typescript/component/ButtonSubmit.d.ts +115 -0
- package/lib/typescript/component/ButtonSubmit.d.ts.map +1 -0
- package/lib/typescript/component/Input.d.ts +112 -0
- package/lib/typescript/component/Input.d.ts.map +1 -0
- package/lib/typescript/component/MultiSelectButtons.d.ts +103 -0
- package/lib/typescript/component/MultiSelectButtons.d.ts.map +1 -0
- package/lib/typescript/component/Rating.d.ts +83 -0
- package/lib/typescript/component/Rating.d.ts.map +1 -0
- package/lib/typescript/component/RatingNumber.d.ts +135 -0
- package/lib/typescript/component/RatingNumber.d.ts.map +1 -0
- package/lib/typescript/component/Textarea.d.ts +115 -0
- package/lib/typescript/component/Textarea.d.ts.map +1 -0
- package/lib/typescript/component/YesNoButtons.d.ts +94 -0
- package/lib/typescript/component/YesNoButtons.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +21 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/service/feedbackService.d.ts +33 -0
- package/lib/typescript/service/feedbackService.d.ts.map +1 -0
- package/lib/typescript/utils/common.d.ts +23 -0
- package/lib/typescript/utils/common.d.ts.map +1 -0
- package/lib/typescript/utils/constants.d.ts +38 -0
- package/lib/typescript/utils/constants.d.ts.map +1 -0
- package/lib/typescript/utils/index.d.ts +12 -0
- package/lib/typescript/utils/index.d.ts.map +1 -0
- package/lib/typescript/utils/networking.d.ts +12 -0
- package/lib/typescript/utils/networking.d.ts.map +1 -0
- package/package.json +175 -39
- package/src/AIAContentUsefulness.tsx +296 -0
- package/src/AIAFeedback.tsx +354 -0
- package/src/AIAFeedbackForm.tsx +267 -0
- package/src/AIAFeedbackSplash.tsx +49 -0
- package/src/AIAFeedbackStyles.ts +311 -0
- package/src/AIAFeedbackSuccess.tsx +67 -0
- package/src/assets/CheckIcon.tsx +18 -0
- package/src/assets/CloseIcon.tsx +18 -0
- package/src/assets/ErrorIcon.tsx +18 -0
- package/src/assets/PlusIcon.tsx +18 -0
- package/src/assets/StarIcon.tsx +18 -0
- package/src/component/Button.tsx +68 -0
- package/src/component/ButtonSubmit.tsx +335 -0
- package/src/component/Input.tsx +288 -0
- package/src/component/MultiSelectButtons.tsx +272 -0
- package/src/component/README.md +215 -0
- package/src/component/READMEVI.md +192 -0
- package/src/component/Rating.tsx +248 -0
- package/src/component/RatingNumber.tsx +421 -0
- package/src/component/Textarea.tsx +282 -0
- package/src/component/YesNoButtons.tsx +236 -0
- package/src/index.tsx +33 -0
- package/src/service/feedbackService.ts +108 -0
- package/src/utils/common.ts +241 -0
- package/src/utils/constants.ts +60 -0
- package/src/utils/index.ts +167 -0
- package/src/utils/networking.ts +134 -0
- package/fonts/AIAEverest-CondensedMedium.ttf +0 -0
- package/fonts/AIAEverest-Medium.ttf +0 -0
- package/fonts/AIAEverest-Regular.ttf +0 -0
- package/fonts/AIAEverestBold.ttf +0 -0
- package/fonts/OpenSans-Bold.ttf +0 -0
- package/fonts/OpenSans-Light.ttf +0 -0
- package/fonts/OpenSans-Regular.ttf +0 -0
- package/fonts/OpenSans-SemiBold.ttf +0 -0
- package/lib/commonjs/api/index.js +0 -1
- package/lib/commonjs/assets/index.js +0 -1
- package/lib/commonjs/component/confirmStep.js +0 -1
- package/lib/commonjs/component/confirmStepTablet.js +0 -1
- package/lib/commonjs/component/contentUI.js +0 -1
- package/lib/commonjs/component/contentUITablet.js +0 -1
- package/lib/commonjs/component/header.js +0 -1
- package/lib/commonjs/component/index.js +0 -1
- package/lib/commonjs/component/inlineSurvey.js +0 -1
- package/lib/commonjs/index.js +0 -1
- package/lib/commonjs/ui/ButtonSubmit.js +0 -1
- package/lib/commonjs/ui/Fineprint.js +0 -1
- package/lib/commonjs/ui/Instruction.js +0 -1
- package/lib/commonjs/ui/MultipleSelectInput.js +0 -1
- package/lib/commonjs/ui/Question.js +0 -1
- package/lib/commonjs/ui/RatingNumber.js +0 -1
- package/lib/commonjs/ui/RatingStar.js +0 -1
- package/lib/commonjs/ui/SecondaryQuestion.js +0 -1
- package/lib/commonjs/ui/TextArea.js +0 -1
- package/lib/commonjs/ui/index.js +0 -1
- package/lib/commonjs/utils/MyFunction.js +0 -1
- package/lib/commonjs/utils/NetworkHelper.js +0 -1
- package/lib/commonjs/utils/StatusBarHeight.js +0 -1
- package/lib/commonjs/utils/const.js +0 -1
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import {useMemo, useState} from 'react'
|
|
2
|
+
import {View, Text, StyleSheet, TextInput, type ViewStyle, type TextStyle} from 'react-native'
|
|
3
|
+
import * as f from '@utils/common'
|
|
4
|
+
import {
|
|
5
|
+
BG_DEFAULT,
|
|
6
|
+
BORDER_FOCUS,
|
|
7
|
+
BORDER_SECONDARY,
|
|
8
|
+
ERROR_COLOR,
|
|
9
|
+
TEXT_DARK,
|
|
10
|
+
TEXT_NOTE,
|
|
11
|
+
TEXT_PLACEHOLDER,
|
|
12
|
+
} from '@utils/constants'
|
|
13
|
+
import ErrorIcon from '@/assets/ErrorIcon'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Props for the single-line `Input` component.
|
|
17
|
+
*
|
|
18
|
+
* Renders a label, single-line text input, optional note, error,
|
|
19
|
+
* and an optional character counter.
|
|
20
|
+
*/
|
|
21
|
+
export interface InputProps {
|
|
22
|
+
/**
|
|
23
|
+
* Current text value.
|
|
24
|
+
*/
|
|
25
|
+
value: string
|
|
26
|
+
/**
|
|
27
|
+
* Called when the text changes.
|
|
28
|
+
*/
|
|
29
|
+
onChangeText: (text: string) => void
|
|
30
|
+
/**
|
|
31
|
+
* Placeholder text.
|
|
32
|
+
*
|
|
33
|
+
* @default 'Placeholder'
|
|
34
|
+
*/
|
|
35
|
+
placeholder?: string
|
|
36
|
+
/**
|
|
37
|
+
* Label text rendered above the field.
|
|
38
|
+
*/
|
|
39
|
+
label?: string
|
|
40
|
+
/**
|
|
41
|
+
* Helper text rendered below the field.
|
|
42
|
+
*/
|
|
43
|
+
note?: string
|
|
44
|
+
/**
|
|
45
|
+
* Error message; when present, border and text are styled as error.
|
|
46
|
+
*/
|
|
47
|
+
error?: string
|
|
48
|
+
/**
|
|
49
|
+
* Maximum number of characters allowed.
|
|
50
|
+
*/
|
|
51
|
+
maxLength?: number
|
|
52
|
+
/**
|
|
53
|
+
* Whether to show the 0/maxLength counter when `maxLength` is set.
|
|
54
|
+
*
|
|
55
|
+
* @default true
|
|
56
|
+
*/
|
|
57
|
+
showCounter?: boolean
|
|
58
|
+
/**
|
|
59
|
+
* App width, used to compute internal padding.
|
|
60
|
+
*/
|
|
61
|
+
appWidth: number
|
|
62
|
+
// Style
|
|
63
|
+
/**
|
|
64
|
+
* Background color when not focused.
|
|
65
|
+
*/
|
|
66
|
+
backgroundColor?: string
|
|
67
|
+
/**
|
|
68
|
+
* Border color when not focused and not errored.
|
|
69
|
+
*/
|
|
70
|
+
borderColor?: string
|
|
71
|
+
/**
|
|
72
|
+
* Background color when focused.
|
|
73
|
+
*/
|
|
74
|
+
focusBackgroundColor?: string
|
|
75
|
+
/**
|
|
76
|
+
* Border color when focused.
|
|
77
|
+
*/
|
|
78
|
+
focusBorderColor?: string
|
|
79
|
+
/**
|
|
80
|
+
* Border color when `error` is present.
|
|
81
|
+
*
|
|
82
|
+
* @default '#D32F2F'
|
|
83
|
+
*/
|
|
84
|
+
errorBorderColor?: string
|
|
85
|
+
/**
|
|
86
|
+
* Font family for the text input.
|
|
87
|
+
*/
|
|
88
|
+
fontFamily?: string
|
|
89
|
+
/**
|
|
90
|
+
* Font size for the text input.
|
|
91
|
+
*
|
|
92
|
+
* @default 16
|
|
93
|
+
*/
|
|
94
|
+
fontSize?: number
|
|
95
|
+
/**
|
|
96
|
+
* Color used for the placeholder text.
|
|
97
|
+
*/
|
|
98
|
+
placeholderTextColor?: string
|
|
99
|
+
/**
|
|
100
|
+
* Color used for the input text.
|
|
101
|
+
*/
|
|
102
|
+
textColor?: string
|
|
103
|
+
/**
|
|
104
|
+
* Style for the label text.
|
|
105
|
+
*/
|
|
106
|
+
labelStyle?: TextStyle
|
|
107
|
+
/**
|
|
108
|
+
* Style for the note text.
|
|
109
|
+
*/
|
|
110
|
+
noteStyle?: TextStyle
|
|
111
|
+
/**
|
|
112
|
+
* Style for the error text.
|
|
113
|
+
*/
|
|
114
|
+
errorStyle?: TextStyle
|
|
115
|
+
/**
|
|
116
|
+
* Style for the character counter text.
|
|
117
|
+
*/
|
|
118
|
+
counterStyle?: TextStyle
|
|
119
|
+
/**
|
|
120
|
+
* Style for the outer wrapper view.
|
|
121
|
+
*/
|
|
122
|
+
style?: ViewStyle
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const DEFAULT_BG = BG_DEFAULT
|
|
126
|
+
const DEFAULT_BORDER = BORDER_SECONDARY
|
|
127
|
+
const DEFAULT_FOCUS_BORDER = BORDER_FOCUS
|
|
128
|
+
const DEFAULT_TEXT = TEXT_DARK
|
|
129
|
+
const DEFAULT_PLACEHOLDER = TEXT_PLACEHOLDER
|
|
130
|
+
const DEFAULT_COUNTER = TEXT_PLACEHOLDER
|
|
131
|
+
const DEFAULT_NOTE = TEXT_NOTE
|
|
132
|
+
const DEFAULT_ERROR = ERROR_COLOR
|
|
133
|
+
|
|
134
|
+
function Input({
|
|
135
|
+
value,
|
|
136
|
+
onChangeText,
|
|
137
|
+
placeholder = 'Placeholder',
|
|
138
|
+
label,
|
|
139
|
+
note,
|
|
140
|
+
error,
|
|
141
|
+
maxLength,
|
|
142
|
+
showCounter = true,
|
|
143
|
+
appWidth,
|
|
144
|
+
backgroundColor = DEFAULT_BG,
|
|
145
|
+
borderColor = DEFAULT_BORDER,
|
|
146
|
+
focusBackgroundColor = DEFAULT_BG,
|
|
147
|
+
focusBorderColor = DEFAULT_FOCUS_BORDER,
|
|
148
|
+
errorBorderColor = DEFAULT_ERROR,
|
|
149
|
+
fontFamily,
|
|
150
|
+
fontSize = 16,
|
|
151
|
+
placeholderTextColor = DEFAULT_PLACEHOLDER,
|
|
152
|
+
textColor = DEFAULT_TEXT,
|
|
153
|
+
labelStyle,
|
|
154
|
+
noteStyle,
|
|
155
|
+
errorStyle,
|
|
156
|
+
counterStyle,
|
|
157
|
+
style,
|
|
158
|
+
}: InputProps) {
|
|
159
|
+
const [focused, setFocused] = useState(false)
|
|
160
|
+
const length = value.length
|
|
161
|
+
const hasError = useMemo(() => error != null && error !== '', [error])
|
|
162
|
+
const showCounterEl = useMemo(() => maxLength != null && showCounter, [maxLength, showCounter])
|
|
163
|
+
|
|
164
|
+
const effectiveBorderColor = useMemo(() => {
|
|
165
|
+
if (hasError) return errorBorderColor
|
|
166
|
+
if (focused) return focusBorderColor
|
|
167
|
+
return borderColor
|
|
168
|
+
}, [hasError, focused, errorBorderColor, focusBorderColor, borderColor])
|
|
169
|
+
|
|
170
|
+
const inputContainerStyle = useMemo(
|
|
171
|
+
() => [
|
|
172
|
+
styles.inputWrapper,
|
|
173
|
+
{
|
|
174
|
+
paddingHorizontal: f.w_p(appWidth, 8),
|
|
175
|
+
paddingVertical: 10,
|
|
176
|
+
borderRadius: f.isTablet ? 6 : 4,
|
|
177
|
+
backgroundColor: focused ? focusBackgroundColor : backgroundColor,
|
|
178
|
+
borderColor: effectiveBorderColor,
|
|
179
|
+
borderWidth: 1,
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
[appWidth, focused, focusBackgroundColor, backgroundColor, effectiveBorderColor],
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
const textInputStyle = useMemo(
|
|
186
|
+
() => [
|
|
187
|
+
styles.input,
|
|
188
|
+
{
|
|
189
|
+
fontFamily,
|
|
190
|
+
fontSize,
|
|
191
|
+
color: textColor,
|
|
192
|
+
paddingRight: showCounterEl ? 48 : 0,
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
[fontFamily, fontSize, textColor, showCounterEl],
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<View style={[styles.wrapper, style]}>
|
|
200
|
+
{label != null && label !== '' && <Text style={[styles.label, labelStyle]}>{label}</Text>}
|
|
201
|
+
<View style={inputContainerStyle}>
|
|
202
|
+
<TextInput
|
|
203
|
+
placeholder={placeholder}
|
|
204
|
+
placeholderTextColor={placeholderTextColor}
|
|
205
|
+
style={textInputStyle}
|
|
206
|
+
value={value}
|
|
207
|
+
onChangeText={text => {
|
|
208
|
+
if (maxLength == null || text.length <= maxLength) {
|
|
209
|
+
onChangeText(text)
|
|
210
|
+
} else {
|
|
211
|
+
onChangeText(text.slice(0, maxLength))
|
|
212
|
+
}
|
|
213
|
+
}}
|
|
214
|
+
onFocus={() => setFocused(true)}
|
|
215
|
+
onBlur={() => setFocused(false)}
|
|
216
|
+
autoCorrect={false}
|
|
217
|
+
/>
|
|
218
|
+
{showCounterEl && (
|
|
219
|
+
<View style={styles.counterWrapper} pointerEvents="none">
|
|
220
|
+
<Text style={[styles.counter, {color: DEFAULT_COUNTER}, counterStyle]}>
|
|
221
|
+
{length}/{maxLength}
|
|
222
|
+
</Text>
|
|
223
|
+
</View>
|
|
224
|
+
)}
|
|
225
|
+
</View>
|
|
226
|
+
{note != null && note !== '' && <Text style={[styles.note, {color: DEFAULT_NOTE}, noteStyle]}>{note}</Text>}
|
|
227
|
+
|
|
228
|
+
{hasError && (
|
|
229
|
+
<View style={styles.errorWrap}>
|
|
230
|
+
<ErrorIcon size={20} color={ERROR_COLOR} />
|
|
231
|
+
<Text style={[styles.error, {color: DEFAULT_ERROR}, errorStyle]}>{error}</Text>
|
|
232
|
+
</View>
|
|
233
|
+
)}
|
|
234
|
+
</View>
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export default Input
|
|
239
|
+
|
|
240
|
+
const styles = StyleSheet.create({
|
|
241
|
+
wrapper: {
|
|
242
|
+
width: '100%',
|
|
243
|
+
},
|
|
244
|
+
label: {
|
|
245
|
+
fontSize: 16,
|
|
246
|
+
fontWeight: '600',
|
|
247
|
+
color: '#000000',
|
|
248
|
+
marginBottom: 8,
|
|
249
|
+
},
|
|
250
|
+
inputWrapper: {
|
|
251
|
+
width: '100%',
|
|
252
|
+
position: 'relative',
|
|
253
|
+
flexDirection: 'row',
|
|
254
|
+
alignItems: 'center',
|
|
255
|
+
},
|
|
256
|
+
input: {
|
|
257
|
+
flex: 1,
|
|
258
|
+
padding: 0,
|
|
259
|
+
minHeight: 20,
|
|
260
|
+
},
|
|
261
|
+
counterWrapper: {
|
|
262
|
+
position: 'absolute',
|
|
263
|
+
right: 12,
|
|
264
|
+
top: 0,
|
|
265
|
+
bottom: 0,
|
|
266
|
+
justifyContent: 'center',
|
|
267
|
+
},
|
|
268
|
+
counter: {
|
|
269
|
+
fontSize: 12,
|
|
270
|
+
},
|
|
271
|
+
note: {
|
|
272
|
+
fontSize: 12,
|
|
273
|
+
marginTop: 8,
|
|
274
|
+
},
|
|
275
|
+
errorWrap: {
|
|
276
|
+
flexDirection: 'row',
|
|
277
|
+
alignItems: 'center',
|
|
278
|
+
marginTop: 8,
|
|
279
|
+
gap: 6,
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
error: {
|
|
283
|
+
fontSize: 14,
|
|
284
|
+
lineHeight: 20,
|
|
285
|
+
flex: 1,
|
|
286
|
+
fontWeight: '400',
|
|
287
|
+
},
|
|
288
|
+
})
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import {useMemo} from 'react'
|
|
2
|
+
import {View, Text, StyleSheet, type TextStyle, type ViewStyle} from 'react-native'
|
|
3
|
+
import * as f from '@utils/common'
|
|
4
|
+
import ButtonSubmit from '@component/ButtonSubmit'
|
|
5
|
+
import CheckIcon from '@/assets/CheckIcon'
|
|
6
|
+
import PlusIcon from '@/assets/PlusIcon'
|
|
7
|
+
import {
|
|
8
|
+
BG_DEFAULT,
|
|
9
|
+
BG_SECONDARY,
|
|
10
|
+
BORDER_DEFAULT,
|
|
11
|
+
BORDER_SELECTED,
|
|
12
|
+
ERROR_COLOR,
|
|
13
|
+
PRIMARY_COLOR,
|
|
14
|
+
TEXT_DARK,
|
|
15
|
+
TEXT_DEFAULT,
|
|
16
|
+
TEXT_WHITE,
|
|
17
|
+
} from '@utils/constants'
|
|
18
|
+
import * as Haptics from 'expo-haptics'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Visual variant for selected tags in `MultiSelectButtons`.
|
|
22
|
+
*
|
|
23
|
+
* - `'primary'` – solid primary background with white text.
|
|
24
|
+
* - `'secondary'` – light background with colored border and dark text.
|
|
25
|
+
*/
|
|
26
|
+
export type MultiSelectSelectedVariant = 'primary' | 'secondary'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Single option for `MultiSelectButtons`.
|
|
30
|
+
*/
|
|
31
|
+
export interface MultiSelectOption {
|
|
32
|
+
/**
|
|
33
|
+
* Value emitted in `value` and `onSelect`.
|
|
34
|
+
*/
|
|
35
|
+
value: string
|
|
36
|
+
/**
|
|
37
|
+
* Label shown on the tag button.
|
|
38
|
+
*/
|
|
39
|
+
label: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Props for the `MultiSelectButtons` component.
|
|
44
|
+
*
|
|
45
|
+
* Renders a question and a wrap layout of tag-like buttons allowing multiple selections.
|
|
46
|
+
*/
|
|
47
|
+
export interface MultiSelectButtonsProps {
|
|
48
|
+
/**
|
|
49
|
+
* Question text displayed above the options.
|
|
50
|
+
*/
|
|
51
|
+
question: string
|
|
52
|
+
/**
|
|
53
|
+
* List of options to render.
|
|
54
|
+
*/
|
|
55
|
+
options: MultiSelectOption[]
|
|
56
|
+
/**
|
|
57
|
+
* Array of currently selected option values.
|
|
58
|
+
*/
|
|
59
|
+
value: string[]
|
|
60
|
+
/**
|
|
61
|
+
* Called when the selection changes (toggle behavior).
|
|
62
|
+
*
|
|
63
|
+
* @param selected The new list of selected values.
|
|
64
|
+
*/
|
|
65
|
+
onSelect: (selected: string[]) => void
|
|
66
|
+
/**
|
|
67
|
+
* App width, passed to underlying `ButtonSubmit` components.
|
|
68
|
+
*/
|
|
69
|
+
appWidth: number
|
|
70
|
+
/**
|
|
71
|
+
* Visual variant for the selected tags.
|
|
72
|
+
*
|
|
73
|
+
* @default 'secondary'
|
|
74
|
+
*/
|
|
75
|
+
selectedVariant?: MultiSelectSelectedVariant
|
|
76
|
+
/**
|
|
77
|
+
* Error message; when present, shown below the options.
|
|
78
|
+
*/
|
|
79
|
+
error?: string
|
|
80
|
+
/**
|
|
81
|
+
* Override background color when an option is selected.
|
|
82
|
+
*/
|
|
83
|
+
selectedBackgroundColor?: string
|
|
84
|
+
/**
|
|
85
|
+
* Override border color when an option is selected.
|
|
86
|
+
*/
|
|
87
|
+
selectedBorderColor?: string
|
|
88
|
+
/**
|
|
89
|
+
* Override text color when an option is selected.
|
|
90
|
+
*/
|
|
91
|
+
selectedTextColor?: string
|
|
92
|
+
/**
|
|
93
|
+
* Style for the question text.
|
|
94
|
+
*/
|
|
95
|
+
questionStyle?: TextStyle
|
|
96
|
+
/**
|
|
97
|
+
* Style for the error text.
|
|
98
|
+
*/
|
|
99
|
+
errorStyle?: TextStyle
|
|
100
|
+
/**
|
|
101
|
+
* Color for the error text and "!" icon.
|
|
102
|
+
*/
|
|
103
|
+
errorColor?: string
|
|
104
|
+
/**
|
|
105
|
+
* Size of the plus/check icons (px).
|
|
106
|
+
*
|
|
107
|
+
* @default 16
|
|
108
|
+
*/
|
|
109
|
+
iconSize?: number
|
|
110
|
+
/**
|
|
111
|
+
* Maximum number of lines for Each option label.
|
|
112
|
+
*
|
|
113
|
+
* @default 2
|
|
114
|
+
*/
|
|
115
|
+
optionNumberOfLines?: number
|
|
116
|
+
/**
|
|
117
|
+
* Style for the outer wrapper view.
|
|
118
|
+
*/
|
|
119
|
+
style?: ViewStyle
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function MultiSelectButtons({
|
|
123
|
+
question,
|
|
124
|
+
options,
|
|
125
|
+
value,
|
|
126
|
+
onSelect,
|
|
127
|
+
appWidth,
|
|
128
|
+
selectedVariant = 'secondary',
|
|
129
|
+
error,
|
|
130
|
+
selectedBackgroundColor,
|
|
131
|
+
selectedBorderColor,
|
|
132
|
+
selectedTextColor,
|
|
133
|
+
questionStyle,
|
|
134
|
+
errorStyle,
|
|
135
|
+
errorColor = ERROR_COLOR,
|
|
136
|
+
iconSize = 16,
|
|
137
|
+
optionNumberOfLines = 1,
|
|
138
|
+
style,
|
|
139
|
+
}: MultiSelectButtonsProps) {
|
|
140
|
+
const hasError = useMemo(() => error != null && error !== '', [error])
|
|
141
|
+
const selectedSet = useMemo(() => new Set(value), [value])
|
|
142
|
+
|
|
143
|
+
const isPrimary = selectedVariant === 'primary'
|
|
144
|
+
|
|
145
|
+
const selectedBg = useMemo(
|
|
146
|
+
() => selectedBackgroundColor ?? (isPrimary ? PRIMARY_COLOR : BG_SECONDARY),
|
|
147
|
+
[selectedBackgroundColor, isPrimary],
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
const selectedBorder = useMemo(
|
|
151
|
+
() => (isPrimary ? selectedBg : selectedBorderColor ?? BORDER_SELECTED),
|
|
152
|
+
[isPrimary, selectedBg, selectedBorderColor],
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
const selectedText = useMemo(
|
|
156
|
+
() => selectedTextColor ?? (isPrimary ? TEXT_WHITE : TEXT_DEFAULT),
|
|
157
|
+
[selectedTextColor, isPrimary],
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const toggle = (optionValue: string) => {
|
|
161
|
+
if (selectedSet.has(optionValue)) {
|
|
162
|
+
onSelect(value.filter(v => v !== optionValue))
|
|
163
|
+
} else {
|
|
164
|
+
onSelect([...value, optionValue])
|
|
165
|
+
}
|
|
166
|
+
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const marginTop = useMemo(() => [styles.optionsRow, {marginTop: f.w_p(appWidth, 10)}], [appWidth])
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<View style={[styles.wrapper, style]}>
|
|
173
|
+
{question != null && question !== '' && <Text style={[styles.question, questionStyle]}>{question}</Text>}
|
|
174
|
+
<View style={marginTop}>
|
|
175
|
+
{options.map(opt => {
|
|
176
|
+
const isSelected = selectedSet.has(opt.value)
|
|
177
|
+
|
|
178
|
+
const buttonStyles = (() => {
|
|
179
|
+
if (isSelected) {
|
|
180
|
+
return {
|
|
181
|
+
bg: selectedBg,
|
|
182
|
+
text: selectedText,
|
|
183
|
+
border: selectedBorder,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (hasError) {
|
|
187
|
+
return {bg: BG_DEFAULT, text: errorColor, border: errorColor}
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
bg: BG_DEFAULT,
|
|
191
|
+
text: TEXT_DARK,
|
|
192
|
+
border: BORDER_DEFAULT,
|
|
193
|
+
}
|
|
194
|
+
})()
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<View key={opt.value} style={styles.optionSlot}>
|
|
198
|
+
<ButtonSubmit
|
|
199
|
+
appWidth={appWidth}
|
|
200
|
+
title={opt.label}
|
|
201
|
+
leftIcon={
|
|
202
|
+
isSelected ? (
|
|
203
|
+
<CheckIcon size={iconSize} color={buttonStyles.text} />
|
|
204
|
+
) : (
|
|
205
|
+
<PlusIcon size={iconSize} color={buttonStyles.text} />
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
onPress={() => toggle(opt.value)}
|
|
209
|
+
variant="secondary"
|
|
210
|
+
fullWidth={false}
|
|
211
|
+
backgroundColor={buttonStyles.bg}
|
|
212
|
+
textColor={buttonStyles.text}
|
|
213
|
+
borderColor={buttonStyles.border}
|
|
214
|
+
containerStyle={styles.tagContainer}
|
|
215
|
+
numberOfLines={optionNumberOfLines}
|
|
216
|
+
style={styles.tagButton}
|
|
217
|
+
/>
|
|
218
|
+
</View>
|
|
219
|
+
)
|
|
220
|
+
})}
|
|
221
|
+
</View>
|
|
222
|
+
{hasError && (
|
|
223
|
+
<View style={styles.errorRow}>
|
|
224
|
+
<Text style={[styles.errorIcon, {color: errorColor}]}>!</Text>
|
|
225
|
+
<Text style={[styles.errorText, {color: errorColor}, errorStyle]}>{error}</Text>
|
|
226
|
+
</View>
|
|
227
|
+
)}
|
|
228
|
+
</View>
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export default MultiSelectButtons
|
|
233
|
+
|
|
234
|
+
const styles = StyleSheet.create({
|
|
235
|
+
wrapper: {
|
|
236
|
+
width: '100%',
|
|
237
|
+
},
|
|
238
|
+
question: {
|
|
239
|
+
fontSize: 16,
|
|
240
|
+
fontWeight: '600',
|
|
241
|
+
color: '#000000',
|
|
242
|
+
marginBottom: 4,
|
|
243
|
+
},
|
|
244
|
+
optionsRow: {
|
|
245
|
+
flexDirection: 'row',
|
|
246
|
+
flexWrap: 'wrap',
|
|
247
|
+
gap: 10,
|
|
248
|
+
},
|
|
249
|
+
optionSlot: {
|
|
250
|
+
alignSelf: 'flex-start',
|
|
251
|
+
},
|
|
252
|
+
tagContainer: {
|
|
253
|
+
marginTop: 0,
|
|
254
|
+
},
|
|
255
|
+
tagButton: {
|
|
256
|
+
paddingVertical: 8,
|
|
257
|
+
paddingHorizontal: 12,
|
|
258
|
+
},
|
|
259
|
+
errorRow: {
|
|
260
|
+
flexDirection: 'row',
|
|
261
|
+
alignItems: 'center',
|
|
262
|
+
marginTop: 8,
|
|
263
|
+
gap: 6,
|
|
264
|
+
},
|
|
265
|
+
errorIcon: {
|
|
266
|
+
fontSize: 14,
|
|
267
|
+
fontWeight: '700',
|
|
268
|
+
},
|
|
269
|
+
errorText: {
|
|
270
|
+
fontSize: 12,
|
|
271
|
+
},
|
|
272
|
+
})
|