@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.

Files changed (166) hide show
  1. package/{LICENSE.txt → LICENSE} +1 -2
  2. package/README.md +69 -176
  3. package/lib/module/AIAContentUsefulness.js +273 -0
  4. package/lib/module/AIAContentUsefulness.js.map +1 -0
  5. package/lib/module/AIAFeedback.js +295 -0
  6. package/lib/module/AIAFeedback.js.map +1 -0
  7. package/lib/module/AIAFeedbackForm.js +212 -0
  8. package/lib/module/AIAFeedbackForm.js.map +1 -0
  9. package/lib/module/AIAFeedbackSplash.js +57 -0
  10. package/lib/module/AIAFeedbackSplash.js.map +1 -0
  11. package/lib/module/AIAFeedbackStyles.js +329 -0
  12. package/lib/module/AIAFeedbackStyles.js.map +1 -0
  13. package/lib/module/AIAFeedbackSuccess.js +68 -0
  14. package/lib/module/AIAFeedbackSuccess.js.map +1 -0
  15. package/lib/module/assets/CheckIcon.js +21 -0
  16. package/lib/module/assets/CheckIcon.js.map +1 -0
  17. package/lib/module/assets/CloseIcon.js +21 -0
  18. package/lib/module/assets/CloseIcon.js.map +1 -0
  19. package/lib/module/assets/ErrorIcon.js +23 -0
  20. package/lib/module/assets/ErrorIcon.js.map +1 -0
  21. package/lib/module/assets/PlusIcon.js +21 -0
  22. package/lib/module/assets/PlusIcon.js.map +1 -0
  23. package/lib/module/assets/StarIcon.js +21 -0
  24. package/lib/module/assets/StarIcon.js.map +1 -0
  25. package/lib/module/component/Button.js +49 -0
  26. package/lib/module/component/Button.js.map +1 -0
  27. package/lib/module/component/ButtonSubmit.js +194 -0
  28. package/lib/module/component/ButtonSubmit.js.map +1 -0
  29. package/lib/module/component/Input.js +172 -0
  30. package/lib/module/component/Input.js.map +1 -0
  31. package/lib/module/component/MultiSelectButtons.js +174 -0
  32. package/lib/module/component/MultiSelectButtons.js.map +1 -0
  33. package/lib/module/component/README.md +215 -0
  34. package/lib/module/component/READMEVI.md +192 -0
  35. package/lib/module/component/Rating.js +168 -0
  36. package/lib/module/component/Rating.js.map +1 -0
  37. package/lib/module/component/RatingNumber.js +268 -0
  38. package/lib/module/component/RatingNumber.js.map +1 -0
  39. package/lib/module/component/Textarea.js +163 -0
  40. package/lib/module/component/Textarea.js.map +1 -0
  41. package/lib/module/component/YesNoButtons.js +161 -0
  42. package/lib/module/component/YesNoButtons.js.map +1 -0
  43. package/lib/module/index.js +13 -0
  44. package/lib/module/index.js.map +1 -0
  45. package/lib/module/package.json +1 -0
  46. package/lib/module/service/feedbackService.js +77 -0
  47. package/lib/module/service/feedbackService.js.map +1 -0
  48. package/lib/module/utils/common.js +199 -0
  49. package/lib/module/utils/common.js.map +1 -0
  50. package/lib/module/utils/constants.js +48 -0
  51. package/lib/module/utils/constants.js.map +1 -0
  52. package/lib/module/utils/index.js +140 -0
  53. package/lib/module/utils/index.js.map +1 -0
  54. package/lib/module/utils/networking.js +121 -0
  55. package/lib/module/utils/networking.js.map +1 -0
  56. package/lib/typescript/AIAContentUsefulness.d.ts +14 -0
  57. package/lib/typescript/AIAContentUsefulness.d.ts.map +1 -0
  58. package/lib/typescript/AIAFeedback.d.ts +15 -0
  59. package/lib/typescript/AIAFeedback.d.ts.map +1 -0
  60. package/lib/typescript/AIAFeedbackForm.d.ts +22 -0
  61. package/lib/typescript/AIAFeedbackForm.d.ts.map +1 -0
  62. package/lib/typescript/AIAFeedbackSplash.d.ts +10 -0
  63. package/lib/typescript/AIAFeedbackSplash.d.ts.map +1 -0
  64. package/lib/typescript/AIAFeedbackStyles.d.ts +342 -0
  65. package/lib/typescript/AIAFeedbackStyles.d.ts.map +1 -0
  66. package/lib/typescript/AIAFeedbackSuccess.d.ts +11 -0
  67. package/lib/typescript/AIAFeedbackSuccess.d.ts.map +1 -0
  68. package/lib/typescript/assets/CheckIcon.d.ts +7 -0
  69. package/lib/typescript/assets/CheckIcon.d.ts.map +1 -0
  70. package/lib/typescript/assets/CloseIcon.d.ts +7 -0
  71. package/lib/typescript/assets/CloseIcon.d.ts.map +1 -0
  72. package/lib/typescript/assets/ErrorIcon.d.ts +7 -0
  73. package/lib/typescript/assets/ErrorIcon.d.ts.map +1 -0
  74. package/lib/typescript/assets/PlusIcon.d.ts +7 -0
  75. package/lib/typescript/assets/PlusIcon.d.ts.map +1 -0
  76. package/lib/typescript/assets/StarIcon.d.ts +7 -0
  77. package/lib/typescript/assets/StarIcon.d.ts.map +1 -0
  78. package/lib/typescript/component/Button.d.ts +30 -0
  79. package/lib/typescript/component/Button.d.ts.map +1 -0
  80. package/lib/typescript/component/ButtonSubmit.d.ts +115 -0
  81. package/lib/typescript/component/ButtonSubmit.d.ts.map +1 -0
  82. package/lib/typescript/component/Input.d.ts +112 -0
  83. package/lib/typescript/component/Input.d.ts.map +1 -0
  84. package/lib/typescript/component/MultiSelectButtons.d.ts +103 -0
  85. package/lib/typescript/component/MultiSelectButtons.d.ts.map +1 -0
  86. package/lib/typescript/component/Rating.d.ts +83 -0
  87. package/lib/typescript/component/Rating.d.ts.map +1 -0
  88. package/lib/typescript/component/RatingNumber.d.ts +135 -0
  89. package/lib/typescript/component/RatingNumber.d.ts.map +1 -0
  90. package/lib/typescript/component/Textarea.d.ts +115 -0
  91. package/lib/typescript/component/Textarea.d.ts.map +1 -0
  92. package/lib/typescript/component/YesNoButtons.d.ts +94 -0
  93. package/lib/typescript/component/YesNoButtons.d.ts.map +1 -0
  94. package/lib/typescript/index.d.ts +21 -0
  95. package/lib/typescript/index.d.ts.map +1 -0
  96. package/lib/typescript/package.json +1 -0
  97. package/lib/typescript/service/feedbackService.d.ts +33 -0
  98. package/lib/typescript/service/feedbackService.d.ts.map +1 -0
  99. package/lib/typescript/utils/common.d.ts +23 -0
  100. package/lib/typescript/utils/common.d.ts.map +1 -0
  101. package/lib/typescript/utils/constants.d.ts +38 -0
  102. package/lib/typescript/utils/constants.d.ts.map +1 -0
  103. package/lib/typescript/utils/index.d.ts +12 -0
  104. package/lib/typescript/utils/index.d.ts.map +1 -0
  105. package/lib/typescript/utils/networking.d.ts +12 -0
  106. package/lib/typescript/utils/networking.d.ts.map +1 -0
  107. package/package.json +175 -39
  108. package/src/AIAContentUsefulness.tsx +296 -0
  109. package/src/AIAFeedback.tsx +354 -0
  110. package/src/AIAFeedbackForm.tsx +267 -0
  111. package/src/AIAFeedbackSplash.tsx +49 -0
  112. package/src/AIAFeedbackStyles.ts +311 -0
  113. package/src/AIAFeedbackSuccess.tsx +67 -0
  114. package/src/assets/CheckIcon.tsx +18 -0
  115. package/src/assets/CloseIcon.tsx +18 -0
  116. package/src/assets/ErrorIcon.tsx +18 -0
  117. package/src/assets/PlusIcon.tsx +18 -0
  118. package/src/assets/StarIcon.tsx +18 -0
  119. package/src/component/Button.tsx +68 -0
  120. package/src/component/ButtonSubmit.tsx +335 -0
  121. package/src/component/Input.tsx +288 -0
  122. package/src/component/MultiSelectButtons.tsx +272 -0
  123. package/src/component/README.md +215 -0
  124. package/src/component/READMEVI.md +192 -0
  125. package/src/component/Rating.tsx +248 -0
  126. package/src/component/RatingNumber.tsx +421 -0
  127. package/src/component/Textarea.tsx +282 -0
  128. package/src/component/YesNoButtons.tsx +236 -0
  129. package/src/index.tsx +33 -0
  130. package/src/service/feedbackService.ts +108 -0
  131. package/src/utils/common.ts +241 -0
  132. package/src/utils/constants.ts +60 -0
  133. package/src/utils/index.ts +167 -0
  134. package/src/utils/networking.ts +134 -0
  135. package/fonts/AIAEverest-CondensedMedium.ttf +0 -0
  136. package/fonts/AIAEverest-Medium.ttf +0 -0
  137. package/fonts/AIAEverest-Regular.ttf +0 -0
  138. package/fonts/AIAEverestBold.ttf +0 -0
  139. package/fonts/OpenSans-Bold.ttf +0 -0
  140. package/fonts/OpenSans-Light.ttf +0 -0
  141. package/fonts/OpenSans-Regular.ttf +0 -0
  142. package/fonts/OpenSans-SemiBold.ttf +0 -0
  143. package/lib/commonjs/api/index.js +0 -1
  144. package/lib/commonjs/assets/index.js +0 -1
  145. package/lib/commonjs/component/confirmStep.js +0 -1
  146. package/lib/commonjs/component/confirmStepTablet.js +0 -1
  147. package/lib/commonjs/component/contentUI.js +0 -1
  148. package/lib/commonjs/component/contentUITablet.js +0 -1
  149. package/lib/commonjs/component/header.js +0 -1
  150. package/lib/commonjs/component/index.js +0 -1
  151. package/lib/commonjs/component/inlineSurvey.js +0 -1
  152. package/lib/commonjs/index.js +0 -1
  153. package/lib/commonjs/ui/ButtonSubmit.js +0 -1
  154. package/lib/commonjs/ui/Fineprint.js +0 -1
  155. package/lib/commonjs/ui/Instruction.js +0 -1
  156. package/lib/commonjs/ui/MultipleSelectInput.js +0 -1
  157. package/lib/commonjs/ui/Question.js +0 -1
  158. package/lib/commonjs/ui/RatingNumber.js +0 -1
  159. package/lib/commonjs/ui/RatingStar.js +0 -1
  160. package/lib/commonjs/ui/SecondaryQuestion.js +0 -1
  161. package/lib/commonjs/ui/TextArea.js +0 -1
  162. package/lib/commonjs/ui/index.js +0 -1
  163. package/lib/commonjs/utils/MyFunction.js +0 -1
  164. package/lib/commonjs/utils/NetworkHelper.js +0 -1
  165. package/lib/commonjs/utils/StatusBarHeight.js +0 -1
  166. 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
+ })